diff --git a/src/Components/InfoPanel.fs b/src/Components/InfoPanel.fs index 2f91e809..a962a228 100644 --- a/src/Components/InfoPanel.fs +++ b/src/Components/InfoPanel.fs @@ -3,6 +3,7 @@ namespace Ionide.VSCode.FSharp open System open Fable.Core open Fable.Core.JsInterop +open Fable.Import.VSCode open Fable.Import.VSCode.Vscode open Ionide.VSCode.Helpers open DTO @@ -11,6 +12,50 @@ module node = Node.Api module InfoPanel = + let private logger = + ConsoleAndOutputChannelLogger(Some "InfoPanel", Level.DEBUG, Some defaultOutputChannel, Some Level.DEBUG) + + [] + let private variadicCommandHandler<'T> (_f: ResizeArray -> 'T) : obj = jsNative + + type private ShowDocumentationRequest = + { XmlDocSig: string + AssemblyName: string + FileName: string option + Line: int option + Character: int option } + + let private isShowDocumentationRequest (value: obj) = + not (isNullOrUndefined value) + && JS.isDefined value?XmlDocSig + && JS.isDefined value?AssemblyName + + let private tryGetOptionalValue<'T> (value: obj) = + if isNullOrUndefined value then + None + else + Some(unbox<'T> value) + + let private toShowDocumentationRequest (o: obj) : ShowDocumentationRequest = + { XmlDocSig = unbox o?XmlDocSig + AssemblyName = unbox o?AssemblyName + FileName = tryGetOptionalValue o?FileName + Line = tryGetOptionalValue o?Line + Character = tryGetOptionalValue o?Character } + + let private toDocumentationRequest (request: ShowDocumentationRequest) : DocumentationForSymbolRequest = + { XmlSig = request.XmlDocSig + Assembly = request.AssemblyName + FileName = request.FileName + Line = request.Line + Character = request.Character } + + let private tryGetShowDocumentationRequest (value: obj) = + if isShowDocumentationRequest value then + Some(toShowDocumentationRequest value) + else + None + let private isFsharpTextEditor (textEditor: TextEditor) = if JS.isDefined textEditor && JS.isDefined textEditor.document then let doc = textEditor.document @@ -206,13 +251,14 @@ module InfoPanel = } |> ignore - let updateOnLink xmlSig assemblyName = + let updateOnLink request = promise { - let! res = LanguageService.documentationForSymbol xmlSig assemblyName + let! res = LanguageService.documentationForSymbol request res |> Option.bind mapContent |> Option.iter setContent } let mutable private timer = None + let mutable private lastTooltipPosition: Position option = None let private clearTimer () = match timer with @@ -263,16 +309,60 @@ module InfoPanel = Panel.update textEditor selection | None -> openPanel () |> ignore - let private showDocumentation o = + let private primeDocumentationContext (request: ShowDocumentationRequest) = + promise { + match request.FileName, request.Line, request.Character with + | Some fileName, Some line, Some character -> + let! _ = LanguageService.documentation (vscode.Uri.file fileName) line character + return () + | _ -> + let textEditor = window.activeTextEditor.Value + + let pos = + match lastTooltipPosition with + | Some pos -> Some pos + | None when isFsharpTextEditor textEditor && textEditor.selections.Count > 0 -> + Some textEditor.selections.[0].active + | None -> None + + match pos with + | Some pos when isFsharpTextEditor textEditor -> + let doc = textEditor.document + let! _ = LanguageService.documentation doc.uri (int pos.line) (int pos.character) + return () + | _ -> return () + } + + let private showDocumentation (args: ResizeArray) = // If the panel doesn't exist, open it // This happens when using click on "Open documentation" from inside // the tooltip promise { - match Panel.panel with - | Some _ -> () - | None -> do! openPanel () - - do! Panel.updateOnLink !!o?XmlDocSig !!o?AssemblyName + let firstArg = if args.Count > 0 then args.[0] else null + + let secondArg = if args.Count > 1 then args.[1] else null + + try + match tryGetShowDocumentationRequest firstArg, tryGetShowDocumentationRequest secondArg with + | Some request, _ + | None, Some request -> + // An explicit documentation link click should pin the linked symbol + // instead of immediately yielding back to cursor-driven updates. + Panel.locked <- true + Context.set "infoPanelLocked" true + + match Panel.panel with + | Some _ -> () + | None -> do! openPanel () + + match request.FileName, request.Line, request.Character with + | Some _, Some _, Some _ -> () + | _ -> do! primeDocumentationContext request + + do! Panel.updateOnLink (toDocumentationRequest request) + | None, None -> () + with ex -> + logger.Error("showDocumentation failed: %O", ex) } let private selectionChanged (event: TextEditorSelectionChangeEvent) = @@ -291,6 +381,7 @@ module InfoPanel = () let tooltipRequested (pos: Position) = + lastTooltipPosition <- Some pos let updateMode = "FSharp.infoPanelUpdate" |> Configuration.get "onCursorMove" if updateMode = "onHover" || updateMode = "both" then @@ -331,7 +422,7 @@ module InfoPanel = commands.registerCommand ("fsharp.openInfoPanel.unlock", unlockPanel |> objfy2) |> context.Subscribe - commands.registerCommand ("fsharp.showDocumentation", showDocumentation |> objfy2) + commands.registerCommand ("fsharp.showDocumentation", variadicCommandHandler showDocumentation |> unbox) |> context.Subscribe if startLocked then diff --git a/src/Core/DTO.fs b/src/Core/DTO.fs index ddac7126..4d7e11b2 100644 --- a/src/Core/DTO.fs +++ b/src/Core/DTO.fs @@ -47,7 +47,12 @@ module DTO = { Files: string[] DisableInMemoryProjectReferences: bool } - type DocumentationForSymbolRequest = { XmlSig: string; Assembly: string } + type DocumentationForSymbolRequest = + { XmlSig: string + Assembly: string + FileName: string option + Line: int option + Character: int option } type OverloadSignature = { Signature: string; Comment: string } diff --git a/src/Core/LanguageService.fs b/src/Core/LanguageService.fs index 071e2149..88047678 100644 --- a/src/Core/LanguageService.fs +++ b/src/Core/LanguageService.fs @@ -260,15 +260,11 @@ Consider: cl.sendRequest ("fsharp/documentation", req) |> Promise.map checkNotificationAndCast> - let documentationForSymbol xmlSig assembly = + let documentationForSymbol (request: DocumentationForSymbolRequest) = match client with | None -> Promise.empty | Some cl -> - let req = - { DocumentationForSymbolRequest.Assembly = assembly - XmlSig = xmlSig } - - cl.sendRequest ("fsharp/documentationSymbol", req) + cl.sendRequest ("fsharp/documentationSymbol", request) |> Promise.map checkNotificationAndCast> let signature (uri: Uri) line col =