Implemented function signature help, and added tests (#153)

* Implemented function signature help, and added tests

* Incremental commit of changes from code review feedback

* Rename test

* Added check to make sure intellisense is enabled

* Use HoverTimeout instead of BindingTimeout
This commit is contained in:
Mitchell Sternke
2016-11-30 14:22:48 -08:00
committed by GitHub
parent 453ff9de15
commit c95933b7e1
3 changed files with 339 additions and 8 deletions

View File

@@ -15,6 +15,7 @@ using Microsoft.SqlServer.Management.SqlParser.Parser;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts;
using Microsoft.SqlTools.ServiceLayer.SqlContext;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.ServiceLayer.Workspace;
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
@@ -679,5 +680,77 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
return null;
}
}
/// <summary>
/// Converts a SQL Parser List of MethodHelpText objects into a VS Code SignatureHelp object
/// </summary>
internal static SignatureHelp ConvertMethodHelpTextListToSignatureHelp(List<Babel.MethodHelpText> methods, Babel.MethodNameAndParamLocations locations, int line, int column)
{
Validate.IsNotNull(nameof(methods), methods);
Validate.IsNotNull(nameof(locations), locations);
Validate.IsGreaterThan(nameof(line), line, 0);
Validate.IsGreaterThan(nameof(column), column, 0);
SignatureHelp help = new SignatureHelp();
help.Signatures = methods.Select(method =>
{
return new SignatureInformation()
{
// Signature label format: <name> param1, param2, ..., paramn RETURNS <type>
Label = method.Name + " " + method.Parameters.Select(parameter => parameter.Display).Aggregate((l, r) => l + "," + r) + " " + method.Type,
Documentation = method.Description,
Parameters = method.Parameters.Select(parameter =>
{
return new ParameterInformation()
{
Label = parameter.Display,
Documentation = parameter.Description
};
}).ToArray()
};
}).Where(method => method.Label.Contains(locations.Name)).ToArray();
if (help.Signatures.Length == 0)
{
return null;
}
// Find the matching method signature at the cursor's location
// For now, take the first match (since we've already filtered by name above)
help.ActiveSignature = 0;
// Determine the current parameter at the cursor
int currentParameter = -1; // Default case: not on any particular parameter
if (locations.ParamStartLocation != null)
{
// Is the cursor past the function name?
var location = locations.ParamStartLocation.Value;
if (line > location.LineNumber || (line == location.LineNumber && line == location.LineNumber && column >= location.ColumnNumber))
{
currentParameter = 0;
}
}
foreach (var location in locations.ParamSeperatorLocations)
{
// Is the cursor past a comma ',' and at least on the next parameter?
if (line > location.LineNumber || (line == location.LineNumber && column > location.ColumnNumber))
{
currentParameter++;
}
}
if (locations.ParamEndLocation != null)
{
// Is the cursor past the end of the parameter list on a different token?
var location = locations.ParamEndLocation.Value;
if (line > location.LineNumber || (line == location.LineNumber && line == location.LineNumber && column > location.ColumnNumber))
{
currentParameter = -1;
}
}
help.ActiveParameter = currentParameter;
return help;
}
}
}

View File

