Skip to content

Commit 9c3f562

Browse files
authored
Tooltips for hints (#15127)
* WIP: Tooltips for hints * casing * Actually, tooltips * Update HintTestFramework.fs * Simplify async
1 parent b9dc588 commit 9c3f562

10 files changed

+229
-14
lines changed

vsintegration/src/FSharp.Editor/Hints/Hints.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Microsoft.VisualStudio.FSharp.Editor.Hints
44

5+
open System.Threading
6+
open Microsoft.CodeAnalysis
57
open FSharp.Compiler.Text
68

79
module Hints =
@@ -17,6 +19,7 @@ module Hints =
1719
Kind: HintKind
1820
Range: range
1921
Parts: TaggedText list
22+
GetTooltip: Document -> Async<TaggedText list>
2023
}
2124

2225
let serialize kind =

vsintegration/src/FSharp.Editor/Hints/InlineParameterNameHints.fs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,47 @@ open Hints
1212

1313
type InlineParameterNameHints(parseResults: FSharpParseFileResults) =
1414

15+
let getTooltip (symbol: FSharpSymbol) _ =
16+
async {
17+
// This brings little value as of now. Basically just discerns fields from parameters
18+
// and fills the tooltip bubble which otherwise looks like a visual glitch.
19+
//
20+
// Now, we could add some type information here, like C# does, for example:
21+
// (parameter) int number
22+
//
23+
// This would work for simple cases but can get weird in more complex ones.
24+
// Consider this code:
25+
//
26+
// let rev list = list |> List.rev
27+
// let reversed = rev [ 42 ]
28+
//
29+
// With the trivial implementation, the tooltip for hint before [ 42 ] will look like:
30+
// parameter 'a list list
31+
//
32+
// Arguably, this can look confusing.
33+
// Hence, I wouldn't add type info to the text until we have some coloring plugged in here.
34+
//
35+
// Some alignment with C# also needs to be kept in mind,
36+
// e.g. taking the type in braces would be opposite to what C# does which can be confusing.
37+
let text = symbol.ToString()
38+
39+
return [ TaggedText(TextTag.Text, text) ]
40+
}
41+
1542
let getParameterHint (range: range, parameter: FSharpParameter) =
1643
{
1744
Kind = HintKind.ParameterNameHint
1845
Range = range.StartRange
1946
Parts = [ TaggedText(TextTag.Text, $"{parameter.DisplayName} = ") ]
47+
GetTooltip = getTooltip parameter
2048
}
2149

2250
let getFieldHint (range: range, field: FSharpField) =
2351
{
2452
Kind = HintKind.ParameterNameHint
2553
Range = range.StartRange
2654
Parts = [ TaggedText(TextTag.Text, $"{field.Name} = ") ]
55+
GetTooltip = getTooltip field
2756
}
2857

2958
let parameterNameExists (parameter: FSharpParameter) = parameter.DisplayName <> ""

vsintegration/src/FSharp.Editor/Hints/InlineReturnTypeHints.fs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,21 @@ type InlineReturnTypeHints(parseFileResults: FSharpParseFileResults, symbol: FSh
1919
TaggedText(TextTag.Space, " ")
2020
])
2121

22+
let getTooltip _ =
23+
async {
24+
let typeAsString = symbol.ReturnParameter.Type.TypeDefinition.ToString()
25+
let text = $"type {typeAsString}"
26+
return [ TaggedText(TextTag.Text, text) ]
27+
}
28+
2229
let getHint symbolUse range =
2330
getHintParts symbolUse
2431
|> Option.map (fun parts ->
2532
{
2633
Kind = HintKind.ReturnTypeHint
2734
Range = range
2835
Parts = parts
36+
GetTooltip = getTooltip
2937
})
3038

3139
let isValidForHint (symbol: FSharpMemberOrFunctionOrValue) = symbol.IsFunction

