diff --git a/src/Microsoft.SqlTools.ServiceLayer/Hosting/ServiceHost.cs b/src/Microsoft.SqlTools.ServiceLayer/Hosting/ServiceHost.cs
index 32b8301e..93136013 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/Hosting/ServiceHost.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/Hosting/ServiceHost.cs
@@ -27,6 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
/// prior to the process shutting down.
///
private const int ShutdownTimeoutInSeconds = 120;
+ public static readonly string[] CompletionTriggerCharacters = new string[] { ".", "-", ":", "\\", "[", "\"" };
#region Singleton Instance Code
@@ -135,7 +136,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
///
///
///
- private async Task HandleInitializeRequest(InitializeRequest initializeParams, RequestContext requestContext)
+ internal async Task HandleInitializeRequest(InitializeRequest initializeParams, RequestContext requestContext)
{
// Call all tasks that registered on the initialize request
var initializeTasks = initializeCallbacks.Select(t => t(initializeParams, requestContext));
@@ -157,7 +158,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
CompletionProvider = new CompletionOptions
{
ResolveProvider = true,
- TriggerCharacters = new string[] { ".", "-", ":", "\\", "[" }
+ TriggerCharacters = CompletionTriggerCharacters
},
SignatureHelpProvider = new SignatureHelpOptions
{
diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Completion/SqlCompletionItem.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Completion/SqlCompletionItem.cs
index 49e3fd44..59a25c05 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Completion/SqlCompletionItem.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Completion/SqlCompletionItem.cs
@@ -3,8 +3,8 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
-using System;
using System.Globalization;
+using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
@@ -19,6 +19,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
public class SqlCompletionItem
{
private static Regex ValidSqlNameRegex = new Regex(@"^[\p{L}_@][\p{L}\p{N}@$#_]{0,127}$");
+ private static DelimitedIdentifier BracketedIdentifiers = new DelimitedIdentifier { Start = "[", End = "]"};
+ private static DelimitedIdentifier[] DelimitedIdentifiers =
+ new DelimitedIdentifier[] { BracketedIdentifiers, new DelimitedIdentifier {Start = "\"", End = "\"" } };
///
/// Create new instance given the SQL parser declaration
@@ -44,12 +47,19 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
private void Init()
{
- InsertText = GetCompletionItemInsertName();
+ InsertText = DeclarationTitle;
+ DelimitedIdentifier delimitedIdentifier = GetDelimitedIdentifier(TokenText);
Label = DeclarationTitle;
- if (StartsWithBracket(TokenText) || AutoCompleteHelper.IsReservedWord(InsertText))
+
+ if (delimitedIdentifier == null && !string.IsNullOrEmpty(DeclarationTitle) &&
+ (!ValidSqlNameRegex.IsMatch(DeclarationTitle) || AutoCompleteHelper.IsReservedWord(InsertText)))
{
- Label = WithBracket(Label);
- InsertText = WithBracket(InsertText);
+ InsertText = WithDelimitedIdentifier(BracketedIdentifiers, DeclarationTitle);
+ }
+ if (delimitedIdentifier != null)
+ {
+ Label = WithDelimitedIdentifier(delimitedIdentifier, Label);
+ InsertText = WithDelimitedIdentifier(delimitedIdentifier, InsertText);
}
Detail = Label;
Kind = CreateCompletionItemKind();
@@ -143,7 +153,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
int row,
int startColumn,
int endColumn)
- {
+ {
CompletionItem item = new CompletionItem()
{
Label = label,
@@ -172,31 +182,22 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
return item;
}
- private string GetCompletionItemInsertName()
+ private bool HasDelimitedIdentifier(DelimitedIdentifier delimiteIidentifier, string text)
{
- string insertText = DeclarationTitle;
- if (!string.IsNullOrEmpty(DeclarationTitle) && !ValidSqlNameRegex.IsMatch(DeclarationTitle))
+ return text != null && delimiteIidentifier != null && text.StartsWith(delimiteIidentifier.Start)
+ && text.EndsWith(delimiteIidentifier.End);
+ }
+
+ private DelimitedIdentifier GetDelimitedIdentifier(string text)
+ {
+ return text != null ? DelimitedIdentifiers.FirstOrDefault(x => text.StartsWith(x.Start)) : null;
+ }
+
+ private string WithDelimitedIdentifier(DelimitedIdentifier delimiteIidentifier, string text)
+ {
+ if (!HasDelimitedIdentifier(delimiteIidentifier, text))
{
- insertText = WithBracket(DeclarationTitle);
- }
- return insertText;
- }
-
- private bool HasBrackets(string text)
- {
- return text != null && text.StartsWith("[") && text.EndsWith("]");
- }
-
- private bool StartsWithBracket(string text)
- {
- return text != null && text.StartsWith("[");
- }
-
- private string WithBracket(string text)
- {
- if (!HasBrackets(text))
- {
- return string.Format(CultureInfo.InvariantCulture, "[{0}]", text);
+ return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", delimiteIidentifier.Start, text, delimiteIidentifier.End);
}
else
{
@@ -204,4 +205,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
}
}
}
+
+ internal class DelimitedIdentifier
+ {
+ public string Start { get; set; }
+ public string End { get; set; }
+ }
}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs
index 35f697d1..793280ba 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/LanguageServiceTests.cs
@@ -161,17 +161,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
internal class TestScriptDocumentInfo : ScriptDocumentInfo
{
- public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo)
+ public TestScriptDocumentInfo(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile, ScriptParseInfo scriptParseInfo,
+ string tokenText = null)
:base(textDocumentPosition, scriptFile, scriptParseInfo)
{
-
+ this.tokenText = string.IsNullOrEmpty(tokenText) ? "doesntmatchanythingintheintellisensedefaultlist" : tokenText;
}
+
+ private string tokenText;
public override string TokenText
{
get
{
- return "doesntmatchanythingintheintellisensedefaultlist";
+ return this.tokenText;
}
}
}
@@ -194,5 +197,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, false);
}
+
+ [Fact]
+ public void GetDefaultCompletionListWithMatchesTest()
+ {
+ var scriptFile = new ScriptFile();
+ scriptFile.SetFileContents("koko wants a bananas");
+
+ ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = false };
+
+ var scriptDocumentInfo = new TestScriptDocumentInfo(
+ new TextDocumentPosition()
+ {
+ TextDocument = new TextDocumentIdentifier() { Uri = TestObjects.ScriptUri },
+ Position = new Position() { Line = 0, Character = 0 }
+ }, scriptFile, scriptInfo, "all");
+
+ CompletionItem[] result = AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, false);
+ Assert.Equal(result.Length, 1);
+
+ }
}
}
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/SqlCompletionItemTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/SqlCompletionItemTests.cs
index a1030578..f5f3486e 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/SqlCompletionItemTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/LanguageServer/SqlCompletionItemTests.cs
@@ -3,8 +3,8 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
+using System;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
-using Microsoft.SqlTools.ServiceLayer.LanguageServices;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Xunit;
@@ -325,6 +325,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
Assert.True(completionItem.InsertText.StartsWith("[") && completionItem.InsertText.EndsWith("]"));
}
+ [Fact]
+ public void ConstructorShouldThrowExceptionGivenEmptyDeclarionType()
+ {
+ string declarationTitle = "";
+ DeclarationType declarationType = DeclarationType.Table;
+ string tokenText = "";
+ Assert.Throws(() => new SqlCompletionItem(declarationTitle, declarationType, tokenText));
+ }
+
+ [Fact]
+ public void ConstructorShouldThrowExceptionGivenNullDeclarion()
+ {
+ string tokenText = "";
+ Assert.Throws(() => new SqlCompletionItem(null, tokenText));
+ }
+
[Fact]
public void InsertTextShouldIncludeBracketGivenNameWithSpecialCharacter()
{
@@ -431,23 +447,83 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
}
[Fact]
- public void LabelShouldIncBracketGivenReservedName()
+ public void LabelShouldIncludeQuotedIdentifiersGivenTokenWithQuotedIdentifier()
+ {
+ string declarationTitle = "name";
+ string expected = "\"" + declarationTitle + "\"";
+ DeclarationType declarationType = DeclarationType.Table;
+ string tokenText = "\"";
+ SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
+ CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
+
+ Assert.Equal(completionItem.Label, expected);
+ Assert.Equal(completionItem.InsertText, expected);
+ Assert.Equal(completionItem.Detail, expected);
+ }
+
+ [Fact]
+ public void LabelShouldIncludeQuotedIdentifiersGivenTokenWithQuotedIdentifiers()
+ {
+ string declarationTitle = "name";
+ string expected = "\"" + declarationTitle + "\"";
+ DeclarationType declarationType = DeclarationType.Table;
+ string tokenText = "\"\"";
+ SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
+ CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
+
+ Assert.Equal(completionItem.Label, expected);
+ Assert.Equal(completionItem.InsertText, expected);
+ Assert.Equal(completionItem.Detail, expected);
+ }
+
+ [Fact]
+ public void InsertTextShouldIncludeBracketGivenReservedName()
{
foreach (string word in ReservedWords)
{
string declarationTitle = word;
string expected = "[" + declarationTitle + "]";
DeclarationType declarationType = DeclarationType.Table;
- string tokenText = "[]";
+ string tokenText = "";
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
- Assert.Equal(completionItem.Label, expected);
+ Assert.Equal(completionItem.Label, word);
Assert.Equal(completionItem.InsertText, expected);
- Assert.Equal(completionItem.Detail, expected);
+ Assert.Equal(completionItem.Detail, word);
}
}
+ [Fact]
+ public void LabelShouldNotIncludeBracketIfTokenIncludesQuotedIdentifiersGivenReservedName()
+ {
+ string declarationTitle = "User";
+ string expected = "\"" + declarationTitle + "\"";
+ DeclarationType declarationType = DeclarationType.Table;
+ string tokenText = "\"";
+ SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
+ CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
+
+ Assert.Equal(completionItem.Label, expected);
+ Assert.Equal(completionItem.InsertText, expected);
+ Assert.Equal(completionItem.Detail, expected);
+ }
+
+ [Fact]
+ public void LabelShouldNotIncludeDoubleBracketIfTokenIncludesBracketsGivenReservedName()
+ {
+ string declarationTitle = "User";
+ string expected = "[" + declarationTitle + "]";
+ DeclarationType declarationType = DeclarationType.Table;
+ string tokenText = "[";
+ SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
+ CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
+
+ Assert.Equal(completionItem.Label, expected);
+ Assert.Equal(completionItem.InsertText, expected);
+ Assert.Equal(completionItem.Detail, expected);
+ }
+
[Fact]
public void KindShouldBeModuleGivenSchemaDeclarationType()
{
diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/ServiceHost/ServiceHostTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/ServiceHost/ServiceHostTests.cs
new file mode 100644
index 00000000..c4a2b22d
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.Test/ServiceHost/ServiceHostTests.cs
@@ -0,0 +1,28 @@
+//
+// 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.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
+using Moq;
+using Xunit;
+
+namespace Microsoft.SqlTools.ServiceLayer.Test.ServiceHost
+{
+ public class ServiceHostTests
+ {
+ [Fact]
+ public async void InitializeResultShouldIncludeTheCharactersThatWouldTriggerTheCompletion()
+ {
+ Hosting.ServiceHost host = Hosting.ServiceHost.Instance;
+ var requesContext = new Mock>();
+ requesContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object()));
+ await host.HandleInitializeRequest(new InitializeRequest(), requesContext.Object);
+ requesContext.Verify(x => x.SendResult(It.Is(
+ i => i.Capabilities.CompletionProvider.TriggerCharacters.All(t => Hosting.ServiceHost.CompletionTriggerCharacters.Contains(t)))));
+ }
+ }
+}