diff --git a/src/FsAutoComplete.Core/DocumentationFormatter.fs b/src/FsAutoComplete.Core/DocumentationFormatter.fs index bc950398e..c7d985781 100644 --- a/src/FsAutoComplete.Core/DocumentationFormatter.fs +++ b/src/FsAutoComplete.Core/DocumentationFormatter.fs @@ -16,7 +16,22 @@ module DocumentationFormatter = let mutable lastDisplayContext: FSharpDisplayContext = FSharpDisplayContext.Empty - let emptyTypeTip = [||], [||], [||], [||], [||], [||] + type EntityInfo = + { Constructors: string array + Fields: string array + Functions: string array + Interfaces: string array + Attributes: string array + DeclaredTypes: string array } + + static member Empty = + + { Constructors = [||] + Fields = [||] + Functions = [||] + Interfaces = [||] + Attributes = [||] + DeclaredTypes = [||] } /// Concat two strings with a space between if both a and b are not IsNullOrWhiteSpace let internal (++) (a: string) (b: string) = @@ -719,8 +734,12 @@ module DocumentationFormatter = ++ fst (formatShowDocumentationLink ne.DisplayName ne.XmlDocSig ne.Assembly.SimpleName)) |> Seq.toArray - - constrc, fields, funcs, interfaces, attrs, types + { Constructors = constrc + Fields = fields + Functions = funcs + Interfaces = interfaces + Attributes = attrs + DeclaredTypes = types } let typeDisplay = let name = @@ -756,9 +775,9 @@ module DocumentationFormatter = if fse.IsFSharpUnion then (typeDisplay + uniontip ()), typeTip () elif fse.IsEnum then - (typeDisplay + enumtip ()), emptyTypeTip + (typeDisplay + enumtip ()), EntityInfo.Empty elif fse.IsDelegate then - (typeDisplay + delegateTip ()), emptyTypeTip + (typeDisplay + delegateTip ()), EntityInfo.Empty else typeDisplay, typeTip () @@ -867,60 +886,60 @@ module DocumentationFormatter = | Some ent when ent.IsValueType || ent.IsEnum -> //ValueTypes let signature = getFuncSignature symbol.DisplayContext func - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | _ -> //ReferenceType constructor let signature = getFuncSignature symbol.DisplayContext func - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.Operator func -> let signature = getFuncSignature symbol.DisplayContext func - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.Pattern func -> //Active pattern or operator let signature = getFuncSignature symbol.DisplayContext func - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.Property prop -> let signature = getFuncSignature symbol.DisplayContext prop - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.ClosureOrNestedFunction func -> //represents a closure or nested function let signature = getFuncSignature symbol.DisplayContext func - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.Function func -> let signature = getFuncSignature symbol.DisplayContext func - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.Val func -> //val name : Type let signature = getValSignature symbol.DisplayContext func - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.Field fsf -> let signature = getFieldSignature symbol.DisplayContext fsf - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.UnionCase uc -> let signature = getUnioncaseSignature symbol.DisplayContext uc - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.ActivePatternCase apc -> let signature = getAPCaseSignature symbol.DisplayContext apc - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.ActivePattern ap -> let signature = getFuncSignature symbol.DisplayContext ap - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | SymbolUse.GenericParameter gp -> let signature = $"'%s{gp.Name} (requires %s{formatGenericParameter false symbol.DisplayContext gp})" - Some((signature, emptyTypeTip), footerForType symbol, cn) + Some((signature, EntityInfo.Empty), footerForType symbol, cn) | _ -> None @@ -942,51 +961,51 @@ module DocumentationFormatter = | Some ent when ent.IsValueType || ent.IsEnum -> //ValueTypes let signature = getFuncSignature lastDisplayContext func - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | _ -> //ReferenceType constructor let signature = getFuncSignature lastDisplayContext func - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | SymbolPatterns.Operator func -> let signature = getFuncSignature lastDisplayContext func - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | Property prop -> let signature = getFuncSignature lastDisplayContext prop - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | ClosureOrNestedFunction func -> //represents a closure or nested function let signature = getFuncSignature lastDisplayContext func - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | Function func -> let signature = getFuncSignature lastDisplayContext func - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | Val func -> //val name : Type let signature = getValSignature lastDisplayContext func - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | Field(fsf, _) -> let signature = getFieldSignature lastDisplayContext fsf - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | UnionCase uc -> let signature = getUnioncaseSignature lastDisplayContext uc - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | ActivePatternCase apc -> let signature = getAPCaseSignature lastDisplayContext apc - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | GenericParameter gp -> let signature = $"'%s{gp.Name} (requires %s{formatGenericParameter false lastDisplayContext gp})" - Some((signature, emptyTypeTip), footerForType' symbol, cn) + Some((signature, EntityInfo.Empty), footerForType' symbol, cn) | _ -> None diff --git a/src/FsAutoComplete.Core/ParseAndCheckResults.fs b/src/FsAutoComplete.Core/ParseAndCheckResults.fs index 78d7dd609..54ad5a6fc 100644 --- a/src/FsAutoComplete.Core/ParseAndCheckResults.fs +++ b/src/FsAutoComplete.Core/ParseAndCheckResults.fs @@ -22,6 +22,21 @@ type FindDeclarationResult = /// The declaration refers to a file. | File of string +[] +module TryGetToolTipEnhancedResult = + + type SymbolInfo = + | Keyword of string + | Symbol of + {| XmlDocSig: string + Assembly: string |} + +type TryGetToolTipEnhancedResult = + { ToolTipText: ToolTipText + Signature: string + Footer: string + SymbolInfo: TryGetToolTipEnhancedResult.SymbolInfo } + type ParseAndCheckResults (parseResults: FSharpParseFileResults, checkResults: FSharpCheckFileResults, entityCache: EntityCache) = @@ -320,7 +335,10 @@ type ParseAndCheckResults | _ -> ResultOrString.Error "No tooltip information" | _ -> Ok(tip) - member x.TryGetToolTipEnhanced (pos: Position) (lineStr: LineStr) = + member x.TryGetToolTipEnhanced + (pos: Position) + (lineStr: LineStr) + : Result, string> = let (|EmptyTooltip|_|) (ToolTipText elems) = match elems with | [] -> Some() @@ -347,7 +365,13 @@ type ParseAndCheckResults match identIsland with | [ ident ] -> match KeywordList.keywordTooltips.TryGetValue ident with - | true, tip -> Ok(Some(tip, ident, "", None)) + | true, tip -> + { ToolTipText = tip + Signature = ident + Footer = "" + SymbolInfo = TryGetToolTipEnhancedResult.Keyword ident } + |> Some + |> Ok | _ -> Error "No tooltip information" | _ -> Error "No tooltip information" | _ -> @@ -355,13 +379,24 @@ type ParseAndCheckResults | None -> Error "No tooltip information" | Some symbol -> + // Retrieve the FSharpSymbol instance so we can find the XmlDocSig + // This mimic, the behavior of the Info Panel on hover + // 1. If this is a concrete type it returns that type reference + // 2. If this a type alias, it returns the aliases type reference + let resolvedType = symbol.Symbol.GetAbbreviatedParent() + match SignatureFormatter.getTooltipDetailsFromSymbolUse symbol with | None -> Error "No tooltip information" | Some(signature, footer) -> - let typeDoc = - getTypeIfConstructor symbol.Symbol |> Option.map (fun n -> n.XmlDocSig) - - Ok(Some(tip, signature, footer, typeDoc)) + { ToolTipText = tip + Signature = signature + Footer = footer + SymbolInfo = + TryGetToolTipEnhancedResult.Symbol + {| XmlDocSig = resolvedType.XmlDocSig + Assembly = symbol.Symbol.Assembly.SimpleName |} } + |> Some + |> Ok member __.TryGetFormattedDocumentation (pos: Position) (lineStr: LineStr) = match Lexer.findLongIdents (pos.Column, lineStr) with @@ -380,7 +415,7 @@ type ParseAndCheckResults match identIsland with | [ ident ] -> match KeywordList.keywordTooltips.TryGetValue ident with - | true, tip -> Ok(Some tip, None, (ident, (DocumentationFormatter.emptyTypeTip)), "", "") + | true, tip -> Ok(Some tip, None, (ident, DocumentationFormatter.EntityInfo.Empty), "", "") | _ -> Error "No tooltip information" | _ -> Error "No documentation information" | _ -> diff --git a/src/FsAutoComplete.Core/TipFormatter.fs b/src/FsAutoComplete.Core/TipFormatter.fs index 14b7a4c82..d0c3ce2bc 100644 --- a/src/FsAutoComplete.Core/TipFormatter.fs +++ b/src/FsAutoComplete.Core/TipFormatter.fs @@ -1,4 +1,4 @@ -// -------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------- // (c) Tomas Petricek, http://tomasp.net/blog // -------------------------------------------------------------------------------------- module FsAutoComplete.TipFormatter @@ -12,6 +12,7 @@ open FSharp.Compiler.EditorServices open FSharp.Compiler.Symbols open FsAutoComplete.Logging open FSharp.Compiler.Text +open Ionide.LanguageServerProtocol.Types let inline nl<'T> = Environment.NewLine @@ -163,18 +164,48 @@ module private Format = | None -> "forceNoHighlight" + // We need to trim the end of the text because the + // user write XML comments with a space between the '///' + // and the '' tag. Then it mess up identifation of new lines + // at the end of the code snippet. + // Example: + // /// + // /// var x = 1; + // /// + // ^ This space is the one we need to remove + let innerText = innerText.TrimEnd() + + // Try to detect how the code snippet is formatted + // so render the markdown code block the best way + // by avoid empty lines at the beginning or the end let formattedText = - if innerText.Contains("\n") then + match innerText.StartsWith("\n"), innerText.EndsWith("\n") with + | true, true -> sprintf "```%s%s```" lang innerText + | true, false -> sprintf "```%s%s\n```" lang innerText + | false, true -> sprintf "```%s\n%s```" lang innerText + | false, false -> sprintf "```%s\n%s\n```" lang innerText - if innerText.StartsWith("\n") then + Some formattedText - sprintf "```%s%s\n```" lang innerText + } + |> applyFormatter - else - sprintf "```%s\n%s\n```" lang innerText + let private example = + { TagName = "example" + Formatter = + function + | VoidElement _ -> None - else - sprintf "`%s`" innerText + | NonVoidElement(innerText, _) -> + let formattedText = + nl + + nl + // This try to keep a visual consistency and indicate that this + // "Example section" is part of it parent section (summary, remarks, etc.) + + """Example:""" + + nl + + nl + + innerText Some formattedText @@ -668,6 +699,7 @@ module private Format = |> removeInvalidOrBlock // Start the transformation process |> paragraph + |> example |> block |> codeInline |> codeBlock @@ -682,6 +714,13 @@ module private Format = |> handleMicrosoftOrList |> unescapeSpecialCharacters +[] +type FormatCommentStyle = + | Legacy + | FullEnhanced + | SummaryOnly + | Documentation + // TODO: Improve this parser. Is there any other XmlDoc parser available? type private XmlDocMember(doc: XmlDocument, indentationSize: int, columnOffset: int) = /// References used to detect if we should remove meaningless spaces @@ -725,7 +764,17 @@ type private XmlDocMember(doc: XmlDocument, indentationSize: int, columnOffset: |> Seq.tryHead let rawExamples = - doc.DocumentElement.GetElementsByTagName "example" |> Seq.cast + doc.DocumentElement.GetElementsByTagName "example" + |> Seq.cast + // We need to filter out the examples node that are children + // of another "main" node + // This is because if the example node is inside a "main" node + // then we render it in place. + // So we don't need to render it independently in the Examples section + |> Seq.filter (fun node -> + [ "summary"; "param"; "returns"; "exception"; "remarks"; "typeparam" ] + |> List.contains node.ParentNode.Name + |> not) let readNamedContentAsKvPair (key, content) = KeyValuePair(key, readContentForTooltip content) @@ -770,6 +819,8 @@ type private XmlDocMember(doc: XmlDocument, indentationSize: int, columnOffset: else "**Description**" + nl + nl + summary + member __.HasTruncatedExamples = examples |> Seq.isEmpty |> not + member __.ToFullEnhancedString() = let content = summary @@ -778,7 +829,6 @@ type private XmlDocMember(doc: XmlDocument, indentationSize: int, columnOffset: + Section.fromKeyValueList "Parameters" parameters + Section.fromOption "Returns" returns + Section.fromKeyValueList "Exceptions" exceptions - + Section.fromList "Examples" examples + Section.fromList "See also" seeAlso // If we where unable to process the doc comment, then just output it as it is @@ -801,6 +851,14 @@ type private XmlDocMember(doc: XmlDocument, indentationSize: int, columnOffset: + Section.fromList "Examples" examples + Section.fromList "See also" seeAlso + member this.FormatComment(formatStyle: FormatCommentStyle) = + match formatStyle with + | FormatCommentStyle.Legacy -> this.ToString() + | FormatCommentStyle.SummaryOnly -> this.ToSummaryOnlyString() + | FormatCommentStyle.FullEnhanced -> this.ToFullEnhancedString() + | FormatCommentStyle.Documentation -> this.ToDocumentationString() + + let rec private readXmlDoc (reader: XmlReader) (indentationSize: int) (acc: Map) = let acc' = match reader.Read() with @@ -887,23 +945,30 @@ let private getXmlDoc dllFile = xmlDocCache.AddOrUpdate(xmlFile, xmlDoc, (fun _ _ -> xmlDoc)) |> ignore Some xmlDoc - with ex -> + with _ -> None // TODO: Remove the empty map from cache to try again in the next request? -[] -type FormatCommentStyle = - | Legacy - | FullEnhanced - | SummaryOnly - | Documentation - // -------------------------------------------------------------------------------------- // Formatting of tool-tip information displayed in F# IntelliSense // -------------------------------------------------------------------------------------- -let private buildFormatComment cmt (formatStyle: FormatCommentStyle) (typeDoc: string option) = - match cmt with - | FSharpXmlDoc.FromXmlText xmldoc -> - try + +[] +type private TryGetXmlDocMemberResult = + | Some of XmlDocMember + | None + | Error + +[] +type TipFormatterResult<'T> = + | Success of 'T + | Error of string + | None + +let private tryGetXmlDocMember (xmlDoc: FSharpXmlDoc) = + try + match xmlDoc with + | FSharpXmlDoc.FromXmlText xmldoc -> + let document = xmldoc.GetXmlText() // We create a "fake" XML document in order to use the same parser for both libraries and user code let xml = sprintf "%s" document @@ -927,49 +992,28 @@ let private buildFormatComment cmt (formatStyle: FormatCommentStyle) (typeDoc: s let xmlDoc = XmlDocMember(doc, indentationSize, 0) - match formatStyle with - | FormatCommentStyle.Legacy -> xmlDoc.ToString() - | FormatCommentStyle.SummaryOnly -> xmlDoc.ToSummaryOnlyString() - | FormatCommentStyle.FullEnhanced -> xmlDoc.ToFullEnhancedString() - | FormatCommentStyle.Documentation -> xmlDoc.ToDocumentationString() - - with ex -> - logger.warn ( - Log.setMessage "TipFormatter - Error while parsing the doc comment" - >> Log.addExn ex - ) - - sprintf - "An error occured when parsing the doc comment, please check that your doc comment is valid.\n\nMore info can be found LSP output" - - | FSharpXmlDoc.FromXmlFile(dllFile, memberName) -> - match getXmlDoc dllFile with - | Some doc when doc.ContainsKey memberName -> - let typeDoc = - match typeDoc with - | Some s when doc.ContainsKey s -> - match formatStyle with - | FormatCommentStyle.Legacy -> doc.[s].ToString() - | FormatCommentStyle.SummaryOnly -> doc.[s].ToSummaryOnlyString() - | FormatCommentStyle.FullEnhanced -> doc.[s].ToFullEnhancedString() - | FormatCommentStyle.Documentation -> doc.[s].ToDocumentationString() - | _ -> "" - - match formatStyle with - | FormatCommentStyle.Legacy -> doc.[memberName].ToString() + (if typeDoc <> "" then "\n\n" + typeDoc else "") - | FormatCommentStyle.SummaryOnly -> - doc.[memberName].ToSummaryOnlyString() - + (if typeDoc <> "" then "\n\n" + typeDoc else "") - | FormatCommentStyle.FullEnhanced -> - doc.[memberName].ToFullEnhancedString() - + (if typeDoc <> "" then "\n\n" + typeDoc else "") - | FormatCommentStyle.Documentation -> - doc.[memberName].ToDocumentationString() - + (if typeDoc <> "" then "\n\n" + typeDoc else "") - | _ -> "" - | _ -> "" - -let formatTaggedText (t: TaggedText) : string = + TryGetXmlDocMemberResult.Some xmlDoc + + | FSharpXmlDoc.FromXmlFile(dllFile, memberName) -> + match getXmlDoc dllFile with + | Some doc when doc.ContainsKey memberName -> TryGetXmlDocMemberResult.Some doc.[memberName] + + | _ -> TryGetXmlDocMemberResult.None + + | FSharpXmlDoc.None -> TryGetXmlDocMemberResult.None + with ex -> + logger.warn ( + Log.setMessage "TipFormatter - Error while parsing the doc comment" + >> Log.addExn ex + ) + + TryGetXmlDocMemberResult.Error + +[] +let private ERROR_WHILE_PARSING_DOC_COMMENT = + "An error occurred when parsing the doc comment, please check that your doc comment is valid.\n\nMore info can be found in the LSP output" + +let private formatTaggedText (t: TaggedText) : string = match t.Tag with | TextTag.ActivePatternResult | TextTag.UnionCase @@ -1006,13 +1050,13 @@ let formatTaggedText (t: TaggedText) : string = | TextTag.Record | TextTag.TypeParameter -> $"`{t.Text}`" -let formatUntaggedText (t: TaggedText) = t.Text +let private formatUntaggedText (t: TaggedText) = t.Text -let formatUntaggedTexts = Array.map formatUntaggedText >> String.concat "" +let private formatUntaggedTexts = Array.map formatUntaggedText >> String.concat "" -let formatTaggedTexts = Array.map formatTaggedText >> String.concat "" +let private formatTaggedTexts = Array.map formatTaggedText >> String.concat "" -let formatGenericParameters (typeMappings: TaggedText[] list) = +let private formatGenericParameters (typeMappings: TaggedText[] list) = typeMappings |> List.map (fun typeMap -> $"* {formatTaggedTexts typeMap}") |> String.concat nl @@ -1026,7 +1070,12 @@ let formatCompletionItemTip (ToolTipText tips) : (string * string) = let makeTooltip (tipElement: ToolTipElementData) = let header = formatUntaggedTexts tipElement.MainDescription - let body = buildFormatComment tipElement.XmlDoc FormatCommentStyle.Legacy None + let body = + match tryGetXmlDocMember tipElement.XmlDoc with + | TryGetXmlDocMemberResult.Some xmlDoc -> xmlDoc.FormatComment(FormatCommentStyle.Legacy) + | TryGetXmlDocMemberResult.None -> "" + | TryGetXmlDocMemberResult.Error -> ERROR_WHILE_PARSING_DOC_COMMENT + header, body items |> List.tryHead |> Option.map makeTooltip @@ -1042,82 +1091,199 @@ let formatPlainTip (ToolTipText tips) : (string * string) = | ToolTipElement.Group items -> let t = items |> Seq.head let signature = formatUntaggedTexts t.MainDescription - let description = buildFormatComment t.XmlDoc FormatCommentStyle.Legacy None + + let description = + match tryGetXmlDocMember t.XmlDoc with + | TryGetXmlDocMemberResult.Some xmlDoc -> xmlDoc.FormatComment(FormatCommentStyle.Legacy) + | TryGetXmlDocMemberResult.None -> "" + | TryGetXmlDocMemberResult.Error -> ERROR_WHILE_PARSING_DOC_COMMENT + Some(signature, description) | ToolTipElement.CompositionError(error) -> Some("", error) | _ -> Some("", "No signature data")) -let formatTipEnhanced - (ToolTipText tips) - (signature: string) - (footer: string) - (typeDoc: string option) - (formatCommentStyle: FormatCommentStyle) - : (string * string * string) list list = - tips - |> List.choose (function - | ToolTipElement.Group items -> - Some( - items - |> List.map (fun i -> - let comment = - if i.TypeMapping.IsEmpty then - buildFormatComment i.XmlDoc formatCommentStyle typeDoc - else - buildFormatComment i.XmlDoc formatCommentStyle typeDoc - + nl - + nl - + "**Generic Parameters**" - + nl - + nl - + formatGenericParameters i.TypeMapping - - (signature, comment, footer)) - ) - | ToolTipElement.CompositionError(error) -> Some [ ("", error, "") ] - | _ -> None) - -let formatDocumentation - (ToolTipText tips) - ((signature, (constructors, fields, functions, interfaces, attrs, ts)): - string * (string[] * string[] * string[] * string[] * string[] * string[])) - (footer: string) - (cn: string) - = +let prepareSignature (signatureText: string) = + signatureText.Split Environment.NewLine + // Remove empty lines + |> Array.filter (not << String.IsNullOrWhiteSpace) + |> String.concat nl + +let prepareFooterLines (footerText: string) = + footerText.Split Environment.NewLine + // Remove empty lines + |> Array.filter (not << String.IsNullOrWhiteSpace) + // Mark each line as an individual string in italics + |> Array.map (fun n -> "*" + n + "*") + + +let private tryComputeTooltipInfo (ToolTipText tips) (formatCommentStyle: FormatCommentStyle) = + + // Note: In the previous code, we were returning a `(string * string * string) list list` + // but always discarding the tooltip later if the list had more than one element + // and only using the first element of the inner list. + // More over, I don't know in which case we can have several elements in the + // `(ToolTipText tips)` parameter. + // So I can't test why we have list of list stuff, but like I said, we were + // discarding the tooltip if it had more than one element. + // + // The new code should do the same thing, as before but instead of + // computing the rendered tooltip, and discarding some of them afterwards, + // we are discarding the things we don't want earlier and only compute the + // tooltip we want to display if we have the right data. + + let computeGenericParametersText (tooltipData: ToolTipElementData) = + // If there are no generic parameters, don't display the section + if tooltipData.TypeMapping.IsEmpty then + None + // If there are generic parameters, display the section + else + "**Generic Parameters**" + + nl + + nl + + formatGenericParameters tooltipData.TypeMapping + |> Some + tips - |> List.choose (function - | ToolTipElement.Group items -> - Some( - items - |> List.map (fun i -> - let comment = - if i.TypeMapping.IsEmpty then - buildFormatComment i.XmlDoc FormatCommentStyle.Documentation None - else - buildFormatComment i.XmlDoc FormatCommentStyle.Documentation None - + nl - + nl - + "**Generic Parameters**" - + nl - + nl - + formatGenericParameters i.TypeMapping - - (signature, constructors, fields, functions, interfaces, attrs, ts, comment, footer, cn)) - ) - | ToolTipElement.CompositionError(error) -> Some [ ("", [||], [||], [||], [||], [||], [||], error, "", "") ] - | _ -> None) - -let formatDocumentationFromXmlSig - (xmlSig: string) - (assembly: string) - ((signature, (constructors, fields, functions, interfaces, attrs, ts)): - string * (string[] * string[] * string[] * string[] * string[] * string[])) - (footer: string) - (cn: string) - = + // Render the first valid tooltip and return it + |> List.tryPick (function + | ToolTipElement.Group(tooltipData :: _) -> + let docComment, hasTruncatedExamples = + match tryGetXmlDocMember tooltipData.XmlDoc with + | TryGetXmlDocMemberResult.Some xmlDoc -> + // Format the doc comment + let docCommentText = xmlDoc.FormatComment formatCommentStyle + + // Concatenate the doc comment and the generic parameters section + let consolidatedDocCommentText = + match computeGenericParametersText tooltipData with + | Some genericParametersText -> docCommentText + nl + nl + genericParametersText + | None -> docCommentText + + consolidatedDocCommentText, xmlDoc.HasTruncatedExamples + + | TryGetXmlDocMemberResult.None -> + // Even if a symbol doesn't have a doc comment, it can still have generic parameters + let docComment = + match computeGenericParametersText tooltipData with + | Some genericParametersText -> genericParametersText + | None -> "" + + docComment, false + | TryGetXmlDocMemberResult.Error -> ERROR_WHILE_PARSING_DOC_COMMENT, false + + {| DocComment = docComment + HasTruncatedExamples = hasTruncatedExamples |} + |> Ok + |> Some + + | ToolTipElement.CompositionError error -> error |> Error |> Some + + | ToolTipElement.Group [] + | ToolTipElement.None -> None) + +/// +/// Try format the given tooltip with the requested style. +/// +/// Tooltip documentation to render in the middle +/// Style of tooltip +/// +/// - TipFormatterResult.Success {| DocComment; HasTruncatedExamples |} if the doc comment has been formatted +/// +/// Where DocComment is the format tooltip and HasTruncatedExamples is true if examples have been truncated +/// +/// - TipFormatterResult.None if the doc comment has not been found +/// - TipFormatterResult.Error string if an error occurred while parsing the doc comment +/// +let tryFormatTipEnhanced toolTipText (formatCommentStyle: FormatCommentStyle) = + + match tryComputeTooltipInfo toolTipText formatCommentStyle with + | Some(Ok tooltipResult) -> TipFormatterResult.Success tooltipResult + + | Some(Error error) -> TipFormatterResult.Error error + + | None -> TipFormatterResult.None + +/// +/// Generate the 'Show documentation' link for the tooltip. +/// +/// The link is rendered differently depending on if examples +/// have been truncated or not. +/// +/// true if the examples have been truncated +/// XmlDocSignature in the format of T:System.String.concat +/// Assembly name, example FSharp.Core +/// Returns a string which represent the show documentation link +let renderShowDocumentationLink (hasTruncatedExamples: bool) (xmlDocSig: string) (assemblyName: string) = + + // TODO: Refactor this code, to avoid duplicate with DocumentationFormatter.fs + let content = + Uri.EscapeDataString(sprintf """[{ "XmlDocSig": "%s", "AssemblyName": "%s" }]""" xmlDocSig assemblyName) + + let text = + if hasTruncatedExamples then + "Open the documentation to see the truncated examples" + else + "Open the documentation" + + $"%s{text}" + +/// +/// Try format the given tooltip as documentation. +/// +/// Tooltip to format +/// +/// - TipFormatterResult.Success string if the doc comment has been formatted +/// - TipFormatterResult.None if the doc comment has not been found +/// - TipFormatterResult.Error string if an error occurred while parsing the doc comment +/// +let tryFormatDocumentationFromTooltip toolTipText = + + match tryComputeTooltipInfo toolTipText FormatCommentStyle.Documentation with + | Some(Ok tooltipResult) -> TipFormatterResult.Success tooltipResult.DocComment + + | Some(Error error) -> TipFormatterResult.Error error + + | None -> TipFormatterResult.None + +/// +/// Try format the doc comment based on the XmlSignature and the assembly name. +/// +/// +/// XmlSignature used to identify the doc comment to format +/// +/// Example: T:System.String.concat +/// +/// +/// Assembly name used to identify the doc comment to format +/// +/// Example: FSharp.Core +/// +/// +/// - TipFormatterResult.Success string if the doc comment has been formatted +/// - TipFormatterResult.None if the doc comment has not been found +/// - TipFormatterResult.Error string if an error occurred while parsing the doc comment +/// +let tryFormatDocumentationFromXmlSig (xmlSig: string) (assembly: string) = let xmlDoc = FSharpXmlDoc.FromXmlFile(assembly, xmlSig) - let comment = buildFormatComment xmlDoc FormatCommentStyle.Documentation None - [ [ (signature, constructors, fields, functions, interfaces, attrs, ts, comment, footer, cn) ] ] + + match tryGetXmlDocMember xmlDoc with + | TryGetXmlDocMemberResult.Some xmlDoc -> + let formattedComment = xmlDoc.FormatComment(FormatCommentStyle.Documentation) + + TipFormatterResult.Success formattedComment + + | TryGetXmlDocMemberResult.None -> TipFormatterResult.None + | TryGetXmlDocMemberResult.Error -> TipFormatterResult.Error ERROR_WHILE_PARSING_DOC_COMMENT + +let formatDocumentationFromXmlDoc xmlDoc = + match tryGetXmlDocMember xmlDoc with + | TryGetXmlDocMemberResult.Some xmlDoc -> + let formattedComment = xmlDoc.FormatComment(FormatCommentStyle.Documentation) + + TipFormatterResult.Success formattedComment + + | TryGetXmlDocMemberResult.None -> TipFormatterResult.None + | TryGetXmlDocMemberResult.Error -> TipFormatterResult.Error ERROR_WHILE_PARSING_DOC_COMMENT let extractSignature (ToolTipText tips) = let getSignature (t: TaggedText[]) = diff --git a/src/FsAutoComplete/CommandResponse.fs b/src/FsAutoComplete/CommandResponse.fs index aa2c2dc73..55d3b1404 100644 --- a/src/FsAutoComplete/CommandResponse.fs +++ b/src/FsAutoComplete/CommandResponse.fs @@ -8,6 +8,7 @@ open FSharp.UMX open FSharp.Compiler.EditorServices open FSharp.Compiler.Diagnostics open FSharp.Compiler.Xml +open FSharp.Compiler.Symbols module internal CompletionUtils = let getIcon (glyph: FSharpGlyph) = @@ -170,15 +171,27 @@ module CommandResponse = type DocumentationDescription = { XmlKey: string - Constructors: string list - Fields: string list - Functions: string list - Interfaces: string list - Attributes: string list - Types: string list + Constructors: string array + Fields: string array + Functions: string array + Interfaces: string array + Attributes: string array + DeclaredTypes: string array Signature: string Comment: string - Footer: string } + FooterLines: string array } + + static member CreateFromError(error: string) = + { XmlKey = "" + Constructors = [||] + Fields = [||] + Functions = [||] + Interfaces = [||] + Attributes = [||] + DeclaredTypes = [||] + FooterLines = [||] + Signature = "" + Comment = error } type CompilerLocationResponse = { Fsc: string option @@ -544,67 +557,101 @@ module CommandResponse = serialize { Kind = "declarations"; Data = decls' } - let formattedDocumentation (serialize: Serializer) (tip, xmlSig, signature, footer, cn) = - let data = - match tip, xmlSig with + let formattedDocumentation + (serialize: Serializer) + (param: + {| Tip: ToolTipText option + XmlSig: (string * string) option + Signature: (string * DocumentationFormatter.EntityInfo) + Footer: string + XmlKey: string |}) + = + + let (signature, entity) = param.Signature + + let createDocumentationDescription formattedDocComment = + { XmlKey = param.XmlKey + Constructors = entity.Constructors + Fields = entity.Fields + Functions = entity.Functions + Interfaces = entity.Interfaces + Attributes = entity.Attributes + DeclaredTypes = entity.DeclaredTypes + FooterLines = TipFormatter.prepareFooterLines param.Footer + Signature = TipFormatter.prepareSignature signature + Comment = formattedDocComment } + + let data: DocumentationDescription = + match param.Tip, param.XmlSig with | Some tip, _ -> - TipFormatter.formatDocumentation tip signature footer cn - |> List.map ( - List.map (fun (n, cns, fds, funcs, intf, attrs, ts, m, f, cn) -> - { XmlKey = cn - Constructors = cns |> Seq.toList - Fields = fds |> Seq.toList - Functions = funcs |> Seq.toList - Interfaces = intf |> Seq.toList - Attributes = attrs |> Seq.toList - Types = ts |> Seq.toList - Footer = f - Signature = n - Comment = m }) - ) + match TipFormatter.tryFormatDocumentationFromTooltip tip with + | TipFormatter.TipFormatterResult.Success formattedDocComment -> + createDocumentationDescription formattedDocComment + + // The old behaviour never took into consideration a possible `None` + // It was using direct access into an array which would have thrown + // I decided to just return an empty string for now + | TipFormatter.TipFormatterResult.None -> createDocumentationDescription "" + + | TipFormatter.TipFormatterResult.Error error -> DocumentationDescription.CreateFromError error + | _, Some(xml, assembly) -> - TipFormatter.formatDocumentationFromXmlSig xml assembly signature footer cn - |> List.map ( - List.map (fun (n, cns, fds, funcs, intf, attrs, ts, m, f, cn) -> - { XmlKey = cn - Constructors = cns |> Seq.toList - Fields = fds |> Seq.toList - Functions = funcs |> Seq.toList - Interfaces = intf |> Seq.toList - Attributes = attrs |> Seq.toList - Types = ts |> Seq.toList - Footer = f - Signature = n - Comment = m }) - ) + match TipFormatter.tryFormatDocumentationFromXmlSig xml assembly with + | TipFormatter.TipFormatterResult.Success formattedDocComment -> + createDocumentationDescription formattedDocComment + + // The old behaviour never took into consideration a possible `None` + // It was using direct access into an array which would have thrown + // I decided to just return an empty string for now + | TipFormatter.TipFormatterResult.None -> createDocumentationDescription "" + + | TipFormatter.TipFormatterResult.Error error -> DocumentationDescription.CreateFromError error + | _ -> failwith "Shouldn't happen" serialize { Kind = "formattedDocumentation" Data = data } - let formattedDocumentationForSymbol (serialize: Serializer) xml assembly (xmldoc: string[]) (signature, footer, cn) = - let data = - TipFormatter.formatDocumentationFromXmlSig xml assembly signature footer cn - |> List.map ( - List.map (fun (n, cns, fds, funcs, intf, attrs, ts, m, f, cn) -> - let m = - if String.IsNullOrWhiteSpace m then - xmldoc |> String.concat Environment.NewLine - else - m - - { XmlKey = cn - Constructors = cns |> Seq.toList - Fields = fds |> Seq.toList - Functions = funcs |> Seq.toList - Interfaces = intf |> Seq.toList - Attributes = attrs |> Seq.toList - Types = ts |> Seq.toList - Footer = f - Signature = n - Comment = m }) - ) + let formattedDocumentationForSymbol + (serialize: Serializer) + (param: + {| Xml: string + Assembly: string + XmlDoc: FSharpXmlDoc + Signature: (string * DocumentationFormatter.EntityInfo) + Footer: string + XmlKey: string |}) + = + let (signature, entity) = param.Signature + + let createDocumentationDescription formattedDocComment = + { XmlKey = param.XmlKey + Constructors = entity.Constructors + Fields = entity.Fields + Functions = entity.Functions + Interfaces = entity.Interfaces + Attributes = entity.Attributes + DeclaredTypes = entity.DeclaredTypes + FooterLines = TipFormatter.prepareFooterLines param.Footer + Signature = TipFormatter.prepareSignature signature + Comment = formattedDocComment } + + let data: DocumentationDescription = + match TipFormatter.tryFormatDocumentationFromXmlSig param.Xml param.Assembly with + | TipFormatter.TipFormatterResult.Success formattedDocComment -> + createDocumentationDescription formattedDocComment + + | TipFormatter.TipFormatterResult.None -> + match TipFormatter.formatDocumentationFromXmlDoc param.XmlDoc with + | TipFormatter.TipFormatterResult.Success formattedDocComment -> + createDocumentationDescription formattedDocComment + + | TipFormatter.TipFormatterResult.None -> createDocumentationDescription "" + + | TipFormatter.TipFormatterResult.Error error -> DocumentationDescription.CreateFromError error + + | TipFormatter.TipFormatterResult.Error error -> DocumentationDescription.CreateFromError error serialize { Kind = "formattedDocumentation" diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 2990ff2c2..6d70414f2 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -2520,10 +2520,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar and! tyRes = forceGetTypeCheckResultsStale filePath |> Result.ofStringErr match tyRes.TryGetToolTipEnhanced pos lineStr with - | Ok(Some(tip, signature, footer, typeDoc) as x) -> + | Ok(Some tooltipResult) -> logger.info ( Log.setMessage "TryGetToolTipEnhanced : {parms}" - >> Log.addContextDestructured "parms" x + >> Log.addContextDestructured "parms" tooltipResult ) let formatCommentStyle = @@ -2536,41 +2536,51 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar else TipFormatter.FormatCommentStyle.Legacy - match TipFormatter.formatTipEnhanced tip signature footer typeDoc formatCommentStyle with - | (sigCommentFooter :: _) :: _ -> - let signature, comment, footer = sigCommentFooter + match TipFormatter.tryFormatTipEnhanced tooltipResult.ToolTipText formatCommentStyle with + | TipFormatter.TipFormatterResult.Success tooltipInfo -> + + // Display the signature as a code block + let signature = + tooltipResult.Signature + |> TipFormatter.prepareSignature + |> (fun content -> MarkedString.WithLanguage { Language = "fsharp"; Value = content }) + + // Display each footer line as a separate line + let footerLines = + tooltipResult.Footer + |> TipFormatter.prepareFooterLines + |> Array.map MarkedString.String + + let contents = + [| signature + MarkedString.String tooltipInfo.DocComment + match tooltipResult.SymbolInfo with + | TryGetToolTipEnhancedResult.Keyword _ -> () + | TryGetToolTipEnhancedResult.Symbol symbolInfo -> + TipFormatter.renderShowDocumentationLink + tooltipInfo.HasTruncatedExamples + symbolInfo.XmlDocSig + symbolInfo.Assembly + |> MarkedString.String + yield! footerLines |] - let markStr lang (value: string) = - MarkedString.WithLanguage { Language = lang; Value = value } - - let fsharpBlock (lines: string[]) = - lines |> String.concat Environment.NewLine |> markStr "fsharp" - - let sigContent = - let lines = - signature.Split Environment.NewLine - |> Array.filter (not << String.IsNullOrWhiteSpace) - - match lines |> Array.splitAt (lines.Length - 1) with - | (h, [| StartsWith "Full name:" fullName |]) -> - [| yield fsharpBlock h; yield MarkedString.String("*" + fullName + "*") |] - | _ -> [| fsharpBlock lines |] - - - let commentContent = comment |> MarkedString.String + let response = + { Contents = MarkedStrings contents + Range = None } - let footerContent = - footer.Split Environment.NewLine - |> Array.filter (not << String.IsNullOrWhiteSpace) - |> Array.map (fun n -> MarkedString.String("*" + n + "*")) + return (Some response) + | TipFormatter.TipFormatterResult.Error error -> + let contents = [| MarkedString.String ""; MarkedString.String error |] let response = - { Contents = MarkedStrings [| yield! sigContent; yield commentContent; yield! footerContent |] + { Contents = MarkedStrings contents Range = None } return (Some response) - | _ -> return None + + | TipFormatter.TipFormatterResult.None -> return None + | Ok(None) -> return! LspResult.internalError $"No TryGetToolTipEnhanced results for {filePath}" @@ -4275,8 +4285,17 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar lastFSharpDocumentationTypeCheck <- Some tyRes match! Commands.FormattedDocumentation tyRes pos lineStr |> Result.ofCoreResponse with - | Some t -> - return Some { Content = CommandResponse.formattedDocumentation FsAutoComplete.JsonSerializer.writeJson t } + | Some(tip, xml, signature, footer, xmlKey) -> + return + Some + { Content = + CommandResponse.formattedDocumentation + JsonSerializer.writeJson + {| Tip = tip + XmlSig = xml + Signature = signature + Footer = footer + XmlKey = xmlKey |} } | None -> return None with e -> trace.SetStatusErrorSafe(e.Message).RecordExceptions(e) |> ignore @@ -4311,22 +4330,18 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar |> Result.ofCoreResponse with | None -> return None - | Some(xml, assembly, doc, signature, footer, cn) -> - - let xmldoc = - match doc with - | FSharpXmlDoc.None -> [||] - | FSharpXmlDoc.FromXmlFile _ -> [||] - | FSharpXmlDoc.FromXmlText d -> d.GetElaboratedXmlLines() + | Some(xml, assembly, xmlDoc, signature, footer, xmlKey) -> return { Content = CommandResponse.formattedDocumentationForSymbol - FsAutoComplete.JsonSerializer.writeJson - xml - assembly - xmldoc - (signature, footer, cn) } + JsonSerializer.writeJson + {| Xml = xml + Assembly = assembly + XmlDoc = xmlDoc + Signature = signature + Footer = footer + XmlKey = xmlKey |} } |> Some with e -> trace.SetStatusErrorSafe(e.Message).RecordExceptions(e) |> ignore diff --git a/src/FsAutoComplete/LspServers/FsAutoComplete.Lsp.fs b/src/FsAutoComplete/LspServers/FsAutoComplete.Lsp.fs index 802025e0c..8ad54220b 100644 --- a/src/FsAutoComplete/LspServers/FsAutoComplete.Lsp.fs +++ b/src/FsAutoComplete/LspServers/FsAutoComplete.Lsp.fs @@ -1569,7 +1569,7 @@ type FSharpLspServer(state: State, lspClient: FSharpLspClient) = | CoreResponse.InfoRes msg -> async.Return(success None) | CoreResponse.ErrorRes msg -> LspResult.internalError msg |> async.Return | CoreResponse.Res None -> async.Return(success None) - | CoreResponse.Res(Some(tip, signature, footer, typeDoc)) -> + | CoreResponse.Res(Some tooltipResult) -> let formatCommentStyle = if config.TooltipMode = "full" then TipFormatter.FormatCommentStyle.FullEnhanced @@ -1578,41 +1578,50 @@ type FSharpLspServer(state: State, lspClient: FSharpLspClient) = else TipFormatter.FormatCommentStyle.Legacy - match TipFormatter.formatTipEnhanced tip signature footer typeDoc formatCommentStyle with - | (sigCommentFooter :: _) :: _ -> - let signature, comment, footer = sigCommentFooter + match TipFormatter.tryFormatTipEnhanced tooltipResult.ToolTipText formatCommentStyle with + | TipFormatter.TipFormatterResult.Success tooltipInfo -> + + // Display the signature as a code block + let signature = + tooltipResult.Signature + |> TipFormatter.prepareSignature + |> (fun content -> MarkedString.WithLanguage { Language = "fsharp"; Value = content }) + + // Display each footer line as a separate line + let footerLines = + tooltipResult.Footer + |> TipFormatter.prepareFooterLines + |> Array.map MarkedString.String + + let contents = + [| signature + MarkedString.String tooltipInfo.DocComment + match tooltipResult.SymbolInfo with + | TryGetToolTipEnhancedResult.Keyword _ -> () + | TryGetToolTipEnhancedResult.Symbol symbolInfo -> + TipFormatter.renderShowDocumentationLink + tooltipInfo.HasTruncatedExamples + symbolInfo.XmlDocSig + symbolInfo.Assembly + |> MarkedString.String + yield! footerLines |] - let markStr lang (value: string) = - MarkedString.WithLanguage { Language = lang; Value = value } - - let fsharpBlock (lines: string[]) = - lines |> String.concat Environment.NewLine |> markStr "fsharp" - - let sigContent = - let lines = - signature.Split Environment.NewLine - |> Array.filter (not << String.IsNullOrWhiteSpace) - - match lines |> Array.splitAt (lines.Length - 1) with - | (h, [| StartsWith "Full name:" fullName |]) -> - [| yield fsharpBlock h; yield MarkedString.String("*" + fullName + "*") |] - | _ -> [| fsharpBlock lines |] - - - let commentContent = comment |> MarkedString.String + let response = + { Contents = MarkedStrings contents + Range = None } - let footerContent = - footer.Split Environment.NewLine - |> Array.filter (not << String.IsNullOrWhiteSpace) - |> Array.map (fun n -> MarkedString.String("*" + n + "*")) + async.Return(success (Some response)) + | TipFormatter.TipFormatterResult.Error error -> + let contents = [| MarkedString.String ""; MarkedString.String error |] let response = - { Contents = MarkedStrings [| yield! sigContent; yield commentContent; yield! footerContent |] + { Contents = MarkedStrings contents Range = None } async.Return(success (Some response)) - | _ -> async.Return(success None)) + + | TipFormatter.TipFormatterResult.None -> async.Return(success None)) override x.TextDocumentPrepareRename p = logger.info ( @@ -2668,12 +2677,16 @@ type FSharpLspServer(state: State, lspClient: FSharpLspClient) = (match Commands.FormattedDocumentation tyRes pos lineStr with | CoreResponse.InfoRes msg -> success None | CoreResponse.ErrorRes msg -> LspResult.internalError msg - | CoreResponse.Res(tip, xml, signature, footer, cm) -> + | CoreResponse.Res(tip, xml, signature, footer, xmlKey) -> let notification: PlainNotification = { Content = CommandResponse.formattedDocumentation - FsAutoComplete.JsonSerializer.writeJson - (tip, xml, signature, footer, cm) } + JsonSerializer.writeJson + {| Tip = tip + XmlSig = xml + Signature = signature + Footer = footer + XmlKey = xmlKey |} } success (Some notification)) |> async.Return) @@ -2691,21 +2704,18 @@ type FSharpLspServer(state: State, lspClient: FSharpLspClient) = match Commands.FormattedDocumentationForSymbol tyRes p.XmlSig p.Assembly with | (CoreResponse.InfoRes msg) -> return success None | (CoreResponse.ErrorRes msg) -> return! AsyncLspResult.internalError msg - | (CoreResponse.Res(xml, assembly, doc, signature, footer, cn)) -> - let xmldoc = - match doc with - | FSharpXmlDoc.None -> [||] - | FSharpXmlDoc.FromXmlFile _ -> [||] - | FSharpXmlDoc.FromXmlText d -> d.GetElaboratedXmlLines() + | (CoreResponse.Res(xml, assembly, xmlDoc, signature, footer, xmlKey)) -> return { Content = CommandResponse.formattedDocumentationForSymbol - FsAutoComplete.JsonSerializer.writeJson - xml - assembly - xmldoc - (signature, footer, cn) } + JsonSerializer.writeJson + {| Xml = xml + Assembly = assembly + XmlDoc = xmlDoc + Signature = signature + Footer = footer + XmlKey = xmlKey |} } |> Some |> success } diff --git a/test/FsAutoComplete.Tests.Lsp/CoreTests.fs b/test/FsAutoComplete.Tests.Lsp/CoreTests.fs index 7d80f8aec..5a84ad03e 100644 --- a/test/FsAutoComplete.Tests.Lsp/CoreTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CoreTests.fs @@ -176,7 +176,12 @@ let tooltipTests state = let (|Signature|_|) (hover: Hover) = match hover with | { Contents = MarkedStrings [| MarkedString.WithLanguage { Language = "fsharp"; Value = tooltip } - MarkedString.String newline + MarkedString.String docComment + MarkedString.String fullname + MarkedString.String assembly |] } -> Some tooltip + | { Contents = MarkedStrings [| MarkedString.WithLanguage { Language = "fsharp"; Value = tooltip } + MarkedString.String docComment + MarkedString.String showDocumentationLink MarkedString.String fullname MarkedString.String assembly |] } -> Some tooltip | _ -> None @@ -189,6 +194,11 @@ let tooltipTests state = MarkedString.String description MarkedString.String fullname MarkedString.String assembly |] } -> Some description + | { Contents = MarkedStrings [| MarkedString.WithLanguage { Language = "fsharp"; Value = tooltip } + MarkedString.String description + MarkedString.String showDocumentationLink + MarkedString.String fullname + MarkedString.String assembly |] } -> Some description | _ -> None let server = @@ -287,10 +297,6 @@ let tooltipTests state = "" "The formatted result." "" - "**Examples**" - "" - "See `Printf.sprintf` (link: ``Microsoft.FSharp.Core.PrintfModule.PrintFormatToStringThen``1``) for examples." - "" "**Generic Parameters**" "" "* `'T` is `string`" ] // verify fancy descriptions for external library functions @@ -352,9 +358,7 @@ let tooltipTests state = verifyDescription 45 15 - [ "" - "" - "**Generic Parameters**" + [ "**Generic Parameters**" "" "* `'a` is `int`" "* `'b` is `int`" diff --git a/test/FsAutoComplete.Tests.Lsp/InfoPanelTests.fs b/test/FsAutoComplete.Tests.Lsp/InfoPanelTests.fs index c7dd38c16..ffb21047b 100644 --- a/test/FsAutoComplete.Tests.Lsp/InfoPanelTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/InfoPanelTests.fs @@ -40,7 +40,7 @@ let docFormattingTest state = match doc with | Result.Error err -> failtest $"Doc error: {err.Message}" - | Result.Ok (Some(As ([ [ model: FsAutoComplete.CommandResponse.DocumentationDescription ] ]))) -> + | Result.Ok (Some(As (model: FsAutoComplete.CommandResponse.DocumentationDescription ))) -> Expect.stringContains model.Signature "'Key, 'U" "Formatted doc contains both params separated by (, )" | Result.Ok _ -> failtest "couldn't parse doc as the json type we expected" }) @@ -57,7 +57,7 @@ let docFormattingTest state = match doc with | Result.Error err -> failtest $"Doc error: {err.Message}" - | Result.Ok (Some (As ([ [ model: FsAutoComplete.CommandResponse.DocumentationDescription ] ]))) -> + | Result.Ok (Some (As (model: FsAutoComplete.CommandResponse.DocumentationDescription))) -> Expect.stringContains model.Signature "'T1 * 'T2 * 'T3" "Formatted doc contains 3 params separated by ( * )" | Result.Ok _ -> failtest "couldn't parse doc as the json type we expected" }) ]