vsintegration/src/FSharp.Editor/Hints/InlineTypeHints.fs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,29 @@ type InlineTypeHints(parseResults: FSharpParseFileResults, symbol: FSharpMemberO
2121
// not sure when this can happen
2222
| None -> []
2323

24+
let getTooltip _ =
25+
async {
26+
// Done this way because I am not sure if we want to show full-blown types everywhere,
27+
// e.g. Microsoft.FSharp.Core.string instead of string.
28+
// On the other hand, for user types this could be useful.
29+
// Then there should be some smarter algorithm here.
30+
let text =
31+
if symbol.FullType.HasTypeDefinition then
32+
let typeAsString = symbol.FullType.TypeDefinition.ToString()
33+
$"type {typeAsString}"
34+
else
35+
// already includes the word "type"
36+
symbol.FullType.ToString()
37+
38+
return [ TaggedText(TextTag.Text, text) ]
39+
}
40+
2441
let getHint symbol (symbolUse: FSharpSymbolUse) =
2542
{
2643
Kind = HintKind.TypeHint
2744
Range = symbolUse.Range.EndRange
2845
Parts = getHintParts symbol symbolUse
46+
GetTooltip = getTooltip
2947
}
3048

3149
let isSolved (symbol: FSharpMemberOrFunctionOrValue) =

vsintegration/src/FSharp.Editor/Hints/NativeToRoslynHintConverter.fs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
namespace Microsoft.VisualStudio.FSharp.Editor.Hints
44

5+
open System
56
open System.Collections.Immutable
7+
open System.Threading
8+
open System.Threading.Tasks
69
open Microsoft.CodeAnalysis.Text
710
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.InlineHints
811
open Microsoft.VisualStudio.FSharp.Editor
12+
open Microsoft.CodeAnalysis
913
open FSharp.Compiler.Text
1014
open Hints
1115

@@ -21,7 +25,14 @@ module NativeToRoslynHintConverter =
2125
let text = taggedText.Text
2226
RoslynTaggedText(tag, text)
2327

28+
let nativeToRoslynFunc nativeFunc =
29+
Func<Document, CancellationToken, Task<ImmutableArray<RoslynTaggedText>>>(fun doc ct ->
30+
nativeFunc doc
31+
|> Async.map (List.map nativeToRoslynText >> ImmutableArray.CreateRange)
32+
|> fun comp -> Async.StartAsTask(comp, cancellationToken = ct))
33+
2434
let convert sourceText hint =
2535
let span = rangeToSpan hint.Range sourceText
2636
let displayParts = hint.Parts |> Seq.map nativeToRoslynText
27-
FSharpInlineHint(span, displayParts.ToImmutableArray())
37+
let getDescription = hint.GetTooltip |> nativeToRoslynFunc
38+
FSharpInlineHint(span, displayParts.ToImmutableArray(), getDescription)

vsintegration/tests/FSharp.Editor.Tests/Hints/HintTestFramework.fs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ module HintTestFramework =
1212

1313
// another representation for extra convenience
1414
type TestHint =
15-
{ Content: string; Location: int * int }
15+
{
16+
Content: string
17+
Location: int * int
18+
Tooltip: string
19+
}
1620

17-
let private convert hint =
21+
let private convert (hint, tooltip) =
1822
let content =
1923
hint.Parts |> Seq.map (fun hintPart -> hintPart.Text) |> String.concat ""
2024

@@ -26,6 +30,7 @@ module HintTestFramework =
2630
{
2731
Content = content
2832
Location = location
33+
Tooltip = tooltip
2934
}
3035

3136
let getFsDocument code =
@@ -57,9 +62,17 @@ module HintTestFramework =
5762
let getHints (document: Document) hintKinds =
5863
async {
5964
let! ct = Async.CancellationToken
65+
66+
let getTooltip hint =
67+
async {
68+
let! roslynTexts = hint.GetTooltip document
69+
return roslynTexts |> Seq.map (fun roslynText -> roslynText.Text) |> String.concat ""
70+
}
71+
6072
let! sourceText = document.GetTextAsync ct |> Async.AwaitTask
6173
let! hints = HintService.getHintsForDocument sourceText document hintKinds "test" ct
62-
return hints |> Seq.map convert
74+
let! tooltips = hints |> Seq.map getTooltip |> Async.Parallel
75+
return tooltips |> Seq.zip hints |> Seq.map convert
6376
}
6477
|> Async.RunSynchronously
6578

0 commit comments

Comments
 (0)