-
Notifications
You must be signed in to change notification settings - Fork 871
Expand file tree
/
Copy pathInlineRenameService.fs
More file actions
228 lines (182 loc) · 9.21 KB
/
Copy pathInlineRenameService.fs
File metadata and controls
228 lines (182 loc) · 9.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace Microsoft.VisualStudio.FSharp.Editor
open System
open System.Collections.Generic
open System.Collections.Immutable
open System.Composition
open System.Linq
open System.Threading
open System.Threading.Tasks
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Symbols
open FSharp.Compiler.Text
open FSharp.Compiler.Tokenization
open CancellableTasks
type internal InlineRenameReplacementInfo(newSolution: Solution, replacementTextValid: bool, documentIds: IEnumerable<DocumentId>) =
inherit FSharpInlineRenameReplacementInfo()
override _.NewSolution = newSolution
override _.ReplacementTextValid = replacementTextValid
override _.DocumentIds = documentIds
override _.GetReplacements _ = Seq.empty
type internal InlineRenameLocationSet
(locations: FSharpInlineRenameLocation[], originalSolution: Solution, symbolKind: LexerSymbolKind, symbol: FSharpSymbol) =
inherit FSharpInlineRenameLocationSet()
static let rec applyChanges
replacementText
(solution: Solution)
(locationsByDocument: (Document * FSharpInlineRenameLocation list) list)
=
cancellableTask {
let! cancellationToken = CancellableTask.getCancellationToken ()
match locationsByDocument with
| [] -> return solution
| (document, locations) :: rest ->
let! oldSource = document.GetTextAsync(cancellationToken)
let newSource =
oldSource.WithChanges(locations |> List.map (fun l -> TextChange(l.TextSpan, replacementText)))
return! applyChanges replacementText (solution.WithDocumentText(document.Id, newSource)) rest
}
override _.Locations = upcast locations.ToList()
override _.GetReplacementsAsync(replacementText, cancellationToken) : Task<FSharpInlineRenameReplacementInfo> =
cancellableTask {
let! newSolution =
applyChanges replacementText originalSolution (locations |> Array.toList |> List.groupBy (fun x -> x.Document))
let replacementText =
match symbolKind with
| LexerSymbolKind.GenericTypeParameter
| LexerSymbolKind.StaticallyResolvedTypeParameter
| LexerSymbolKind.Operator -> replacementText
| _ -> FSharpKeywords.NormalizeIdentifierBackticks replacementText
let replacementTextValid =
Tokenizer.isValidNameForSymbol (symbolKind, symbol, replacementText)
let documentIds = locations |> Seq.map (fun doc -> doc.Document.Id) |> Seq.distinct
return new InlineRenameReplacementInfo(newSolution, replacementTextValid, documentIds) :> FSharpInlineRenameReplacementInfo
}
|> CancellableTask.start cancellationToken
type internal InlineRenameInfo
(
document: Document,
triggerSpan: TextSpan,
sourceText: SourceText,
lexerSymbol: LexerSymbol,
symbolUse: FSharpSymbolUse,
checkFileResults: FSharpCheckFileResults,
ct: CancellationToken
) =
inherit FSharpInlineRenameInfo()
let getDocumentText (document: Document) =
match document.TryGetText() with
| true, text -> CancellableTask.singleton text
| _ ->
cancellableTask {
let! cancellationToken = CancellableTask.getCancellationToken ()
return! document.GetTextAsync(cancellationToken)
}
let symbolUses =
SymbolHelpers.getSymbolUsesInSolution (symbolUse, checkFileResults, document) ct
let symbolDisplayName = symbolUse.Symbol.DisplayName
override _.CanRename = true
override _.LocalizedErrorMessage = null
override _.TriggerSpan = triggerSpan
override _.HasOverloads = false
override _.ForceRenameOverloads = false
override _.DisplayName = symbolUse.Symbol.DisplayName
override _.FullDisplayName =
try
symbolUse.Symbol.FullName
with _ ->
symbolUse.Symbol.DisplayName
override _.Glyph = Glyph.MethodPublic
override _.GetFinalSymbolName replacementText = replacementText
override _.DefinitionLocations =
ImmutableArray.Create(new FSharpInlineRenameLocation(document, triggerSpan))
override _.GetReferenceEditSpan(location, cancellationToken) =
let text =
if location.Document = document then
sourceText
else
let textTask = getDocumentText location.Document
CancellableTask.runSynchronously cancellationToken textTask
Tokenizer.fixupSpan (text, location.TextSpan)
override _.GetConflictEditSpan(location, replacementText, cancellationToken) =
let text =
if location.Document = document then
sourceText
else
let textTask = getDocumentText location.Document
CancellableTask.runSynchronously cancellationToken textTask
let spanText = text.ToString(location.TextSpan)
let position = spanText.LastIndexOf(replacementText, StringComparison.Ordinal)
if position < 0 then
Nullable()
else
Nullable(TextSpan(location.TextSpan.Start + position, replacementText.Length))
override _.FindRenameLocationsAsync(_, _, cancellationToken) =
cancellableTask {
let! symbolUsesByDocumentId = symbolUses
let! results =
seq {
for (KeyValue(documentId, symbolUses)) in symbolUsesByDocumentId do
cancellableTask {
let! cancellationToken = CancellableTask.getCancellationToken ()
let document = document.Project.Solution.GetDocument(documentId)
let! sourceText = document.GetTextAsync(cancellationToken)
return
[|
for symbolUse in symbolUses do
match Tokenizer.TryFSharpRangeToTextSpanForEditor(sourceText, symbolUse, symbolDisplayName) with
| ValueSome textSpan -> yield FSharpInlineRenameLocation(document, textSpan)
| ValueNone -> ()
|]
}
}
|> CancellableTask.whenAll
let locations = Array.concat results
return
InlineRenameLocationSet(locations, document.Project.Solution, lexerSymbol.Kind, symbolUse.Symbol)
:> FSharpInlineRenameLocationSet
}
|> CancellableTask.start cancellationToken
[<Export(typeof<FSharpInlineRenameServiceImplementation>); Shared>]
type internal InlineRenameService [<ImportingConstructor>] () =
inherit FSharpInlineRenameServiceImplementation()
override _.GetRenameInfoAsync(document: Document, position: int, cancellationToken: CancellationToken) : Task<FSharpInlineRenameInfo> =
cancellableTask {
let! ct = CancellableTask.getCancellationToken ()
let! sourceText = document.GetTextAsync(ct)
let textLine = sourceText.Lines.GetLineFromPosition(position)
let textLinePos = sourceText.Lines.GetLinePosition(position)
let fcsTextLineNumber = Line.fromZ textLinePos.Line
let! symbol =
document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, nameof (InlineRenameService))
// TODO: Rewrite to less nested variant after everything works.
match symbol with
| None -> return Unchecked.defaultof<_>
| Some symbol ->
let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof (InlineRenameService))
let symbolUse =
checkFileResults.GetSymbolUseAtLocation(
fcsTextLineNumber,
symbol.Ident.idRange.EndColumn,
textLine.Text.ToString(),
symbol.FullIsland
)
match symbolUse with
| None -> return Unchecked.defaultof<_>
| Some symbolUse ->
match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with
| ValueNone -> return Unchecked.defaultof<_>
| ValueSome textSpan ->
match textSpan with
| Tokenizer.FixedSpan sourceText symbolUse.Symbol.DisplayName triggerSpan ->
let result =
InlineRenameInfo(document, triggerSpan, sourceText, symbol, symbolUse, checkFileResults, ct)
return result :> FSharpInlineRenameInfo
| _ ->
// #18270: Abort if user clicked on phantom get/set accessor keyword
return Unchecked.defaultof<_>
}
|> CancellableTask.start cancellationToken