diff --git a/monodevelop/MonoDevelop.FSharpBinding/FSharpTextEditorCompletion.fs b/monodevelop/MonoDevelop.FSharpBinding/FSharpTextEditorCompletion.fs index 15ba2e03..8f18e80e 100644 --- a/monodevelop/MonoDevelop.FSharpBinding/FSharpTextEditorCompletion.fs +++ b/monodevelop/MonoDevelop.FSharpBinding/FSharpTextEditorCompletion.fs @@ -24,55 +24,88 @@ type internal FSharpMemberCompletionData(mi:Declaration) = override x.Description = TipFormatter.formatTip mi.DescriptionText override x.Icon = new MonoDevelop.Core.IconId(ServiceUtils.getIcon mi.Glyph) - +/// Completion data representing a delayed fetch of completion data type internal FSharpTryAgainMemberCompletionData() = inherit CompletionData(CompletionText = "", DisplayText="Declarations list not yet available...", DisplayFlags = DisplayFlags.None ) override x.Description = "The declaration list is not yet available or the operation timed out. Try again?" override x.Icon = new MonoDevelop.Core.IconId("md-event") +/// Completion data representing a failure in the ability to get completion data type internal FSharpErrorCompletionData(exn:exn) = inherit CompletionData(CompletionText = "", DisplayText=exn.Message, DisplayFlags = DisplayFlags.None ) let text = exn.ToString() override x.Description = text override x.Icon = new MonoDevelop.Core.IconId("md-event") +/// Provide information to the 'method overloads' windows that comes up when you type '(' type ParameterDataProvider(nameStart: int, meths: MethodOverloads) = interface IParameterDataProvider with member x.Count = meths.Methods.Length + + // Get the index into the file where the parameter completion was triggered member x.StartOffset = nameStart - // Returns the markup to use to represent the specified method overload - // in the parameter information window. + /// Returns the markup to use to represent the specified method overload + /// in the parameter information window. member x.GetHeading (overload:int, parameterMarkup:string[], currentParameter:int) = let meth = meths.Methods.[overload] let text = TipFormatter.formatTip meth.Description - let lines = text.Split([| '\n';'\r' |]) - if lines.Length = 0 then meths.Name else lines.[0] - //prename = GLib.Markup.EscapeText (function.FullName.Substring (0, len + 2)); - //return prename + "" + function.Name + "" + " (" + paramTxt + ")" + cons; - + let lines = text.Split [| '\n';'\r' |] + + // Try to highlight the current parameter in bold. Hack apart the text based on (, comma, and ), then + // put it back together again. + // + // @todo This will not be perfect when the text contains generic types with more than one type parameter + // since they will have extra commas. + + let text = if lines.Length = 0 then meths.Name else lines.[0] + let textL = text.Split '(' + if textL.Length <> 2 then text else + let text0 = textL.[0] + let text1 = textL.[1] + let text1L = text1.Split ')' + if text1L.Length <> 2 then text else + let text10 = text1L.[0] + let text11 = text1L.[1] + let text10L = text10.Split ',' + let text10L = text10L |> Array.mapi (fun i x -> if i = currentParameter then "" + x + "" else x) + textL.[0] + "(" + String.Join(",", text10L) + ")" + text11 + + /// Get the lower part of the text for the display of an overload member x.GetDescription (overload:int, currentParameter:int) = let meth = meths.Methods.[overload] let text = TipFormatter.formatTip meth.Description let lines = text.Split([| '\n';'\r' |]) - if lines.Length <= 1 then "" else lines.[1..] |> String.concat "\n" + let lines = if lines.Length <= 1 then [| "" |] else lines.[1..] + let param = + meth.Parameters |> Array.mapi (fun i param -> + let paramDesc = + // Sometimes the parameter decription is hidden in the XML docs + match TipFormatter.extractParamTip param.Name meth.Description with + | Some tip -> tip + | None -> param.Description + let name = param.Name + let name = if i = currentParameter then "" + name + "" else name + let text = name + ": " + GLib.Markup.EscapeText paramDesc + text + ) + String.Join("\n\n", Array.append lines param) // Returns the text to use to represent the specified parameter member x.GetParameterDescription (overload:int, paramIndex:int) = let meth = meths.Methods.[overload] let param = meth.Parameters.[paramIndex] - param.Description - //return GLib.Markup.EscapeText (function.Parameters[paramIndex]); + param.Name // Returns the number of parameters of the specified method member x.GetParameterCount (overload:int) = let meth = meths.Methods.[overload] meth.Parameters.Length + // @todo should return 'true' for param-list methods member x.AllowParameterList (overload: int) = false - //let meth = meths.Methods.[overload] - //meth. + /// Implements text editor extension for MonoDevelop that shows F# completion type FSharpTextEditorCompletion() = inherit CompletionTextEditorExtension() @@ -83,6 +116,7 @@ type FSharpTextEditorCompletion() = override x.Initialize() = base.Initialize() + /// Provide parameter and method overload information when you type '(', ',' or ')' override x.HandleParameterCompletion(context:CodeCompletionContext, completionChar:char) : IParameterDataProvider = try if (completionChar <> '(' && completionChar <> ',' && completionChar <> ')' ) then null else @@ -149,6 +183,7 @@ type FSharpTextEditorCompletion() = result.Add(new FSharpErrorCompletionData(e)) result :> ICompletionDataList + // @todo find out what this is used for override x.GetParameterCompletionCommandOffset(cpos:byref) = false diff --git a/monodevelop/MonoDevelop.FSharpBinding/Services/LanguageService.fs b/monodevelop/MonoDevelop.FSharpBinding/Services/LanguageService.fs index 85f2c50f..fec01a3e 100644 --- a/monodevelop/MonoDevelop.FSharpBinding/Services/LanguageService.fs +++ b/monodevelop/MonoDevelop.FSharpBinding/Services/LanguageService.fs @@ -59,21 +59,65 @@ module ServiceSettings = /// Formatting of tool-tip information displayed in F# IntelliSense module internal TipFormatter = + /// A standard memoization function + let memoize f = + let d = new System.Collections.Generic.Dictionary<_,_>(HashIdentity.Structural) + fun x -> if d.ContainsKey x then d.[x] else let res = f x in d.[x] <- res; res + + /// Memoize the objects that manage access to XML files. + /// + /// @todo consider if this needs to be a weak table in some way + let xmlDocProvider = memoize (fun x -> ICSharpCode.NRefactory.Documentation.XmlDocumentationProvider(x)) + + /// Return the XmlDocumentationProvider for an assembly + let findXmlDocProviderForAssembly file = + let tryExists s = try if File.Exists s then Some s else None with _ -> None + let e = + match tryExists (Path.ChangeExtension(file,"xml")) with + | Some x -> Some x + | None -> tryExists (Path.ChangeExtension(file,"XML")) + match e with + | None -> None + | Some xmlFile -> + let docReader = xmlDocProvider xmlFile + if docReader = null then None else Some docReader + + + /// Format some of the data returned by the F# compiler let private buildFormatComment cmt (sb:StringBuilder) = match cmt with - | XmlCommentText(s) -> sb.AppendLine("" + GLib.Markup.EscapeText(s) + "") + | XmlCommentText(s) -> sb.AppendLine("" + GLib.Markup.EscapeText(s) + "") |> ignore // For 'XmlCommentSignature' we could get documentation from 'xml' // files, but I'm not sure whether these are available on Mono - | _ -> sb - - // If 'isSingle' is true (meaning that this is the only tip displayed) - // then we add first line "Multiple overloads" because MD prints first - // int in bold (so that no overload is highlighted) + | XmlCommentSignature(file,key) -> + match findXmlDocProviderForAssembly file with + | None -> () + | Some docReader -> + let doc = docReader.GetDocumentation(key) + if not (System.String.IsNullOrEmpty(doc)) then + let summary = + let tag1 = "" + let tag2 = "" + let idx1 = doc.IndexOf tag1 + let idx2 = doc.IndexOf tag2 + if (idx2 >= 0 && idx1 >= 0) then doc.Substring (idx1 + tag1.Length, idx2 - idx1 - tag1.Length) + elif (idx1 >= 0) then doc.Substring (idx1 + tag1.Length) + elif (idx2 >= 0) then doc.Substring (0, idx2 - 1) + else doc + sb.AppendLine("" + GLib.Markup.EscapeText(summary) + "") |> ignore + | _ -> () + + /// Format some of the data returned by the F# compiler + /// + /// If 'isSingle' is true (meaning that this is the only tip displayed) + /// then we add first line "Multiple overloads" because MD prints first + /// int in bold (so that no overload is highlighted) let private buildFormatElement isSingle el (sb:StringBuilder) = match el with - | DataTipElementNone -> sb + | DataTipElementNone -> () | DataTipElement(it, comment) -> - sb.AppendLine(GLib.Markup.EscapeText(it)) |> buildFormatComment comment + sb.AppendLine(GLib.Markup.EscapeText(it)) |> ignore + buildFormatComment comment sb | DataTipElementGroup(items) -> let items, msg = if items.Length > 10 then @@ -82,24 +126,71 @@ module internal TipFormatter = else items, null if (isSingle && items.Length > 1) then sb.AppendLine("Multiple overloads") |> ignore - for (it, comment) in items do - sb.AppendLine(GLib.Markup.EscapeText(it)) |> buildFormatComment comment |> ignore - if msg <> null then sb.AppendFormat(msg) else sb + items |> Seq.iteri (fun i (it,comment) -> + sb.AppendLine(GLib.Markup.EscapeText it) |> ignore + if i = 0 then + sb.Append(GLib.Markup.EscapeText "\n") |> ignore + buildFormatComment comment sb |> ignore + sb.Append(GLib.Markup.EscapeText "\n") |> ignore + ) + if msg <> null then sb.Append(msg) |> ignore | DataTipElementCompositionError(err) -> - sb.Append("Composition error: " + GLib.Markup.EscapeText(err)) + sb.Append("Composition error: " + GLib.Markup.EscapeText(err)) |> ignore + /// Format some of the data returned by the F# compiler let private buildFormatTip tip (sb:StringBuilder) = match tip with - | DataTipText([single]) -> sb |> buildFormatElement true single + | DataTipText([single]) -> buildFormatElement true single sb | DataTipText(its) -> sb.AppendLine("Multiple items") |> ignore - its |> Seq.mapi (fun i it -> i = 0, it) |> Seq.fold (fun sb (first, item) -> - if not first then sb.AppendLine("\n--------------------\n") |> ignore - sb |> buildFormatElement false item) sb + its |> Seq.iteri (fun i item -> + if i <> 0 then sb.AppendLine("\n--------------------\n") |> ignore + buildFormatElement false item sb) /// Format tool-tip that we get from the language service as string let formatTip tip = - (buildFormatTip tip (new StringBuilder())).ToString().Trim('\n', '\r') + let sb = new StringBuilder() + buildFormatTip tip sb + sb.ToString().Trim('\n', '\r') + + /// For elements with XML docs, the paramater descriptions are buried in the XML. Fetch it. + let private extractParamTipFromComment paramName comment = + match comment with + | XmlCommentText(s) -> None + // For 'XmlCommentSignature' we could get documentation from 'xml' + // files, but I'm not sure whether these are available on Mono + | XmlCommentSignature(file,key) -> + match findXmlDocProviderForAssembly file with + | None -> None + | Some docReader -> + let doc = docReader.GetDocumentation(key) + if System.String.IsNullOrEmpty(doc) then None else + // get the ... node + let summary = + let tag1 = "" + let tag2 = "" + let idx1 = doc.IndexOf tag1 + let idx2 = doc.IndexOf(tag2, max idx1 0) + if (idx2 >= 0 && idx1 >= 0) then doc.Substring (idx1 + tag1.Length, idx2 - idx1 - tag1.Length) + elif (idx1 >= 0) then doc.Substring (idx1 + tag1.Length) + elif (idx2 >= 0) then doc.Substring (0, idx2 - 1) + else doc + // remove the from the text + let summary = summary.Replace("","").Replace("\"/>","") + Some (GLib.Markup.EscapeText(summary)) + | _ -> None + + /// For elements with XML docs, the paramater descriptions are buried in the XML. Fetch it. + let private extractParamTipFromElement paramName element = + match element with + | DataTipElementNone -> None + | DataTipElement(it, comment) -> extractParamTipFromComment paramName comment + | DataTipElementGroup(items) -> List.tryPick (snd >> extractParamTipFromComment paramName) items + | DataTipElementCompositionError(err) -> None + + /// For elements with XML docs, the paramater descriptions are buried in the XML. Fetch it. + let extractParamTip paramName (DataTipText elements) = + List.tryPick (extractParamTipFromElement paramName) elements /// Formats tool-tip and turns the first line into heading /// MonoDevelop does this automatically for completion data,