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 .Generic
8
+ open System.Collections .Immutable
9
+ open System.Threading
10
+ open System.Threading .Tasks
11
+
12
+ open Microsoft.CodeAnalysis
13
+ open Microsoft.CodeAnalysis .Editor
14
+ open Microsoft.CodeAnalysis .Host .Mef
15
+ open Microsoft.CodeAnalysis .Text
16
+ open Microsoft.CodeAnalysis .Editor .Implementation .InlineRename
17
+
18
+ open Microsoft.FSharp .Compiler
19
+ open Microsoft.FSharp .Compiler .Parser
20
+ open Microsoft.FSharp .Compiler .Range
21
+ open Microsoft.FSharp .Compiler .SourceCodeServices
22
+
23
+ type internal FailureInlineRenameInfo private () =
24
+ interface IInlineRenameInfo with
25
+ member __.CanRename = false
26
+ member __.LocalizedErrorMessage = EditorFeaturesResources.You_ cannot_ rename_ this_ element
27
+ member __.TriggerSpan = Unchecked.defaultof<_>
28
+ member __.HasOverloads = false
29
+ member __.ForceRenameOverloads = true
30
+ member __.DisplayName = " "
31
+ member __.FullDisplayName = " "
32
+ member __.Glyph = Glyph.MethodPublic
33
+ member __.GetFinalSymbolName _replacementText = " "
34
+ member __.GetReferenceEditSpan ( _location , _cancellationToken ) = Unchecked.defaultof<_>
35
+ member __.GetConflictEditSpan ( _location , _replacementText , _cancellationToken ) = Nullable()
36
+ member __.FindRenameLocationsAsync ( _optionSet , _cancellationToken ) = Task< IInlineRenameLocationSet>. FromResult null
37
+ member __.TryOnBeforeGlobalSymbolRenamed ( _workspace , _changedDocumentIDs , _replacementText ) = false
38
+ member __.TryOnAfterGlobalSymbolRenamed ( _workspace , _changedDocumentIDs , _replacementText ) = false
39
+ static member Instance = FailureInlineRenameInfo()
40
+
41
+ type internal DocumentLocations =
42
+ { Document: Document
43
+ Locations: InlineRenameLocation [] }
44
+
45
+ type internal InlineRenameLocationSet ( locationsByDocument : DocumentLocations [], originalSolution : Solution ) =
46
+ interface IInlineRenameLocationSet with
47
+ member __.Locations : IList < InlineRenameLocation > =
48
+ [| for doc in locationsByDocument do yield ! doc.Locations |] :> _
49
+
50
+ member this.GetReplacementsAsync ( replacementText , _optionSet , cancellationToken ) : Task < IInlineRenameReplacementInfo > =
51
+ let rec applyChanges i ( solution : Solution ) =
52
+ async {
53
+ if i = locationsByDocument.Length then
54
+ return solution
55
+ else
56
+ let doc = locationsByDocument.[ i]
57
+ let! oldSourceText = doc.Document.GetTextAsync( cancellationToken) |> Async.AwaitTask
58
+ let changes = doc.Locations |> Seq.map ( fun loc -> TextChange( loc.TextSpan, replacementText))
59
+ let newSource = oldSourceText.WithChanges( changes)
60
+ return ! applyChanges ( i + 1 ) ( solution.WithDocumentText( doc.Document.Id, newSource))
61
+ }
62
+
63
+ async {
64
+ let! newSolution = applyChanges 0 originalSolution
65
+ return
66
+ { new IInlineRenameReplacementInfo with
67
+ member __.NewSolution = newSolution
68
+ member __.ReplacementTextValid = true
69
+ member __.DocumentIds = locationsByDocument |> Seq.map ( fun doc -> doc.Document.Id)
70
+ member __.GetReplacements ( documentId ) = Seq.empty }
71
+ }
72
+ |> CommonRoslynHelpers.StartAsyncAsTask( cancellationToken)
73
+
74
+ type internal InlineRenameInfo
75
+ (
76
+ checker: FSharpChecker,
77
+ projectInfoManager: ProjectInfoManager,
78
+ document: Document,
79
+ sourceText: SourceText,
80
+ symbolUse: FSharpSymbolUse,
81
+ declLoc: SymbolDeclarationLocation,
82
+ checkFileResults: FSharpCheckFileResults
83
+ ) =
84
+
85
+ let getDocumentText ( document : Document ) cancellationToken =
86
+ match document.TryGetText() with
87
+ | true , text -> text
88
+ | _ -> document.GetTextAsync( cancellationToken) .Result
89
+
90
+ let triggerSpan =
91
+ let span = CommonRoslynHelpers.FSharpRangeToTextSpan( sourceText, symbolUse.RangeAlternate)
92
+ CommonHelpers.fixupSpan( sourceText, span)
93
+
94
+ let symbolUses =
95
+ async {
96
+ let! symbolUses =
97
+ match declLoc with
98
+ | SymbolDeclarationLocation.CurrentDocument ->
99
+ checkFileResults.GetUsesOfSymbolInFile( symbolUse.Symbol)
100
+ | SymbolDeclarationLocation.Projects ( projects, isInternalToProject) ->
101
+ let projects =
102
+ if isInternalToProject then projects
103
+ else
104
+ [ for project in projects do
105
+ yield project
106
+ yield ! project.GetDependentProjects() ]
107
+ |> List.distinctBy ( fun x -> x.Id)
108
+
109
+ projects
110
+ |> Seq.map ( fun project ->
111
+ async {
112
+ match projectInfoManager.TryGetOptionsForProject( project.Id) with
113
+ | Some options ->
114
+ let! projectCheckResults = checker.ParseAndCheckProject( options)
115
+ return ! projectCheckResults.GetUsesOfSymbol( symbolUse.Symbol)
116
+ | None -> return [||]
117
+ })
118
+ |> Async.Parallel
119
+ |> Async.Map Array.concat
120
+
121
+ return
122
+ ( symbolUses
123
+ |> Seq.collect ( fun symbolUse ->
124
+ document.Project.Solution.GetDocumentIdsWithFilePath( symbolUse.FileName) |> Seq.map ( fun id -> id, symbolUse))
125
+ |> Seq.groupBy fst
126
+ ) .ToImmutableDictionary(
127
+ ( fun ( id , _ ) -> id),
128
+ fun ( _ , xs ) -> xs |> Seq.map snd |> Seq.toArray)
129
+ } |> Async.Cache
130
+
131
+ interface IInlineRenameInfo with
132
+ member __.CanRename = true
133
+ member __.LocalizedErrorMessage = null
134
+ member __.TriggerSpan = triggerSpan
135
+ member __.HasOverloads = false
136
+ member __.ForceRenameOverloads = true
137
+ member __.DisplayName = symbolUse.Symbol.DisplayName
138
+ member __.FullDisplayName = try symbolUse.Symbol.FullName with _ -> symbolUse.Symbol.DisplayName
139
+ member __.Glyph = Glyph.MethodPublic
140
+ member __.GetFinalSymbolName replacementText = replacementText
141
+
142
+ member __.GetReferenceEditSpan ( location , cancellationToken ) =
143
+ let text = getDocumentText location.Document cancellationToken
144
+ CommonHelpers.fixupSpan( text, location.TextSpan)
145
+
146
+ member __.GetConflictEditSpan ( location , _replacementText , _cancellationToken ) = Nullable( location.TextSpan)
147
+
148
+ member __.FindRenameLocationsAsync ( _optionSet , cancellationToken ) =
149
+ async {
150
+ let! symbolUsesByDocumentId = symbolUses
151
+ let! locationsByDocument =
152
+ symbolUsesByDocumentId
153
+ |> Seq.map ( fun ( KeyValue ( documentId , symbolUses )) ->
154
+ async {
155
+ let document = document.Project.Solution.GetDocument( documentId)
156
+ let! sourceText = document.GetTextAsync( cancellationToken) |> Async.AwaitTask
157
+ let locations =
158
+ symbolUses
159
+ |> Array.map ( fun symbolUse ->
160
+ let textSpan = CommonHelpers.fixupSpan( sourceText, CommonRoslynHelpers.FSharpRangeToTextSpan( sourceText, symbolUse.RangeAlternate))
161
+ InlineRenameLocation( document, textSpan))
162
+ return { Document = document; Locations = locations }
163
+ })
164
+ |> Async.Parallel
165
+ return InlineRenameLocationSet( locationsByDocument, document.Project.Solution) :> IInlineRenameLocationSet
166
+ } |> CommonRoslynHelpers.StartAsyncAsTask( cancellationToken)
167
+
168
+ member __.TryOnBeforeGlobalSymbolRenamed ( _workspace , _changedDocumentIDs , _replacementText ) = true
169
+ member __.TryOnAfterGlobalSymbolRenamed ( _workspace , _changedDocumentIDs , _replacementText ) = true
170
+
171
+ [<ExportLanguageService( typeof< IEditorInlineRenameService>, FSharpCommonConstants.FSharpLanguageName); Shared>]
172
+ type internal InlineRenameService
173
+ [<ImportingConstructor>]
174
+ (
175
+ projectInfoManager: ProjectInfoManager,
176
+ checkerProvider: FSharpCheckerProvider,
177
+ [< ImportMany>] _ refactorNotifyServices: seq< IRefactorNotifyService>
178
+ ) =
179
+
180
+ static member GetInlineRenameInfo ( checker : FSharpChecker , projectInfoManager : ProjectInfoManager , document : Document , sourceText : SourceText , position : int ,
181
+ defines : string list , options : FSharpProjectOptions , textVersionHash : int , cancellationToken : CancellationToken ) : Async < IInlineRenameInfo > =
182
+ async {
183
+ let textLine = sourceText.Lines.GetLineFromPosition( position)
184
+ let textLinePos = sourceText.Lines.GetLinePosition( position)
185
+ let fcsTextLineNumber = textLinePos.Line + 1 // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based
186
+
187
+ match CommonHelpers.tryClassifyAtPosition( document.Id, sourceText, document.FilePath, defines, position, cancellationToken) with
188
+ | Some ( islandColumn, qualifiers, _) ->
189
+ let! _parseResults , checkFileAnswer = checker.ParseAndCheckFileInProject( document.FilePath, textVersionHash, sourceText.ToString(), options)
190
+
191
+ match checkFileAnswer with
192
+ | FSharpCheckFileAnswer.Aborted -> return FailureInlineRenameInfo.Instance :> _
193
+ | FSharpCheckFileAnswer.Succeeded( checkFileResults) ->
194
+
195
+ let! symbolUse = checkFileResults.GetSymbolUseAtLocation( fcsTextLineNumber, islandColumn, textLine.Text.ToString(), qualifiers)
196
+
197
+ match symbolUse with
198
+ | Some symbolUse ->
199
+ match symbolUse.GetDeclarationLocation( document) with
200
+ | Some declLoc -> return InlineRenameInfo( checker, projectInfoManager, document, sourceText, symbolUse, declLoc, checkFileResults) :> _
201
+ | _ -> return FailureInlineRenameInfo.Instance :> _
202
+ | _ -> return FailureInlineRenameInfo.Instance :> _
203
+ | None -> return FailureInlineRenameInfo.Instance :> _
204
+ }
205
+
206
+ interface IEditorInlineRenameService with
207
+ member __.GetRenameInfoAsync ( document : Document , position : int , cancellationToken : CancellationToken ) : Task < IInlineRenameInfo > =
208
+ async {
209
+ match projectInfoManager.TryGetOptionsForEditingDocumentOrProject( document) with
210
+ | Some options ->
211
+ let! sourceText = document.GetTextAsync( cancellationToken) |> Async.AwaitTask
212
+ let! textVersion = document.GetTextVersionAsync( cancellationToken) |> Async.AwaitTask
213
+ let defines = CompilerEnvironment.GetCompilationDefinesForEditing( document.Name, options.OtherOptions |> Seq.toList)
214
+ return ! InlineRenameService.GetInlineRenameInfo( checkerProvider.Checker, projectInfoManager, document, sourceText, position, defines, options, textVersion.GetHashCode(), cancellationToken)
215
+ | None -> return FailureInlineRenameInfo.Instance :> _
216
+ }
217
+ |> CommonRoslynHelpers.StartAsyncAsTask( cancellationToken)
0 commit comments