Adding completion extension loading and execution logic (#833)

* Adding ICompletionExtension interface

* Adding extension loading and execution logic

* Fixing compilation error in VS 2017

* Using MEF for completion extension discovery

* using await on GetCompletionItems

* Adding an integration test for completion extension and update the completion extension interface

* Update the completion extension test

* Fix issues based on review comments

* Remove try/cache based on review comments, fix a integration test.

* More changes based on review comments

* Fixing SendResult logic for completion extension loading

* Only load completion extension from the assembly passed in, add more comments in the test

* Adding right assert messages in the test.

* More fixes based on review comments

* Dropping ICompletionExtensionProvider, load assembly only if it's loaded at the first time or updated since last load.

* Fix based on the latest review comments

* Adding missing TSQL functions in default completion list

* Update jsonrpc documentation for completion/extLoad
This commit is contained in:
Shengyu Fu
2019-07-19 12:04:03 -07:00
committed by Karl Burtram
parent e3ec6eb739
commit e1b9890f5c
18 changed files with 1000 additions and 354 deletions

View File

@@ -29,305 +29,394 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
private static CompletionItem[] emptyCompletionList = new CompletionItem[0];
private static readonly string[] DefaultCompletionText = new string[]
{
"all",
"alter",
"and",
"apply",
"as",
"asc",
"at",
"backup",
"begin",
"between",
"binary",
"bit",
"break",
"bulk",
"by",
"call",
"cascade",
"case",
"catch",
"char",
"character",
"check",
"checkpoint",
"close",
"clustered",
"column",
"columnstore",
"commit",
"connect",
"constraint",
"continue",
"create",
"cross",
"current_date",
"cursor",
"cursor_close_on_commit",
"cursor_default",
"data",
"data_compression",
"database",
"date",
"datetime",
"datetime2",
"days",
"dbcc",
"dec",
"decimal",
"declare",
"default",
"delete",
"deny",
"desc",
"description",
"disabled",
"disk",
"distinct",
"double",
"drop",
"drop_existing",
"dump",
"dynamic",
"else",
"enable",
"encrypted",
"end",
"end-exec",
"except",
"exec",
"execute",
"exists",
"exit",
"external",
"fast_forward",
"fetch",
"file",
"filegroup",
"filename",
"filestream",
"filter",
"first",
"float",
"for",
"foreign",
"from",
"full",
"function",
"geography",
"get",
"global",
"go",
"goto",
"grant",
"group",
"hash",
"hashed",
"having",
"hidden",
"hierarchyid",
"holdlock",
"hours",
"identity",
"identitycol",
"if",
"image",
"immediate",
"include",
"index",
"inner",
"insert",
"instead",
"int",
"integer",
"intersect",
"into",
"isolation",
"join",
"json",
"key",
"language",
"last",
"left",
"level",
"lineno",
"load",
"local",
"locate",
"location",
"login",
"masked",
"maxdop",
"merge",
"message",
"modify",
"move",
"namespace",
"native_compilation",
"nchar",
"next",
"no",
"nocheck",
"nocount",
"nonclustered",
"none",
"norecompute",
"not",
"now",
"null",
"numeric",
"nvarchar",
"object",
"of",
"off",
"offsets",
"on",
"online",
"open",
"openrowset",
"openxml",
"option",
"or",
"order",
"out",
"outer",
"output",
"over",
"owner",
"partial",
"partition",
"password",
"path",
"percent",
"percentage",
"period",
"persisted",
"plan",
"policy",
"precision",
"predicate",
"primary",
"print",
"prior",
"proc",
"procedure",
"public",
"query_store",
"quoted_identifier",
"raiserror",
"range",
"raw",
"read",
"read_committed_snapshot",
"read_only",
"read_write",
"readonly",
"readtext",
"real",
"rebuild",
"receive",
"reconfigure",
"recovery",
"recursive",
"recursive_triggers",
"references",
"relative",
"remove",
"reorganize",
"required",
"restart",
"restore",
"restrict",
"resume",
"return",
"returns",
"revert",
"revoke",
"rollback",
"rollup",
"row",
"rowcount",
"rowguidcol",
"rows",
"rule",
"sample",
"save",
"schema",
"schemabinding",
"scoped",
"scroll",
"secondary",
"security",
"select",
"send",
"sent",
"sequence",
"server",
"session",
"set",
"sets",
"setuser",
"simple",
"smallint",
"smallmoney",
"snapshot",
"sql",
"standard",
"start",
"started",
"state",
"statement",
"static",
"statistics",
"statistics_norecompute",
"status",
"stopped",
"sysname",
"system",
"system_time",
"table",
"take",
"target",
"then",
"throw",
"time",
"timestamp",
"tinyint",
"to",
"top",
"tran",
"transaction",
"trigger",
"truncate",
"try",
"tsql",
"type",
"uncommitted",
"union",
"unique",
"uniqueidentifier",
"update",
"updatetext",
"use",
"user",
"using",
"value",
"values",
"varchar",
"version",
"view",
"waitfor",
"when",
"where",
"while",
"with",
"within",
"without",
"writetext",
"xact_abort",
"xml",
{
"abs",
"acos",
"all",
"alter",
"and",
"apply",
"approx_count_distinct",
"as",
"asc",
"ascii",
"asin",
"at",
"atan",
"atn2",
"avg",
"backup",
"begin",
"between",
"binary",
"bit",
"break",
"bulk",
"by",
"call",
"cascade",
"case",
"cast",
"catch",
"ceiling",
"char",
"character",
"charindex",
"check",
"checkpoint",
"checksum_agg",
"close",
"clustered",
"coalesce",
"column",
"columnstore",
"commit",
"concat",
"concat_ws",
"connect",
"constraint",
"continue",
"convert",
"cos",
"cot",
"count",
"count_big",
"create",
"cross",
"current_date",
"current_timestamp",
"current_user",
"cursor",
"cursor_close_on_commit",
"cursor_default",
"data",
"data_compression",
"database",
"datalength",
"date",
"dateadd",
"datediff",
"datefromparts",
"datename",
"datepart",
"datetime",
"datetime2",
"day",
"days",
"dbcc",
"dec",
"decimal",
"declare",
"default",
"degrees",
"delete",
"deny",
"desc",
"description",
"difference",
"disabled",
"disk",
"distinct",
"double",
"drop",
"drop_existing",
"dump",
"dynamic",
"else",
"enable",
"encrypted",
"end",
"end-exec",
"except",
"exec",
"execute",
"exists",
"exit",
"exp",
"external",
"fast_forward",
"fetch",
"file",
"filegroup",
"filename",
"filestream",
"filter",
"first",
"float",
"floor",
"for",
"foreign",
"format",
"from",
"full",
"function",
"geography",
"get",
"getdate",
"getutcdate",
"global",
"go",
"goto",
"grant",
"group",
"grouping",
"grouping_id",
"hash",
"hashed",
"having",
"hidden",
"hierarchyid",
"holdlock",
"hours",
"identity",
"identitycol",
"if",
"iif",
"image",
"immediate",
"include",
"index",
"inner",
"insert",
"instead",
"int",
"integer",
"intersect",
"into",
"isdate",
"isnull",
"isnumeric",
"isolation",
"join",
"json",
"key",
"language",
"last",
"left",
"len",
"level",
"lineno",
"load",
"local",
"locate",
"location",
"log",
"log10",
"login",
"lower",
"ltrim",
"masked",
"max",
"maxdop",
"merge",
"message",
"min",
"modify",
"month",
"move",
"namespace",
"native_compilation",
"nchar",
"next",
"no",
"nocheck",
"nocount",
"nonclustered",
"none",
"norecompute",
"not",
"now",
"null",
"nullif",
"numeric",
"nvarchar",
"object",
"of",
"off",
"offsets",
"on",
"online",
"open",
"openrowset",
"openxml",
"option",
"or",
"order",
"out",
"outer",
"output",
"over",
"owner",
"partial",
"partition",
"password",
"path",
"patindex",
"percent",
"percentage",
"period",
"persisted",
"pi",
"plan",
"policy",
"power",
"precision",
"predicate",
"primary",
"print",
"prior",
"proc",
"procedure",
"public",
"query_store",
"quoted_identifier",
"quotename",
"radians",
"raiserror",
"rand",
"range",
"raw",
"read",
"read_committed_snapshot",
"read_only",
"read_write",
"readonly",
"readtext",
"real",
"rebuild",
"receive",
"reconfigure",
"recovery",
"recursive",
"recursive_triggers",
"references",
"relative",
"remove",
"reorganize",
"replace",
"replicate",
"required",
"restart",
"restore",
"restrict",
"resume",
"return",
"returns",
"reverse",
"revert",
"revoke",
"right",
"rollback",
"rollup",
"round",
"row",
"rowcount",
"rowguidcol",
"rows",
"rtrim",
"rule",
"sample",
"save",
"schema",
"schemabinding",
"scoped",
"scroll",
"secondary",
"security",
"select",
"send",
"sent",
"sequence",
"server",
"session",
"session_user",
"sessionproperty",
"set",
"sets",
"setuser",
"sign",
"simple",
"sin",
"smallint",
"smallmoney",
"snapshot",
"soundex",
"space",
"sql",
"sqrt",
"square",
"standard",
"start",
"started",
"state",
"statement",
"static",
"statistics",
"statistics_norecompute",
"status",
"stdev",
"stdevp",
"stopped",
"str",
"string_agg",
"stuff",
"substring",
"sum",
"sysdatetime",
"sysname",
"system",
"system_time",
"system_user",
"table",
"take",
"tan",
"target",
"then",
"throw",
"time",
"timestamp",
"tinyint",
"to",
"top",
"tran",
"transaction",
"translate",
"trigger",
"trim",
"truncate",
"try",
"tsql",
"type",
"uncommitted",
"unicode",
"union",
"unique",
"uniqueidentifier",
"update",
"updatetext",
"upper",
"use",
"user",
"user_name",
"using",
"value",
"values",
"var",
"varchar",
"varp",
"version",
"view",
"waitfor",
"when",
"where",
"while",
"with",
"within",
"without",
"writetext",
"xact_abort",
"xml",
"year",
};
/// <summary>
@@ -367,7 +456,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
int startColumn = scriptDocumentInfo.StartColumn;
int endColumn = scriptDocumentInfo.EndColumn;
string tokenText = scriptDocumentInfo.TokenText;
// determine how many default completion items there will be
// determine how many default completion items there will be
int listSize = DefaultCompletionText.Length;
if (!string.IsNullOrWhiteSpace(tokenText))
{
@@ -392,14 +481,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
int completionItemIndex = 0;
foreach (var completionText in DefaultCompletionText)
{
// add item to list if the tokenText is null (meaning return whole list)
// add item to list if the tokenText is null (meaning return whole list)
// or if the completion item begins with the tokenText
if (string.IsNullOrWhiteSpace(tokenText) || completionText.StartsWith(tokenText, StringComparison.OrdinalIgnoreCase))
{
completionItems[completionItemIndex] = CreateDefaultCompletionItem(
useLowerCase ? completionText.ToLowerInvariant() : completionText.ToUpperInvariant(),
row,
startColumn,
row,
startColumn,
endColumn);
++completionItemIndex;
}
@@ -417,8 +506,8 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="endColumn"></param>
private static CompletionItem CreateDefaultCompletionItem(
string label,
int row,
int startColumn,
int row,
int startColumn,
int endColumn)
{
return SqlCompletionItem.CreateCompletionItem(label, label + " keyword", label, CompletionItemKind.Keyword, row, startColumn, endColumn);
@@ -450,14 +539,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <param name="cursorColumn"></param>
/// <returns></returns>
internal static CompletionItem[] ConvertDeclarationsToCompletionItems(
IEnumerable<Declaration> suggestions,
IEnumerable<Declaration> suggestions,
int row,
int startColumn,
int endColumn,
int endColumn,
string tokenText = null)
{
{
List<CompletionItem> completions = new List<CompletionItem>();
foreach (var autoCompleteItem in suggestions)
{
SqlCompletionItem sqlCompletionItem = new SqlCompletionItem(autoCompleteItem, tokenText);
@@ -489,7 +578,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
markedStrings[0] = new MarkedString()
{
Language = "SQL",
Value = quickInfo.Text
Value = quickInfo.Text
};
return new Hover()
@@ -535,7 +624,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// Signature label format: <name> param1, param2, ..., paramn RETURNS <type>
Label = method.Name + " " + method.Parameters.Select(parameter => parameter.Display).Aggregate((l, r) => l + "," + r) + " " + method.Type,
Documentation = method.Description,
Parameters = method.Parameters.Select(parameter =>
Parameters = method.Parameters.Select(parameter =>
{
return new ParameterInformation()
{
@@ -560,7 +649,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
if (locations.ParamStartLocation != null)
{
// Is the cursor past the function name?
var location = locations.ParamStartLocation.Value;
var location = locations.ParamStartLocation.Value;
if (line > location.LineNumber || (line == location.LineNumber && line == location.LineNumber && column >= location.ColumnNumber))
{
currentParameter = 0;
@@ -577,7 +666,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
if (locations.ParamEndLocation != null)
{
// Is the cursor past the end of the parameter list on a different token?
var location = locations.ParamEndLocation.Value;
var location = locations.ParamEndLocation.Value;
if (line > location.LineNumber || (line == location.LineNumber && line == location.LineNumber && column > location.ColumnNumber))
{
currentParameter = -1;

View File

@@ -0,0 +1,32 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion.Extension
{
[Serializable]
public class CompletionExtensionParams
{
/// <summary>
/// Absolute path for the assembly containing the completion extension
/// </summary>
public string AssemblyPath { get; set; }
/// <summary>
/// The type name for the completion extension
/// </summary>
public string TypeName { get; set; }
/// <summary>
/// Property bag for initializing the completion extension
/// </summary>
public Dictionary<string, object> Properties { get; set; }
}
}

View File

@@ -0,0 +1,40 @@
//
// 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.Connection;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion.Extension
{
public interface ICompletionExtension : IDisposable
{
/// <summary>
/// Unique name for the extension
/// </summary>
string Name { get; }
/// <summary>
/// Method for initializing the extension, this is called once when the extension is loaded
/// </summary>
/// <param name="properties">Parameters needed by the extension</param>
/// <param name="cancelToken">Cancellation token used to indicate that the initialization should be cancelled</param>
/// <returns></returns>
Task Initialize(IReadOnlyDictionary<string, object> properties, CancellationToken token);
/// <summary>
/// Handles the completion request, returning the modified CompletionItemList if used
/// </summary>
/// <param name="connInfo">Connection information for the completion session</param>
/// <param name="scriptDocumentInfo">Script parsing information</param>
/// <param name="completions">Current completion list</param>
/// <param name="cancelToken">Token used to indicate that the completion request should be cancelled</param>
/// <returns></returns>
Task<CompletionItem[]> HandleCompletionAsync(ConnectionInfo connInfo, ScriptDocumentInfo scriptDocumentInfo, CompletionItem[] completions, CancellationToken cancelToken);
}
}

View File

@@ -5,6 +5,7 @@
using System.Diagnostics;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion.Extension;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
@@ -23,6 +24,13 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
RequestType<CompletionItem, CompletionItem>.Create("completionItem/resolve");
}
public class CompletionExtLoadRequest
{
public static readonly
RequestType<CompletionExtensionParams, bool> Type =
RequestType<CompletionExtensionParams, bool>.Create("completion/extLoad");
}
public enum CompletionItemKind
{
Text = 1,
@@ -45,6 +53,19 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
Reference = 18
}
public class Command
{
/// <summary>
/// The identifier of the actual command handler, like `vsintellicode.completionItemSelected`.
/// </summary>
public string CommandStr { get; set; }
/// <summary>
/// Arguments that the command handler should be invoked with.
/// </summary>
public object[] Arguments { get; set; }
}
[DebuggerDisplay("Kind = {Kind.ToString()}, Label = {Label}, Detail = {Detail}")]
public class CompletionItem
{
@@ -74,5 +95,15 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts
/// resolve request.
/// </summary>
public object Data { get; set; }
/// <summary>
/// Exposing a command field for a completion item for passing telemetry
/// </summary>
public Command Command { get; set; }
/// <summary>
/// Whether this completion item is preselected or not
/// </summary>
public bool? Preselect { get; set; }
}
}

View File

@@ -7,7 +7,10 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlServer.Management.Common;
@@ -17,13 +20,14 @@ using Microsoft.SqlServer.Management.SqlParser.Common;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlServer.Management.SqlParser.SqlCodeDom;
using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion.Extension;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
using Microsoft.SqlTools.ServiceLayer.Scripting;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Utility;
@@ -72,6 +76,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
internal const int PeekDefinitionTimeout = 10 * OneSecond;
internal const int ExtensionLoadingTimeout = 10 * OneSecond;
internal const int CompletionExtTimeout = 200;
private ConnectionService connectionService = null;
private WorkspaceService<SqlToolsSettings> workspaceServiceInstance;
@@ -95,6 +103,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
private Lazy<Dictionary<string, ScriptParseInfo>> scriptParseInfoMap
= new Lazy<Dictionary<string, ScriptParseInfo>>(() => new Dictionary<string, ScriptParseInfo>());
private readonly ConcurrentDictionary<string, ICompletionExtension> completionExtensions = new ConcurrentDictionary<string, ICompletionExtension>();
private readonly ConcurrentDictionary<string, DateTime> extAssemblyLastUpdateTime = new ConcurrentDictionary<string, DateTime>();
/// <summary>
/// Gets a mapping dictionary for SQL file URIs to ScriptParseInfo objects
/// </summary>
@@ -245,6 +256,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
serviceHost.SetRequestHandler(SyntaxParseRequest.Type, HandleSyntaxParseRequest);
serviceHost.SetRequestHandler(CompletionExtLoadRequest.Type, HandleCompletionExtLoadRequest);
serviceHost.SetEventHandler(RebuildIntelliSenseNotification.Type, HandleRebuildIntelliSenseNotification);
serviceHost.SetEventHandler(LanguageFlavorChangeNotification.Type, HandleDidChangeLanguageFlavorNotification);
@@ -283,6 +295,90 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
#region Request Handlers
/// <summary>
/// Completion extension load request callback
/// </summary>
/// <param name="param"></param>
/// <param name="requestContext"></param>
/// <returns></returns>
internal async Task HandleCompletionExtLoadRequest(CompletionExtensionParams param, RequestContext<bool> requestContext)
{
try
{
//register the new assembly
var serviceProvider = (ExtensionServiceProvider)ServiceHostInstance.ServiceProvider;
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(param.AssemblyPath);
var assemblies = new Assembly[] { assembly };
serviceProvider.AddAssembliesToConfiguration(assemblies);
foreach (var ext in serviceProvider.GetServices<ICompletionExtension>())
{
var cancellationTokenSource = new CancellationTokenSource(ExtensionLoadingTimeout);
var cancellationToken = cancellationTokenSource.Token;
string extName = ext.Name;
string extTypeName = ext.GetType().FullName;
if (extTypeName != param.TypeName)
{
continue;
}
if (!CheckIfAssemblyShouldBeLoaded(param.AssemblyPath, extTypeName))
{
await requestContext.SendError(string.Format("Skip loading {0} because it's already loaded", param.AssemblyPath));
return;
}
await ext.Initialize(param.Properties, cancellationToken).WithTimeout(ExtensionLoadingTimeout);
cancellationTokenSource.Dispose();
if (!string.IsNullOrEmpty(extName))
{
completionExtensions[extName] = ext;
await requestContext.SendResult(true);
return;
}
else
{
await requestContext.SendError(string.Format("Skip loading an unnamed completion extension from {0}", param.AssemblyPath));
return;
}
}
}
catch (Exception ex)
{
await requestContext.SendError(ex.Message);
return;
}
await requestContext.SendError(string.Format("Couldn't discover completion extension with type {0} in {1}", param.TypeName, param.AssemblyPath));
}
/// <summary>
/// Check whether a particular assembly should be reloaded based on
/// whether it's been updated since it was last loaded.
/// </summary>
/// <param name="assemblyPath">The assembly path</param>
/// <param name="extTypeName">The type loading from the assembly</param>
/// <returns></returns>
private bool CheckIfAssemblyShouldBeLoaded(string assemblyPath, string extTypeName)
{
var lastModified = File.GetLastWriteTime(assemblyPath);
if (extAssemblyLastUpdateTime.ContainsKey(extTypeName))
{
if (lastModified > extAssemblyLastUpdateTime[extTypeName])
{
extAssemblyLastUpdateTime[extTypeName] = lastModified;
return true;
}
}
else
{
extAssemblyLastUpdateTime[extTypeName] = lastModified;
return true;
}
return false;
}
/// <summary>
/// T-SQL syntax parse request callback
/// </summary>
@@ -352,7 +448,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
scriptFile.ClientFilePath,
out connInfo);
var completionItems = GetCompletionItems(
var completionItems = await GetCompletionItems(
textDocumentPosition, scriptFile, connInfo);
await requestContext.SendResult(completionItems);
@@ -1466,7 +1562,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// This method does not await cache builds since it expects to return quickly
/// </summary>
/// <param name="textDocumentPosition"></param>
public CompletionItem[] GetCompletionItems(
public async Task<CompletionItem[]> GetCompletionItems(
TextDocumentPosition textDocumentPosition,
ScriptFile scriptFile,
ConnectionInfo connInfo)
@@ -1482,7 +1578,11 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
if (scriptParseInfo == null)
{
return AutoCompleteHelper.GetDefaultCompletionItems(ScriptDocumentInfo.CreateDefaultDocumentInfo(textDocumentPosition, scriptFile), useLowerCaseSuggestions);
var scriptDocInfo = ScriptDocumentInfo.CreateDefaultDocumentInfo(textDocumentPosition, scriptFile);
resultCompletionItems = AutoCompleteHelper.GetDefaultCompletionItems(scriptDocInfo, useLowerCaseSuggestions);
//call completion extensions only for default completion list
resultCompletionItems = await ApplyCompletionExtensions(connInfo, resultCompletionItems, scriptDocInfo);
return resultCompletionItems;
}
ScriptDocumentInfo scriptDocumentInfo = new ScriptDocumentInfo(textDocumentPosition, scriptFile, scriptParseInfo);
@@ -1496,7 +1596,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// if the parse failed then return the default list
if (scriptParseInfo.ParseResult == null)
{
return AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
resultCompletionItems = AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
//call completion extensions only for default completion list
resultCompletionItems = await ApplyCompletionExtensions(connInfo, resultCompletionItems, scriptDocumentInfo);
return resultCompletionItems;
}
AutoCompletionResult result = completionService.CreateCompletions(connInfo, scriptDocumentInfo, useLowerCaseSuggestions);
// cache the current script parse info object to resolve completions later
@@ -1507,6 +1610,38 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
if (resultCompletionItems == null)
{
resultCompletionItems = AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
//call completion extensions only for default completion list
resultCompletionItems = await ApplyCompletionExtensions(connInfo, resultCompletionItems, scriptDocumentInfo);
}
return resultCompletionItems;
}
/// <summary>
/// Run all completion extensions
/// </summary>
/// <param name="connInfo"></param>
/// <param name="resultCompletionItems"></param>
/// <param name="scriptDocumentInfo"></param>
/// <returns></returns>
private async Task<CompletionItem[]> ApplyCompletionExtensions(ConnectionInfo connInfo, CompletionItem[] resultCompletionItems, ScriptDocumentInfo scriptDocumentInfo)
{
//invoke the completion extensions
foreach (var completionExt in completionExtensions.Values)
{
var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.CancelAfter(CompletionExtTimeout);
var cancellationToken = cancellationTokenSource.Token;
try
{
resultCompletionItems = await completionExt.HandleCompletionAsync(connInfo, scriptDocumentInfo, resultCompletionItems, cancellationToken).WithTimeout(CompletionExtTimeout);
}
catch (Exception e)
{
Logger.Write(TraceEventType.Error, string.Format("Exception in calling completion extension {0}:\n{1}", completionExt.Name, e.ToString()));
}
cancellationTokenSource.Dispose();
}
return resultCompletionItems;

View File

@@ -14,7 +14,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
/// <summary>
/// A class to calculate the numbers used by SQL parser using the text positions and content
/// </summary>
internal class ScriptDocumentInfo
public class ScriptDocumentInfo
{
/// <summary>
/// Create new instance

View File

@@ -12,7 +12,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
/// <summary>
/// Class for storing cached metadata regarding a parsed SQL file
/// </summary>
internal class ScriptParseInfo
public class ScriptParseInfo
{
private object buildingMetadataLock = new object();

View File

@@ -1,4 +1,4 @@
//
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
@@ -6,6 +6,7 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SqlTools.Utility;
@@ -17,7 +18,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
/// Adds handling to check the Exception field of a task and log it if the task faulted
/// </summary>
/// <remarks>
/// This will effectively swallow exceptions in the task chain.
/// This will effectively swallow exceptions in the task chain.
/// </remarks>
/// <param name="antecedent">The task to continue</param>
/// <param name="continuationAction">
@@ -33,7 +34,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
{
return;
}
LogTaskExceptions(task.Exception);
// Run the continuation task that was provided
@@ -54,7 +55,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
/// This version allows for async code to be ran in the continuation function.
/// </summary>
/// <remarks>
/// This will effectively swallow exceptions in the task chain.
/// This will effectively swallow exceptions in the task chain.
/// </remarks>
/// <param name="antecedent">The task to continue</param>
/// <param name="continuationFunc">
@@ -97,5 +98,38 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility
}
Logger.Write(TraceEventType.Error, sb.ToString());
}
/// <summary>
/// This will enforce time out to run an async task with returning result
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="task">The async task to run</param>
/// <param name="timeout">Time out in milliseconds</param>
/// <returns></returns>
public static async Task<TResult> WithTimeout<TResult>(this Task<TResult> task, int timeout)
{
if (task == await Task.WhenAny(task, Task.Delay(timeout)))
{
return await task;
}
throw new TimeoutException();
}
/// <summary>
/// This will enforce time out to run an async task without returning result
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="task">The async task to run</param>
/// <param name="timeout">Time out in milliseconds</param>
/// <returns></returns>
public static async Task WithTimeout(this Task task, int timeout)
{
if (task == await Task.WhenAny(task, Task.Delay(timeout)))
{
await task;
return;
}
throw new TimeoutException();
}
}
}