|
| 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