added support for QUOTED_IDENTIFIER in auto complete list (#190)

*  added support for QUOTED_IDENTIFIER in auto complete list
This commit is contained in:
Leila Lali
2017-01-04 14:03:10 -08:00
committed by GitHub
parent 3ad7cc1a8b
commit 379decf4e9
5 changed files with 174 additions and 39 deletions

View File

@@ -27,6 +27,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
/// prior to the process shutting down. /// prior to the process shutting down.
/// </summary> /// </summary>
private const int ShutdownTimeoutInSeconds = 120; private const int ShutdownTimeoutInSeconds = 120;
public static readonly string[] CompletionTriggerCharacters = new string[] { ".", "-", ":", "\\", "[", "\"" };
#region Singleton Instance Code #region Singleton Instance Code
@@ -135,7 +136,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
/// <param name="initializeParams"></param> /// <param name="initializeParams"></param>
/// <param name="requestContext"></param> /// <param name="requestContext"></param>
/// <returns></returns> /// <returns></returns>
private async Task HandleInitializeRequest(InitializeRequest initializeParams, RequestContext<InitializeResult> requestContext) internal async Task HandleInitializeRequest(InitializeRequest initializeParams, RequestContext<InitializeResult> requestContext)
{ {
// Call all tasks that registered on the initialize request // Call all tasks that registered on the initialize request
var initializeTasks = initializeCallbacks.Select(t => t(initializeParams, requestContext)); var initializeTasks = initializeCallbacks.Select(t => t(initializeParams, requestContext));
@@ -157,7 +158,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Hosting
CompletionProvider = new CompletionOptions CompletionProvider = new CompletionOptions
{ {
ResolveProvider = true, ResolveProvider = true,
TriggerCharacters = new string[] { ".", "-", ":", "\\", "[" } TriggerCharacters = CompletionTriggerCharacters
}, },
SignatureHelpProvider = new SignatureHelpOptions SignatureHelpProvider = new SignatureHelpOptions
{ {

View File

@@ -3,8 +3,8 @@
// 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 System;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
@@ -19,6 +19,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
public class SqlCompletionItem public class SqlCompletionItem
{ {
private static Regex ValidSqlNameRegex = new Regex(@"^[\p{L}_@][\p{L}\p{N}@$#_]{0,127}$"); 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 = "\"" } };
/// <summary> /// <summary>
/// Create new instance given the SQL parser declaration /// Create new instance given the SQL parser declaration
@@ -44,12 +47,19 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
private void Init() private void Init()
{ {
InsertText = GetCompletionItemInsertName(); InsertText = DeclarationTitle;
DelimitedIdentifier delimitedIdentifier = GetDelimitedIdentifier(TokenText);
Label = DeclarationTitle; Label = DeclarationTitle;
if (StartsWithBracket(TokenText) || AutoCompleteHelper.IsReservedWord(InsertText))
if (delimitedIdentifier == null && !string.IsNullOrEmpty(DeclarationTitle) &&
(!ValidSqlNameRegex.IsMatch(DeclarationTitle) || AutoCompleteHelper.IsReservedWord(InsertText)))
{ {
Label = WithBracket(Label); InsertText = WithDelimitedIdentifier(BracketedIdentifiers, DeclarationTitle);
InsertText = WithBracket(InsertText); }
if (delimitedIdentifier != null)
{
Label = WithDelimitedIdentifier(delimitedIdentifier, Label);
InsertText = WithDelimitedIdentifier(delimitedIdentifier, InsertText);
} }
Detail = Label; Detail = Label;
Kind = CreateCompletionItemKind(); Kind = CreateCompletionItemKind();
@@ -172,31 +182,22 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
return item; return item;
} }
private string GetCompletionItemInsertName() private bool HasDelimitedIdentifier(DelimitedIdentifier delimiteIidentifier, string text)
{ {
string insertText = DeclarationTitle; return text != null && delimiteIidentifier != null && text.StartsWith(delimiteIidentifier.Start)
if (!string.IsNullOrEmpty(DeclarationTitle) && !ValidSqlNameRegex.IsMatch(DeclarationTitle)) && text.EndsWith(delimiteIidentifier.End);
{
insertText = WithBracket(DeclarationTitle);
}
return insertText;
} }
private bool HasBrackets(string text) private DelimitedIdentifier GetDelimitedIdentifier(string text)
{ {
return text != null && text.StartsWith("[") && text.EndsWith("]"); return text != null ? DelimitedIdentifiers.FirstOrDefault(x => text.StartsWith(x.Start)) : null;
} }
private bool StartsWithBracket(string text) private string WithDelimitedIdentifier(DelimitedIdentifier delimiteIidentifier, string text)
{ {
return text != null && text.StartsWith("["); if (!HasDelimitedIdentifier(delimiteIidentifier, text))
}
private string WithBracket(string text)
{ {
if (!HasBrackets(text)) return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", delimiteIidentifier.Start, text, delimiteIidentifier.End);
{
return string.Format(CultureInfo.InvariantCulture, "[{0}]", text);
} }
else else
{ {
@@ -204,4 +205,10 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
} }
} }
} }
internal class DelimitedIdentifier
{
public string Start { get; set; }
public string End { get; set; }
}
} }

View File

@@ -161,17 +161,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
internal class TestScriptDocumentInfo : ScriptDocumentInfo 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) :base(textDocumentPosition, scriptFile, scriptParseInfo)
{ {
this.tokenText = string.IsNullOrEmpty(tokenText) ? "doesntmatchanythingintheintellisensedefaultlist" : tokenText;
} }
private string tokenText;
public override string TokenText public override string TokenText
{ {
get get
{ {
return "doesntmatchanythingintheintellisensedefaultlist"; return this.tokenText;
} }
} }
} }
@@ -194,5 +197,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, false); 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);
}
} }
} }

