From d0ed695b2beccbb37b0043f7c14f4e2d1cd75e2e Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Wed, 30 Aug 2023 13:37:09 +0200 Subject: [PATCH 01/20] wip --- .../Classification/ClassificationService.fs | 6 +- .../Commands/XmlDocCommandService.fs | 120 +++++++++--------- .../Common/CodeAnalysisExtensions.fs | 13 +- .../src/FSharp.Editor/Common/Extensions.fs | 71 +++++++++++ .../src/FSharp.Editor/Common/Pervasive.fs | 15 +++ .../src/FSharp.Editor/Common/RoslynHelpers.fs | 6 +- .../Debugging/BreakpointResolutionService.fs | 17 ++- .../DocumentHighlightsService.fs | 4 +- .../Formatting/BraceMatchingService.fs | 4 +- .../InlineRename/InlineRenameService.fs | 8 +- .../LanguageService/LanguageService.fs | 4 +- .../LanguageService/SymbolHelpers.fs | 4 +- .../LanguageService/WorkspaceExtensions.fs | 8 +- .../Navigation/FindUsagesService.fs | 8 +- .../Navigation/GoToDefinition.fs | 6 +- .../Navigation/NavigateToSearchService.fs | 2 +- .../Navigation/NavigationBarItemService.fs | 57 +++++---- .../QuickInfo/QuickInfoProvider.fs | 4 +- .../Refactor/ChangeDerefToValueRefactoring.fs | 62 +++++---- .../ChangeTypeofWithNameToNameofExpression.fs | 69 +++++----- .../Structure/BlockStructureService.fs | 2 +- 21 files changed, 296 insertions(+), 194 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs index b837385c1a9..8900417cdc0 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs @@ -52,7 +52,7 @@ type internal FSharpClassificationService [] () = ClassificationTypeNames.Text match RoslynHelpers.TryFSharpRangeToTextSpan(text, tok.Range) with - | Some span -> result.Add(ClassifiedSpan(TextSpan(textSpan.Start + span.Start, span.Length), spanKind)) + | ValueSome span -> result.Add(ClassifiedSpan(TextSpan(textSpan.Start + span.Start, span.Length), spanKind)) | _ -> () let flags = @@ -79,8 +79,8 @@ type internal FSharpClassificationService [] () = = for item in items do match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) with - | None -> () - | Some span -> + | ValueNone -> () + | ValueSome span -> let span = match item.Type with | SemanticClassificationType.Printf -> span diff --git a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs index f2bcf18e870..4efd4e51f38 100644 --- a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs @@ -16,6 +16,8 @@ open Microsoft.VisualStudio.TextManager.Interop open Microsoft.VisualStudio.LanguageServices open Microsoft.VisualStudio.Utilities open FSharp.Compiler.EditorServices +open CancellableTasks.CancellableTaskBuilder +open CancellableTasks type internal XmlDocCommandFilter(wpfTextView: IWpfTextView, filePath: string, workspace: VisualStudioWorkspace) = @@ -58,76 +60,80 @@ type internal XmlDocCommandFilter(wpfTextView: IWpfTextView, filePath: string, w match XmlDocComment.IsBlank lineWithLastCharInserted with | Some i when i = indexOfCaret -> - asyncMaybe { + cancellableTask { try // XmlDocable line #1 are 1-based, editor is 0-based let curEditorLineNum = wpfTextView.Caret.Position.BufferPosition.GetContainingLine().LineNumber - let! document = getLastDocument () - let! cancellationToken = Async.CancellationToken |> liftAsync - let! sourceText = document.GetTextAsync(cancellationToken) - let! parseResults = document.GetFSharpParseResultsAsync(nameof (XmlDocCommandFilter)) |> liftAsync - - let xmlDocables = - XmlDocParser.GetXmlDocables(sourceText.ToFSharpSourceText(), parseResults.ParseTree) - - let xmlDocablesBelowThisLine = - // +1 because looking below current line for e.g. a 'member' or 'let' - xmlDocables - |> List.filter (fun (XmlDocable (line, _indent, _paramNames)) -> line = curEditorLineNum + 1) - - match xmlDocablesBelowThisLine with - | [] -> () - | XmlDocable (_line, indent, paramNames) :: _xs -> - // delete the slashes the user typed (they may be indented wrong) - let editorLineToDelete = - wpfTextView.TextBuffer.CurrentSnapshot.GetLineFromLineNumber( - wpfTextView.Caret.Position.BufferPosition.GetContainingLine().LineNumber - ) - - wpfTextView.TextBuffer.Delete(editorLineToDelete.Extent.Span) |> ignore - // add the new xmldoc comment - let toInsert = new Text.StringBuilder() - - toInsert - .Append(' ', indent) - .AppendLine("/// ") - .Append(' ', indent) - .AppendLine("/// ") - .Append(' ', indent) - .Append("/// ") - |> ignore - - paramNames - |> List.iter (fun p -> - toInsert - .AppendLine() - .Append(' ', indent) - .Append(sprintf "/// " p) - |> ignore) + let document = getLastDocument () + + match document with + | None -> () + | Some document -> + let! cancellationToken = CancellableTask.getCancellationToken () + let! sourceText = document.GetTextAsync(cancellationToken) + let! parseResults = document.GetFSharpParseResultsAsync(nameof (XmlDocCommandFilter)) + + let xmlDocables = + XmlDocParser.GetXmlDocables(sourceText.ToFSharpSourceText(), parseResults.ParseTree) - let _newSS = - wpfTextView.TextBuffer.Insert( - wpfTextView.Caret.Position.BufferPosition.Position, - toInsert.ToString() - ) - // move the caret to between the summary tags - let lastLine = wpfTextView.Caret.Position.BufferPosition.GetContainingLine() + let xmlDocablesBelowThisLine = + // +1 because looking below current line for e.g. a 'member' or 'let' + xmlDocables + |> List.filter (fun (XmlDocable (line, _indent, _paramNames)) -> line = curEditorLineNum + 1) - let middleSummaryLine = - wpfTextView.TextSnapshot.GetLineFromLineNumber(lastLine.LineNumber - 1 - paramNames.Length) + match xmlDocablesBelowThisLine with + | [] -> () + | XmlDocable (_line, indent, paramNames) :: _xs -> + // delete the slashes the user typed (they may be indented wrong) + let editorLineToDelete = + wpfTextView.TextBuffer.CurrentSnapshot.GetLineFromLineNumber( + wpfTextView.Caret.Position.BufferPosition.GetContainingLine().LineNumber + ) - wpfTextView.Caret.MoveTo(wpfTextView.GetTextViewLineContainingBufferPosition(middleSummaryLine.Start)) - |> ignore + wpfTextView.TextBuffer.Delete(editorLineToDelete.Extent.Span) |> ignore + // add the new xmldoc comment + let toInsert = new Text.StringBuilder() - shouldCommitCharacter <- false + toInsert + .Append(' ', indent) + .AppendLine("/// ") + .Append(' ', indent) + .AppendLine("/// ") + .Append(' ', indent) + .Append("/// ") + |> ignore + + paramNames + |> List.iter (fun p -> + toInsert + .AppendLine() + .Append(' ', indent) + .Append(sprintf "/// " p) + |> ignore) + + let _newSS = + wpfTextView.TextBuffer.Insert( + wpfTextView.Caret.Position.BufferPosition.Position, + toInsert.ToString() + ) + // move the caret to between the summary tags + let lastLine = wpfTextView.Caret.Position.BufferPosition.GetContainingLine() + + let middleSummaryLine = + wpfTextView.TextSnapshot.GetLineFromLineNumber(lastLine.LineNumber - 1 - paramNames.Length) + + wpfTextView.Caret.MoveTo(wpfTextView.GetTextViewLineContainingBufferPosition(middleSummaryLine.Start)) + |> ignore + + shouldCommitCharacter <- false with ex -> Assert.Exception ex () } - |> Async.Ignore - |> Async.StartImmediate + |> CancellableTask.startAsTaskWithoutCancellation // We don't have a cancellation token here at the moment, maybe there's a better way of handling it in modern VS? + |> ignore | Some _ | None -> () | _ -> () diff --git a/vsintegration/src/FSharp.Editor/Common/CodeAnalysisExtensions.fs b/vsintegration/src/FSharp.Editor/Common/CodeAnalysisExtensions.fs index 888505ecda3..c42ace1a366 100644 --- a/vsintegration/src/FSharp.Editor/Common/CodeAnalysisExtensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/CodeAnalysisExtensions.fs @@ -53,17 +53,8 @@ type Solution with // It's crucial to normalize file path here (specificaly, remove relative parts), // otherwise Roslyn does not find documents. self.GetDocumentIdsWithFilePath(Path.GetFullPath filePath) - |> Seq.tryHead - |> Option.map (fun docId -> self.GetDocument docId) - - /// Try to find the document corresponding to the provided filepath and ProjectId within this solution - member self.TryGetDocumentFromPath(filePath, projId: ProjectId) = - // It's crucial to normalize file path here (specificaly, remove relative parts), - // otherwise Roslyn does not find documents. - self.GetDocumentIdsWithFilePath(Path.GetFullPath filePath) - |> Seq.filter (fun x -> x.ProjectId = projId) - |> Seq.tryHead - |> Option.map (fun docId -> self.GetDocument docId) + |> ImmutableArray.tryHeadV + |> ValueOption.map (fun docId -> self.GetDocument docId) /// Try to get a project inside the solution using the project's id member self.TryGetProject(projId: ProjectId) = diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index d05b5166a08..3c9d54f4261 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -349,6 +349,77 @@ module Array = loop 0 + let inline chooseV ([] chooser: 'T -> 'U voption) (array: 'T[]) = + + let mutable i = 0 + let mutable first = Unchecked.defaultof<'U> + let mutable found = false + + while i < array.Length && not found do + let element = array.[i] + + match chooser element with + | ValueNone -> i <- i + 1 + | ValueSome b -> + first <- b + found <- true + + if i <> array.Length then + + let chunk1: 'U[] = + Array.zeroCreate ((array.Length >>> 2) + 1) + + chunk1.[0] <- first + let mutable count = 1 + i <- i + 1 + + while count < chunk1.Length && i < array.Length do + let element = array.[i] + + match chooser element with + | ValueNone -> () + | ValueSome b -> + chunk1.[count] <- b + count <- count + 1 + + i <- i + 1 + + if i < array.Length then + let chunk2: 'U[] = + Array.zeroCreate (array.Length - i) + + count <- 0 + + while i < array.Length do + let element = array.[i] + + match chooser element with + | ValueNone -> () + | ValueSome b -> + chunk2.[count] <- b + count <- count + 1 + + i <- i + 1 + + let res: 'U[] = + Array.zeroCreate (chunk1.Length + count) + + Array.Copy(chunk1, res, chunk1.Length) + Array.Copy(chunk2, 0, res, chunk1.Length, count) + res + else + Array.sub chunk1 0 count + else + Array.empty + +[] +module ImmutableArray = + let inline tryHeadV (xs: ImmutableArray<'T>) : 'T voption = + if xs.Length = 0 then + ValueNone + else + ValueSome xs[0] + [] module List = let rec tryFindV predicate list = diff --git a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs index 18e5c463c72..57f78491e9c 100644 --- a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs +++ b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs @@ -57,6 +57,13 @@ type MaybeBuilder() = [] member inline _.Bind(value, f: 'T -> 'U option) : 'U option = Option.bind f value + // M<'T> * ('T -> M<'U>) -> M<'U> + [] + member inline _.Bind(value: 'T voption, f: 'T -> 'U option) : 'U option = + match value with + | ValueNone -> None + | ValueSome value -> f value + // 'T * ('T -> M<'U>) -> M<'U> when 'U :> IDisposable [] member _.Using(resource: ('T :> System.IDisposable), body: _ -> _ option) : _ option = @@ -137,6 +144,14 @@ type AsyncMaybeBuilder() = | Some result -> return! f result } + [] + member _.Bind(value: 'T voption, f: 'T -> Async<'U option>) : Async<'U option> = + async { + match value with + | ValueNone -> return None + | ValueSome result -> return! f result + } + [] member _.Using(resource: ('T :> IDisposable), body: 'T -> Async<'U option>) : Async<'U option> = async { diff --git a/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs b/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs index 41dee649d81..2c0475d4d37 100644 --- a/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs +++ b/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs @@ -46,12 +46,12 @@ module internal RoslynHelpers = TextSpan(startPosition, endPosition - startPosition) - let TryFSharpRangeToTextSpan (sourceText: SourceText, range: range) : TextSpan option = + let TryFSharpRangeToTextSpan (sourceText: SourceText, range: range) : TextSpan voption = try - Some(FSharpRangeToTextSpan(sourceText, range)) + ValueSome(FSharpRangeToTextSpan(sourceText, range)) with e -> //Assert.Exception(e) - None + ValueNone let TextSpanToFSharpRange (fileName: string, textSpan: TextSpan, sourceText: SourceText) : range = let startLine = sourceText.Lines.GetLineFromPosition textSpan.Start diff --git a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs index d73b1a61d05..a7e33fbfcb3 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs @@ -33,18 +33,17 @@ type internal FSharpBreakpointResolutionService [] () = sourceText.GetSubText(sourceText.Lines.[textLinePos.Line].Span).ToString() if String.IsNullOrWhiteSpace textInLine then - return None + return ValueNone else let textLineColumn = textLinePos.Character let fcsTextLineNumber = Line.fromZ textLinePos.Line // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpBreakpointResolutionService)) - |> liftAsync - match parseResults with - | Some parseResults -> return parseResults.ValidateBreakpointLocation(mkPos fcsTextLineNumber textLineColumn) - | _ -> return None + let location = parseResults.ValidateBreakpointLocation(mkPos fcsTextLineNumber textLineColumn) + + return ValueOption.ofOption location } interface IFSharpBreakpointResolutionService with @@ -58,14 +57,14 @@ type internal FSharpBreakpointResolutionService [] () = let! range = FSharpBreakpointResolutionService.GetBreakpointLocation(document, textSpan) match range with - | None -> return Unchecked.defaultof<_> - | Some range -> + | ValueNone -> return Unchecked.defaultof<_> + | ValueSome range -> let! sourceText = document.GetTextAsync(cancellationToken) let span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) match span with - | None -> return Unchecked.defaultof<_> - | Some span -> return FSharpBreakpointResolutionResult.CreateSpanResult(document, span) + | ValueNone -> return Unchecked.defaultof<_> + | ValueSome span -> return FSharpBreakpointResolutionResult.CreateSpanResult(document, span) } |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs index aaf2a8a5db1..7947b2dec99 100644 --- a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs +++ b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs @@ -97,8 +97,8 @@ type internal FSharpDocumentHighlightsService [] () = [| for symbolUse in symbolUses do match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with - | None -> () - | Some span -> + | ValueNone -> () + | ValueSome span -> yield { IsDefinition = symbolUse.IsFromDefinition diff --git a/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs b/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs index 11fa356b1f4..1b58774e7a1 100644 --- a/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs +++ b/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs @@ -28,8 +28,8 @@ type internal FSharpBraceMatchingService [] () = let isPositionInRange range = match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) with - | None -> false - | Some span -> + | ValueNone -> false + | ValueSome span -> if forFormatting then let length = position - span.Start length >= 0 && length <= span.Length diff --git a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs index 0cb40be0b10..d63e2d016df 100644 --- a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs +++ b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs @@ -167,10 +167,10 @@ type internal InlineRenameInfo [| for symbolUse in symbolUses do match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse) with - | Some span -> + | ValueSome span -> let textSpan = Tokenizer.fixupSpan (sourceText, span) yield FSharpInlineRenameLocation(document, textSpan) - | None -> () + | ValueNone -> () |] } } @@ -220,8 +220,8 @@ type internal InlineRenameService [] () = let span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) match span with - | None -> return Unchecked.defaultof<_> - | Some span -> + | ValueNone -> return Unchecked.defaultof<_> + | ValueSome span -> let triggerSpan = Tokenizer.fixupSpan (sourceText, span) let result = diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index ce2b568fe8b..e77ce6424b5 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -95,10 +95,10 @@ type internal FSharpWorkspaceServiceFactory [] let getSource filename = async { match workspace.CurrentSolution.TryGetDocumentFromPath filename with - | Some document -> + | ValueSome document -> let! text = document.GetTextAsync() |> Async.AwaitTask return Some(text.ToFSharpSourceText()) - | None -> return None + | ValueNone -> return None } lock gate (fun () -> diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index b567c74a199..9871d042f2f 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -110,12 +110,12 @@ module internal SymbolHelpers = let! otherFileCheckResults = match currentDocument.Project.Solution.TryGetDocumentFromPath otherFile with - | Some doc -> + | ValueSome doc -> cancellableTask { let! _, checkFileResults = doc.GetFSharpParseAndCheckResultsAsync("findReferencedSymbolsAsync") return [ checkFileResults, doc ] } - | None -> CancellableTask.singleton [] + | ValueNone -> CancellableTask.singleton [] let symbolUses = (checkFileResults, currentDocument) :: otherFileCheckResults diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index b8bc19cc4a3..cb93bfe7d4c 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -25,9 +25,9 @@ module private CheckerExtensions = /// Parse the source text from the Roslyn document. member checker.ParseDocument(document: Document, parsingOptions: FSharpParsingOptions, userOpName: string) = - async { - let! ct = Async.CancellationToken - let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask + cancellableTask { + let! ct = CancellableTask.getCancellationToken () + let! sourceText = document.GetTextAsync(ct) return! checker.ParseFile(document.FilePath, sourceText.ToFSharpSourceText(), parsingOptions, userOpName = userOpName) } @@ -209,7 +209,7 @@ type Document with /// Parses the given F# document. member this.GetFSharpParseResultsAsync(userOpName) = - async { + cancellableTask { let! checker, _, parsingOptions, _ = this.GetFSharpCompilationOptionsAsync(userOpName) return! checker.ParseDocument(this, parsingOptions, userOpName) } diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs index 88aad129dc5..ab27f8d92af 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs @@ -33,8 +33,8 @@ module FSharpFindUsagesService = match declarationRange, RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse) with | Some declRange, _ when Range.equals declRange symbolUse -> () - | _, None -> () - | _, Some textSpan -> + | _, ValueNone -> () + | _, ValueSome textSpan -> if allReferences then let definitionItem = if isExternal then @@ -71,10 +71,10 @@ module FSharpFindUsagesService = let! sourceText = doc.GetTextAsync(cancellationToken) match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) with - | Some span -> + | ValueSome span -> let span = Tokenizer.fixupSpan (sourceText, span) return Some(FSharpDocumentSpan(doc, span)) - | None -> return None + | ValueNone -> return None } } |> CancellableTask.whenAll diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 905919f4ce9..2545b367849 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -177,8 +177,8 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let! refSourceText = refDocument.GetTextAsync(cancellationToken) |> Async.AwaitTask match RoslynHelpers.TryFSharpRangeToTextSpan(refSourceText, range) with - | None -> return None - | Some refTextSpan -> return Some(FSharpGoToDefinitionNavigableItem(refDocument, refTextSpan)) + | ValueNone -> return None + | ValueSome refTextSpan -> return Some(FSharpGoToDefinitionNavigableItem(refDocument, refTextSpan)) else return None } @@ -585,7 +585,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let span = match RoslynHelpers.TryFSharpRangeToTextSpan(tmpShownDoc.GetTextAsync(cancellationToken).Result, r) with - | Some span -> span + | ValueSome span -> span | _ -> TextSpan() return span diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index 1107e6f5869..b0bb007f161 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -44,7 +44,7 @@ type internal FSharpNavigateToSearchService [] | true, (version, items) when version = currentVersion -> return items | _ -> let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpNavigateToSearchService)) - let items = parseResults.ParseTree |> NavigateTo.GetNavigableItems + let items = NavigateTo.GetNavigableItems parseResults.ParseTree cache[document.Id] <- currentVersion, items return items } diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs index 9173dd5f863..d40a18e9485 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs @@ -9,6 +9,7 @@ open System.Threading.Tasks open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor open FSharp.Compiler.EditorServices +open CancellableTasks type internal NavigationBarSymbolItem(text, glyph, spans, childItems) = inherit FSharpNavigationBarItem(text, glyph, spans, childItems) @@ -20,10 +21,12 @@ type internal FSharpNavigationBarItemService [] () = interface IFSharpNavigationBarItemService with member _.GetItemsAsync(document, cancellationToken) : Task> = - asyncMaybe { + cancellableTask { + + let! cancellationToken = CancellableTask.getCancellationToken () + let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpNavigationBarItemService)) - |> liftAsync let navItems = Navigation.getNavigation parseResults.ParseTree let! sourceText = document.GetTextAsync(cancellationToken) @@ -31,27 +34,29 @@ type internal FSharpNavigationBarItemService [] () = let rangeToTextSpan range = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) - return - navItems.Declarations - |> Array.choose (fun topLevelDecl -> - rangeToTextSpan (topLevelDecl.Declaration.Range) - |> Option.map (fun topLevelTextSpan -> - let childItems = - topLevelDecl.Nested - |> Array.choose (fun decl -> - rangeToTextSpan (decl.Range) - |> Option.map (fun textSpan -> - NavigationBarSymbolItem(decl.LogicalName, decl.RoslynGlyph, [| textSpan |], null) - :> FSharpNavigationBarItem)) - - NavigationBarSymbolItem( - topLevelDecl.Declaration.LogicalName, - topLevelDecl.Declaration.RoslynGlyph, - [| topLevelTextSpan |], - childItems - ) - :> FSharpNavigationBarItem)) - :> IList<_> - } - |> Async.map (Option.defaultValue emptyResult) - |> RoslynHelpers.StartAsyncAsTask(cancellationToken) + if navItems.Declarations.Length = 0 then + return emptyResult + else + + return + navItems.Declarations + |> Array.chooseV (fun topLevelDecl -> + rangeToTextSpan (topLevelDecl.Declaration.Range) + |> ValueOption.map (fun topLevelTextSpan -> + let childItems = + topLevelDecl.Nested + |> Array.chooseV (fun decl -> + rangeToTextSpan (decl.Range) + |> ValueOption.map (fun textSpan -> + NavigationBarSymbolItem(decl.LogicalName, decl.RoslynGlyph, [| textSpan |], null) + :> FSharpNavigationBarItem)) + + NavigationBarSymbolItem( + topLevelDecl.Declaration.LogicalName, + topLevelDecl.Declaration.RoslynGlyph, + [| topLevelTextSpan |], + childItems + ) + :> FSharpNavigationBarItem)) + :> IList<_> + } |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs index 7fb51b9f83b..6e7df021a2c 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs @@ -64,8 +64,8 @@ type internal FSharpAsyncQuickInfoSource let textSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lexerSymbol.Range) match textSpan with - | None -> return None - | Some textSpan -> + | ValueNone -> return None + | ValueSome textSpan -> let trackingSpan = textBuffer.CurrentSnapshot.CreateTrackingSpan(textSpan.Start, textSpan.Length, SpanTrackingMode.EdgeInclusive) diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs index 59e7288b623..86607965acf 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs @@ -15,51 +15,59 @@ open FSharp.Compiler.Syntax open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeRefactorings open Microsoft.CodeAnalysis.CodeActions +open CancellableTasks [] type internal FSharpChangeDerefToValueRefactoring [] () = inherit CodeRefactoringProvider() override _.ComputeRefactoringsAsync context = - asyncMaybe { + cancellableTask { let document = context.Document - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + let! ct = CancellableTask.getCancellationToken () + let! sourceText = context.Document.GetTextAsync(ct) let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpChangeDerefToValueRefactoring)) - |> liftAsync let selectionRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) - let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos selectionRange.Start - let! exprRange = parseResults.TryRangeOfExpressionBeingDereferencedContainingPos selectionRange.Start + let derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos selectionRange.Start + let exprRange = parseResults.TryRangeOfExpressionBeingDereferencedContainingPos selectionRange.Start - let combinedRange = Range.unionRanges derefRange exprRange - let! combinedSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, combinedRange) + match derefRange, exprRange with + | Some derefRange, Some exprRange -> - let replacementString = - // Trim off the `!` - sourceText.GetSubText(combinedSpan).ToString().[1..] + ".Value" + let combinedRange = Range.unionRanges derefRange exprRange - let title = SR.UseValueInsteadOfDeref() + let combinedSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, combinedRange) - let getChangedText (sourceText: SourceText) = - sourceText.WithChanges(TextChange(combinedSpan, replacementString)) + match combinedSpan with + | ValueNone -> () + | ValueSome combinedSpan -> + let replacementString = + // Trim off the `!` + sourceText.GetSubText(combinedSpan).ToString().[1..] + ".Value" - let codeAction = - CodeAction.Create( - title, - (fun (cancellationToken: CancellationToken) -> - async { - let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask - return context.Document.WithText(getChangedText sourceText) - } - |> RoslynHelpers.StartAsyncAsTask(cancellationToken)), - title - ) + let title = SR.UseValueInsteadOfDeref() - context.RegisterRefactoring(codeAction) + let getChangedText (sourceText: SourceText) = + sourceText.WithChanges(TextChange(combinedSpan, replacementString)) + + let codeAction = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + cancellableTask { + let! sourceText = context.Document.GetTextAsync(cancellationToken) + return context.Document.WithText(getChangedText sourceText) + } + |> CancellableTask.start cancellationToken), + title + ) + + context.RegisterRefactoring(codeAction) + | _ -> () } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + |> CancellableTask.startAsTask context.CancellationToken diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs index d1559708f23..2efeb8bf6e7 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs @@ -15,48 +15,55 @@ open FSharp.Compiler.Syntax open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeRefactorings open Microsoft.CodeAnalysis.CodeActions +open CancellableTasks [] type internal FSharpChangeTypeofWithNameToNameofExpressionRefactoring [] () = inherit CodeRefactoringProvider() override _.ComputeRefactoringsAsync context = - asyncMaybe { + cancellableTask { let document = context.Document - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + let! ct = CancellableTask.getCancellationToken () + let! sourceText = context.Document.GetTextAsync(ct) let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpChangeTypeofWithNameToNameofExpressionRefactoring)) - |> liftAsync let selectionRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) - let! namedTypeOfResults = parseResults.TryRangeOfTypeofWithNameAndTypeExpr(selectionRange.Start) - - let! namedTypeSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, namedTypeOfResults.NamedIdentRange) - let! typeofAndNameSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, namedTypeOfResults.FullExpressionRange) - let namedTypeName = sourceText.GetSubText(namedTypeSpan) - let replacementString = $"nameof({namedTypeName})" - - let title = SR.UseNameof() - - let getChangedText (sourceText: SourceText) = - sourceText.WithChanges(TextChange(typeofAndNameSpan, replacementString)) - - let codeAction = - CodeAction.Create( - title, - (fun (cancellationToken: CancellationToken) -> - async { - let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask - return context.Document.WithText(getChangedText sourceText) - } - |> RoslynHelpers.StartAsyncAsTask(cancellationToken)), - title - ) - - context.RegisterRefactoring(codeAction) - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + let namedTypeOfResults = parseResults.TryRangeOfTypeofWithNameAndTypeExpr(selectionRange.Start) + + match namedTypeOfResults with + | None -> () + | Some namedTypeOfResults -> + + let namedTypeSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, namedTypeOfResults.NamedIdentRange) + let typeofAndNameSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, namedTypeOfResults.FullExpressionRange) + + match namedTypeSpan, typeofAndNameSpan with + | ValueSome namedTypeSpan, ValueSome typeofAndNameSpan -> + + let namedTypeName = sourceText.GetSubText(namedTypeSpan) + let replacementString = $"nameof({namedTypeName})" + + let title = SR.UseNameof() + + let getChangedText (sourceText: SourceText) = + sourceText.WithChanges(TextChange(typeofAndNameSpan, replacementString)) + + let codeAction = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + cancellableTask { + let! sourceText = context.Document.GetTextAsync(cancellationToken) + return context.Document.WithText(getChangedText sourceText) + } |> CancellableTask.start cancellationToken), + title + ) + + context.RegisterRefactoring(codeAction) + | _ -> () + } |> CancellableTask.startAsTask context.CancellationToken diff --git a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs index ac35e69af22..ca11623b55e 100644 --- a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs +++ b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs @@ -128,7 +128,7 @@ module internal BlockStructure = let hintSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, scopeRange.Range) match textSpan, hintSpan with - | Some textSpan, Some hintSpan -> + | ValueSome textSpan, ValueSome hintSpan -> let line = sourceText.Lines.GetLineFromPosition textSpan.Start let bannerText = From f1778eb54d593094604f8375287d8c6acf279ffe Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Fri, 8 Sep 2023 16:43:55 +0200 Subject: [PATCH 02/20] wip --- .../FSharp.Editor/Common/CancellableTasks.fs | 7 +- .../src/FSharp.Editor/Common/Extensions.fs | 80 ++- .../src/FSharp.Editor/Common/Pervasive.fs | 2 +- .../LanguageService/WorkspaceExtensions.fs | 6 +- .../Navigation/FindDefinitionService.fs | 10 +- .../Navigation/GoToDefinition.fs | 556 ++++++++++-------- .../Navigation/GoToDefinitionService.fs | 11 +- .../Navigation/NavigateToSearchService.fs | 71 ++- .../Refactor/AddExplicitTypeToParameter.fs | 164 +++--- .../BreakpointResolutionServiceTests.fs | 4 +- .../FindReferencesTests.fs | 8 +- 11 files changed, 555 insertions(+), 364 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs index 6e703fc2c59..7d5ed3205d2 100644 --- a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs +++ b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs @@ -451,13 +451,13 @@ module CancellableTasks = /// /// Builds a cancellableTask using computation expression syntax. - /// Default behaviour when binding (v)options is to return a cacnelled task. + /// Default behaviour when binding (v)options is to return a cancelled task. /// let foregroundCancellableTask = CancellableTaskBuilder(false) /// /// Builds a cancellableTask using computation expression syntax which switches to execute on a background thread if not already doing so. - /// Default behaviour when binding (v)options is to return a cacnelled task. + /// Default behaviour when binding (v)options is to return a cancelled task. /// let cancellableTask = CancellableTaskBuilder(true) @@ -1105,8 +1105,7 @@ module CancellableTasks = return! Task.WhenAll (seq { for task in tasks do yield startTask ct task }) } - let inline ignore (ctask: CancellableTask<_>) = - ctask |> toUnit + let inline ignore ([] ctask: CancellableTask<_>) = toUnit ctask /// [] diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 3c9d54f4261..6cb1246d21d 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -299,9 +299,65 @@ module ValueOption = | _ -> None [] +module IEnumerator = + let chooseV f (e: IEnumerator<'T>) = + let mutable started = false + let mutable curr = None + + let get () = + if not started then + raise(InvalidOperationException("Not started")) + + match curr with + | None -> + raise(InvalidOperationException("Already finished")) + | Some x -> x + + + { new IEnumerator<'U> with + member _.Current = get () + interface System.Collections.IEnumerator with + member _.Current = box (get ()) + + member _.MoveNext() = + if not started then + started <- true + + curr <- None + + while (curr.IsNone && e.MoveNext()) do + curr <- f e.Current + + Option.isSome curr + + member _.Reset() = + raise(NotSupportedException("Reset is not supported")) + interface System.IDisposable with + member _.Dispose() = + e.Dispose() + } +[] module Seq = - let toImmutableArray (xs: seq<'a>) : ImmutableArray<'a> = xs.ToImmutableArray() + let mkSeq f = + { new IEnumerable<'U> with + member _.GetEnumerator() = f() + + interface System.Collections.IEnumerable with + member _.GetEnumerator() = (f() :> System.Collections.IEnumerator) } + + let inline revamp f (ie: seq<_>) = + mkSeq (fun () -> f (ie.GetEnumerator())) + + let inline toImmutableArray (xs: seq<'a>) : ImmutableArray<'a> = xs.ToImmutableArray() + + let inline tryHeadV (source: seq<_>) = + use e = source.GetEnumerator() + + if (e.MoveNext()) then + ValueSome e.Current + else + ValueNone let inline tryFindV ([] predicate) (source: seq<'T>) = use e = source.GetEnumerator() @@ -326,6 +382,18 @@ module Seq = loop 0 + let inline tryPickV ([] chooser) (source: seq<'T>) = + use e = source.GetEnumerator() + let mutable res = ValueNone + + while (ValueOption.isNone res && e.MoveNext()) do + res <- chooser e.Current + + res + + let chooseV chooser source = + revamp (IEnumerator.chooseV chooser) source + [] module Array = let inline foldi ([] folder: 'State -> int -> 'T -> 'State) (state: 'State) (xs: 'T[]) = @@ -340,6 +408,12 @@ module Array = let toImmutableArray (xs: 'T[]) = xs.ToImmutableArray() + let inline tryHeadV (array: _[]) = + if array.Length = 0 then + ValueNone + else + ValueSome array[0] + let inline tryFindV ([] predicate) (array: _[]) = let rec loop i = @@ -420,6 +494,10 @@ module ImmutableArray = else ValueSome xs[0] + let inline empty<'T> = ImmutableArray<'T>.Empty + + let inline create<'T> (x: 'T) = ImmutableArray.Create<'T>(x) + [] module List = let rec tryFindV predicate list = diff --git a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs index 57f78491e9c..0ad2b8ce8c7 100644 --- a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs +++ b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs @@ -6,7 +6,7 @@ open System.IO open System.Diagnostics /// Checks if the filePath ends with ".fsi" -let isSignatureFile (filePath: string) = +let inline isSignatureFile (filePath: string) = String.Equals(Path.GetExtension filePath, ".fsi", StringComparison.OrdinalIgnoreCase) /// Returns the corresponding signature file path for given implementation file path or vice versa diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index cb93bfe7d4c..77d89a7d7a0 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -258,10 +258,10 @@ type Document with /// Try to find a F# lexer/token symbol of the given F# document and position. member this.TryFindFSharpLexerSymbolAsync(position, lookupKind, wholeActivePattern, allowStringToken, userOpName) = - async { + cancellableTask { let! defines, langVersion, strictIndentation = this.GetFsharpParsingOptionsAsync(userOpName) - let! ct = Async.CancellationToken - let! sourceText = this.GetTextAsync(ct) |> Async.AwaitTask + let! ct = CancellableTask.getCancellationToken () + let! sourceText = this.GetTextAsync(ct) return Tokenizer.getSymbolAtPosition ( diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs index 1864e99c508..0013da2d154 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs @@ -10,15 +10,15 @@ open FSharp.Compiler.Text.Range open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.ExternalAccess.FSharp.GoToDefinition -open System.Collections.Immutable -open System.Threading.Tasks +open CancellableTasks [)>] [)>] type internal FSharpFindDefinitionService [] (metadataAsSource: FSharpMetadataAsSourceService) = interface IFSharpFindDefinitionService with member _.FindDefinitionsAsync(document: Document, position: int, cancellationToken: CancellationToken) = - let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup) + cancellableTask { + let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup) + return! navigation.FindDefinitionsAsync(position) + } |> CancellableTask.start cancellationToken - let definitions = navigation.FindDefinitions(position, cancellationToken) // TODO: probably will need to be async all the way down - ImmutableArray.CreateRange(definitions) |> Task.FromResult diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 2545b367849..74429229bdd 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -185,54 +185,79 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = /// Helper function that is used to determine the navigation strategy to apply, can be tuned towards signatures or implementation files. member private _.FindSymbolHelper(originDocument: Document, originRange: range, sourceText: SourceText, preferSignature: bool) = - asyncMaybe { - let userOpName = "FindSymbolHelper" - let! originTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, originRange) - let position = originTextSpan.Start - let! lexerSymbol = originDocument.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, userOpName) - let textLinePos = sourceText.Lines.GetLinePosition position - let fcsTextLineNumber = Line.fromZ textLinePos.Line - let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() - let idRange = lexerSymbol.Ident.idRange + let originTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, originRange) + match originTextSpan with + | ValueNone -> + CancellableTask.singleton None + | ValueSome originTextSpan -> + cancellableTask { + let userOpName = "FindSymbolHelper" - let! ct = Async.CancellationToken |> liftAsync + let position = originTextSpan.Start + let! lexerSymbol = originDocument.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, userOpName) - let! _, checkFileResults = - originDocument.GetFSharpParseAndCheckResultsAsync(nameof (GoToDefinition)) - |> CancellableTask.start ct - |> Async.AwaitTask - |> liftAsync + match lexerSymbol with + | None -> return None + | Some lexerSymbol -> + let textLinePos = sourceText.Lines.GetLinePosition position + let fcsTextLineNumber = Line.fromZ textLinePos.Line + let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() + let idRange = lexerSymbol.Ident.idRange - let! fsSymbolUse = - checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland) + let! ct = CancellableTask.getCancellationToken () - let symbol = fsSymbolUse.Symbol - // if the tooltip was spawned in an implementation file and we have a range targeting - // a signature file, try to find the corresponding implementation file and target the - // desired symbol - if isSignatureFile fsSymbolUse.FileName && preferSignature = false then - let fsfilePath = Path.ChangeExtension(originRange.FileName, "fs") + let! _, checkFileResults = + originDocument.GetFSharpParseAndCheckResultsAsync(nameof (GoToDefinition)) - if not (File.Exists fsfilePath) then - return! None - else - let! implDoc = originDocument.Project.Solution.TryGetDocumentFromPath fsfilePath - let! implSourceText = implDoc.GetTextAsync() + let fsSymbolUse = + checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland) - let! _, checkFileResults = - implDoc.GetFSharpParseAndCheckResultsAsync(userOpName) - |> CancellableTask.start ct - |> Async.AwaitTask - |> liftAsync - - let symbolUses = checkFileResults.GetUsesOfSymbolInFile symbol - let! implSymbol = symbolUses |> Array.tryHead - let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, implSymbol.Range) - return FSharpGoToDefinitionNavigableItem(implDoc, implTextSpan) - else - let! targetDocument = originDocument.Project.Solution.TryGetDocumentFromFSharpRange fsSymbolUse.Range - return! rangeToNavigableItem (fsSymbolUse.Range, targetDocument) - } + match fsSymbolUse with + | None -> return None + | Some fsSymbolUse -> + + let symbol = fsSymbolUse.Symbol + // if the tooltip was spawned in an implementation file and we have a range targeting + // a signature file, try to find the corresponding implementation file and target the + // desired symbol + if isSignatureFile fsSymbolUse.FileName && preferSignature = false then + let fsfilePath = Path.ChangeExtension(originRange.FileName, "fs") + + if not (File.Exists fsfilePath) then + return None + else + let implDoc = originDocument.Project.Solution.TryGetDocumentFromPath fsfilePath + + match implDoc with + | ValueNone -> return None + | ValueSome implDoc -> + let! implSourceText = implDoc.GetTextAsync(ct) + + let! _, checkFileResults = + implDoc.GetFSharpParseAndCheckResultsAsync(userOpName) + + let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbol, ct) + + let implSymbol = Array.tryHeadV symbolUses + + match implSymbol with + | ValueNone -> return None + | ValueSome implSymbol -> + let implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, implSymbol.Range) + + match implTextSpan with + | ValueNone -> return None + | ValueSome implTextSpan -> + return Some(FSharpGoToDefinitionNavigableItem(implDoc, implTextSpan)) + else + let targetDocument = originDocument.Project.Solution.TryGetDocumentFromFSharpRange fsSymbolUse.Range + + match targetDocument with + | None -> return None + | Some targetDocument -> + let! navItem = rangeToNavigableItem (fsSymbolUse.Range, targetDocument) + return navItem + } /// if the symbol is defined in the given file, return its declaration location, otherwise use the targetSymbol to find the first /// instance of its presence in the provided source file. The first case is needed to return proper declaration location for @@ -256,9 +281,10 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = return implSymbol.Range } - member private this.FindDefinitionAtPosition(originDocument: Document, position: int, cancellationToken: CancellationToken) = - asyncMaybe { + member internal this.FindDefinitionAtPosition(originDocument: Document, position: int) = + cancellableTask { let userOpName = "FindDefinitionAtPosition" + let! cancellationToken = CancellableTask.getCancellationToken () let! sourceText = originDocument.GetTextAsync(cancellationToken) let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position @@ -266,131 +292,179 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let fcsTextLineNumber = Line.fromZ textLinePos.Line let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() - let! ct = Async.CancellationToken |> liftAsync + let! cancellationToken = CancellableTask.getCancellationToken () let preferSignature = isSignatureFile originDocument.FilePath let! lexerSymbol = originDocument.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, userOpName) - let idRange = lexerSymbol.Ident.idRange - - let! _, checkFileResults = - originDocument.GetFSharpParseAndCheckResultsAsync(userOpName) - |> CancellableTask.start ct - |> Async.AwaitTask - |> liftAsync - - let declarations = - checkFileResults.GetDeclarationLocation( - fcsTextLineNumber, - lexerSymbol.Ident.idRange.EndColumn, - textLineString, - lexerSymbol.FullIsland, - preferSignature - ) + + match lexerSymbol with + | None -> return ValueNone + | Some lexerSymbol -> + + let idRange = lexerSymbol.Ident.idRange - let! targetSymbolUse = - checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland) - - match declarations with - | FindDeclResult.ExternalDecl (assembly, targetExternalSym) -> - let projectOpt = - originDocument.Project.Solution.Projects - |> Seq.tryFindV (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase)) - - match projectOpt with - | ValueSome project -> - let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, (fun _ -> true)) - - let roslynSymbols = - symbols |> Seq.collect ExternalSymbol.ofRoslynSymbol |> Array.ofSeq - - let! symbol = - roslynSymbols - |> Seq.tryPick (fun (sym, externalSym) -> if externalSym = targetExternalSym then Some sym else None) - - let! location = symbol.Locations |> Seq.tryHead - - return - (FSharpGoToDefinitionResult.NavigableItem( - FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan) - ), - idRange) - | _ -> - let metadataReferences = originDocument.Project.MetadataReferences - return (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange) - - | FindDeclResult.DeclFound targetRange -> - // If the file is not associated with a document, it's considered external. - if not (originDocument.Project.Solution.ContainsDocumentWithFilePath(targetRange.FileName)) then - let metadataReferences = originDocument.Project.MetadataReferences - return (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange) - else - // if goto definition is called at we are alread at the declaration location of a symbol in - // either a signature or an implementation file then we jump to it's respective postion in thethe - if - lexerSymbol.Range = targetRange - then - // jump from signature to the corresponding implementation - if isSignatureFile originDocument.FilePath then - let implFilePath = Path.ChangeExtension(originDocument.FilePath, "fs") - - if not (File.Exists implFilePath) then - return! None - else - let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath - - let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument) - let! implSourceText = implDocument.GetTextAsync(cancellationToken) |> liftTaskAsync - let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange) - let navItem = FSharpGoToDefinitionNavigableItem(implDocument, implTextSpan) - return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) - else // jump from implementation to the corresponding signature - let declarations = - checkFileResults.GetDeclarationLocation( - fcsTextLineNumber, - lexerSymbol.Ident.idRange.EndColumn, - textLineString, - lexerSymbol.FullIsland, - true - ) - - match declarations with - | FindDeclResult.DeclFound targetRange -> - let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName - let! sigSourceText = sigDocument.GetTextAsync(cancellationToken) |> liftTaskAsync - let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sigSourceText, targetRange) - let navItem = FSharpGoToDefinitionNavigableItem(sigDocument, sigTextSpan) - return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) - | _ -> return! None - // when the target range is different follow the navigation convention of - // - gotoDefn origin = signature , gotoDefn destination = signature - // - gotoDefn origin = implementation, gotoDefn destination = implementation - else - let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName - let! sigSourceText = sigDocument.GetTextAsync(cancellationToken) |> liftTaskAsync - 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 = FSharpGoToDefinitionNavigableItem(sigDocument, sigTextSpan) - return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) - else // we need to get an FSharpSymbol from the targetRange found in the signature - // that symbol will be used to find the destination in the corresponding implementation file - let implFilePath = - // Bugfix: apparently sigDocument not always is a signature file - if isSignatureFile sigDocument.FilePath then - Path.ChangeExtension(sigDocument.FilePath, "fs") - else - sigDocument.FilePath - - let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath - - let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument) + let! _, checkFileResults = + originDocument.GetFSharpParseAndCheckResultsAsync(userOpName) + + let declarations = + checkFileResults.GetDeclarationLocation( + fcsTextLineNumber, + idRange.EndColumn, + textLineString, + lexerSymbol.FullIsland, + preferSignature + ) - let! implSourceText = implDocument.GetTextAsync() |> liftTaskAsync - let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange) - let navItem = FSharpGoToDefinitionNavigableItem(implDocument, implTextSpan) - return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) - | _ -> return! None + let targetSymbolUse = + checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland) + + match targetSymbolUse with + | None -> return ValueNone + | Some targetSymbolUse -> + + match declarations with + | FindDeclResult.ExternalDecl (assembly, targetExternalSym) -> + let projectOpt = + originDocument.Project.Solution.Projects + |> Seq.tryFindV (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase)) + + match projectOpt with + | ValueSome project -> + let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, (fun _ -> true), cancellationToken) + + let roslynSymbols = + Seq.collect ExternalSymbol.ofRoslynSymbol symbols + + let symbol = + Seq.tryPickV (fun (sym, externalSym) -> if externalSym = targetExternalSym then ValueSome sym else ValueNone) roslynSymbols + + let location = + symbol + |> ValueOption.map _.Locations + |> ValueOption.bind Seq.tryHeadV + + match location with + | ValueNone -> return ValueNone + | ValueSome location -> + + return + ValueSome ( + FSharpGoToDefinitionResult.NavigableItem( + FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan) + ), idRange) + | _ -> + let metadataReferences = originDocument.Project.MetadataReferences + return ValueSome (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange) + + | FindDeclResult.DeclFound targetRange -> + // If the file is not associated with a document, it's considered external. + if not (originDocument.Project.Solution.ContainsDocumentWithFilePath(targetRange.FileName)) then + let metadataReferences = originDocument.Project.MetadataReferences + return ValueSome (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange) + else + // if goto definition is called at we are alread at the declaration location of a symbol in + // either a signature or an implementation file then we jump to it's respective postion in thethe + if + lexerSymbol.Range = targetRange + then + // jump from signature to the corresponding implementation + if isSignatureFile originDocument.FilePath then + let implFilePath = Path.ChangeExtension(originDocument.FilePath, "fs") + + if not (File.Exists implFilePath) then + return ValueNone + else + let implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath + + match implDocument with + | ValueNone -> return ValueNone + | ValueSome implDocument -> + let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument) + + match targetRange with + | None -> return ValueNone + | Some targetRange -> + let! implSourceText = implDocument.GetTextAsync(cancellationToken) + let implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange) + + match implTextSpan with + | ValueNone -> return ValueNone + | ValueSome implTextSpan -> + let navItem = FSharpGoToDefinitionNavigableItem(implDocument, implTextSpan) + return ValueSome(FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) + + else // jump from implementation to the corresponding signature + let declarations = + checkFileResults.GetDeclarationLocation( + fcsTextLineNumber, + idRange.EndColumn, + textLineString, + lexerSymbol.FullIsland, + true + ) + + match declarations with + | FindDeclResult.DeclFound targetRange -> + let sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName + + match sigDocument with + | ValueNone -> return ValueNone + | ValueSome sigDocument -> + let! sigSourceText = sigDocument.GetTextAsync(cancellationToken) + + let sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sigSourceText, targetRange) + + match sigTextSpan with + | ValueNone -> return ValueNone + | ValueSome sigTextSpan -> + let navItem = FSharpGoToDefinitionNavigableItem(sigDocument, sigTextSpan) + return ValueSome(FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) + | _ -> return ValueNone + // when the target range is different follow the navigation convention of + // - gotoDefn origin = signature , gotoDefn destination = signature + // - gotoDefn origin = implementation, gotoDefn destination = implementation + else + let sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName + match sigDocument with + | ValueNone -> return ValueNone + | ValueSome sigDocument -> + let! sigSourceText = sigDocument.GetTextAsync(cancellationToken) + let sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sigSourceText, targetRange) + match sigTextSpan with + | ValueNone -> return ValueNone + | ValueSome sigTextSpan -> + // 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 = FSharpGoToDefinitionNavigableItem(sigDocument, sigTextSpan) + return ValueSome(FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) + else // we need to get an FSharpSymbol from the targetRange found in the signature + // that symbol will be used to find the destination in the corresponding implementation file + let implFilePath = + // Bugfix: apparently sigDocument not always is a signature file + if isSignatureFile sigDocument.FilePath then + Path.ChangeExtension(sigDocument.FilePath, "fs") + else + sigDocument.FilePath + + let implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath + + match implDocument with + | ValueNone -> return ValueNone + | ValueSome implDocument -> + let! targetRange = this.FindSymbolDeclarationInDocument(targetSymbolUse, implDocument) + + match targetRange with + | None -> return ValueNone + | Some targetRange -> + let! implSourceText = implDocument.GetTextAsync(cancellationToken) + let implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange) + match implTextSpan with + | ValueNone -> return ValueNone + | ValueSome implTextSpan -> + let navItem = FSharpGoToDefinitionNavigableItem(implDocument, implTextSpan) + return ValueSome(FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) + | _ -> return ValueNone } /// find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition @@ -401,16 +475,11 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = member this.FindDefinitionOfSymbolAtRange(targetDocument: Document, symbolRange: range, targetSourceText: SourceText) = this.FindSymbolHelper(targetDocument, symbolRange, targetSourceText, preferSignature = false) - member this.FindDefinitionsForPeekTask(originDocument: Document, position: int, cancellationToken: CancellationToken) = - this.FindDefinitionAtPosition(originDocument, position, cancellationToken) - |> Async.map (Option.toArray >> Array.toSeq) - |> RoslynHelpers.StartAsyncAsTask cancellationToken - /// Construct a task that will return a navigation target for the implementation definition of the symbol /// at the provided position in the document. member this.FindDefinitionTask(originDocument: Document, position: int, cancellationToken: CancellationToken) = - this.FindDefinitionAtPosition(originDocument, position, cancellationToken) - |> RoslynHelpers.StartAsyncAsTask cancellationToken + this.FindDefinitionAtPosition(originDocument, position) + |> CancellableTask.start cancellationToken /// Navigate to the positon of the textSpan in the provided document /// used by quickinfo link navigation when the tooltip contains the correct destination range. @@ -452,17 +521,24 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = else statusBar.TempMessage(SR.CannotNavigateUnknown()) + result + /// Find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition member this.NavigateToSymbolDeclarationAsync ( targetDocument: Document, targetSourceText: SourceText, - symbolRange: range, - cancellationToken: CancellationToken + symbolRange: range ) = - asyncMaybe { + cancellableTask { let! item = this.FindDeclarationOfSymbolAtRange(targetDocument, symbolRange, targetSourceText) - return this.NavigateToItem(item, cancellationToken) + + match item with + | None -> + return false + | Some item -> + let! cancellationToken = CancellableTask.getCancellationToken () + return this.NavigateToItem(item, cancellationToken) } /// Find the definition location (implementation file/.fs) of the target symbol @@ -470,12 +546,16 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = ( targetDocument: Document, targetSourceText: SourceText, - symbolRange: range, - cancellationToken: CancellationToken + symbolRange: range ) = - asyncMaybe { + cancellableTask { let! item = this.FindDefinitionOfSymbolAtRange(targetDocument, symbolRange, targetSourceText) - return this.NavigateToItem(item, cancellationToken) + match item with + | None -> + return false + | Some item -> + let! cancellationToken = CancellableTask.getCancellationToken () + return this.NavigateToItem(item, cancellationToken) } member this.NavigateToExternalDeclaration @@ -623,33 +703,40 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, ThreadHelper.JoinableTaskFactory.Run( SR.NavigatingTo(), (fun _progress cancellationToken -> - Async.StartImmediateAsTask( - asyncMaybe { - let! targetDoc = solution.TryGetDocumentFromFSharpRange(range, initialDoc.Project.Id) + cancellableTask { + let targetDoc = solution.TryGetDocumentFromFSharpRange(range, initialDoc.Project.Id) + + match targetDoc with + | None -> () + | Some targetDoc -> + + let! cancellationToken = CancellableTask.getCancellationToken () + let! targetSource = targetDoc.GetTextAsync(cancellationToken) - let! targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(targetSource, range) - let gtd = GoToDefinition(metadataAsSource) - - // Whenever possible: - // - signature files (.fsi) should navigate to other signature files - // - implementation files (.fs) should navigate to other implementation files - if isSignatureFile initialDoc.FilePath then - // Target range will point to .fsi file if only there is one so we can just use Roslyn navigation service. - return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken) - else - // Navigation request was made in a .fs file, so we try to find the implmentation of the symbol at target range. - // This is the part that may take some time, because of type checks involved. - let! result = - gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range, cancellationToken) - |> liftAsync - - if result.IsNone then - // In case the above fails, we just navigate to target range. - return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken) - } - |> Async.Ignore, - cancellationToken - )), + let targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(targetSource, range) + match targetTextSpan with + | ValueNone -> () + | ValueSome targetTextSpan -> + + let gtd = GoToDefinition(metadataAsSource) + + // Whenever possible: + // - signature files (.fsi) should navigate to other signature files + // - implementation files (.fs) should navigate to other implementation files + if isSignatureFile initialDoc.FilePath then + // Target range will point to .fsi file if only there is one so we can just use Roslyn navigation service. + do gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken) + else + // Navigation request was made in a .fs file, so we try to find the implmentation of the symbol at target range. + // This is the part that may take some time, because of type checks involved. + let! result = + gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range) + + if not result then + // In case the above fails, we just navigate to target range. + do gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken) + } |> CancellableTask.start cancellationToken + ), // Default wait time before VS shows the dialog allowing to cancel the long running task is 2 seconds. // This seems a bit too long to leave the user without any feedback, so we shorten it to 1 second. // Note: it seems anything less than 1 second will get rounded down to zero, resulting in flashing dialog @@ -659,17 +746,16 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, with :? OperationCanceledException -> () - member _.FindDefinitions(position, cancellationToken) = - let gtd = GoToDefinition(metadataAsSource) - let task = gtd.FindDefinitionsForPeekTask(initialDoc, position, cancellationToken) - task.Wait(cancellationToken) - let results = task.Result + member _.FindDefinitionsAsync(position) = + cancellableTask { + let gtd = GoToDefinition(metadataAsSource) + let! result = gtd.FindDefinitionAtPosition(initialDoc, position) - results - |> Seq.choose (fun (result, _) -> - match result with - | FSharpGoToDefinitionResult.NavigableItem (navItem) -> Some navItem - | _ -> None) + return + match result with + | ValueSome (FSharpGoToDefinitionResult.NavigableItem (navItem), _) -> ImmutableArray.create navItem + | _ -> ImmutableArray.empty + } member _.TryGoToDefinition(position, cancellationToken) = let gtd = GoToDefinition(metadataAsSource) @@ -739,24 +825,32 @@ type internal DocCommentId = type FSharpNavigableLocation(metadataAsSource: FSharpMetadataAsSourceService, symbolRange: range, project: Project) = interface IFSharpNavigableLocation with member _.NavigateToAsync(_options: FSharpNavigationOptions2, cancellationToken: CancellationToken) : Task = - asyncMaybe { + cancellableTask { let targetPath = symbolRange.FileName - let! targetDoc = project.Solution.TryGetDocumentFromFSharpRange(symbolRange, project.Id) - let! targetSource = targetDoc.GetTextAsync(cancellationToken) - let gtd = GoToDefinition(metadataAsSource) - - let (|Signature|Implementation|) filepath = - if isSignatureFile filepath then - Signature - else - Implementation - - match targetPath with - | Signature -> return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, symbolRange, cancellationToken) - | Implementation -> return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, symbolRange, cancellationToken) - } - |> Async.map Option.isSome - |> RoslynHelpers.StartAsyncAsTask cancellationToken + + let! cancellationToken = CancellableTask.getCancellationToken () + + let targetDoc = project.Solution.TryGetDocumentFromFSharpRange(symbolRange, project.Id) + + match targetDoc with + | None -> + return false + | Some targetDoc -> + let! targetSource = targetDoc.GetTextAsync(cancellationToken) + let gtd = GoToDefinition(metadataAsSource) + + let (|Signature|Implementation|) filepath = + if isSignatureFile filepath then + Signature + else + Implementation + + match targetPath with + | Signature -> + return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, symbolRange) + | Implementation -> + return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, symbolRange) + } |> CancellableTask.start cancellationToken [)>] [)>] diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index a3a800c9ef6..1cea931c17d 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -4,12 +4,13 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition open System.Threading -open System.Threading.Tasks open FSharp.Compiler.Text.Range open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor +open CancellableTasks +open System.Collections.Generic [)>] [)>] @@ -18,9 +19,11 @@ type internal FSharpGoToDefinitionService [] (metadataAsSo interface IFSharpGoToDefinitionService with /// Invoked with Peek Definition. member _.FindDefinitionsAsync(document: Document, position: int, cancellationToken: CancellationToken) = - let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup) - - navigation.FindDefinitions(position, cancellationToken) |> Task.FromResult + cancellableTask { + let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup) + let! res = navigation.FindDefinitionsAsync(position) + return (res :> IEnumerable<_>) + } |> CancellableTask.start cancellationToken /// Invoked with Go to Definition. /// Try to navigate to the definiton of the symbol at the symbolRange in the originDocument diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index b0bb007f161..07a3e52f8bc 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -135,46 +135,50 @@ type internal FSharpNavigateToSearchService [] if item.NeedsBackticks then match name.IndexOf(searchPattern, StringComparison.CurrentCultureIgnoreCase) with - | i when i > 0 -> PatternMatch(PatternMatchKind.Substring, false, false) |> Some - | 0 when name.Length = searchPattern.Length -> PatternMatch(PatternMatchKind.Exact, false, false) |> Some - | 0 -> PatternMatch(PatternMatchKind.Prefix, false, false) |> Some - | _ -> None + | i when i > 0 -> ValueSome(PatternMatch(PatternMatchKind.Substring, false, false)) + | 0 when name.Length = searchPattern.Length -> ValueSome(PatternMatch(PatternMatchKind.Exact, false, false)) + | 0 -> ValueSome(PatternMatch(PatternMatchKind.Prefix, false, false)) + | _ -> ValueNone else // full name with dots allows for path matching, e.g. // "f.c.so.elseif" will match "Fantomas.Core.SyntaxOak.ElseIfNode" - patternMatcher.TryMatch $"{item.Container.FullName}.{name}" |> Option.ofNullable + patternMatcher.TryMatch $"{item.Container.FullName}.{name}" |> ValueOption.ofNullable - let processDocument (tryMatch: NavigableItem -> PatternMatch option) (kinds: IImmutableSet) (document: Document) = + let processDocument (tryMatch: NavigableItem -> PatternMatch voption) (kinds: IImmutableSet) (document: Document) = cancellableTask { let! ct = CancellableTask.getCancellationToken () let! sourceText = document.GetTextAsync ct - let processItem (item: NavigableItem) = - asyncMaybe { // TODO: make a flat cancellable task - - do! Option.guard (kinds.Contains(navigateToItemKindToRoslynKind item.Kind)) - - let! m = tryMatch item - - let! sourceSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) - let glyph = navigateToItemKindToGlyph item.Kind - let kind = navigateToItemKindToRoslynKind item.Kind - let additionalInfo = formatInfo item.Container document - - return - FSharpNavigateToSearchResult( - additionalInfo, - kind, - patternMatchKindToNavigateToMatchKind m.Kind, - item.Name, - FSharpNavigableItem(glyph, ImmutableArray.Create(TaggedText(TextTags.Text, item.Name)), document, sourceSpan) - ) - } - let! items = getNavigableItems document - let! processed = items |> Seq.map processItem |> Async.Parallel - return processed |> Array.choose id + + let processed = + [| + for item in items do + let contains = kinds.Contains (navigateToItemKindToRoslynKind item.Kind) + let patternMatch = tryMatch item + match contains, patternMatch with + | true, ValueSome m -> + let sourceSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) + match sourceSpan with + | ValueNone -> () + | ValueSome sourceSpan -> + let glyph = navigateToItemKindToGlyph item.Kind + let kind = navigateToItemKindToRoslynKind item.Kind + let additionalInfo = formatInfo item.Container document + + yield + FSharpNavigateToSearchResult( + additionalInfo, + kind, + patternMatchKindToNavigateToMatchKind m.Kind, + item.Name, + FSharpNavigableItem(glyph, ImmutableArray.Create(TaggedText(TextTags.Text, item.Name)), document, sourceSpan) + ) + | _ -> () + |] + + return processed } interface IFSharpNavigateToSearchService with @@ -190,7 +194,10 @@ type internal FSharpNavigateToSearchService [] let tryMatch = createMatcherFor searchPattern let tasks = - Seq.map (fun doc -> processDocument tryMatch kinds doc) project.Documents + [| + for doc in project.Documents do + yield processDocument tryMatch kinds doc + |] let! results = CancellableTask.whenAll tasks @@ -214,7 +221,7 @@ type internal FSharpNavigateToSearchService [] ) : Task> = cancellableTask { let! result = processDocument (createMatcherFor searchPattern) kinds document - return result |> Array.toImmutableArray + return Array.toImmutableArray result } |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs index a018bb52558..527da6f83e7 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs @@ -6,7 +6,6 @@ open System open System.Composition open System.Threading -open FSharp.Compiler open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols open FSharp.Compiler.Text @@ -22,16 +21,18 @@ type internal FSharpAddExplicitTypeToParameterRefactoring [ liftTaskAsync + + 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! ct = Async.CancellationToken |> liftAsync - let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync( position, @@ -41,79 +42,88 @@ type internal FSharpAddExplicitTypeToParameterRefactoring [ CancellableTask.start ct - |> Async.AwaitTask - |> liftAsync - - let! symbolUse = - checkFileResults.GetSymbolUseAtLocation( - fcsTextLineNumber, - lexerSymbol.Ident.idRange.EndColumn, - textLine.ToString(), - lexerSymbol.FullIsland - ) + match lexerSymbol with + | None -> return () + | Some lexerSymbol -> + + let! parseFileResults, checkFileResults = + document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpAddExplicitTypeToParameterRefactoring)) - let isValidParameterWithoutTypeAnnotation (funcOrValue: FSharpMemberOrFunctionOrValue) (symbolUse: FSharpSymbolUse) = - let isLambdaIfFunction = - funcOrValue.IsFunction - && parseFileResults.IsBindingALambdaAtPosition symbolUse.Range.Start - - (funcOrValue.IsValue || isLambdaIfFunction) - && parseFileResults.IsPositionContainedInACurriedParameter symbolUse.Range.Start - && not (parseFileResults.IsTypeAnnotationGivenAtPosition symbolUse.Range.Start) - && not funcOrValue.IsMember - && not funcOrValue.IsMemberThisValue - && not funcOrValue.IsConstructorThisValue - && not (PrettyNaming.IsOperatorDisplayName funcOrValue.DisplayName) - - match symbolUse.Symbol with - | :? FSharpMemberOrFunctionOrValue as v when isValidParameterWithoutTypeAnnotation v symbolUse -> - let typeString = v.FullType.FormatWithConstraints symbolUse.DisplayContext - let title = SR.AddTypeAnnotation() - - let! symbolSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) - - let alreadyWrappedInParens = - let rec leftLoop ch pos = - if not (Char.IsWhiteSpace(ch)) then - ch = '(' - else - leftLoop sourceText.[pos - 1] (pos - 1) - - let rec rightLoop ch pos = - if not (Char.IsWhiteSpace(ch)) then - ch = ')' - else - rightLoop sourceText.[pos + 1] (pos + 1) - - let hasLeftParen = leftLoop sourceText.[symbolSpan.Start - 1] (symbolSpan.Start - 1) - let hasRightParen = rightLoop sourceText.[symbolSpan.End] symbolSpan.End - hasLeftParen && hasRightParen - - let getChangedText (sourceText: SourceText) = - if alreadyWrappedInParens then - sourceText.WithChanges(TextChange(TextSpan(symbolSpan.End, 0), ": " + typeString)) - else - sourceText - .WithChanges(TextChange(TextSpan(symbolSpan.Start, 0), "(")) - .WithChanges(TextChange(TextSpan(symbolSpan.End + 1, 0), ": " + typeString + ")")) - - let codeAction = - CodeAction.Create( - title, - (fun (cancellationToken: CancellationToken) -> - async { - let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask - return context.Document.WithText(getChangedText sourceText) - } - |> RoslynHelpers.StartAsyncAsTask(cancellationToken)), - title + let symbolUse = + checkFileResults.GetSymbolUseAtLocation( + fcsTextLineNumber, + lexerSymbol.Ident.idRange.EndColumn, + textLine.ToString(), + lexerSymbol.FullIsland ) - context.RegisterRefactoring(codeAction) - | _ -> () + let isValidParameterWithoutTypeAnnotation (funcOrValue: FSharpMemberOrFunctionOrValue) (symbolUse: FSharpSymbolUse) = + let isLambdaIfFunction = + funcOrValue.IsFunction + && parseFileResults.IsBindingALambdaAtPosition symbolUse.Range.Start + + (funcOrValue.IsValue || isLambdaIfFunction) + && parseFileResults.IsPositionContainedInACurriedParameter symbolUse.Range.Start + && not (parseFileResults.IsTypeAnnotationGivenAtPosition symbolUse.Range.Start) + && not funcOrValue.IsMember + && not funcOrValue.IsMemberThisValue + && not funcOrValue.IsConstructorThisValue + && not (PrettyNaming.IsOperatorDisplayName funcOrValue.DisplayName) + + match symbolUse with + | None -> return () + | Some symbolUse -> + + match symbolUse.Symbol with + | :? FSharpMemberOrFunctionOrValue as v when isValidParameterWithoutTypeAnnotation v symbolUse -> + let typeString = v.FullType.FormatWithConstraints symbolUse.DisplayContext + let title = SR.AddTypeAnnotation() + + let symbolSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) + + match symbolSpan with + | ValueNone -> return () + | ValueSome symbolSpan -> + + let alreadyWrappedInParens = + let rec leftLoop ch pos = + if not (Char.IsWhiteSpace(ch)) then + ch = '(' + else + leftLoop sourceText.[pos - 1] (pos - 1) + + let rec rightLoop ch pos = + if not (Char.IsWhiteSpace(ch)) then + ch = ')' + else + rightLoop sourceText.[pos + 1] (pos + 1) + + let hasLeftParen = leftLoop sourceText.[symbolSpan.Start - 1] (symbolSpan.Start - 1) + let hasRightParen = rightLoop sourceText.[symbolSpan.End] symbolSpan.End + hasLeftParen && hasRightParen + + let getChangedText (sourceText: SourceText) = + if alreadyWrappedInParens then + sourceText.WithChanges(TextChange(TextSpan(symbolSpan.End, 0), ": " + typeString)) + else + sourceText + .WithChanges(TextChange(TextSpan(symbolSpan.Start, 0), "(")) + .WithChanges(TextChange(TextSpan(symbolSpan.End + 1, 0), ": " + typeString + ")")) + + let codeAction = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + cancellableTask { + let! ct = CancellableTask.getCancellationToken () + let! sourceText = context.Document.GetTextAsync(ct) + return context.Document.WithText(getChangedText sourceText) + } + |> CancellableTask.start cancellationToken), + title + ) + + context.RegisterRefactoring(codeAction) + | _ -> () } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + |> CancellableTask.startAsTask context.CancellationToken diff --git a/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs index 08a73dc2816..cd01e6434d7 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs @@ -66,8 +66,8 @@ let main argv = task.Result match actualResolutionOption with - | None -> Assert.True(expectedResolution.IsNone, "BreakpointResolutionService failed to resolve breakpoint position") - | Some (actualResolutionRange) -> + | ValueNone -> Assert.True(expectedResolution.IsNone, "BreakpointResolutionService failed to resolve breakpoint position") + | ValueSome (actualResolutionRange) -> let actualResolution = sourceText .GetSubText(RoslynHelpers.FSharpRangeToTextSpan(sourceText, actualResolutionRange)) diff --git a/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs index 7f3d4d0e804..4a10dacc6c6 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs @@ -71,7 +71,7 @@ module FindReferences = let document = solution.TryGetDocumentFromPath documentPath - |> Option.defaultWith (fun _ -> failwith "Document not found") + |> ValueOption.defaultWith (fun _ -> failwith "Document not found") findUsagesService .FindReferencesAsync(document, getPositionOf "funcParam" documentPath, context) @@ -94,7 +94,7 @@ module FindReferences = let document = solution.TryGetDocumentFromPath documentPath - |> Option.defaultWith (fun _ -> failwith "Document not found") + |> ValueOption.defaultWith (fun _ -> failwith "Document not found") findUsagesService .FindReferencesAsync(document, getPositionOf "funcParam" documentPath, context) @@ -116,7 +116,7 @@ module FindReferences = let document = solution.TryGetDocumentFromPath documentPath - |> Option.defaultWith (fun _ -> failwith "Document not found") + |> ValueOption.defaultWith (fun _ -> failwith "Document not found") findUsagesService .FindReferencesAsync(document, getPositionOf "sharedFunc" documentPath, context) @@ -157,7 +157,7 @@ module FindReferences = let document = solution2.TryGetDocumentFromPath documentPath - |> Option.defaultWith (fun _ -> failwith "Document not found") + |> ValueOption.defaultWith (fun _ -> failwith "Document not found") findUsagesService .FindReferencesAsync(document, getPositionOf operator documentPath, context) From a94bb5fbe611bf72852fabb53c179bd58d820c08 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Fri, 8 Sep 2023 16:58:06 +0200 Subject: [PATCH 03/20] wip --- .../src/FSharp.Editor/FSharp.Editor.fsproj | 2 +- .../Navigation/GoToDefinition.fs | 23 +++++++++---------- .../Navigation/NavigableSymbolsService.fs | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index b73a33b690d..70d9291c092 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -18,7 +18,7 @@ - + true Microsoft.VisualStudio.FSharp.Editor.SR diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 74429229bdd..018ee7e1859 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -297,11 +297,11 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let preferSignature = isSignatureFile originDocument.FilePath let! lexerSymbol = originDocument.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, false, false, userOpName) - + match lexerSymbol with | None -> return ValueNone | Some lexerSymbol -> - + let idRange = lexerSymbol.Ident.idRange let! _, checkFileResults = @@ -340,8 +340,8 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = Seq.tryPickV (fun (sym, externalSym) -> if externalSym = targetExternalSym then ValueSome sym else ValueNone) roslynSymbols let location = - symbol - |> ValueOption.map _.Locations + symbol + |> ValueOption.map (fun s -> s.Locations) |> ValueOption.bind Seq.tryHeadV match location with @@ -407,7 +407,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = match declarations with | FindDeclResult.DeclFound targetRange -> let sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName - + match sigDocument with | ValueNone -> return ValueNone | ValueSome sigDocument -> @@ -532,7 +532,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = ) = cancellableTask { let! item = this.FindDeclarationOfSymbolAtRange(targetDocument, symbolRange, targetSourceText) - + match item with | None -> return false @@ -678,7 +678,6 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, span) this.NavigateToItem(navItem, cancellationToken) - true | _ -> false | _ -> false @@ -705,7 +704,7 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, (fun _progress cancellationToken -> cancellableTask { let targetDoc = solution.TryGetDocumentFromFSharpRange(range, initialDoc.Project.Id) - + match targetDoc with | None -> () | Some targetDoc -> @@ -716,7 +715,7 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, let targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(targetSource, range) match targetTextSpan with | ValueNone -> () - | ValueSome targetTextSpan -> + | ValueSome targetTextSpan -> let gtd = GoToDefinition(metadataAsSource) @@ -774,7 +773,7 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then match gtdTask.Result.Value with | FSharpGoToDefinitionResult.NavigableItem (navItem), _ -> - gtd.NavigateToItem(navItem, cancellationToken) + gtd.NavigateToItem(navItem, cancellationToken) |> ignore // 'true' means do it, like Sheev Palpatine would want us to. true | FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences), _ -> @@ -831,9 +830,9 @@ type FSharpNavigableLocation(metadataAsSource: FSharpMetadataAsSourceService, sy let! cancellationToken = CancellableTask.getCancellationToken () let targetDoc = project.Solution.TryGetDocumentFromFSharpRange(symbolRange, project.Id) - + match targetDoc with - | None -> + | None -> return false | Some targetDoc -> let! targetSource = targetDoc.GetTextAsync(cancellationToken) diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs index ab7a4b6a29a..32a4d8a9bab 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs @@ -21,7 +21,7 @@ open Microsoft.VisualStudio.Telemetry type internal FSharpNavigableSymbol(item: FSharpNavigableItem, span: SnapshotSpan, gtd: GoToDefinition) = interface INavigableSymbol with member _.Navigate(_: INavigableRelationship) = - gtd.NavigateToItem(item, CancellationToken.None) + gtd.NavigateToItem(item, CancellationToken.None) |> ignore member _.Relationships = seq { yield PredefinedNavigableRelationships.Definition } From c24a1a368dd2b8d1bffe415b94c5de9d291dba2b Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Fri, 8 Sep 2023 18:06:29 +0200 Subject: [PATCH 04/20] wip --- .../src/FSharp.Editor/Common/Extensions.fs | 2 +- .../Refactor/AddExplicitTypeToParameter.fs | 165 ++++++++---------- .../Refactor/ChangeDerefToValueRefactoring.fs | 63 +++---- .../ChangeTypeofWithNameToNameofExpression.fs | 70 ++++---- 4 files changed, 140 insertions(+), 160 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 6cb1246d21d..1cd8f5cc36e 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -349,7 +349,7 @@ module Seq = let inline revamp f (ie: seq<_>) = mkSeq (fun () -> f (ie.GetEnumerator())) - let inline toImmutableArray (xs: seq<'a>) : ImmutableArray<'a> = xs.ToImmutableArray() + let toImmutableArray (xs: seq<'a>) : ImmutableArray<'a> = xs.ToImmutableArray() let inline tryHeadV (source: seq<_>) = use e = source.GetEnumerator() diff --git a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs index 527da6f83e7..a6f9dbb8ecb 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs @@ -21,18 +21,16 @@ type internal FSharpAddExplicitTypeToParameterRefactoring [ liftTaskAsync let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position let fcsTextLineNumber = Line.fromZ textLinePos.Line + let! ct = Async.CancellationToken |> liftAsync + let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync( position, @@ -40,90 +38,81 @@ type internal FSharpAddExplicitTypeToParameterRefactoring [ CancellableTask.start ct |> Async.AwaitTask + + let! parseFileResults, checkFileResults = + document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpAddExplicitTypeToParameterRefactoring)) + |> CancellableTask.start ct + |> Async.AwaitTask + |> liftAsync + + let! symbolUse = + checkFileResults.GetSymbolUseAtLocation( + fcsTextLineNumber, + lexerSymbol.Ident.idRange.EndColumn, + textLine.ToString(), + lexerSymbol.FullIsland ) - match lexerSymbol with - | None -> return () - | Some lexerSymbol -> - - let! parseFileResults, checkFileResults = - document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpAddExplicitTypeToParameterRefactoring)) - - let symbolUse = - checkFileResults.GetSymbolUseAtLocation( - fcsTextLineNumber, - lexerSymbol.Ident.idRange.EndColumn, - textLine.ToString(), - lexerSymbol.FullIsland + let isValidParameterWithoutTypeAnnotation (funcOrValue: FSharpMemberOrFunctionOrValue) (symbolUse: FSharpSymbolUse) = + let isLambdaIfFunction = + funcOrValue.IsFunction + && parseFileResults.IsBindingALambdaAtPosition symbolUse.Range.Start + + (funcOrValue.IsValue || isLambdaIfFunction) + && parseFileResults.IsPositionContainedInACurriedParameter symbolUse.Range.Start + && not (parseFileResults.IsTypeAnnotationGivenAtPosition symbolUse.Range.Start) + && not funcOrValue.IsMember + && not funcOrValue.IsMemberThisValue + && not funcOrValue.IsConstructorThisValue + && not (PrettyNaming.IsOperatorDisplayName funcOrValue.DisplayName) + + match symbolUse.Symbol with + | :? FSharpMemberOrFunctionOrValue as v when isValidParameterWithoutTypeAnnotation v symbolUse -> + let typeString = v.FullType.FormatWithConstraints symbolUse.DisplayContext + let title = SR.AddTypeAnnotation() + + let! symbolSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) + + let alreadyWrappedInParens = + let rec leftLoop ch pos = + if not (Char.IsWhiteSpace(ch)) then + ch = '(' + else + leftLoop sourceText.[pos - 1] (pos - 1) + + let rec rightLoop ch pos = + if not (Char.IsWhiteSpace(ch)) then + ch = ')' + else + rightLoop sourceText.[pos + 1] (pos + 1) + + let hasLeftParen = leftLoop sourceText.[symbolSpan.Start - 1] (symbolSpan.Start - 1) + let hasRightParen = rightLoop sourceText.[symbolSpan.End] symbolSpan.End + hasLeftParen && hasRightParen + + let getChangedText (sourceText: SourceText) = + if alreadyWrappedInParens then + sourceText.WithChanges(TextChange(TextSpan(symbolSpan.End, 0), ": " + typeString)) + else + sourceText + .WithChanges(TextChange(TextSpan(symbolSpan.Start, 0), "(")) + .WithChanges(TextChange(TextSpan(symbolSpan.End + 1, 0), ": " + typeString + ")")) + + let codeAction = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + async { + let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask + return context.Document.WithText(getChangedText sourceText) + } + |> RoslynHelpers.StartAsyncAsTask(cancellationToken)), + title ) - let isValidParameterWithoutTypeAnnotation (funcOrValue: FSharpMemberOrFunctionOrValue) (symbolUse: FSharpSymbolUse) = - let isLambdaIfFunction = - funcOrValue.IsFunction - && parseFileResults.IsBindingALambdaAtPosition symbolUse.Range.Start - - (funcOrValue.IsValue || isLambdaIfFunction) - && parseFileResults.IsPositionContainedInACurriedParameter symbolUse.Range.Start - && not (parseFileResults.IsTypeAnnotationGivenAtPosition symbolUse.Range.Start) - && not funcOrValue.IsMember - && not funcOrValue.IsMemberThisValue - && not funcOrValue.IsConstructorThisValue - && not (PrettyNaming.IsOperatorDisplayName funcOrValue.DisplayName) - - match symbolUse with - | None -> return () - | Some symbolUse -> - - match symbolUse.Symbol with - | :? FSharpMemberOrFunctionOrValue as v when isValidParameterWithoutTypeAnnotation v symbolUse -> - let typeString = v.FullType.FormatWithConstraints symbolUse.DisplayContext - let title = SR.AddTypeAnnotation() - - let symbolSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) - - match symbolSpan with - | ValueNone -> return () - | ValueSome symbolSpan -> - - let alreadyWrappedInParens = - let rec leftLoop ch pos = - if not (Char.IsWhiteSpace(ch)) then - ch = '(' - else - leftLoop sourceText.[pos - 1] (pos - 1) - - let rec rightLoop ch pos = - if not (Char.IsWhiteSpace(ch)) then - ch = ')' - else - rightLoop sourceText.[pos + 1] (pos + 1) - - let hasLeftParen = leftLoop sourceText.[symbolSpan.Start - 1] (symbolSpan.Start - 1) - let hasRightParen = rightLoop sourceText.[symbolSpan.End] symbolSpan.End - hasLeftParen && hasRightParen - - let getChangedText (sourceText: SourceText) = - if alreadyWrappedInParens then - sourceText.WithChanges(TextChange(TextSpan(symbolSpan.End, 0), ": " + typeString)) - else - sourceText - .WithChanges(TextChange(TextSpan(symbolSpan.Start, 0), "(")) - .WithChanges(TextChange(TextSpan(symbolSpan.End + 1, 0), ": " + typeString + ")")) - - let codeAction = - CodeAction.Create( - title, - (fun (cancellationToken: CancellationToken) -> - cancellableTask { - let! ct = CancellableTask.getCancellationToken () - let! sourceText = context.Document.GetTextAsync(ct) - return context.Document.WithText(getChangedText sourceText) - } - |> CancellableTask.start cancellationToken), - title - ) - - context.RegisterRefactoring(codeAction) - | _ -> () + context.RegisterRefactoring(codeAction) + | _ -> () } - |> CancellableTask.startAsTask context.CancellationToken + |> Async.Ignore + |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs index 86607965acf..07408c049b1 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs @@ -22,52 +22,47 @@ type internal FSharpChangeDerefToValueRefactoring [] () = inherit CodeRefactoringProvider() override _.ComputeRefactoringsAsync context = - cancellableTask { + asyncMaybe { let document = context.Document - let! ct = CancellableTask.getCancellationToken () - let! sourceText = context.Document.GetTextAsync(ct) + let! sourceText = context.Document.GetTextAsync(context.CancellationToken) let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpChangeDerefToValueRefactoring)) + |> CancellableTask.start context.CancellationToken + |> Async.AwaitTask + |> liftAsync let selectionRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) - let derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos selectionRange.Start - let exprRange = parseResults.TryRangeOfExpressionBeingDereferencedContainingPos selectionRange.Start + let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos selectionRange.Start + let! exprRange = parseResults.TryRangeOfExpressionBeingDereferencedContainingPos selectionRange.Start - match derefRange, exprRange with - | Some derefRange, Some exprRange -> + let combinedRange = Range.unionRanges derefRange exprRange + let! combinedSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, combinedRange) - let combinedRange = Range.unionRanges derefRange exprRange + let replacementString = + // Trim off the `!` + sourceText.GetSubText(combinedSpan).ToString().[1..] + ".Value" - let combinedSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, combinedRange) + let title = SR.UseValueInsteadOfDeref() - match combinedSpan with - | ValueNone -> () - | ValueSome combinedSpan -> - let replacementString = - // Trim off the `!` - sourceText.GetSubText(combinedSpan).ToString().[1..] + ".Value" + let getChangedText (sourceText: SourceText) = + sourceText.WithChanges(TextChange(combinedSpan, replacementString)) - let title = SR.UseValueInsteadOfDeref() + let codeAction = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + async { + let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask + return context.Document.WithText(getChangedText sourceText) + } + |> RoslynHelpers.StartAsyncAsTask(cancellationToken)), + title + ) - let getChangedText (sourceText: SourceText) = - sourceText.WithChanges(TextChange(combinedSpan, replacementString)) - - let codeAction = - CodeAction.Create( - title, - (fun (cancellationToken: CancellationToken) -> - cancellableTask { - let! sourceText = context.Document.GetTextAsync(cancellationToken) - return context.Document.WithText(getChangedText sourceText) - } - |> CancellableTask.start cancellationToken), - title - ) - - context.RegisterRefactoring(codeAction) - | _ -> () + context.RegisterRefactoring(codeAction) } - |> CancellableTask.startAsTask context.CancellationToken + |> Async.Ignore + |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs index 2efeb8bf6e7..6cc93b06d87 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeTypeofWithNameToNameofExpression.fs @@ -22,48 +22,44 @@ type internal FSharpChangeTypeofWithNameToNameofExpressionRefactoring [ CancellableTask.start context.CancellationToken + |> Async.AwaitTask + |> liftAsync let selectionRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) - let namedTypeOfResults = parseResults.TryRangeOfTypeofWithNameAndTypeExpr(selectionRange.Start) - - match namedTypeOfResults with - | None -> () - | Some namedTypeOfResults -> - - let namedTypeSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, namedTypeOfResults.NamedIdentRange) - let typeofAndNameSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, namedTypeOfResults.FullExpressionRange) - - match namedTypeSpan, typeofAndNameSpan with - | ValueSome namedTypeSpan, ValueSome typeofAndNameSpan -> - - let namedTypeName = sourceText.GetSubText(namedTypeSpan) - let replacementString = $"nameof({namedTypeName})" - - let title = SR.UseNameof() - - let getChangedText (sourceText: SourceText) = - sourceText.WithChanges(TextChange(typeofAndNameSpan, replacementString)) - - let codeAction = - CodeAction.Create( - title, - (fun (cancellationToken: CancellationToken) -> - cancellableTask { - let! sourceText = context.Document.GetTextAsync(cancellationToken) - return context.Document.WithText(getChangedText sourceText) - } |> CancellableTask.start cancellationToken), - title - ) - - context.RegisterRefactoring(codeAction) - | _ -> () - } |> CancellableTask.startAsTask context.CancellationToken + let! namedTypeOfResults = parseResults.TryRangeOfTypeofWithNameAndTypeExpr(selectionRange.Start) + + let! namedTypeSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, namedTypeOfResults.NamedIdentRange) + let! typeofAndNameSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, namedTypeOfResults.FullExpressionRange) + let namedTypeName = sourceText.GetSubText(namedTypeSpan) + let replacementString = $"nameof({namedTypeName})" + + let title = SR.UseNameof() + + let getChangedText (sourceText: SourceText) = + sourceText.WithChanges(TextChange(typeofAndNameSpan, replacementString)) + + let codeAction = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + async { + let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask + return context.Document.WithText(getChangedText sourceText) + } + |> RoslynHelpers.StartAsyncAsTask(cancellationToken)), + title + ) + + context.RegisterRefactoring(codeAction) + } + |> Async.Ignore + |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) From cc5f92bf2877c42fa3eeb3d720cd68b27099eb5c Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 11 Sep 2023 14:59:49 +0200 Subject: [PATCH 05/20] wip --- .../Commands/XmlDocCommandService.fs | 118 +++++++++--------- .../src/FSharp.Editor/Common/Extensions.fs | 2 +- 2 files changed, 58 insertions(+), 62 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs index 4efd4e51f38..b8547654c5b 100644 --- a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs @@ -60,80 +60,76 @@ type internal XmlDocCommandFilter(wpfTextView: IWpfTextView, filePath: string, w match XmlDocComment.IsBlank lineWithLastCharInserted with | Some i when i = indexOfCaret -> - cancellableTask { + asyncMaybe { try // XmlDocable line #1 are 1-based, editor is 0-based let curEditorLineNum = wpfTextView.Caret.Position.BufferPosition.GetContainingLine().LineNumber - let document = getLastDocument () - - match document with - | None -> () - | Some document -> - let! cancellationToken = CancellableTask.getCancellationToken () - let! sourceText = document.GetTextAsync(cancellationToken) - let! parseResults = document.GetFSharpParseResultsAsync(nameof (XmlDocCommandFilter)) - - let xmlDocables = - XmlDocParser.GetXmlDocables(sourceText.ToFSharpSourceText(), parseResults.ParseTree) + let! document = getLastDocument () + let! cancellationToken = Async.CancellationToken |> liftAsync + let! sourceText = document.GetTextAsync(cancellationToken) + let! parseResults = document.GetFSharpParseResultsAsync(nameof (XmlDocCommandFilter)) |> CancellableTask.start cancellationToken |> Async.AwaitTask |> liftAsync + + let xmlDocables = + XmlDocParser.GetXmlDocables(sourceText.ToFSharpSourceText(), parseResults.ParseTree) + + let xmlDocablesBelowThisLine = + // +1 because looking below current line for e.g. a 'member' or 'let' + xmlDocables + |> List.filter (fun (XmlDocable (line, _indent, _paramNames)) -> line = curEditorLineNum + 1) + + match xmlDocablesBelowThisLine with + | [] -> () + | XmlDocable (_line, indent, paramNames) :: _xs -> + // delete the slashes the user typed (they may be indented wrong) + let editorLineToDelete = + wpfTextView.TextBuffer.CurrentSnapshot.GetLineFromLineNumber( + wpfTextView.Caret.Position.BufferPosition.GetContainingLine().LineNumber + ) + + wpfTextView.TextBuffer.Delete(editorLineToDelete.Extent.Span) |> ignore + // add the new xmldoc comment + let toInsert = new Text.StringBuilder() + + toInsert + .Append(' ', indent) + .AppendLine("/// ") + .Append(' ', indent) + .AppendLine("/// ") + .Append(' ', indent) + .Append("/// ") + |> ignore + + paramNames + |> List.iter (fun p -> + toInsert + .AppendLine() + .Append(' ', indent) + .Append(sprintf "/// " p) + |> ignore) - let xmlDocablesBelowThisLine = - // +1 because looking below current line for e.g. a 'member' or 'let' - xmlDocables - |> List.filter (fun (XmlDocable (line, _indent, _paramNames)) -> line = curEditorLineNum + 1) + let _newSS = + wpfTextView.TextBuffer.Insert( + wpfTextView.Caret.Position.BufferPosition.Position, + toInsert.ToString() + ) + // move the caret to between the summary tags + let lastLine = wpfTextView.Caret.Position.BufferPosition.GetContainingLine() - match xmlDocablesBelowThisLine with - | [] -> () - | XmlDocable (_line, indent, paramNames) :: _xs -> - // delete the slashes the user typed (they may be indented wrong) - let editorLineToDelete = - wpfTextView.TextBuffer.CurrentSnapshot.GetLineFromLineNumber( - wpfTextView.Caret.Position.BufferPosition.GetContainingLine().LineNumber - ) + let middleSummaryLine = + wpfTextView.TextSnapshot.GetLineFromLineNumber(lastLine.LineNumber - 1 - paramNames.Length) - wpfTextView.TextBuffer.Delete(editorLineToDelete.Extent.Span) |> ignore - // add the new xmldoc comment - let toInsert = new Text.StringBuilder() + wpfTextView.Caret.MoveTo(wpfTextView.GetTextViewLineContainingBufferPosition(middleSummaryLine.Start)) + |> ignore - toInsert - .Append(' ', indent) - .AppendLine("/// ") - .Append(' ', indent) - .AppendLine("/// ") - .Append(' ', indent) - .Append("/// ") - |> ignore - - paramNames - |> List.iter (fun p -> - toInsert - .AppendLine() - .Append(' ', indent) - .Append(sprintf "/// " p) - |> ignore) - - let _newSS = - wpfTextView.TextBuffer.Insert( - wpfTextView.Caret.Position.BufferPosition.Position, - toInsert.ToString() - ) - // move the caret to between the summary tags - let lastLine = wpfTextView.Caret.Position.BufferPosition.GetContainingLine() - - let middleSummaryLine = - wpfTextView.TextSnapshot.GetLineFromLineNumber(lastLine.LineNumber - 1 - paramNames.Length) - - wpfTextView.Caret.MoveTo(wpfTextView.GetTextViewLineContainingBufferPosition(middleSummaryLine.Start)) - |> ignore - - shouldCommitCharacter <- false + shouldCommitCharacter <- false with ex -> Assert.Exception ex () } - |> CancellableTask.startAsTaskWithoutCancellation // We don't have a cancellation token here at the moment, maybe there's a better way of handling it in modern VS? - |> ignore + |> Async.Ignore + |> Async.StartImmediate | Some _ | None -> () | _ -> () diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 1cd8f5cc36e..4328952339c 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -529,7 +529,7 @@ module Exception = |> String.concat " ---> " type Async with - + static member RunImmediateExceptOnUI(computation: Async<'T>, ?cancellationToken) = match SynchronizationContext.Current with | null -> From 38ea5406acd1bd63262389a52735a1fb7ef4ce93 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 11 Sep 2023 15:00:51 +0200 Subject: [PATCH 06/20] Fantomas --- .../Commands/XmlDocCommandService.fs | 7 +- .../src/FSharp.Editor/Common/Extensions.fs | 45 +++---- .../Debugging/BreakpointResolutionService.fs | 6 +- .../Navigation/FindDefinitionService.fs | 4 +- .../Navigation/GoToDefinition.fs | 112 +++++++++--------- .../Navigation/GoToDefinitionService.fs | 3 +- .../Navigation/NavigateToSearchService.fs | 14 ++- .../Navigation/NavigationBarItemService.fs | 6 +- .../Refactor/AddExplicitTypeToParameter.fs | 4 +- 9 files changed, 104 insertions(+), 97 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs index b8547654c5b..5b762414c59 100644 --- a/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/XmlDocCommandService.fs @@ -69,7 +69,12 @@ type internal XmlDocCommandFilter(wpfTextView: IWpfTextView, filePath: string, w let! document = getLastDocument () let! cancellationToken = Async.CancellationToken |> liftAsync let! sourceText = document.GetTextAsync(cancellationToken) - let! parseResults = document.GetFSharpParseResultsAsync(nameof (XmlDocCommandFilter)) |> CancellableTask.start cancellationToken |> Async.AwaitTask |> liftAsync + + let! parseResults = + document.GetFSharpParseResultsAsync(nameof (XmlDocCommandFilter)) + |> CancellableTask.start cancellationToken + |> Async.AwaitTask + |> liftAsync let xmlDocables = XmlDocParser.GetXmlDocables(sourceText.ToFSharpSourceText(), parseResults.ParseTree) diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 4328952339c..87d0007be82 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -306,14 +306,12 @@ module IEnumerator = let get () = if not started then - raise(InvalidOperationException("Not started")) + raise (InvalidOperationException("Not started")) match curr with - | None -> - raise(InvalidOperationException("Already finished")) + | None -> raise (InvalidOperationException("Already finished")) | Some x -> x - { new IEnumerator<'U> with member _.Current = get () interface System.Collections.IEnumerator with @@ -331,20 +329,21 @@ module IEnumerator = Option.isSome curr member _.Reset() = - raise(NotSupportedException("Reset is not supported")) + raise (NotSupportedException("Reset is not supported")) interface System.IDisposable with - member _.Dispose() = - e.Dispose() + member _.Dispose() = e.Dispose() } + [] module Seq = let mkSeq f = { new IEnumerable<'U> with - member _.GetEnumerator() = f() - + member _.GetEnumerator() = f () interface System.Collections.IEnumerable with - member _.GetEnumerator() = (f() :> System.Collections.IEnumerator) } + member _.GetEnumerator() = + (f () :> System.Collections.IEnumerator) + } let inline revamp f (ie: seq<_>) = mkSeq (fun () -> f (ie.GetEnumerator())) @@ -354,10 +353,7 @@ module Seq = let inline tryHeadV (source: seq<_>) = use e = source.GetEnumerator() - if (e.MoveNext()) then - ValueSome e.Current - else - ValueNone + if (e.MoveNext()) then ValueSome e.Current else ValueNone let inline tryFindV ([] predicate) (source: seq<'T>) = use e = source.GetEnumerator() @@ -409,10 +405,7 @@ module Array = let toImmutableArray (xs: 'T[]) = xs.ToImmutableArray() let inline tryHeadV (array: _[]) = - if array.Length = 0 then - ValueNone - else - ValueSome array[0] + if array.Length = 0 then ValueNone else ValueSome array[0] let inline tryFindV ([] predicate) (array: _[]) = @@ -440,8 +433,7 @@ module Array = if i <> array.Length then - let chunk1: 'U[] = - Array.zeroCreate ((array.Length >>> 2) + 1) + let chunk1: 'U[] = Array.zeroCreate ((array.Length >>> 2) + 1) chunk1.[0] <- first let mutable count = 1 @@ -459,8 +451,7 @@ module Array = i <- i + 1 if i < array.Length then - let chunk2: 'U[] = - Array.zeroCreate (array.Length - i) + let chunk2: 'U[] = Array.zeroCreate (array.Length - i) count <- 0 @@ -475,8 +466,7 @@ module Array = i <- i + 1 - let res: 'U[] = - Array.zeroCreate (chunk1.Length + count) + let res: 'U[] = Array.zeroCreate (chunk1.Length + count) Array.Copy(chunk1, res, chunk1.Length) Array.Copy(chunk2, 0, res, chunk1.Length, count) @@ -489,10 +479,7 @@ module Array = [] module ImmutableArray = let inline tryHeadV (xs: ImmutableArray<'T>) : 'T voption = - if xs.Length = 0 then - ValueNone - else - ValueSome xs[0] + if xs.Length = 0 then ValueNone else ValueSome xs[0] let inline empty<'T> = ImmutableArray<'T>.Empty @@ -529,7 +516,7 @@ module Exception = |> String.concat " ---> " type Async with - + static member RunImmediateExceptOnUI(computation: Async<'T>, ?cancellationToken) = match SynchronizationContext.Current with | null -> diff --git a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs index a7e33fbfcb3..56ad5a26518 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs @@ -38,10 +38,10 @@ type internal FSharpBreakpointResolutionService [] () = let textLineColumn = textLinePos.Character let fcsTextLineNumber = Line.fromZ textLinePos.Line // Roslyn line numbers are zero-based, FSharp.Compiler.Service line numbers are 1-based - let! parseResults = - document.GetFSharpParseResultsAsync(nameof (FSharpBreakpointResolutionService)) + let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpBreakpointResolutionService)) - let location = parseResults.ValidateBreakpointLocation(mkPos fcsTextLineNumber textLineColumn) + let location = + parseResults.ValidateBreakpointLocation(mkPos fcsTextLineNumber textLineColumn) return ValueOption.ofOption location } diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs index 0013da2d154..bc408c01741 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs @@ -20,5 +20,5 @@ type internal FSharpFindDefinitionService [] (metadataAsSo cancellableTask { let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup) return! navigation.FindDefinitionsAsync(position) - } |> CancellableTask.start cancellationToken - + } + |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 018ee7e1859..08591a6915b 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -186,9 +186,9 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = /// Helper function that is used to determine the navigation strategy to apply, can be tuned towards signatures or implementation files. member private _.FindSymbolHelper(originDocument: Document, originRange: range, sourceText: SourceText, preferSignature: bool) = let originTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, originRange) + match originTextSpan with - | ValueNone -> - CancellableTask.singleton None + | ValueNone -> CancellableTask.singleton None | ValueSome originTextSpan -> cancellableTask { let userOpName = "FindSymbolHelper" @@ -206,8 +206,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let! ct = CancellableTask.getCancellationToken () - let! _, checkFileResults = - originDocument.GetFSharpParseAndCheckResultsAsync(nameof (GoToDefinition)) + let! _, checkFileResults = originDocument.GetFSharpParseAndCheckResultsAsync(nameof (GoToDefinition)) let fsSymbolUse = checkFileResults.GetSymbolUseAtLocation(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland) @@ -233,24 +232,24 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = | ValueSome implDoc -> let! implSourceText = implDoc.GetTextAsync(ct) - let! _, checkFileResults = - implDoc.GetFSharpParseAndCheckResultsAsync(userOpName) + let! _, checkFileResults = implDoc.GetFSharpParseAndCheckResultsAsync(userOpName) let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbol, ct) - let implSymbol = Array.tryHeadV symbolUses + let implSymbol = Array.tryHeadV symbolUses match implSymbol with | ValueNone -> return None | ValueSome implSymbol -> - let implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, implSymbol.Range) + let implTextSpan = + RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, implSymbol.Range) match implTextSpan with | ValueNone -> return None - | ValueSome implTextSpan -> - return Some(FSharpGoToDefinitionNavigableItem(implDoc, implTextSpan)) + | ValueSome implTextSpan -> return Some(FSharpGoToDefinitionNavigableItem(implDoc, implTextSpan)) else - let targetDocument = originDocument.Project.Solution.TryGetDocumentFromFSharpRange fsSymbolUse.Range + let targetDocument = + originDocument.Project.Solution.TryGetDocumentFromFSharpRange fsSymbolUse.Range match targetDocument with | None -> return None @@ -304,8 +303,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let idRange = lexerSymbol.Ident.idRange - let! _, checkFileResults = - originDocument.GetFSharpParseAndCheckResultsAsync(userOpName) + let! _, checkFileResults = originDocument.GetFSharpParseAndCheckResultsAsync(userOpName) let declarations = checkFileResults.GetDeclarationLocation( @@ -333,11 +331,16 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = | ValueSome project -> let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, (fun _ -> true), cancellationToken) - let roslynSymbols = - Seq.collect ExternalSymbol.ofRoslynSymbol symbols + let roslynSymbols = Seq.collect ExternalSymbol.ofRoslynSymbol symbols let symbol = - Seq.tryPickV (fun (sym, externalSym) -> if externalSym = targetExternalSym then ValueSome sym else ValueNone) roslynSymbols + Seq.tryPickV + (fun (sym, externalSym) -> + if externalSym = targetExternalSym then + ValueSome sym + else + ValueNone) + roslynSymbols let location = symbol @@ -349,19 +352,21 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = | ValueSome location -> return - ValueSome ( + ValueSome( FSharpGoToDefinitionResult.NavigableItem( FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan) - ), idRange) + ), + idRange + ) | _ -> let metadataReferences = originDocument.Project.MetadataReferences - return ValueSome (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange) + return ValueSome(FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange) | FindDeclResult.DeclFound targetRange -> // If the file is not associated with a document, it's considered external. if not (originDocument.Project.Solution.ContainsDocumentWithFilePath(targetRange.FileName)) then let metadataReferences = originDocument.Project.MetadataReferences - return ValueSome (FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange) + return ValueSome(FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), idRange) else // if goto definition is called at we are alread at the declaration location of a symbol in // either a signature or an implementation file then we jump to it's respective postion in thethe @@ -375,7 +380,8 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = if not (File.Exists implFilePath) then return ValueNone else - let implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath + let implDocument = + originDocument.Project.Solution.TryGetDocumentFromPath implFilePath match implDocument with | ValueNone -> return ValueNone @@ -386,7 +392,9 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = | None -> return ValueNone | Some targetRange -> let! implSourceText = implDocument.GetTextAsync(cancellationToken) - let implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange) + + let implTextSpan = + RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange) match implTextSpan with | ValueNone -> return ValueNone @@ -406,7 +414,8 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = match declarations with | FindDeclResult.DeclFound targetRange -> - let sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName + let sigDocument = + originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName match sigDocument with | ValueNone -> return ValueNone @@ -425,12 +434,15 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = // - gotoDefn origin = signature , gotoDefn destination = signature // - gotoDefn origin = implementation, gotoDefn destination = implementation else - let sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName + let sigDocument = + originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName + match sigDocument with | ValueNone -> return ValueNone | ValueSome sigDocument -> let! sigSourceText = sigDocument.GetTextAsync(cancellationToken) let sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sigSourceText, targetRange) + match sigTextSpan with | ValueNone -> return ValueNone | ValueSome sigTextSpan -> @@ -447,7 +459,8 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = else sigDocument.FilePath - let implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath + let implDocument = + originDocument.Project.Solution.TryGetDocumentFromPath implFilePath match implDocument with | ValueNone -> return ValueNone @@ -458,7 +471,10 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = | None -> return ValueNone | Some targetRange -> let! implSourceText = implDocument.GetTextAsync(cancellationToken) - let implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange) + + let implTextSpan = + RoslynHelpers.TryFSharpRangeToTextSpan(implSourceText, targetRange) + match implTextSpan with | ValueNone -> return ValueNone | ValueSome implTextSpan -> @@ -524,35 +540,24 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = result /// Find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition - member this.NavigateToSymbolDeclarationAsync - ( - targetDocument: Document, - targetSourceText: SourceText, - symbolRange: range - ) = + member this.NavigateToSymbolDeclarationAsync(targetDocument: Document, targetSourceText: SourceText, symbolRange: range) = cancellableTask { let! item = this.FindDeclarationOfSymbolAtRange(targetDocument, symbolRange, targetSourceText) match item with - | None -> - return false + | None -> return false | Some item -> let! cancellationToken = CancellableTask.getCancellationToken () return this.NavigateToItem(item, cancellationToken) } /// Find the definition location (implementation file/.fs) of the target symbol - member this.NavigateToSymbolDefinitionAsync - ( - targetDocument: Document, - targetSourceText: SourceText, - symbolRange: range - ) = + member this.NavigateToSymbolDefinitionAsync(targetDocument: Document, targetSourceText: SourceText, symbolRange: range) = cancellableTask { let! item = this.FindDefinitionOfSymbolAtRange(targetDocument, symbolRange, targetSourceText) + match item with - | None -> - return false + | None -> return false | Some item -> let! cancellationToken = CancellableTask.getCancellationToken () return this.NavigateToItem(item, cancellationToken) @@ -713,6 +718,7 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, let! targetSource = targetDoc.GetTextAsync(cancellationToken) let targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(targetSource, range) + match targetTextSpan with | ValueNone -> () | ValueSome targetTextSpan -> @@ -728,14 +734,13 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, else // Navigation request was made in a .fs file, so we try to find the implmentation of the symbol at target range. // This is the part that may take some time, because of type checks involved. - let! result = - gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range) + let! result = gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range) if not result then // In case the above fails, we just navigate to target range. do gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken) - } |> CancellableTask.start cancellationToken - ), + } + |> CancellableTask.start cancellationToken), // Default wait time before VS shows the dialog allowing to cancel the long running task is 2 seconds. // This seems a bit too long to leave the user without any feedback, so we shorten it to 1 second. // Note: it seems anything less than 1 second will get rounded down to zero, resulting in flashing dialog @@ -829,11 +834,11 @@ type FSharpNavigableLocation(metadataAsSource: FSharpMetadataAsSourceService, sy let! cancellationToken = CancellableTask.getCancellationToken () - let targetDoc = project.Solution.TryGetDocumentFromFSharpRange(symbolRange, project.Id) + let targetDoc = + project.Solution.TryGetDocumentFromFSharpRange(symbolRange, project.Id) match targetDoc with - | None -> - return false + | None -> return false | Some targetDoc -> let! targetSource = targetDoc.GetTextAsync(cancellationToken) let gtd = GoToDefinition(metadataAsSource) @@ -845,11 +850,10 @@ type FSharpNavigableLocation(metadataAsSource: FSharpMetadataAsSourceService, sy Implementation match targetPath with - | Signature -> - return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, symbolRange) - | Implementation -> - return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, symbolRange) - } |> CancellableTask.start cancellationToken + | Signature -> return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, symbolRange) + | Implementation -> return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, symbolRange) + } + |> CancellableTask.start cancellationToken [)>] [)>] diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index 1cea931c17d..9e822e0e26f 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -23,7 +23,8 @@ type internal FSharpGoToDefinitionService [] (metadataAsSo let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup) let! res = navigation.FindDefinitionsAsync(position) return (res :> IEnumerable<_>) - } |> CancellableTask.start cancellationToken + } + |> CancellableTask.start cancellationToken /// Invoked with Go to Definition. /// Try to navigate to the definiton of the symbol at the symbolRange in the originDocument diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index 07a3e52f8bc..e901e0c0113 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -142,7 +142,8 @@ type internal FSharpNavigateToSearchService [] else // full name with dots allows for path matching, e.g. // "f.c.so.elseif" will match "Fantomas.Core.SyntaxOak.ElseIfNode" - patternMatcher.TryMatch $"{item.Container.FullName}.{name}" |> ValueOption.ofNullable + patternMatcher.TryMatch $"{item.Container.FullName}.{name}" + |> ValueOption.ofNullable let processDocument (tryMatch: NavigableItem -> PatternMatch voption) (kinds: IImmutableSet) (document: Document) = cancellableTask { @@ -155,11 +156,13 @@ type internal FSharpNavigateToSearchService [] let processed = [| for item in items do - let contains = kinds.Contains (navigateToItemKindToRoslynKind item.Kind) + let contains = kinds.Contains(navigateToItemKindToRoslynKind item.Kind) let patternMatch = tryMatch item + match contains, patternMatch with | true, ValueSome m -> let sourceSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, item.Range) + match sourceSpan with | ValueNone -> () | ValueSome sourceSpan -> @@ -173,7 +176,12 @@ type internal FSharpNavigateToSearchService [] kind, patternMatchKindToNavigateToMatchKind m.Kind, item.Name, - FSharpNavigableItem(glyph, ImmutableArray.Create(TaggedText(TextTags.Text, item.Name)), document, sourceSpan) + FSharpNavigableItem( + glyph, + ImmutableArray.Create(TaggedText(TextTags.Text, item.Name)), + document, + sourceSpan + ) ) | _ -> () |] diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs index d40a18e9485..b31b42c9822 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigationBarItemService.fs @@ -25,8 +25,7 @@ type internal FSharpNavigationBarItemService [] () = let! cancellationToken = CancellableTask.getCancellationToken () - let! parseResults = - document.GetFSharpParseResultsAsync(nameof (FSharpNavigationBarItemService)) + let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpNavigationBarItemService)) let navItems = Navigation.getNavigation parseResults.ParseTree let! sourceText = document.GetTextAsync(cancellationToken) @@ -59,4 +58,5 @@ type internal FSharpNavigationBarItemService [] () = ) :> FSharpNavigationBarItem)) :> IList<_> - } |> CancellableTask.start cancellationToken + } + |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs index a6f9dbb8ecb..bcea39dd8ec 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs @@ -38,7 +38,9 @@ type internal FSharpAddExplicitTypeToParameterRefactoring [ CancellableTask.start ct |> Async.AwaitTask + ) + |> CancellableTask.start ct + |> Async.AwaitTask let! parseFileResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpAddExplicitTypeToParameterRefactoring)) From 709d6d5a5674d3c3421c3cc63369650d4ba9fb41 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 11 Sep 2023 16:06:43 +0200 Subject: [PATCH 07/20] more wip --- .../Navigation/GoToDefinition.fs | 294 +++++++----------- .../Navigation/GoToDefinitionService.fs | 5 - .../Navigation/NavigableSymbolsService.fs | 82 +++-- 3 files changed, 147 insertions(+), 234 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 08591a6915b..3f242c63b68 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -110,45 +110,6 @@ module private ExternalSymbol = | _ -> [] -// TODO: Uncomment code when VS has a fix for updating the status bar. -type StatusBar() = - let statusBar = - ServiceProvider.GlobalProvider.GetService() - - let mutable _searchIcon = - int16 Microsoft.VisualStudio.Shell.Interop.Constants.SBAI_Find :> obj - - let _clear () = - // unfreeze the statusbar - statusBar.FreezeOutput 0 |> ignore - statusBar.Clear() |> ignore - - member _.Message(_msg: string) = () - //let _, frozen = statusBar.IsFrozen() - //// unfreeze the status bar - //if frozen <> 0 then statusBar.FreezeOutput 0 |> ignore - //statusBar.SetText msg |> ignore - //// freeze the status bar - //statusBar.FreezeOutput 1 |> ignore - - member this.TempMessage(_msg: string) = () - //this.Message msg - //async { - // do! Async.Sleep 4000 - // match statusBar.GetText() with - // | 0, currentText when currentText <> msg -> () - // | _ -> clear() - //}|> Async.Start - - member _.Clear() = () //clear() - - /// Animated magnifying glass that displays on the status bar while a symbol search is in progress. - member _.Animate() : IDisposable = - //statusBar.Animation (1, &searchIcon) |> ignore - { new IDisposable with - member _.Dispose() = () - } //statusBar.Animation(0, &searchIcon) |> ignore } - type internal FSharpGoToDefinitionNavigableItem(document, sourceSpan) = inherit FSharpNavigableItem(Glyph.BasicFile, ImmutableArray.Empty, document, sourceSpan) @@ -161,7 +122,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = /// Use an origin document to provide the solution & workspace used to /// find the corresponding textSpan and INavigableItem for the range let rangeToNavigableItem (range: range, document: Document) = - async { + cancellableTask { let fileName = try System.IO.Path.GetFullPath range.FileName @@ -493,16 +454,15 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = /// Construct a task that will return a navigation target for the implementation definition of the symbol /// at the provided position in the document. - member this.FindDefinitionTask(originDocument: Document, position: int, cancellationToken: CancellationToken) = + member this.FindDefinitionAsync(originDocument: Document, position: int) = this.FindDefinitionAtPosition(originDocument, position) - |> CancellableTask.start cancellationToken /// Navigate to the positon of the textSpan in the provided document /// used by quickinfo link navigation when the tooltip contains the correct destination range. member _.TryNavigateToTextSpan ( document: Document, - textSpan: Microsoft.CodeAnalysis.Text.TextSpan, + textSpan: TextSpan, cancellationToken: CancellationToken ) = let navigableItem = FSharpGoToDefinitionNavigableItem(document, textSpan) @@ -511,17 +471,9 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let navigationService = workspace.Services.GetService() - let navigationSucceeded = - navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, cancellationToken) - - if not navigationSucceeded then - StatusBar().TempMessage(SR.CannotNavigateUnknown()) + navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, cancellationToken) |> ignore member _.NavigateToItem(navigableItem: FSharpNavigableItem, cancellationToken: CancellationToken) = - let statusBar = StatusBar() - use __ = statusBar.Animate() - - statusBar.Message(SR.NavigatingTo()) let workspace = navigableItem.Document.Project.Solution.Workspace @@ -532,11 +484,6 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let result = navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, cancellationToken) - if result then - statusBar.Clear() - else - statusBar.TempMessage(SR.CannotNavigateUnknown()) - result /// Find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition @@ -567,11 +514,8 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = ( targetSymbolUse: FSharpSymbolUse, metadataReferences: seq, - cancellationToken: CancellationToken, - statusBar: StatusBar + cancellationToken: CancellationToken ) = - use __ = statusBar.Animate() - statusBar.Message(SR.NavigatingTo()) let textOpt = match targetSymbolUse.Symbol with @@ -593,103 +537,97 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = |> Option.map (fun text -> text, symbol.DisplayName) | _ -> None - let result = - match textOpt with - | Some (text, fileName) -> - let tmpProjInfo, tmpDocInfo = - MetadataAsSource.generateTemporaryDocument ( - AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), - fileName, - metadataReferences - ) + match textOpt with + | Some (text, fileName) -> + let tmpProjInfo, tmpDocInfo = + MetadataAsSource.generateTemporaryDocument ( + AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), + fileName, + metadataReferences + ) - let tmpShownDocOpt = - metadataAsSource.ShowDocument(tmpProjInfo, tmpDocInfo.FilePath, SourceText.From(text.ToString())) + let tmpShownDocOpt = + metadataAsSource.ShowDocument(tmpProjInfo, tmpDocInfo.FilePath, SourceText.From(text.ToString())) - match tmpShownDocOpt with - | ValueSome tmpShownDoc -> - let goToAsync = - asyncMaybe { + match tmpShownDocOpt with + | ValueSome tmpShownDoc -> + let goToAsync = + asyncMaybe { - let! ct = Async.CancellationToken |> liftAsync + let! ct = Async.CancellationToken |> liftAsync - let! _, checkResults = - tmpShownDoc.GetFSharpParseAndCheckResultsAsync("NavigateToExternalDeclaration") - |> CancellableTask.start ct - |> Async.AwaitTask - |> liftAsync + let! _, checkResults = + tmpShownDoc.GetFSharpParseAndCheckResultsAsync("NavigateToExternalDeclaration") + |> CancellableTask.start ct + |> Async.AwaitTask + |> liftAsync - let! r = - let rec areTypesEqual (ty1: FSharpType) (ty2: FSharpType) = - let ty1 = ty1.StripAbbreviations() - let ty2 = ty2.StripAbbreviations() + let! r = + let rec areTypesEqual (ty1: FSharpType) (ty2: FSharpType) = + let ty1 = ty1.StripAbbreviations() + let ty2 = ty2.StripAbbreviations() - let generic = - ty1.IsGenericParameter && ty2.IsGenericParameter - || (ty1.GenericArguments.Count = ty2.GenericArguments.Count - && (ty1.GenericArguments, ty2.GenericArguments) ||> Seq.forall2 areTypesEqual) + let generic = + ty1.IsGenericParameter && ty2.IsGenericParameter + || (ty1.GenericArguments.Count = ty2.GenericArguments.Count + && (ty1.GenericArguments, ty2.GenericArguments) ||> Seq.forall2 areTypesEqual) - if generic then - true - else - let namesEqual = ty1.TypeDefinition.DisplayName = ty2.TypeDefinition.DisplayName - let accessPathsEqual = ty1.TypeDefinition.AccessPath = ty2.TypeDefinition.AccessPath - namesEqual && accessPathsEqual - - // This tries to find the best possible location of the target symbol's location in the metadata source. - // We really should rely on symbol equality within FCS instead of doing it here, - // but the generated metadata as source isn't perfect for symbol equality. - checkResults.GetAllUsesOfAllSymbolsInFile(cancellationToken) - |> Seq.tryFindV (fun x -> - match x.Symbol, targetSymbolUse.Symbol with - | (:? FSharpEntity as symbol1), (:? FSharpEntity as symbol2) when x.IsFromDefinition -> - symbol1.DisplayName = symbol2.DisplayName - | (:? FSharpMemberOrFunctionOrValue as symbol1), (:? FSharpMemberOrFunctionOrValue as symbol2) -> - symbol1.DisplayName = symbol2.DisplayName - && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with - | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName - | _ -> false) - && symbol1.GenericParameters.Count = symbol2.GenericParameters.Count - && symbol1.CurriedParameterGroups.Count = symbol2.CurriedParameterGroups.Count - && ((symbol1.CurriedParameterGroups, symbol2.CurriedParameterGroups) - ||> Seq.forall2 (fun pg1 pg2 -> - pg1.Count = pg2.Count - && ((pg1, pg2) ||> Seq.forall2 (fun p1 p2 -> areTypesEqual p1.Type p2.Type)))) - && areTypesEqual symbol1.ReturnParameter.Type symbol2.ReturnParameter.Type - | (:? FSharpField as symbol1), (:? FSharpField as symbol2) when x.IsFromDefinition -> - symbol1.DisplayName = symbol2.DisplayName - && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with - | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName - | _ -> false) - | (:? FSharpUnionCase as symbol1), (:? FSharpUnionCase as symbol2) -> - symbol1.DisplayName = symbol2.DisplayName - && symbol1.DeclaringEntity.CompiledName = symbol2.DeclaringEntity.CompiledName - | _ -> false) - |> ValueOption.map (fun x -> x.Range) - |> ValueOption.toOption - - let span = - match RoslynHelpers.TryFSharpRangeToTextSpan(tmpShownDoc.GetTextAsync(cancellationToken).Result, r) with - | ValueSome span -> span - | _ -> TextSpan() - - return span - } - - let span = - match Async.RunImmediateExceptOnUI(goToAsync, cancellationToken = cancellationToken) with - | Some span -> span - | _ -> TextSpan() - - let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, span) - this.NavigateToItem(navItem, cancellationToken) - | _ -> false - | _ -> false + if generic then + true + else + let namesEqual = ty1.TypeDefinition.DisplayName = ty2.TypeDefinition.DisplayName + let accessPathsEqual = ty1.TypeDefinition.AccessPath = ty2.TypeDefinition.AccessPath + namesEqual && accessPathsEqual + + // This tries to find the best possible location of the target symbol's location in the metadata source. + // We really should rely on symbol equality within FCS instead of doing it here, + // but the generated metadata as source isn't perfect for symbol equality. + checkResults.GetAllUsesOfAllSymbolsInFile(cancellationToken) + |> Seq.tryFindV (fun x -> + match x.Symbol, targetSymbolUse.Symbol with + | (:? FSharpEntity as symbol1), (:? FSharpEntity as symbol2) when x.IsFromDefinition -> + symbol1.DisplayName = symbol2.DisplayName + | (:? FSharpMemberOrFunctionOrValue as symbol1), (:? FSharpMemberOrFunctionOrValue as symbol2) -> + symbol1.DisplayName = symbol2.DisplayName + && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with + | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName + | _ -> false) + && symbol1.GenericParameters.Count = symbol2.GenericParameters.Count + && symbol1.CurriedParameterGroups.Count = symbol2.CurriedParameterGroups.Count + && ((symbol1.CurriedParameterGroups, symbol2.CurriedParameterGroups) + ||> Seq.forall2 (fun pg1 pg2 -> + pg1.Count = pg2.Count + && ((pg1, pg2) ||> Seq.forall2 (fun p1 p2 -> areTypesEqual p1.Type p2.Type)))) + && areTypesEqual symbol1.ReturnParameter.Type symbol2.ReturnParameter.Type + | (:? FSharpField as symbol1), (:? FSharpField as symbol2) when x.IsFromDefinition -> + symbol1.DisplayName = symbol2.DisplayName + && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with + | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName + | _ -> false) + | (:? FSharpUnionCase as symbol1), (:? FSharpUnionCase as symbol2) -> + symbol1.DisplayName = symbol2.DisplayName + && symbol1.DeclaringEntity.CompiledName = symbol2.DeclaringEntity.CompiledName + | _ -> false) + |> ValueOption.map (fun x -> x.Range) + |> ValueOption.toOption + + let span = + match RoslynHelpers.TryFSharpRangeToTextSpan(tmpShownDoc.GetTextAsync(cancellationToken).Result, r) with + | ValueSome span -> span + | _ -> TextSpan() + + return span + } - if result then - statusBar.Clear() - else - statusBar.TempMessage(SR.CannotNavigateUnknown()) + let span = + match Async.RunImmediateExceptOnUI(goToAsync, cancellationToken = cancellationToken) with + | Some span -> span + | _ -> TextSpan() + + let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, span) + this.NavigateToItem(navItem, cancellationToken) + | _ -> false + | _ -> false type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, initialDoc: Document, thisSymbolUseRange: range) = @@ -762,39 +700,31 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, } member _.TryGoToDefinition(position, cancellationToken) = - let gtd = GoToDefinition(metadataAsSource) - let statusBar = StatusBar() - let gtdTask = gtd.FindDefinitionTask(initialDoc, position, cancellationToken) - - // Wrap this in a try/with as if the user clicks "Cancel" on the thread dialog, we'll be cancelled. - // Task.Wait throws an exception if the task is cancelled, so be sure to catch it. try - // This call to Wait() is fine because we want to be able to provide the error message in the status bar. - use _ = - TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinition, [||]) - - gtdTask.Wait(cancellationToken) - - if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then - match gtdTask.Result.Value with - | FSharpGoToDefinitionResult.NavigableItem (navItem), _ -> - gtd.NavigateToItem(navItem, cancellationToken) |> ignore - // 'true' means do it, like Sheev Palpatine would want us to. - true - | FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences), _ -> - gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken, statusBar) - // 'true' means do it, like Sheev Palpatine would want us to. - true - else - statusBar.TempMessage(SR.CannotDetermineSymbol()) - false - with exc -> - TelemetryReporter.ReportFault(TelemetryEvents.GoToDefinition, FaultSeverity.General, exc) - statusBar.TempMessage(String.Format(SR.NavigateToFailed(), Exception.flattenMessage exc)) - - // Don't show the dialog box as it's most likely that the user cancelled. - // Don't make them click twice. - true + let gtd = GoToDefinition(metadataAsSource) + let gtdTask = + cancellableTask { + use _ = TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinition, [||]) + let! definition = gtd.FindDefinitionAsync(initialDoc, position) + let! cancellationToken = CancellableTask.getCancellationToken () + + match definition with + | ValueSome (FSharpGoToDefinitionResult.NavigableItem (navItem), _) -> + gtd.NavigateToItem(navItem, cancellationToken) |> ignore + return true + | ValueSome (FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences), _) -> + gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken) |> ignore + return true + | _ -> return false + } + ThreadHelper.JoinableTaskFactory.Run( + SR.NavigatingTo(), + (fun _ ct -> + let cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, ct) + CancellableTask.start cts.Token gtdTask), + TimeSpan.FromSeconds 1) + with :? OperationCanceledException -> + false [] type internal SymbolMemberType = diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index 9e822e0e26f..3130163b192 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -29,10 +29,5 @@ type internal FSharpGoToDefinitionService [] (metadataAsSo /// Invoked with Go to Definition. /// Try to navigate to the definiton of the symbol at the symbolRange in the originDocument member _.TryGoToDefinition(document: Document, position: int, cancellationToken: CancellationToken) = - let statusBar = StatusBar() - statusBar.Message(SR.LocatingSymbol()) - use __ = statusBar.Animate() - let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup) - navigation.TryGoToDefinition(position, cancellationToken) diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs index 32a4d8a9bab..a5e354a74cf 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs @@ -16,6 +16,8 @@ open Microsoft.VisualStudio.Text.Editor open Microsoft.VisualStudio.Utilities open Microsoft.VisualStudio.FSharp.Editor.Telemetry open Microsoft.VisualStudio.Telemetry +open CancellableTasks.CancellableTaskBuilder +open CancellableTasks [] type internal FSharpNavigableSymbol(item: FSharpNavigableItem, span: SnapshotSpan, gtd: GoToDefinition) = @@ -31,7 +33,6 @@ type internal FSharpNavigableSymbolSource(metadataAsSource) = let mutable disposed = false let gtd = GoToDefinition(metadataAsSource) - let statusBar = StatusBar() interface INavigableSymbolSource with member _.GetNavigableSymbolAsync(triggerSpan: SnapshotSpan, cancellationToken: CancellationToken) = @@ -39,69 +40,56 @@ type internal FSharpNavigableSymbolSource(metadataAsSource) = if disposed then null else - asyncMaybe { + cancellableTask { + use _ = + TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinitionGetSymbol, [||]) + let snapshot = triggerSpan.Snapshot let position = triggerSpan.Start.Position let document = snapshot.GetOpenDocumentInCurrentContextWithChanges() - let! sourceText = document.GetTextAsync(cancellationToken) |> liftTaskAsync - - statusBar.Message(SR.LocatingSymbol()) - use _ = statusBar.Animate() - - let gtdTask = gtd.FindDefinitionTask(document, position, cancellationToken) + let! cancellationToken = CancellableTask.getCancellationToken () + let! sourceText = document.GetTextAsync(cancellationToken) - // Wrap this in a try/with as if the user clicks "Cancel" on the thread dialog, we'll be cancelled. - // Task.Wait throws an exception if the task is cancelled, so be sure to catch it. try - // This call to Wait() is fine because we want to be able to provide the error message in the status bar. - use _ = - TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinitionGetSymbol, [||]) + let! definition = gtd.FindDefinitionAsync(document, position) - gtdTask.Wait(cancellationToken) - statusBar.Clear() - - if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then - let result, range = gtdTask.Result.Value + match definition with + | ValueNone -> return null + | ValueSome (result, range) -> + let declarationTextSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range) let declarationSpan = Span(declarationTextSpan.Start, declarationTextSpan.Length) let symbolSpan = SnapshotSpan(snapshot, declarationSpan) match result with - | FSharpGoToDefinitionResult.NavigableItem (navItem) -> - return FSharpNavigableSymbol(navItem, symbolSpan, gtd) :> INavigableSymbol - - | FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences) -> - let nav = - { new INavigableSymbol with - member _.Navigate(_: INavigableRelationship) = - // Need to new up a CTS here instead of re-using the other one, since VS - // will navigate disconnected from the outer routine, leading to an - // OperationCancelledException if you use the one defined outside. - use ct = new CancellationTokenSource() - gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, ct.Token, statusBar) - - member _.Relationships = seq { yield PredefinedNavigableRelationships.Definition } - - member _.SymbolSpan = symbolSpan - } - - return nav - else - statusBar.TempMessage(SR.CannotDetermineSymbol()) - - // The NavigableSymbols API accepts 'null' when there's nothing to navigate to. - return null - with exc -> - TelemetryReporter.ReportFault(TelemetryEvents.GoToDefinitionGetSymbol, FaultSeverity.General, exc) + | FSharpGoToDefinitionResult.NavigableItem (navItem) -> + return FSharpNavigableSymbol(navItem, symbolSpan, gtd) :> INavigableSymbol - statusBar.TempMessage(String.Format(SR.NavigateToFailed(), Exception.flattenMessage exc)) + | FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences) -> + let nav = + { new INavigableSymbol with + member _.Navigate(_: INavigableRelationship) = + // Need to new up a CTS here instead of re-using the other one, since VS + // will navigate disconnected from the outer routine, leading to an + // OperationCancelledException if you use the one defined outside. + // TODO: review if it's a proper way of doing it + use ct = new CancellationTokenSource() + do gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, ct.Token) |> ignore + member _.Relationships = seq { yield PredefinedNavigableRelationships.Definition } + + member _.SymbolSpan = symbolSpan + } + + return nav + + with exc -> + TelemetryReporter.ReportFault(TelemetryEvents.GoToDefinitionGetSymbol, FaultSeverity.General, exc) // The NavigableSymbols API accepts 'null' when there's nothing to navigate to. return null } - |> Async.map Option.toObj - |> RoslynHelpers.StartAsyncAsTask cancellationToken + |> CancellableTask.start cancellationToken member _.Dispose() = disposed <- true From 18b07a52b00295e2992e98c3345ba10ee7ce2097 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 11 Sep 2023 17:12:15 +0200 Subject: [PATCH 08/20] wip: cache semantic highlightings for opened docs --- .../Classification/ClassificationService.fs | 81 ++++++++++++++----- .../src/FSharp.Editor/Common/DocumentCache.fs | 3 + 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs index 8900417cdc0..31dcd549109 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs @@ -111,7 +111,7 @@ type internal FSharpClassificationService [] () = | true, items -> items | _ -> let items = ResizeArray() - lookup.[dataItem.Range.StartLine] <- items + lookup[dataItem.Range.StartLine] <- items items items.Add dataItem @@ -120,8 +120,25 @@ type internal FSharpClassificationService [] () = lookup :> IReadOnlyDictionary<_, _> - let semanticClassificationCache = - new DocumentCache("fsharp-semantic-classification-cache") + static let itemTosemanticClassificationLookup (d: SemanticClassificationItem array) = + let lookup = Dictionary>() + for item in d do + let items = + let startLine = item.Range.StartLine + match lookup.TryGetValue startLine with + | true, items -> items + | _ -> + let items = ResizeArray() + lookup[startLine] <- items + items + items.Add item + lookup :> IReadOnlyDictionary<_, _> + + static let unopenedDocumentsSemanticClassificationCache = + new DocumentCache("fsharp-unopened-documents-semantic-classification-cache", 5.) + + static let openedDocumentsSemanticClassificationCache = + new DocumentCache("fsharp-opened-documents-semantic-classification-cache", 2.) interface IFSharpClassificationService with // Do not perform classification if we don't have project options (#defines matter) @@ -197,7 +214,7 @@ type internal FSharpClassificationService [] () = let isOpenDocument = document.Project.Solution.Workspace.IsDocumentOpen document.Id if not isOpenDocument then - match! semanticClassificationCache.TryGetValueAsync document with + match! unopenedDocumentsSemanticClassificationCache.TryGetValueAsync document with | ValueSome classificationDataLookup -> let eventProps: (string * obj) array = [| @@ -212,7 +229,7 @@ type internal FSharpClassificationService [] () = TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticCalssifications, eventProps) addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result - | _ -> + | ValueNone -> let eventProps: (string * obj) array = [| "context.document.project.id", document.Project.Id.Id.ToString() @@ -227,28 +244,50 @@ type internal FSharpClassificationService [] () = let! classificationData = document.GetFSharpSemanticClassificationAsync(nameof (FSharpClassificationService)) let classificationDataLookup = toSemanticClassificationLookup classificationData - do! semanticClassificationCache.SetAsync(document, classificationDataLookup) + do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup) addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result else - let eventProps: (string * obj) array = - [| - "context.document.project.id", document.Project.Id.Id.ToString() - "context.document.id", document.Id.Id.ToString() - "isOpenDocument", isOpenDocument - "textSpanLength", textSpan.Length - "cacheHit", false - |] - use _eventDuration = - TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticCalssifications, eventProps) + match! openedDocumentsSemanticClassificationCache.TryGetValueAsync document with + | ValueSome classificationDataLookup -> + let eventProps: (string * obj) array = + [| + "context.document.project.id", document.Project.Id.Id.ToString() + "context.document.id", document.Id.Id.ToString() + "isOpenDocument", isOpenDocument + "textSpanLength", textSpan.Length + "cacheHit", true + |] + + use _eventDuration = + TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticCalssifications, eventProps) + + addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result + | ValueNone -> + + let eventProps: (string * obj) array = + [| + "context.document.project.id", document.Project.Id.Id.ToString() + "context.document.id", document.Id.Id.ToString() + "isOpenDocument", isOpenDocument + "textSpanLength", textSpan.Length + "cacheHit", false + |] + + use _eventDuration = + TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticCalssifications, eventProps) + + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof (IFSharpClassificationService)) + + let targetRange = + RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText) - let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof (IFSharpClassificationService)) + let classificationData = checkResults.GetSemanticClassification(Some targetRange) - let targetRange = - RoslynHelpers.TextSpanToFSharpRange(document.FilePath, textSpan, sourceText) + let classificationDataLookup = itemTosemanticClassificationLookup classificationData + do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup) - let classificationData = checkResults.GetSemanticClassification(Some targetRange) - addSemanticClassification sourceText textSpan classificationData result + addSemanticClassification sourceText textSpan classificationData result } |> CancellableTask.startAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs b/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs index f812e5f8b0f..6c88bc0f912 100644 --- a/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs +++ b/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs @@ -16,6 +16,9 @@ type DocumentCache<'Value when 'Value: not struct>(name: string, ?cacheItemPolic let policy = defaultArg cacheItemPolicy (CacheItemPolicy(SlidingExpiration = (TimeSpan.FromSeconds defaultSlidingExpiration))) + new(name: string, slidingExpirationSeconds: float) = + new DocumentCache<'Value>(name, CacheItemPolicy(SlidingExpiration = (TimeSpan.FromSeconds slidingExpirationSeconds))) + member _.TryGetValueAsync(doc: Document) = cancellableTask { let! ct = CancellableTask.getCancellationToken () From 98c2a5abfea4a465ac385f4c6ba2dfdcaf64475e Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 11 Sep 2023 19:28:44 +0200 Subject: [PATCH 09/20] more wip --- .../Diagnostics/DocumentDiagnosticAnalyzer.fs | 6 +- .../FSharpAnalysisSaveFileCommandHandler.fs | 73 ++++++++-------- .../FSharpProjectOptionsManager.fs | 85 ++++++++++--------- .../LanguageService/WorkspaceExtensions.fs | 8 +- 4 files changed, 87 insertions(+), 85 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs index 1e547e86335..e1ff85c4ee6 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs @@ -16,7 +16,7 @@ open FSharp.Compiler.Diagnostics open CancellableTasks open Microsoft.VisualStudio.FSharp.Editor.Telemetry -[] +[] type internal DiagnosticsType = | Syntax | Semantic @@ -125,12 +125,12 @@ type internal FSharpDocumentDiagnosticAnalyzer [] () = if document.Project.IsFSharpMetadata then Task.FromResult ImmutableArray.Empty else - cancellableTask { return! FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) } + FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) |> CancellableTask.start cancellationToken member _.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken) : Task> = if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then Task.FromResult ImmutableArray.Empty else - cancellableTask { return! FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic) } + FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic) |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs index 30baf56eb3e..f6388c590ca 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs @@ -12,6 +12,7 @@ open Microsoft.VisualStudio.FSharp.Editor.Logging open Microsoft.VisualStudio.Text.Editor.Commanding.Commands open Microsoft.VisualStudio.Commanding open Microsoft.VisualStudio.Utilities +open CancellableTasks // This causes re-analysis to happen when a F# document is saved. // We do this because FCS relies on the file system and existing open documents @@ -45,43 +46,41 @@ type internal FSharpAnalysisSaveFileCommandHandler [] (ana | null -> () | _ -> let document = solution.GetDocument(documentId) - - async { - try - if document.Project.Language = LanguageNames.FSharp then - let openDocIds = workspace.GetOpenDocumentIds() - - let docIdsToReanalyze = - if document.IsFSharpScript then - openDocIds - |> Seq.filter (fun x -> - x <> document.Id - && (let doc = solution.GetDocument(x) - - match doc with - | null -> false - | _ -> doc.IsFSharpScript)) - |> Array.ofSeq - else - let depProjIds = document.Project.GetDependentProjectIds().Add(document.Project.Id) - - openDocIds - |> Seq.filter (fun x -> - depProjIds.Contains(x.ProjectId) - && x <> document.Id - && (let doc = solution.GetDocument(x) - - match box doc with - | null -> false - | _ -> doc.Project.Language = LanguageNames.FSharp)) - |> Array.ofSeq - - if docIdsToReanalyze.Length > 0 then - analyzerService.Reanalyze(workspace, documentIds = docIdsToReanalyze) - with ex -> - logException ex - } - |> Async.Start // fire and forget + if document.Project.Language <> LanguageNames.FSharp then + () + else + cancellableTask { + try + if document.Project.Language = LanguageNames.FSharp then + let openDocIds = workspace.GetOpenDocumentIds() + + let docIdsToReanalyze = + if document.IsFSharpScript then + [| for x in openDocIds do + if x <> document.Id + && (let doc = solution.GetDocument(x) + match doc with + | null -> false + | _ -> doc.IsFSharpScript) then + yield x |] + else + let depProjIds = document.Project.GetDependentProjectIds().Add(document.Project.Id) + + [| for x in openDocIds do + if depProjIds.Contains(x.ProjectId) + && x <> document.Id + && (let doc = solution.GetDocument(x) + match box doc with + | null -> false + | _ -> doc.Project.Language = LanguageNames.FSharp) then + yield x |] + + if docIdsToReanalyze.Length > 0 then + analyzerService.Reanalyze(workspace, documentIds = docIdsToReanalyze) + with ex -> + Telemetry.TelemetryReporter.ReportFault("FSharpAnalysisSaveFileCommandHandler.ExecuteCommand", e = ex) + logException ex + } |> CancellableTask.startWithoutCancellation |> ignore // fire and forget nextCommandHandler.Invoke() diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index 345bd3d707f..7834be87c52 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -15,6 +15,7 @@ open Microsoft.VisualStudio.FSharp.Editor open System.Threading open Microsoft.VisualStudio.FSharp.Interactive.Session open System.Runtime.CompilerServices +open CancellableTasks [] module private FSharpProjectOptionsHelpers = @@ -55,7 +56,7 @@ module private FSharpProjectOptionsHelpers = and set (v) = errorReporter <- v } - let hasProjectVersionChanged (oldProject: Project) (newProject: Project) = + let inline hasProjectVersionChanged (oldProject: Project) (newProject: Project) = oldProject.Version <> newProject.Version let hasDependentVersionChanged (oldProject: Project) (newProject: Project) (ct: CancellationToken) = @@ -97,10 +98,10 @@ module private FSharpProjectOptionsHelpers = type private FSharpProjectOptionsMessage = | TryGetOptionsByDocument of Document * - AsyncReplyChannel<(FSharpParsingOptions * FSharpProjectOptions) option> * + AsyncReplyChannel<(FSharpParsingOptions * FSharpProjectOptions) voption> * CancellationToken * userOpName: string - | TryGetOptionsByProject of Project * AsyncReplyChannel<(FSharpParsingOptions * FSharpProjectOptions) option> * CancellationToken + | TryGetOptionsByProject of Project * AsyncReplyChannel<(FSharpParsingOptions * FSharpProjectOptions) voption> * CancellationToken | ClearOptions of ProjectId | ClearSingleFileOptionsCache of DocumentId @@ -188,13 +189,14 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = weakPEReferences.Add(comp, fsRefProj) fsRefProj - let rec tryComputeOptionsBySingleScriptOrFile (document: Document) (ct: CancellationToken) userOpName = - async { - let! fileStamp = document.GetTextVersionAsync(ct) |> Async.AwaitTask + let rec tryComputeOptionsBySingleScriptOrFile (document: Document) userOpName = + cancellableTask { + let! ct = CancellableTask.getCancellationToken () + let! fileStamp = document.GetTextVersionAsync(ct) match singleFileCache.TryGetValue(document.Id) with | false, _ -> - let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask + let! sourceText = document.GetTextAsync(ct) let! scriptProjectOptions, _ = checker.GetProjectOptionsFromScript( @@ -243,29 +245,30 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = singleFileCache.[document.Id] <- (document.Project, fileStamp, parsingOptions, projectOptions) - return Some(parsingOptions, projectOptions) + return ValueSome(parsingOptions, projectOptions) | true, (oldProject, oldFileStamp, parsingOptions, projectOptions) -> if fileStamp <> oldFileStamp || isProjectInvalidated document.Project oldProject ct then singleFileCache.TryRemove(document.Id) |> ignore - return! tryComputeOptionsBySingleScriptOrFile document ct userOpName + return! tryComputeOptionsBySingleScriptOrFile document userOpName else - return Some(parsingOptions, projectOptions) + return ValueSome(parsingOptions, projectOptions) } let tryGetProjectSite (project: Project) = // Cps if commandLineOptions.ContainsKey project.Id then - Some(mapCpsProjectToSite (project, commandLineOptions)) + ValueSome(mapCpsProjectToSite (project, commandLineOptions)) else // Legacy match legacyProjectSites.TryGetValue project.Id with - | true, site -> Some site - | _ -> None + | true, site -> ValueSome site + | _ -> ValueNone - let rec tryComputeOptions (project: Project) ct = - async { + let rec tryComputeOptions (project: Project) = + cancellableTask { let projectId = project.Id + let! ct = CancellableTask.getCancellationToken () match cache.TryGetValue(projectId) with | false, _ -> @@ -276,29 +279,29 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = let referencedProjects = ResizeArray() + if project.AreFSharpInMemoryCrossProjectReferencesEnabled then for projectReference in project.ProjectReferences do let referencedProject = project.Solution.GetProject(projectReference.ProjectId) if referencedProject.Language = FSharpConstants.FSharpLanguageName then - match! tryComputeOptions referencedProject ct with - | None -> canBail <- true - | Some (_, projectOptions) -> + match! tryComputeOptions referencedProject with + | ValueNone -> canBail <- true + | ValueSome (_, projectOptions) -> referencedProjects.Add( FSharpReferencedProject.FSharpReference(referencedProject.OutputFilePath, projectOptions) ) elif referencedProject.SupportsCompilation then - let! comp = referencedProject.GetCompilationAsync(ct) |> Async.AwaitTask + let! comp = referencedProject.GetCompilationAsync(ct) let peRef = createPEReference referencedProject comp referencedProjects.Add(peRef) if canBail then - return None + return ValueNone else - match tryGetProjectSite project with - | None -> return None - | Some projectSite -> + | ValueNone -> return ValueNone + | ValueSome projectSite -> let otherOptions = [| @@ -321,7 +324,7 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = "--ignorelinedirectives" |] - let! ver = project.GetDependentVersionAsync(ct) |> Async.AwaitTask + let! ver = project.GetDependentVersionAsync(ct) let projectOptions = { @@ -340,7 +343,7 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = // This can happen if we didn't receive the callback from HandleCommandLineChanges yet. if Array.isEmpty projectOptions.SourceFiles then - return None + return ValueNone else // Clear any caches that need clearing and invalidate the project. let currentSolution = project.Solution.Workspace.CurrentSolution @@ -371,14 +374,14 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = cache.[projectId] <- (project, parsingOptions, projectOptions) - return Some(parsingOptions, projectOptions) + return ValueSome(parsingOptions, projectOptions) | true, (oldProject, parsingOptions, projectOptions) -> if isProjectInvalidated oldProject project ct then cache.TryRemove(projectId) |> ignore return! tryComputeOptions project ct else - return Some(parsingOptions, projectOptions) + return ValueSome(parsingOptions, projectOptions) } let loop (agent: MailboxProcessor) = @@ -387,17 +390,17 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = match! agent.Receive() with | FSharpProjectOptionsMessage.TryGetOptionsByDocument (document, reply, ct, userOpName) -> if ct.IsCancellationRequested then - reply.Reply None + reply.Reply ValueNone else try // For now, disallow miscellaneous workspace since we are using the hacky F# miscellaneous files project. if document.Project.Solution.Workspace.Kind = WorkspaceKind.MiscellaneousFiles then - reply.Reply None + reply.Reply ValueNone elif document.Project.IsFSharpMiscellaneousOrMetadata then - let! options = tryComputeOptionsBySingleScriptOrFile document ct userOpName + let! options = tryComputeOptionsBySingleScriptOrFile document userOpName |> CancellableTask.start ct |> Async.AwaitTask if ct.IsCancellationRequested then - reply.Reply None + reply.Reply ValueNone else reply.Reply options else @@ -407,43 +410,43 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = document.Project.Solution.Workspace.CurrentSolution.GetProject(document.Project.Id) if not (isNull project) then - let! options = tryComputeOptions project ct + let! options = tryComputeOptions project |> CancellableTask.start ct |> Async.AwaitTask if ct.IsCancellationRequested then - reply.Reply None + reply.Reply ValueNone else reply.Reply options else - reply.Reply None + reply.Reply ValueNone with _ -> - reply.Reply None + reply.Reply ValueNone | FSharpProjectOptionsMessage.TryGetOptionsByProject (project, reply, ct) -> if ct.IsCancellationRequested then - reply.Reply None + reply.Reply ValueNone else try if project.Solution.Workspace.Kind = WorkspaceKind.MiscellaneousFiles || project.IsFSharpMiscellaneousOrMetadata then - reply.Reply None + reply.Reply ValueNone else // We only care about the latest project in the workspace's solution. // We do this to prevent any possible cache thrashing in FCS. let project = project.Solution.Workspace.CurrentSolution.GetProject(project.Id) if not (isNull project) then - let! options = tryComputeOptions project ct + let! options = tryComputeOptions project |> CancellableTask.start ct |> Async.AwaitTask if ct.IsCancellationRequested then - reply.Reply None + reply.Reply ValueNone else reply.Reply options else - reply.Reply None + reply.Reply ValueNone with _ -> - reply.Reply None + reply.Reply ValueNone | FSharpProjectOptionsMessage.ClearOptions (projectId) -> match cache.TryRemove(projectId) with diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index 77d89a7d7a0..a13b116c4ab 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -152,8 +152,8 @@ type Document with let! ct = CancellableTask.getCancellationToken () match! projectOptionsManager.TryGetOptionsForDocumentOrProject(this, ct, userOpName) with - | None -> return raise (OperationCanceledException("FSharp project options not found.")) - | Some (parsingOptions, projectOptions) -> + | ValueNone -> return raise (OperationCanceledException("FSharp project options not found.")) + | ValueSome (parsingOptions, projectOptions) -> let result = (service.Checker, projectOptionsManager, parsingOptions, projectOptions) @@ -344,8 +344,8 @@ type Project with let projectOptionsManager = service.FSharpProjectOptionsManager match! projectOptionsManager.TryGetOptionsByProject(this, ct) with - | None -> return raise (OperationCanceledException("FSharp project options not found.")) - | Some (parsingOptions, projectOptions) -> + | ValueNone -> return raise (OperationCanceledException("FSharp project options not found.")) + | ValueSome (parsingOptions, projectOptions) -> let result = (service.Checker, projectOptionsManager, parsingOptions, projectOptions) From 73877dc643591cddc42b2500db32e6ba12cf9a69 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Tue, 12 Sep 2023 14:27:30 +0200 Subject: [PATCH 10/20] wip --- .../Classification/ClassificationService.fs | 8 +++- .../FSharpAnalysisSaveFileCommandHandler.fs | 46 ++++++++++++------- .../FSharpProjectOptionsManager.fs | 8 ++-- .../Navigation/GoToDefinition.fs | 25 +++++----- .../Navigation/NavigableSymbolsService.fs | 44 +++++++++--------- .../Navigation/NavigateToSearchService.fs | 8 +--- 6 files changed, 78 insertions(+), 61 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs index 31dcd549109..35eefa24828 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs @@ -120,18 +120,22 @@ type internal FSharpClassificationService [] () = lookup :> IReadOnlyDictionary<_, _> - static let itemTosemanticClassificationLookup (d: SemanticClassificationItem array) = + static let itemToSemanticClassificationLookup (d: SemanticClassificationItem array) = let lookup = Dictionary>() + for item in d do let items = let startLine = item.Range.StartLine + match lookup.TryGetValue startLine with | true, items -> items | _ -> let items = ResizeArray() lookup[startLine] <- items items + items.Add item + lookup :> IReadOnlyDictionary<_, _> static let unopenedDocumentsSemanticClassificationCache = @@ -284,7 +288,7 @@ type internal FSharpClassificationService [] () = let classificationData = checkResults.GetSemanticClassification(Some targetRange) - let classificationDataLookup = itemTosemanticClassificationLookup classificationData + let classificationDataLookup = itemToSemanticClassificationLookup classificationData do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup) addSemanticClassification sourceText textSpan classificationData result diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs index f6388c590ca..1b44c731e7a 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs @@ -46,41 +46,53 @@ type internal FSharpAnalysisSaveFileCommandHandler [] (ana | null -> () | _ -> let document = solution.GetDocument(documentId) + if document.Project.Language <> LanguageNames.FSharp then () else cancellableTask { try - if document.Project.Language = LanguageNames.FSharp then - let openDocIds = workspace.GetOpenDocumentIds() + let openDocIds = workspace.GetOpenDocumentIds() - let docIdsToReanalyze = - if document.IsFSharpScript then - [| for x in openDocIds do - if x <> document.Id + let docIdsToReanalyze = + if document.IsFSharpScript then + [| + for x in openDocIds do + if + x <> document.Id && (let doc = solution.GetDocument(x) + match doc with | null -> false - | _ -> doc.IsFSharpScript) then - yield x |] - else - let depProjIds = document.Project.GetDependentProjectIds().Add(document.Project.Id) + | _ -> doc.IsFSharpScript) + then + yield x + |] + else + let depProjIds = document.Project.GetDependentProjectIds().Add(document.Project.Id) - [| for x in openDocIds do - if depProjIds.Contains(x.ProjectId) + [| + for x in openDocIds do + if + depProjIds.Contains(x.ProjectId) && x <> document.Id && (let doc = solution.GetDocument(x) + match box doc with | null -> false - | _ -> doc.Project.Language = LanguageNames.FSharp) then - yield x |] + | _ -> doc.Project.Language = LanguageNames.FSharp) + then + yield x + |] - if docIdsToReanalyze.Length > 0 then - analyzerService.Reanalyze(workspace, documentIds = docIdsToReanalyze) + if docIdsToReanalyze.Length > 0 then + analyzerService.Reanalyze(workspace, documentIds = docIdsToReanalyze) with ex -> Telemetry.TelemetryReporter.ReportFault("FSharpAnalysisSaveFileCommandHandler.ExecuteCommand", e = ex) logException ex - } |> CancellableTask.startWithoutCancellation |> ignore // fire and forget + } + |> CancellableTask.startWithoutCancellation + |> ignore // fire and forget nextCommandHandler.Invoke() diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index 7834be87c52..4fcc180201b 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -279,7 +279,6 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = let referencedProjects = ResizeArray() - if project.AreFSharpInMemoryCrossProjectReferencesEnabled then for projectReference in project.ProjectReferences do let referencedProject = project.Solution.GetProject(projectReference.ProjectId) @@ -324,7 +323,7 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = "--ignorelinedirectives" |] - let! ver = project.GetDependentVersionAsync(ct) + let! ver = project.GetDependentVersionAsync(ct) let projectOptions = { @@ -397,7 +396,10 @@ type private FSharpProjectOptionsReactor(checker: FSharpChecker) = if document.Project.Solution.Workspace.Kind = WorkspaceKind.MiscellaneousFiles then reply.Reply ValueNone elif document.Project.IsFSharpMiscellaneousOrMetadata then - let! options = tryComputeOptionsBySingleScriptOrFile document userOpName |> CancellableTask.start ct |> Async.AwaitTask + let! options = + tryComputeOptionsBySingleScriptOrFile document userOpName + |> CancellableTask.start ct + |> Async.AwaitTask if ct.IsCancellationRequested then reply.Reply ValueNone diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 3f242c63b68..3301faa30ce 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -459,19 +459,15 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = /// Navigate to the positon of the textSpan in the provided document /// used by quickinfo link navigation when the tooltip contains the correct destination range. - member _.TryNavigateToTextSpan - ( - document: Document, - textSpan: TextSpan, - cancellationToken: CancellationToken - ) = + member _.TryNavigateToTextSpan(document: Document, textSpan: TextSpan, cancellationToken: CancellationToken) = let navigableItem = FSharpGoToDefinitionNavigableItem(document, textSpan) let workspace = document.Project.Solution.Workspace let navigationService = workspace.Services.GetService() - navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, cancellationToken) |> ignore + navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, cancellationToken) + |> ignore member _.NavigateToItem(navigableItem: FSharpNavigableItem, cancellationToken: CancellationToken) = @@ -702,27 +698,34 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, member _.TryGoToDefinition(position, cancellationToken) = try let gtd = GoToDefinition(metadataAsSource) + let gtdTask = cancellableTask { - use _ = TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinition, [||]) + use _ = + TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinition, [||]) + let! definition = gtd.FindDefinitionAsync(initialDoc, position) let! cancellationToken = CancellableTask.getCancellationToken () - + match definition with | ValueSome (FSharpGoToDefinitionResult.NavigableItem (navItem), _) -> gtd.NavigateToItem(navItem, cancellationToken) |> ignore return true | ValueSome (FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences), _) -> - gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken) |> ignore + gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken) + |> ignore + return true | _ -> return false } + ThreadHelper.JoinableTaskFactory.Run( SR.NavigatingTo(), (fun _ ct -> let cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, ct) CancellableTask.start cts.Token gtdTask), - TimeSpan.FromSeconds 1) + TimeSpan.FromSeconds 1 + ) with :? OperationCanceledException -> false diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs index a5e354a74cf..6d918913fc5 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs @@ -56,33 +56,35 @@ type internal FSharpNavigableSymbolSource(metadataAsSource) = match definition with | ValueNone -> return null | ValueSome (result, range) -> - let declarationTextSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range) let declarationSpan = Span(declarationTextSpan.Start, declarationTextSpan.Length) let symbolSpan = SnapshotSpan(snapshot, declarationSpan) match result with - | FSharpGoToDefinitionResult.NavigableItem (navItem) -> - return FSharpNavigableSymbol(navItem, symbolSpan, gtd) :> INavigableSymbol - - | FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences) -> - let nav = - { new INavigableSymbol with - member _.Navigate(_: INavigableRelationship) = - // Need to new up a CTS here instead of re-using the other one, since VS - // will navigate disconnected from the outer routine, leading to an - // OperationCancelledException if you use the one defined outside. - // TODO: review if it's a proper way of doing it - use ct = new CancellationTokenSource() - do gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, ct.Token) |> ignore - - member _.Relationships = seq { yield PredefinedNavigableRelationships.Definition } - - member _.SymbolSpan = symbolSpan - } - - return nav + | FSharpGoToDefinitionResult.NavigableItem (navItem) -> + return FSharpNavigableSymbol(navItem, symbolSpan, gtd) :> INavigableSymbol + + | FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences) -> + let nav = + { new INavigableSymbol with + member _.Navigate(_: INavigableRelationship) = + // Need to new up a CTS here instead of re-using the other one, since VS + // will navigate disconnected from the outer routine, leading to an + // OperationCancelledException if you use the one defined outside. + // TODO: review if it's a proper way of doing it + use ct = new CancellationTokenSource() + + do + gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, ct.Token) + |> ignore + + member _.Relationships = seq { yield PredefinedNavigableRelationships.Definition } + + member _.SymbolSpan = symbolSpan + } + + return nav with exc -> TelemetryReporter.ReportFault(TelemetryEvents.GoToDefinitionGetSymbol, FaultSeverity.General, exc) diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index e901e0c0113..8cce297ac0d 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -220,13 +220,7 @@ type internal FSharpNavigateToSearchService [] } |> CancellableTask.start cancellationToken - member _.SearchDocumentAsync - ( - document: Document, - searchPattern, - kinds, - cancellationToken - ) : Task> = + member _.SearchDocumentAsync(document: Document, searchPattern, kinds, cancellationToken) = cancellableTask { let! result = processDocument (createMatcherFor searchPattern) kinds document return Array.toImmutableArray result From 524c12c0ba8eee57700f3322ccae506c6fc88f6f Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Tue, 12 Sep 2023 17:05:44 +0200 Subject: [PATCH 11/20] wip --- .../Navigation/GoToDefinition.fs | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 3301faa30ce..d57b15f7fbc 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -696,37 +696,31 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, } member _.TryGoToDefinition(position, cancellationToken) = + // Once we migrate to Roslyn-exposed MAAS and sourcelink (https://github.com/dotnet/fsharp/issues/13951), this can be a "normal" task + // Wrap this in a try/with as if the user clicks "Cancel" on the thread dialog, we'll be cancelled. + // Task.Wait throws an exception if the task is cancelled, so be sure to catch it. try - let gtd = GoToDefinition(metadataAsSource) - - let gtdTask = - cancellableTask { - use _ = - TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinition, [||]) - - let! definition = gtd.FindDefinitionAsync(initialDoc, position) - let! cancellationToken = CancellableTask.getCancellationToken () - - match definition with - | ValueSome (FSharpGoToDefinitionResult.NavigableItem (navItem), _) -> - gtd.NavigateToItem(navItem, cancellationToken) |> ignore - return true - | ValueSome (FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences), _) -> - gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken) - |> ignore + use _ = + TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinition, [||]) - return true - | _ -> return false - } - - ThreadHelper.JoinableTaskFactory.Run( - SR.NavigatingTo(), - (fun _ ct -> - let cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, ct) - CancellableTask.start cts.Token gtdTask), - TimeSpan.FromSeconds 1 - ) - with :? OperationCanceledException -> + let gtd = GoToDefinition(metadataAsSource) + let gtdTask = gtd.FindDefinitionAsync(initialDoc, position) cancellationToken + + gtdTask.Wait() + + if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then + match gtdTask.Result with + | ValueSome (FSharpGoToDefinitionResult.NavigableItem (navItem), _) -> + gtd.NavigateToItem(navItem, cancellationToken) |> ignore + true + | ValueSome (FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences), _) -> + gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken) |> ignore + true + | _ -> false + else + false + with exc -> + TelemetryReporter.ReportFault(TelemetryEvents.GoToDefinition, FaultSeverity.General, exc) false [] From a6a16640abea427300aa1a203911da14660a5016 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Tue, 12 Sep 2023 18:00:20 +0200 Subject: [PATCH 12/20] fantomas --- .../src/FSharp.Editor/Navigation/GoToDefinition.fs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index d57b15f7fbc..a82a58e7406 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -704,7 +704,7 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GoToDefinition, [||]) let gtd = GoToDefinition(metadataAsSource) - let gtdTask = gtd.FindDefinitionAsync(initialDoc, position) cancellationToken + let gtdTask = gtd.FindDefinitionAsync (initialDoc, position) cancellationToken gtdTask.Wait() @@ -714,7 +714,9 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService, gtd.NavigateToItem(navItem, cancellationToken) |> ignore true | ValueSome (FSharpGoToDefinitionResult.ExternalAssembly (targetSymbolUse, metadataReferences), _) -> - gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken) |> ignore + gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken) + |> ignore + true | _ -> false else From 9116623e1073a632a50a3195a9cbe2e7038c3cb7 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Thu, 14 Sep 2023 15:48:51 +0200 Subject: [PATCH 13/20] fix external navigation --- .../src/FSharp.Editor/Common/Extensions.fs | 4 + .../Navigation/GoToDefinition.fs | 127 ++++++++++-------- 2 files changed, 73 insertions(+), 58 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 87d0007be82..caa87db2286 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -515,6 +515,10 @@ module Exception = |> flattenInner |> String.concat " ---> " +[] +module TextSpan = + let empty = TextSpan() + type Async with static member RunImmediateExceptOnUI(computation: Async<'T>, ?cancellationToken) = diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index a82a58e7406..7623c41c830 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -17,7 +17,6 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation open Microsoft.VisualStudio open Microsoft.VisualStudio.Shell -open Microsoft.VisualStudio.Shell.Interop open Microsoft.VisualStudio.LanguageServices open FSharp.Compiler.CodeAnalysis @@ -30,6 +29,7 @@ open System.Text.RegularExpressions open CancellableTasks open Microsoft.VisualStudio.FSharp.Editor.Telemetry open Microsoft.VisualStudio.Telemetry +open System.Collections.Generic module private Symbol = let fullName (root: ISymbol) : string = @@ -119,6 +119,59 @@ type internal FSharpGoToDefinitionResult = | ExternalAssembly of FSharpSymbolUse * MetadataReference seq type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = + + let rec areTypesEqual (ty1: FSharpType) (ty2: FSharpType) = + let ty1 = ty1.StripAbbreviations() + let ty2 = ty2.StripAbbreviations() + + let generic = + ty1.IsGenericParameter && ty2.IsGenericParameter + || (ty1.GenericArguments.Count = ty2.GenericArguments.Count + && (ty1.GenericArguments, ty2.GenericArguments) ||> Seq.forall2 areTypesEqual) + + if generic then + true + else + let namesEqual = ty1.TypeDefinition.DisplayName = ty2.TypeDefinition.DisplayName + let accessPathsEqual = ty1.TypeDefinition.AccessPath = ty2.TypeDefinition.AccessPath + namesEqual && accessPathsEqual + + let tryFindExternalSymbolUse (targetSymbolUse: FSharpSymbolUse) (x: FSharpSymbolUse) = + match x.Symbol, targetSymbolUse.Symbol with + | (:? FSharpEntity as symbol1), (:? FSharpEntity as symbol2) when x.IsFromDefinition -> + symbol1.DisplayName = symbol2.DisplayName + + | (:? FSharpMemberOrFunctionOrValue as symbol1), (:? FSharpMemberOrFunctionOrValue as symbol2) -> + symbol1.DisplayName = symbol2.DisplayName + && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with + | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName + | _ -> false) + && symbol1.GenericParameters.Count = symbol2.GenericParameters.Count + && symbol1.CurriedParameterGroups.Count = symbol2.CurriedParameterGroups.Count + && ((symbol1.CurriedParameterGroups, symbol2.CurriedParameterGroups) + ||> Seq.forall2 (fun pg1 pg2 -> + let pg1, pg2 = pg1.ToArray(), pg2.ToArray() + // We filter out/fixup first "unit" parameter in the group, since it just represents the `()` call notation, for example `"string".Clone()` will have one curried group with one parameter which type is unit. + let pg1 = // If parameter has no name and it's unit type, filter it out + if pg1.Length > 0 + && Option.isNone pg1[0].Name + && pg1[0].Type.StripAbbreviations().TypeDefinition.DisplayName = "Unit" then + pg1[1..] + else + pg1 + pg1.Length = pg2.Length + && ((pg1, pg2) ||> Seq.forall2 (fun p1 p2 -> areTypesEqual p1.Type p2.Type)))) + && areTypesEqual symbol1.ReturnParameter.Type symbol2.ReturnParameter.Type + | (:? FSharpField as symbol1), (:? FSharpField as symbol2) when x.IsFromDefinition -> + symbol1.DisplayName = symbol2.DisplayName + && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with + | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName + | _ -> false) + | (:? FSharpUnionCase as symbol1), (:? FSharpUnionCase as symbol2) -> + symbol1.DisplayName = symbol2.DisplayName + && symbol1.DeclaringEntity.CompiledName = symbol2.DeclaringEntity.CompiledName + | _ -> false + /// Use an origin document to provide the solution & workspace used to /// find the corresponding textSpan and INavigableItem for the range let rangeToNavigableItem (range: range, document: Document) = @@ -548,77 +601,35 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = match tmpShownDocOpt with | ValueSome tmpShownDoc -> let goToAsync = - asyncMaybe { + cancellableTask { - let! ct = Async.CancellationToken |> liftAsync + let! cancellationToken = CancellableTask.getCancellationToken () let! _, checkResults = tmpShownDoc.GetFSharpParseAndCheckResultsAsync("NavigateToExternalDeclaration") - |> CancellableTask.start ct - |> Async.AwaitTask - |> liftAsync - - let! r = - let rec areTypesEqual (ty1: FSharpType) (ty2: FSharpType) = - let ty1 = ty1.StripAbbreviations() - let ty2 = ty2.StripAbbreviations() - - let generic = - ty1.IsGenericParameter && ty2.IsGenericParameter - || (ty1.GenericArguments.Count = ty2.GenericArguments.Count - && (ty1.GenericArguments, ty2.GenericArguments) ||> Seq.forall2 areTypesEqual) - - if generic then - true - else - let namesEqual = ty1.TypeDefinition.DisplayName = ty2.TypeDefinition.DisplayName - let accessPathsEqual = ty1.TypeDefinition.AccessPath = ty2.TypeDefinition.AccessPath - namesEqual && accessPathsEqual + let r = // This tries to find the best possible location of the target symbol's location in the metadata source. // We really should rely on symbol equality within FCS instead of doing it here, // but the generated metadata as source isn't perfect for symbol equality. - checkResults.GetAllUsesOfAllSymbolsInFile(cancellationToken) - |> Seq.tryFindV (fun x -> - match x.Symbol, targetSymbolUse.Symbol with - | (:? FSharpEntity as symbol1), (:? FSharpEntity as symbol2) when x.IsFromDefinition -> - symbol1.DisplayName = symbol2.DisplayName - | (:? FSharpMemberOrFunctionOrValue as symbol1), (:? FSharpMemberOrFunctionOrValue as symbol2) -> - symbol1.DisplayName = symbol2.DisplayName - && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with - | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName - | _ -> false) - && symbol1.GenericParameters.Count = symbol2.GenericParameters.Count - && symbol1.CurriedParameterGroups.Count = symbol2.CurriedParameterGroups.Count - && ((symbol1.CurriedParameterGroups, symbol2.CurriedParameterGroups) - ||> Seq.forall2 (fun pg1 pg2 -> - pg1.Count = pg2.Count - && ((pg1, pg2) ||> Seq.forall2 (fun p1 p2 -> areTypesEqual p1.Type p2.Type)))) - && areTypesEqual symbol1.ReturnParameter.Type symbol2.ReturnParameter.Type - | (:? FSharpField as symbol1), (:? FSharpField as symbol2) when x.IsFromDefinition -> - symbol1.DisplayName = symbol2.DisplayName - && (match symbol1.DeclaringEntity, symbol2.DeclaringEntity with - | Some e1, Some e2 -> e1.CompiledName = e2.CompiledName - | _ -> false) - | (:? FSharpUnionCase as symbol1), (:? FSharpUnionCase as symbol2) -> - symbol1.DisplayName = symbol2.DisplayName - && symbol1.DeclaringEntity.CompiledName = symbol2.DeclaringEntity.CompiledName - | _ -> false) + let symbols = checkResults.GetAllUsesOfAllSymbolsInFile(cancellationToken) + + symbols + |> Seq.tryFindV (tryFindExternalSymbolUse targetSymbolUse) |> ValueOption.map (fun x -> x.Range) |> ValueOption.toOption - let span = - match RoslynHelpers.TryFSharpRangeToTextSpan(tmpShownDoc.GetTextAsync(cancellationToken).Result, r) with - | ValueSome span -> span - | _ -> TextSpan() + match r with + | None -> return TextSpan.empty + | Some r -> + let! text = tmpShownDoc.GetTextAsync(cancellationToken) + match RoslynHelpers.TryFSharpRangeToTextSpan(text, r) with + | ValueSome span -> return span + | _ -> return TextSpan.empty - return span } - let span = - match Async.RunImmediateExceptOnUI(goToAsync, cancellationToken = cancellationToken) with - | Some span -> span - | _ -> TextSpan() + let span = CancellableTask.runSynchronously cancellationToken goToAsync let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, span) this.NavigateToItem(navItem, cancellationToken) From a5ac09d8d2ddfa11544adc82e65dcfaa2e0074d7 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 18 Sep 2023 15:30:01 +0200 Subject: [PATCH 14/20] wip --- .../Classification/ClassificationService.fs | 8 +- .../CodeFixes/AddOpenCodeFixProvider.fs | 6 +- .../FSharp.Editor/Common/CancellableTasks.fs | 6 +- .../src/FSharp.Editor/Common/Extensions.fs | 12 +-- .../Completion/CompletionProvider.fs | 74 +++++++++---------- .../AssemblyContentProvider.fs | 11 ++- .../Structure/BlockStructureService.fs | 15 ++-- .../CompletionProviderTests.fs | 4 +- .../FsxCompletionProviderTests.fs | 2 +- 9 files changed, 68 insertions(+), 70 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs index 35eefa24828..28c6e96220c 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs @@ -247,6 +247,7 @@ type internal FSharpClassificationService [] () = TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSemanticCalssifications, eventProps) let! classificationData = document.GetFSharpSemanticClassificationAsync(nameof (FSharpClassificationService)) + let classificationDataLookup = toSemanticClassificationLookup classificationData do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup) addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result @@ -288,10 +289,11 @@ type internal FSharpClassificationService [] () = let classificationData = checkResults.GetSemanticClassification(Some targetRange) - let classificationDataLookup = itemToSemanticClassificationLookup classificationData - do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup) + if classificationData.Length > 0 then + let classificationDataLookup = itemToSemanticClassificationLookup classificationData + do! unopenedDocumentsSemanticClassificationCache.SetAsync(document, classificationDataLookup) - addSemanticClassification sourceText textSpan classificationData result + addSemanticClassification sourceText textSpan classificationData result } |> CancellableTask.startAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs index 08ce2ae91c6..b611146d2e0 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs @@ -121,8 +121,8 @@ type internal AddOpenCodeFixProvider [] (assemblyContentPr let entities = assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies checkResults - |> List.collect (fun s -> - [ + |> Array.collect (fun s -> + [| yield s.TopRequireQualifiedAccessParent, s.AutoOpenParent, s.Namespace, s.CleanedIdents if isAttribute then let lastIdent = s.CleanedIdents.[s.CleanedIdents.Length - 1] @@ -137,7 +137,7 @@ type internal AddOpenCodeFixProvider [] (assemblyContentPr s.Namespace, s.CleanedIdents |> Array.replace (s.CleanedIdents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 9)) - ]) + |]) let longIdent = ParsedInput.GetLongIdentAt parseResults.ParseTree unresolvedIdentRange.End diff --git a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs index 7d5ed3205d2..1cd71d4228e 100644 --- a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs +++ b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs @@ -1096,13 +1096,15 @@ module CancellableTasks = let inline whenAll (tasks: CancellableTask<'a> seq) = cancellableTask { let! ct = getCancellationToken () - return! Task.WhenAll (seq { for task in tasks do yield start ct task }) + let tasks = seq { for task in tasks do yield start ct task } + return! Task.WhenAll (tasks) } let inline whenAllTasks (tasks: CancellableTask seq) = cancellableTask { let! ct = getCancellationToken () - return! Task.WhenAll (seq { for task in tasks do yield startTask ct task }) + let tasks = seq { for task in tasks do yield startTask ct task } + return! Task.WhenAll (tasks) } let inline ignore ([] ctask: CancellableTask<_>) = toUnit ctask diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index caa87db2286..7a5b69355f9 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -302,15 +302,15 @@ module ValueOption = module IEnumerator = let chooseV f (e: IEnumerator<'T>) = let mutable started = false - let mutable curr = None + let mutable curr = ValueNone let get () = if not started then raise (InvalidOperationException("Not started")) match curr with - | None -> raise (InvalidOperationException("Already finished")) - | Some x -> x + | ValueNone -> raise (InvalidOperationException("Already finished")) + | ValueSome x -> x { new IEnumerator<'U> with member _.Current = get () @@ -321,12 +321,12 @@ module IEnumerator = if not started then started <- true - curr <- None + curr <- ValueNone while (curr.IsNone && e.MoveNext()) do curr <- f e.Current - Option.isSome curr + ValueOption.isSome curr member _.Reset() = raise (NotSupportedException("Reset is not supported")) @@ -387,7 +387,7 @@ module Seq = res - let chooseV chooser source = + let chooseV (chooser: 'a -> 'b voption) source = revamp (IEnumerator.chooseV chooser) source [] diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index d0ce229f64b..01a7a171da1 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -150,14 +150,14 @@ type internal FSharpCompletionProvider ( document: Document, caretPosition: int, - getAllSymbols: FSharpCheckFileResults -> AssemblySymbol list + getAllSymbols: FSharpCheckFileResults -> AssemblySymbol array ) = cancellableTask { - let! parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux") - let! ct = CancellableTask.getCancellationToken () + let! parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux") + let! sourceText = document.GetTextAsync(ct) let textLines = sourceText.Lines let caretLinePos = textLines.GetLinePosition(caretPosition) @@ -194,9 +194,8 @@ type internal FSharpCompletionProvider let results = List() - declarationItems <- - declarations.Items - |> Array.sortWith (fun x y -> + Array.sortInPlaceWith + (fun (x: DeclarationListItem) (y: DeclarationListItem) -> let mutable n = (not x.IsResolved).CompareTo(not y.IsResolved) if n <> 0 then @@ -219,7 +218,9 @@ type internal FSharpCompletionProvider if n <> 0 then n else - x.MinorPriority.CompareTo(y.MinorPriority)) + x.MinorPriority.CompareTo(y.MinorPriority)) declarations.Items + + declarationItems <- declarations.Items let maxHints = if mruItems.Values.Count = 0 then @@ -227,23 +228,19 @@ type internal FSharpCompletionProvider else Seq.max mruItems.Values - declarationItems - |> Array.iteri (fun number declarationItem -> + for number = 0 to declarationItems.Length - 1 do + let declarationItem = declarationItems[number] let glyph = Tokenizer.FSharpGlyphToRoslynGlyph(declarationItem.Glyph, declarationItem.Accessibility) - let namespaceName = - match declarationItem.NamespaceToOpen with - | Some namespaceToOpen -> namespaceToOpen - | _ -> null // Icky, but this is how roslyn handles it - - let filterText = + let namespaceName, filterText = match declarationItem.NamespaceToOpen, declarationItem.NameInList.Split '.' with // There is no namespace to open and the item name does not contain dots, so we don't need to pass special FilterText to Roslyn. - | None, [| _ |] -> null + | None, [| _ |] -> null, null + | Some namespaceToOpen, idents -> namespaceToOpen, Array.last idents // Either we have a namespace to open ("DateTime (open System)") or item name contains dots ("Array.map"), or both. // We are passing last part of long ident as FilterText. - | _, idents -> Array.last idents + | None, idents -> null, Array.last idents let completionItem = FSharpCommonCompletionItem @@ -282,8 +279,8 @@ type internal FSharpCompletionProvider let sortText = priority.ToString("D6") let completionItem = completionItem.WithSortText(sortText) - results.Add(completionItem)) - + results.Add(completionItem) + if results.Count > 0 && not declarations.IsForType @@ -352,11 +349,11 @@ type internal FSharpCompletionProvider ) if shouldProvideCompetion then - let getAllSymbols (fileCheckResults: FSharpCheckFileResults) = + let inline getAllSymbols (fileCheckResults: FSharpCheckFileResults) = if settings.IntelliSense.IncludeSymbolsFromUnopenedNamespacesOrModules then assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults) else - [] + [||] let! results = FSharpCompletionProvider.ProvideCompletionsAsyncAux(context.Document, context.Position, getAllSymbols) @@ -369,31 +366,26 @@ type internal FSharpCompletionProvider ( document: Document, completionItem: Completion.CompletionItem, - cancellationToken: CancellationToken + _cancellationToken: CancellationToken ) : Task = match completionItem.Properties.TryGetValue IndexPropName with | true, completionItemIndexStr when int completionItemIndexStr >= declarationItems.Length -> Task.FromResult CompletionDescription.Empty | true, completionItemIndexStr -> - // TODO: Not entirely sure why do we use tasks here, since everything here is synchronous. - cancellableTask { - use _logBlock = - Logger.LogBlockMessage document.Name LogEditorFunctionId.Completion_GetDescriptionAsync - - let completionItemIndex = int completionItemIndexStr + use _logBlock = + Logger.LogBlockMessage document.Name LogEditorFunctionId.Completion_GetDescriptionAsync - let declarationItem = declarationItems.[completionItemIndex] - let description = declarationItem.Description - let documentation = List() - let collector = RoslynHelpers.CollectTaggedText documentation - // mix main description and xmldoc by using one collector - XmlDocumentation.BuildDataTipText(documentationBuilder, collector, collector, collector, collector, collector, description) + let completionItemIndex = int completionItemIndexStr - return CompletionDescription.Create(documentation.ToImmutableArray()) - } - |> CancellableTask.start cancellationToken + let declarationItem = declarationItems.[completionItemIndex] + let description = declarationItem.Description + let documentation = List() + let collector = RoslynHelpers.CollectTaggedText documentation + // mix main description and xmldoc by using one collector + XmlDocumentation.BuildDataTipText(documentationBuilder, collector, collector, collector, collector, collector, description) + Task.FromResult(CompletionDescription.Create(documentation.ToImmutableArray())) | _ -> match completionItem.Properties.TryGetValue KeywordDescription with | true, keywordDescription -> Task.FromResult(CompletionDescription.FromText(keywordDescription)) @@ -406,8 +398,8 @@ type internal FSharpCompletionProvider let fullName = match item.Properties.TryGetValue FullNamePropName with - | true, x -> Some x - | _ -> None + | true, x -> ValueSome x + | _ -> ValueNone // do not add extension members and unresolved symbols to the MRU list if @@ -415,7 +407,7 @@ type internal FSharpCompletionProvider && not (item.Properties.ContainsKey IsExtensionMemberPropName) then match fullName with - | Some fullName -> + | ValueSome fullName -> match mruItems.TryGetValue fullName with | true, hints -> mruItems.[fullName] <- hints + 1 | _ -> mruItems.[fullName] <- 1 @@ -439,7 +431,7 @@ type internal FSharpCompletionProvider let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpCompletionProvider)) let fullNameIdents = - fullName |> Option.map (fun x -> x.Split '.') |> Option.defaultValue [||] + fullName |> ValueOption.map (fun x -> x.Split '.') |> ValueOption.defaultValue [||] let insertionPoint = if settings.CodeFixes.AlwaysPlaceOpensAtTopLevel then diff --git a/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs b/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs index f34ca68a2dc..e2e06d2725d 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs @@ -12,8 +12,8 @@ open FSharp.Compiler.EditorServices type internal AssemblyContentProvider() = let entityCache = EntityCache() - member x.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults: FSharpCheckFileResults) = - [ + member _.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults: FSharpCheckFileResults) = + [| yield! AssemblyContent.GetAssemblySignatureContent AssemblyContentType.Full fileCheckResults.PartialAssemblySignature // FCS sometimes returns several FSharpAssembly for single referenced assembly. // For example, it returns two different ones for Swensen.Unquote; the first one @@ -24,11 +24,10 @@ type internal AssemblyContentProvider() = fileCheckResults.ProjectContext.GetReferencedAssemblies() |> Seq.groupBy (fun asm -> asm.FileName) |> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms) - |> Seq.toList - |> List.rev // if mscorlib.dll is the first then FSC raises exception when we try to - // get Content.Entities from it. + |> Seq.rev // if mscorlib.dll is the first then FSC raises exception when we try to get Content.Entities from it. + |> Seq.toArray for fileName, signatures in assembliesByFileName do let contentType = AssemblyContentType.Public // it's always Public for now since we don't support InternalsVisibleTo attribute yet yield! AssemblyContent.GetAssemblyContent entityCache.Locking contentType fileName signatures - ] + |] diff --git a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs index ca11623b55e..c84a82b9632 100644 --- a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs +++ b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs @@ -115,12 +115,15 @@ module internal BlockStructure = | Scope.While | Scope.For -> false + [] + let ellipsis = "..." + let createBlockSpans isBlockStructureEnabled (sourceText: SourceText) (parsedInput: ParsedInput) = let linetext = sourceText.Lines |> Seq.map (fun x -> x.ToString()) |> Seq.toArray Structure.getOutliningRanges linetext parsedInput |> Seq.distinctBy (fun x -> x.Range.StartLine) - |> Seq.choose (fun scopeRange -> + |> Seq.chooseV (fun scopeRange -> // the range of text to collapse let textSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, scopeRange.CollapseRange) @@ -132,9 +135,9 @@ module internal BlockStructure = let line = sourceText.Lines.GetLineFromPosition textSpan.Start let bannerText = - match Option.ofNullable (line.Span.Intersection textSpan) with - | Some span -> sourceText.GetSubText(span).ToString() + "..." - | None -> "..." + match ValueOption.ofNullable (line.Span.Intersection textSpan) with + | ValueSome span -> sourceText.GetSubText(span).ToString() + ellipsis + | ValueNone -> ellipsis let blockType = if isBlockStructureEnabled then @@ -142,8 +145,8 @@ module internal BlockStructure = else FSharpBlockTypes.Nonstructural - Some(FSharpBlockSpan(blockType, true, textSpan, hintSpan, bannerText, autoCollapse = isAutoCollapsible scopeRange.Scope)) - | _, _ -> None) + ValueSome(FSharpBlockSpan(blockType, true, textSpan, hintSpan, bannerText, autoCollapse = isAutoCollapsible scopeRange.Scope)) + | _, _ -> ValueNone) open BlockStructure open CancellableTasks diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index e8294caff85..3b8a354e570 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -34,7 +34,7 @@ module CompletionProviderTests = let results = let task = - FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [])) + FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [||])) |> CancellableTask.start CancellationToken.None task.Result |> Seq.map (fun result -> result.DisplayText) @@ -82,7 +82,7 @@ module CompletionProviderTests = let actual = let task = - FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [])) + FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [||])) |> CancellableTask.start CancellationToken.None task.Result diff --git a/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs index 49955dedbb4..9d9432aaf13 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs @@ -32,7 +32,7 @@ type Worker() = let actual = let x = - FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [])) + FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [||])) |> CancellableTask.start CancellationToken.None x.Result From 27cb2f3f507cd58e802ce1030a915d145ebe7369 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 18 Sep 2023 16:38:51 +0200 Subject: [PATCH 15/20] wip --- .../BraceCompletionSessionProvider.fs | 28 ++++++-------- .../src/FSharp.Editor/Common/Pervasive.fs | 38 +++++++++---------- .../src/FSharp.Editor/Common/RoslynHelpers.fs | 7 ---- .../LanguageService/WorkspaceExtensions.fs | 12 ------ .../Navigation/FindUsagesService.fs | 4 +- 5 files changed, 33 insertions(+), 56 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs index d9339a099d7..1fd0d3da25b 100644 --- a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs +++ b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs @@ -22,14 +22,10 @@ open Microsoft.CodeAnalysis.Classification [] module BraceCompletionSessionProviderHelpers = - let tryGetCaretPoint (buffer: ITextBuffer) (session: IBraceCompletionSession) = - let point = - session.TextView.Caret.Position.Point.GetPoint(buffer, PositionAffinity.Predecessor) + let inline tryGetCaretPoint (buffer: ITextBuffer) (session: IBraceCompletionSession) = + ValueOption.ofNullable (session.TextView.Caret.Position.Point.GetPoint(buffer, PositionAffinity.Predecessor)) - if point.HasValue then Some point.Value else None - - let tryGetCaretPosition session = - session |> tryGetCaretPoint session.SubjectBuffer + let inline tryGetCaretPosition (session: IBraceCompletionSession) = tryGetCaretPoint session.SubjectBuffer session let tryInsertAdditionalBracePair (session: IBraceCompletionSession) openingChar closingChar = let sourceCode = session.TextView.TextSnapshot @@ -134,13 +130,13 @@ type BraceCompletionSession // exit without setting the closing point which will take us off the stack edit.Cancel() undo.Cancel() - None + ValueNone else - Some(edit.Apply()) // FIXME: perhaps, it should be ApplyAndLogExceptions() + ValueSome(edit.Apply()) // FIXME: perhaps, it should be ApplyAndLogExceptions() match nextSnapshot with - | None -> () - | Some (nextSnapshot) -> + | ValueNone -> () + | ValueSome (nextSnapshot) -> let beforePoint = beforeTrackingPoint.GetPoint(textView.TextSnapshot) @@ -185,7 +181,7 @@ type BraceCompletionSession if closingSnapshotPoint.Position > 0 then match tryGetCaretPosition this with - | Some caretPos when not (this.HasNoForwardTyping(caretPos, closingSnapshotPoint.Subtract(1))) -> true + | ValueSome caretPos when not (this.HasNoForwardTyping(caretPos, closingSnapshotPoint.Subtract(1))) -> true | _ -> false else false @@ -265,7 +261,7 @@ type BraceCompletionSession match caretPos with // ensure that we are within the session before clearing - | Some caretPos when + | ValueSome caretPos when caretPos.Position < closingSnapshotPoint.Position && closingSnapshotPoint.Position > 0 -> @@ -310,7 +306,7 @@ type BraceCompletionSession member this.PostReturn() = match tryGetCaretPosition this with - | Some caretPos -> + | ValueSome caretPos -> let closingSnapshotPoint = closingPoint.GetPoint(subjectBuffer.CurrentSnapshot) if @@ -580,13 +576,13 @@ type BraceCompletionSessionProvider [] maybe { let! document = openingPoint.Snapshot.GetOpenDocumentInCurrentContextWithChanges() - |> Option.ofObj + |> ValueOption.ofObj let! sessionFactory = document.TryGetLanguageService() let! session = sessionFactory.TryCreateSession(document, openingPoint.Position, openingBrace, CancellationToken.None) - |> Option.ofObj + |> ValueOption.ofObj let undoHistory = undoManager.GetTextBufferUndoManager(textView.TextBuffer).TextBufferUndoHistory diff --git a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs index 0ad2b8ce8c7..bbd45cdbb43 100644 --- a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs +++ b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs @@ -10,14 +10,14 @@ let inline isSignatureFile (filePath: string) = String.Equals(Path.GetExtension filePath, ".fsi", StringComparison.OrdinalIgnoreCase) /// Returns the corresponding signature file path for given implementation file path or vice versa -let getOtherFile (filePath: string) = +let inline getOtherFile (filePath: string) = if isSignatureFile filePath then Path.ChangeExtension(filePath, ".fs") else Path.ChangeExtension(filePath, ".fsi") /// Checks if the file paht ends with '.fsx' or '.fsscript' -let isScriptFile (filePath: string) = +let inline isScriptFile (filePath: string) = let ext = Path.GetExtension filePath String.Equals(ext, ".fsx", StringComparison.OrdinalIgnoreCase) @@ -42,7 +42,7 @@ type MaybeBuilder() = // (unit -> M<'T>) -> M<'T> [] - member _.Delay(f: unit -> 'T option) : 'T option = f () + member inline _.Delay([] f: unit -> 'T option) : 'T option = f () // M<'T> -> M<'T> -> M<'T> // or @@ -55,18 +55,18 @@ type MaybeBuilder() = // M<'T> * ('T -> M<'U>) -> M<'U> [] - member inline _.Bind(value, f: 'T -> 'U option) : 'U option = Option.bind f value + member inline _.Bind(value, [] f: 'T -> 'U option) : 'U option = Option.bind f value // M<'T> * ('T -> M<'U>) -> M<'U> [] - member inline _.Bind(value: 'T voption, f: 'T -> 'U option) : 'U option = + member inline _.Bind(value: 'T voption, [] f: 'T -> 'U option) : 'U option = match value with | ValueNone -> None | ValueSome value -> f value // 'T * ('T -> M<'U>) -> M<'U> when 'U :> IDisposable [] - member _.Using(resource: ('T :> System.IDisposable), body: _ -> _ option) : _ option = + member inline _.Using(resource: ('T :> System.IDisposable), [] body: _ -> _ option) : _ option = try body resource finally @@ -86,28 +86,28 @@ type MaybeBuilder() = // or // seq<'T> * ('T -> M<'U>) -> seq> [] - member x.For(sequence: seq<_>, body: 'T -> unit option) : _ option = + member inline x.For(sequence: seq<_>, [] body: 'T -> unit option) : _ option = // OPTIMIZE: This could be simplified so we don't need to make calls to Using, While, Delay. x.Using(sequence.GetEnumerator(), (fun enum -> x.While(enum.MoveNext, x.Delay(fun () -> body enum.Current)))) let maybe = MaybeBuilder() -[] +[] type AsyncMaybeBuilder() = [] - member _.Return value : Async<'T option> = Some value |> async.Return + member inline _.Return value : Async<'T option> = async.Return(Some value) [] - member _.ReturnFrom value : Async<'T option> = value + member inline _.ReturnFrom value : Async<'T option> = value [] - member _.ReturnFrom(value: 'T option) : Async<'T option> = async.Return value + member inline _.ReturnFrom(value: 'T option) : Async<'T option> = async.Return value [] - member _.Zero() : Async = Some() |> async.Return + member inline _.Zero() : Async = async.Return(Some()) [] - member _.Delay(f: unit -> Async<'T option>) : Async<'T option> = async.Delay f + member inline _.Delay([] f: unit -> Async<'T option>) : Async<'T option> = async.Delay f [] member _.Combine(r1, r2: Async<'T option>) : Async<'T option> = @@ -120,7 +120,7 @@ type AsyncMaybeBuilder() = } [] - member _.Bind(value: Async<'T option>, f: 'T -> Async<'U option>) : Async<'U option> = + member inline _.Bind(value: Async<'T option>, [] f: 'T -> Async<'U option>) : Async<'U option> = async { let! value' = value @@ -130,14 +130,14 @@ type AsyncMaybeBuilder() = } [] - member _.Bind(value: System.Threading.Tasks.Task<'T>, f: 'T -> Async<'U option>) : Async<'U option> = + member inline _.Bind(value: System.Threading.Tasks.Task<'T>, [] f: 'T -> Async<'U option>) : Async<'U option> = async { let! value' = Async.AwaitTask value return! f value' } [] - member _.Bind(value: 'T option, f: 'T -> Async<'U option>) : Async<'U option> = + member inline _.Bind(value: 'T option, [] f: 'T -> Async<'U option>) : Async<'U option> = async { match value with | None -> return None @@ -145,7 +145,7 @@ type AsyncMaybeBuilder() = } [] - member _.Bind(value: 'T voption, f: 'T -> Async<'U option>) : Async<'U option> = + member inline _.Bind(value: 'T voption, [] f: 'T -> Async<'U option>) : Async<'U option> = async { match value with | ValueNone -> return None @@ -153,7 +153,7 @@ type AsyncMaybeBuilder() = } [] - member _.Using(resource: ('T :> IDisposable), body: 'T -> Async<'U option>) : Async<'U option> = + member inline _.Using(resource: ('T :> IDisposable), [] body: 'T -> Async<'U option>) : Async<'U option> = async { use resource = resource return! body resource @@ -167,7 +167,7 @@ type AsyncMaybeBuilder() = x.Zero() [] - member x.For(sequence: seq<_>, body: 'T -> Async) : Async = + member inline x.For(sequence: seq<_>, [] body: 'T -> Async) : Async = x.Using(sequence.GetEnumerator(), (fun enum -> x.While(enum.MoveNext, x.Delay(fun () -> body enum.Current)))) [] diff --git a/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs b/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs index 2c0475d4d37..eec57d2a238 100644 --- a/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs +++ b/vsintegration/src/FSharp.Editor/Common/RoslynHelpers.fs @@ -62,13 +62,6 @@ module internal RoslynHelpers = (Position.fromZ startLine.LineNumber (textSpan.Start - startLine.Start)) (Position.fromZ endLine.LineNumber (textSpan.End - endLine.Start)) - let GetCompletedTaskResult (task: Task<'TResult>) = - if task.Status = TaskStatus.RanToCompletion then - task.Result - else - Assert.Exception(task.Exception.GetBaseException()) - raise (task.Exception.GetBaseException()) - /// maps from `TextTag` of the F# Compiler to Roslyn `TextTags` for use in tooltips let roslynTag = function diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index a13b116c4ab..e994b957111 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -2,10 +2,7 @@ module internal Microsoft.VisualStudio.FSharp.Editor.WorkspaceExtensions open System -open System.Diagnostics open System.Runtime.CompilerServices -open System.Threading -open System.Threading.Tasks open Microsoft.CodeAnalysis open Microsoft.VisualStudio.FSharp.Editor @@ -15,8 +12,6 @@ open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks -open CancellableTasks -open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks [] module private CheckerExtensions = @@ -161,13 +156,6 @@ type Document with ProjectCache.Projects.GetValue(this.Project, ConditionalWeakTable<_, _>.CreateValueCallback (fun _ -> result)) } - /// Get the compilation defines from F# project that is associated with the given F# document. - member this.GetFSharpCompilationDefinesAsync(userOpName) = - async { - let! _, _, parsingOptions, _ = this.GetFSharpCompilationOptionsAsync(userOpName) - return CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions - } - /// Get the compilation defines and language version from F# project that is associated with the given F# document. member this.GetFsharpParsingOptionsAsync(userOpName) = async { diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs index ab27f8d92af..429b21ce64a 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs @@ -122,14 +122,14 @@ module FSharpFindUsagesService = let! declarationSpans = match declarationRange with - | Some range -> cancellableTask { return! rangeToDocumentSpans (document.Project.Solution, range) } + | Some range -> rangeToDocumentSpans (document.Project.Solution, range) | None -> CancellableTask.singleton [||] let declarationSpans = declarationSpans |> Array.distinctBy (fun x -> x.Document.FilePath, x.Document.Project.FilePath) - let isExternal = declarationSpans |> Array.isEmpty + let isExternal = Array.isEmpty declarationSpans let displayParts = ImmutableArray.Create(Microsoft.CodeAnalysis.TaggedText(TextTags.Text, symbol.Ident.idText)) From 4901a68a5640465db26f8eccadb0974d4c278d73 Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Wed, 20 Sep 2023 12:30:26 +0200 Subject: [PATCH 16/20] wip --- vsintegration/src/FSharp.Editor/Common/Extensions.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 7a5b69355f9..0f058aee79b 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -269,7 +269,7 @@ module String = [] module Option = - let guard (x: bool) : Option = if x then Some() else None + let guard (x: bool) : ValueOption = if x then ValueSome() else ValueNone let attempt (f: unit -> 'T) = try From bd4baba8abcfe2bb87047865bb42008ce10cadcc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 13:21:15 +0000 Subject: [PATCH 17/20] Automated command ran: fantomas Co-authored-by: vzarytovskii <1260985+vzarytovskii@users.noreply.github.com> --- .../BraceCompletionSessionProvider.fs | 3 ++- .../Completion/CompletionProvider.fs | 10 ++++++--- .../LanguageService/WorkspaceExtensions.fs | 1 - .../Navigation/GoToDefinition.fs | 22 ++++++++++--------- .../Structure/BlockStructureService.fs | 4 +++- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs index 1fd0d3da25b..6c090529f2c 100644 --- a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs +++ b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs @@ -25,7 +25,8 @@ module BraceCompletionSessionProviderHelpers = let inline tryGetCaretPoint (buffer: ITextBuffer) (session: IBraceCompletionSession) = ValueOption.ofNullable (session.TextView.Caret.Position.Point.GetPoint(buffer, PositionAffinity.Predecessor)) - let inline tryGetCaretPosition (session: IBraceCompletionSession) = tryGetCaretPoint session.SubjectBuffer session + let inline tryGetCaretPosition (session: IBraceCompletionSession) = + tryGetCaretPoint session.SubjectBuffer session let tryInsertAdditionalBracePair (session: IBraceCompletionSession) openingChar closingChar = let sourceCode = session.TextView.TextSnapshot diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index 01a7a171da1..e78e7783790 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -218,7 +218,8 @@ type internal FSharpCompletionProvider if n <> 0 then n else - x.MinorPriority.CompareTo(y.MinorPriority)) declarations.Items + x.MinorPriority.CompareTo(y.MinorPriority)) + declarations.Items declarationItems <- declarations.Items @@ -230,6 +231,7 @@ type internal FSharpCompletionProvider for number = 0 to declarationItems.Length - 1 do let declarationItem = declarationItems[number] + let glyph = Tokenizer.FSharpGlyphToRoslynGlyph(declarationItem.Glyph, declarationItem.Accessibility) @@ -280,7 +282,7 @@ type internal FSharpCompletionProvider let sortText = priority.ToString("D6") let completionItem = completionItem.WithSortText(sortText) results.Add(completionItem) - + if results.Count > 0 && not declarations.IsForType @@ -431,7 +433,9 @@ type internal FSharpCompletionProvider let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpCompletionProvider)) let fullNameIdents = - fullName |> ValueOption.map (fun x -> x.Split '.') |> ValueOption.defaultValue [||] + fullName + |> ValueOption.map (fun x -> x.Split '.') + |> ValueOption.defaultValue [||] let insertionPoint = if settings.CodeFixes.AlwaysPlaceOpensAtTopLevel then diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index e994b957111..3f3842b27ce 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -12,7 +12,6 @@ open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks - [] module private CheckerExtensions = diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 7623c41c830..0c5727fb4cb 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -138,8 +138,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let tryFindExternalSymbolUse (targetSymbolUse: FSharpSymbolUse) (x: FSharpSymbolUse) = match x.Symbol, targetSymbolUse.Symbol with - | (:? FSharpEntity as symbol1), (:? FSharpEntity as symbol2) when x.IsFromDefinition -> - symbol1.DisplayName = symbol2.DisplayName + | (:? FSharpEntity as symbol1), (:? FSharpEntity as symbol2) when x.IsFromDefinition -> symbol1.DisplayName = symbol2.DisplayName | (:? FSharpMemberOrFunctionOrValue as symbol1), (:? FSharpMemberOrFunctionOrValue as symbol2) -> symbol1.DisplayName = symbol2.DisplayName @@ -153,12 +152,15 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let pg1, pg2 = pg1.ToArray(), pg2.ToArray() // We filter out/fixup first "unit" parameter in the group, since it just represents the `()` call notation, for example `"string".Clone()` will have one curried group with one parameter which type is unit. let pg1 = // If parameter has no name and it's unit type, filter it out - if pg1.Length > 0 + if + pg1.Length > 0 && Option.isNone pg1[0].Name - && pg1[0].Type.StripAbbreviations().TypeDefinition.DisplayName = "Unit" then - pg1[1..] + && pg1[0].Type.StripAbbreviations().TypeDefinition.DisplayName = "Unit" + then + pg1[1..] else - pg1 + pg1 + pg1.Length = pg2.Length && ((pg1, pg2) ||> Seq.forall2 (fun p1 p2 -> areTypesEqual p1.Type p2.Type)))) && areTypesEqual symbol1.ReturnParameter.Type symbol2.ReturnParameter.Type @@ -605,16 +607,15 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let! cancellationToken = CancellableTask.getCancellationToken () - let! _, checkResults = - tmpShownDoc.GetFSharpParseAndCheckResultsAsync("NavigateToExternalDeclaration") + let! _, checkResults = tmpShownDoc.GetFSharpParseAndCheckResultsAsync("NavigateToExternalDeclaration") let r = // This tries to find the best possible location of the target symbol's location in the metadata source. // We really should rely on symbol equality within FCS instead of doing it here, // but the generated metadata as source isn't perfect for symbol equality. let symbols = checkResults.GetAllUsesOfAllSymbolsInFile(cancellationToken) - - symbols + + symbols |> Seq.tryFindV (tryFindExternalSymbolUse targetSymbolUse) |> ValueOption.map (fun x -> x.Range) |> ValueOption.toOption @@ -623,6 +624,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = | None -> return TextSpan.empty | Some r -> let! text = tmpShownDoc.GetTextAsync(cancellationToken) + match RoslynHelpers.TryFSharpRangeToTextSpan(text, r) with | ValueSome span -> return span | _ -> return TextSpan.empty diff --git a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs index c84a82b9632..f96ea848c1f 100644 --- a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs +++ b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs @@ -145,7 +145,9 @@ module internal BlockStructure = else FSharpBlockTypes.Nonstructural - ValueSome(FSharpBlockSpan(blockType, true, textSpan, hintSpan, bannerText, autoCollapse = isAutoCollapsible scopeRange.Scope)) + ValueSome( + FSharpBlockSpan(blockType, true, textSpan, hintSpan, bannerText, autoCollapse = isAutoCollapsible scopeRange.Scope) + ) | _, _ -> ValueNone) open BlockStructure From 29b7c4b828ae0a1a0ae92c2005f882e4c13e5c5a Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 9 Oct 2023 12:12:27 +0200 Subject: [PATCH 18/20] wip --- .../CodeFixes/AddOpenCodeFixProvider.fs | 31 +++++++++++-------- .../LanguageService/LanguageService.fs | 3 +- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs index 8b507c6fbeb..ac0b952f65f 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs @@ -55,21 +55,26 @@ type internal AddOpenCodeFixProvider [] (assemblyContentPr // attribute, shouldn't be here | line when line.StartsWith "[<" && line.EndsWith ">]" -> - let moduleDeclLineNumber = + let moduleDeclLineNumberOpt = sourceText.Lines |> Seq.skip insertionLineNumber - |> Seq.findIndex (fun line -> line.ToString().Contains "module") - // add back the skipped lines - |> fun i -> insertionLineNumber + i + |> Seq.tryFindIndex (fun line -> line.ToString().Contains "module") - let moduleDeclLineText = sourceText.Lines[ moduleDeclLineNumber ].ToString().Trim() + match moduleDeclLineNumberOpt with + // implicit top level module + | None -> insertionLineNumber, $"{margin}open {ns}{br}{br}" + // explicit top level module + | Some number -> + // add back the skipped lines + let moduleDeclLineNumber = insertionLineNumber + number + let moduleDeclLineText = sourceText.Lines[ moduleDeclLineNumber ].ToString().Trim() - if moduleDeclLineText.EndsWith "=" then - insertionLineNumber, $"{margin}open {ns}{br}{br}" - else - moduleDeclLineNumber + 2, $"{margin}open {ns}{br}{br}" + if moduleDeclLineText.EndsWith "=" then + insertionLineNumber, $"{margin}open {ns}{br}{br}" + else + moduleDeclLineNumber + 2, $"{margin}open {ns}{br}{br}" - // something else, shot in the dark + // implicit top level module | _ -> insertionLineNumber, $"{margin}open {ns}{br}{br}" | ScopeKind.Namespace -> insertionLineNumber + 3, $"{margin}open {ns}{br}{br}" @@ -183,8 +188,8 @@ type internal AddOpenCodeFixProvider [] (assemblyContentPr let entities = assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies checkResults - |> Array.collect (fun s -> - [| + |> List.collect (fun s -> + [ yield s.TopRequireQualifiedAccessParent, s.AutoOpenParent, s.Namespace, s.CleanedIdents if isAttribute then let lastIdent = s.CleanedIdents.[s.CleanedIdents.Length - 1] @@ -199,7 +204,7 @@ type internal AddOpenCodeFixProvider [] (assemblyContentPr s.Namespace, s.CleanedIdents |> Array.replace (s.CleanedIdents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 9)) - |]) + ]) ParsedInput.GetLongIdentAt parseResults.ParseTree unresolvedIdentRange.End |> Option.bind (fun longIdent -> diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index e77ce6424b5..b22c772fc00 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -94,9 +94,10 @@ type internal FSharpWorkspaceServiceFactory [] let getSource filename = async { + let! ct = Async.CancellationToken match workspace.CurrentSolution.TryGetDocumentFromPath filename with | ValueSome document -> - let! text = document.GetTextAsync() |> Async.AwaitTask + let! text = document.GetTextAsync(ct) |> Async.AwaitTask return Some(text.ToFSharpSourceText()) | ValueNone -> return None } From dcbdb2301c16ccd9487095b1913d51c97f433d0b Mon Sep 17 00:00:00 2001 From: Vlad Zarytovskii Date: Mon, 9 Oct 2023 15:28:08 +0200 Subject: [PATCH 19/20] wip --- global.json | 3 +-- .../src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs | 6 +++--- .../src/FSharp.Editor/Completion/CompletionProvider.fs | 2 +- .../LanguageService/AssemblyContentProvider.fs | 1 - .../FSharpAnalysisSaveFileCommandHandler.fs | 3 ++- .../FSharp.Editor/LanguageService/WorkspaceExtensions.fs | 3 +-- .../src/FSharp.Editor/Telemetry/TelemetryReporter.fs | 7 +++++-- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/global.json b/global.json index 8b3ca467ab8..1ffce949700 100644 --- a/global.json +++ b/global.json @@ -1,8 +1,7 @@ { "sdk": { "version": "8.0.100-rc.1.23455.8", - "allowPrerelease": true, - "rollForward": "latestMajor" + "allowPrerelease": true }, "tools": { "dotnet": "8.0.100-rc.1.23455.8", diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs index ac0b952f65f..e2db1a58dd4 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddOpenCodeFixProvider.fs @@ -188,8 +188,8 @@ type internal AddOpenCodeFixProvider [] (assemblyContentPr let entities = assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies checkResults - |> List.collect (fun s -> - [ + |> Array.collect (fun s -> + [| yield s.TopRequireQualifiedAccessParent, s.AutoOpenParent, s.Namespace, s.CleanedIdents if isAttribute then let lastIdent = s.CleanedIdents.[s.CleanedIdents.Length - 1] @@ -204,7 +204,7 @@ type internal AddOpenCodeFixProvider [] (assemblyContentPr s.Namespace, s.CleanedIdents |> Array.replace (s.CleanedIdents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 9)) - ]) + |]) ParsedInput.GetLongIdentAt parseResults.ParseTree unresolvedIdentRange.End |> Option.bind (fun longIdent -> diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index e78e7783790..7d121bf4ab6 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -355,7 +355,7 @@ type internal FSharpCompletionProvider if settings.IntelliSense.IncludeSymbolsFromUnopenedNamespacesOrModules then assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults) else - [||] + Array.empty let! results = FSharpCompletionProvider.ProvideCompletionsAsyncAux(context.Document, context.Position, getAllSymbols) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs b/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs index e2e06d2725d..7a4b39c6737 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/AssemblyContentProvider.fs @@ -25,7 +25,6 @@ type internal AssemblyContentProvider() = |> Seq.groupBy (fun asm -> asm.FileName) |> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms) |> Seq.rev // if mscorlib.dll is the first then FSC raises exception when we try to get Content.Entities from it. - |> Seq.toArray for fileName, signatures in assembliesByFileName do let contentType = AssemblyContentType.Public // it's always Public for now since we don't support InternalsVisibleTo attribute yet diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs index 1b44c731e7a..305cd868921 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpAnalysisSaveFileCommandHandler.fs @@ -13,6 +13,7 @@ open Microsoft.VisualStudio.Text.Editor.Commanding.Commands open Microsoft.VisualStudio.Commanding open Microsoft.VisualStudio.Utilities open CancellableTasks +open Microsoft.VisualStudio.FSharp.Editor.Telemetry // This causes re-analysis to happen when a F# document is saved. // We do this because FCS relies on the file system and existing open documents @@ -88,7 +89,7 @@ type internal FSharpAnalysisSaveFileCommandHandler [] (ana if docIdsToReanalyze.Length > 0 then analyzerService.Reanalyze(workspace, documentIds = docIdsToReanalyze) with ex -> - Telemetry.TelemetryReporter.ReportFault("FSharpAnalysisSaveFileCommandHandler.ExecuteCommand", e = ex) + TelemetryReporter.ReportFault(TelemetryEvents.AnalysisSaveFileHandler, e = ex) logException ex } |> CancellableTask.startWithoutCancellation diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index 3f3842b27ce..ddc2e593b76 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -94,8 +94,7 @@ module private CheckerExtensions = return results else - let! results = parseAndCheckFile - return results + return! parseAndCheckFile } /// Parse and check the source text from the Roslyn document. diff --git a/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs b/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs index fae5dde7797..2a520d72066 100644 --- a/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs +++ b/vsintegration/src/FSharp.Editor/Telemetry/TelemetryReporter.fs @@ -48,6 +48,9 @@ module TelemetryEvents = [] let GoToDefinitionGetSymbol = "gotodefinition/getsymbol" + [] + let AnalysisSaveFileHandler = "analysis/savefilehandler" + // TODO: needs to be something more sophisticated in future [] type TelemetryThrottlingStrategy = @@ -108,7 +111,7 @@ type TelemetryReporter private (name: string, props: (string * obj) array, stopw static member ReportFault(name, ?severity: FaultSeverity, ?e: exn) = if TelemetryReporter.SendAdditionalTelemetry.Value then - let faultName = String.Concat(name, "/fault") + let faultName = String.Concat(TelemetryReporter.eventPrefix, name, "/fault") match severity, e with | Some s, Some e -> TelemetryService.DefaultSession.PostFault(faultName, name, s, e) @@ -120,7 +123,7 @@ type TelemetryReporter private (name: string, props: (string * obj) array, stopw static member ReportCustomFailure(name, ?props) = if TelemetryReporter.SendAdditionalTelemetry.Value then let props = defaultArg props [||] - let name = String.Concat(name, "/failure") + let name = String.Concat(TelemetryReporter.eventPrefix, name, "/failure") let event = TelemetryReporter.createEvent name props TelemetryService.DefaultSession.PostEvent event From 5130812f4327e014611d2a4c7380e7fcca81158a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:00:44 +0000 Subject: [PATCH 20/20] Automated command ran: fantomas Co-authored-by: vzarytovskii <1260985+vzarytovskii@users.noreply.github.com> --- .../src/FSharp.Editor/LanguageService/LanguageService.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index e224a92d169..0512a48c1ad 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -95,6 +95,7 @@ type internal FSharpWorkspaceServiceFactory [] let getSource filename = async { let! ct = Async.CancellationToken + match workspace.CurrentSolution.TryGetDocumentFromPath filename with | ValueSome document -> let! text = document.GetTextAsync(ct) |> Async.AwaitTask