adding a new event for when definition is requested (#167)

* sending telemetry events for intellisense usage
This commit is contained in:
Leila Lali
2016-12-08 14:05:42 -08:00
committed by GitHub
parent 54f30887cc
commit 0b295e78c2
16 changed files with 848 additions and 147 deletions

View File

@@ -0,0 +1,54 @@
//
// 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 System.Diagnostics;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
{
/// <summary>
/// Includes the objects created by auto completion service
/// </summary>
public class AutoCompletionResult
{
/// <summary>
/// Creates new instance
/// </summary>
public AutoCompletionResult()
{
Stopwatch = new Stopwatch();
Stopwatch.Start();
}
private Stopwatch Stopwatch { get; set; }
/// <summary>
/// Completes the results to calculate the duration
/// </summary>
public void CompleteResult(CompletionItem[] completionItems)
{
Stopwatch.Stop();
CompletionItems = completionItems;
}
/// <summary>
/// The number of milliseconds to process the result
/// </summary>
public double Duration
{
get
{
return Stopwatch.ElapsedMilliseconds;
}
}
/// <summary>
/// Completion list
/// </summary>
public CompletionItem[] CompletionItems { get; private set; }
}
}

View File

@@ -0,0 +1,166 @@
//
// 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 System.Threading;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
{
/// <summary>
/// A service to create auto complete list for given script document
/// </summary>
internal class CompletionService
{
private ConnectedBindingQueue BindingQueue { get; set; }
/// <summary>
/// Created new instance given binding queue
/// </summary>
public CompletionService(ConnectedBindingQueue bindingQueue)
{
BindingQueue = bindingQueue;
}
private ISqlParserWrapper sqlParserWrapper;
/// <summary>
/// SQL parser wrapper to create the completion list
/// </summary>
public ISqlParserWrapper SqlParserWrapper
{
get
{
if(this.sqlParserWrapper == null)
{
this.sqlParserWrapper = new SqlParserWrapper();
}
return this.sqlParserWrapper;
}
set
{
this.sqlParserWrapper = value;
}
}
/// <summary>
/// Creates a completion list given connection and document info
/// </summary>
public AutoCompletionResult CreateCompletions(
ConnectionInfo connInfo,
ScriptDocumentInfo scriptDocumentInfo,
bool useLowerCaseSuggestions)
{
AutoCompletionResult result = new AutoCompletionResult();
// check if the file is connected and the file lock is available
if (scriptDocumentInfo.ScriptParseInfo.IsConnected && Monitor.TryEnter(scriptDocumentInfo.ScriptParseInfo.BuildingMetadataLock))
{
try
{
QueueItem queueItem = AddToQueue(connInfo, scriptDocumentInfo.ScriptParseInfo, scriptDocumentInfo, useLowerCaseSuggestions);
// wait for the queue item
queueItem.ItemProcessed.WaitOne();
var completionResult = queueItem.GetResultAsT<AutoCompletionResult>();
if (completionResult != null && completionResult.CompletionItems != null && completionResult.CompletionItems.Length > 0)
{
result = completionResult;
}
else if (!ShouldShowCompletionList(scriptDocumentInfo.Token))
{
result.CompleteResult(AutoCompleteHelper.EmptyCompletionList);
}
}
finally
{
Monitor.Exit(scriptDocumentInfo.ScriptParseInfo.BuildingMetadataLock);
}
}
return result;
}
private QueueItem AddToQueue(
ConnectionInfo connInfo,
ScriptParseInfo scriptParseInfo,
ScriptDocumentInfo scriptDocumentInfo,
bool useLowerCaseSuggestions)
{
// queue the completion task with the binding queue
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
key: scriptParseInfo.ConnectionKey,
bindingTimeout: LanguageService.BindingTimeout,
bindOperation: (bindingContext, cancelToken) =>
{
return CreateCompletionsFromSqlParser(connInfo, scriptParseInfo, scriptDocumentInfo, bindingContext.MetadataDisplayInfoProvider);
},
timeoutOperation: (bindingContext) =>
{
// return the default list if the connected bind fails
return CreateDefaultCompletionItems(scriptParseInfo, scriptDocumentInfo, useLowerCaseSuggestions);
});
return queueItem;
}
private static bool ShouldShowCompletionList(Token token)
{
bool result = true;
if (token != null)
{
switch (token.Id)
{
case (int)Tokens.LEX_MULTILINE_COMMENT:
case (int)Tokens.LEX_END_OF_LINE_COMMENT:
result = false;
break;
}
}
return result;
}
private AutoCompletionResult CreateDefaultCompletionItems(ScriptParseInfo scriptParseInfo, ScriptDocumentInfo scriptDocumentInfo, bool useLowerCaseSuggestions)
{
AutoCompletionResult result = new AutoCompletionResult();
CompletionItem[] completionList = AutoCompleteHelper.GetDefaultCompletionItems(scriptDocumentInfo, useLowerCaseSuggestions);
result.CompleteResult(completionList);
return result;
}
private AutoCompletionResult CreateCompletionsFromSqlParser(
ConnectionInfo connInfo,
ScriptParseInfo scriptParseInfo,
ScriptDocumentInfo scriptDocumentInfo,
MetadataDisplayInfoProvider metadataDisplayInfoProvider)
{
AutoCompletionResult result = new AutoCompletionResult();
IEnumerable<Declaration> suggestions = SqlParserWrapper.FindCompletions(
scriptParseInfo.ParseResult,
scriptDocumentInfo.ParserLine,
scriptDocumentInfo.ParserColumn,
metadataDisplayInfoProvider);
// get the completion list from SQL Parser
scriptParseInfo.CurrentSuggestions = suggestions;
// convert the suggestion list to the VS Code format
CompletionItem[] completionList = AutoCompleteHelper.ConvertDeclarationsToCompletionItems(
scriptParseInfo.CurrentSuggestions,
scriptDocumentInfo.StartLine,
scriptDocumentInfo.StartColumn,
scriptDocumentInfo.EndColumn,
scriptDocumentInfo.TokenText);
result.CompleteResult(completionList);
//The bucket for number of milliseconds will take to send back auto complete list
connInfo.IntellisenseMetrics.UpdateMetrics(result.Duration, 1, (k2, v2) => v2 + 1);
return result;
}
}
}

