Skip to content

Commit 0b8044b

Browse files
committed
Visual Studio integration of the Inlay Hints feature
This hooks up Inlay Hints to the editor via a new InlineHintsService.
1 parent b9be1e8 commit 0b8044b

File tree

3 files changed

+145
-3
lines changed

3 files changed

+145
-3
lines changed

vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs

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

33
namespace Microsoft.VisualStudio.FSharp.Editor
44

5-
open System
65
open System.Composition
76
open System.Collections.Generic
87
open System.Threading
@@ -61,5 +60,3 @@ type internal FSharpLanguageDebugInfoService [<ImportingConstructor>]() =
6160
return result
6261
}
6362
|> RoslynHelpers.StartAsyncAsTask(cancellationToken)
64-
65-

vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
<Compile Include="QuickInfo\WpfNagivableTextRunViewElementFactory.fs" />
8585
<Compile Include="QuickInfo\Views.fs" />
8686
<Compile Include="QuickInfo\QuickInfoProvider.fs" />
87+
<Compile Include="InlineHints\InlineHints.fs" />
8788
<Compile Include="Structure\BlockStructureService.fs" />
8889
<Compile Include="Commands\HelpContextService.fs" />
8990
<Compile Include="Commands\FsiCommandService.fs" />
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
namespace Microsoft.VisualStudio.FSharp.Editor
3+
4+
open System
5+
open System.Collections.Immutable
6+
open System.Threading
7+
open System.ComponentModel.Composition
8+
9+
open Microsoft.CodeAnalysis
10+
open Microsoft.CodeAnalysis.Text
11+
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.InlineHints
12+
13+
open FSharp.Compiler.CodeAnalysis
14+
open FSharp.Compiler.EditorServices
15+
open FSharp.Compiler.Symbols
16+
open FSharp.Compiler.Syntax
17+
open FSharp.Compiler.Text
18+
19+
[<Export(typeof<IFSharpInlineHintsService>)>]
20+
type internal FSharpInlineHintsService
21+
[<ImportingConstructor>]
22+
(
23+
) =
24+
25+
static let userOpName = "FSharpInlineHints"
26+
27+
static let getFirstPositionAfterParen (str: string) startPos =
28+
match str with
29+
| null -> -1
30+
| str when startPos > str.Length -> -1
31+
| str ->
32+
str.IndexOf('(') + 1
33+
34+
interface IFSharpInlineHintsService with
35+
member _.GetInlineHintsAsync(document: Document, textSpan: TextSpan, cancellationToken: CancellationToken) =
36+
asyncMaybe {
37+
do! Option.guard (not (isSignatureFile document.FilePath))
38+
39+
let! sourceText = document.GetTextAsync(cancellationToken)
40+
let! parseFileResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> Async.map Some
41+
let range = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText)
42+
let symbolUses =
43+
checkFileResults.GetAllUsesOfAllSymbolsInFile(cancellationToken)
44+
|> Seq.filter (fun su -> Range.rangeContainsRange range su.Range)
45+
46+
let typeHints = ImmutableArray.CreateBuilder()
47+
let parameterHints = ImmutableArray.CreateBuilder()
48+
49+
let isValidForTypeHint (funcOrValue: FSharpMemberOrFunctionOrValue) (symbolUse: FSharpSymbolUse) =
50+
let isLambdaIfFunction =
51+
funcOrValue.IsFunction &&
52+
parseFileResults.IsBindingALambdaAtPosition symbolUse.Range.Start
53+
54+
(funcOrValue.IsValue || isLambdaIfFunction) &&
55+
not (parseFileResults.IsTypeAnnotationGivenAtPosition symbolUse.Range.Start) &&
56+
symbolUse.IsFromDefinition &&
57+
not funcOrValue.IsMember &&
58+
not funcOrValue.IsMemberThisValue &&
59+
not funcOrValue.IsConstructorThisValue &&
60+
not (PrettyNaming.IsOperatorDisplayName funcOrValue.DisplayName)
61+
62+
for symbolUse in symbolUses do
63+
match symbolUse.Symbol with
64+
| :? FSharpMemberOrFunctionOrValue as funcOrValue when isValidForTypeHint funcOrValue symbolUse ->
65+
66+
let typeInfo =
67+
funcOrValue.GetReturnTypeLayout symbolUse.DisplayContext
68+
|> Option.defaultValue Array.empty
69+
70+
let displayParts = ImmutableArray.CreateBuilder()
71+
displayParts.Add(RoslynTaggedText(TextTags.Text, ": "))
72+
73+
for tt in typeInfo do
74+
displayParts.Add(RoslynTaggedText(RoslynHelpers.roslynTag tt.Tag, tt.Text))
75+
76+
let symbolSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, symbolUse.Range)
77+
78+
let hint = FSharpInlineHint(TextSpan(symbolSpan.End, 0), displayParts.ToImmutableArray())
79+
typeHints.Add(hint)
80+
81+
| :? FSharpMemberOrFunctionOrValue as func when func.IsFunction && not symbolUse.IsFromDefinition ->
82+
let appliedArgRangesOpt = parseFileResults.GetAllArgumentsForFunctionApplicationAtPostion symbolUse.Range.Start
83+
match appliedArgRangesOpt with
84+
| None -> ()
85+
| Some [] -> ()
86+
| Some appliedArgRanges ->
87+
let parameters = func.CurriedParameterGroups |> Seq.concat
88+
let appliedArgRanges = appliedArgRanges |> Array.ofList
89+
let definitionArgs = parameters |> Array.ofSeq
90+
91+
for idx = 0 to appliedArgRanges.Length - 1 do
92+
let appliedArgRange = appliedArgRanges.[idx]
93+
let definitionArgName = definitionArgs.[idx].DisplayName
94+
if not (String.IsNullOrWhiteSpace(definitionArgName)) then
95+
let appliedArgSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, appliedArgRange)
96+
let displayParts = ImmutableArray.Create(RoslynTaggedText(TextTags.Text, definitionArgName + " ="))
97+
let hint = FSharpInlineHint(TextSpan(appliedArgSpan.Start, 0), displayParts)
98+
parameterHints.Add(hint)
99+
100+
| :? FSharpMemberOrFunctionOrValue as methodOrConstructor when methodOrConstructor.IsMethod || methodOrConstructor.IsConstructor ->
101+
let endPosForMethod = symbolUse.Range.End
102+
let line, _ = Position.toZ endPosForMethod
103+
let afterParenPosInLine = getFirstPositionAfterParen (sourceText.Lines.[line].ToString()) (endPosForMethod.Column)
104+
let tupledParamInfos = parseFileResults.FindParameterLocations(Position.fromZ line afterParenPosInLine)
105+
let appliedArgRanges = parseFileResults.GetAllArgumentsForFunctionApplicationAtPostion symbolUse.Range.Start
106+
match tupledParamInfos, appliedArgRanges with
107+
| None, None -> ()
108+
109+
// Prefer looking at the "tupled" view if it exists, even if the other ranges exist.
110+
// M(1, 2) can give results for both, but in that case we want the "tupled" view.
111+
| Some tupledParamInfos, _ ->
112+
let parameters = methodOrConstructor.CurriedParameterGroups |> Seq.concat |> Array.ofSeq
113+
for idx = 0 to parameters.Length - 1 do
114+
let paramLocationInfo = tupledParamInfos.ArgumentLocations.[idx]
115+
let paramName = parameters.[idx].DisplayName
116+
if not paramLocationInfo.IsNamedArgument && not (String.IsNullOrWhiteSpace(paramName)) then
117+
let appliedArgSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, paramLocationInfo.ArgumentRange)
118+
let displayParts = ImmutableArray.Create(RoslynTaggedText(TextTags.Text, paramName + " ="))
119+
let hint = FSharpInlineHint(TextSpan(appliedArgSpan.Start, 0), displayParts)
120+
parameterHints.Add(hint)
121+
122+
// This will only happen for curried methods defined in F#.
123+
| _, Some appliedArgRanges ->
124+
let parameters = methodOrConstructor.CurriedParameterGroups |> Seq.concat
125+
let appliedArgRanges = appliedArgRanges |> Array.ofList
126+
let definitionArgs = parameters |> Array.ofSeq
127+
128+
for idx = 0 to appliedArgRanges.Length - 1 do
129+
let appliedArgRange = appliedArgRanges.[idx]
130+
let definitionArgName = definitionArgs.[idx].DisplayName
131+
if not (String.IsNullOrWhiteSpace(definitionArgName)) then
132+
let appliedArgSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, appliedArgRange)
133+
let displayParts = ImmutableArray.Create(RoslynTaggedText(TextTags.Text, definitionArgName + " ="))
134+
let hint = FSharpInlineHint(TextSpan(appliedArgSpan.Start, 0), displayParts)
135+
parameterHints.Add(hint)
136+
| _ -> ()
137+
138+
let typeHints = typeHints.ToImmutableArray()
139+
let parameterHints = parameterHints.ToImmutableArray()
140+
141+
return typeHints.AddRange(parameterHints)
142+
}
143+
|> Async.map (Option.defaultValue ImmutableArray<_>.Empty)
144+
|> RoslynHelpers.StartAsyncAsTask(cancellationToken)

0 commit comments

Comments
 (0)