diff --git a/src/ServiceHost/LanguageSupport/AutoCompleteService.cs b/src/ServiceHost/LanguageSupport/AutoCompleteService.cs deleted file mode 100644 index 2cf98484..00000000 --- a/src/ServiceHost/LanguageSupport/AutoCompleteService.cs +++ /dev/null @@ -1,112 +0,0 @@ -// -// 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.EditorServices.Connection; -using Microsoft.SqlTools.EditorServices.Protocol.LanguageServer; -using System; -using System.Collections.Generic; - -namespace Microsoft.SqlTools.LanguageSupport -{ - /// - /// Main class for Autocomplete functionality - /// - public class AutoCompleteService - { - /// - /// Singleton service instance - /// - private static Lazy instance - = new Lazy(() => new AutoCompleteService()); - - /// - /// The current autocomplete candidate list - /// - private IEnumerable autoCompleteList; - - /// - /// Gets the current autocomplete candidate list - /// - public IEnumerable AutoCompleteList - { - get - { - return this.autoCompleteList; - } - } - - /// - /// Gets the singleton service instance - /// - public static AutoCompleteService Instance - { - get - { - return instance.Value; - } - } - - /// - /// Update the cached autocomplete candidate list when the user connects to a database - /// - /// - public void UpdateAutoCompleteCache(ISqlConnection connection) - { - this.autoCompleteList = connection.GetServerObjects(); - } - - /// - /// Return the completion item list for the current text position - /// - /// - public CompletionItem[] GetCompletionItems(TextDocumentPosition textDocumentPosition) - { - var completions = new List(); - - int i = 0; - - // the completion list will be null is user not connected to server - if (this.AutoCompleteList != null) - { - foreach (var autoCompleteItem in this.AutoCompleteList) - { - // convert the completion item candidates into CompletionItems - completions.Add(new CompletionItem() - { - Label = autoCompleteItem, - Kind = CompletionItemKind.Keyword, - Detail = autoCompleteItem + " details", - Documentation = autoCompleteItem + " documentation", - TextEdit = new TextEdit - { - NewText = autoCompleteItem, - Range = new Range - { - Start = new Position - { - Line = textDocumentPosition.Position.Line, - Character = textDocumentPosition.Position.Character - }, - End = new Position - { - Line = textDocumentPosition.Position.Line, - Character = textDocumentPosition.Position.Character + 5 - } - } - } - }); - - // only show 50 items - if (++i == 50) - { - break; - } - } - } - return completions.ToArray(); - } - - } -} diff --git a/src/ServiceHost/LanguageSupport/LanguageService.cs b/src/ServiceHost/LanguageSupport/LanguageService.cs deleted file mode 100644 index ff4f4a65..00000000 --- a/src/ServiceHost/LanguageSupport/LanguageService.cs +++ /dev/null @@ -1,78 +0,0 @@ -// -// 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.EditorServices; -using Microsoft.SqlTools.EditorServices.Session; -using Microsoft.SqlServer.Management.SqlParser.Parser; -using System.Collections.Generic; - -namespace Microsoft.SqlTools.LanguageSupport -{ - /// - /// Main class for Language Service functionality - /// - public class LanguageService - { - /// - /// The cached parse result from previous incremental parse - /// - private ParseResult prevParseResult; - - /// - /// Gets or sets the current SQL Tools context - /// - /// - private SqlToolsContext Context { get; set; } - - /// - /// Constructor for the Language Service class - /// - /// - public LanguageService(SqlToolsContext context) - { - this.Context = context; - } - - /// - /// Gets a list of semantic diagnostic marks for the provided script file - /// - /// - public ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile) - { - // parse current SQL file contents to retrieve a list of errors - ParseOptions parseOptions = new ParseOptions(); - ParseResult parseResult = Parser.IncrementalParse( - scriptFile.Contents, - prevParseResult, - parseOptions); - - // save previous result for next incremental parse - this.prevParseResult = parseResult; - - // build a list of SQL script file markers from the errors - List markers = new List(); - foreach (var error in parseResult.Errors) - { - markers.Add(new ScriptFileMarker() - { - Message = error.Message, - Level = ScriptFileMarkerLevel.Error, - ScriptRegion = new ScriptRegion() - { - File = scriptFile.FilePath, - StartLineNumber = error.Start.LineNumber, - StartColumnNumber = error.Start.ColumnNumber, - StartOffset = 0, - EndLineNumber = error.End.LineNumber, - EndColumnNumber = error.End.ColumnNumber, - EndOffset = 0 - } - }); - } - - return markers.ToArray(); - } - } -} diff --git a/src/ServiceHost/Server/LanguageServer.cs b/src/ServiceHost/Server/LanguageServer.cs deleted file mode 100644 index c2952f92..00000000 --- a/src/ServiceHost/Server/LanguageServer.cs +++ /dev/null @@ -1,583 +0,0 @@ -// -// 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.EditorServices.Protocol.LanguageServer; -using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol; -using Microsoft.SqlTools.EditorServices.Protocol.MessageProtocol.Channel; -using Microsoft.SqlTools.EditorServices.Session; -using System.Threading.Tasks; -using Microsoft.SqlTools.EditorServices.Utility; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Linq; -using System; -using Microsoft.SqlTools.EditorServices.Connection; -using Microsoft.SqlTools.LanguageSupport; - -namespace Microsoft.SqlTools.EditorServices.Protocol.Server -{ - /// - /// SQL Tools VS Code Language Server request handler - /// - public class LanguageServer : LanguageServerBase - { - private static CancellationTokenSource existingRequestCancellation; - - private LanguageServerSettings currentSettings = new LanguageServerSettings(); - - private EditorSession editorSession; - - /// - /// Provides details about the host application. - /// - public LanguageServer(HostDetails hostDetails, ProfilePaths profilePaths) - : base(new StdioServerChannel()) - { - this.editorSession = new EditorSession(); - this.editorSession.StartSession(hostDetails, profilePaths); - } - - /// - /// Initialize the VS Code request/response callbacks - /// - protected override void Initialize() - { - // Register all supported message types - this.SetRequestHandler(InitializeRequest.Type, this.HandleInitializeRequest); - this.SetEventHandler(DidChangeTextDocumentNotification.Type, this.HandleDidChangeTextDocumentNotification); - this.SetEventHandler(DidOpenTextDocumentNotification.Type, this.HandleDidOpenTextDocumentNotification); - this.SetEventHandler(DidCloseTextDocumentNotification.Type, this.HandleDidCloseTextDocumentNotification); - this.SetEventHandler(DidChangeConfigurationNotification.Type, this.HandleDidChangeConfigurationNotification); - - this.SetRequestHandler(DefinitionRequest.Type, this.HandleDefinitionRequest); - this.SetRequestHandler(ReferencesRequest.Type, this.HandleReferencesRequest); - this.SetRequestHandler(CompletionRequest.Type, this.HandleCompletionRequest); - this.SetRequestHandler(CompletionResolveRequest.Type, this.HandleCompletionResolveRequest); - this.SetRequestHandler(SignatureHelpRequest.Type, this.HandleSignatureHelpRequest); - this.SetRequestHandler(DocumentHighlightRequest.Type, this.HandleDocumentHighlightRequest); - this.SetRequestHandler(HoverRequest.Type, this.HandleHoverRequest); - this.SetRequestHandler(DocumentSymbolRequest.Type, this.HandleDocumentSymbolRequest); - this.SetRequestHandler(WorkspaceSymbolRequest.Type, this.HandleWorkspaceSymbolRequest); - - this.SetRequestHandler(ConnectionRequest.Type, this.HandleConnectRequest); - - // register an OnConnection callback - ConnectionService.Instance.RegisterOnConnectionTask(OnConnection); - } - - /// - /// Callback for when a user connection is done processing - /// - /// - public Task OnConnection(ISqlConnection sqlConnection) - { - AutoCompleteService.Instance.UpdateAutoCompleteCache(sqlConnection); - return Task.FromResult(true); - } - - /// - /// Handles the shutdown event for the Language Server - /// - protected override async Task Shutdown() - { - Logger.Write(LogLevel.Normal, "Language service is shutting down..."); - - if (this.editorSession != null) - { - this.editorSession.Dispose(); - this.editorSession = null; - } - - await Task.FromResult(true); - } - - /// - /// Handles the initialization request - /// - /// - /// - /// - protected async Task HandleInitializeRequest( - InitializeRequest initializeParams, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleDidChangeTextDocumentNotification"); - - // Grab the workspace path from the parameters - editorSession.Workspace.WorkspacePath = initializeParams.RootPath; - - await requestContext.SendResult( - new InitializeResult - { - Capabilities = new ServerCapabilities - { - TextDocumentSync = TextDocumentSyncKind.Incremental, - DefinitionProvider = true, - ReferencesProvider = true, - DocumentHighlightProvider = true, - DocumentSymbolProvider = true, - WorkspaceSymbolProvider = true, - HoverProvider = true, - CompletionProvider = new CompletionOptions - { - ResolveProvider = true, - TriggerCharacters = new string[] { ".", "-", ":", "\\" } - }, - SignatureHelpProvider = new SignatureHelpOptions - { - TriggerCharacters = new string[] { " " } // TODO: Other characters here? - } - } - }); - } - - /// - /// Handles text document change events - /// - /// - /// - /// - protected async Task HandleDidChangeTextDocumentNotification( - DidChangeTextDocumentParams textChangeParams, - EventContext eventContext) - { - StringBuilder msg = new StringBuilder(); - msg.Append("HandleDidChangeTextDocumentNotification"); - List changedFiles = new List(); - - // A text change notification can batch multiple change requests - foreach (var textChange in textChangeParams.ContentChanges) - { - string fileUri = textChangeParams.Uri ?? textChangeParams.TextDocument.Uri; - msg.AppendLine(); - msg.Append(" File: "); - msg.Append(fileUri); - - ScriptFile changedFile = editorSession.Workspace.GetFile(fileUri); - - changedFile.ApplyChange( - GetFileChangeDetails( - textChange.Range.Value, - textChange.Text)); - - changedFiles.Add(changedFile); - } - - Logger.Write(LogLevel.Verbose, msg.ToString()); - - await this.RunScriptDiagnostics( - changedFiles.ToArray(), - editorSession, - eventContext); - - await Task.FromResult(true); - } - - /// - /// Handle the file open notification - /// - /// - /// - protected Task HandleDidOpenTextDocumentNotification( - DidOpenTextDocumentNotification openParams, - EventContext eventContext) - { - Logger.Write(LogLevel.Verbose, "HandleDidOpenTextDocumentNotification"); - - // read the SQL file contents into the ScriptFile - ScriptFile openedFile = - editorSession.Workspace.GetFileBuffer( - openParams.Uri, - openParams.Text); - - // run diagnostics on the opened file - this.RunScriptDiagnostics( - new ScriptFile[] { openedFile }, - editorSession, - eventContext); - - return Task.FromResult(true); - } - - /// - /// Handle the close document notication - /// - /// - /// - protected Task HandleDidCloseTextDocumentNotification( - TextDocumentIdentifier closeParams, - EventContext eventContext) - { - Logger.Write(LogLevel.Verbose, "HandleDidCloseTextDocumentNotification"); - return Task.FromResult(true); - } - - /// - /// Handles the configuration change event - /// - /// - /// - protected async Task HandleDidChangeConfigurationNotification( - DidChangeConfigurationParams configChangeParams, - EventContext eventContext) - { - Logger.Write(LogLevel.Verbose, "HandleDidChangeConfigurationNotification"); - - bool oldLoadProfiles = this.currentSettings.EnableProfileLoading; - bool oldScriptAnalysisEnabled = - this.currentSettings.ScriptAnalysis.Enable.HasValue; - string oldScriptAnalysisSettingsPath = - this.currentSettings.ScriptAnalysis.SettingsPath; - - this.currentSettings.Update( - configChangeParams.Settings.SqlTools, - this.editorSession.Workspace.WorkspacePath); - - // If script analysis settings have changed we need to clear & possibly update the current diagnostic records. - if ((oldScriptAnalysisEnabled != this.currentSettings.ScriptAnalysis.Enable)) - { - // If the user just turned off script analysis or changed the settings path, send a diagnostics - // event to clear the analysis markers that they already have. - if (!this.currentSettings.ScriptAnalysis.Enable.Value) - { - ScriptFileMarker[] emptyAnalysisDiagnostics = new ScriptFileMarker[0]; - - foreach (var scriptFile in editorSession.Workspace.GetOpenedFiles()) - { - await PublishScriptDiagnostics( - scriptFile, - emptyAnalysisDiagnostics, - eventContext); - } - } - else - { - await this.RunScriptDiagnostics( - this.editorSession.Workspace.GetOpenedFiles(), - this.editorSession, - eventContext); - } - } - - await Task.FromResult(true); - } - - protected async Task HandleDefinitionRequest( - TextDocumentPosition textDocumentPosition, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleDefinitionRequest"); - await Task.FromResult(true); - } - - protected async Task HandleReferencesRequest( - ReferencesParams referencesParams, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleReferencesRequest"); - await Task.FromResult(true); - } - - /// - /// Handles the completion list request - /// - /// - /// - protected async Task HandleCompletionRequest( - TextDocumentPosition textDocumentPosition, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleCompletionRequest"); - - // get teh current list of completion items and return to client - var completionItems = AutoCompleteService.Instance.GetCompletionItems(textDocumentPosition); - await requestContext.SendResult(completionItems); - } - - protected async Task HandleCompletionResolveRequest( - CompletionItem completionItem, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleCompletionResolveRequest"); - await Task.FromResult(true); - } - - protected async Task HandleSignatureHelpRequest( - TextDocumentPosition textDocumentPosition, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleSignatureHelpRequest"); - await Task.FromResult(true); - } - - protected async Task HandleDocumentHighlightRequest( - TextDocumentPosition textDocumentPosition, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleDocumentHighlightRequest"); - await Task.FromResult(true); - } - - protected async Task HandleHoverRequest( - TextDocumentPosition textDocumentPosition, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleHoverRequest"); - await Task.FromResult(true); - } - - protected async Task HandleDocumentSymbolRequest( - TextDocumentIdentifier textDocumentIdentifier, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleDocumentSymbolRequest"); - await Task.FromResult(true); - } - - protected async Task HandleWorkspaceSymbolRequest( - WorkspaceSymbolParams workspaceSymbolParams, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleWorkspaceSymbolRequest"); - await Task.FromResult(true); - } - - /// - /// Handle new connection requests - /// - /// - /// - /// - protected async Task HandleConnectRequest( - ConnectionDetails connectionDetails, - RequestContext requestContext) - { - Logger.Write(LogLevel.Verbose, "HandleConnectRequest"); - - // open connection base on request details - ConnectionResult result = ConnectionService.Instance.Connect(connectionDetails); - - await requestContext.SendResult(result); - } - - /// - /// Runs script diagnostics on changed files - /// - /// - /// - /// - private Task RunScriptDiagnostics( - ScriptFile[] filesToAnalyze, - EditorSession editorSession, - EventContext eventContext) - { - if (!this.currentSettings.ScriptAnalysis.Enable.Value) - { - // If the user has disabled script analysis, skip it entirely - return Task.FromResult(true); - } - - // If there's an existing task, attempt to cancel it - try - { - if (existingRequestCancellation != null) - { - // Try to cancel the request - existingRequestCancellation.Cancel(); - - // If cancellation didn't throw an exception, - // clean up the existing token - existingRequestCancellation.Dispose(); - existingRequestCancellation = null; - } - } - catch (Exception e) - { - Logger.Write( - LogLevel.Error, - string.Format( - "Exception while cancelling analysis task:\n\n{0}", - e.ToString())); - - TaskCompletionSource cancelTask = new TaskCompletionSource(); - cancelTask.SetCanceled(); - return cancelTask.Task; - } - - // Create a fresh cancellation token and then start the task. - // We create this on a different TaskScheduler so that we - // don't block the main message loop thread. - existingRequestCancellation = new CancellationTokenSource(); - Task.Factory.StartNew( - () => - DelayThenInvokeDiagnostics( - 750, - filesToAnalyze, - editorSession, - eventContext, - existingRequestCancellation.Token), - CancellationToken.None, - TaskCreationOptions.None, - TaskScheduler.Default); - - return Task.FromResult(true); - } - - /// - /// Actually run the script diagnostics after waiting for some small delay - /// - /// - /// - /// - /// - /// - private static async Task DelayThenInvokeDiagnostics( - int delayMilliseconds, - ScriptFile[] filesToAnalyze, - EditorSession editorSession, - EventContext eventContext, - CancellationToken cancellationToken) - { - // First of all, wait for the desired delay period before - // analyzing the provided list of files - try - { - await Task.Delay(delayMilliseconds, cancellationToken); - } - catch (TaskCanceledException) - { - // If the task is cancelled, exit directly - return; - } - - // If we've made it past the delay period then we don't care - // about the cancellation token anymore. This could happen - // when the user stops typing for long enough that the delay - // period ends but then starts typing while analysis is going - // on. It makes sense to send back the results from the first - // delay period while the second one is ticking away. - - // Get the requested files - foreach (ScriptFile scriptFile in filesToAnalyze) - { - ScriptFileMarker[] semanticMarkers = null; - if (editorSession.LanguageService != null) - { - Logger.Write(LogLevel.Verbose, "Analyzing script file: " + scriptFile.FilePath); - semanticMarkers = editorSession.LanguageService.GetSemanticMarkers(scriptFile); - Logger.Write(LogLevel.Verbose, "Analysis complete."); - } - else - { - // Semantic markers aren't available if the AnalysisService - // isn't available - semanticMarkers = new ScriptFileMarker[0]; - } - - await PublishScriptDiagnostics( - scriptFile, - semanticMarkers, - eventContext); - } - } - - /// - /// Send the diagnostic results back to the host application - /// - /// - /// - /// - private static async Task PublishScriptDiagnostics( - ScriptFile scriptFile, - ScriptFileMarker[] semanticMarkers, - EventContext eventContext) - { - var allMarkers = scriptFile.SyntaxMarkers != null - ? scriptFile.SyntaxMarkers.Concat(semanticMarkers) - : semanticMarkers; - - // Always send syntax and semantic errors. We want to - // make sure no out-of-date markers are being displayed. - await eventContext.SendEvent( - PublishDiagnosticsNotification.Type, - new PublishDiagnosticsNotification - { - Uri = scriptFile.ClientFilePath, - Diagnostics = - allMarkers - .Select(GetDiagnosticFromMarker) - .ToArray() - }); - } - - /// - /// Convert a ScriptFileMarker to a Diagnostic that is Language Service compatible - /// - /// - /// - private static Diagnostic GetDiagnosticFromMarker(ScriptFileMarker scriptFileMarker) - { - return new Diagnostic - { - Severity = MapDiagnosticSeverity(scriptFileMarker.Level), - Message = scriptFileMarker.Message, - Range = new Range - { - // TODO: What offsets should I use? - Start = new Position - { - Line = scriptFileMarker.ScriptRegion.StartLineNumber - 1, - Character = scriptFileMarker.ScriptRegion.StartColumnNumber - 1 - }, - End = new Position - { - Line = scriptFileMarker.ScriptRegion.EndLineNumber - 1, - Character = scriptFileMarker.ScriptRegion.EndColumnNumber - 1 - } - } - }; - } - - /// - /// Map ScriptFileMarker severity to Diagnostic severity - /// - /// - private static DiagnosticSeverity MapDiagnosticSeverity(ScriptFileMarkerLevel markerLevel) - { - switch (markerLevel) - { - case ScriptFileMarkerLevel.Error: - return DiagnosticSeverity.Error; - - case ScriptFileMarkerLevel.Warning: - return DiagnosticSeverity.Warning; - - case ScriptFileMarkerLevel.Information: - return DiagnosticSeverity.Information; - - default: - return DiagnosticSeverity.Error; - } - } - - /// - /// Switch from 0-based offsets to 1 based offsets - /// - /// - /// - private static FileChange GetFileChangeDetails(Range changeRange, string insertString) - { - // The protocol's positions are zero-based so add 1 to all offsets - return new FileChange - { - InsertString = insertString, - Line = changeRange.Start.Line + 1, - Offset = changeRange.Start.Character + 1, - EndLine = changeRange.End.Line + 1, - EndOffset = changeRange.End.Character + 1 - }; - } - } -} diff --git a/test/ServiceHost.Test/Utility/TestObjects.cs b/test/ServiceHost.Test/Utility/TestObjects.cs index 4e086279..c506d600 100644 --- a/test/ServiceHost.Test/Utility/TestObjects.cs +++ b/test/ServiceHost.Test/Utility/TestObjects.cs @@ -53,7 +53,7 @@ namespace Microsoft.SqlTools.Test.Utility /// public static LanguageService GetTestLanguageService() { - return new LanguageService(new SqlToolsContext(null, null)); + return new LanguageService(); } ///