mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-17 02:51:45 -05:00
added support for QUOTED_IDENTIFIER in auto complete list (#190)
* added support for QUOTED_IDENTIFIER in auto complete list
This commit is contained in:
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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)))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user