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
}
}