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,