mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 10:58:30 -05:00
Autocomplete bug fixes (#43)
Fix-up the autocomplete support to better handle binding timeouts. Also provide a default keyword suggestion list.
This commit is contained in:
@@ -136,13 +136,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
|
|||||||
TextDocumentSync = TextDocumentSyncKind.Incremental,
|
TextDocumentSync = TextDocumentSyncKind.Incremental,
|
||||||
DefinitionProvider = true,
|
DefinitionProvider = true,
|
||||||
ReferencesProvider = true,
|
ReferencesProvider = true,
|
||||||
DocumentHighlightProvider = true,
|
DocumentHighlightProvider = true,
|
||||||
DocumentSymbolProvider = true,
|
|
||||||
WorkspaceSymbolProvider = true,
|
|
||||||
CompletionProvider = new CompletionOptions
|
CompletionProvider = new CompletionOptions
|
||||||
{
|
{
|
||||||
ResolveProvider = true,
|
ResolveProvider = true,
|
||||||
TriggerCharacters = new string[] { ".", "-", ":", "\\" }
|
TriggerCharacters = new string[] { ".", "-", ":", "\\", ",", " " }
|
||||||
},
|
},
|
||||||
SignatureHelpProvider = new SignatureHelpOptions
|
SignatureHelpProvider = new SignatureHelpOptions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,514 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main class for Language Service functionality including anything that reqires knowledge of
|
||||||
|
/// the language to perfom, such as definitions, intellisense, etc.
|
||||||
|
/// </summary>
|
||||||
|
public static class AutoCompleteHelper
|
||||||
|
{
|
||||||
|
private static readonly string[] DefaultCompletionText = new string[]
|
||||||
|
{
|
||||||
|
"absolute",
|
||||||
|
"accent_sensitivity",
|
||||||
|
"action",
|
||||||
|
"activation",
|
||||||
|
"add",
|
||||||
|
"address",
|
||||||
|
"admin",
|
||||||
|
"after",
|
||||||
|
"aggregate",
|
||||||
|
"algorithm",
|
||||||
|
"allow_page_locks",
|
||||||
|
"allow_row_locks",
|
||||||
|
"allow_snapshot_isolation",
|
||||||
|
"alter",
|
||||||
|
"always",
|
||||||
|
"ansi_null_default",
|
||||||
|
"ansi_nulls",
|
||||||
|
"ansi_padding",
|
||||||
|
"ansi_warnings",
|
||||||
|
"application",
|
||||||
|
"arithabort",
|
||||||
|
"as",
|
||||||
|
"asc",
|
||||||
|
"assembly",
|
||||||
|
"asymmetric",
|
||||||
|
"at",
|
||||||
|
"atomic",
|
||||||
|
"audit",
|
||||||
|
"authentication",
|
||||||
|
"authorization",
|
||||||
|
"auto",
|
||||||
|
"auto_close",
|
||||||
|
"auto_shrink",
|
||||||
|
"auto_update_statistics",
|
||||||
|
"auto_update_statistics_async",
|
||||||
|
"availability",
|
||||||
|
"backup",
|
||||||
|
"before",
|
||||||
|
"begin",
|
||||||
|
"binary",
|
||||||
|
"bit",
|
||||||
|
"block",
|
||||||
|
"break",
|
||||||
|
"browse",
|
||||||
|
"bucket_count",
|
||||||
|
"bulk",
|
||||||
|
"by",
|
||||||
|
"call",
|
||||||
|
"caller",
|
||||||
|
"card",
|
||||||
|
"cascade",
|
||||||
|
"case",
|
||||||
|
"catalog",
|
||||||
|
"catch",
|
||||||
|
"change_tracking",
|
||||||
|
"changes",
|
||||||
|
"char",
|
||||||
|
"character",
|
||||||
|
"check",
|
||||||
|
"checkpoint",
|
||||||
|
"close",
|
||||||
|
"clustered",
|
||||||
|
"collection",
|
||||||
|
"column",
|
||||||
|
"column_encryption_key",
|
||||||
|
"columnstore",
|
||||||
|
"commit",
|
||||||
|
"compatibility_level",
|
||||||
|
"compress_all_row_groups",
|
||||||
|
"compression",
|
||||||
|
"compression_delay",
|
||||||
|
"compute",
|
||||||
|
"concat_null_yields_null",
|
||||||
|
"configuration",
|
||||||
|
"connect",
|
||||||
|
"constraint",
|
||||||
|
"containstable",
|
||||||
|
"continue",
|
||||||
|
"create",
|
||||||
|
"cube",
|
||||||
|
"current",
|
||||||
|
"current_date",
|
||||||
|
"cursor",
|
||||||
|
"cursor_close_on_commit",
|
||||||
|
"cursor_default",
|
||||||
|
"data",
|
||||||
|
"data_compression",
|
||||||
|
"database",
|
||||||
|
"date",
|
||||||
|
"date_correlation_optimization",
|
||||||
|
"datefirst",
|
||||||
|
"datetime",
|
||||||
|
"datetime2",
|
||||||
|
"days",
|
||||||
|
"db_chaining",
|
||||||
|
"dbcc",
|
||||||
|
"deallocate",
|
||||||
|
"dec",
|
||||||
|
"decimal",
|
||||||
|
"declare",
|
||||||
|
"default",
|
||||||
|
"delayed_durability",
|
||||||
|
"delete",
|
||||||
|
"deny",
|
||||||
|
"desc",
|
||||||
|
"description",
|
||||||
|
"disable_broker",
|
||||||
|
"disabled",
|
||||||
|
"disk",
|
||||||
|
"distinct",
|
||||||
|
"distributed",
|
||||||
|
"double",
|
||||||
|
"drop",
|
||||||
|
"drop_existing",
|
||||||
|
"dump",
|
||||||
|
"durability",
|
||||||
|
"dynamic",
|
||||||
|
"else",
|
||||||
|
"enable",
|
||||||
|
"encrypted",
|
||||||
|
"encryption_type",
|
||||||
|
"end",
|
||||||
|
"end-exec",
|
||||||
|
"entry",
|
||||||
|
"errlvl",
|
||||||
|
"escape",
|
||||||
|
"event",
|
||||||
|
"except",
|
||||||
|
"exec",
|
||||||
|
"execute",
|
||||||
|
"exit",
|
||||||
|
"external",
|
||||||
|
"fast_forward",
|
||||||
|
"fetch",
|
||||||
|
"file",
|
||||||
|
"filegroup",
|
||||||
|
"filename",
|
||||||
|
"filestream",
|
||||||
|
"fillfactor",
|
||||||
|
"filter",
|
||||||
|
"first",
|
||||||
|
"float",
|
||||||
|
"for",
|
||||||
|
"foreign",
|
||||||
|
"freetext",
|
||||||
|
"freetexttable",
|
||||||
|
"from",
|
||||||
|
"full",
|
||||||
|
"fullscan",
|
||||||
|
"fulltext",
|
||||||
|
"function",
|
||||||
|
"generated",
|
||||||
|
"geography",
|
||||||
|
"get",
|
||||||
|
"global",
|
||||||
|
"go",
|
||||||
|
"goto",
|
||||||
|
"grant",
|
||||||
|
"group",
|
||||||
|
"hash",
|
||||||
|
"hashed",
|
||||||
|
"having",
|
||||||
|
"hidden",
|
||||||
|
"hierarchyid",
|
||||||
|
"holdlock",
|
||||||
|
"hours",
|
||||||
|
"identity",
|
||||||
|
"identity_insert",
|
||||||
|
"identitycol",
|
||||||
|
"if",
|
||||||
|
"ignore_dup_key",
|
||||||
|
"image",
|
||||||
|
"immediate",
|
||||||
|
"include",
|
||||||
|
"index",
|
||||||
|
"inflectional",
|
||||||
|
"insensitive",
|
||||||
|
"insert",
|
||||||
|
"instead",
|
||||||
|
"int",
|
||||||
|
"integer",
|
||||||
|
"integrated",
|
||||||
|
"intersect",
|
||||||
|
"into",
|
||||||
|
"isolation",
|
||||||
|
"json",
|
||||||
|
"key",
|
||||||
|
"kill",
|
||||||
|
"language",
|
||||||
|
"last",
|
||||||
|
"legacy_cardinality_estimation",
|
||||||
|
"level",
|
||||||
|
"lineno",
|
||||||
|
"load",
|
||||||
|
"local",
|
||||||
|
"locate",
|
||||||
|
"location",
|
||||||
|
"login",
|
||||||
|
"masked",
|
||||||
|
"master",
|
||||||
|
"maxdop",
|
||||||
|
"memory_optimized",
|
||||||
|
"merge",
|
||||||
|
"message",
|
||||||
|
"modify",
|
||||||
|
"move",
|
||||||
|
"multi_user",
|
||||||
|
"namespace",
|
||||||
|
"national",
|
||||||
|
"native_compilation",
|
||||||
|
"nchar",
|
||||||
|
"next",
|
||||||
|
"no",
|
||||||
|
"nocheck",
|
||||||
|
"nocount",
|
||||||
|
"nonclustered",
|
||||||
|
"none",
|
||||||
|
"norecompute",
|
||||||
|
"now",
|
||||||
|
"numeric",
|
||||||
|
"numeric_roundabort",
|
||||||
|
"object",
|
||||||
|
"of",
|
||||||
|
"off",
|
||||||
|
"offsets",
|
||||||
|
"on",
|
||||||
|
"online",
|
||||||
|
"open",
|
||||||
|
"opendatasource",
|
||||||
|
"openquery",
|
||||||
|
"openrowset",
|
||||||
|
"openxml",
|
||||||
|
"option",
|
||||||
|
"order",
|
||||||
|
"out",
|
||||||
|
"output",
|
||||||
|
"over",
|
||||||
|
"owner",
|
||||||
|
"pad_index",
|
||||||
|
"page",
|
||||||
|
"page_verify",
|
||||||
|
"parameter_sniffing",
|
||||||
|
"parameterization",
|
||||||
|
"partial",
|
||||||
|
"partition",
|
||||||
|
"password",
|
||||||
|
"path",
|
||||||
|
"percent",
|
||||||
|
"percentage",
|
||||||
|
"period",
|
||||||
|
"persisted",
|
||||||
|
"plan",
|
||||||
|
"policy",
|
||||||
|
"population",
|
||||||
|
"precision",
|
||||||
|
"predicate",
|
||||||
|
"primary",
|
||||||
|
"print",
|
||||||
|
"prior",
|
||||||
|
"proc",
|
||||||
|
"procedure",
|
||||||
|
"public",
|
||||||
|
"query_optimizer_hotfixes",
|
||||||
|
"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",
|
||||||
|
"replication",
|
||||||
|
"required",
|
||||||
|
"restart",
|
||||||
|
"restore",
|
||||||
|
"restrict",
|
||||||
|
"resume",
|
||||||
|
"return",
|
||||||
|
"returns",
|
||||||
|
"revert",
|
||||||
|
"revoke",
|
||||||
|
"role",
|
||||||
|
"rollback",
|
||||||
|
"rollup",
|
||||||
|
"row",
|
||||||
|
"rowcount",
|
||||||
|
"rowguidcol",
|
||||||
|
"rows",
|
||||||
|
"rule",
|
||||||
|
"sample",
|
||||||
|
"save",
|
||||||
|
"schema",
|
||||||
|
"schemabinding",
|
||||||
|
"scoped",
|
||||||
|
"scroll",
|
||||||
|
"secondary",
|
||||||
|
"security",
|
||||||
|
"securityaudit",
|
||||||
|
"select",
|
||||||
|
"semantickeyphrasetable",
|
||||||
|
"semanticsimilaritydetailstable",
|
||||||
|
"semanticsimilaritytable",
|
||||||
|
"send",
|
||||||
|
"sent",
|
||||||
|
"sequence",
|
||||||
|
"server",
|
||||||
|
"session",
|
||||||
|
"set",
|
||||||
|
"sets",
|
||||||
|
"setuser",
|
||||||
|
"shutdown",
|
||||||
|
"simple",
|
||||||
|
"smallint",
|
||||||
|
"smallmoney",
|
||||||
|
"snapshot",
|
||||||
|
"sort_in_tempdb",
|
||||||
|
"sql",
|
||||||
|
"standard",
|
||||||
|
"start",
|
||||||
|
"started",
|
||||||
|
"state",
|
||||||
|
"statement",
|
||||||
|
"static",
|
||||||
|
"statistics",
|
||||||
|
"statistics_norecompute",
|
||||||
|
"status",
|
||||||
|
"stopped",
|
||||||
|
"supported",
|
||||||
|
"symmetric",
|
||||||
|
"sysname",
|
||||||
|
"system",
|
||||||
|
"system_time",
|
||||||
|
"system_versioning",
|
||||||
|
"table",
|
||||||
|
"tablesample",
|
||||||
|
"take",
|
||||||
|
"target",
|
||||||
|
"textimage_on",
|
||||||
|
"textsize",
|
||||||
|
"then",
|
||||||
|
"thesaurus",
|
||||||
|
"throw",
|
||||||
|
"time",
|
||||||
|
"timestamp",
|
||||||
|
"tinyint",
|
||||||
|
"to",
|
||||||
|
"top",
|
||||||
|
"tran",
|
||||||
|
"transaction",
|
||||||
|
"trigger",
|
||||||
|
"truncate",
|
||||||
|
"trustworthy",
|
||||||
|
"try",
|
||||||
|
"tsql",
|
||||||
|
"type",
|
||||||
|
"union",
|
||||||
|
"unique",
|
||||||
|
"uniqueidentifier",
|
||||||
|
"unlimited",
|
||||||
|
"updatetext",
|
||||||
|
"use",
|
||||||
|
"user",
|
||||||
|
"using",
|
||||||
|
"value",
|
||||||
|
"values",
|
||||||
|
"varchar",
|
||||||
|
"varying",
|
||||||
|
"version",
|
||||||
|
"view",
|
||||||
|
"waitfor",
|
||||||
|
"weight",
|
||||||
|
"when",
|
||||||
|
"where",
|
||||||
|
"while",
|
||||||
|
"with",
|
||||||
|
"within",
|
||||||
|
"within group",
|
||||||
|
"without",
|
||||||
|
"writetext",
|
||||||
|
"xact_abort",
|
||||||
|
"xml",
|
||||||
|
"zone"
|
||||||
|
};
|
||||||
|
|
||||||
|
internal static CompletionItem[] GetDefaultCompletionItems(
|
||||||
|
int row,
|
||||||
|
int startColumn,
|
||||||
|
int endColumn)
|
||||||
|
{
|
||||||
|
var completionItems = new CompletionItem[DefaultCompletionText.Length];
|
||||||
|
for (int i = 0; i < DefaultCompletionText.Length; ++i)
|
||||||
|
{
|
||||||
|
completionItems[i] = CreateDefaultCompletionItem(
|
||||||
|
DefaultCompletionText[i].ToUpper(),
|
||||||
|
row,
|
||||||
|
startColumn,
|
||||||
|
endColumn);
|
||||||
|
}
|
||||||
|
return completionItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompletionItem CreateDefaultCompletionItem(
|
||||||
|
string label,
|
||||||
|
int row,
|
||||||
|
int startColumn,
|
||||||
|
int endColumn)
|
||||||
|
{
|
||||||
|
return new CompletionItem()
|
||||||
|
{
|
||||||
|
Label = label,
|
||||||
|
Kind = CompletionItemKind.Keyword,
|
||||||
|
Detail = label + " keyword",
|
||||||
|
TextEdit = new TextEdit
|
||||||
|
{
|
||||||
|
NewText = label,
|
||||||
|
Range = new Range
|
||||||
|
{
|
||||||
|
Start = new Position
|
||||||
|
{
|
||||||
|
Line = row,
|
||||||
|
Character = startColumn
|
||||||
|
},
|
||||||
|
End = new Position
|
||||||
|
{
|
||||||
|
Line = row,
|
||||||
|
Character = endColumn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a list of Declaration objects to CompletionItem objects
|
||||||
|
/// since VS Code expects CompletionItems but SQL Parser works with Declarations
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="suggestions"></param>
|
||||||
|
/// <param name="cursorRow"></param>
|
||||||
|
/// <param name="cursorColumn"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal static CompletionItem[] ConvertDeclarationsToCompletionItems(
|
||||||
|
IEnumerable<Declaration> suggestions,
|
||||||
|
int row,
|
||||||
|
int startColumn,
|
||||||
|
int endColumn)
|
||||||
|
{
|
||||||
|
List<CompletionItem> completions = new List<CompletionItem>();
|
||||||
|
foreach (var autoCompleteItem in suggestions)
|
||||||
|
{
|
||||||
|
// convert the completion item candidates into CompletionItems
|
||||||
|
completions.Add(new CompletionItem()
|
||||||
|
{
|
||||||
|
Label = autoCompleteItem.Title,
|
||||||
|
Kind = CompletionItemKind.Variable,
|
||||||
|
Detail = autoCompleteItem.Title,
|
||||||
|
Documentation = autoCompleteItem.Description,
|
||||||
|
TextEdit = new TextEdit
|
||||||
|
{
|
||||||
|
NewText = autoCompleteItem.Title,
|
||||||
|
Range = new Range
|
||||||
|
{
|
||||||
|
Start = new Position
|
||||||
|
{
|
||||||
|
Line = row,
|
||||||
|
Character = startColumn
|
||||||
|
},
|
||||||
|
End = new Position
|
||||||
|
{
|
||||||
|
Line = row,
|
||||||
|
Character = endColumn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return completions.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright (c) Microsoft. All rights reserved.
|
|
||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
||||||
//
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data.SqlClient;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.SqlServer.Management.Common;
|
|
||||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
|
||||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Main class for Autocomplete functionality
|
|
||||||
/// </summary>
|
|
||||||
public class AutoCompleteService
|
|
||||||
{
|
|
||||||
#region Singleton Instance Implementation
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Singleton service instance
|
|
||||||
/// </summary>
|
|
||||||
private static Lazy<AutoCompleteService> instance
|
|
||||||
= new Lazy<AutoCompleteService>(() => new AutoCompleteService());
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the singleton service instance
|
|
||||||
/// </summary>
|
|
||||||
public static AutoCompleteService Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return instance.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default, parameterless constructor.
|
|
||||||
/// Internal constructor for use in test cases only
|
|
||||||
/// </summary>
|
|
||||||
internal AutoCompleteService()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private ConnectionService connectionService = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Internal for testing purposes only
|
|
||||||
/// </summary>
|
|
||||||
internal ConnectionService ConnectionServiceInstance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(connectionService == null)
|
|
||||||
{
|
|
||||||
connectionService = ConnectionService.Instance;
|
|
||||||
}
|
|
||||||
return connectionService;
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
connectionService = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InitializeService(ServiceHost serviceHost)
|
|
||||||
{
|
|
||||||
// Register auto-complete request handler
|
|
||||||
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
|
|
||||||
|
|
||||||
// Register a callback for when a connection is created
|
|
||||||
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateAutoCompleteCache);
|
|
||||||
|
|
||||||
// Register a callback for when a connection is closed
|
|
||||||
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Auto-complete completion provider request callback
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="textDocumentPosition"></param>
|
|
||||||
/// <param name="requestContext"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static async Task HandleCompletionRequest(
|
|
||||||
TextDocumentPosition textDocumentPosition,
|
|
||||||
RequestContext<CompletionItem[]> requestContext)
|
|
||||||
{
|
|
||||||
// get the current list of completion items and return to client
|
|
||||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
|
|
||||||
textDocumentPosition.TextDocument.Uri);
|
|
||||||
|
|
||||||
ConnectionInfo connInfo;
|
|
||||||
ConnectionService.Instance.TryFindConnection(
|
|
||||||
scriptFile.ClientFilePath,
|
|
||||||
out connInfo);
|
|
||||||
|
|
||||||
var completionItems = Instance.GetCompletionItems(
|
|
||||||
textDocumentPosition, scriptFile, connInfo);
|
|
||||||
|
|
||||||
await requestContext.SendResult(completionItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove a reference to an autocomplete cache from a URI. If
|
|
||||||
/// it is the last URI connected to a particular connection,
|
|
||||||
/// then remove the cache.
|
|
||||||
/// </summary>
|
|
||||||
public async Task RemoveAutoCompleteCacheUriReference(ConnectionSummary summary)
|
|
||||||
{
|
|
||||||
// currently this method is disabled, but we need to reimplement now that the
|
|
||||||
// implementation of the 'cache' has changed.
|
|
||||||
await Task.FromResult(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update the cached autocomplete candidate list when the user connects to a database
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="info"></param>
|
|
||||||
public async Task UpdateAutoCompleteCache(ConnectionInfo info)
|
|
||||||
{
|
|
||||||
await Task.Run( () =>
|
|
||||||
{
|
|
||||||
if (!LanguageService.Instance.ScriptParseInfoMap.ContainsKey(info.OwnerUri))
|
|
||||||
{
|
|
||||||
var sqlConn = info.SqlConnection as SqlConnection;
|
|
||||||
if (sqlConn != null)
|
|
||||||
{
|
|
||||||
var srvConn = new ServerConnection(sqlConn);
|
|
||||||
var displayInfoProvider = new MetadataDisplayInfoProvider();
|
|
||||||
var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn);
|
|
||||||
var binder = BinderProvider.CreateBinder(metadataProvider);
|
|
||||||
|
|
||||||
LanguageService.Instance.ScriptParseInfoMap.Add(info.OwnerUri,
|
|
||||||
new ScriptParseInfo()
|
|
||||||
{
|
|
||||||
Binder = binder,
|
|
||||||
MetadataProvider = metadataProvider,
|
|
||||||
MetadataDisplayInfoProvider = displayInfoProvider
|
|
||||||
});
|
|
||||||
|
|
||||||
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(info.OwnerUri);
|
|
||||||
|
|
||||||
LanguageService.Instance.ParseAndBind(scriptFile, info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Find the position of the previous delimeter for autocomplete token replacement.
|
|
||||||
/// SQL Parser may have similar functionality in which case we'll delete this method.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sql"></param>
|
|
||||||
/// <param name="startRow"></param>
|
|
||||||
/// <param name="startColumn"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private int PositionOfPrevDelimeter(string sql, int startRow, int startColumn)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(sql))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int prevLineColumns = 0;
|
|
||||||
for (int i = 0; i < startRow; ++i)
|
|
||||||
{
|
|
||||||
while (sql[prevLineColumns] != '\n' && prevLineColumns < sql.Length)
|
|
||||||
{
|
|
||||||
++prevLineColumns;
|
|
||||||
}
|
|
||||||
++prevLineColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
startColumn += prevLineColumns;
|
|
||||||
|
|
||||||
if (startColumn - 1 < sql.Length)
|
|
||||||
{
|
|
||||||
while (--startColumn >= prevLineColumns)
|
|
||||||
{
|
|
||||||
if (sql[startColumn] == ' '
|
|
||||||
|| sql[startColumn] == '\t'
|
|
||||||
|| sql[startColumn] == '\n'
|
|
||||||
|| sql[startColumn] == '.'
|
|
||||||
|| sql[startColumn] == '+'
|
|
||||||
|| sql[startColumn] == '-'
|
|
||||||
|| sql[startColumn] == '*'
|
|
||||||
|| sql[startColumn] == '>'
|
|
||||||
|| sql[startColumn] == '<'
|
|
||||||
|| sql[startColumn] == '='
|
|
||||||
|| sql[startColumn] == '/'
|
|
||||||
|| sql[startColumn] == '%')
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return startColumn + 1 - prevLineColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether a reparse and bind is required to provide autocomplete
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="info"></param>
|
|
||||||
/// <returns>TEMP: Currently hard-coded to false for perf</returns>
|
|
||||||
private bool RequiresReparse(ScriptParseInfo info)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a list of Declaration objects to CompletionItem objects
|
|
||||||
/// since VS Code expects CompletionItems but SQL Parser works with Declarations
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="suggestions"></param>
|
|
||||||
/// <param name="cursorRow"></param>
|
|
||||||
/// <param name="cursorColumn"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private CompletionItem[] ConvertDeclarationsToCompletionItems(
|
|
||||||
IEnumerable<Declaration> suggestions,
|
|
||||||
int row,
|
|
||||||
int startColumn,
|
|
||||||
int endColumn)
|
|
||||||
{
|
|
||||||
List<CompletionItem> completions = new List<CompletionItem>();
|
|
||||||
foreach (var autoCompleteItem in suggestions)
|
|
||||||
{
|
|
||||||
// convert the completion item candidates into CompletionItems
|
|
||||||
completions.Add(new CompletionItem()
|
|
||||||
{
|
|
||||||
Label = autoCompleteItem.Title,
|
|
||||||
Kind = CompletionItemKind.Keyword,
|
|
||||||
Detail = autoCompleteItem.Title,
|
|
||||||
Documentation = autoCompleteItem.Description,
|
|
||||||
TextEdit = new TextEdit
|
|
||||||
{
|
|
||||||
NewText = autoCompleteItem.Title,
|
|
||||||
Range = new Range
|
|
||||||
{
|
|
||||||
Start = new Position
|
|
||||||
{
|
|
||||||
Line = row,
|
|
||||||
Character = startColumn
|
|
||||||
},
|
|
||||||
End = new Position
|
|
||||||
{
|
|
||||||
Line = row,
|
|
||||||
Character = endColumn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return completions.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return the completion item list for the current text position.
|
|
||||||
/// This method does not await cache builds since it expects to return quickly
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="textDocumentPosition"></param>
|
|
||||||
public CompletionItem[] GetCompletionItems(
|
|
||||||
TextDocumentPosition textDocumentPosition,
|
|
||||||
ScriptFile scriptFile,
|
|
||||||
ConnectionInfo connInfo)
|
|
||||||
{
|
|
||||||
string filePath = textDocumentPosition.TextDocument.Uri;
|
|
||||||
|
|
||||||
// Take a reference to the list at a point in time in case we update and replace the list
|
|
||||||
if (connInfo == null
|
|
||||||
|| !LanguageService.Instance.ScriptParseInfoMap.ContainsKey(textDocumentPosition.TextDocument.Uri))
|
|
||||||
{
|
|
||||||
return new CompletionItem[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// reparse and bind the SQL statement if needed
|
|
||||||
var scriptParseInfo = LanguageService.Instance.ScriptParseInfoMap[textDocumentPosition.TextDocument.Uri];
|
|
||||||
if (RequiresReparse(scriptParseInfo))
|
|
||||||
{
|
|
||||||
LanguageService.Instance.ParseAndBind(scriptFile, connInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scriptParseInfo.ParseResult == null)
|
|
||||||
{
|
|
||||||
return new CompletionItem[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the completion list from SQL Parser
|
|
||||||
var suggestions = Resolver.FindCompletions(
|
|
||||||
scriptParseInfo.ParseResult,
|
|
||||||
textDocumentPosition.Position.Line + 1,
|
|
||||||
textDocumentPosition.Position.Character + 1,
|
|
||||||
scriptParseInfo.MetadataDisplayInfoProvider);
|
|
||||||
|
|
||||||
// convert the suggestion list to the VS Code format
|
|
||||||
return ConvertDeclarationsToCompletionItems(
|
|
||||||
suggestions,
|
|
||||||
textDocumentPosition.Position.Line,
|
|
||||||
PositionOfPrevDelimeter(
|
|
||||||
scriptFile.Contents,
|
|
||||||
textDocumentPosition.Position.Line,
|
|
||||||
textDocumentPosition.Position.Character),
|
|
||||||
textDocumentPosition.Position.Character);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main class for Language Service functionality including anything that reqires knowledge of
|
||||||
|
/// the language to perfom, such as definitions, intellisense, etc.
|
||||||
|
/// </summary>
|
||||||
|
public static class DiagnosticsHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send the diagnostic results back to the host application
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scriptFile"></param>
|
||||||
|
/// <param name="semanticMarkers"></param>
|
||||||
|
/// <param name="eventContext"></param>
|
||||||
|
internal 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()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a ScriptFileMarker to a Diagnostic that is Language Service compatible
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scriptFileMarker"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
internal static Diagnostic GetDiagnosticFromMarker(ScriptFileMarker scriptFileMarker)
|
||||||
|
{
|
||||||
|
return new Diagnostic
|
||||||
|
{
|
||||||
|
Severity = MapDiagnosticSeverity(scriptFileMarker.Level),
|
||||||
|
Message = scriptFileMarker.Message,
|
||||||
|
Range = new Range
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Map ScriptFileMarker severity to Diagnostic severity
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="markerLevel"></param>
|
||||||
|
internal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,22 +5,28 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.SqlTools.EditorServices.Utility;
|
using Microsoft.SqlTools.EditorServices.Utility;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
using Microsoft.SqlTools.ServiceLayer.Hosting;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
||||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
using Microsoft.SqlTools.ServiceLayer.SqlContext;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
using Microsoft.SqlTools.ServiceLayer.Workspace;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
|
||||||
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
|
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
|
||||||
using Microsoft.SqlServer.Management.Common;
|
using Microsoft.SqlServer.Management.Common;
|
||||||
using Microsoft.SqlServer.Management.SqlParser;
|
using Microsoft.SqlServer.Management.SqlParser;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
|
|
||||||
|
using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
{
|
{
|
||||||
@@ -30,6 +36,42 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class LanguageService
|
public sealed class LanguageService
|
||||||
{
|
{
|
||||||
|
public const string DefaultBatchSeperator = "GO";
|
||||||
|
|
||||||
|
private const int DiagnosticParseDelay = 750;
|
||||||
|
|
||||||
|
private const int FindCompletionsTimeout = 3000;
|
||||||
|
|
||||||
|
private const int FindCompletionStartTimeout = 50;
|
||||||
|
|
||||||
|
private const int OnConnectionWaitTimeout = 30000;
|
||||||
|
|
||||||
|
private bool ShouldEnableAutocomplete()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionService connectionService = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
internal ConnectionService ConnectionServiceInstance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if(connectionService == null)
|
||||||
|
{
|
||||||
|
connectionService = ConnectionService.Instance;
|
||||||
|
}
|
||||||
|
return connectionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
connectionService = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region Singleton Instance Implementation
|
#region Singleton Instance Implementation
|
||||||
|
|
||||||
@@ -98,8 +140,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
serviceHost.SetRequestHandler(SignatureHelpRequest.Type, HandleSignatureHelpRequest);
|
serviceHost.SetRequestHandler(SignatureHelpRequest.Type, HandleSignatureHelpRequest);
|
||||||
serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest);
|
serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest);
|
||||||
serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest);
|
serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest);
|
||||||
serviceHost.SetRequestHandler(DocumentSymbolRequest.Type, HandleDocumentSymbolRequest);
|
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
|
||||||
serviceHost.SetRequestHandler(WorkspaceSymbolRequest.Type, HandleWorkspaceSymbolRequest);
|
|
||||||
|
|
||||||
// Register a no-op shutdown task for validation of the shutdown logic
|
// Register a no-op shutdown task for validation of the shutdown logic
|
||||||
serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) =>
|
serviceHost.RegisterShutdownTask(async (shutdownParams, shutdownRequestContext) =>
|
||||||
@@ -115,104 +156,47 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocChangeCallback(HandleDidChangeTextDocumentNotification);
|
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocChangeCallback(HandleDidChangeTextDocumentNotification);
|
||||||
|
|
||||||
// Register the file open update handler
|
// Register the file open update handler
|
||||||
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification);
|
WorkspaceService<SqlToolsSettings>.Instance.RegisterTextDocOpenCallback(HandleDidOpenTextDocumentNotification);
|
||||||
|
|
||||||
|
// Register a callback for when a connection is created
|
||||||
|
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateLanguageServiceOnConnection);
|
||||||
|
|
||||||
|
// Register a callback for when a connection is closed
|
||||||
|
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
||||||
|
|
||||||
// Store the SqlToolsContext for future use
|
// Store the SqlToolsContext for future use
|
||||||
Context = context;
|
Context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Request Handlers
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parses the SQL text and binds it to the SMO metadata provider if connected
|
/// Auto-complete completion provider request callback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath"></param>
|
/// <param name="textDocumentPosition"></param>
|
||||||
/// <param name="sqlText"></param>
|
/// <param name="requestContext"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
private static async Task HandleCompletionRequest(
|
||||||
|
TextDocumentPosition textDocumentPosition,
|
||||||
|
RequestContext<CompletionItem[]> requestContext)
|
||||||
{
|
{
|
||||||
ScriptParseInfo parseInfo = null;
|
// get the current list of completion items and return to client
|
||||||
if (this.ScriptParseInfoMap.ContainsKey(scriptFile.ClientFilePath))
|
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
|
||||||
{
|
textDocumentPosition.TextDocument.Uri);
|
||||||
parseInfo = this.ScriptParseInfoMap[scriptFile.ClientFilePath];
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse current SQL file contents to retrieve a list of errors
|
|
||||||
ParseOptions parseOptions = new ParseOptions();
|
|
||||||
ParseResult parseResult = Parser.IncrementalParse(
|
|
||||||
scriptFile.Contents,
|
|
||||||
parseInfo != null ? parseInfo.ParseResult : null,
|
|
||||||
parseOptions);
|
|
||||||
|
|
||||||
// save previous result for next incremental parse
|
|
||||||
if (parseInfo != null)
|
|
||||||
{
|
|
||||||
parseInfo.ParseResult = parseResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connInfo != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
List<ParseResult> parseResults = new List<ParseResult>();
|
|
||||||
parseResults.Add(parseResult);
|
|
||||||
parseInfo.Binder.Bind(
|
|
||||||
parseResults,
|
|
||||||
connInfo.ConnectionDetails.DatabaseName,
|
|
||||||
BindMode.Batch);
|
|
||||||
}
|
|
||||||
catch (ConnectionException)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
|
||||||
}
|
|
||||||
catch (SqlParserInternalBinderError)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of semantic diagnostic marks for the provided script file
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptFile"></param>
|
|
||||||
public ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
|
||||||
{
|
|
||||||
ConnectionInfo connInfo;
|
ConnectionInfo connInfo;
|
||||||
ConnectionService.Instance.TryFindConnection(
|
ConnectionService.Instance.TryFindConnection(
|
||||||
scriptFile.ClientFilePath,
|
scriptFile.ClientFilePath,
|
||||||
out connInfo);
|
out connInfo);
|
||||||
|
|
||||||
var parseResult = ParseAndBind(scriptFile, connInfo);
|
|
||||||
|
|
||||||
// build a list of SQL script file markers from the errors
|
var completionItems = Instance.GetCompletionItems(
|
||||||
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
textDocumentPosition, scriptFile, connInfo);
|
||||||
foreach (var error in parseResult.Errors)
|
|
||||||
{
|
|
||||||
markers.Add(new ScriptFileMarker()
|
|
||||||
{
|
|
||||||
Message = error.Message,
|
|
||||||
Level = ScriptFileMarkerLevel.Error,
|
|
||||||
ScriptRegion = new ScriptRegion()
|
|
||||||
{
|
|
||||||
File = scriptFile.FilePath,
|
|
||||||
StartLineNumber = error.Start.LineNumber,
|
|
||||||
StartColumnNumber = error.Start.ColumnNumber,
|
|
||||||
StartOffset = 0,
|
|
||||||
EndLineNumber = error.End.LineNumber,
|
|
||||||
EndColumnNumber = error.End.ColumnNumber,
|
|
||||||
EndOffset = 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return markers.ToArray();
|
await requestContext.SendResult(completionItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Request Handlers
|
|
||||||
|
|
||||||
private static async Task HandleDefinitionRequest(
|
private static async Task HandleDefinitionRequest(
|
||||||
TextDocumentPosition textDocumentPosition,
|
TextDocumentPosition textDocumentPosition,
|
||||||
RequestContext<Location[]> requestContext)
|
RequestContext<Location[]> requestContext)
|
||||||
@@ -261,22 +245,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
await Task.FromResult(true);
|
await Task.FromResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task HandleDocumentSymbolRequest(
|
|
||||||
DocumentSymbolParams documentSymbolParams,
|
|
||||||
RequestContext<SymbolInformation[]> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleDocumentSymbolRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task HandleWorkspaceSymbolRequest(
|
|
||||||
WorkspaceSymbolParams workspaceSymbolParams,
|
|
||||||
RequestContext<SymbolInformation[]> requestContext)
|
|
||||||
{
|
|
||||||
Logger.Write(LogLevel.Verbose, "HandleWorkspaceSymbolRequest");
|
|
||||||
await Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Handlers for Events from Other Services
|
#region Handlers for Events from Other Services
|
||||||
@@ -336,7 +304,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
|
|
||||||
foreach (var scriptFile in WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetOpenedFiles())
|
foreach (var scriptFile in WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetOpenedFiles())
|
||||||
{
|
{
|
||||||
await PublishScriptDiagnostics(scriptFile, emptyAnalysisDiagnostics, eventContext);
|
await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, emptyAnalysisDiagnostics, eventContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -352,7 +320,269 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Private Helpers
|
|
||||||
|
#region "AutoComplete Provider methods"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove a reference to an autocomplete cache from a URI. If
|
||||||
|
/// it is the last URI connected to a particular connection,
|
||||||
|
/// then remove the cache.
|
||||||
|
/// </summary>
|
||||||
|
public async Task RemoveAutoCompleteCacheUriReference(ConnectionSummary summary)
|
||||||
|
{
|
||||||
|
// currently this method is disabled, but we need to reimplement now that the
|
||||||
|
// implementation of the 'cache' has changed.
|
||||||
|
await Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the SQL text and binds it to the SMO metadata provider if connected
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath"></param>
|
||||||
|
/// <param name="sqlText"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public ParseResult ParseAndBind(ScriptFile scriptFile, ConnectionInfo connInfo)
|
||||||
|
{
|
||||||
|
ScriptParseInfo parseInfo = null;
|
||||||
|
if (this.ScriptParseInfoMap.ContainsKey(scriptFile.ClientFilePath))
|
||||||
|
{
|
||||||
|
parseInfo = this.ScriptParseInfoMap[scriptFile.ClientFilePath];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parseInfo = new ScriptParseInfo();
|
||||||
|
this.ScriptParseInfoMap.Add(scriptFile.ClientFilePath, parseInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionsTimeout))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
parseInfo.BuildingMetadataEvent.Reset();
|
||||||
|
|
||||||
|
// parse current SQL file contents to retrieve a list of errors
|
||||||
|
ParseResult parseResult = Parser.IncrementalParse(
|
||||||
|
scriptFile.Contents,
|
||||||
|
parseInfo.ParseResult,
|
||||||
|
parseInfo.ParseOptions);
|
||||||
|
|
||||||
|
parseInfo.ParseResult = parseResult;
|
||||||
|
|
||||||
|
if (connInfo != null && parseInfo.IsConnected)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<ParseResult> parseResults = new List<ParseResult>();
|
||||||
|
parseResults.Add(parseResult);
|
||||||
|
parseInfo.Binder.Bind(
|
||||||
|
parseResults,
|
||||||
|
connInfo.ConnectionDetails.DatabaseName,
|
||||||
|
BindMode.Batch);
|
||||||
|
}
|
||||||
|
catch (ConnectionException)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||||
|
}
|
||||||
|
catch (SqlParserInternalBinderError)
|
||||||
|
{
|
||||||
|
Logger.Write(LogLevel.Error, "Hit connection exception while binding - disposing binder object...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
parseInfo.BuildingMetadataEvent.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInfo.ParseResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the cached autocomplete candidate list when the user connects to a database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info"></param>
|
||||||
|
public async Task UpdateLanguageServiceOnConnection(ConnectionInfo info)
|
||||||
|
{
|
||||||
|
await Task.Run( () =>
|
||||||
|
{
|
||||||
|
if (ShouldEnableAutocomplete())
|
||||||
|
{
|
||||||
|
ScriptParseInfo scriptInfo =
|
||||||
|
this.ScriptParseInfoMap.ContainsKey(info.OwnerUri)
|
||||||
|
? this.ScriptParseInfoMap[info.OwnerUri]
|
||||||
|
: new ScriptParseInfo();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
scriptInfo.BuildingMetadataEvent.WaitOne(LanguageService.OnConnectionWaitTimeout);
|
||||||
|
scriptInfo.BuildingMetadataEvent.Reset();
|
||||||
|
|
||||||
|
var sqlConn = info.SqlConnection as SqlConnection;
|
||||||
|
if (sqlConn != null)
|
||||||
|
{
|
||||||
|
ServerConnection serverConn = new ServerConnection(sqlConn);
|
||||||
|
scriptInfo.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider();
|
||||||
|
scriptInfo.MetadataProvider = SmoMetadataProvider.CreateConnectedProvider(serverConn);
|
||||||
|
scriptInfo.Binder = BinderProvider.CreateBinder(scriptInfo.MetadataProvider);
|
||||||
|
scriptInfo.ServerConnection = new ServerConnection(sqlConn);
|
||||||
|
this.ScriptParseInfoMap[info.OwnerUri] = scriptInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
scriptInfo.IsConnected = false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Set Metadata Build event to Signal state.
|
||||||
|
// (Tell Language Service that I am ready with Metadata Provider Object)
|
||||||
|
scriptInfo.BuildingMetadataEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptInfo.IsConnected)
|
||||||
|
{
|
||||||
|
var scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(info.OwnerUri);
|
||||||
|
ParseAndBind(scriptFile, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a reparse and bind is required to provide autocomplete
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info"></param>
|
||||||
|
private bool RequiresReparse(ScriptParseInfo info, ScriptFile scriptFile)
|
||||||
|
{
|
||||||
|
if (info.ParseResult == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string prevSqlText = info.ParseResult.Script.Sql;
|
||||||
|
string currentSqlText = scriptFile.Contents;
|
||||||
|
|
||||||
|
return prevSqlText.Length != currentSqlText.Length
|
||||||
|
|| !string.Equals(prevSqlText, currentSqlText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the completion item list for the current text position.
|
||||||
|
/// This method does not await cache builds since it expects to return quickly
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="textDocumentPosition"></param>
|
||||||
|
public CompletionItem[] GetCompletionItems(
|
||||||
|
TextDocumentPosition textDocumentPosition,
|
||||||
|
ScriptFile scriptFile,
|
||||||
|
ConnectionInfo connInfo)
|
||||||
|
{
|
||||||
|
string filePath = textDocumentPosition.TextDocument.Uri;
|
||||||
|
int startLine = textDocumentPosition.Position.Line;
|
||||||
|
int startColumn = TextUtilities.PositionOfPrevDelimeter(
|
||||||
|
scriptFile.Contents,
|
||||||
|
textDocumentPosition.Position.Line,
|
||||||
|
textDocumentPosition.Position.Character);
|
||||||
|
int endColumn = textDocumentPosition.Position.Character;
|
||||||
|
|
||||||
|
// Take a reference to the list at a point in time in case we update and replace the list
|
||||||
|
if (connInfo == null
|
||||||
|
|| !LanguageService.Instance.ScriptParseInfoMap.ContainsKey(textDocumentPosition.TextDocument.Uri))
|
||||||
|
{
|
||||||
|
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reparse and bind the SQL statement if needed
|
||||||
|
var scriptParseInfo = ScriptParseInfoMap[textDocumentPosition.TextDocument.Uri];
|
||||||
|
if (RequiresReparse(scriptParseInfo, scriptFile))
|
||||||
|
{
|
||||||
|
ParseAndBind(scriptFile, connInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptParseInfo.ParseResult == null)
|
||||||
|
{
|
||||||
|
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptParseInfo.IsConnected
|
||||||
|
&& scriptParseInfo.BuildingMetadataEvent.WaitOne(LanguageService.FindCompletionStartTimeout))
|
||||||
|
{
|
||||||
|
scriptParseInfo.BuildingMetadataEvent.Reset();
|
||||||
|
Task<CompletionItem[]> findCompletionsTask = Task.Run(() => {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// get the completion list from SQL Parser
|
||||||
|
var suggestions = Resolver.FindCompletions(
|
||||||
|
scriptParseInfo.ParseResult,
|
||||||
|
textDocumentPosition.Position.Line + 1,
|
||||||
|
textDocumentPosition.Position.Character + 1,
|
||||||
|
scriptParseInfo.MetadataDisplayInfoProvider);
|
||||||
|
|
||||||
|
// convert the suggestion list to the VS Code format
|
||||||
|
return AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
|
||||||
|
suggestions,
|
||||||
|
startLine,
|
||||||
|
startColumn,
|
||||||
|
endColumn);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
scriptParseInfo.BuildingMetadataEvent.Set();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
findCompletionsTask.Wait(LanguageService.FindCompletionsTimeout);
|
||||||
|
if (findCompletionsTask.IsCompleted
|
||||||
|
&& findCompletionsTask.Result != null
|
||||||
|
&& findCompletionsTask.Result.Length > 0)
|
||||||
|
{
|
||||||
|
return findCompletionsTask.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AutoCompleteHelper.GetDefaultCompletionItems(startLine, startColumn, endColumn);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Diagnostic Provider methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of semantic diagnostic marks for the provided script file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scriptFile"></param>
|
||||||
|
internal ScriptFileMarker[] GetSemanticMarkers(ScriptFile scriptFile)
|
||||||
|
{
|
||||||
|
ConnectionInfo connInfo;
|
||||||
|
ConnectionService.Instance.TryFindConnection(
|
||||||
|
scriptFile.ClientFilePath,
|
||||||
|
out connInfo);
|
||||||
|
|
||||||
|
var parseResult = ParseAndBind(scriptFile, connInfo);
|
||||||
|
|
||||||
|
// build a list of SQL script file markers from the errors
|
||||||
|
List<ScriptFileMarker> markers = new List<ScriptFileMarker>();
|
||||||
|
foreach (var error in parseResult.Errors)
|
||||||
|
{
|
||||||
|
markers.Add(new ScriptFileMarker()
|
||||||
|
{
|
||||||
|
Message = error.Message,
|
||||||
|
Level = ScriptFileMarkerLevel.Error,
|
||||||
|
ScriptRegion = new ScriptRegion()
|
||||||
|
{
|
||||||
|
File = scriptFile.FilePath,
|
||||||
|
StartLineNumber = error.Start.LineNumber,
|
||||||
|
StartColumnNumber = error.Start.ColumnNumber,
|
||||||
|
StartOffset = 0,
|
||||||
|
EndLineNumber = error.End.LineNumber,
|
||||||
|
EndColumnNumber = error.End.ColumnNumber,
|
||||||
|
EndOffset = 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return markers.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs script diagnostics on changed files
|
/// Runs script diagnostics on changed files
|
||||||
@@ -401,7 +631,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
Task.Factory.StartNew(
|
Task.Factory.StartNew(
|
||||||
() =>
|
() =>
|
||||||
DelayThenInvokeDiagnostics(
|
DelayThenInvokeDiagnostics(
|
||||||
750,
|
LanguageService.DiagnosticParseDelay,
|
||||||
filesToAnalyze,
|
filesToAnalyze,
|
||||||
eventContext,
|
eventContext,
|
||||||
ExistingRequestCancellation.Token),
|
ExistingRequestCancellation.Token),
|
||||||
@@ -451,85 +681,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile);
|
ScriptFileMarker[] semanticMarkers = GetSemanticMarkers(scriptFile);
|
||||||
Logger.Write(LogLevel.Verbose, "Analysis complete.");
|
Logger.Write(LogLevel.Verbose, "Analysis complete.");
|
||||||
|
|
||||||
await PublishScriptDiagnostics(scriptFile, semanticMarkers, eventContext);
|
await DiagnosticsHelper.PublishScriptDiagnostics(scriptFile, semanticMarkers, eventContext);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send the diagnostic results back to the host application
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptFile"></param>
|
|
||||||
/// <param name="semanticMarkers"></param>
|
|
||||||
/// <param name="eventContext"></param>
|
|
||||||
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()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert a ScriptFileMarker to a Diagnostic that is Language Service compatible
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="scriptFileMarker"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static Diagnostic GetDiagnosticFromMarker(ScriptFileMarker scriptFileMarker)
|
|
||||||
{
|
|
||||||
return new Diagnostic
|
|
||||||
{
|
|
||||||
Severity = MapDiagnosticSeverity(scriptFileMarker.Level),
|
|
||||||
Message = scriptFileMarker.Message,
|
|
||||||
Range = new Range
|
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Map ScriptFileMarker severity to Diagnostic severity
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="markerLevel"></param>
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,14 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
using Microsoft.SqlServer.Management.Common;
|
||||||
|
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
using Microsoft.SqlServer.Management.SqlParser.Binder;
|
||||||
|
using Microsoft.SqlServer.Management.SqlParser.Common;
|
||||||
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
|
||||||
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
using Microsoft.SqlServer.Management.SqlParser.Parser;
|
||||||
using Microsoft.SqlServer.Management.SmoMetadataProvider;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
||||||
{
|
{
|
||||||
@@ -15,6 +19,110 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class ScriptParseInfo
|
internal class ScriptParseInfo
|
||||||
{
|
{
|
||||||
|
private ManualResetEvent buildingMetadataEvent = new ManualResetEvent(initialState: true);
|
||||||
|
|
||||||
|
private ParseOptions parseOptions = new ParseOptions();
|
||||||
|
|
||||||
|
private ServerConnection serverConnection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event which tells if MetadataProvider is built fully or not
|
||||||
|
/// </summary>
|
||||||
|
public ManualResetEvent BuildingMetadataEvent
|
||||||
|
{
|
||||||
|
get { return this.buildingMetadataEvent; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a flag determining is the LanguageService is connected
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConnected { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the LanguageService SMO ServerConnection
|
||||||
|
/// </summary>
|
||||||
|
public ServerConnection ServerConnection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.serverConnection;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.serverConnection = value;
|
||||||
|
this.parseOptions = new ParseOptions(
|
||||||
|
batchSeparator: LanguageService.DefaultBatchSeperator,
|
||||||
|
isQuotedIdentifierSet: true,
|
||||||
|
compatibilityLevel: DatabaseCompatibilityLevel,
|
||||||
|
transactSqlVersion: TransactSqlVersion);
|
||||||
|
this.IsConnected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Language Service ServerVersion
|
||||||
|
/// </summary>
|
||||||
|
public ServerVersion ServerVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.ServerConnection != null
|
||||||
|
? this.ServerConnection.ServerVersion
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current DataEngineType
|
||||||
|
/// </summary>
|
||||||
|
public DatabaseEngineType DatabaseEngineType
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.ServerConnection != null
|
||||||
|
? this.ServerConnection.DatabaseEngineType
|
||||||
|
: DatabaseEngineType.Standalone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current connections TransactSqlVersion
|
||||||
|
/// </summary>
|
||||||
|
public TransactSqlVersion TransactSqlVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.IsConnected
|
||||||
|
? GetTransactSqlVersion(this.ServerVersion)
|
||||||
|
: TransactSqlVersion.Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current DatabaseCompatibilityLevel
|
||||||
|
/// </summary>
|
||||||
|
public DatabaseCompatibilityLevel DatabaseCompatibilityLevel
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.IsConnected
|
||||||
|
? GetDatabaseCompatibilityLevel(this.ServerVersion)
|
||||||
|
: DatabaseCompatibilityLevel.Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current ParseOptions
|
||||||
|
/// </summary>
|
||||||
|
public ParseOptions ParseOptions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return this.parseOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SMO binder for schema-aware intellisense
|
/// Gets or sets the SMO binder for schema-aware intellisense
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -28,13 +136,63 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or set the SMO metadata provider that's bound to the current connection
|
/// Gets or set the SMO metadata provider that's bound to the current connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
|
||||||
public SmoMetadataProvider MetadataProvider { get; set; }
|
public SmoMetadataProvider MetadataProvider { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the SMO metadata display info provider
|
/// Gets or sets the SMO metadata display info provider
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
|
||||||
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
public MetadataDisplayInfoProvider MetadataDisplayInfoProvider { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the database compatibility level from a server version
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverVersion"></param>
|
||||||
|
private static DatabaseCompatibilityLevel GetDatabaseCompatibilityLevel(ServerVersion serverVersion)
|
||||||
|
{
|
||||||
|
int versionMajor = Math.Max(serverVersion.Major, 8);
|
||||||
|
|
||||||
|
switch (versionMajor)
|
||||||
|
{
|
||||||
|
case 8:
|
||||||
|
return DatabaseCompatibilityLevel.Version80;
|
||||||
|
case 9:
|
||||||
|
return DatabaseCompatibilityLevel.Version90;
|
||||||
|
case 10:
|
||||||
|
return DatabaseCompatibilityLevel.Version100;
|
||||||
|
case 11:
|
||||||
|
return DatabaseCompatibilityLevel.Version110;
|
||||||
|
case 12:
|
||||||
|
return DatabaseCompatibilityLevel.Version120;
|
||||||
|
case 13:
|
||||||
|
return DatabaseCompatibilityLevel.Version130;
|
||||||
|
default:
|
||||||
|
return DatabaseCompatibilityLevel.Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the transaction sql version from a server version
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverVersion"></param>
|
||||||
|
private static TransactSqlVersion GetTransactSqlVersion(ServerVersion serverVersion)
|
||||||
|
{
|
||||||
|
int versionMajor = Math.Max(serverVersion.Major, 9);
|
||||||
|
|
||||||
|
switch (versionMajor)
|
||||||
|
{
|
||||||
|
case 9:
|
||||||
|
case 10:
|
||||||
|
// In case of 10.0 we still use Version 10.5 as it is the closest available.
|
||||||
|
return TransactSqlVersion.Version105;
|
||||||
|
case 11:
|
||||||
|
return TransactSqlVersion.Version110;
|
||||||
|
case 12:
|
||||||
|
return TransactSqlVersion.Version120;
|
||||||
|
case 13:
|
||||||
|
return TransactSqlVersion.Version130;
|
||||||
|
default:
|
||||||
|
return TransactSqlVersion.Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ namespace Microsoft.SqlTools.ServiceLayer
|
|||||||
|
|
||||||
// Initialize the services that will be hosted here
|
// Initialize the services that will be hosted here
|
||||||
WorkspaceService<SqlToolsSettings>.Instance.InitializeService(serviceHost);
|
WorkspaceService<SqlToolsSettings>.Instance.InitializeService(serviceHost);
|
||||||
AutoCompleteService.Instance.InitializeService(serviceHost);
|
|
||||||
LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext);
|
LanguageService.Instance.InitializeService(serviceHost, sqlToolsContext);
|
||||||
ConnectionService.Instance.InitializeService(serviceHost);
|
ConnectionService.Instance.InitializeService(serviceHost);
|
||||||
CredentialService.Instance.InitializeService(serviceHost);
|
CredentialService.Instance.InitializeService(serviceHost);
|
||||||
|
|||||||
@@ -14,13 +14,10 @@ using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
|||||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reader for SSMS formatted file streams
|
/// Reader for service buffer formatted file streams
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ServiceBufferFileStreamReader : IFileStreamReader
|
public class ServiceBufferFileStreamReader : IFileStreamReader
|
||||||
{
|
{
|
||||||
// Most of this code is based on code from the Microsoft.SqlServer.Management.UI.Grid, SSMS DataStorage
|
|
||||||
// $\Data Tools\SSMS_XPlat\sql\ssms\core\DataStorage\src\FileStreamReader.cs
|
|
||||||
|
|
||||||
private const int DefaultBufferSize = 8192;
|
private const int DefaultBufferSize = 8192;
|
||||||
|
|
||||||
#region Member Variables
|
#region Member Variables
|
||||||
|
|||||||
@@ -14,13 +14,10 @@ using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
|||||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writer for SSMS formatted file streams
|
/// Writer for service buffer formatted file streams
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ServiceBufferFileStreamWriter : IFileStreamWriter
|
public class ServiceBufferFileStreamWriter : IFileStreamWriter
|
||||||
{
|
{
|
||||||
// Most of this code is based on code from the Microsoft.SqlServer.Management.UI.Grid, SSMS DataStorage
|
|
||||||
// $\Data Tools\SSMS_XPlat\sql\ssms\core\DataStorage\src\FileStreamWriter.cs
|
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
public const int DefaultBufferLength = 8192;
|
public const int DefaultBufferLength = 8192;
|
||||||
|
|||||||
@@ -8,12 +8,6 @@ namespace Microsoft.SqlTools.ServiceLayer.SqlContext
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SqlToolsSettings
|
public class SqlToolsSettings
|
||||||
{
|
{
|
||||||
// TODO: Is this needed? I can't make sense of this comment.
|
|
||||||
// NOTE: This property is capitalized as 'SqlTools' because the
|
|
||||||
// mode name sent from the client is written as 'SqlTools' and
|
|
||||||
// JSON.net is using camelCasing.
|
|
||||||
//public ServiceHostSettings SqlTools { get; set; }
|
|
||||||
|
|
||||||
public SqlToolsSettings()
|
public SqlToolsSettings()
|
||||||
{
|
{
|
||||||
this.ScriptAnalysis = new ScriptAnalysisSettings();
|
this.ScriptAnalysis = new ScriptAnalysisSettings();
|
||||||
|
|||||||
62
src/Microsoft.SqlTools.ServiceLayer/Utility/TextUtilities.cs
Normal file
62
src/Microsoft.SqlTools.ServiceLayer/Utility/TextUtilities.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// Copyright (c) Microsoft. All rights reserved.
|
||||||
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
|
//
|
||||||
|
|
||||||
|
namespace Microsoft.SqlTools.EditorServices.Utility
|
||||||
|
{
|
||||||
|
public static class TextUtilities
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Find the position of the previous delimeter for autocomplete token replacement.
|
||||||
|
/// SQL Parser may have similar functionality in which case we'll delete this method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sql"></param>
|
||||||
|
/// <param name="startRow"></param>
|
||||||
|
/// <param name="startColumn"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static int PositionOfPrevDelimeter(string sql, int startRow, int startColumn)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(sql))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int prevLineColumns = 0;
|
||||||
|
for (int i = 0; i < startRow; ++i)
|
||||||
|
{
|
||||||
|
while (sql[prevLineColumns] != '\n' && prevLineColumns < sql.Length)
|
||||||
|
{
|
||||||
|
++prevLineColumns;
|
||||||
|
}
|
||||||
|
++prevLineColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
startColumn += prevLineColumns;
|
||||||
|
|
||||||
|
if (startColumn - 1 < sql.Length)
|
||||||
|
{
|
||||||
|
while (--startColumn >= prevLineColumns)
|
||||||
|
{
|
||||||
|
if (sql[startColumn] == ' '
|
||||||
|
|| sql[startColumn] == '\t'
|
||||||
|
|| sql[startColumn] == '\n'
|
||||||
|
|| sql[startColumn] == '.'
|
||||||
|
|| sql[startColumn] == '+'
|
||||||
|
|| sql[startColumn] == '-'
|
||||||
|
|| sql[startColumn] == '*'
|
||||||
|
|| sql[startColumn] == '>'
|
||||||
|
|| sql[startColumn] == '<'
|
||||||
|
|| sql[startColumn] == '='
|
||||||
|
|| sql[startColumn] == '/'
|
||||||
|
|| sql[startColumn] == '%')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return startColumn + 1 - prevLineColumns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -164,7 +164,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
textDocument.Position.Character = 7;
|
textDocument.Position.Character = 7;
|
||||||
scriptFile.Contents = "select ";
|
scriptFile.Contents = "select ";
|
||||||
|
|
||||||
var autoCompleteService = AutoCompleteService.Instance;
|
var autoCompleteService = LanguageService.Instance;
|
||||||
var completions = autoCompleteService.GetCompletionItems(
|
var completions = autoCompleteService.GetCompletionItems(
|
||||||
textDocument,
|
textDocument,
|
||||||
scriptFile,
|
scriptFile,
|
||||||
|
|||||||
@@ -67,14 +67,6 @@ namespace Microsoft.SqlTools.Test.Utility
|
|||||||
return new LanguageService();
|
return new LanguageService();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a test autocomplete service instance
|
|
||||||
/// </summary>
|
|
||||||
public static AutoCompleteService GetAutoCompleteService()
|
|
||||||
{
|
|
||||||
return AutoCompleteService.Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a test sql connection factory instance
|
/// Creates a test sql connection factory instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user