public override SignatureHelp OnSignatureHelp(TextDocumentPosition parameters) { AnalyticsWrapper.Telemetry.TrackEvent("[SignatureHelp]", EventType.Completion); //Send event to analytics var fileCompiler = GetFileCompilerFromStringUri(parameters.uri); if (fileCompiler?.CompilationResultsForProgram?.ProcessedTokensDocumentSnapshot == null) //Semantic snapshot is not available { return(null); } var wrappedCodeElement = CodeElementFinder(fileCompiler, parameters.position).FirstOrDefault(); if (wrappedCodeElement == null) //No codeelements found { return(null); } var node = CompletionFactory.GetMatchingNode(fileCompiler, wrappedCodeElement); //Get procedure name or qualified name string procedureName = CompletionFactoryHelpers.GetProcedureNameFromTokens(wrappedCodeElement.ArrangedConsumedTokens); //Try to get procedure by its name var calledProcedures = node.SymbolTable.GetFunctions( p => p.Name.Equals(procedureName) || p.QualifiedName.ToString().Equals(procedureName), new List <SymbolTable.Scope> { SymbolTable.Scope.Declarations, SymbolTable.Scope.Intrinsic, SymbolTable.Scope.Namespace }); var signatureHelp = new SignatureHelp(); if (calledProcedures == null) { return(null); } if (calledProcedures.Count() == 1) { var calledProcedure = calledProcedures.First(); //Create and return SignatureHelp object signatureHelp.signatures = new SignatureInformation[1]; signatureHelp.signatures[0] = ProcedureSignatureHelper.SignatureHelperSignatureFormatter(calledProcedure); signatureHelp.activeSignature = 0; //Set the active signature as the one just created //Select the current parameter the user is expecting signatureHelp.activeParameter = ProcedureSignatureHelper.SignatureHelperParameterSelecter(calledProcedure, wrappedCodeElement, parameters.position); //There is only one possibility so the context can be set right now _SignatureCompletionContext = calledProcedure; return(signatureHelp); } //Else try to find the best matching signature //Get all given INPUT var givenInputParameters = CompletionFactoryHelpers.AggregateTokens( wrappedCodeElement.ArrangedConsumedTokens.SkipWhile(t => t.TokenType != TokenType.INPUT) .Skip(1) //Ignore the INPUT Token .TakeWhile(t => !(t.TokenType == TokenType.OUTPUT || t.TokenType == TokenType.IN_OUT))).ToList(); //Get all given OUTPUT var givenOutputParameters = CompletionFactoryHelpers.AggregateTokens( wrappedCodeElement.ArrangedConsumedTokens.SkipWhile(t => t.TokenType != TokenType.OUTPUT) .Skip(1) //Ignore the INPUT Token .TakeWhile(t => !(t.TokenType == TokenType.INPUT || t.TokenType == TokenType.IN_OUT))).ToList(); //Get all given INOUT var givenInoutParameters = CompletionFactoryHelpers.AggregateTokens( wrappedCodeElement.ArrangedConsumedTokens.SkipWhile(t => t.TokenType != TokenType.IN_OUT) .Skip(1) //Ignore the INPUT Token .TakeWhile(t => !(t.TokenType == TokenType.OUTPUT || t.TokenType == TokenType.INPUT))).ToList(); var totalGivenParameters = givenInputParameters.Count + givenInoutParameters.Count + givenOutputParameters.Count; var signatureInformation = new List <SignatureInformation>(); _FunctionDeclarationSignatureDictionary.Clear(); FunctionDeclaration bestmatchingProcedure = null; int previousMatchingWeight = 0, selectedSignatureIndex = 0; if (totalGivenParameters == 0) { foreach (var procedure in calledProcedures) //No parameters given, return all possibilities { var formattedSignatureInformation = ProcedureSignatureHelper.SignatureHelperSignatureFormatter(procedure); signatureInformation.Add(formattedSignatureInformation); _FunctionDeclarationSignatureDictionary.Add(formattedSignatureInformation, procedure); } } else { foreach (var procedure in calledProcedures) { //The commented parts allow to restrict the potential compatible signature to return to the client int matchingWeight = 0; //Test INPUT parameters matchingWeight = matchingWeight + ProcedureSignatureHelper.ParametersTester(procedure.Profile.InputParameters, givenInputParameters, node); //Test OUTPUT parameters matchingWeight = matchingWeight + ProcedureSignatureHelper.ParametersTester(procedure.Profile.OutputParameters, givenOutputParameters, node); //Test INOUT parameters matchingWeight = matchingWeight + ProcedureSignatureHelper.ParametersTester(procedure.Profile.InoutParameters, givenInoutParameters, node); if (matchingWeight > 0 && matchingWeight >= previousMatchingWeight && totalGivenParameters > 0) { if (matchingWeight > previousMatchingWeight) { signatureInformation.Clear(); //If the matchingWeight is superior than previous, it means that the previous signature is not precise enough. } var formattedSignatureInformation = ProcedureSignatureHelper.SignatureHelperSignatureFormatter(procedure); signatureInformation.Add(formattedSignatureInformation); _FunctionDeclarationSignatureDictionary.Add(formattedSignatureInformation, procedure); previousMatchingWeight = matchingWeight; bestmatchingProcedure = procedure; } selectedSignatureIndex++; } } signatureHelp.signatures = signatureInformation.ToArray(); if (signatureInformation.Count == 1) { _SignatureCompletionContext = bestmatchingProcedure; //Set the completion context signatureHelp.activeSignature = 0; //Select the only signature for the client signatureHelp.activeParameter = ProcedureSignatureHelper.SignatureHelperParameterSelecter(bestmatchingProcedure, wrappedCodeElement, parameters.position); //Select the current parameter } return(signatureHelp); }
/// <summary> /// Request to request completion at a given text document position. The request's /// parameter is of type[TextDocumentPosition](#TextDocumentPosition) the response /// is of type[CompletionItem[]](#CompletionItem) or a Thenable that resolves to such. /// </summary> public override List <CompletionItem> OnCompletion(TextDocumentPosition parameters) { var fileCompiler = GetFileCompilerFromStringUri(parameters.uri); if (fileCompiler == null) { return(null); } List <CompletionItem> items = new List <CompletionItem>(); if (fileCompiler.CompilationResultsForProgram != null && fileCompiler.CompilationResultsForProgram.ProcessedTokensDocumentSnapshot != null) { var wrappedCodeElements = CodeElementFinder(fileCompiler, parameters.position); if (wrappedCodeElements == null) { return(new List <CompletionItem>()); } Token userFilterToken = null; Token lastSignificantToken = null; //Try to get a significant token for competion and return the codeelement containing the matching token. CodeElement matchingCodeElement = CodeElementMatcher.MatchCompletionCodeElement(parameters.position, wrappedCodeElements, out userFilterToken, out lastSignificantToken); //Magic happens here var userFilterText = userFilterToken == null ? string.Empty : userFilterToken.Text; if (lastSignificantToken != null) { AnalyticsWrapper.Telemetry.TrackEvent("[Completion] " + lastSignificantToken.TokenType, EventType.Completion); switch (lastSignificantToken.TokenType) { case TokenType.PERFORM: { items.AddRange(CompletionFactory.GetCompletionPerformParagraph(fileCompiler, matchingCodeElement, userFilterToken)); break; } case TokenType.CALL: { _FunctionDeclarationSignatureDictionary.Clear(); //Clear to avoid key collision items.AddRange(CompletionFactory.GetCompletionForProcedure(fileCompiler, matchingCodeElement, userFilterToken, _FunctionDeclarationSignatureDictionary)); items.AddRange(CompletionFactory.GetCompletionForLibrary(fileCompiler, matchingCodeElement, userFilterToken)); break; } case TokenType.TYPE: { items.AddRange(CompletionFactory.GetCompletionForType(fileCompiler, matchingCodeElement, userFilterToken)); items.AddRange(CompletionFactory.GetCompletionForLibrary(fileCompiler, matchingCodeElement, userFilterToken)); break; } case TokenType.QualifiedNameSeparator: { items.AddRange(CompletionFactory.GetCompletionForQualifiedName(parameters.position, fileCompiler, matchingCodeElement, lastSignificantToken, userFilterToken, _FunctionDeclarationSignatureDictionary)); break; } case TokenType.INPUT: case TokenType.OUTPUT: case TokenType.IN_OUT: { items.AddRange(CompletionFactory.GetCompletionForProcedureParameter(parameters.position, fileCompiler, matchingCodeElement, userFilterToken, lastSignificantToken, _SignatureCompletionContext)); break; } case TokenType.MOVE: { items.AddRange(CompletionFactory.GetCompletionForVariable(fileCompiler, matchingCodeElement, da => da.Name.StartsWith(userFilterText, StringComparison.InvariantCultureIgnoreCase) && ((da.CodeElement != null && (da.CodeElement as DataDefinitionEntry).LevelNumber.Value < 88) || (da.CodeElement == null && da is IndexDefinition)))); //Ignore 88 level variable break; } case TokenType.TO: { items.AddRange(CompletionFactory.GetCompletionForTo(fileCompiler, matchingCodeElement, userFilterToken, lastSignificantToken)); break; } case TokenType.INTO: { items.AddRange(CompletionFactory.GetCompletionForVariable(fileCompiler, matchingCodeElement, v => v.Name.StartsWith(userFilterText, StringComparison.CurrentCultureIgnoreCase) && ((v.CodeElement as DataDefinitionEntry) != null && v.DataType == DataType.Alphabetic || v.DataType == DataType.Alphanumeric || v.DataType == DataType.AlphanumericEdited) )); break; } case TokenType.SET: { items.AddRange(CompletionFactory.GetCompletionForVariable(fileCompiler, matchingCodeElement, v => v.Name.StartsWith(userFilterText, StringComparison.CurrentCultureIgnoreCase) && (((v.CodeElement as DataDefinitionEntry) != null && (v.CodeElement as DataDefinitionEntry).LevelNumber.Value == 88) //Level 88 Variable || v.DataType == DataType.Numeric || //Numeric Integer Variable v.Usage == DataUsage.Pointer) //Or usage is pointer )); break; } case TokenType.OF: { items.AddRange(CompletionFactory.GetCompletionForOf(fileCompiler, matchingCodeElement, userFilterToken, parameters.position)); break; } default: break; } } else { //If no known keyword has been found, let's try to get the context and return available variables. if (matchingCodeElement == null && wrappedCodeElements.Any()) { userFilterToken = wrappedCodeElements.First().ArrangedConsumedTokens.FirstOrDefault( t => parameters.position.character <= t.StopIndex + 1 && parameters.position.character > t.StartIndex && t.Line == parameters.position.line + 1 && t.TokenType == TokenType.UserDefinedWord); //Get the userFilterToken to filter the results userFilterText = userFilterToken == null ? string.Empty : userFilterToken.Text; //Convert token to text items.AddRange(CompletionFactory.GetCompletionForVariable(fileCompiler, wrappedCodeElements.First(), da => da.Name.StartsWith(userFilterText, StringComparison.InvariantCultureIgnoreCase))); } else { //Return a default text to inform the user that completion is not available after the given token items.Add(new CompletionItem("Completion is not available in this context") { insertText = "" }); } } if (userFilterToken != null) { //Add the range object to let the client know the position of the user filter token var range = new Range(userFilterToken.Line - 1, userFilterToken.StartIndex, userFilterToken.Line - 1, userFilterToken.StopIndex + 1); //-1 on lne to 0 based / +1 on stop index to include the last character items = items.Select(c => { if (c.data != null && c.data.GetType().IsArray) { ((object[])c.data)[0] = range; } else { c.data = range; } return(c); }).ToList(); } } return(items); }