mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-01 01:25:43 -05:00
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:
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user