From ee664949c506fe00b2a59a59d5eb5b7c0bd6667a Mon Sep 17 00:00:00 2001 From: Karl Burtram Date: Sat, 16 Jul 2016 02:37:49 -0700 Subject: [PATCH] Additional code cleanup following initial check-in. Enable more ScriptFile code and hook in stub for error parsing. --- ServiceHost/Server/LanguageServer.cs | 236 +++++++++++++++++++++- ServiceHost/Session/EditorSession.cs | 4 - ServiceHost/Workspace/BufferPosition.cs | 1 - ServiceHost/Workspace/FilePosition.cs | 10 +- ServiceHost/Workspace/ScriptFile.cs | 48 +---- ServiceHost/Workspace/ScriptFileMarker.cs | 62 ------ ServiceHost/Workspace/Workspace.cs | 88 +------- 7 files changed, 231 insertions(+), 218 deletions(-) diff --git a/ServiceHost/Server/LanguageServer.cs b/ServiceHost/Server/LanguageServer.cs index 2f0f7963..7386d71f 100644 --- a/ServiceHost/Server/LanguageServer.cs +++ b/ServiceHost/Server/LanguageServer.cs @@ -10,11 +10,18 @@ using System.Threading.Tasks; using Microsoft.SqlTools.EditorServices.Utility; using System.Collections.Generic; using System.Text; +using System.Threading; +using System.Linq; namespace Microsoft.SqlTools.EditorServices.Protocol.Server { + /// + /// SQL Tools VS Code Language Server request handler + /// public class LanguageServer : LanguageServerBase { + private static CancellationTokenSource existingRequestCancellation; + private EditorSession editorSession; /// @@ -118,21 +125,20 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.Server ScriptFile changedFile = editorSession.Workspace.GetFile(fileUri); - // changedFile.ApplyChange( - // GetFileChangeDetails( - // textChange.Range.Value, - // textChange.Text)); + changedFile.ApplyChange( + GetFileChangeDetails( + textChange.Range.Value, + textChange.Text)); - // changedFiles.Add(changedFile); + changedFiles.Add(changedFile); } Logger.Write(LogLevel.Normal, msg.ToString()); - // // TODO: Get all recently edited files in the workspace - // this.RunScriptDiagnostics( - // changedFiles.ToArray(), - // editorSession, - // eventContext); + this.RunScriptDiagnostics( + changedFiles.ToArray(), + editorSession, + eventContext); return Task.FromResult(true); } @@ -231,6 +237,214 @@ namespace Microsoft.SqlTools.EditorServices.Protocol.Server { Logger.Write(LogLevel.Normal, "HandleWorkspaceSymbolRequest"); await Task.FromResult(true); - } + } + + 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) + // { + // // TODO: Catch a more specific exception! + // 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. + // TODO: Is there a better way to do this? + existingRequestCancellation = new CancellationTokenSource(); + Task.Factory.StartNew( + () => + DelayThenInvokeDiagnostics( + 750, + filesToAnalyze, + editorSession, + eventContext, + existingRequestCancellation.Token), + CancellationToken.None, + TaskCreationOptions.None, + TaskScheduler.Default); + + return Task.FromResult(true); + } + + + 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.AnalysisService != null) + // { + // Logger.Write(LogLevel.Verbose, "Analyzing script file: " + scriptFile.FilePath); + + // semanticMarkers = + // editorSession.AnalysisService.GetSemanticMarkers( + // scriptFile); + + // Logger.Write(LogLevel.Verbose, "Analysis complete."); + // } + // else + { + // Semantic markers aren't available if the AnalysisService + // isn't available + semanticMarkers = new ScriptFileMarker[0]; + // semanticMarkers = new ScriptFileMarker[1]; + // semanticMarkers[0] = new ScriptFileMarker() + // { + // Message = "Error message", + // Level = ScriptFileMarkerLevel.Error, + // ScriptRegion = new ScriptRegion() + // { + // File = scriptFile.FilePath, + // StartLineNumber = 2, + // StartColumnNumber = 2, + // StartOffset = 0, + // EndLineNumber = 4, + // EndColumnNumber = 10, + // EndOffset = 0 + // } + // }; + } + + await PublishScriptDiagnostics( + scriptFile, + semanticMarkers, + eventContext); + } + } + + 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() + }); + } + + 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 + } + } + }; + } + + 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; + } + } + + 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/ServiceHost/Session/EditorSession.cs b/ServiceHost/Session/EditorSession.cs index 4b6c9a49..310a44c8 100644 --- a/ServiceHost/Session/EditorSession.cs +++ b/ServiceHost/Session/EditorSession.cs @@ -75,10 +75,6 @@ namespace Microsoft.SqlTools.EditorServices #if false #region Properties - - - - /// /// Gets the LanguageService instance for this session. /// diff --git a/ServiceHost/Workspace/BufferPosition.cs b/ServiceHost/Workspace/BufferPosition.cs index 0a892f79..8f790d85 100644 --- a/ServiceHost/Workspace/BufferPosition.cs +++ b/ServiceHost/Workspace/BufferPosition.cs @@ -108,4 +108,3 @@ namespace Microsoft.SqlTools.EditorServices #endregion } } - diff --git a/ServiceHost/Workspace/FilePosition.cs b/ServiceHost/Workspace/FilePosition.cs index 89d915ee..2cb58745 100644 --- a/ServiceHost/Workspace/FilePosition.cs +++ b/ServiceHost/Workspace/FilePosition.cs @@ -11,14 +11,6 @@ namespace Microsoft.SqlTools.EditorServices /// public class FilePosition : BufferPosition { - public FilePosition( - ScriptFile scriptFile, - int line, - int column) - : base(line, column) - { - } -#if false #region Private Fields private ScriptFile scriptFile; @@ -112,7 +104,7 @@ namespace Microsoft.SqlTools.EditorServices } #endregion -#endif + } } diff --git a/ServiceHost/Workspace/ScriptFile.cs b/ServiceHost/Workspace/ScriptFile.cs index 7b041832..90d66244 100644 --- a/ServiceHost/Workspace/ScriptFile.cs +++ b/ServiceHost/Workspace/ScriptFile.cs @@ -8,8 +8,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -//using System.Management.Automation; -//using System.Management.Automation.Language; namespace Microsoft.SqlTools.EditorServices { @@ -18,34 +16,8 @@ namespace Microsoft.SqlTools.EditorServices /// public class ScriptFile { - public ScriptFile( - string filePath, - string clientFilePath, - TextReader textReader, - Version SqlToolsVersion) - { - } - - /// - /// Creates a new ScriptFile instance with the specified file contents. - /// - /// The path at which the script file resides. - /// The path which the client uses to identify the file. - /// The initial contents of the script file. - /// The version of SqlTools for which the script is being parsed. - public ScriptFile( - string filePath, - string clientFilePath, - string initialBuffer, - Version SqlToolsVersion) - { - } - - -#if false #region Private Fields - private Token[] scriptTokens; private Version SqlToolsVersion; #endregion @@ -121,23 +93,6 @@ namespace Microsoft.SqlTools.EditorServices private set; } - /// - /// Gets the ScriptBlockAst representing the parsed script contents. - /// - public ScriptBlockAst ScriptAst - { - get; - private set; - } - - /// - /// Gets the array of Tokens representing the parsed script contents. - /// - public Token[] ScriptTokens - { - get { return this.scriptTokens; } - } - /// /// Gets the array of filepaths dot sourced in this ScriptFile /// @@ -502,6 +457,7 @@ namespace Microsoft.SqlTools.EditorServices /// private void ParseFileContents() { +#if false ParseError[] parseErrors = null; // First, get the updated file range @@ -574,9 +530,9 @@ namespace Microsoft.SqlTools.EditorServices //Get all dot sourced referenced files and store them this.ReferencedFiles = AstOperations.FindDotSourcedIncludes(this.ScriptAst); +#endif } #endregion -#endif } } diff --git a/ServiceHost/Workspace/ScriptFileMarker.cs b/ServiceHost/Workspace/ScriptFileMarker.cs index fbe944d6..87c2576c 100644 --- a/ServiceHost/Workspace/ScriptFileMarker.cs +++ b/ServiceHost/Workspace/ScriptFileMarker.cs @@ -3,14 +3,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.SqlTools.EditorServices.Utility; -using System; -//using System.Management.Automation.Language; - -#if ScriptAnalyzer -using Microsoft.Windows.SqlTools.ScriptAnalyzer.Generic; -#endif - namespace Microsoft.SqlTools.EditorServices { /// @@ -59,60 +51,6 @@ namespace Microsoft.SqlTools.EditorServices public ScriptRegion ScriptRegion { get; set; } #endregion - - #region Public Methods - -#if false - internal static ScriptFileMarker FromParseError( - ParseError parseError) - { - Validate.IsNotNull("parseError", parseError); - - return new ScriptFileMarker - { - Message = parseError.Message, - Level = ScriptFileMarkerLevel.Error, - ScriptRegion = ScriptRegion.Create(parseError.Extent) - }; - } -#endif - -#if ScriptAnalyzer - internal static ScriptFileMarker FromDiagnosticRecord( - DiagnosticRecord diagnosticRecord) - { - Validate.IsNotNull("diagnosticRecord", diagnosticRecord); - - return new ScriptFileMarker - { - Message = diagnosticRecord.Message, - Level = GetMarkerLevelFromDiagnosticSeverity(diagnosticRecord.Severity), - ScriptRegion = ScriptRegion.Create(diagnosticRecord.Extent) - }; - } - - private static ScriptFileMarkerLevel GetMarkerLevelFromDiagnosticSeverity( - DiagnosticSeverity diagnosticSeverity) - { - switch (diagnosticSeverity) - { - case DiagnosticSeverity.Information: - return ScriptFileMarkerLevel.Information; - case DiagnosticSeverity.Warning: - return ScriptFileMarkerLevel.Warning; - case DiagnosticSeverity.Error: - return ScriptFileMarkerLevel.Error; - default: - throw new ArgumentException( - string.Format( - "The provided DiagnosticSeverity value '{0}' is unknown.", - diagnosticSeverity), - "diagnosticSeverity"); - } - } -#endif - - #endregion } } diff --git a/ServiceHost/Workspace/Workspace.cs b/ServiceHost/Workspace/Workspace.cs index 6188a806..39e1d70f 100644 --- a/ServiceHost/Workspace/Workspace.cs +++ b/ServiceHost/Workspace/Workspace.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; +using System.Linq; namespace Microsoft.SqlTools.EditorServices { @@ -149,15 +150,7 @@ namespace Microsoft.SqlTools.EditorServices return Regex.Replace(path, @"`(?=[ \[\]])", ""); } - #endregion - -#if false - - - #region Public Methods - - - /// + /// /// Gets a new ScriptFile instance which is identified by the given file /// path and initially contains the given buffer contents. /// @@ -211,80 +204,6 @@ namespace Microsoft.SqlTools.EditorServices this.workspaceFiles.Remove(scriptFile.Id); } - /// - /// Gets all file references by recursively searching - /// through referenced files in a scriptfile - /// - /// Contains the details and contents of an open script file - /// A scriptfile array where the first file - /// in the array is the "root file" of the search - public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile) - { - Dictionary referencedScriptFiles = new Dictionary(); - List expandedReferences = new List(); - - // add original file so it's not searched for, then find all file references - referencedScriptFiles.Add(scriptFile.Id, scriptFile); - RecursivelyFindReferences(scriptFile, referencedScriptFiles); - - // remove original file from referened file and add it as the first element of the - // expanded referenced list to maintain order so the original file is always first in the list - referencedScriptFiles.Remove(scriptFile.Id); - expandedReferences.Add(scriptFile); - - if (referencedScriptFiles.Count > 0) - { - expandedReferences.AddRange(referencedScriptFiles.Values); - } - - return expandedReferences.ToArray(); - } - - #endregion - - #region Private Methods - - /// - /// Recusrively searches through referencedFiles in scriptFiles - /// and builds a Dictonary of the file references - /// - /// Details an contents of "root" script file - /// A Dictionary of referenced script files - private void RecursivelyFindReferences( - ScriptFile scriptFile, - Dictionary referencedScriptFiles) - { - // Get the base path of the current script for use in resolving relative paths - string baseFilePath = - GetBaseFilePath( - scriptFile.FilePath); - - ScriptFile referencedFile; - foreach (string referencedFileName in scriptFile.ReferencedFiles) - { - string resolvedScriptPath = - this.ResolveRelativeScriptPath( - baseFilePath, - referencedFileName); - - // Make sure file exists before trying to get the file - if (File.Exists(resolvedScriptPath)) - { - // Get the referenced file if it's not already in referencedScriptFiles - referencedFile = this.GetFile(resolvedScriptPath); - - // Normalize the resolved script path and add it to the - // referenced files list if it isn't there already - resolvedScriptPath = resolvedScriptPath.ToLower(); - if (!referencedScriptFiles.ContainsKey(resolvedScriptPath)) - { - referencedScriptFiles.Add(resolvedScriptPath, referencedFile); - RecursivelyFindReferences(referencedFile, referencedScriptFiles); - } - } - } - } - private string GetBaseFilePath(string filePath) { if (IsPathInMemory(filePath)) @@ -324,7 +243,6 @@ namespace Microsoft.SqlTools.EditorServices return combinedPath; } - #endregion -#endif + #endregion } }