View File

@@ -0,0 +1,206 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Management.SqlParser.Intellisense;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
{
/// <summary>
/// Creates a completion item from SQL parser declaration item
/// </summary>
public class SqlCompletionItem
{
private static Regex ValidSqlNameRegex = new Regex(@"^[\p{L}_@][\p{L}\p{N}@$#_]{0,127}$");
/// <summary>
/// Create new instance given the SQL parser declaration
/// </summary>
public SqlCompletionItem(Declaration declaration, string tokenText) :
this(declaration == null ? null : declaration.Title, declaration == null ? DeclarationType.Table : declaration.Type, tokenText)
{
}
/// <summary>
/// Creates new instance given declaration title and type
/// </summary>
public SqlCompletionItem(string declarationTitle, DeclarationType declarationType, string tokenText)
{
Validate.IsNotNullOrEmptyString("declarationTitle", declarationTitle);
DeclarationTitle = declarationTitle;
DeclarationType = declarationType;
TokenText = tokenText;
Init();
}
private void Init()
{
InsertText = GetCompletionItemInsertName();
Label = DeclarationTitle;
if (StartsWithBracket(TokenText))
{
Label = WithBracket(Label);
InsertText = WithBracket(InsertText);
}
Detail = Label;
Kind = CreateCompletionItemKind();
}
private CompletionItemKind CreateCompletionItemKind()
{
CompletionItemKind kind = CompletionItemKind.Variable;
switch (DeclarationType)
{
case DeclarationType.Schema:
kind = CompletionItemKind.Module;
break;
case DeclarationType.Column:
kind = CompletionItemKind.Field;
break;
case DeclarationType.Table:
case DeclarationType.View:
kind = CompletionItemKind.File;
break;
case DeclarationType.Database:
kind = CompletionItemKind.Method;
break;
case DeclarationType.ScalarValuedFunction:
case DeclarationType.TableValuedFunction:
case DeclarationType.BuiltInFunction:
kind = CompletionItemKind.Value;
break;
default:
kind = CompletionItemKind.Unit;
break;
}
return kind;
}
/// <summary>
/// Declaration Title
/// </summary>
public string DeclarationTitle { get; private set; }
/// <summary>
/// Token text from the editor
/// </summary>
public string TokenText { get; private set; }
/// <summary>
/// SQL declaration type
/// </summary>
public DeclarationType DeclarationType { get; private set; }
/// <summary>
/// Completion item label
/// </summary>
public string Label { get; private set; }
/// <summary>
/// Completion item kind
/// </summary>
public CompletionItemKind Kind { get; private set; }
/// <summary>
/// Completion insert text
/// </summary>
public string InsertText { get; private set; }
/// <summary>
/// Completion item detail
/// </summary>
public string Detail { get; private set; }
/// <summary>
/// Creates a completion item given the editor info
/// </summary>
public CompletionItem CreateCompletionItem(
int row,
int startColumn,
int endColumn)
{
return CreateCompletionItem(Label, Detail, InsertText, Kind, row, startColumn, endColumn);
}
/// <summary>
/// Creates a completion item
/// </summary>
public static CompletionItem CreateCompletionItem(
string label,
string detail,
string insertText,
CompletionItemKind kind,
int row,
int startColumn,
int endColumn)
{
CompletionItem item = new CompletionItem()
{
Label = label,
Kind = kind,
Detail = detail,
InsertText = insertText,
TextEdit = new TextEdit
{
NewText = insertText,
Range = new Range
{
Start = new Position
{
Line = row,
Character = startColumn
},
End = new Position
{
Line = row,
Character = endColumn
}
}
}
};
return item;
}
private string GetCompletionItemInsertName()
{
string insertText = DeclarationTitle;
if (!string.IsNullOrEmpty(DeclarationTitle) && !ValidSqlNameRegex.IsMatch(DeclarationTitle))
{
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);
}
else
{
return text;
}
}
}
}

View File

@@ -0,0 +1,34 @@
//
// 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.SqlServer.Management.SqlParser.MetadataProvider;
using Microsoft.SqlServer.Management.SqlParser.Parser;
namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Completion
{
/// <summary>
/// SqlParserWrapper interface
/// </summary>
public interface ISqlParserWrapper
{
IEnumerable<Declaration> FindCompletions(ParseResult parseResult, int line, int col, IMetadataDisplayInfoProvider displayInfoProvider);
}
/// <summary>
/// A wrapper class around SQL parser methods to make the operations testable
/// </summary>
public class SqlParserWrapper : ISqlParserWrapper
{
/// <summary>
/// Creates completion list given SQL script info
/// </summary>
public IEnumerable<Declaration> FindCompletions(ParseResult parseResult, int line, int col, IMetadataDisplayInfoProvider displayInfoProvider)
{
return Resolver.FindCompletions(parseResult, line, col, displayInfoProvider);
}
}
}