View File

@@ -3,8 +3,8 @@
// 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 System;
using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Xunit; using Xunit;
@@ -325,6 +325,22 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
Assert.True(completionItem.InsertText.StartsWith("[") && completionItem.InsertText.EndsWith("]")); Assert.True(completionItem.InsertText.StartsWith("[") && completionItem.InsertText.EndsWith("]"));
} }
[Fact]
public void ConstructorShouldThrowExceptionGivenEmptyDeclarionType()
{
string declarationTitle = "";
DeclarationType declarationType = DeclarationType.Table;
string tokenText = "";
Assert.Throws<ArgumentException>(() => new SqlCompletionItem(declarationTitle, declarationType, tokenText));
}
[Fact]
public void ConstructorShouldThrowExceptionGivenNullDeclarion()
{
string tokenText = "";
Assert.Throws<ArgumentException>(() => new SqlCompletionItem(null, tokenText));
}
[Fact] [Fact]
public void InsertTextShouldIncludeBracketGivenNameWithSpecialCharacter() public void InsertTextShouldIncludeBracketGivenNameWithSpecialCharacter()
{ {
@@ -431,14 +447,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
} }
[Fact] [Fact]
public void LabelShouldIncBracketGivenReservedName() public void LabelShouldIncludeQuotedIdentifiersGivenTokenWithQuotedIdentifier()
{ {
foreach (string word in ReservedWords) string declarationTitle = "name";
{ string expected = "\"" + declarationTitle + "\"";
string declarationTitle = word;
string expected = "[" + declarationTitle + "]";
DeclarationType declarationType = DeclarationType.Table; DeclarationType declarationType = DeclarationType.Table;
string tokenText = "[]"; string tokenText = "\"";
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText); SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2); CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
@@ -446,6 +460,68 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServer
Assert.Equal(completionItem.InsertText, expected); Assert.Equal(completionItem.InsertText, expected);
Assert.Equal(completionItem.Detail, 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 = "";
SqlCompletionItem item = new SqlCompletionItem(declarationTitle, declarationType, tokenText);
CompletionItem completionItem = item.CreateCompletionItem(0, 1, 2);
Assert.Equal(completionItem.Label, word);
Assert.Equal(completionItem.InsertText, 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] [Fact]

View File

@@ -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<RequestContext<InitializeResult>>();
requesContext.Setup(x => x.SendResult(It.IsAny<InitializeResult>())).Returns(Task.FromResult(new object()));
await host.HandleInitializeRequest(new InitializeRequest(), requesContext.Object);
requesContext.Verify(x => x.SendResult(It.Is<InitializeResult>(
i => i.Capabilities.CompletionProvider.TriggerCharacters.All(t => Hosting.ServiceHost.CompletionTriggerCharacters.Contains(t)))));
}
}
}