未验证 提交 f0154fbd 编写于 作者: J Jakub Majocha 提交者: GitHub

VS: Some further QuickInfo improvements (#14995)

* first shot

* wip

* reduce diff

* hide params

* fix ugly hack

* feedback

* fix
上级 a92a9389
......@@ -1726,7 +1726,7 @@ type internal TypeCheckInfo
}
let toolTipElement =
FormatStructuredDescriptionOfItem displayFullName infoReader accessorDomain m denv itemWithInst None
FormatStructuredDescriptionOfItem displayFullName infoReader accessorDomain m denv itemWithInst (Some symbol) None
ToolTipText [ toolTipElement ]
......@@ -1757,7 +1757,8 @@ type internal TypeCheckInfo
ToolTipText(
items
|> List.map (fun x ->
FormatStructuredDescriptionOfItem false infoReader tcAccessRights m denv x.ItemWithInst width)
let symbol = Some(FSharpSymbol.Create(cenv, x.Item))
FormatStructuredDescriptionOfItem false infoReader tcAccessRights m denv x.ItemWithInst symbol width)
))
(fun err ->
......@@ -2716,12 +2717,7 @@ type FSharpCheckFileResults
| None -> ()
| Some kwDescription ->
let kwText = kw |> TaggedText.tagKeyword |> wordL |> LayoutRender.toArray
let kwTip = ToolTipElementData.Create(kwText, FSharpXmlDoc.None)
let descText = kwDescription |> TaggedText.tagText |> wordL |> LayoutRender.toArray
let descTip = ToolTipElementData.Create(descText, FSharpXmlDoc.None)
yield ToolTipElement.Group [ kwTip; descTip ]
yield ToolTipElement.Single(kwText, FSharpXmlDoc.FromXmlText(Xml.XmlDoc([| kwDescription |], range.Zero)))
]
/// Resolve the names at the given location to give a data tip
......
......@@ -34,14 +34,16 @@ open FSharp.Compiler.TypedTreeOps
/// A single data tip display element
[<RequireQualifiedAccess>]
type ToolTipElementData =
{ MainDescription: TaggedText[]
{
Symbol: FSharpSymbol option
MainDescription: TaggedText[]
XmlDoc: FSharpXmlDoc
TypeMapping: TaggedText[] list
Remarks: TaggedText[] option
ParamName : string option }
static member internal Create(layout, xml, ?typeMapping, ?paramName, ?remarks) =
{ MainDescription=layout; XmlDoc=xml; TypeMapping=defaultArg typeMapping []; ParamName=paramName; Remarks=remarks }
static member internal Create(layout, xml, ?typeMapping, ?paramName, ?remarks, ?symbol) =
{ MainDescription=layout; XmlDoc=xml; TypeMapping=defaultArg typeMapping []; ParamName=paramName; Remarks=remarks; Symbol = symbol }
/// A single data tip display element
[<RequireQualifiedAccess>]
......@@ -54,8 +56,8 @@ type ToolTipElement =
/// An error occurred formatting this element
| CompositionError of errorText: string
static member Single(layout, xml, ?typeMapping, ?paramName, ?remarks) =
Group [ ToolTipElementData.Create(layout, xml, ?typeMapping=typeMapping, ?paramName=paramName, ?remarks=remarks) ]
static member Single(layout, xml, ?typeMapping, ?paramName, ?remarks, ?symbol) =
Group [ ToolTipElementData.Create(layout, xml, ?typeMapping=typeMapping, ?paramName=paramName, ?remarks=remarks, ?symbol = symbol) ]
/// Information for building a data tip box.
type ToolTipText =
......@@ -93,7 +95,7 @@ module DeclarationListHelpers =
let emptyToolTip = ToolTipText []
/// Generate the structured tooltip for a method info
let FormatOverloadsToList (infoReader: InfoReader) m denv (item: ItemWithInst) minfos (width: int option) : ToolTipElement =
let FormatOverloadsToList (infoReader: InfoReader) m denv (item: ItemWithInst) minfos symbol (width: int option) : ToolTipElement =
ToolTipFault |> Option.iter (fun msg ->
let exn = Error((0, msg), range.Zero)
let ph = PhasedDiagnostic.Create(exn, BuildPhase.TypeCheck)
......@@ -107,7 +109,7 @@ module DeclarationListHelpers =
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
let tpsL = List.map toArray tpsL
ToolTipElementData.Create(layout, xml, tpsL) ]
ToolTipElementData.Create(layout, xml, tpsL, ?symbol = symbol) ]
ToolTipElement.Group layouts
......@@ -149,15 +151,16 @@ module DeclarationListHelpers =
let pubpathOfTyconRef (x: TyconRef) = x.PublicPath
/// Output the quick info information of a language item
let rec FormatItemDescriptionToToolTipElement displayFullName (infoReader: InfoReader) ad m denv (item: ItemWithInst) (width: int option) =
let rec FormatItemDescriptionToToolTipElement displayFullName (infoReader: InfoReader) ad m denv (item: ItemWithInst) symbol (width: int option) =
let g = infoReader.g
let amap = infoReader.amap
let denv = SimplerDisplayEnv denv
let xml = GetXmlCommentForItem infoReader m item.Item
match item.Item with
| Item.ImplicitOp(_, { contents = Some(TraitConstraintSln.FSMethSln(vref=vref)) }) ->
// operator with solution
FormatItemDescriptionToToolTipElement displayFullName infoReader ad m denv { item with Item = Item.Value vref } width
FormatItemDescriptionToToolTipElement displayFullName infoReader ad m denv { item with Item = Item.Value vref } symbol width
| Item.Value vref | Item.CustomBuilder (_, vref) ->
let prettyTyparInst, resL = NicePrint.layoutQualifiedValOrMember denv infoReader item.TyparInstantiation vref
......@@ -167,7 +170,7 @@ module DeclarationListHelpers =
let resL = PrintUtilities.squashToWidth width resL
let resL = toArray resL
let remarks = toArray remarks
ToolTipElement.Single(resL, xml, tpsL, remarks=remarks)
ToolTipElement.Single(resL, xml, tpsL, remarks=remarks, ?symbol = symbol)
// Union tags (constructors)
| Item.UnionCase(ucinfo, _) ->
......@@ -184,7 +187,7 @@ module DeclarationListHelpers =
NicePrint.layoutType denv unionTy
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml)
ToolTipElement.Single (layout, xml, ?symbol = symbol)
// Active pattern tag inside the declaration (result)
| Item.ActivePatternResult(apinfo, ty, idx, _) ->
......@@ -196,7 +199,7 @@ module DeclarationListHelpers =
NicePrint.layoutType denv ty
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml)
ToolTipElement.Single (layout, xml, ?symbol = symbol)
// Active pattern tags
| Item.ActivePatternCase apref ->
......@@ -217,7 +220,7 @@ module DeclarationListHelpers =
let layout = toArray layout
let tpsL = List.map toArray tpsL
let remarks = toArray remarks
ToolTipElement.Single (layout, xml, tpsL, remarks=remarks)
ToolTipElement.Single (layout, xml, tpsL, remarks=remarks, ?symbol = symbol)
// F# exception names
| Item.ExnCase ecref ->
......@@ -226,7 +229,7 @@ module DeclarationListHelpers =
let remarks = OutputFullName displayFullName pubpathOfTyconRef fullDisplayTextOfExnRefAsLayout ecref
let layout = toArray layout
let remarks = toArray remarks
ToolTipElement.Single (layout, xml, remarks=remarks)
ToolTipElement.Single (layout, xml, remarks=remarks, ?symbol = symbol)
| Item.RecdField rfinfo when rfinfo.TyconRef.IsFSharpException ->
let ty, _ = PrettyTypes.PrettifyType g rfinfo.FieldType
......@@ -238,7 +241,7 @@ module DeclarationListHelpers =
NicePrint.layoutType denv ty
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml, paramName = id)
ToolTipElement.Single (layout, xml, paramName = id, ?symbol = symbol)
// F# record field names
| Item.RecdField rfinfo ->
......@@ -257,7 +260,7 @@ module DeclarationListHelpers =
)
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml)
ToolTipElement.Single (layout, xml, ?symbol = symbol)
| Item.UnionCaseField (ucinfo, fieldIndex) ->
let rfield = ucinfo.UnionCase.GetFieldByIndex(fieldIndex)
......@@ -270,7 +273,7 @@ module DeclarationListHelpers =
NicePrint.layoutType denv fieldTy
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml, paramName = id.idText)
ToolTipElement.Single (layout, xml, paramName = id.idText, ?symbol = symbol)
// Not used
| Item.NewDef id ->
......@@ -279,7 +282,7 @@ module DeclarationListHelpers =
wordL (tagUnknownEntity id.idText)
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml)
ToolTipElement.Single (layout, xml, ?symbol = symbol)
// .NET fields
| Item.ILField finfo ->
......@@ -299,7 +302,7 @@ module DeclarationListHelpers =
)
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml)
ToolTipElement.Single (layout, xml, ?symbol = symbol)
// .NET events
| Item.Event einfo ->
......@@ -314,14 +317,14 @@ module DeclarationListHelpers =
NicePrint.layoutType denv eventTy
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml)
ToolTipElement.Single (layout, xml, ?symbol = symbol)
// F# and .NET properties
| Item.Property(_, pinfo :: _) ->
let layout = NicePrint.prettyLayoutOfPropInfoFreeStyle g amap m denv pinfo
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml)
ToolTipElement.Single (layout, xml, ?symbol = symbol)
// Custom operations in queries
| Item.CustomOperation (customOpName, usageText, Some minfo) ->
......@@ -348,12 +351,12 @@ module DeclarationListHelpers =
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml)
ToolTipElement.Single (layout, xml, ?symbol = symbol)
// F# constructors and methods
| Item.CtorGroup(_, minfos)
| Item.MethodGroup(_, minfos, _) ->
FormatOverloadsToList infoReader m denv item minfos width
FormatOverloadsToList infoReader m denv item minfos symbol width
// The 'fake' zero-argument constructors of .NET interfaces.
// This ideally should never appear in intellisense, but we do get here in repros like:
......@@ -365,7 +368,7 @@ module DeclarationListHelpers =
let layout = NicePrint.layoutTyconRef denv (tcrefOfAppTy g ty)
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single(layout, xml)
ToolTipElement.Single(layout, xml, ?symbol = symbol)
// The 'fake' representation of constructors of .NET delegate types
| Item.DelegateCtor delTy ->
......@@ -378,7 +381,7 @@ module DeclarationListHelpers =
RightL.rightParen
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single(layout, xml)
ToolTipElement.Single(layout, xml, ?symbol = symbol)
// Types.
| Item.Types(_, TType_app(tcref, _, _) :: _)
......@@ -395,20 +398,20 @@ module DeclarationListHelpers =
let remarks = OutputFullName displayFullName pubpathOfTyconRef fullDisplayTextOfTyconRefAsLayout tcref
let layout = toArray layout
let remarks = toArray remarks
ToolTipElement.Single (layout, xml, remarks=remarks)
ToolTipElement.Single (layout, xml, remarks=remarks, ?symbol = symbol)
// Type variables
| Item.TypeVar (_, typar) ->
let layout = NicePrint.prettyLayoutOfTypar denv typar
let layout = PrintUtilities.squashToWidth width layout
ToolTipElement.Single (toArray layout, xml)
ToolTipElement.Single (toArray layout, xml, ?symbol = symbol)
// Traits
| Item.Trait traitInfo ->
let denv = { denv with shortConstraints = false}
let layout = NicePrint.prettyLayoutOfTrait denv traitInfo
let layout = PrintUtilities.squashToWidth width layout
ToolTipElement.Single (toArray layout, xml)
ToolTipElement.Single (toArray layout, xml, ?symbol = symbol)
// F# Modules and namespaces
| Item.ModuleOrNamespaces(modref :: _ as modrefs) ->
......@@ -449,11 +452,11 @@ module DeclarationListHelpers =
)
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml)
ToolTipElement.Single (layout, xml, ?symbol = symbol)
else
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml)
ToolTipElement.Single (layout, xml, ?symbol = symbol)
| Item.AnonRecdField(anon, argTys, i, _) ->
let argTy = argTys[i]
......@@ -466,7 +469,7 @@ module DeclarationListHelpers =
NicePrint.layoutType denv argTy
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, FSharpXmlDoc.None)
ToolTipElement.Single (layout, FSharpXmlDoc.None, ?symbol = symbol)
// Named parameters
| Item.ArgName (Some id, argTy, _, _) ->
......@@ -478,10 +481,10 @@ module DeclarationListHelpers =
NicePrint.layoutType denv argTy
let layout = PrintUtilities.squashToWidth width layout
let layout = toArray layout
ToolTipElement.Single (layout, xml, paramName = id.idText)
ToolTipElement.Single (layout, xml, paramName = id.idText, ?symbol = symbol)
| Item.SetterArg (_, item) ->
FormatItemDescriptionToToolTipElement displayFullName infoReader ad m denv (ItemWithNoInst item) width
FormatItemDescriptionToToolTipElement displayFullName infoReader ad m denv (ItemWithNoInst item) symbol width
| Item.ArgName (None, _, _, _)
......@@ -523,9 +526,9 @@ module DeclarationListHelpers =
| Item.CustomOperation (_, _, None) -> ToolTipElement.None
/// Format the structured version of a tooltip for an item
let FormatStructuredDescriptionOfItem isDecl infoReader ad m denv item width =
let FormatStructuredDescriptionOfItem isDecl infoReader ad m denv item symbol width =
DiagnosticsScope.Protect m
(fun () -> FormatItemDescriptionToToolTipElement isDecl infoReader ad m denv item width)
(fun () -> FormatItemDescriptionToToolTipElement isDecl infoReader ad m denv item symbol width)
(fun err -> ToolTipElement.CompositionError err)
/// Represents one parameter for one method (or other item) in a group.
......@@ -1034,7 +1037,7 @@ type DeclarationListItem(textInDeclList: string, textInCode: string, fullName: s
member _.Description =
match info with
| Choice1Of2 (items: CompletionItem list, infoReader, ad, m, denv) ->
ToolTipText(items |> List.map (fun x -> FormatStructuredDescriptionOfItem true infoReader ad m denv x.ItemWithInst None))
ToolTipText(items |> List.map (fun x -> FormatStructuredDescriptionOfItem true infoReader ad m denv x.ItemWithInst None None))
| Choice2Of2 result ->
result
......@@ -1297,7 +1300,7 @@ type MethodGroup( name: string, unsortedMethods: MethodGroupItem[] ) =
(fun () -> PrettyParamsAndReturnTypeOfItem infoReader m denv { item with Item = flatItem })
(fun err -> [], wordL (tagText err))
let description = ToolTipText [FormatStructuredDescriptionOfItem true infoReader ad m denv { item with Item = flatItem } None]
let description = ToolTipText [FormatStructuredDescriptionOfItem true infoReader ad m denv { item with Item = flatItem } None None]
let hasParamArrayArg =
match flatItem with
......
......@@ -17,6 +17,8 @@ open FSharp.Compiler.AccessibilityLogic
[<RequireQualifiedAccess>]
type public ToolTipElementData =
{
Symbol: FSharpSymbol option
MainDescription: TaggedText[]
XmlDoc: FSharpXmlDoc
......@@ -31,7 +33,7 @@ type public ToolTipElementData =
ParamName: string option
}
static member internal Create: layout: TaggedText[] * xml: FSharpXmlDoc * ?typeMapping: TaggedText[] list * ?paramName: string * ?remarks: TaggedText[] -> ToolTipElementData
static member internal Create: layout: TaggedText[] * xml: FSharpXmlDoc * ?typeMapping: TaggedText[] list * ?paramName: string * ?remarks: TaggedText[] * ?symbol: FSharpSymbol -> ToolTipElementData
/// A single tool tip display element
//
......@@ -46,7 +48,7 @@ type public ToolTipElement =
/// An error occurred formatting this element
| CompositionError of errorText: string
static member Single: layout: TaggedText[] * xml: FSharpXmlDoc * ?typeMapping: TaggedText[] list * ?paramName: string * ?remarks: TaggedText[] -> ToolTipElement
static member Single: layout: TaggedText[] * xml: FSharpXmlDoc * ?typeMapping: TaggedText[] list * ?paramName: string * ?remarks: TaggedText[] * ?symbol: FSharpSymbol -> ToolTipElement
/// Information for building a tool tip box.
//
......@@ -226,7 +228,7 @@ type public MethodGroup =
static member internal Empty: MethodGroup
module internal DeclarationListHelpers =
val FormatStructuredDescriptionOfItem: isDecl:bool -> InfoReader -> AccessorDomain -> range -> DisplayEnv -> ItemWithInst -> int option -> ToolTipElement
val FormatStructuredDescriptionOfItem: isDecl:bool -> InfoReader -> AccessorDomain -> range -> DisplayEnv -> ItemWithInst -> FSharpSymbol option -> int option -> ToolTipElement
val RemoveDuplicateCompletionItems: TcGlobals -> CompletionItem list -> CompletionItem list
......
......@@ -3970,7 +3970,7 @@ FSharp.Compiler.EditorServices.ToolTipElement: Boolean get_IsNone()
FSharp.Compiler.EditorServices.ToolTipElement: FSharp.Compiler.EditorServices.ToolTipElement NewCompositionError(System.String)
FSharp.Compiler.EditorServices.ToolTipElement: FSharp.Compiler.EditorServices.ToolTipElement NewGroup(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.EditorServices.ToolTipElementData])
FSharp.Compiler.EditorServices.ToolTipElement: FSharp.Compiler.EditorServices.ToolTipElement None
FSharp.Compiler.EditorServices.ToolTipElement: FSharp.Compiler.EditorServices.ToolTipElement Single(FSharp.Compiler.Text.TaggedText[], FSharp.Compiler.Symbols.FSharpXmlDoc, Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Text.TaggedText[]]], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.TaggedText[]])
FSharp.Compiler.EditorServices.ToolTipElement: FSharp.Compiler.EditorServices.ToolTipElement Single(FSharp.Compiler.Text.TaggedText[], FSharp.Compiler.Symbols.FSharpXmlDoc, Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Text.TaggedText[]]], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.TaggedText[]], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Symbols.FSharpSymbol])
FSharp.Compiler.EditorServices.ToolTipElement: FSharp.Compiler.EditorServices.ToolTipElement get_None()
FSharp.Compiler.EditorServices.ToolTipElement: FSharp.Compiler.EditorServices.ToolTipElement+CompositionError
FSharp.Compiler.EditorServices.ToolTipElement: FSharp.Compiler.EditorServices.ToolTipElement+Group
......@@ -3991,12 +3991,14 @@ FSharp.Compiler.EditorServices.ToolTipElementData: Int32 GetHashCode()
FSharp.Compiler.EditorServices.ToolTipElementData: Int32 GetHashCode(System.Collections.IEqualityComparer)
FSharp.Compiler.EditorServices.ToolTipElementData: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Text.TaggedText[]] TypeMapping
FSharp.Compiler.EditorServices.ToolTipElementData: Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Text.TaggedText[]] get_TypeMapping()
FSharp.Compiler.EditorServices.ToolTipElementData: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Symbols.FSharpSymbol] Symbol
FSharp.Compiler.EditorServices.ToolTipElementData: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Symbols.FSharpSymbol] get_Symbol()
FSharp.Compiler.EditorServices.ToolTipElementData: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.TaggedText[]] Remarks
FSharp.Compiler.EditorServices.ToolTipElementData: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.TaggedText[]] get_Remarks()
FSharp.Compiler.EditorServices.ToolTipElementData: Microsoft.FSharp.Core.FSharpOption`1[System.String] ParamName
FSharp.Compiler.EditorServices.ToolTipElementData: Microsoft.FSharp.Core.FSharpOption`1[System.String] get_ParamName()
FSharp.Compiler.EditorServices.ToolTipElementData: System.String ToString()
FSharp.Compiler.EditorServices.ToolTipElementData: Void .ctor(FSharp.Compiler.Text.TaggedText[], FSharp.Compiler.Symbols.FSharpXmlDoc, Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Text.TaggedText[]], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.TaggedText[]], Microsoft.FSharp.Core.FSharpOption`1[System.String])
FSharp.Compiler.EditorServices.ToolTipElementData: Void .ctor(Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Symbols.FSharpSymbol], FSharp.Compiler.Text.TaggedText[], FSharp.Compiler.Symbols.FSharpXmlDoc, Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Text.TaggedText[]], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.TaggedText[]], Microsoft.FSharp.Core.FSharpOption`1[System.String])
FSharp.Compiler.EditorServices.ToolTipText: Boolean Equals(FSharp.Compiler.EditorServices.ToolTipText)
FSharp.Compiler.EditorServices.ToolTipText: Boolean Equals(System.Object)
FSharp.Compiler.EditorServices.ToolTipText: Boolean Equals(System.Object, System.Collections.IEqualityComparer)
......@@ -298,3 +298,16 @@ module internal OpenDeclarationHelper =
module internal TaggedText =
let toString (tts: TaggedText[]) =
tts |> Array.map (fun tt -> tt.Text) |> String.concat ""
// http://www.fssnip.net/7S3/title/Intersperse-a-list
module List =
/// The intersperse function takes an element and a list and
/// 'intersperses' that element between the elements of the list.
let intersperse sep ls =
List.foldBack
(fun x ->
function
| [] -> [ x ]
| xs -> x :: sep :: xs)
ls
[]
......@@ -324,7 +324,7 @@ module internal XmlDocumentation =
/// Append Xml documentation contents into the StringBuilder
override this.AppendDocumentation
( /// ITaggedTextCollector to add to
( // ITaggedTextCollector to add to
xmlCollector: ITaggedTextCollector,
exnCollector: ITaggedTextCollector,
fileName: string,
......@@ -392,12 +392,105 @@ module internal XmlDocumentation =
paramName
)
[<Literal>]
let separatorText = "-------------"
let private AddSeparator (collector: ITaggedTextCollector) =
if not collector.IsEmpty then
EnsureHardLine collector
collector.Add(tagText "-------------")
collector.Add(tagText separatorText)
AppendHardLine collector
type LineLimits =
{
LineLimit: int
TypeParameterLimit: int
OverLoadsLimit: int
}
let DefaultLineLimits =
{
LineLimit = 45
TypeParameterLimit = 6
OverLoadsLimit = 5
}
let BuildSingleTipText (documentationProvider: IDocumentationBuilder, dataTipElement: ToolTipElement, limits: LineLimits) =
let {
LineLimit = lineLimit
TypeParameterLimit = typeParameterLineLimit
OverLoadsLimit = overLoadsLimit
} =
limits
let mainDescription, documentation, typeParameterMap, exceptions, usage =
ResizeArray(), ResizeArray(), ResizeArray(), ResizeArray(), ResizeArray()
let textCollector: ITaggedTextCollector =
TextSanitizingCollector(mainDescription.Add, lineLimit = lineLimit)
let xmlCollector: ITaggedTextCollector =
TextSanitizingCollector(documentation.Add, lineLimit = lineLimit)
let typeParameterMapCollector: ITaggedTextCollector =
TextSanitizingCollector(typeParameterMap.Add, lineLimit = typeParameterLineLimit)
let exnCollector: ITaggedTextCollector =
TextSanitizingCollector(exceptions.Add, lineLimit = lineLimit)
let usageCollector: ITaggedTextCollector =
TextSanitizingCollector(usage.Add, lineLimit = lineLimit)
let ProcessGenericParameters (tps: TaggedText[] list) =
if not tps.IsEmpty then
AppendHardLine typeParameterMapCollector
AppendOnNewLine typeParameterMapCollector (SR.GenericParametersHeader())
for tp in tps do
AppendHardLine typeParameterMapCollector
typeParameterMapCollector.Add(tagSpace " ")
tp |> Array.iter typeParameterMapCollector.Add
let collectDocumentation () =
[ documentation; typeParameterMap; exceptions; usage ]
|> List.filter (Seq.isEmpty >> not)
|> List.map List.ofSeq
|> List.intersperse [ lineBreak ]
|> Seq.concat
|> List.ofSeq
match dataTipElement with
| ToolTipElement.Group overloads when not overloads.IsEmpty ->
overloads[.. overLoadsLimit - 1]
|> List.map (fun item -> item.MainDescription)
|> List.intersperse [| lineBreak |]
|> Seq.concat
|> Seq.iter textCollector.Add
if not overloads[overLoadsLimit..].IsEmpty then
AppendOnNewLine textCollector $"({(PrettyNaming.FormatAndOtherOverloadsString overloads[overLoadsLimit..].Length)})"
let item0 = overloads.Head
item0.Remarks
|> Option.iter (fun r ->
if TaggedText.toString r <> "" then
AppendHardLine usageCollector
r |> Seq.iter usageCollector.Add)
AppendXmlComment(documentationProvider, xmlCollector, exnCollector, item0.XmlDoc, true, false, item0.ParamName)
ProcessGenericParameters item0.TypeMapping
item0.Symbol, mainDescription |> List.ofSeq, collectDocumentation ()
| ToolTipElement.CompositionError (errText) ->
textCollector.Add(tagText errText)
None, mainDescription |> List.ofSeq, collectDocumentation ()
| _ -> None, [], []
/// Build a data tip text string with xml comments injected.
let BuildTipText
(
......
......@@ -10,22 +10,15 @@ open FSharp.Compiler.Text.Range
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.GoToDefinition
open Microsoft.VisualStudio.Shell
open Microsoft.VisualStudio.Shell.Interop
open System.Collections.Immutable
open System.Threading.Tasks
[<Export(typeof<IFSharpFindDefinitionService>)>]
[<Export(typeof<FSharpFindDefinitionService>)>]
type internal FSharpFindDefinitionService [<ImportingConstructor>] (metadataAsSource: FSharpMetadataAsSourceService) =
let statusBar =
StatusBar(ServiceProvider.GlobalProvider.GetService<SVsStatusbar, IVsStatusbar>())
interface IFSharpFindDefinitionService with
member _.FindDefinitionsAsync(document: Document, position: int, cancellationToken: CancellationToken) =
let navigation =
FSharpNavigation(statusBar, metadataAsSource, document, rangeStartup)
let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup)
let definitions = navigation.FindDefinitions(position, cancellationToken)
ImmutableArray.CreateRange(definitions) |> Task.FromResult
......@@ -109,7 +109,10 @@ module private ExternalSymbol =
| _ -> []
// TODO: Uncomment code when VS has a fix for updating the status bar.
type StatusBar(statusBar: IVsStatusbar) =
type StatusBar() =
let statusBar =
ServiceProvider.GlobalProvider.GetService<SVsStatusbar, IVsStatusbar>()
let mutable _searchIcon =
int16 Microsoft.VisualStudio.Shell.Interop.Constants.SBAI_Find :> obj
......@@ -394,7 +397,6 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
(
document: Document,
textSpan: Microsoft.CodeAnalysis.Text.TextSpan,
statusBar: StatusBar,
cancellationToken: CancellationToken
) =
let navigableItem = FSharpGoToDefinitionNavigableItem(document, textSpan)
......@@ -407,9 +409,10 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, cancellationToken)
if not navigationSucceeded then
statusBar.TempMessage(SR.CannotNavigateUnknown())
StatusBar().TempMessage(SR.CannotNavigateUnknown())
member _.NavigateToItem(navigableItem: FSharpNavigableItem, statusBar: StatusBar, cancellationToken: CancellationToken) =
member _.NavigateToItem(navigableItem: FSharpNavigableItem, cancellationToken: CancellationToken) =
let statusBar = StatusBar()
use __ = statusBar.Animate()
statusBar.Message(SR.NavigatingTo())
......@@ -434,12 +437,11 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
targetDocument: Document,
targetSourceText: SourceText,
symbolRange: range,
statusBar: StatusBar,
cancellationToken: CancellationToken
) =
asyncMaybe {
let! item = this.FindDeclarationOfSymbolAtRange(targetDocument, symbolRange, targetSourceText)
return this.NavigateToItem(item, statusBar, cancellationToken)
return this.NavigateToItem(item, cancellationToken)
}
/// Find the definition location (implementation file/.fs) of the target symbol
......@@ -448,12 +450,11 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
targetDocument: Document,
targetSourceText: SourceText,
symbolRange: range,
statusBar: StatusBar,
cancellationToken: CancellationToken
) =
asyncMaybe {
let! item = this.FindDefinitionOfSymbolAtRange(targetDocument, symbolRange, targetSourceText)
return this.NavigateToItem(item, statusBar, cancellationToken)
return this.NavigateToItem(item, cancellationToken)
}
member this.NavigateToExternalDeclaration
......@@ -546,7 +547,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
| _ -> TextSpan()
let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, span)
this.NavigateToItem(navItem, statusBar, cancellationToken)
this.NavigateToItem(navItem, cancellationToken)
true
| _ -> false
| _ -> false
......@@ -556,201 +557,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) =
else
statusBar.TempMessage(SR.CannotNavigateUnknown())
type internal FSharpQuickInfo =
{
StructuredText: ToolTipText
Span: TextSpan
Symbol: FSharpSymbol option
SymbolKind: LexerSymbolKind
}
module internal FSharpQuickInfo =
let userOpName = "QuickInfo"
// when a construct has been declared in a signature file the documentation comments that are
// written in that file are the ones that go into the generated xml when the project is compiled
// therefore we should include these doccoms in our design time quick info
let getQuickInfoFromRange
(
document: Document,
declRange: range,
width: int option,
cancellationToken: CancellationToken
) : Async<FSharpQuickInfo option> =
asyncMaybe {
let userOpName = "getQuickInfoFromRange"
let solution = document.Project.Solution
// ascertain the location of the target declaration in the signature file
let! extDocId = solution.GetDocumentIdsWithFilePath declRange.FileName |> Seq.tryHead
let extDocument = solution.GetProject(extDocId.ProjectId).GetDocument extDocId
let! extSourceText = extDocument.GetTextAsync cancellationToken
let! extSpan = RoslynHelpers.TryFSharpRangeToTextSpan(extSourceText, declRange)
let extLineText = (extSourceText.Lines.GetLineFromPosition extSpan.Start).ToString()
// project options need to be retrieved because the signature file could be in another project
let! extLexerSymbol = extDocument.TryFindFSharpLexerSymbolAsync(extSpan.Start, SymbolLookupKind.Greedy, true, true, userOpName)
let! _, extCheckFileResults = extDocument.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync
let extQuickInfoText =
extCheckFileResults.GetToolTip(
declRange.StartLine,
extLexerSymbol.Ident.idRange.EndColumn,
extLineText,
extLexerSymbol.FullIsland,
FSharpTokenTag.IDENT,
?width = width
)
match extQuickInfoText with
| ToolTipText []
| ToolTipText [ ToolTipElement.None ] -> return! None
| extQuickInfoText ->
let! extSymbolUse =
extCheckFileResults.GetSymbolUseAtLocation(
declRange.StartLine,
extLexerSymbol.Ident.idRange.EndColumn,
extLineText,
extLexerSymbol.FullIsland
)
let! span = RoslynHelpers.TryFSharpRangeToTextSpan(extSourceText, extLexerSymbol.Range)
return
{
StructuredText = extQuickInfoText
Span = span
Symbol = Some extSymbolUse.Symbol
SymbolKind = extLexerSymbol.Kind
}
}
/// Get QuickInfo combined from doccom of Signature and definition
let getQuickInfo
(
document: Document,
position: int,
width: int option,
cancellationToken: CancellationToken
) : Async<(range * FSharpQuickInfo option * FSharpQuickInfo option) option> =
asyncMaybe {
let userOpName = "getQuickInfo"
let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, true, true, userOpName)
let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync
let! sourceText = document.GetTextAsync cancellationToken
let idRange = lexerSymbol.Ident.idRange
let textLinePos = sourceText.Lines.GetLinePosition position
let fcsTextLineNumber = Line.fromZ textLinePos.Line
let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
/// Gets the QuickInfo information for the orignal target
let getTargetSymbolQuickInfo (symbol, tag) =
asyncMaybe {
let targetQuickInfo =
match lexerSymbol.Kind with
| LexerSymbolKind.Keyword -> checkFileResults.GetKeywordTooltip(lexerSymbol.FullIsland)
| _ ->
checkFileResults.GetToolTip(
fcsTextLineNumber,
idRange.EndColumn,
lineText,
lexerSymbol.FullIsland,
tag,
?width = width
)
match targetQuickInfo with
| ToolTipText []
| ToolTipText [ ToolTipElement.None ] -> return! None
| _ ->
let! targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lexerSymbol.Range)
return
{
StructuredText = targetQuickInfo
Span = targetTextSpan
Symbol = symbol
SymbolKind = lexerSymbol.Kind
}
}
match lexerSymbol.Kind with
| LexerSymbolKind.Keyword
| LexerSymbolKind.String ->
let! targetQuickInfo = getTargetSymbolQuickInfo (None, FSharpTokenTag.STRING)
return lexerSymbol.Range, None, Some targetQuickInfo
| _ ->
let! symbolUse =
checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland)
// if the target is in a signature file, adjusting the quick info is unnecessary
if isSignatureFile document.FilePath then
let! targetQuickInfo = getTargetSymbolQuickInfo (Some symbolUse.Symbol, FSharpTokenTag.IDENT)
return symbolUse.Range, None, Some targetQuickInfo
else
// find the declaration location of the target symbol, with a preference for signature files
let findSigDeclarationResult =
checkFileResults.GetDeclarationLocation(
idRange.StartLine,
idRange.EndColumn,
lineText,
lexerSymbol.FullIsland,
preferFlag = true
)
// it is necessary to retrieve the backup quick info because this acquires
// the textSpan designating where we want the quick info to appear.
let! targetQuickInfo = getTargetSymbolQuickInfo (Some symbolUse.Symbol, FSharpTokenTag.IDENT)
let! result =
match findSigDeclarationResult with
| FindDeclResult.DeclFound declRange when isSignatureFile declRange.FileName ->
asyncMaybe {
let! sigQuickInfo = getQuickInfoFromRange (document, declRange, width, cancellationToken)
// if the target was declared in a signature file, and the current file
// is not the corresponding module implementation file for that signature,
// the doccoms from the signature will overwrite any doccoms that might be
// present on the definition/implementation
let findImplDefinitionResult =
checkFileResults.GetDeclarationLocation(
idRange.StartLine,
idRange.EndColumn,
lineText,
lexerSymbol.FullIsland,
preferFlag = false
)
match findImplDefinitionResult with
| FindDeclResult.DeclNotFound _
| FindDeclResult.ExternalDecl _ -> return symbolUse.Range, Some sigQuickInfo, None
| FindDeclResult.DeclFound declRange ->
let! implQuickInfo = getQuickInfoFromRange (document, declRange, width, cancellationToken)
return
symbolUse.Range,
Some sigQuickInfo,
Some
{ implQuickInfo with
Span = targetQuickInfo.Span
}
}
| _ -> async.Return None
|> liftAsync
return result |> Option.defaultValue (symbolUse.Range, None, Some targetQuickInfo)
}
type internal FSharpNavigation
(
statusBar: StatusBar,
metadataAsSource: FSharpMetadataAsSourceService,
initialDoc: Document,
thisSymbolUseRange: range
) =
type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, initialDoc: Document, thisSymbolUseRange: range) =
let workspace = initialDoc.Project.Solution.Workspace
let solution = workspace.CurrentSolution
......@@ -791,15 +598,13 @@ type internal FSharpNavigation
match initialDoc.FilePath, targetPath with
| Signature, Signature
| Implementation, Implementation -> return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, statusBar, cancellationToken)
| Implementation, Implementation -> return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken)
// Adjust the target from signature to implementation.
| Implementation, Signature ->
return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range, statusBar, cancellationToken)
| Implementation, Signature -> return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range, cancellationToken)
// Adjust the target from implmentation to signature.
| Signature, Implementation ->
return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, range, statusBar, cancellationToken)
| Signature, Implementation -> return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, range, cancellationToken)
}
|> Async.Ignore
|> Async.StartImmediate
......@@ -818,6 +623,7 @@ type internal FSharpNavigation
member _.TryGoToDefinition(position, cancellationToken) =
let gtd = GoToDefinition(metadataAsSource)
let statusBar = StatusBar()
let gtdTask = gtd.FindDefinitionTask(initialDoc, position, cancellationToken)
// Wrap this in a try/with as if the user clicks "Cancel" on the thread dialog, we'll be cancelled.
......@@ -829,7 +635,7 @@ type internal FSharpNavigation
if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then
match gtdTask.Result.Value with
| FSharpGoToDefinitionResult.NavigableItem (navItem), _ ->
gtd.NavigateToItem(navItem, statusBar, cancellationToken)
gtd.NavigateToItem(navItem, cancellationToken)
// 'true' means do it, like Sheev Palpatine would want us to.
true
| FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences), _ ->
......@@ -876,7 +682,7 @@ type internal DocCommentId =
| Type of EntityPath: string list
| None
type FSharpNavigableLocation(statusBar: StatusBar, metadataAsSource: FSharpMetadataAsSourceService, symbolRange: range, project: Project) =
type FSharpNavigableLocation(metadataAsSource: FSharpMetadataAsSourceService, symbolRange: range, project: Project) =
interface IFSharpNavigableLocation with
member _.NavigateToAsync(_options: FSharpNavigationOptions2, cancellationToken: CancellationToken) : Task<bool> =
asyncMaybe {
......@@ -892,10 +698,8 @@ type FSharpNavigableLocation(statusBar: StatusBar, metadataAsSource: FSharpMetad
Implementation
match targetPath with
| Signature ->
return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, symbolRange, statusBar, cancellationToken)
| Implementation ->
return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, symbolRange, statusBar, cancellationToken)
| Signature -> return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, symbolRange, cancellationToken)
| Implementation -> return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, symbolRange, cancellationToken)
}
|> Async.map (fun a -> a.IsSome)
|> RoslynHelpers.StartAsyncAsTask cancellationToken
......@@ -908,9 +712,6 @@ type FSharpCrossLanguageSymbolNavigationService() =
let workspace = componentModel.GetService<VisualStudioWorkspace>()
let statusBar =
StatusBar(ServiceProvider.GlobalProvider.GetService<SVsStatusbar, IVsStatusbar>())
let metadataAsSource =
componentModel
.DefaultExportProvider
......@@ -1141,7 +942,7 @@ type FSharpCrossLanguageSymbolNavigationService() =
// More results can theoretically be returned in case of method overloads, or when we have both signature and implementation files.
if locations.Count() >= 1 then
let (location, project) = locations.First()
return FSharpNavigableLocation(statusBar, metadataAsSource, location, project) :> IFSharpNavigableLocation
return FSharpNavigableLocation(metadataAsSource, location, project) :> IFSharpNavigableLocation
else
return Unchecked.defaultof<_> // returning null here, so Roslyn can fallback to default source-as-metadata implementation.
}
......@@ -2,7 +2,6 @@
namespace Microsoft.VisualStudio.FSharp.Editor
open System
open System.Composition
open System.Threading
open System.Threading.Tasks
......@@ -12,31 +11,24 @@ open FSharp.Compiler.Text.Range
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor
open Microsoft.VisualStudio.Shell
open Microsoft.VisualStudio.Shell.Interop
[<Export(typeof<IFSharpGoToDefinitionService>)>]
[<Export(typeof<FSharpGoToDefinitionService>)>]
type internal FSharpGoToDefinitionService [<ImportingConstructor>] (metadataAsSource: FSharpMetadataAsSourceService) =
let statusBar =
StatusBar(ServiceProvider.GlobalProvider.GetService<SVsStatusbar, IVsStatusbar>())
interface IFSharpGoToDefinitionService with
/// Invoked with Peek Definition.
member _.FindDefinitionsAsync(document: Document, position: int, cancellationToken: CancellationToken) =
let navigation =
FSharpNavigation(statusBar, metadataAsSource, document, rangeStartup)
let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup)
navigation.FindDefinitions(position, cancellationToken) |> Task.FromResult
/// Invoked with Go to Definition.
/// Try to navigate to the definiton of the symbol at the symbolRange in the originDocument
member _.TryGoToDefinition(document: Document, position: int, cancellationToken: CancellationToken) =
let statusBar = StatusBar()
statusBar.Message(SR.LocatingSymbol())
use __ = statusBar.Animate()
let navigation =
FSharpNavigation(statusBar, metadataAsSource, document, rangeStartup)
let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup)
navigation.TryGoToDefinition(position, cancellationToken)
......@@ -8,31 +8,28 @@ open System.Threading.Tasks
open System.ComponentModel.Composition
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.Navigation
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation
open Microsoft.VisualStudio.Language.Intellisense
open Microsoft.VisualStudio.Text
open Microsoft.VisualStudio.Text.Editor
open Microsoft.VisualStudio.Shell.Interop
open Microsoft.VisualStudio.Utilities
open Microsoft.VisualStudio.Shell
[<AllowNullLiteral>]
type internal FSharpNavigableSymbol(item: FSharpNavigableItem, span: SnapshotSpan, gtd: GoToDefinition, statusBar: StatusBar) =
type internal FSharpNavigableSymbol(item: FSharpNavigableItem, span: SnapshotSpan, gtd: GoToDefinition) =
interface INavigableSymbol with
member _.Navigate(_: INavigableRelationship) =
gtd.NavigateToItem(item, statusBar, CancellationToken.None)
gtd.NavigateToItem(item, CancellationToken.None)
member _.Relationships = seq { yield PredefinedNavigableRelationships.Definition }
member _.SymbolSpan = span
type internal FSharpNavigableSymbolSource(metadataAsSource, serviceProvider: IServiceProvider) =
type internal FSharpNavigableSymbolSource(metadataAsSource) =
let mutable disposed = false
let gtd = GoToDefinition(metadataAsSource)
let statusBar = StatusBar(serviceProvider.GetService<SVsStatusbar, IVsStatusbar>())
let statusBar = StatusBar()
interface INavigableSymbolSource with
member _.GetNavigableSymbolAsync(triggerSpan: SnapshotSpan, cancellationToken: CancellationToken) =
......@@ -67,7 +64,7 @@ type internal FSharpNavigableSymbolSource(metadataAsSource, serviceProvider: ISe
match result with
| FSharpGoToDefinitionResult.NavigableItem (navItem) ->
return FSharpNavigableSymbol(navItem, symbolSpan, gtd, statusBar) :> INavigableSymbol
return FSharpNavigableSymbol(navItem, symbolSpan, gtd) :> INavigableSymbol
| FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences) ->
let nav =
......@@ -105,12 +102,8 @@ type internal FSharpNavigableSymbolSource(metadataAsSource, serviceProvider: ISe
[<Name("F# Navigable Symbol Service")>]
[<ContentType(Constants.FSharpContentType)>]
[<Order>]
type internal FSharpNavigableSymbolService [<ImportingConstructor>]
(
[<Import(typeof<SVsServiceProvider>)>] serviceProvider: IServiceProvider,
metadataAsSource: FSharpMetadataAsSourceService
) =
type internal FSharpNavigableSymbolService [<ImportingConstructor>] (metadataAsSource: FSharpMetadataAsSourceService) =
interface INavigableSymbolSourceProvider with
member _.TryCreateNavigableSymbolSource(_: ITextView, _: ITextBuffer) =
new FSharpNavigableSymbolSource(metadataAsSource, serviceProvider) :> INavigableSymbolSource
new FSharpNavigableSymbolSource(metadataAsSource)
......@@ -2,194 +2,126 @@
namespace Microsoft.VisualStudio.FSharp.Editor.QuickInfo
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open System.ComponentModel.Composition
open System.Text
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text
open Microsoft.VisualStudio.Language.Intellisense
open Microsoft.VisualStudio.Shell
open Microsoft.VisualStudio.Shell.Interop
open Microsoft.VisualStudio.Text
open Microsoft.VisualStudio.Shell
open Microsoft.VisualStudio.Utilities
open Microsoft.VisualStudio.FSharp.Editor
open FSharp.Compiler.Text
open Microsoft.IO
open FSharp.Compiler.EditorServices
type internal FSharpAsyncQuickInfoSource
(
statusBar: StatusBar,
xmlMemberIndexService: IVsXMLMemberIndexService,
xmlMemberIndexService,
metadataAsSource: FSharpMetadataAsSourceService,
textBuffer: ITextBuffer,
editorOptions: EditorOptions
) =
// test helper
static member ProvideQuickInfo(document: Document, position: int, ?width: int) =
let getQuickInfoItem (sourceText, (document: Document), (lexerSymbol: LexerSymbol), (ToolTipText elements)) =
asyncMaybe {
let! _, sigQuickInfo, targetQuickInfo = FSharpQuickInfo.getQuickInfo (document, position, width, CancellationToken.None)
return! sigQuickInfo |> Option.orElse targetQuickInfo
}
let documentationBuilder =
XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService)
let getSingleContent (data: ToolTipElement) =
let symbol, description, documentation =
XmlDocumentation.BuildSingleTipText(documentationBuilder, data, XmlDocumentation.DefaultLineLimits)
let getLinkTooltip filePath =
let solutionDir = Path.GetDirectoryName(document.Project.Solution.FilePath)
let projectDir = Path.GetDirectoryName(document.Project.FilePath)
[
Path.GetRelativePath(projectDir, filePath)
Path.GetRelativePath(solutionDir, filePath)
]
|> List.minBy String.length
QuickInfoViewProvider.provideContent (
Tokenizer.GetImageIdForSymbol(symbol, lexerSymbol.Kind),
description,
documentation,
FSharpNavigation(metadataAsSource, document, lexerSymbol.Range),
getLinkTooltip
)
static member BuildSingleQuickInfoItem (documentationBuilder: IDocumentationBuilder) (quickInfo: FSharpQuickInfo) =
let mainDescription, documentation, typeParameterMap, usage, exceptions =
ResizeArray(), ResizeArray(), ResizeArray(), ResizeArray(), ResizeArray()
let content = elements |> List.map getSingleContent
do! Option.guard (not content.IsEmpty)
XmlDocumentation.BuildDataTipText(
documentationBuilder,
mainDescription.Add,
documentation.Add,
typeParameterMap.Add,
usage.Add,
exceptions.Add,
quickInfo.StructuredText
)
let! textSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lexerSymbol.Range)
let docs =
RoslynHelpers.joinWithLineBreaks [ documentation; typeParameterMap; usage; exceptions ]
let trackingSpan =
textBuffer.CurrentSnapshot.CreateTrackingSpan(textSpan.Start, textSpan.Length, SpanTrackingMode.EdgeInclusive)
(mainDescription, docs)
return QuickInfoItem(trackingSpan, QuickInfoViewProvider.stackWithSeparators content)
}
static member TryGetToolTip(document: Document, position, ?width) =
asyncMaybe {
let userOpName = "getQuickInfo"
let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, true, true, userOpName)
let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync
let! cancellationToken = Async.CancellationToken |> liftAsync
let! sourceText = document.GetTextAsync cancellationToken
let range = lexerSymbol.Range
let textLinePos = sourceText.Lines.GetLinePosition position
let fcsTextLineNumber = Line.fromZ textLinePos.Line
let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
let tooltip =
match lexerSymbol.Kind with
| LexerSymbolKind.Keyword -> checkFileResults.GetKeywordTooltip(lexerSymbol.FullIsland)
| LexerSymbolKind.String ->
checkFileResults.GetToolTip(
fcsTextLineNumber,
range.EndColumn,
lineText,
lexerSymbol.FullIsland,
FSharp.Compiler.Tokenization.FSharpTokenTag.String,
?width = width
)
| _ ->
checkFileResults.GetToolTip(
fcsTextLineNumber,
range.EndColumn,
lineText,
lexerSymbol.FullIsland,
FSharp.Compiler.Tokenization.FSharpTokenTag.IDENT,
?width = width
)
return sourceText, document, lexerSymbol, tooltip
}
interface IAsyncQuickInfoSource with
override _.Dispose() = () // no cleanup necessary
// This method can be called from the background thread.
// Do not call IServiceProvider.GetService here.
override _.GetQuickInfoItemAsync(session: IAsyncQuickInfoSession, cancellationToken: CancellationToken) : Task<QuickInfoItem> =
let triggerPoint = session.GetTriggerPoint(textBuffer.CurrentSnapshot)
match triggerPoint.HasValue with
| false -> Task.FromResult<QuickInfoItem>(null)
| true ->
let triggerPoint = triggerPoint.GetValueOrDefault()
let width = editorOptions.QuickInfo.DescriptionWidth
asyncMaybe {
let document =
textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges()
let! symbolUseRange, sigQuickInfo, targetQuickInfo =
FSharpQuickInfo.getQuickInfo (document, triggerPoint.Position, width, cancellationToken)
let getTooltip filePath =
let solutionDir = Path.GetDirectoryName(document.Project.Solution.FilePath)
let projectDir = Path.GetDirectoryName(document.Project.FilePath)
[
Path.GetRelativePath(projectDir, filePath)
Path.GetRelativePath(solutionDir, filePath)
]
|> List.minBy String.length
let getTrackingSpan (span: TextSpan) =
textBuffer.CurrentSnapshot.CreateTrackingSpan(span.Start, span.Length, SpanTrackingMode.EdgeInclusive)
let documentationBuilder =
XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService)
match sigQuickInfo, targetQuickInfo with
| None, None -> return null
| Some quickInfo, None
| None, Some quickInfo ->
let mainDescription, docs =
FSharpAsyncQuickInfoSource.BuildSingleQuickInfoItem documentationBuilder quickInfo
let imageId = Tokenizer.GetImageIdForSymbol(quickInfo.Symbol, quickInfo.SymbolKind)
let navigation =
FSharpNavigation(statusBar, metadataAsSource, document, symbolUseRange)
let content =
QuickInfoViewProvider.provideContent (
imageId,
mainDescription |> List.ofSeq,
[ docs |> List.ofSeq ],
navigation,
getTooltip
)
let span = getTrackingSpan quickInfo.Span
return QuickInfoItem(span, content)
| Some sigQuickInfo, Some targetQuickInfo ->
let mainDescription, targetDocumentation, sigDocumentation, typeParameterMap, exceptions, usage =
ResizeArray(), ResizeArray(), ResizeArray(), ResizeArray(), ResizeArray(), ResizeArray()
XmlDocumentation.BuildDataTipText(
documentationBuilder,
ignore,
sigDocumentation.Add,
ignore,
ignore,
ignore,
sigQuickInfo.StructuredText
)
XmlDocumentation.BuildDataTipText(
documentationBuilder,
mainDescription.Add,
targetDocumentation.Add,
typeParameterMap.Add,
exceptions.Add,
usage.Add,
targetQuickInfo.StructuredText
)
// get whitespace nomalized documentation text
let getText (tts: seq<TaggedText>) =
let text =
(StringBuilder(), tts)
||> Seq.fold (fun sb tt ->
if String.IsNullOrWhiteSpace tt.Text then
sb
else
sb.Append tt.Text)
|> string
if String.IsNullOrWhiteSpace text then None else Some text
let documentationParts: TaggedText list list =
[
match getText targetDocumentation, getText sigDocumentation with
| None, None -> ()
| None, Some _ -> sigDocumentation |> List.ofSeq
| Some _, None -> targetDocumentation |> List.ofSeq
| Some implText, Some sigText when implText.Equals(sigText, StringComparison.OrdinalIgnoreCase) ->
sigDocumentation |> List.ofSeq
| Some _, Some _ ->
sigDocumentation |> List.ofSeq
targetDocumentation |> List.ofSeq
RoslynHelpers.joinWithLineBreaks [ typeParameterMap; usage; exceptions ]
|> List.ofSeq
]
let imageId =
Tokenizer.GetImageIdForSymbol(targetQuickInfo.Symbol, targetQuickInfo.SymbolKind)
let navigation =
FSharpNavigation(statusBar, metadataAsSource, document, symbolUseRange)
let content =
QuickInfoViewProvider.provideContent (
imageId,
mainDescription |> List.ofSeq,
documentationParts,
navigation,
getTooltip
)
let span = getTrackingSpan targetQuickInfo.Span
return QuickInfoItem(span, content)
}
|> Async.map Option.toObj
|> RoslynHelpers.StartAsyncAsTask cancellationToken
asyncMaybe {
let document =
textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges()
let! triggerPoint = session.GetTriggerPoint(textBuffer.CurrentSnapshot) |> Option.ofNullable
let position = triggerPoint.Position
let! tipdata =
FSharpAsyncQuickInfoSource.TryGetToolTip(document, position, ?width = editorOptions.QuickInfo.DescriptionWidth)
return! getQuickInfoItem tipdata
}
|> Async.map Option.toObj
|> RoslynHelpers.StartAsyncAsTask cancellationToken
[<Export(typeof<IAsyncQuickInfoSourceProvider>)>]
[<Name("F# Quick Info Provider")>]
......@@ -197,17 +129,12 @@ type internal FSharpAsyncQuickInfoSource
[<Order>]
type internal FSharpAsyncQuickInfoSourceProvider [<ImportingConstructor>]
(
[<Import(typeof<SVsServiceProvider>)>] serviceProvider: IServiceProvider,
[<Import(typeof<SVsServiceProvider>)>] serviceProvider: System.IServiceProvider,
metadataAsSource: FSharpMetadataAsSourceService,
editorOptions: EditorOptions
) =
interface IAsyncQuickInfoSourceProvider with
override _.TryCreateQuickInfoSource(textBuffer: ITextBuffer) : IAsyncQuickInfoSource =
// GetService calls must be made on the UI thread
// It is safe to do it here (see #4713)
let statusBar = StatusBar(serviceProvider.GetService<SVsStatusbar, IVsStatusbar>())
let xmlMemberIndexService = serviceProvider.XMLMemberIndexService
new FSharpAsyncQuickInfoSource(statusBar, xmlMemberIndexService, metadataAsSource, textBuffer, editorOptions)
:> IAsyncQuickInfoSource
new FSharpAsyncQuickInfoSource(xmlMemberIndexService, metadataAsSource, textBuffer, editorOptions)
......@@ -11,7 +11,6 @@ open Microsoft.VisualStudio.Text.Adornments
open Microsoft.VisualStudio.FSharp.Editor
module internal QuickInfoViewProvider =
let layoutTagToClassificationTag (layoutTag: TextTag) =
match layoutTag with
| TextTag.ActivePatternCase
......@@ -56,27 +55,20 @@ module internal QuickInfoViewProvider =
| TaggedText (TextTag.LineBreak, _) -> Some()
| _ -> None
let (|DocSeparator|_|) =
function
| LineBreak :: TaggedText (TextTag.Text, "-------------") :: LineBreak :: rest -> Some rest
| _ -> None
let wrapContent (elements: obj list) =
ContainerElement(ContainerElementStyle.Wrapped, elements |> Seq.map box)
let stackContent (elements: obj list) =
ContainerElement(ContainerElementStyle.Stacked, elements |> Seq.map box)
let encloseRuns runs = wrapContent (runs |> List.rev) |> box
let emptyLine =
wrapContent [ ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, "") |> box ]
let encloseRuns runs =
ClassifiedTextElement(runs |> List.rev) |> box
let provideContent
(
imageId: ImageId option,
description: TaggedText list,
documentation: TaggedText list list,
documentation: TaggedText list,
navigation: FSharpNavigation,
getTooltip
) =
......@@ -84,10 +76,10 @@ module internal QuickInfoViewProvider =
let encloseText text =
let rec loop text runs stack =
match (text: TaggedText list) with
| [] when runs |> List.isEmpty -> stackContent (stack |> List.rev)
| [] -> stackContent (encloseRuns runs :: stack |> List.rev)
| DocSeparator (LineBreak :: rest)
| DocSeparator rest -> loop rest [] (box Separator :: encloseRuns runs :: stack)
| LineBreak :: rest when runs |> List.isEmpty -> loop rest [] (emptyLine :: stack)
// smaller gap instead of huge double line break
| LineBreak :: rest when runs |> List.isEmpty -> loop rest [] (box (Separator false) :: stack)
| LineBreak :: rest -> loop rest [] (encloseRuns runs :: stack)
| :? NavigableTaggedText as item :: rest when navigation.IsTargetValid item.Range ->
let classificationTag = layoutTagToClassificationTag item.Tag
......@@ -108,6 +100,10 @@ module internal QuickInfoViewProvider =
| Some imageId -> wrapContent [ stackContent [ ImageElement(imageId) ]; encloseText description ]
| None -> ContainerElement(ContainerElementStyle.Wrapped, encloseText description)
let separated = stackContent (documentation |> List.map encloseText)
wrapContent [ stackContent [ innerElement; encloseText documentation ] ]
wrapContent [ stackContent [ innerElement; separated ] ]
let stackWithSeparators elements =
elements
|> List.map box
|> List.intersperse (box (Separator true))
|> stackContent
......@@ -11,43 +11,66 @@ open Microsoft.VisualStudio.Text.Editor
open Microsoft.VisualStudio.Utilities
open Microsoft.VisualStudio.FSharp.Editor
open Microsoft.VisualStudio.Text.Classification
type Separator = Separator
type Separator =
| Separator of visible: bool
// preserve old behavior on mac
override this.ToString() =
match this with
| Separator true -> XmlDocumentation.separatorText
| _ -> System.Environment.NewLine
[<Export(typeof<IViewElementFactory>)>]
[<Name("QuickInfoElement to UIElement")>]
[<TypeConversion(typeof<ClassifiedTextRun>, typeof<UIElement>)>]
type WpfNavigableTextRunFactory [<ImportingConstructor>] (viewElementFactoryService: IViewElementFactoryService, settings: EditorOptions) =
[<Name("ClassifiedTextElement to UIElement")>]
[<TypeConversion(typeof<ClassifiedTextElement>, typeof<UIElement>)>]
type WpfClassifiedTextElementFactory [<ImportingConstructor>]
(
classificationformatMapService: IClassificationFormatMapService,
classificationTypeRegistry: IClassificationTypeRegistryService,
settings: EditorOptions
) =
let resources = Microsoft.VisualStudio.FSharp.UIResources.NavStyles().Resources
let formatMap = classificationformatMapService.GetClassificationFormatMap("tooltip")
interface IViewElementFactory with
member _.CreateViewElement(textView: ITextView, model: obj) =
member _.CreateViewElement(_textView: ITextView, model: obj) =
match model with
| :? ClassifiedTextRun as classifiedTextRun ->
// use the default converters to get a UIElement
let classifiedTextElement = ClassifiedTextElement([ classifiedTextRun ])
let convertedElement =
viewElementFactoryService.CreateViewElement<UIElement>(textView, classifiedTextElement)
// Apply custom underline.
match convertedElement with
| :? TextBlock as tb when classifiedTextRun.NavigationAction <> null && settings.QuickInfo.DisplayLinks ->
match tb.Inlines.FirstInline with
| :? Documents.Hyperlink as hyperlink ->
| :? ClassifiedTextElement as text ->
let tb = TextBlock()
tb.FontSize <- formatMap.DefaultTextProperties.FontRenderingEmSize
tb.FontFamily <- formatMap.DefaultTextProperties.Typeface.FontFamily
tb.TextWrapping <- TextWrapping.Wrap
for run in text.Runs do
let ctype =
classificationTypeRegistry.GetClassificationType(run.ClassificationTypeName)
let props = formatMap.GetTextProperties(ctype)
let inl = Documents.Run(run.Text, Foreground = props.ForegroundBrush)
match run.NavigationAction |> Option.ofObj with
| Some action ->
let link =
{ new Documents.Hyperlink(inl) with
override _.OnClick() = action.Invoke()
}
let key =
match settings.QuickInfo.UnderlineStyle with
| QuickInfoUnderlineStyle.Solid -> "solid_underline"
| QuickInfoUnderlineStyle.Dash -> "dash_underline"
| QuickInfoUnderlineStyle.Dot -> "dot_underline"
// Fix color and apply styles.
hyperlink.Foreground <- hyperlink.Inlines.FirstInline.Foreground
hyperlink.Style <- downcast resources[key]
| _ -> ()
| _ -> ()
box convertedElement :?> _
link.Style <- downcast resources[key]
link.Foreground <- props.ForegroundBrush
tb.Inlines.Add(link)
| _ -> tb.Inlines.Add(inl)
box tb :?> _
| _ ->
failwith $"Invalid type conversion. Supported conversion is {typeof<ClassifiedTextRun>.Name} to {typeof<UIElement>.Name}."
failwith
$"Invalid type conversion. Supported conversion is {typeof<ClassifiedTextElement>.Name} to {typeof<UIElement>.Name}."
[<Export(typeof<IViewElementFactory>)>]
[<Name("Separator to UIElement")>]
......@@ -56,5 +79,11 @@ type WpfSeparatorFactory() =
interface IViewElementFactory with
member _.CreateViewElement(_, model: obj) =
match model with
| :? Separator -> Controls.Separator(Opacity = 0.4, Margin = Thickness(0, 10, 0, 10)) |> box :?> _
| :? Separator as Separator visible ->
if visible then
Controls.Separator(Opacity = 0.3, Margin = Thickness(0, 8, 0, 8))
else
Controls.Separator(Opacity = 0)
|> box
:?> _
| _ -> failwith $"Invalid type conversion. Supported conversion is {typeof<Separator>.Name} to {typeof<UIElement>.Name}."
......@@ -16,71 +16,93 @@ type public AssemblyResolverTestFixture() =
member public __.Init() = AssemblyResolver.addResolver ()
module QuickInfoProviderTests =
type Expected =
| QuickInfo of description: string * docs: string
| Desc of string
| Empty
| Error
let filePath = "C:\\test.fs"
type TestCase = TestCase of prompt: string * Expected
let private normalizeLineEnds (s: string) =
s.Replace("\r\n", "\n").Replace("\n\n", "\n")
let private tooltipTextToRawString (ToolTipText elements) : string =
let rec parseElement =
function
| ToolTipElement.None -> ""
| ToolTipElement.Group (xs) ->
let descriptions = xs |> List.map (fun item -> item.MainDescription)
let mkFull prompt desc docs =
TestCase(prompt, QuickInfo(normalizeLineEnds desc, normalizeLineEnds docs))
let mkDesc prompt desc =
TestCase(prompt, Desc(normalizeLineEnds desc))
let mkNone prompt = TestCase(prompt, Empty)
let filePath = "C:\\test.fs"
let private tooltipElementToExpected expected =
function
| ToolTipElement.None -> Empty
| ToolTipElement.Group (xs) ->
let descriptions = xs |> List.map (fun item -> item.MainDescription)
let descriptionTexts =
descriptions
|> List.map (fun taggedTexts -> taggedTexts |> Array.map (fun taggedText -> taggedText.Text))
let descriptionTexts =
descriptions
|> List.map (fun taggedTexts -> taggedTexts |> Array.map (fun taggedText -> taggedText.Text))
let descriptionText = descriptionTexts |> Array.concat |> String.concat ""
let descriptionText = descriptionTexts |> Array.concat |> String.concat ""
let remarks = xs |> List.choose (fun item -> item.Remarks)
let remarks = xs |> List.choose (fun item -> item.Remarks)
let remarkTexts =
remarks |> Array.concat |> Array.map (fun taggedText -> taggedText.Text)
let remarkTexts =
remarks |> Array.concat |> Array.map (fun taggedText -> taggedText.Text)
let remarkText =
(match remarks with
| [] -> ""
| _ -> "\n" + String.concat "" remarkTexts)
let remarkText =
(match remarks with
| [] -> ""
| _ -> "\n" + String.concat "" remarkTexts)
let tps = xs |> List.collect (fun item -> item.TypeMapping)
let tps = xs |> List.collect (fun item -> item.TypeMapping)
let tpTexts =
tps |> List.map (fun x -> x |> Array.map (fun y -> y.Text) |> String.concat "")
let tpTexts =
tps |> List.map (fun x -> x |> Array.map (fun y -> y.Text) |> String.concat "")
let tpText =
(match tps with
| [] -> ""
| _ -> "\n" + String.concat "\n" tpTexts)
let tpText =
(match tps with
| [] -> ""
| _ -> "\n" + String.concat "\n" tpTexts)
descriptionText + remarkText + tpText
| ToolTipElement.CompositionError (error) -> error
let collectDocs (element: ToolTipElementData) =
match element.XmlDoc with
| FSharp.Compiler.Symbols.FSharpXmlDoc.FromXmlText xmlDoc -> xmlDoc.UnprocessedLines |> String.concat "\n"
| _ -> ""
elements |> List.map parseElement |> String.concat "\n" |> normalizeLineEnds
let desc =
[ descriptionText; remarkText; tpText ] |> String.concat "" |> normalizeLineEnds
let docs = xs |> List.map collectDocs |> String.concat "" |> normalizeLineEnds
match expected with
| QuickInfo _ -> QuickInfo(desc, docs)
| _ -> Desc desc
| ToolTipElement.CompositionError (error) -> Error
let executeQuickInfoTest (programText: string) testCases =
let document =
RoslynTestHelpers.CreateSolution(programText)
|> RoslynTestHelpers.GetSingleDocument
for (symbol: string, expected: string option) in testCases do
let expected =
expected
|> Option.map normalizeLineEnds
|> Option.map (fun s -> s.Replace("___", ""))
for TestCase (symbol, expected) in testCases do
let caretPosition = programText.IndexOf(symbol) + symbol.Length - 1
let quickInfo =
FSharpAsyncQuickInfoSource.ProvideQuickInfo(document, caretPosition)
FSharpAsyncQuickInfoSource.TryGetToolTip(document, caretPosition)
|> Async.RunSynchronously
let actual =
quickInfo |> Option.map (fun qi -> tooltipTextToRawString qi.StructuredText)
quickInfo
|> Option.map (fun (_, _, _, ToolTipText elements) -> elements |> List.map (tooltipElementToExpected expected))
|> Option.defaultValue [ Empty ]
actual |> Assert.shouldBeEqualWith expected $"Symbol: {symbol}"
actual.Head |> Assert.shouldBeEqualWith expected $"Symbol: {symbol}"
[<Fact>]
let ShouldShowQuickInfoAtCorrectPositions () =
......@@ -93,20 +115,20 @@ System.Console.WriteLine(x + y)
let testCases =
[
"let", Some "let___Used to associate, or bind, a name to a value or function."
"x", Some "val x: int\nFull name: Test.x"
"y", Some "val y: int\nFull name: Test.y"
"1", None
"2", None
"x +",
Some
mkFull "let" "let" "Used to associate, or bind, a name to a value or function."
mkDesc "x" "val x: int\nFull name: Test.x"
mkDesc "y" "val y: int\nFull name: Test.y"
mkNone "1"
mkNone "2"
mkDesc
"x +"
"""val (+) : x: 'T1 -> y: 'T2 -> 'T3 (requires member (+))
Full name: Microsoft.FSharp.Core.Operators.(+)
'T1 is int
'T2 is int
'T3 is int"""
"System", Some "namespace System"
"WriteLine", Some "System.Console.WriteLine(value: int) : unit"
mkDesc "System" "namespace System"
mkDesc "WriteLine" "System.Console.WriteLine(value: int) : unit"
]
executeQuickInfoTest fileContents testCases
......@@ -122,17 +144,20 @@ module internal MyModule =
let testCases =
[
"namespace",
Some
"namespace___Used to associate a name with a group of related types and modules, to logically separate it from other code."
"module",
Some
"module___Used to associate a name with a group of related types, values, and functions, to logically separate it from other code."
"internal", Some "internal___Used to specify that a member is visible inside an assembly but not outside it."
"val", Some "val___Used in a signature to indicate a value, or in a type to declare a member, in limited situations."
"->",
Some
"->___In function types, delimits arguments and return values. Yields an expression (in sequence expressions); equivalent to the yield keyword. Used in match expressions"
mkFull
"namespace"
"namespace"
"Used to associate a name with a group of related types and modules, to logically separate it from other code."
mkFull
"module"
"module"
"Used to associate a name with a group of related types, values, and functions, to logically separate it from other code."
mkFull "internal" "internal" "Used to specify that a member is visible inside an assembly but not outside it."
mkFull "val" "val" "Used in a signature to indicate a value, or in a type to declare a member, in limited situations."
mkFull
"->"
"->"
"In function types, delimits arguments and return values. Yields an expression (in sequence expressions); equivalent to the yield keyword. Used in match expressions"
]
executeQuickInfoTest fileContents testCases
......@@ -157,8 +182,8 @@ let x =
let testCases =
[
"let!", Some "let!___Used in computation expressions to bind a name to the result of another computation expression."
"return", Some "return___Used to provide a value for the result of the containing computation expression."
mkFull "let!" "let!" "Used in computation expressions to bind a name to the result of another computation expression."
mkFull "return" "return" "Used to provide a value for the result of the containing computation expression."
]
executeQuickInfoTest fileContents testCases
......@@ -167,7 +192,7 @@ let x =
let ShouldShowQuickInfoForGenericParameters () =
let fileContents =
"""
type C() =
member x.FSharpGenericMethodExplitTypeParams<'T>(a:'T, y:'T) = (a,y)
......@@ -194,94 +219,94 @@ let res8 = abs 5.0<kg>
let testCases =
[
"GroupBy",
Some
mkDesc
"GroupBy"
"(extension) System.Collections.Generic.IEnumerable.GroupBy<'TSource,'TKey>(keySelector: System.Func<'TSource,'TKey>) : System.Collections.Generic.IEnumerable<IGrouping<'TKey,'TSource>>
'TSource is int * string
'TKey is int"
"Sort",
Some
mkDesc
"Sort"
"System.Array.Sort<'T>(array: 'T array) : unit
'T is int"
"let test4 x = C().FSharpGenericMethodExplitTypeParams",
Some
mkDesc
"let test4 x = C().FSharpGenericMethodExplitTypeParams"
"member C.FSharpGenericMethodExplitTypeParams: a: 'T0 * y: 'T0 -> 'T0 * 'T0
'T is 'a list"
"let test5<'U> (x: 'U) = C().FSharpGenericMethodExplitTypeParams",
Some
mkDesc
"let test5<'U> (x: 'U) = C().FSharpGenericMethodExplitTypeParams"
"member C.FSharpGenericMethodExplitTypeParams: a: 'T0 * y: 'T0 -> 'T0 * 'T0
'T is 'U list"
"let test6 = C().FSharpGenericMethodExplitTypeParams",
Some
mkDesc
"let test6 = C().FSharpGenericMethodExplitTypeParams"
"member C.FSharpGenericMethodExplitTypeParams: a: 'T0 * y: 'T0 -> 'T0 * 'T0
'T is int"
"let test7 x = C().FSharpGenericMethodInferredTypeParams",
Some
mkDesc
"let test7 x = C().FSharpGenericMethodInferredTypeParams"
"member C.FSharpGenericMethodInferredTypeParams: a: 'a1 * y: 'b2 -> 'a1 * 'b2
'a is 'a0 list
'b is 'a0 list"
"let test8 = C().FSharpGenericMethodInferredTypeParams",
Some
mkDesc
"let test8 = C().FSharpGenericMethodInferredTypeParams"
"member C.FSharpGenericMethodInferredTypeParams: a: 'a0 * y: 'b1 -> 'a0 * 'b1
'a is int
'b is int"
"let test9<'U> (x: 'U) = C().FSharpGenericMethodInferredTypeParams",
Some
mkDesc
"let test9<'U> (x: 'U) = C().FSharpGenericMethodInferredTypeParams"
"member C.FSharpGenericMethodInferredTypeParams: a: 'a0 * y: 'b1 -> 'a0 * 'b1
'a is 'U list
'b is 'U list"
"let res3 = [1] |>",
Some
mkDesc
"let res3 = [1] |>"
"val (|>) : arg: 'T1 -> func: ('T1 -> 'U) -> 'U
Full name: Microsoft.FSharp.Core.Operators.(|>)
'T1 is int list
'U is int list"
"let res3 = [1] |> List.map id",
Some
mkDesc
"let res3 = [1] |> List.map id"
"val id: x: 'T -> 'T
Full name: Microsoft.FSharp.Core.Operators.id
'T is int"
"let res4 = (1.0,[1]) ||>",
Some
mkDesc
"let res4 = (1.0,[1]) ||>"
"val (||>) : arg1: 'T1 * arg2: 'T2 -> func: ('T1 -> 'T2 -> 'U) -> 'U
Full name: Microsoft.FSharp.Core.Operators.(||>)
'T1 is float
'T2 is int list
'U is float"
"let res4 = (1.0,[1]) ||> List.fold",
Some
mkDesc
"let res4 = (1.0,[1]) ||> List.fold"
"val fold: folder: ('State -> 'T -> 'State) -> state: 'State -> list: 'T list -> 'State
Full name: Microsoft.FSharp.Collections.List.fold
'T is int
'State is float"
"let res4 = (1.0,[1]) ||> List.fold (fun s x -> string s +",
Some
mkDesc
"let res4 = (1.0,[1]) ||> List.fold (fun s x -> string s +"
"val (+) : x: 'T1 -> y: 'T2 -> 'T3 (requires member (+))
Full name: Microsoft.FSharp.Core.Operators.(+)
'T1 is string
'T2 is string
'T3 is float"
"let res5 = 1 +",
Some
mkDesc
"let res5 = 1 +"
"val (+) : x: 'T1 -> y: 'T2 -> 'T3 (requires member (+))
Full name: Microsoft.FSharp.Core.Operators.(+)
'T1 is int
'T2 is int
'T3 is int"
"let res6 = System.DateTime.Now +",
Some
mkDesc
"let res6 = System.DateTime.Now +"
"val (+) : x: 'T1 -> y: 'T2 -> 'T3 (requires member (+))
Full name: Microsoft.FSharp.Core.Operators.(+)
'T1 is System.DateTime
'T2 is System.TimeSpan
'T3 is System.DateTime"
"let res7 = sin",
Some
mkDesc
"let res7 = sin"
"val sin: value: 'T -> 'T (requires member Sin)
Full name: Microsoft.FSharp.Core.Operators.sin
'T is float"
"let res8 = abs",
Some
mkDesc
"let res8 = abs"
"val abs: value: 'T -> 'T (requires member Abs)
Full name: Microsoft.FSharp.Core.Operators.abs
'T is int"
......
......@@ -8,6 +8,7 @@ open Xunit
open FSharp.Editor.Tests.Helpers
module QuickInfo =
open FSharp.Compiler.EditorServices
let private GetCaretPosition (codeWithCaret: string) =
let caretSentinel = "$$"
......@@ -26,11 +27,12 @@ module QuickInfo =
cursorInfo
let internal GetQuickInfo (code: string) caretPosition =
async {
asyncMaybe {
let document =
RoslynTestHelpers.CreateSolution(code) |> RoslynTestHelpers.GetSingleDocument
return! FSharpAsyncQuickInfoSource.ProvideQuickInfo(document, caretPosition)
let! _, _, _, tooltip = FSharpAsyncQuickInfoSource.TryGetToolTip(document, caretPosition)
return tooltip
}
|> Async.RunSynchronously
......@@ -39,15 +41,15 @@ module QuickInfo =
let sigHelp = GetQuickInfo code caretPosition
match sigHelp with
| Some (quickInfo) ->
| Some (ToolTipText elements) when not elements.IsEmpty ->
let documentationBuilder =
{ new IDocumentationBuilder with
override _.AppendDocumentationFromProcessedXML(_, _, _, _, _, _) = ()
override _.AppendDocumentation(_, _, _, _, _, _, _) = ()
}
let mainDescription, docs =
FSharpAsyncQuickInfoSource.BuildSingleQuickInfoItem documentationBuilder quickInfo
let _, mainDescription, docs =
XmlDocumentation.BuildSingleTipText(documentationBuilder, elements.Head, XmlDocumentation.DefaultLineLimits)
let mainTextItems = mainDescription |> Seq.map (fun x -> x.Text)
let docTextItems = docs |> Seq.map (fun x -> x.Text)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册