From c4a8ff2ea0bc82c5f040d9db0d5d102a6fa5794c Mon Sep 17 00:00:00 2001 From: Don Syme Date: Wed, 7 Jun 2017 01:59:21 +0200 Subject: [PATCH] fix span bug (#3181) --- .../Classification/ColorizationService.fs | 7 +++++-- .../src/FSharp.Editor/Common/RoslynHelpers.fs | 4 ++-- .../Debugging/BreakpointResolutionService.fs | 3 ++- .../DocumentHighlightsService.fs | 7 +++++-- .../Formatting/BraceMatchingService.fs | 12 ++++++----- .../InlineRename/InlineRenameService.fs | 21 +++++++++++-------- .../LanguageService/SymbolHelpers.fs | 9 +++++--- .../Navigation/GoToDefinitionService.fs | 17 ++++++++------- .../Navigation/NavigateToSearchService.fs | 12 ++++++----- .../QuickInfo/QuickInfoProvider.fs | 10 +++++---- 10 files changed, 61 insertions(+), 41 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Classification/ColorizationService.fs b/vsintegration/src/FSharp.Editor/Classification/ColorizationService.fs index b933d22f4..942ca6395 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ColorizationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ColorizationService.fs @@ -45,8 +45,11 @@ type internal FSharpColorizationService let colorizationData = checkResults.GetSemanticClassification (Some targetRange) |> Array.distinctBy fst for (range, classificationType) in colorizationData do - let span = Tokenizer.fixupSpan(sourceText, RoslynHelpers.FSharpRangeToTextSpan(sourceText, range)) - result.Add(ClassifiedSpan(span, FSharpClassificationTypes.getClassificationTypeName(classificationType))) + match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) with + | None -> () + | Some span -> + let span = Tokenizer.fixupSpan(sourceText, span) + result.Add(ClassifiedSpan(span, FSharpClassificationTypes.getClassificationTypeName(classificationType))) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs b/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs index 6055c7345..cc108313e 100644 --- a/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs +++ b/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs @@ -21,8 +21,8 @@ module internal RoslynHelpers = let FSharpRangeToTextSpan(sourceText: SourceText, range: range) = // Roslyn TextLineCollection is zero-based, F# range lines are one-based - let startPosition = sourceText.Lines.[range.StartLine - 1].Start + range.StartColumn - let endPosition = sourceText.Lines.[range.EndLine - 1].Start + range.EndColumn + let startPosition = sourceText.Lines.[max 0 (range.StartLine - 1)].Start + range.StartColumn + let endPosition = sourceText.Lines.[min (range.EndLine - 1) (sourceText.Lines.Count - 1)].Start + range.EndColumn TextSpan(startPosition, endPosition - startPosition) let TryFSharpRangeToTextSpan(sourceText: SourceText, range: range) : TextSpan option = diff --git a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs index 537463030..827bca085 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs @@ -52,7 +52,8 @@ type internal FSharpBreakpointResolutionService let! options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document) let! sourceText = document.GetTextAsync(cancellationToken) let! range = FSharpBreakpointResolutionService.GetBreakpointLocation(checkerProvider.Checker, sourceText, document.Name, textSpan, options) - return BreakpointResolutionResult.CreateSpanResult(document, RoslynHelpers.FSharpRangeToTextSpan(sourceText, range)) + let! span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) + return BreakpointResolutionResult.CreateSpanResult(document, span) } |> Async.map Option.toObj |> RoslynHelpers.StartAsyncAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs index 1eab782c6..f0486b636 100644 --- a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs +++ b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs @@ -64,8 +64,11 @@ type internal FSharpDocumentHighlightsService [] (checkerP let! symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) |> liftAsync return [| for symbolUse in symbolUses do - yield { IsDefinition = symbolUse.IsFromDefinition - TextSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, symbolUse.RangeAlternate) } |] + match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.RangeAlternate) with + | None -> () + | Some span -> + yield { IsDefinition = symbolUse.IsFromDefinition + TextSpan = span } |] |> fixInvalidSymbolSpans sourceText symbol.Ident.idText } diff --git a/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs b/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs index 2661b6e3c..1dcd1fe8a 100644 --- a/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs +++ b/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs @@ -19,7 +19,10 @@ type internal FSharpBraceMatchingService static member GetBraceMatchingResult(checker: FSharpChecker, sourceText, fileName, options, position: int) = async { let! matchedBraces = checker.MatchBraces(fileName, sourceText.ToString(), options, userOpName = userOpName) - let isPositionInRange range = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range).Contains(position) + let isPositionInRange range = + match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) with + | None -> false + | Some range -> range.Contains(position) return matchedBraces |> Array.tryFind(fun (left, right) -> isPositionInRange left || isPositionInRange right) } @@ -29,10 +32,9 @@ type internal FSharpBraceMatchingService let! options = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document) let! sourceText = document.GetTextAsync(cancellationToken) let! (left, right) = FSharpBraceMatchingService.GetBraceMatchingResult(checkerProvider.Checker, sourceText, document.Name, options, position) - return - BraceMatchingResult( - RoslynHelpers.FSharpRangeToTextSpan(sourceText, left), - RoslynHelpers.FSharpRangeToTextSpan(sourceText, right)) + let! leftSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, left) + let! rightSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, right) + return BraceMatchingResult(leftSpan, rightSpan) } |> Async.map Option.toNullable |> RoslynHelpers.StartAsyncAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs index d6648d091..50c933324 100644 --- a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs +++ b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs @@ -75,7 +75,7 @@ type internal InlineRenameInfo checker: FSharpChecker, projectInfoManager: ProjectInfoManager, document: Document, - sourceText: SourceText, + triggerSpan: TextSpan, lexerSymbol: LexerSymbol, symbolUse: FSharpSymbolUse, declLoc: SymbolDeclarationLocation, @@ -89,10 +89,6 @@ type internal InlineRenameInfo | true, text -> text | _ -> document.GetTextAsync(cancellationToken).Result - let triggerSpan = - let span = RoslynHelpers.FSharpRangeToTextSpan(sourceText, symbolUse.RangeAlternate) - Tokenizer.fixupSpan(sourceText, span) - let symbolUses = SymbolHelpers.getSymbolUsesInSolution(symbolUse.Symbol, declLoc, checkFileResults, projectInfoManager, checker, document.Project.Solution, userOpName) |> Async.cache @@ -126,9 +122,12 @@ type internal InlineRenameInfo let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let locations = symbolUses - |> Array.map (fun symbolUse -> - let textSpan = Tokenizer.fixupSpan(sourceText, RoslynHelpers.FSharpRangeToTextSpan(sourceText, symbolUse.RangeAlternate)) - InlineRenameLocation(document, textSpan)) + |> 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 } }) |> Async.Parallel @@ -158,7 +157,11 @@ type internal InlineRenameService let! _, _, checkFileResults = checker.ParseAndCheckDocument(document, options, allowStaleResults = true, userOpName = userOpName) let! symbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, symbol.Ident.idRange.EndColumn, textLine.Text.ToString(), symbol.FullIsland, userOpName=userOpName) let! declLoc = symbolUse.GetDeclarationLocation(document) - return InlineRenameInfo(checker, projectInfoManager, document, sourceText, symbol, symbolUse, declLoc, checkFileResults) :> IInlineRenameInfo + + let! span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.RangeAlternate) + let triggerSpan = Tokenizer.fixupSpan(sourceText, span) + + return InlineRenameInfo(checker, projectInfoManager, document, triggerSpan, symbol, symbolUse, declLoc, checkFileResults) :> IInlineRenameInfo } interface IEditorInlineRenameService with diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index 8f8d9c52b..e29742a11 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -95,9 +95,12 @@ module internal SymbolHelpers = let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let mutable sourceText = sourceText for symbolUse in symbolUses do - let textSpan = Tokenizer.fixupSpan(sourceText, RoslynHelpers.FSharpRangeToTextSpan(sourceText, symbolUse.RangeAlternate)) - sourceText <- sourceText.Replace(textSpan, newText) - solution <- solution.WithDocumentText(documentId, sourceText) + match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.RangeAlternate) with + | None -> () + | Some span -> + let textSpan = Tokenizer.fixupSpan(sourceText, span) + sourceText <- sourceText.Replace(textSpan, newText) + solution <- solution.WithDocumentText(documentId, sourceText) return solution } |> RoslynHelpers.StartAsyncAsTask cancellationToken), originalText diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index 54a6954cd..556bbb216 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -49,8 +49,9 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: Project let refDocument = document.Project.Solution.GetDocument refDocumentId let! cancellationToken = Async.CancellationToken let! refSourceText = refDocument.GetTextAsync(cancellationToken) |> Async.AwaitTask - let refTextSpan = RoslynHelpers.FSharpRangeToTextSpan (refSourceText, range) - return Some (FSharpNavigableItem (refDocument, refTextSpan)) + match RoslynHelpers.TryFSharpRangeToTextSpan (refSourceText, range) with + | None -> return None + | Some refTextSpan -> return Some (FSharpNavigableItem (refDocument, refTextSpan)) else return None } @@ -59,7 +60,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: Project asyncMaybe { let! projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject originDocument let defines = CompilerEnvironment.GetCompilationDefinesForEditing (originDocument.FilePath, projectOptions.OtherOptions |> Seq.toList) - let originTextSpan = RoslynHelpers.FSharpRangeToTextSpan (sourceText, originRange) + let! originTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sourceText, originRange) let position = originTextSpan.Start let! lexerSymbol = Tokenizer.getSymbolAtPosition (originDocument.Id, sourceText, position, originDocument.FilePath, defines, SymbolLookupKind.Greedy, false) @@ -83,7 +84,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: Project let! _, _, checkFileResults = checker.ParseAndCheckDocument (implDoc, projectOptions, allowStaleResults=true, sourceText=implSourceText, userOpName = userOpName) let! symbolUses = checkFileResults.GetUsesOfSymbolInFile symbol |> liftAsync let! implSymbol = symbolUses |> Array.tryHead - let implTextSpan = RoslynHelpers.FSharpRangeToTextSpan (implSourceText, implSymbol.RangeAlternate) + let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, implSymbol.RangeAlternate) return FSharpNavigableItem (implDoc, implTextSpan) else let! targetDocument = originDocument.Project.Solution.TryGetDocumentFromFSharpRange fsSymbolUse.RangeAlternate @@ -241,7 +242,7 @@ type internal FSharpGoToDefinitionService let! targetRange = gotoDefinition.FindSymbolDeclarationInFile(targetSymbolUse, implFilePath, implSourceText.ToString(), projectOptions, implVersion.GetHashCode()) - let implTextSpan = RoslynHelpers.FSharpRangeToTextSpan (implSourceText, targetRange) + let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) let navItem = FSharpNavigableItem (implDocument, implTextSpan) return navItem else // jump from implementation to the corresponding signature @@ -250,7 +251,7 @@ type internal FSharpGoToDefinitionService | FSharpFindDeclResult.DeclFound targetRange -> let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync - let sigTextSpan = RoslynHelpers.FSharpRangeToTextSpan (sigSourceText, targetRange) + let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange) let navItem = FSharpNavigableItem (sigDocument, sigTextSpan) return navItem | _ -> return! None @@ -260,7 +261,7 @@ type internal FSharpGoToDefinitionService else let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync - let sigTextSpan = RoslynHelpers.FSharpRangeToTextSpan (sigSourceText, targetRange) + let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange) // if the gotodef call originated from a signature and the returned target is a signature, navigate there if isSignatureFile targetRange.FileName && preferSignature then let navItem = FSharpNavigableItem (sigDocument, sigTextSpan) @@ -280,7 +281,7 @@ type internal FSharpGoToDefinitionService let! targetRange = gotoDefinition.FindSymbolDeclarationInFile(targetSymbolUse, implFilePath, implSourceText.ToString(), projectOptions, implVersion.GetHashCode()) - let implTextSpan = RoslynHelpers.FSharpRangeToTextSpan (implSourceText, targetRange) + let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) let navItem = FSharpNavigableItem (implDocument, implTextSpan) return navItem | _ -> return! None diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index 25be80541..bdf2d1a81 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -196,11 +196,13 @@ type internal FSharpNavigateToSearchService match parseResults.ParseTree |> Option.map NavigateTo.getNavigableItems with | Some items -> [| for item in items do - let sourceSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, item.Range) - let glyph = Utils.navigateToItemKindToGlyph item.Kind - let kind = Utils.navigateToItemKindToRoslynKind item.Kind - let additionalInfo = Utils.containerToString item.Container document.Project - yield NavigableItem(document, sourceSpan, glyph, item.Name, kind, additionalInfo) |] + match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) with + | None -> () + | Some sourceSpan -> + let glyph = Utils.navigateToItemKindToGlyph item.Kind + let kind = Utils.navigateToItemKindToRoslynKind item.Kind + let additionalInfo = Utils.containerToString item.Container document.Project + yield NavigableItem(document, sourceSpan, glyph, item.Name, kind, additionalInfo) |] | None -> [||] } diff --git a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs index e724423de..540856832 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs @@ -50,7 +50,7 @@ module private FSharpQuickInfo = let! extDocId = solution.GetDocumentIdsWithFilePath declRange.FileName |> Seq.tryHead let extDocument = solution.GetProject(extDocId.ProjectId).GetDocument extDocId let! extSourceText = extDocument.GetTextAsync cancellationToken - let extSpan = RoslynHelpers.FSharpRangeToTextSpan (extSourceText, declRange) + let! extSpan = RoslynHelpers.TryFSharpRangeToTextSpan (extSourceText, declRange) let extLineText = (extSourceText.Lines.GetLineFromPosition extSpan.Start).ToString() // project options need to be retrieved because the signature file could be in another project @@ -69,9 +69,10 @@ module private FSharpQuickInfo = | extTooltipText -> let! extSymbolUse = extCheckFileResults.GetSymbolUseAtLocation(declRange.StartLine, extLexerSymbol.Ident.idRange.EndColumn, extLineText, extLexerSymbol.FullIsland, userOpName=userOpName) + let! span = RoslynHelpers.TryFSharpRangeToTextSpan (extSourceText, extLexerSymbol.Range) return { StructuredText = extTooltipText - Span = RoslynHelpers.FSharpRangeToTextSpan (extSourceText, extLexerSymbol.Range) + Span = span Symbol = extSymbolUse.Symbol SymbolKind = extLexerSymbol.Kind } } @@ -110,7 +111,7 @@ module private FSharpQuickInfo = | FSharpToolTipText [] | FSharpToolTipText [FSharpStructuredToolTipElement.None] -> return! None | _ -> - let targetTextSpan = RoslynHelpers.FSharpRangeToTextSpan (sourceText, lexerSymbol.Range) + let! targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sourceText, lexerSymbol.Range) return { StructuredText = targetTooltip Span = targetTextSpan Symbol = symbolUse.Symbol @@ -180,7 +181,8 @@ type internal FSharpQuickInfoProvider | FSharpToolTipText [FSharpStructuredToolTipElement.None] -> return! None | _ -> let! symbolUse = checkFileResults.GetSymbolUseAtLocation (textLineNumber, symbol.Ident.idRange.EndColumn, textLine.ToString(), symbol.FullIsland, userOpName=FSharpQuickInfo.userOpName) - return res, RoslynHelpers.FSharpRangeToTextSpan (sourceText, symbol.Range), symbolUse.Symbol, symbol.Kind + let! symbolSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sourceText, symbol.Range) + return res, symbolSpan, symbolUse.Symbol, symbol.Kind } interface IQuickInfoProvider with -- GitLab