Skip to content

Commit a9bd7f3

Browse files
author
Omar Tawfik
committed
Added FSharpGoToDefinitionService
1 parent fd874f2 commit a9bd7f3

File tree

2 files changed

+137
-0
lines changed

2 files changed

+137
-0
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@
6464
<Compile Include="CompletionServiceFactory.fs">
6565
<Link>Completion\CompletionServiceFactory.fs</Link>
6666
</Compile>
67+
<Compile Include="GoToDefinitionService.fs">
68+
<Link>GoToDefinition\GoToDefinitionService.fs</Link>
69+
</Compile>
6770
<Compile Include="ContentType.fs" />
6871
</ItemGroup>
6972
<ItemGroup>
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
namespace Microsoft.VisualStudio.FSharp.Editor
4+
5+
open System
6+
open System.Composition
7+
open System.Collections.Concurrent
8+
open System.Collections.Generic
9+
open System.Collections.Immutable
10+
open System.Linq
11+
open System.Threading
12+
open System.Threading.Tasks
13+
open System.Runtime.CompilerServices
14+
15+
open Microsoft.CodeAnalysis
16+
open Microsoft.CodeAnalysis.Classification
17+
open Microsoft.CodeAnalysis.Editor
18+
open Microsoft.CodeAnalysis.Editor.Host
19+
open Microsoft.CodeAnalysis.Editor.Navigation
20+
open Microsoft.CodeAnalysis.Editor.Shared.Utilities
21+
open Microsoft.CodeAnalysis.Host.Mef
22+
open Microsoft.CodeAnalysis.Text
23+
24+
open Microsoft.VisualStudio.FSharp.LanguageService
25+
open Microsoft.VisualStudio.Text
26+
open Microsoft.VisualStudio.Text.Tagging
27+
28+
open Microsoft.FSharp.Compiler.Range
29+
open Microsoft.FSharp.Compiler.SourceCodeServices
30+
31+
type internal FSharpNavigableItem(document: Document, textSpan: TextSpan, displayString: string) =
32+
interface INavigableItem with
33+
member this.Glyph = Glyph.BasicFile
34+
member this.DisplayFileLocation = true
35+
member this.DisplayString = displayString
36+
member this.Document = document
37+
member this.SourceSpan = textSpan
38+
member this.ChildItems = ImmutableArray<INavigableItem>.Empty
39+
40+
[<Shared>]
41+
[<ExportLanguageService(typeof<IGoToDefinitionService>, FSharpCommonConstants.FSharpLanguageName)>]
42+
type internal FSharpGoToDefinitionService [<ImportingConstructor>] ([<ImportMany>]presenters: IEnumerable<INavigableItemsPresenter>) =
43+
44+
static member FindDefinition (sourceText: SourceText,
45+
filePath: string,
46+
position: int,
47+
defines: string list,
48+
options: FSharpProjectOptions,
49+
textVersionHash: int,
50+
cancellationToken: CancellationToken)
51+
: Async<Option<range>> = async {
52+
53+
let textLine = sourceText.Lines.GetLineFromPosition(position)
54+
let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based
55+
let textLineColumn = sourceText.Lines.GetLinePosition(position).Character
56+
let classifiedSpanOption =
57+
FSharpColorizationService.GetColorizationData(sourceText, textLine.Span, Some(filePath), defines, cancellationToken)
58+
|> Seq.tryFind(fun classifiedSpan -> classifiedSpan.TextSpan.Contains(position))
59+
60+
let processQualifiedIdentifier(qualifiers, islandColumn) = async {
61+
let! parseResults = FSharpChecker.Instance.ParseFileInProject(filePath, sourceText.ToString(), options)
62+
let! checkFileAnswer = FSharpChecker.Instance.CheckFileInProject(parseResults, filePath, textVersionHash, sourceText.ToString(), options)
63+
let checkFileResults = match checkFileAnswer with
64+
| FSharpCheckFileAnswer.Aborted -> failwith "Compilation isn't complete yet"
65+
| FSharpCheckFileAnswer.Succeeded(results) -> results
66+
67+
let! declarations = checkFileResults.GetDeclarationLocationAlternate (textLineNumber, islandColumn, textLine.ToString(), qualifiers, false)
68+
69+
return match declarations with
70+
| FSharpFindDeclResult.DeclFound(range) -> Some(range)
71+
| _ -> None
72+
}
73+
74+
return match classifiedSpanOption with
75+
| Some(classifiedSpan) ->
76+
match classifiedSpan.ClassificationType with
77+
| ClassificationTypeNames.Identifier ->
78+
match QuickParse.GetCompleteIdentifierIsland true (textLine.ToString()) textLineColumn with
79+
| Some(islandIdentifier, islandColumn, isQuoted) ->
80+
let qualifiers = if isQuoted then [islandIdentifier] else islandIdentifier.Split '.' |> Array.toList
81+
processQualifiedIdentifier(qualifiers, islandColumn) |> Async.RunSynchronously
82+
| None -> None
83+
| _ -> None
84+
| None -> None
85+
}
86+
87+
// FSROSLYNTODO: Since we are not integrated with the Roslyn project system yet, the below call
88+
// document.Project.Solution.GetDocumentIdsWithFilePath() will only access files in the same project.
89+
// Either Roslyn INavigableItem needs to be extended to allow arbitary full paths, or we need to
90+
// fully integrate with their project system.
91+
member this.FindDefinitionsAsyncAux(document: Document, position: int, cancellationToken: CancellationToken) =
92+
let computation = async {
93+
let results = List<INavigableItem>()
94+
match FSharpLanguageService.GetOptions(document.Project.Id) with
95+
| Some(options) ->
96+
let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask
97+
let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask
98+
let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList)
99+
let! definition = FSharpGoToDefinitionService.FindDefinition(sourceText, document.FilePath, position, defines, options, textVersion.GetHashCode(), cancellationToken)
100+
101+
match definition with
102+
| Some(range) ->
103+
let refDocumentId = document.Project.Solution.GetDocumentIdsWithFilePath(range.FileName).First()
104+
let refDocument = document.Project.Solution.GetDocument(refDocumentId)
105+
let! refSourceText = refDocument.GetTextAsync(cancellationToken) |> Async.AwaitTask
106+
let refTextSpan = CommonRoslynHelpers.FSharpRangeToTextSpan(refSourceText, range)
107+
let refDisplayString = refSourceText.GetSubText(refTextSpan).ToString()
108+
results.Add(FSharpNavigableItem(refDocument, refTextSpan, refDisplayString))
109+
| None -> ()
110+
| None -> ()
111+
return results.AsEnumerable()
112+
}
113+
114+
Async.StartAsTask(computation, TaskCreationOptions.None, cancellationToken)
115+
.ContinueWith(CommonRoslynHelpers.GetCompletedTaskResult, cancellationToken)
116+
117+
interface IGoToDefinitionService with
118+
member this.FindDefinitionsAsync(document: Document, position: int, cancellationToken: CancellationToken) =
119+
this.FindDefinitionsAsyncAux(document, position, cancellationToken)
120+
121+
member this.TryGoToDefinition(document: Document, position: int, cancellationToken: CancellationToken) =
122+
let definitionTask = this.FindDefinitionsAsyncAux(document, position, cancellationToken)
123+
definitionTask.Wait(cancellationToken)
124+
125+
if definitionTask.Status = TaskStatus.RanToCompletion then
126+
if definitionTask.Result.Any() then
127+
let navigableItem = definitionTask.Result.First() // F# API provides only one INavigableItem
128+
for presenter in presenters do
129+
presenter.DisplayResult(navigableItem.DisplayString, definitionTask.Result)
130+
true
131+
else
132+
false
133+
else
134+
false

0 commit comments

Comments
 (0)