/// <nodoc/> public Result <SignatureHelp, ResponseError> SignatureHelp(TextDocumentPositionParams positionParams, CancellationToken token) { // TODO: support cancellation if (!TryFindNode(positionParams, out var nodeAtPosition)) { return(Result <SignatureHelp, ResponseError> .Success(new SignatureHelp())); } ICallExpression callExpression = GetContainingCallExpression(nodeAtPosition); if (callExpression == null) { Logger.LanguageServerNonCriticalInternalIssue(LoggingContext, FormattableStringEx.I($"Couldn't find call expression containing {nodeAtPosition.GetFormattedText()}")); return(Result <SignatureHelp, ResponseError> .Success(new SignatureHelp())); } var callSymbol = TypeChecker.GetSymbolAtLocation(callExpression.Expression); // If the user has typed a call expresion to a symbol (function) that doesn't exist (i.e. "foo.bar()") // Then just issue a debug writeline and a success instead of crashing. // There is going to be a red-line squiggle under it anyway. if (callSymbol == null) { Logger.LanguageServerNonCriticalInternalIssue(LoggingContext, FormattableStringEx.I($"Couldn't find symbol for call expression containing {nodeAtPosition.GetFormattedText()}")); return(Result <SignatureHelp, ResponseError> .Success(new SignatureHelp())); } var signature = TypeChecker.GetSignaturesOfType(TypeChecker.GetTypeAtLocation(callExpression.Expression), SignatureKind.Call).FirstOrDefault(); if (signature == null) { Logger.LanguageServerNonCriticalInternalIssue(LoggingContext, FormattableStringEx.I($"Couldn't find call signature for call expression containing {nodeAtPosition.GetFormattedText()}")); return(Result <SignatureHelp, ResponseError> .Success(new SignatureHelp())); } var functionDeclaration = DScriptFunctionSignature.FromSignature(callSymbol.Name, signature); var parameterInformations = functionDeclaration.FormattedParameterNames.Select(formattedParameterName => new ParameterInformation() { Label = formattedParameterName, }); int activeParameterIndex = DScriptFunctionSignature.GetActiveParameterIndex(callExpression, nodeAtPosition); var signatureHelp = new SignatureHelp() { Signatures = new SignatureInformation[] { new SignatureInformation() { Label = functionDeclaration.FormattedFullFunctionSignature, Parameters = parameterInformations.ToArray(), Documentation = DocumentationUtilities.GetDocumentationForSymbolAsString(callSymbol), }, }, ActiveParameter = activeParameterIndex, ActiveSignature = 0, }; return(Result <SignatureHelp, ResponseError> .Success(signatureHelp)); }
public async Task HandleRequestAsync_CSharpProjection_InvokesCSharpLanguageServer() { // Arrange var called = false; var expectedContents = new SumType <SumType <string, MarkedString>, SumType <string, MarkedString>[], MarkupContent>( new MarkedString() { Language = "markdown", Value = "Hover Details" } ); var lspResponse = new Hover() { Range = new Range() { Start = new Position(10, 0), End = new Position(10, 1) }, Contents = expectedContents }; var expectedItem = new Hover() { Range = new Range() { Start = new Position(0, 0), End = new Position(0, 1) }, Contents = expectedContents }; var hoverRequest = new TextDocumentPositionParams() { TextDocument = new TextDocumentIdentifier() { Uri = Uri }, Position = new Position(0, 1) }; var documentManager = new TestDocumentManager(); documentManager.AddDocument(Uri, Mock.Of <LSPDocumentSnapshot>()); var requestInvoker = new Mock <LSPRequestInvoker>(); requestInvoker .Setup(r => r.RequestServerAsync <TextDocumentPositionParams, Hover>(It.IsAny <string>(), It.IsAny <LanguageServerKind>(), It.IsAny <TextDocumentPositionParams>(), It.IsAny <CancellationToken>())) .Callback <string, LanguageServerKind, TextDocumentPositionParams, CancellationToken>((method, serverKind, hoverParams, ct) => { Assert.Equal(Methods.TextDocumentHoverName, method); Assert.Equal(LanguageServerKind.CSharp, serverKind); called = true; }) .Returns(Task.FromResult(lspResponse)); var projectionResult = new ProjectionResult() { LanguageKind = RazorLanguageKind.CSharp, }; var projectionProvider = new Mock <LSPProjectionProvider>(); projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny <LSPDocumentSnapshot>(), It.IsAny <Position>(), It.IsAny <CancellationToken>())).Returns(Task.FromResult(projectionResult)); var remappingResult = new RazorMapToDocumentRangeResponse() { Range = new Range() { Start = new Position(0, 0), End = new Position(0, 1) } }; var documentMappingProvider = new Mock <LSPDocumentMappingProvider>(); documentMappingProvider.Setup(d => d.MapToDocumentRangeAsync(RazorLanguageKind.CSharp, It.IsAny <Uri>(), It.IsAny <Range>(), It.IsAny <CancellationToken>())). Returns(Task.FromResult(remappingResult)); var hoverHandler = new HoverHandler(JoinableTaskContext, requestInvoker.Object, documentManager, projectionProvider.Object, documentMappingProvider.Object); // Act var result = await hoverHandler.HandleRequestAsync(hoverRequest, new ClientCapabilities(), CancellationToken.None).ConfigureAwait(false); // Assert Assert.True(called); Assert.Equal(expectedItem.Contents, result.Contents); Assert.Equal(expectedItem.Range, result.Range); }
public Task <DocumentHighlight[]> GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) => _protocol.ExecuteRequestAsync <TextDocumentPositionParams, DocumentHighlight[]>(Methods.TextDocumentDocumentHighlightName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
public Task <LSP.Location[]> GetTextDocumentImplementationsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) => _protocol.ExecuteRequestAsync <TextDocumentPositionParams, LSP.Location[]>(Methods.TextDocumentImplementationName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
public Task <Hover?> GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) => _requestHandlerProvider.ExecuteRequestAsync <TextDocumentPositionParams, Hover?>(_queue, Methods.TextDocumentHoverName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
public Task <SignatureHelp> GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) => _requestHandlerProvider.ExecuteRequestAsync <TextDocumentPositionParams, SignatureHelp>(_queue, Methods.TextDocumentSignatureHelpName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
public Task <LSP.SignatureHelp?> GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) { Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); return(RequestDispatcher.ExecuteRequestAsync <TextDocumentPositionParams, LSP.SignatureHelp?>(Queue, Methods.TextDocumentSignatureHelpName, textDocumentPositionParams, _clientCapabilities, ClientName, cancellationToken)); }
public Task <SumType <Location[]?, VSInternalReferenceItem[]?> > GoToImplementationAsync(TextDocumentPositionParams positionParams, CancellationToken cancellationToken) { if (positionParams is null) { throw new ArgumentNullException(nameof(positionParams)); } return(ExecuteRequestAsync <TextDocumentPositionParams, SumType <Location[]?, VSInternalReferenceItem[]?> >(Methods.TextDocumentImplementationName, positionParams, ClientCapabilities, cancellationToken)); }
/// <nodoc /> public Result <ArrayOrObject <Location, Location>, ResponseError> GetDefinitionAtPosition(TextDocumentPositionParams documentPosition, CancellationToken token) { return(m_gotoDefinitionProvider.GetDefinitionAtPosition(documentPosition, token)); }
public Task <SignatureHelp> GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) { Contract.ThrowIfNull(_clientCapabilities, $"{nameof(InitializeAsync)} has not been called."); return(_requestHandlerProvider.ExecuteRequestAsync <TextDocumentPositionParams, SignatureHelp>(_queue, Methods.TextDocumentSignatureHelpName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken)); }
/// <nodoc /> public Result <ArrayOrObject <CompletionItem, CompletionList>, ResponseError> Completion(TextDocumentPositionParams position, CancellationToken token) { return(m_autoCompleteProvider.Completion(position, token)); }
/// <nodoc /> public Result <SignatureHelp, ResponseError> SignatureHelp(TextDocumentPositionParams textDocumentPosition, CancellationToken token) { return(m_signatureHelpProvider.SignatureHelp(textDocumentPosition, token)); }
/// <nodoc /> public Result <Hover, ResponseError> Hover(TextDocumentPositionParams position, CancellationToken token) { return(m_hoverProvider.Hover(position, token)); }
/// <summary> /// Returns an array with all usages of the identifier at the given position (if any) as DocumentHighlights. /// Returns null if the given file is listed as to be ignored, /// or if some parameters are unspecified (null), /// or if the specified position is not a valid position within the currently processed file content, /// or if no identifier exists at the specified position at this time. /// </summary> public DocumentHighlight[] DocumentHighlights(TextDocumentPositionParams param) => ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.DocumentHighlights(param) : null;
/// <summary> /// Implements the "textDocument/hover" portion of the language server protocol. /// </summary> public Result <Hover, ResponseError> Hover(TextDocumentPositionParams positionParams, CancellationToken token) { // TODO: support cancellation Contract.Requires(positionParams.Position != null); if (!TryFindNode(positionParams, out var node)) { return(Result <Hover, ResponseError> .Success(null)); } // Label names are things such as "break", "continue", "jump" for which // we do not need to provide any hover. if (DScriptUtilities.IsLabelName(node)) { return(Result <Hover, ResponseError> .Success(null)); } // Attempt to get the type from the node var typeAtLocation = TypeChecker.GetTypeAtLocation(node); if ((typeAtLocation.Flags & TypeFlags.Any) != TypeFlags.None && node.Kind == SyntaxKind.Identifier) { // Now if we are likely to return any (unknown), and we are on an identifier // its parent is a type-reference then let's display that instead. // An example of this is "someTemplateVariable.merge<>" if (node.Parent?.Kind == SyntaxKind.TypeReference) { node = node.Parent; } else if (node.Parent?.Kind == SyntaxKind.QualifiedName) { // The same type of thing can happen with values referenced // from an imported value (i.e. qualified name) such as // import "A" from "SomeModule"; // let z = A.B.C.value; // "value" is the identifier and "A.B.C" are the qualified names. // As you walk up the chain, you will eventually find "A" which is // a type-reference. var nodeWalk = node.Parent; while (nodeWalk != null && nodeWalk.Kind == SyntaxKind.QualifiedName) { nodeWalk = nodeWalk.Parent; } if (nodeWalk.Kind == SyntaxKind.TypeReference) { node = nodeWalk; } } } // TODO: Not sure why GetSymbolAtLocation isn't enough var symbol = TypeChecker.GetSymbolAtLocation(node) ?? node.Symbol ?? node.ResolvedSymbol; // Find the first declaration. We will use it to special case a few things below. var firstDeclaration = symbol?.GetFirstDeclarationOrDefault(); // Grab the symbol name from the first declaration. string symbolAsString = firstDeclaration?.Name?.Text; // Handle a few special cases before we default to showing the type. // Typically these special cases are where hover is performed on a declaration of // things such as an enum, import statement (and eventually others) if (firstDeclaration != null && symbolAsString != null) { // Special case import * from "" and import {} from "" and import {x as y} from "" // So we can show the user a hover that says "import {} from 'module' so they can easily // see where it is coming from. if (firstDeclaration.Kind == SyntaxKind.ImportSpecifier || firstDeclaration.Kind == SyntaxKind.NamespaceImport) { IImportClause importClause; if (firstDeclaration.Kind == SyntaxKind.ImportSpecifier) { var importSpecifier = firstDeclaration.Cast <IImportSpecifier>(); var namedImports = importSpecifier.Parent.Cast <INamedImports>(); importClause = namedImports.Parent.Cast <IImportClause>(); } else { var namespaceImport = firstDeclaration.Cast <INamespaceImport>(); importClause = namespaceImport.Parent.Cast <IImportClause>(); } var importDeclaration = importClause.Parent.Cast <IImportDeclaration>(); return(CreateScriptHoverWithCodeSnippetAndDocumentation( symbol, node, string.Format(BuildXL.Ide.LanguageServer.Strings.HoverImportFormat, symbolAsString, importDeclaration.ModuleSpecifier.ToDisplayString()))); } // For interface declarations, just show a hover of "interface X" if (firstDeclaration.Kind == SyntaxKind.InterfaceDeclaration) { return(CreateScriptHoverWithCodeSnippetAndDocumentation(symbol, node, firstDeclaration.GetFormattedText())); // We have decided to show the formatted text of the declaration as it allows // the user to browse the entire interface. // If we decide we want a more conscise hover, we can uncomment the following // code. //var interfaceDeclaration = firstDeclaration.Cast<IInterfaceDeclaration>(); //return CreateScriptHoverWithCodeSnippetAndDocumentation(symbol, node, string.Format(Strings.HoverInterfaceFormat, interfaceDeclaration.Name.Text)); } // For enum declarations, show a hover of "enum { x, y, z}" if (firstDeclaration.Kind == SyntaxKind.EnumDeclaration) { return(CreateScriptHoverWithCodeSnippetAndDocumentation(symbol, node, firstDeclaration.GetFormattedText())); // We have decided to show the formatted text of the declaration as it allows // the user to browse the entire enumeration. // If we decide we want a more conscise hover, we can uncomment the following // code. /* * var enumDeclaration = firstDeclaration.Cast<IEnumDeclaration>(); * * var memberStrings = new List<string>(); * foreach (var member in enumDeclaration.Members) * { * memberStrings.Add(member.Cast<IEnumMember>().Name.Text); * } * * var memberString = memberStrings.Aggregate((current, next) => string.Format(Strings.HoverEnumMemberFormat, current, next)); * * return CreateScriptHoverWithCodeSnippetAndDocumentation(symbol, node, string.Format(Strings.HoverEnumFormat, enumDeclaration.Name.Text, memberString)); */ } // Handle things that are function-like (such as interface methods, functions, etc). var functionLike = firstDeclaration.As <IFunctionLikeDeclaration>(); if (functionLike != null) { return(CreateScriptHoverWithCodeSnippetAndDocumentation(symbol, node, functionLike.ToDisplayString())); } } // So if we can't find a suitable declaration for the node to display for hover, // Then we will display the type associated the symbol, which is super useful // for things like variable declarations, etc. (You'll notice that variable declarations are not // handled above, for exactly this purpose). if (node != null && symbol != null) { return(CreateHoverBasedOnTypeInformation(symbol, node, symbolAsString)); } return(Result <Hover, ResponseError> .Success(null)); }
/// <summary> /// Returns a list of suggested completion items for the given location. /// <para/> /// Returns null if the given file is listed as to be ignored, or if the given parameter or its uri or position is null. /// </summary> public CompletionList Completions(TextDocumentPositionParams param) => ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.Completions(param) : null;
/// <nodoc /> public Result <ArrayOrObject <Location, Location>, ResponseError> GetDefinitionAtPosition(TextDocumentPositionParams @params, CancellationToken token) { // TODO: support cancellation if (!TryFindNode(@params, out var node)) { return(SilentError()); } // TODO: consider special hint/message if the current node is a template expression with interpolated expressions. if (node.Kind == SyntaxKind.NoSubstitutionTemplateLiteral && node.Parent.Kind == SyntaxKind.TaggedTemplateExpression && node.Parent.Cast <ITaggedTemplateExpression>().IsWellKnownTemplateExpression(out var name)) { return(DefinitionForTemplateExpression(node, name)); } var result = GetDefinitionFromVariableDeclarationList(node); if (result != null) { return(result); } if (DScriptUtilities.IsJumpStatementTarget(node)) { var labelName = node.Cast <IIdentifier>().Text; var label = DScriptUtilities.GetTargetLabel(node.Parent, labelName); // TODO: saqadri - Port // return label ? [createDefinitionInfo(label, ScriptElementKind.label, labelName, /*containerName*/ undefined)] : undefined; return(Success(new[] { GetLocationFromNode(label), })); } // TODO: saqadri - port // Triple slash reference comments // const comment = forEach(sourceFile.referencedFiles, r => (r.pos <= position && position < r.end) ? r : undefined); // if (comment) // { // const referenceFile = tryResolveScriptReference(program, sourceFile, comment); // if (referenceFile) // { // return [{ // fileName: referenceFile.fileName, // textSpan: createTextSpanFromBounds(0, 0), // kind: ScriptElementKind.scriptElement, // name: comment.fileName, // containerName: undefined, // containerKind: undefined // }]; // } // return undefined; // } var symbol = TypeChecker.GetSymbolAtLocation(node) ?? node.Symbol ?? node.ResolvedSymbol; // Could not find a symbol e.g. node is string or number keyword, // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol if (symbol == null) { // return null; return(SilentError()); } // If this is an alias, and the request came at the declaration location // get the aliased symbol instead. This allows for goto def on an import e.g. // import {A, B} from "mod"; // to jump to the implementation directly. if ((symbol.Flags & SymbolFlags.Alias) != SymbolFlags.None) { var declaration = symbol.Declarations.FirstOrDefault(); // Go to the original declaration for cases: // // (1) when the aliased symbol was declared in the location(parent). // (2) when the aliased symbol is originating from a named import. if (node.Kind == SyntaxKind.Identifier && declaration != null && (node.Parent.ResolveUnionType() == declaration.ResolveUnionType() || (declaration.Kind == SyntaxKind.ImportSpecifier && declaration?.Parent.Kind == SyntaxKind.NamedImports))) { symbol = TypeChecker.GetAliasedSymbol(symbol); } } // Because name in short-hand property assignment has two different meanings: property name and property value, // using go-to-definition at such position should go to the variable declaration of the property value rather than // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition // is performed at the location of property access, we would like to go to definition of the property in the short-hand // assignment. This case and others are handled by the following code. if (node.Parent.Kind == SyntaxKind.ShorthandPropertyAssignment) { var shorthandSymbol = TypeChecker.GetShorthandAssignmentValueSymbol(symbol.ValueDeclaration); if (shorthandSymbol == null) { return(Success(new Location[] { })); } var shorthandDeclaratons = shorthandSymbol.GetDeclarations(); // var shorthandSymbolKind = GetSymbolKind(shorthandSymbol, node); // var shorthandSymbolName = TypeChecker.SymbolToString(shorthandSymbol); // var shorthandContainerName = TypeChecker.SymbolToString(shorthandSymbol.Parent, node); return(Success(shorthandDeclaratons.Select( declaration => GetLocationFromNode(declaration)).ToArray())); } if (DScriptUtilities.IsNameOfPropertyAssignment(node)) { return(GetDefinitionForPropertyAssignment(node)); } return(GetDefinitionFromSymbol(symbol, node)); }
public async Task HandleRequestAsync_CSharpProjection_InvokesCSharpLanguageServer_FailsRemappingResultRangeWithHostVersionChanged() { // Arrange var called = false; var expectedContents = new SumType <SumType <string, MarkedString>, SumType <string, MarkedString>[], MarkupContent>( new MarkedString() { Language = "markdown", Value = "Hover Details" } ); var lspResponse = new Hover() { Range = new Range() { Start = new Position(10, 0), End = new Position(10, 1) }, Contents = expectedContents }; var hoverRequest = new TextDocumentPositionParams() { TextDocument = new TextDocumentIdentifier() { Uri = Uri }, Position = new Position(0, 1) }; var requestInvoker = new Mock <LSPRequestInvoker>(MockBehavior.Strict); requestInvoker .Setup(r => r.ReinvokeRequestOnServerAsync <TextDocumentPositionParams, Hover>( It.IsAny <ITextBuffer>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <TextDocumentPositionParams>(), It.IsAny <CancellationToken>())) .Callback <ITextBuffer, string, string, TextDocumentPositionParams, CancellationToken>((textBuffer, method, clientName, hoverParams, ct) => { Assert.Equal(Methods.TextDocumentHoverName, method); Assert.Equal(RazorLSPConstants.RazorCSharpLanguageServerName, clientName); called = true; }) .Returns(Task.FromResult(new ReinvocationResponse <Hover>("LanguageClientName", lspResponse))); var projectionResult = new ProjectionResult() { LanguageKind = RazorLanguageKind.CSharp, }; var projectionProvider = new Mock <LSPProjectionProvider>(MockBehavior.Strict); projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny <LSPDocumentSnapshot>(), It.IsAny <Position>(), It.IsAny <CancellationToken>())).Returns(Task.FromResult(projectionResult)); var remappingResult = new RazorMapToDocumentRangesResponse() { Ranges = new[] { new Range() }, HostDocumentVersion = 1 }; var documentMappingProvider = new Mock <LSPDocumentMappingProvider>(MockBehavior.Strict); documentMappingProvider.Setup(d => d.MapToDocumentRangesAsync(RazorLanguageKind.CSharp, It.IsAny <Uri>(), It.IsAny <Range[]>(), It.IsAny <CancellationToken>())). Returns(Task.FromResult(remappingResult)); var hoverHandler = new HoverHandler(requestInvoker.Object, DocumentManager, projectionProvider.Object, documentMappingProvider.Object, LoggerProvider); // Act var result = await hoverHandler.HandleRequestAsync(hoverRequest, new ClientCapabilities(), CancellationToken.None).ConfigureAwait(false); // Assert Assert.True(called); Assert.Null(result); }
public async Task HandleRequestAsync_CSharpProjection_InvokesCSharpLanguageServer() { // Arrange var invokedLSPRequest = false; var invokedRemapRequest = false; var expectedLocation = GetLocation(5, 5, 5, 5, Uri); var virtualCSharpUri = new Uri("C:/path/to/file.razor.g.cs"); var csharpLocation = GetLocation(100, 100, 100, 100, virtualCSharpUri); var requestInvoker = new Mock <LSPRequestInvoker>(MockBehavior.Strict); requestInvoker .Setup(r => r.ReinvokeRequestOnServerAsync <TextDocumentPositionParams, Location[]>( It.IsAny <ITextBuffer>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <TextDocumentPositionParams>(), It.IsAny <CancellationToken>())) .Callback <ITextBuffer, string, string, TextDocumentPositionParams, CancellationToken>((textBuffer, method, clientName, implementationParams, ct) => { Assert.Equal(Methods.TextDocumentImplementationName, method); Assert.Equal(RazorLSPConstants.RazorCSharpLanguageServerName, clientName); invokedLSPRequest = true; }) .Returns(Task.FromResult(new ReinvocationResponse <Location[]>("LanguageClientName", new[] { csharpLocation }))); var projectionResult = new ProjectionResult() { LanguageKind = RazorLanguageKind.CSharp, }; var projectionProvider = new Mock <LSPProjectionProvider>(MockBehavior.Strict); projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny <LSPDocumentSnapshot>(), It.IsAny <Position>(), It.IsAny <CancellationToken>())).Returns(Task.FromResult(projectionResult)); var documentMappingProvider = new Mock <LSPDocumentMappingProvider>(MockBehavior.Strict); documentMappingProvider .Setup(d => d.RemapLocationsAsync(It.IsAny <Location[]>(), It.IsAny <CancellationToken>())) .Callback <Location[], CancellationToken>((locations, token) => { Assert.Equal(csharpLocation, locations[0]); invokedRemapRequest = true; }) .Returns(Task.FromResult(Array.Empty <Location>())); var implementationHandler = new GoToImplementationHandler(requestInvoker.Object, DocumentManager, projectionProvider.Object, documentMappingProvider.Object, LoggerProvider); var implementationRequest = new TextDocumentPositionParams() { TextDocument = new TextDocumentIdentifier() { Uri = Uri }, Position = new Position(10, 5) }; // Act var result = await implementationHandler.HandleRequestAsync(implementationRequest, new ClientCapabilities(), CancellationToken.None).ConfigureAwait(false); // Assert Assert.True(invokedLSPRequest); Assert.True(invokedRemapRequest); // Actual remapping behavior is tested elsewhere. }
public Task <LSP.Location[]> GetTextDocumentImplementationsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) => _requestHandlerProvider.ExecuteRequestAsync <TextDocumentPositionParams, LSP.Location[]>(_queue, Methods.TextDocumentImplementationName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
public override async Task <SignatureHelp> SignatureHelp(TextDocumentPositionParams @params, CancellationToken token) { var uri = @params.textDocument.uri; TraceMessage($"Signatures in {uri} at {@params.position}"); var analysis = GetEntry(@params.textDocument) is ProjectEntry entry ? await entry.GetAnalysisAsync(waitingTimeout : 50, cancellationToken : token) : null; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); return(new SignatureHelp()); } ProjectFiles.GetEntry(@params.textDocument, @params._version, out _, out var tree); IEnumerable <IOverloadResult> overloads; int activeSignature = -1, activeParameter = -1; if (!string.IsNullOrEmpty(@params._expr)) { TraceMessage($"Getting signatures for {@params._expr}"); overloads = analysis.GetSignatures(@params._expr, @params.position); } else { var finder = new ExpressionFinder(tree, new GetExpressionOptions { Calls = true }); var index = tree.LocationToIndex(@params.position); if (finder.GetExpression(@params.position) is CallExpression callExpr) { TraceMessage($"Getting signatures for {callExpr.ToCodeString(tree, CodeFormattingOptions.Traditional)}"); overloads = analysis.GetSignatures(callExpr.Target, @params.position); activeParameter = -1; if (callExpr.GetArgumentAtIndex(tree, index, out activeParameter) && activeParameter < 0) { // Returned 'true' and activeParameter == -1 means that we are after // the trailing comma, so assume partially typed expression such as 'pow(x, y, |) activeParameter = callExpr.Args.Count; } } else { TraceMessage($"No signatures found in {uri} at {@params.position}"); return(new SignatureHelp()); } } var sigs = overloads.Select(ToSignatureInformation).ToArray(); if (activeParameter >= 0 && activeSignature < 0) { // TODO: Better selection of active signature activeSignature = sigs .Select((s, i) => Tuple.Create(s, i)) .OrderBy(t => t.Item1.parameters.Length) .FirstOrDefault(t => t.Item1.parameters.Length > activeParameter) ?.Item2 ?? -1; } activeSignature = activeSignature >= 0 ? activeSignature : (sigs.Length > 0 ? 0 : -1); return(new SignatureHelp { signatures = sigs, activeSignature = activeSignature, activeParameter = activeParameter }); }
public async Task HandleRequestAsync_HtmlProjection_InvokesHtmlLanguageServer() { // Arrange var invokedLSPRequest = false; var invokedRemapRequest = false; var expectedLocation = GetLocation(5, 5, 5, 5, Uri); var documentManager = new TestDocumentManager(); documentManager.AddDocument(Uri, Mock.Of <LSPDocumentSnapshot>()); var virtualHtmlUri = new Uri("C:/path/to/file.razor__virtual.html"); var htmlLocation = GetLocation(100, 100, 100, 100, virtualHtmlUri); var requestInvoker = new Mock <LSPRequestInvoker>(MockBehavior.Strict); requestInvoker .Setup(r => r.ReinvokeRequestOnServerAsync <TextDocumentPositionParams, Location[]>(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <TextDocumentPositionParams>(), It.IsAny <CancellationToken>())) .Callback <string, string, TextDocumentPositionParams, CancellationToken>((method, serverContentType, definitionParams, ct) => { Assert.Equal(Methods.TextDocumentDefinitionName, method); Assert.Equal(RazorLSPConstants.HtmlLSPContentTypeName, serverContentType); invokedLSPRequest = true; }) .Returns(Task.FromResult(new[] { htmlLocation })); var projectionResult = new ProjectionResult() { LanguageKind = RazorLanguageKind.Html, }; var projectionProvider = new Mock <LSPProjectionProvider>(MockBehavior.Strict); projectionProvider.Setup(p => p.GetProjectionAsync(It.IsAny <LSPDocumentSnapshot>(), It.IsAny <Position>(), It.IsAny <CancellationToken>())).Returns(Task.FromResult(projectionResult)); var documentMappingProvider = new Mock <LSPDocumentMappingProvider>(); documentMappingProvider .Setup(d => d.RemapLocationsAsync(It.IsAny <Location[]>(), It.IsAny <CancellationToken>())) .Callback <Location[], CancellationToken>((locations, token) => { Assert.Equal(htmlLocation, locations[0]); invokedRemapRequest = true; }) .Returns(Task.FromResult(Array.Empty <Location>())); var definitionHandler = new GoToDefinitionHandler(requestInvoker.Object, documentManager, projectionProvider.Object, documentMappingProvider.Object); var definitionRequest = new TextDocumentPositionParams() { TextDocument = new TextDocumentIdentifier() { Uri = Uri }, Position = new Position(10, 5) }; // Act var result = await definitionHandler.HandleRequestAsync(definitionRequest, new ClientCapabilities(), CancellationToken.None).ConfigureAwait(false); // Assert Assert.True(invokedLSPRequest); Assert.True(invokedRemapRequest); // Actual remapping behavior is tested elsewhere. }
/// <summary> /// Returns the source file and position where the item at the given position is declared at, /// if such a declaration exists, and returns the given position and file otherwise. /// Returns null if the given file is listed as to be ignored or if the information cannot be determined at this point. /// </summary> public Location DefinitionLocation(TextDocumentPositionParams param) => ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.DefinitionLocation(param) : null;
public Task <Hover?> GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) => _protocol.ExecuteRequestAsync <TextDocumentPositionParams, Hover?>(Methods.TextDocumentHoverName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
/// <summary> /// Returns the signature help information for a call expression if there is such an expression at the specified position. /// Returns null if the given file is listed as to be ignored, /// or if some parameters are unspecified (null), /// or if the specified position is not a valid position within the currently processed file content /// or if no call expression exists at the specified position at this time /// or if no signature help information can be provided for the call expression at the specified position. /// </summary> public SignatureHelp SignatureHelp(TextDocumentPositionParams param, MarkupKind format = MarkupKind.PlainText) => ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.SignatureHelp(param, format) : null;
public Task <SignatureHelp> GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) => _protocol.ExecuteRequestAsync <TextDocumentPositionParams, SignatureHelp>(Methods.TextDocumentSignatureHelpName, textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
/// <summary> /// Returns information about the item at the specified position as Hover information. /// Returns null if the given file is listed as to be ignored, /// or if some parameters are unspecified (null), /// or if the specified position is not a valid position within the currently processed file content, /// or if no token exists at the specified position at this time. /// </summary> public Hover HoverInformation(TextDocumentPositionParams param, MarkupKind format = MarkupKind.PlainText) => ValidFileUri(param?.TextDocument?.Uri) && !IgnoreFile(param.TextDocument.Uri) ? this.Projects.HoverInformation(param, format) : null;
public override async Task <Hover> Hover(TextDocumentPositionParams @params, CancellationToken cancellationToken) { var uri = @params.textDocument.uri; ProjectFiles.GetEntry(@params.textDocument, @params._version, out var entry, out var tree); TraceMessage($"Hover in {uri} at {@params.position}"); var analysis = entry != null ? await entry.GetAnalysisAsync(50, cancellationToken) : null; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); return(EmptyHover); } tree = GetParseTree(entry, uri, cancellationToken, out var version) ?? tree; Expression expr; SourceSpan?exprSpan; var finder = new ExpressionFinder(tree, GetExpressionOptions.Hover); expr = finder.GetExpression(@params.position) as Expression; exprSpan = expr?.GetSpan(tree); if (expr == null) { TraceMessage($"No hover info found in {uri} at {@params.position}"); return(EmptyHover); } TraceMessage($"Getting hover for {expr.ToCodeString(tree, CodeFormattingOptions.Traditional)}"); var hover = await GetSelfHoverAsync(expr, analysis, tree, @params.position, cancellationToken); if (hover != null && hover != EmptyHover) { return(hover); } // First try values from expression. This works for the import statement most of the time. var values = analysis.GetValues(expr, @params.position, null).ToList(); if (values.Count == 0) { values = GetImportHover(entry, analysis, tree, @params.position, out hover).ToList(); if (hover != null) { return(hover); } } if (values.Count > 0) { string originalExpr; if (expr is ConstantExpression || expr is ErrorExpression) { originalExpr = null; } else { originalExpr = @params._expr?.Trim(); if (string.IsNullOrEmpty(originalExpr)) { originalExpr = expr.ToCodeString(tree, CodeFormattingOptions.Traditional); } } var names = values.Select(GetFullTypeName).Where(n => !string.IsNullOrEmpty(n)).Distinct().ToArray(); var res = new Hover { contents = GetMarkupContent( _displayTextBuilder.GetDocumentation(values, originalExpr), _clientCaps.textDocument?.hover?.contentFormat), range = exprSpan, _version = version?.Version, _typeNames = names }; return(res); } return(EmptyHover); }
public override async Task <Hover> Hover(TextDocumentPositionParams @params, CancellationToken cancellationToken) { var uri = @params.textDocument.uri; ProjectFiles.GetEntry(@params.textDocument, @params._version, out var entry, out var tree); TraceMessage($"Hover in {uri} at {@params.position}"); var analysis = entry != null ? await entry.GetAnalysisAsync(50, cancellationToken) : null; if (analysis == null) { TraceMessage($"No analysis found for {uri}"); return(EmptyHover); } tree = GetParseTree(entry, uri, cancellationToken, out var version) ?? tree; Expression expr; SourceSpan?exprSpan; var finder = new ExpressionFinder(tree, GetExpressionOptions.Hover); expr = finder.GetExpression(@params.position) as Expression; exprSpan = expr?.GetSpan(tree); if (expr == null) { TraceMessage($"No hover info found in {uri} at {@params.position}"); return(EmptyHover); } TraceMessage($"Getting hover for {expr.ToCodeString(tree, CodeFormattingOptions.Traditional)}"); // First try values from expression. This works for the import statement most of the time. var values = analysis.GetValues(expr, @params.position, null).ToList(); if (values.Count == 0) { // See if this is hover over import statement var index = tree.LocationToIndex(@params.position); var w = new ImportedModuleNameWalker(entry, index, tree); tree.Walk(w); if (w.ImportedType != null) { values = analysis.GetValues(w.ImportedType.Name, @params.position).ToList(); } else { var sb = new StringBuilder(); var span = SourceSpan.Invalid; foreach (var n in w.ImportedModules) { if (Analyzer.Modules.TryGetImportedModule(n.Name, out var modRef) && modRef.AnalysisModule != null) { if (sb.Length > 0) { sb.AppendLine(); sb.AppendLine(); } sb.Append(_displayTextBuilder.GetModuleDocumentation(modRef)); span = span.IsValid ? span.Union(n.SourceSpan) : n.SourceSpan; } } if (sb.Length > 0) { return(new Hover { contents = sb.ToString(), range = span }); } } } if (values.Count > 0) { string originalExpr; if (expr is ConstantExpression || expr is ErrorExpression) { originalExpr = null; } else { originalExpr = @params._expr?.Trim(); if (string.IsNullOrEmpty(originalExpr)) { originalExpr = expr.ToCodeString(tree, CodeFormattingOptions.Traditional); } } var names = values.Select(GetFullTypeName).Where(n => !string.IsNullOrEmpty(n)).Distinct().ToArray(); var res = new Hover { contents = GetMarkupContent( _displayTextBuilder.GetDocumentation(values, originalExpr), _clientCaps.textDocument?.hover?.contentFormat), range = exprSpan, _version = version?.Version, _typeNames = names }; return(res); } return(EmptyHover); }