Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 44 additions & 43 deletions vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<IInlineRenameLocationSet>.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<IInlineRenameLocationSet>.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<InlineRenameLocation> =
upcast [| for doc in locationsByDocument do yield! doc.Locations |].ToList()
member __.Locations = upcast locations.ToList()

member this.GetReplacementsAsync(replacementText, _optionSet, cancellationToken) : Task<IInlineRenameReplacementInfo> =
let rec applyChanges i (solution: Solution) =
member __.GetReplacementsAsync(replacementText, _optionSet, cancellationToken) : Task<IInlineRenameReplacementInfo> =
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)

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -142,8 +144,7 @@ type internal InlineRenameService
[<ImportingConstructor>]
(
projectInfoManager: FSharpProjectOptionsManager,
checkerProvider: FSharpCheckerProvider,
[<ImportMany>] _refactorNotifyServices: seq<IRefactorNotifyService>
checkerProvider: FSharpCheckerProvider
) =

static let userOpName = "InlineRename"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,8 @@ module internal SymbolHelpers =
|> Async.Parallel
|> Async.map Array.concat

let declarationLength =
symbol.DeclarationLocation
|> Option.map (fun m -> m.EndColumn - m.StartColumn)

return
(symbolUses
|> Seq.filter (fun su ->
match declarationLength with
| Some declLength -> su.RangeAlternate.EndColumn - su.RangeAlternate.StartColumn = declLength
| None -> true)
|> Seq.collect (fun symbolUse ->
solution.GetDocumentIdsWithFilePath(symbolUse.FileName) |> Seq.map (fun id -> id, symbolUse))
|> Seq.groupBy fst
Expand Down