diff --git a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs index 81d87ed4a5e7ab098fe7e5355b9c939ac01909f6..cde09f1dbea757f0b33a708a6331ae792f27f250 100644 --- a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs +++ b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs @@ -4,7 +4,6 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.Composition -open System.Collections.Generic open System.Linq open System.Threading open System.Threading.Tasks @@ -29,44 +28,43 @@ type internal FailureInlineRenameInfo private () = member __.DisplayName = "" member __.FullDisplayName = "" member __.Glyph = Glyph.MethodPublic - member __.GetFinalSymbolName _replacementText = "" - member __.GetReferenceEditSpan(_location, _cancellationToken) = Unchecked.defaultof<_> - member __.GetConflictEditSpan(_location, _replacementText, _cancellationToken) = Nullable() - member __.FindRenameLocationsAsync(_optionSet, _cancellationToken) = Task.FromResult null - member __.TryOnBeforeGlobalSymbolRenamed(_workspace, _changedDocumentIDs, _replacementText) = false - member __.TryOnAfterGlobalSymbolRenamed(_workspace, _changedDocumentIDs, _replacementText) = false + member __.GetFinalSymbolName _ = "" + member __.GetReferenceEditSpan(_, _) = Unchecked.defaultof<_> + member __.GetConflictEditSpan(_, _, _) = Nullable() + member __.FindRenameLocationsAsync(_, _) = Task.FromResult null + member __.TryOnBeforeGlobalSymbolRenamed(_, _, _) = false + member __.TryOnAfterGlobalSymbolRenamed(_, _, _) = false static member Instance = FailureInlineRenameInfo() :> IInlineRenameInfo -type internal DocumentLocations = - { Document: Document - Locations: InlineRenameLocation [] } - -type internal InlineRenameLocationSet(locationsByDocument: DocumentLocations [], originalSolution: Solution, symbolKind: LexerSymbolKind, symbol: FSharpSymbol) = +type internal InlineRenameLocationSet(locations: InlineRenameLocation [], originalSolution: Solution, symbolKind: LexerSymbolKind, symbol: FSharpSymbol) = interface IInlineRenameLocationSet with - member __.Locations : IList = - upcast [| for doc in locationsByDocument do yield! doc.Locations |].ToList() + member __.Locations = upcast locations.ToList() - member this.GetReplacementsAsync(replacementText, _optionSet, cancellationToken) : Task = - let rec applyChanges i (solution: Solution) = + member __.GetReplacementsAsync(replacementText, _optionSet, cancellationToken) : Task = + let rec applyChanges (solution: Solution) (locationsByDocument: (Document * InlineRenameLocation list) list) = async { - if i = locationsByDocument.Length then - return solution - else - let doc = locationsByDocument.[i] - let! oldSourceText = doc.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask - let changes = doc.Locations |> Seq.map (fun loc -> TextChange(loc.TextSpan, replacementText)) - let newSource = oldSourceText.WithChanges(changes) - return! applyChanges (i + 1) (solution.WithDocumentText(doc.Document.Id, newSource)) + match locationsByDocument with + | [] -> return solution + | (document, locations) :: rest -> + let! oldSource = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let newSource = oldSource.WithChanges(locations |> List.map (fun l -> TextChange(l.TextSpan, replacementText))) + return! applyChanges (solution.WithDocumentText(document.Id, newSource)) rest } async { - let! newSolution = applyChanges 0 originalSolution + let! newSolution = applyChanges originalSolution (locations |> Array.toList |> List.groupBy (fun x -> x.Document)) + // > debug + let newDoc = newSolution.GetDocument(locations.[0].Document.Id) + let! newSource = newDoc.GetTextAsync(cancellationToken) |> Async.AwaitTask + let newText = newSource.ToString() + let _ = newText + // < debug return { new IInlineRenameReplacementInfo with member __.NewSolution = newSolution member __.ReplacementTextValid = Tokenizer.isValidNameForSymbol(symbolKind, symbol, replacementText) - member __.DocumentIds = locationsByDocument |> Seq.map (fun doc -> doc.Document.Id) - member __.GetReplacements(documentId) = Seq.empty } + member __.DocumentIds = locations |> Seq.map (fun doc -> doc.Document.Id) |> Seq.distinct + member __.GetReplacements _ = Seq.empty } } |> RoslynHelpers.StartAsyncAsTask(cancellationToken) @@ -98,7 +96,7 @@ type internal InlineRenameInfo member __.LocalizedErrorMessage = null member __.TriggerSpan = triggerSpan member __.HasOverloads = false - member __.ForceRenameOverloads = true + member __.ForceRenameOverloads = false member __.DisplayName = symbolUse.Symbol.DisplayName member __.FullDisplayName = try symbolUse.Symbol.FullName with _ -> symbolUse.Symbol.DisplayName member __.Glyph = Glyph.MethodPublic @@ -108,30 +106,34 @@ type internal InlineRenameInfo let text = getDocumentText location.Document cancellationToken Tokenizer.fixupSpan(text, location.TextSpan) - member __.GetConflictEditSpan(location, _replacementText, _cancellationToken) = Nullable(location.TextSpan) + member __.GetConflictEditSpan(location, replacementText, cancellationToken) = + let text = getDocumentText location.Document cancellationToken + 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)) member __.FindRenameLocationsAsync(_optionSet, cancellationToken) = async { let! symbolUsesByDocumentId = symbolUses - let! locationsByDocument = + let! locations = symbolUsesByDocumentId |> Seq.map (fun (KeyValue(documentId, symbolUses)) -> async { let document = document.Project.Solution.GetDocument(documentId) - let! cancellationToken = Async.CancellationToken let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask - let locations = - symbolUses - |> Array.choose (fun symbolUse -> - RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.RangeAlternate) - |> Option.map (fun span -> - let textSpan = Tokenizer.fixupSpan(sourceText, span) - InlineRenameLocation(document, textSpan))) - - return { Document = document; Locations = locations } + return + [| for symbolUse in symbolUses do + match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.RangeAlternate) with + | Some span -> + let textSpan = Tokenizer.fixupSpan(sourceText, span) + yield InlineRenameLocation(document, textSpan) + | None -> () |] }) |> Async.Parallel - return InlineRenameLocationSet(locationsByDocument, document.Project.Solution, lexerSymbol.Kind, symbolUse.Symbol) :> IInlineRenameLocationSet + |> Async.map Array.concat + + return InlineRenameLocationSet(locations, document.Project.Solution, lexerSymbol.Kind, symbolUse.Symbol) :> IInlineRenameLocationSet } |> RoslynHelpers.StartAsyncAsTask(cancellationToken) member __.TryOnBeforeGlobalSymbolRenamed(_workspace, _changedDocumentIDs, _replacementText) = true @@ -142,8 +144,7 @@ type internal InlineRenameService [] ( projectInfoManager: FSharpProjectOptionsManager, - checkerProvider: FSharpCheckerProvider, - [] _refactorNotifyServices: seq + checkerProvider: FSharpCheckerProvider ) = static let userOpName = "InlineRename"