@@ -200,9 +200,9 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
// turn off until needed (10/28/2016)
// serviceHost.SetRequestHandler(DefinitionRequest.Type, HandleDefinitionRequest);
// serviceHost.SetRequestHandler(ReferencesRequest.Type, HandleReferencesRequest);
// serviceHost.SetRequestHandler(SignatureHelpRequest.Type, HandleSignatureHelpRequest);
// serviceHost.SetRequestHandler(DocumentHighlightRequest.Type, HandleDocumentHighlightRequest);
serviceHost.SetRequestHandler(SignatureHelpRequest.Type, HandleSignatureHelpRequest);
serviceHost.SetRequestHandler(CompletionResolveRequest.Type, HandleCompletionResolveRequest);
serviceHost.SetRequestHandler(HoverRequest.Type, HandleHoverRequest);
serviceHost.SetRequestHandler(CompletionRequest.Type, HandleCompletionRequest);
@@ -309,13 +309,6 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
await Task.FromResult(true);
}
private static async Task HandleSignatureHelpRequest(
TextDocumentPosition textDocumentPosition,
RequestContext<SignatureHelp> requestContext)
{
await Task.FromResult(true);
}
private static async Task HandleDocumentHighlightRequest(
TextDocumentPosition textDocumentPosition,
RequestContext<DocumentHighlight[]> requestContext)
@@ -324,6 +317,32 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
}
#endif
private static async Task HandleSignatureHelpRequest(
TextDocumentPosition textDocumentPosition,
RequestContext<SignatureHelp> requestContext)
{
// check if Intellisense suggestions are enabled
if (!WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings.IsSuggestionsEnabled)
{
await Task.FromResult(true);
}
else
{
ScriptFile scriptFile = WorkspaceService<SqlToolsSettings>.Instance.Workspace.GetFile(
textDocumentPosition.TextDocument.Uri);
SignatureHelp help = LanguageService.Instance.GetSignatureHelp(textDocumentPosition, scriptFile);
if (help != null)
{
await requestContext.SendResult(help);
}
else
{
await requestContext.SendResult(new SignatureHelp());
}
}
}
private static async Task HandleHoverRequest(
TextDocumentPosition textDocumentPosition,
RequestContext<Hover> requestContext)
@@ -690,6 +709,76 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
return null;
}
/// <summary>
/// Get function signature help for the current position
/// </summary>
internal SignatureHelp GetSignatureHelp(TextDocumentPosition textDocumentPosition, ScriptFile scriptFile)
{
int startLine = textDocumentPosition.Position.Line;
int startColumn = TextUtilities.PositionOfPrevDelimeter(
scriptFile.Contents,
textDocumentPosition.Position.Line,
textDocumentPosition.Position.Character);
int endColumn = textDocumentPosition.Position.Character;
ScriptParseInfo scriptParseInfo = GetScriptParseInfo(textDocumentPosition.TextDocument.Uri);
ConnectionInfo connInfo;
LanguageService.ConnectionServiceInstance.TryFindConnection(
scriptFile.ClientFilePath,
out connInfo);
// reparse and bind the SQL statement if needed
if (RequiresReparse(scriptParseInfo, scriptFile))
{
ParseAndBind(scriptFile, connInfo);
}
if (scriptParseInfo != null && scriptParseInfo.ParseResult != null)
{
if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock))
{
try
{
QueueItem queueItem = this.BindingQueue.QueueBindingOperation(
key: scriptParseInfo.ConnectionKey,
bindingTimeout: LanguageService.BindingTimeout,
bindOperation: (bindingContext, cancelToken) =>
{
// get the list of possible current methods for signature help
var methods = Resolver.FindMethods(
scriptParseInfo.ParseResult,
startLine + 1,
endColumn + 1,
bindingContext.MetadataDisplayInfoProvider);
// get positional information on the current method
var methodLocations = Resolver.GetMethodNameAndParams(scriptParseInfo.ParseResult,
startLine + 1,
endColumn + 1,
bindingContext.MetadataDisplayInfoProvider);
// convert from the parser format to the VS Code wire format
return AutoCompleteHelper.ConvertMethodHelpTextListToSignatureHelp(methods,
methodLocations,
startLine + 1,
endColumn + 1);
});
queueItem.ItemProcessed.WaitOne();
return queueItem.GetResultAsT<SignatureHelp>();
}
finally
{
Monitor.Exit(scriptParseInfo.BuildingMetadataLock);
}
}
}
// return null if there isn't a tooltip for the current location
return null;
}
/// <summary>
/// Return the completion item list for the current text position.
/// This method does not await cache builds since it expects to return quickly