diff --git a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs index aaef7be91b7..d9339a099d7 100644 --- a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs +++ b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs @@ -499,17 +499,19 @@ type EditorBraceCompletionSessionFactory() = let sourceCode = sourceCodeTask.Result position = 0 - || (let colorizationData = - Tokenizer.getClassifiedSpans ( - document.Id, - sourceCode, - TextSpan(position - 1, 1), - Some(document.FilePath), - [], - None, - None, - cancellationToken - ) + || (let colorizationData = ResizeArray<_>() + + Tokenizer.classifySpans ( + document.Id, + sourceCode, + TextSpan(position - 1, 1), + Some(document.FilePath), + [], + None, + None, + colorizationData, + cancellationToken + ) colorizationData.Count = 0 || colorizationData.Exists(fun classifiedSpan -> diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs index 442e13b44cd..02b6c3c52f9 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs @@ -188,15 +188,15 @@ module internal ClassificationDefinitions = do VSColorTheme.add_ThemeChanged handler member _.GetColor(ctype) = - match customColorData |> List.tryFind (fun item -> item.ClassificationName = ctype) with - | Some item -> + match customColorData |> List.tryFindV (fun item -> item.ClassificationName = ctype) with + | ValueSome item -> let light, dark = item.LightThemeColor, item.DarkThemeColor match getCurrentThemeId () with | LightTheme -> Nullable light | DarkTheme -> Nullable dark | UnknownTheme -> Nullable() - | None -> Nullable() + | ValueNone -> Nullable() interface ISetThemeColors with member _.SetColors() = setColors () diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs index f64904512cc..b837385c1a9 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs @@ -118,7 +118,7 @@ type internal FSharpClassificationService [] () = d.ForEach(f) - Collections.ObjectModel.ReadOnlyDictionary lookup :> IReadOnlyDictionary<_, _> + lookup :> IReadOnlyDictionary<_, _> let semanticClassificationCache = new DocumentCache("fsharp-semantic-classification-cache") @@ -137,7 +137,7 @@ type internal FSharpClassificationService [] () = cancellableTask { use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Syntactic) - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () let defines, langVersion, strictIndentation = document.GetFsharpParsingOptions() @@ -165,17 +165,16 @@ type internal FSharpClassificationService [] () = result.AddRange(classifiedSpans) else - result.AddRange( - Tokenizer.getClassifiedSpans ( - document.Id, - sourceText, - textSpan, - Some(document.FilePath), - defines, - Some langVersion, - strictIndentation, - cancellationToken - ) + Tokenizer.classifySpans ( + document.Id, + sourceText, + textSpan, + Some(document.FilePath), + defines, + Some langVersion, + strictIndentation, + result, + cancellationToken ) } |> CancellableTask.startAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs index 76c337a8c57..136116deef5 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs @@ -38,7 +38,7 @@ type internal AddMissingFunKeywordCodeFixProvider [] () = if textOfError <> "->" then return ValueNone else - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () let document = context.Document let! defines, langVersion, strictIndentation = diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingRecToMutuallyRecFunctions.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingRecToMutuallyRecFunctions.fs index 713d628a895..0a601af0e55 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingRecToMutuallyRecFunctions.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingRecToMutuallyRecFunctions.fs @@ -24,7 +24,7 @@ type internal AddMissingRecToMutuallyRecFunctionsCodeFixProvider [] type internal AddOpenCodeFixProvider [] (assemblyContentProvider: AssemblyContentProvider) = @@ -68,6 +70,8 @@ type internal AddOpenCodeFixProvider [] (assemblyContentPr let! parseResults, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof (AddOpenCodeFixProvider)) + |> CancellableTask.start context.CancellationToken + |> Async.AwaitTask |> liftAsync let line = sourceText.Lines.GetLineFromPosition(context.Span.End) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs index 5f048d4d9de..6b914815a76 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs @@ -5,6 +5,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.Composition open System.Threading.Tasks +open System.Threading open System.Collections.Immutable open Microsoft.CodeAnalysis.Text @@ -13,6 +14,7 @@ open Microsoft.CodeAnalysis.CodeFixes open FSharp.Compiler.EditorServices open FSharp.Compiler.Text open FSharp.Compiler.Symbols +open CancellableTasks [] type internal AddTypeAnnotationToObjectOfIndeterminateTypeFixProvider [] () = @@ -44,6 +46,8 @@ type internal AddTypeAnnotationToObjectOfIndeterminateTypeFixProvider [ CancellableTask.start context.CancellationToken + |> Async.AwaitTask |> liftAsync let decl = diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs b/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs index 5579a7719cd..6ea37164caa 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs @@ -125,7 +125,7 @@ module internal CodeFixExtensions = member ctx.GetSourceTextAsync() = cancellableTask { - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () return! ctx.Document.GetTextAsync cancellationToken } @@ -157,7 +157,7 @@ module IFSharpCodeFixProviderExtensions = cancellableTask { let sw = Stopwatch.StartNew() - let! token = CancellableTask.getCurrentCancellationToken () + let! token = CancellableTask.getCancellationToken () let! sourceText = doc.GetTextAsync token let! codeFixOpts = @@ -167,10 +167,10 @@ module IFSharpCodeFixProviderExtensions = // TODO: this crops the diags on a very high level, // a proper fix is needed. |> Seq.distinctBy (fun d -> d.Id, d.Location) - |> Seq.map (fun diag -> CodeFixContext(doc, diag, IFSharpCodeFixProvider.Action, token)) - |> Seq.map (fun context -> provider.GetCodeFixIfAppliesAsync context) - |> Seq.map (fun task -> task token) - |> Task.WhenAll + |> Seq.map (fun diag -> + let context = CodeFixContext(doc, diag, IFSharpCodeFixProvider.Action, token) + provider.GetCodeFixIfAppliesAsync context) + |> CancellableTask.whenAll let codeFixes = codeFixOpts |> Seq.map ValueOption.toOption |> Seq.choose id let changes = codeFixes |> Seq.collect (fun codeFix -> codeFix.Changes) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs index c4773d38f28..7834cfa0795 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs @@ -34,7 +34,7 @@ type internal ConvertCSharpLambdaToFSharpLambdaCodeFixProvider [] type internal InterfaceState = @@ -189,8 +190,12 @@ type internal ImplementInterfaceCodeFixProvider [] () = override _.RegisterCodeFixesAsync context : Task = asyncMaybe { + let! ct = Async.CancellationToken |> liftAsync + let! parseResults, checkFileResults = context.Document.GetFSharpParseAndCheckResultsAsync(nameof (ImplementInterfaceCodeFixProvider)) + |> CancellableTask.start ct + |> Async.AwaitTask |> liftAsync let cancellationToken = context.CancellationToken @@ -199,6 +204,8 @@ type internal ImplementInterfaceCodeFixProvider [] () = let! _, _, parsingOptions, _ = context.Document.GetFSharpCompilationOptionsAsync(nameof (ImplementInterfaceCodeFixProvider)) + |> CancellableTask.start ct + |> Async.AwaitTask |> liftAsync let defines = CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/MakeDeclarationMutable.fs b/vsintegration/src/FSharp.Editor/CodeFixes/MakeDeclarationMutable.fs index d3ad0ff8218..a4d8e276ba9 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/MakeDeclarationMutable.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/MakeDeclarationMutable.fs @@ -4,6 +4,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition open System.Threading.Tasks +open System.Threading open System.Collections.Immutable open Microsoft.CodeAnalysis.Text @@ -11,6 +12,7 @@ open Microsoft.CodeAnalysis.CodeFixes open FSharp.Compiler.EditorServices open FSharp.Compiler.Text +open CancellableTasks [] type internal MakeDeclarationMutableFixProvider [] () = @@ -43,6 +45,8 @@ type internal MakeDeclarationMutableFixProvider [] () = let! parseFileResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof (MakeDeclarationMutableFixProvider)) + |> CancellableTask.start context.CancellationToken + |> Async.AwaitTask |> liftAsync let decl = diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/MissingReferenceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFixes/MissingReferenceCodeFixProvider.fs index f96b79997eb..eabc0cd2880 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/MissingReferenceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/MissingReferenceCodeFixProvider.fs @@ -63,11 +63,11 @@ type internal MissingReferenceCodeFixProvider() = let exactProjectMatches = solution.Projects - |> Seq.tryFind (fun project -> + |> Seq.tryFindV (fun project -> String.Compare(project.AssemblyName, assemblyName, StringComparison.OrdinalIgnoreCase) = 0) match exactProjectMatches with - | Some refProject -> + | ValueSome refProject -> let codefix = createCodeFix ( String.Format(SR.AddProjectReference(), refProject.Name), @@ -76,21 +76,21 @@ type internal MissingReferenceCodeFixProvider() = ) context.RegisterCodeFix(codefix, ImmutableArray.Create diagnostic) - | None -> + | ValueNone -> let metadataReferences = solution.Projects |> Seq.collect (fun project -> project.MetadataReferences) - |> Seq.tryFind (fun ref -> + |> Seq.tryFindV (fun ref -> let referenceAssemblyName = Path.GetFileNameWithoutExtension(ref.Display) String.Compare(referenceAssemblyName, assemblyName, StringComparison.OrdinalIgnoreCase) = 0) match metadataReferences with - | Some metadataRef -> + | ValueSome metadataRef -> let codefix = createCodeFix (String.Format(SR.AddAssemblyReference(), assemblyName), context, AddMetadataRef metadataRef) context.RegisterCodeFix(codefix, ImmutableArray.Create diagnostic) - | None -> () + | ValueNone -> () | _ -> ()) } |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveSuperfluousCaptureForUnionCaseWithNoData.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveSuperfluousCaptureForUnionCaseWithNoData.fs index f7e18945fe5..6c7a50b7e15 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveSuperfluousCaptureForUnionCaseWithNoData.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveSuperfluousCaptureForUnionCaseWithNoData.fs @@ -42,8 +42,7 @@ type internal RemoveSuperfluousCaptureForUnionCaseWithNoDataCodeFixProvider [ Array.tryFind (fun c -> c.Type = SemanticClassificationType.UnionCase) - |> ValueOption.ofOption + |> Array.tryFindV (fun c -> c.Type = SemanticClassificationType.UnionCase) |> ValueOption.map (fun unionCaseItem -> // The error/warning captures entire pattern match, like "Ns.Type.DuName bindingName". We want to keep type info when suggesting a replacement, and only remove "bindingName". let typeInfoLength = unionCaseItem.Range.EndColumn - m.StartColumn diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnusedBinding.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnusedBinding.fs index 918819cd6f2..26fb65244f2 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnusedBinding.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnusedBinding.fs @@ -32,7 +32,7 @@ type internal RemoveUnusedBindingCodeFixProvider [] () = interface IFSharpCodeFixProvider with member _.GetCodeFixIfAppliesAsync context = cancellableTask { - let! token = CancellableTask.getCurrentCancellationToken () + let! token = CancellableTask.getCancellationToken () let! sourceText = context.Document.GetTextAsync token let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof RemoveUnusedBindingCodeFixProvider) diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ReplaceWithSuggestion.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ReplaceWithSuggestion.fs index d3a4c3adeab..70e5334a125 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ReplaceWithSuggestion.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ReplaceWithSuggestion.fs @@ -13,6 +13,7 @@ open FSharp.Compiler.Diagnostics open FSharp.Compiler.EditorServices open FSharp.Compiler.Syntax open FSharp.Compiler.Text +open CancellableTasks [] type internal ReplaceWithSuggestionCodeFixProvider [] (settings: EditorOptions) = @@ -28,6 +29,8 @@ type internal ReplaceWithSuggestionCodeFixProvider [] (set let! parseFileResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof (ReplaceWithSuggestionCodeFixProvider)) + |> CancellableTask.start context.CancellationToken + |> Async.AwaitTask |> liftAsync // This is all needed to get a declaration list diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/UseMutationWhenValueIsMutable.fs b/vsintegration/src/FSharp.Editor/CodeFixes/UseMutationWhenValueIsMutable.fs index bf2735b043b..cb0e9dda55a 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/UseMutationWhenValueIsMutable.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/UseMutationWhenValueIsMutable.fs @@ -12,6 +12,7 @@ open Microsoft.CodeAnalysis.CodeFixes open FSharp.Compiler.Symbols open FSharp.Compiler.Text +open CancellableTasks [] type internal UseMutationWhenValueIsMutableCodeFixProvider [] () = @@ -51,6 +52,8 @@ type internal UseMutationWhenValueIsMutableCodeFixProvider [ CancellableTask.start context.CancellationToken + |> Async.AwaitTask |> liftAsync let! symbolUse = diff --git a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs index 73fdb5d265b..7c4d3f40ffb 100644 --- a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs @@ -22,7 +22,7 @@ type internal FSharpHelpContextService [] () = static member GetHelpTerm(document: Document, span: TextSpan, tokens: List) : CancellableTask = cancellableTask { - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () let! _, check = document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpHelpContextService)) let! sourceText = document.GetTextAsync(cancellationToken) @@ -53,31 +53,31 @@ type internal FSharpHelpContextService [] () = let getTokenAt line col = if col < 0 || line < 0 then - None + ValueNone else let start = textLines.[line].Start + col let span = TextSpan.FromBounds(start, start + 1) tokens - |> Seq.tryFindIndex (fun t -> t.TextSpan.Contains(span)) - |> Option.map (fun i -> tokens.[i]) + |> Seq.tryFindIndexV (fun t -> t.TextSpan.Contains(span)) + |> ValueOption.map (fun i -> tokens[i]) match getTokenAt line col with - | Some t as original -> // when col > 0 && shouldTryToFindSurroundingIdent t -> + | ValueSome t as original -> // when col > 0 && shouldTryToFindSurroundingIdent t -> if shouldTryToFindSurroundingIdent t then match getTokenAt line (col - 1) with - | Some t as newInfo when not (shouldTryToFindSurroundingIdent t) -> newInfo, col - 1 + | ValueSome t as newInfo when not (shouldTryToFindSurroundingIdent t) -> newInfo, col - 1 | _ -> match getTokenAt line (col + 1) with - | Some t as newInfo when not (shouldTryToFindSurroundingIdent t) -> newInfo, col + 1 + | ValueSome t as newInfo when not (shouldTryToFindSurroundingIdent t) -> newInfo, col + 1 | _ -> original, col else original, col | otherwise -> otherwise, col match tokenInformation with - | None -> return "" - | Some token -> + | ValueNone -> return "" + | ValueSome token -> match token.ClassificationType with | ClassificationTypeNames.Keyword | ClassificationTypeNames.Operator @@ -104,32 +104,34 @@ type internal FSharpHelpContextService [] () = } interface IHelpContextService with - member this.Language = FSharpConstants.FSharpLanguageLongName - member this.Product = FSharpConstants.FSharpLanguageLongName + member _.Language = FSharpConstants.FSharpLanguageLongName + member _.Product = FSharpConstants.FSharpLanguageLongName - member this.GetHelpTermAsync(document, textSpan, cancellationToken) = + member _.GetHelpTermAsync(document, textSpan, cancellationToken) = cancellableTask { - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () let! sourceText = document.GetTextAsync(cancellationToken) let defines, langVersion, strictIndentation = document.GetFsharpParsingOptions() let textLine = sourceText.Lines.GetLineFromPosition(textSpan.Start) - let classifiedSpans = - Tokenizer.getClassifiedSpans ( - document.Id, - sourceText, - textLine.Span, - Some document.Name, - defines, - Some langVersion, - strictIndentation, - cancellationToken - ) + let classifiedSpans = ResizeArray<_>() + + Tokenizer.classifySpans ( + document.Id, + sourceText, + textLine.Span, + Some document.Name, + defines, + Some langVersion, + strictIndentation, + classifiedSpans, + cancellationToken + ) return! FSharpHelpContextService.GetHelpTerm(document, textSpan, classifiedSpans) } |> CancellableTask.start cancellationToken - member this.FormatSymbol(_symbol) = Unchecked.defaultof<_> + member _.FormatSymbol(_symbol) = Unchecked.defaultof<_> diff --git a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs index c4d606f2b43..cb81934debb 100644 --- a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs +++ b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs @@ -37,6 +37,15 @@ module CancellableTasks = open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators open Microsoft.FSharp.Collections + [] + type VolatileBarrier() = + + [] + let mutable isStopped = false + + member _.Proceed = not isStopped + member _.Stop() = isStopped <- true + /// A type that looks like an Awaiter type Awaiter<'Awaiter, 'TResult when 'Awaiter :> ICriticalNotifyCompletion @@ -754,6 +763,56 @@ module CancellableTasks = [] module HighPriority = + let inline startAsyncImmediateAsTask computation (cancellationToken: CancellationToken) = + // Protect against blocking the UI thread by switching to thread pool + let computation = + match SynchronizationContext.Current with + | null -> computation + | _ -> + async { + do! Async.SwitchToThreadPool() + return! computation + } + + // try not to yield if on bg thread already + let tcs = new TaskCompletionSource<_>(TaskCreationOptions.None) + let barrier = VolatileBarrier() + + let reg = + cancellationToken.Register(fun _ -> + if barrier.Proceed then + tcs.TrySetCanceled(cancellationToken) |> ignore) + + let task = tcs.Task + + let disposeReg () = + barrier.Stop() + + if not task.IsCanceled then + reg.Dispose() + + Async.StartWithContinuations( + computation, + continuation = + (fun result -> + disposeReg () + tcs.TrySetResult(result) |> ignore), + exceptionContinuation = + (fun exn -> + disposeReg () + + match exn with + | :? OperationCanceledException -> tcs.TrySetCanceled(cancellationToken) |> ignore + | exn -> tcs.TrySetException(exn) |> ignore), + cancellationContinuation = + (fun _oce -> + disposeReg () + tcs.TrySetCanceled(cancellationToken) |> ignore), + cancellationToken = cancellationToken + ) + + task + type Control.Async with /// Return an asynchronous computation that will wait for the given task to complete and return @@ -775,7 +834,7 @@ module CancellableTasks = /// Runs an asynchronous computation, starting on the current operating system thread. static member inline AsCancellableTask(computation: Async<'T>) : CancellableTask<_> = - fun ct -> Async.StartImmediateAsTask(computation, cancellationToken = ct) + fun ct -> startAsyncImmediateAsTask computation ct // High priority extensions type CancellableTaskBuilderBase with @@ -896,7 +955,7 @@ module CancellableTasks = /// This will print "2" 2 seconds from start, "3" 3 seconds from start, "5" 5 seconds from start, cease computation and then /// followed by "Tasks Finished". /// - let inline getCurrentCancellationToken () : CancellationToken -> Task = + let inline getCancellationToken () : CancellationToken -> Task = fun ct -> Task.FromResult ct /// Lifts an item to a CancellableTask. @@ -968,7 +1027,7 @@ module CancellableTasks = ([] right: CancellableTask<'right>) = cancellableTask { - let! ct = getCurrentCancellationToken () + let! ct = getCancellationToken () let r1 = left ct let r2 = right ct let! r1 = r1 @@ -997,7 +1056,51 @@ module CancellableTasks = let inline start ct ([] ctask: CancellableTask<_>) = ctask ct - let inline startAsTask ct ([] ctask: CancellableTask<_>) = (ctask ct) :> Task + let inline startTask ct ([] ctask: CancellableTask) = ctask ct + + let inline startWithoutCancellation ([] ctask: CancellableTask<_>) = start CancellationToken.None ctask + + let inline startTaskWithoutCancellation ([] ctask: CancellableTask) = startTask CancellationToken.None ctask + + let inline runSynchronously ct ([] ctask: CancellableTask<_>) = + let task = start ct ctask + task.GetAwaiter().GetResult() + + let inline runSynchronouslyWithoutCancellation ([] ctask: CancellableTask<_>) = + let task = startWithoutCancellation ctask + task.GetAwaiter().GetResult() + + let inline runTaskSynchronously ct ([] ctask: CancellableTask) = + let task = startTask ct ctask + task.GetAwaiter().GetResult() + + let inline runTaskSynchronouslyWithoutCancellation ([] ctask: CancellableTask) = + let task = startTaskWithoutCancellation ctask + task.GetAwaiter().GetResult() + + let inline startAsTask ct ([] ctask: CancellableTask<_>) = (start ct ctask) :> Task + + let inline startAsTaskWithoutCancellation ([] ctask: CancellableTask<_>) = (start CancellationToken.None ctask) :> Task + + let inline runAsTaskSynchronously ct ([] ctask: CancellableTask<_>) = + let task = startAsTask ct ctask + task.GetAwaiter().GetResult() + + let inline runAsTaskSynchronouslyWithoutCancellation ([] ctask: CancellableTask<_>) = + let task = startAsTaskWithoutCancellation ctask + task.GetAwaiter().GetResult() + + 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 inline whenAllTasks (tasks: CancellableTask seq) = + cancellableTask { + let! ct = getCancellationToken () + return! Task.WhenAll (seq { for task in tasks do yield startTask ct task }) + } /// [] @@ -1014,7 +1117,7 @@ module CancellableTasks = ) : CancellationToken -> TaskAwaiter<'TResult1 * 'TResult2> = cancellableTask { - let! ct = CancellableTask.getCurrentCancellationToken () + let! ct = CancellableTask.getCancellationToken () let leftStarted = left ct let rightStarted = right ct let! leftResult = leftStarted diff --git a/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs b/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs index f29e4c2e200..f812e5f8b0f 100644 --- a/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs +++ b/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs @@ -18,7 +18,7 @@ type DocumentCache<'Value when 'Value: not struct>(name: string, ?cacheItemPolic member _.TryGetValueAsync(doc: Document) = cancellableTask { - let! ct = CancellableTask.getCurrentCancellationToken () + let! ct = CancellableTask.getCancellationToken () let! currentVersion = doc.GetTextVersionAsync ct match cache.Get(doc.Id.ToString()) with @@ -33,9 +33,9 @@ type DocumentCache<'Value when 'Value: not struct>(name: string, ?cacheItemPolic member _.SetAsync(doc: Document, value: 'Value) = cancellableTask { - let! ct = CancellableTask.getCurrentCancellationToken () + let! ct = CancellableTask.getCancellationToken () let! currentVersion = doc.GetTextVersionAsync ct - cache.Set(doc.Id.ToString(), (currentVersion, value), policy) + do cache.Set(doc.Id.ToString(), (currentVersion, value), policy) } interface IDisposable with diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 0c4f4a42b4e..d05b5166a08 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -6,6 +6,7 @@ module internal Microsoft.VisualStudio.FSharp.Editor.Extensions open System open System.IO open System.Collections.Immutable +open System.Collections.Generic open System.Threading open System.Threading.Tasks @@ -302,9 +303,32 @@ module Seq = let toImmutableArray (xs: seq<'a>) : ImmutableArray<'a> = xs.ToImmutableArray() + let inline tryFindV ([] predicate) (source: seq<'T>) = + use e = source.GetEnumerator() + let mutable res = ValueNone + + while (ValueOption.isNone res && e.MoveNext()) do + let c = e.Current + + if predicate c then + res <- ValueSome c + + res + + let inline tryFindIndexV ([] predicate) (source: seq<_>) = + use ie = source.GetEnumerator() + + let rec loop i = + if ie.MoveNext() then + if predicate ie.Current then ValueSome i else loop (i + 1) + else + ValueNone + + loop 0 + [] module Array = - let foldi (folder: 'State -> int -> 'T -> 'State) (state: 'State) (xs: 'T[]) = + let inline foldi ([] folder: 'State -> int -> 'T -> 'State) (state: 'State) (xs: 'T[]) = let mutable state = state let mutable i = 0 @@ -316,6 +340,22 @@ module Array = let toImmutableArray (xs: 'T[]) = xs.ToImmutableArray() + let inline tryFindV ([] predicate) (array: _[]) = + + let rec loop i = + if i >= array.Length then ValueNone + else if predicate array.[i] then ValueSome array[i] + else loop (i + 1) + + loop 0 + +[] +module List = + let rec tryFindV predicate list = + match list with + | [] -> ValueNone + | h :: t -> if predicate h then ValueSome h else tryFindV predicate t + [] module Exception = diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index f5bbcf10b4c..d0ce229f64b 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -156,7 +156,7 @@ type internal FSharpCompletionProvider cancellableTask { let! parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux") - let! ct = CancellableTask.getCurrentCancellationToken () + let! ct = CancellableTask.getCancellationToken () let! sourceText = document.GetTextAsync(ct) let textLines = sourceText.Lines @@ -167,11 +167,15 @@ type internal FSharpCompletionProvider let line = caretLine.ToString() let partialName = QuickParse.GetPartialLongNameEx(line, caretLineColumn - 1) - let getAllSymbols () = - getAllSymbols checkFileResults - |> List.filter (fun assemblySymbol -> - assemblySymbol.FullName.Contains "." - && not (PrettyNaming.IsOperatorDisplayName assemblySymbol.Symbol.DisplayName)) + let inline getAllSymbols () = + [ + for assemblySymbol in getAllSymbols checkFileResults do + if + assemblySymbol.FullName.Contains(".") + && not (PrettyNaming.IsOperatorDisplayName assemblySymbol.Symbol.DisplayName) + then + yield assemblySymbol + ] let completionContextPos = Position.fromZ caretLinePos.Line caretLinePos.Character @@ -318,7 +322,7 @@ type internal FSharpCompletionProvider use _logBlock = Logger.LogBlockMessage context.Document.Name LogEditorFunctionId.Completion_ProvideCompletionsAsync - let! ct = CancellableTask.getCurrentCancellationToken () + let! ct = CancellableTask.getCancellationToken () let document = context.Document @@ -367,40 +371,33 @@ type internal FSharpCompletionProvider completionItem: Completion.CompletionItem, cancellationToken: CancellationToken ) : Task = - cancellableTask { - use _logBlock = - Logger.LogBlockMessage document.Name LogEditorFunctionId.Completion_GetDescriptionAsync - match completionItem.Properties.TryGetValue IndexPropName with - | true, completionItemIndexStr -> + 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 - if completionItemIndex < declarationItems.Length then - 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 - ) - - return CompletionDescription.Create(documentation.ToImmutableArray()) - else - return CompletionDescription.Empty - | _ -> - // Try keyword descriptions if they exist - match completionItem.Properties.TryGetValue KeywordDescription with - | true, keywordDescription -> return CompletionDescription.FromText(keywordDescription) - | false, _ -> return CompletionDescription.Empty - } - |> 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) + + return CompletionDescription.Create(documentation.ToImmutableArray()) + } + |> CancellableTask.start cancellationToken + + | _ -> + match completionItem.Properties.TryGetValue KeywordDescription with + | true, keywordDescription -> Task.FromResult(CompletionDescription.FromText(keywordDescription)) + | false, _ -> Task.FromResult(CompletionDescription.Empty) override _.GetChangeAsync(document, item, _, cancellationToken) : Task = cancellableTask { diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs index 65a4d21e652..01888458d3c 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs @@ -104,17 +104,19 @@ module internal CompletionUtils = let textLines = sourceText.Lines let triggerLine = textLines.GetLineFromPosition triggerPosition - let classifiedSpans = - Tokenizer.getClassifiedSpans ( - documentId, - sourceText, - triggerLine.Span, - Some filePath, - defines, - langVersion, - strictIndentation, - ct - ) + let classifiedSpans = ResizeArray<_>() + + Tokenizer.classifySpans ( + documentId, + sourceText, + triggerLine.Span, + Some filePath, + defines, + langVersion, + strictIndentation, + classifiedSpans, + ct + ) classifiedSpans.Count = 0 || // we should provide completion at the start of empty line, where there are no tokens at all @@ -189,22 +191,33 @@ module internal CompletionUtils = // backticks before later valid backticks on a line, this is an acceptable compromise in order to support // the majority of common cases. - let classifiedSpans = - Tokenizer.getClassifiedSpans (documentId, sourceText, line.Span, Some filePath, defines, langVersion, strictIndentation, ct) + let classifiedSpans = ResizeArray<_>() + + Tokenizer.classifySpans ( + documentId, + sourceText, + line.Span, + Some filePath, + defines, + langVersion, + strictIndentation, + classifiedSpans, + ct + ) - let isBacktickIdentifier (classifiedSpan: ClassifiedSpan) = + let inline isBacktickIdentifier (classifiedSpan: ClassifiedSpan) = classifiedSpan.ClassificationType = ClassificationTypeNames.Identifier && Tokenizer.isDoubleBacktickIdent (sourceText.ToString(classifiedSpan.TextSpan)) - let isUnclosedBacktick (classifiedSpan: ClassifiedSpan) = + let inline isUnclosedBacktick (classifiedSpan: ClassifiedSpan) = classifiedSpan.ClassificationType = ClassificationTypeNames.Identifier && sourceText.ToString(classifiedSpan.TextSpan).StartsWith "``" match classifiedSpans - |> Seq.tryFind (fun cs -> isBacktickIdentifier cs && cs.TextSpan.IntersectsWith caretIndex) + |> Seq.tryFindV (fun cs -> isBacktickIdentifier cs && cs.TextSpan.IntersectsWith caretIndex) with - | Some backtickIdentifier -> + | ValueSome backtickIdentifier -> // Backtick enclosed identifier found intersecting with caret, use its span. backtickIdentifier.TextSpan | _ -> diff --git a/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs index 4dd4ee2122c..633f39e65d3 100644 --- a/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs @@ -74,7 +74,9 @@ type internal HashDirectiveCompletionProvider let textLines = text.Lines let triggerLine = textLines.GetLineFromPosition(position) - Tokenizer.getClassifiedSpans ( + let classifiedSpans = ResizeArray<_>() + + Tokenizer.classifySpans ( documentId, text, triggerLine.Span, @@ -82,9 +84,12 @@ type internal HashDirectiveCompletionProvider defines, Some langVersion, strictIndentation, + classifiedSpans, CancellationToken.None ) + classifiedSpans + let isInStringLiteral (text: SourceText, position: int) : bool = getClassifiedSpans (text, position) |> Seq.exists (fun classifiedSpan -> diff --git a/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs index 2002ec439bc..3f001390031 100644 --- a/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs @@ -4,6 +4,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.Composition +open System.Threading open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text @@ -19,6 +20,7 @@ open FSharp.Compiler.Text open FSharp.Compiler.Text.Position open FSharp.Compiler.Text.Range open FSharp.Compiler.Tokenization +open CancellableTasks type SignatureHelpParameterInfo = { @@ -607,7 +609,12 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi possibleCurrentSignatureHelpSessionKind: CurrentSignatureHelpSessionKind option ) = asyncMaybe { - let! parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideSignatureHelp") |> liftAsync + + let! parseResults, checkFileResults = + document.GetFSharpParseAndCheckResultsAsync("ProvideSignatureHelp") + |> CancellableTask.start CancellationToken.None + |> Async.AwaitTask + |> liftAsync let! sourceText = document.GetTextAsync() |> liftTaskAsync diff --git a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs index ff927ef8742..d73b1a61d05 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs @@ -23,7 +23,7 @@ type internal FSharpBreakpointResolutionService [] () = static member GetBreakpointLocation(document: Document, textSpan: TextSpan) = cancellableTask { - let! ct = CancellableTask.getCurrentCancellationToken () + let! ct = CancellableTask.getCancellationToken () let! sourceText = document.GetTextAsync(ct) diff --git a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs index 8105c45ade6..006fe570d6c 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs @@ -58,21 +58,23 @@ type internal FSharpLanguageDebugInfoService [] () = cancellableTask { let defines, langVersion, strictIndentation = document.GetFsharpParsingOptions() - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () let! sourceText = document.GetTextAsync(cancellationToken) let textSpan = TextSpan.FromBounds(0, sourceText.Length) - let classifiedSpans = - Tokenizer.getClassifiedSpans ( - document.Id, - sourceText, - textSpan, - Some(document.Name), - defines, - Some langVersion, - strictIndentation, - cancellationToken - ) + let classifiedSpans = ResizeArray<_>() + + Tokenizer.classifySpans ( + document.Id, + sourceText, + textSpan, + Some(document.Name), + defines, + Some langVersion, + strictIndentation, + classifiedSpans, + cancellationToken + ) let result = match FSharpLanguageDebugInfoService.GetDataTipInformation(sourceText, position, classifiedSpans) with diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs index 38bdf8766c7..1e547e86335 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs @@ -67,33 +67,35 @@ type internal FSharpDocumentDiagnosticAnalyzer [] () = use _eventDuration = TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GetDiagnosticsForDocument, eventProps) - let! ct = CancellableTask.getCurrentCancellationToken () - - let! parseResults = document.GetFSharpParseResultsAsync("GetDiagnostics") + let! ct = CancellableTask.getCancellationToken () let! sourceText = document.GetTextAsync(ct) let filePath = document.FilePath - let! errors = - cancellableTask { - match diagnosticType with - | DiagnosticsType.Semantic -> - let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync("GetDiagnostics") - // In order to eliminate duplicates, we should not return parse errors here because they are returned by `AnalyzeSyntaxAsync` method. - let allDiagnostics = HashSet(checkResults.Diagnostics, diagnosticEqualityComparer) - allDiagnostics.ExceptWith(parseResults.Diagnostics) - return Seq.toArray allDiagnostics - | DiagnosticsType.Syntax -> return parseResults.Diagnostics - } - - let results = - HashSet(errors, diagnosticEqualityComparer) - |> Seq.choose (fun diagnostic -> - if diagnostic.StartLine = 0 || diagnostic.EndLine = 0 then - // F# diagnostic line numbers are one-based. Compiler returns 0 for global errors (reported by ProjectDiagnosticAnalyzer) - None - else - // Roslyn line numbers are zero-based + let errors = HashSet(diagnosticEqualityComparer) + + let! parseResults = document.GetFSharpParseResultsAsync("GetDiagnostics") + + match diagnosticType with + | DiagnosticsType.Syntax -> + for diagnostic in parseResults.Diagnostics do + errors.Add(diagnostic) |> ignore + + | DiagnosticsType.Semantic -> + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync("GetDiagnostics") + + for diagnostic in checkResults.Diagnostics do + errors.Add(diagnostic) |> ignore + + errors.ExceptWith(parseResults.Diagnostics) + + if errors.Count = 0 then + return ImmutableArray.Empty + else + let iab = ImmutableArray.CreateBuilder(errors.Count) + + for diagnostic in errors do + if diagnostic.StartLine <> 0 && diagnostic.EndLine <> 0 then let linePositionSpan = LinePositionSpan( LinePosition(diagnostic.StartLine - 1, diagnostic.StartColumn), @@ -112,28 +114,23 @@ type internal FSharpDocumentDiagnosticAnalyzer [] () = TextSpan.FromBounds(start, sourceText.Length) let location = Location.Create(filePath, correctedTextSpan, linePositionSpan) - Some(RoslynHelpers.ConvertError(diagnostic, location))) - |> Seq.toImmutableArray + iab.Add(RoslynHelpers.ConvertError(diagnostic, location)) - return results + return iab.ToImmutable() } interface IFSharpDocumentDiagnosticAnalyzer with member _.AnalyzeSyntaxAsync(document: Document, cancellationToken: CancellationToken) : Task> = - cancellableTask { - if document.Project.IsFSharpMetadata then - return ImmutableArray.Empty - else - return! FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) - } - |> CancellableTask.start cancellationToken + if document.Project.IsFSharpMetadata then + Task.FromResult ImmutableArray.Empty + else + cancellableTask { return! FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) } + |> CancellableTask.start cancellationToken member _.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken) : Task> = - cancellableTask { - if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then - return ImmutableArray.Empty - else - return! FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic) - } - |> CancellableTask.start cancellationToken + if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then + Task.FromResult ImmutableArray.Empty + else + cancellableTask { return! FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic) } + |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs index e89f942e0d7..6db264c5938 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/SimplifyNameDiagnosticAnalyzer.fs @@ -13,6 +13,7 @@ open System.Runtime.Caching open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics open FSharp.Compiler.EditorServices open FSharp.Compiler.Text +open CancellableTasks type private PerDocumentSavedData = { @@ -57,6 +58,8 @@ type internal SimplifyNameDiagnosticAnalyzer [] () = let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof (SimplifyNameDiagnosticAnalyzer)) + |> CancellableTask.start cancellationToken + |> Async.AwaitTask |> liftAsync let! result = diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs index 867d7a505f7..3dd0628fa5d 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs @@ -10,11 +10,11 @@ open System.Threading open Microsoft.CodeAnalysis -open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.EditorServices open FSharp.Compiler.Text open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics +open CancellableTasks [)>] type internal UnusedOpensDiagnosticAnalyzer [] () = @@ -22,10 +22,13 @@ type internal UnusedOpensDiagnosticAnalyzer [] () = static member GetUnusedOpenRanges(document: Document) : Async> = asyncMaybe { do! Option.guard document.Project.IsFSharpCodeFixesUnusedOpensEnabled - let! sourceText = document.GetTextAsync() + let! ct = Async.CancellationToken |> liftAsync + let! sourceText = document.GetTextAsync(ct) let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof (UnusedOpensDiagnosticAnalyzer)) + |> CancellableTask.start ct + |> Async.AwaitTask |> liftAsync let! unusedOpens = diff --git a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs index 9d6a4d46e8c..c19f23caf1f 100644 --- a/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs +++ b/vsintegration/src/FSharp.Editor/DocumentHighlights/DocumentHighlightsService.fs @@ -11,9 +11,8 @@ open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.ExternalAccess.FSharp.DocumentHighlighting -open FSharp.Compiler -open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Text +open CancellableTasks type internal FSharpHighlightSpan = { @@ -76,6 +75,8 @@ type internal FSharpDocumentHighlightsService [] () = let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpDocumentHighlightsService)) + |> CancellableTask.start ct + |> Async.AwaitTask |> liftAsync let! symbolUse = diff --git a/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs b/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs index e795e43ef6a..11fa356b1f4 100644 --- a/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs +++ b/vsintegration/src/FSharp.Editor/Formatting/BraceMatchingService.fs @@ -7,6 +7,8 @@ open Microsoft.CodeAnalysis.Text open FSharp.Compiler.CodeAnalysis open System.Runtime.InteropServices open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor +open CancellableTasks.CancellableTaskBuilder +open CancellableTasks [)>] type internal FSharpBraceMatchingService [] () = @@ -44,6 +46,8 @@ type internal FSharpBraceMatchingService [] () = asyncMaybe { let! checker, _, parsingOptions, _ = document.GetFSharpCompilationOptionsAsync(nameof (FSharpBraceMatchingService)) + |> CancellableTask.start cancellationToken + |> Async.AwaitTask |> liftAsync let! sourceText = document.GetTextAsync(cancellationToken) diff --git a/vsintegration/src/FSharp.Editor/Hints/FSharpInlineHintsService.fs b/vsintegration/src/FSharp.Editor/Hints/FSharpInlineHintsService.fs index fe30a2bd5ca..acdd0fdc97d 100644 --- a/vsintegration/src/FSharp.Editor/Hints/FSharpInlineHintsService.fs +++ b/vsintegration/src/FSharp.Editor/Hints/FSharpInlineHintsService.fs @@ -28,7 +28,7 @@ type internal FSharpInlineHintsService [] (settings: Edito Task.FromResult ImmutableArray.Empty else cancellableTask { - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () let! sourceText = document.GetTextAsync cancellationToken let! nativeHints = HintService.getHintsForDocument sourceText document hintKinds userOpName diff --git a/vsintegration/src/FSharp.Editor/Hints/HintService.fs b/vsintegration/src/FSharp.Editor/Hints/HintService.fs index 756ee0e4980..1eb4c4fa373 100644 --- a/vsintegration/src/FSharp.Editor/Hints/HintService.fs +++ b/vsintegration/src/FSharp.Editor/Hints/HintService.fs @@ -62,7 +62,7 @@ module HintService = [| ("hints.kinds", hintKindsSerialized); ("cacheHit", false) |] ) - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () let! parseResults, checkResults = document.GetFSharpParseAndCheckResultsAsync userOpName let nativeHints = diff --git a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs index 9bc946a9cbf..5c2c4632fdc 100644 --- a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs +++ b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs @@ -21,6 +21,7 @@ open FSharp.Compiler.Symbols open FSharp.Compiler.Text open FSharp.Compiler.Tokenization open Symbols +open CancellableTasks type internal InlineRenameReplacementInfo(newSolution: Solution, replacementTextValid: bool, documentIds: IEnumerable) = inherit FSharpInlineRenameReplacementInfo() @@ -172,6 +173,8 @@ type internal InlineRenameService [] () = let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof (InlineRenameService)) + |> CancellableTask.start ct + |> Async.AwaitTask |> liftAsync let! symbolUse = diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 0972540f604..21c694f060e 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -194,7 +194,7 @@ type internal FSharpWorkspaceServiceFactory [] if enableLiveBuffers then workspace.WorkspaceChanged.Add(fun args -> if args.DocumentId <> null then - backgroundTask { + cancellableTask { let document = args.NewSolution.GetDocument(args.DocumentId) let! _, _, _, options = @@ -202,6 +202,7 @@ type internal FSharpWorkspaceServiceFactory [] do! checker.NotifyFileChanged(document.FilePath, options) } + |> CancellableTask.startAsTask CancellationToken.None |> ignore) checker diff --git a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs index 6272dcf31db..8c13aa65f63 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs @@ -109,10 +109,10 @@ module internal MetadataAsSource = let documentId = workspace.GetDocumentIdInCurrentContext(textContainer) match box documentId with - | null -> None - | _ -> solution.GetDocument(documentId) |> Some + | null -> ValueNone + | _ -> solution.GetDocument(documentId) |> ValueSome else - None + ValueNone [] [); Composition.Shared>] @@ -157,8 +157,8 @@ type FSharpMetadataAsSourceService() = projsArr |> Array.iter (fun pair -> clear pair.Key pair.Value) member _.ShowDocument(projInfo: ProjectInfo, filePath: string, text: Text.SourceText) = - match projInfo.Documents |> Seq.tryFind (fun doc -> doc.FilePath = filePath) with - | Some document -> + match projInfo.Documents |> Seq.tryFindV (fun doc -> doc.FilePath = filePath) with + | ValueSome document -> let _ = let directoryName = Path.GetDirectoryName(filePath) @@ -175,4 +175,4 @@ type FSharpMetadataAsSourceService() = projs.[filePath] <- projectContext MetadataAsSource.showDocument (filePath, Path.GetFileName(filePath), serviceProvider) - | _ -> None + | _ -> ValueNone diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs b/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs index 06f33e3c81e..f2f5cb0a4fe 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs @@ -173,11 +173,11 @@ type internal FSharpMiscellaneousFileService if project <> null then let documentOpt = project.Documents - |> Seq.tryFind (fun x -> String.Equals(x.FilePath, filePath, StringComparison.OrdinalIgnoreCase)) + |> Seq.tryFindV (fun x -> String.Equals(x.FilePath, filePath, StringComparison.OrdinalIgnoreCase)) match documentOpt with - | None -> () - | Some (document) -> + | ValueNone -> () + | ValueSome (document) -> optionsManager.ClearSingleFileOptionsCache(document.Id) projectContext.Value.Dispose() diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index 2b02a33c73e..4530560dec0 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -13,13 +13,21 @@ open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols open FSharp.Compiler.Text open Microsoft.VisualStudio.FSharp.Editor.Telemetry +open CancellableTasks module internal SymbolHelpers = /// Used for local code fixes in a document, e.g. to rename local parameters let getSymbolUsesOfSymbolAtLocationInDocument (document: Document, position: int) = asyncMaybe { let userOpName = "getSymbolUsesOfSymbolAtLocationInDocument" - let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync + let! ct = Async.CancellationToken |> liftAsync + + let! _, checkFileResults = + document.GetFSharpParseAndCheckResultsAsync(userOpName) + |> CancellableTask.start ct + |> Async.AwaitTask + |> liftAsync + let! defines, langVersion, strictIndentation = document.GetFsharpParsingOptionsAsync(userOpName) |> liftAsync let! cancellationToken = Async.CancellationToken |> liftAsync @@ -79,11 +87,15 @@ module internal SymbolHelpers = // TODO: this needs to be a single event with a duration TelemetryReporter.ReportSingleEvent(TelemetryEvents.GetSymbolUsesInProjectsStarted, props) - do! - projects - |> Seq.map (fun project -> - Task.Run(fun () -> project.FindFSharpReferencesAsync(symbol, onFound, "getSymbolUsesInProjects", ct))) - |> Task.WhenAll + let tasks = + [| + for project in projects do + yield + project.FindFSharpReferencesAsync(symbol, onFound, "getSymbolUsesInProjects") + |> CancellableTask.startAsTask ct + |] + + do! Task.WhenAll tasks TelemetryReporter.ReportSingleEvent(TelemetryEvents.GetSymbolUsesInProjectsFinished, props) } diff --git a/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs b/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs index 5cdcdccbef1..f376718ea55 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs @@ -5,15 +5,12 @@ open System.Collections.Generic open System.Collections.Concurrent open System.Diagnostics open System.Threading -open System.Threading.Tasks -open System.Runtime.CompilerServices open System.Runtime.Caching open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Classification open Microsoft.CodeAnalysis.Text -open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.EditorServices open FSharp.Compiler.Symbols open FSharp.Compiler.Syntax @@ -23,9 +20,6 @@ open FSharp.Compiler.Tokenization open Microsoft.VisualStudio.Core.Imaging open Microsoft.VisualStudio.Imaging -open Microsoft.CodeAnalysis.ExternalAccess.FSharp -open CancellableTasks - type private FSharpGlyph = FSharp.Compiler.EditorServices.FSharpGlyph type private Glyph = Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpGlyph @@ -586,6 +580,7 @@ module internal Tokenizer = let mutable startPosition = 0 let mutable endPosition = startPosition + let classifiedSpans = new List() while startPosition < colorMap.Length do @@ -689,7 +684,7 @@ module internal Tokenizer = ] /// Generates a list of Classified Spans for tokens which undergo syntactic classification (i.e., are not typechecked). - let getClassifiedSpans + let classifySpans ( documentKey: DocumentId, sourceText: SourceText, @@ -698,9 +693,9 @@ module internal Tokenizer = defines: string list, langVersion, strictIndentation, + result: ResizeArray, cancellationToken: CancellationToken - ) : ResizeArray = - let result = new ResizeArray() + ) : unit = try let sourceTokenizer = @@ -716,19 +711,21 @@ module internal Tokenizer = getFromRefreshedTokenCache (lines, startLine, endLine, sourceTokenizer, sourceTextData, cancellationToken) for lineData, _ in lineDataResults do - result.AddRange( - lineData.ClassifiedSpans - |> Array.filter (fun token -> - textSpan.Contains(token.TextSpan.Start) + for token in lineData.ClassifiedSpans do + if + (token.TextSpan.Start <= textSpan.Start && textSpan.End <= token.TextSpan.End) + || textSpan.Contains(token.TextSpan.Start) || textSpan.Contains(token.TextSpan.End - 1) - || (token.TextSpan.Start <= textSpan.Start && textSpan.End <= token.TextSpan.End)) - ) + then + result.Add token with - | :? System.OperationCanceledException -> reraise () + | :? OperationCanceledException -> reraise () | ex -> Assert.Exception(ex) - result + () + + let inline (||>) struct (arg1, arg2) ([] func: 'T1 -> 'T2 -> 'T3) = func arg1 arg2 /// Returns symbol at a given position. let private getSymbolFromSavedTokens @@ -781,11 +778,11 @@ module internal Tokenizer = let draftTokens = let tokensCount = savedTokens.Length - (([], None), savedTokens) - ||> Array.foldi (fun (acc, lastToken: DraftTokenInfo option) index token -> + struct (([], ValueNone), savedTokens) + ||> Array.foldi (fun (acc, lastToken: DraftTokenInfo voption) index token -> match lastToken with - | Some t when token.LeftColumn <= t.RightColumn -> acc, lastToken - | Some ({ Kind = LexerSymbolKind.ActivePattern } as lastToken) when + | ValueSome t when token.LeftColumn <= t.RightColumn -> acc, lastToken + | ValueSome ({ Kind = LexerSymbolKind.ActivePattern } as lastToken) when wholeActivePatterns && (token.Tag = FSharpTokenTag.BAR || token.Tag = FSharpTokenTag.IDENT @@ -798,37 +795,37 @@ module internal Tokenizer = MatchedLength = lastToken.MatchedLength + token.MatchedLength } - acc, Some mergedToken + acc, ValueSome mergedToken | _ -> let isLastToken = index = tokensCount - 1 match token with | GenericTypeParameterPrefix when not isLastToken -> - acc, Some(DraftTokenInfo.Create LexerSymbolKind.GenericTypeParameter token) + acc, ValueSome(DraftTokenInfo.Create LexerSymbolKind.GenericTypeParameter token) | StaticallyResolvedTypeParameterPrefix when not isLastToken -> - acc, Some(DraftTokenInfo.Create LexerSymbolKind.StaticallyResolvedTypeParameter token) - | ActivePattern when wholeActivePatterns -> acc, Some(DraftTokenInfo.Create LexerSymbolKind.ActivePattern token) + acc, ValueSome(DraftTokenInfo.Create LexerSymbolKind.StaticallyResolvedTypeParameter token) + | ActivePattern when wholeActivePatterns -> acc, ValueSome(DraftTokenInfo.Create LexerSymbolKind.ActivePattern token) | _ -> let draftToken = match lastToken with - | Some { - Kind = LexerSymbolKind.GenericTypeParameter | LexerSymbolKind.StaticallyResolvedTypeParameter as kind - } when token.IsIdentifier -> + | ValueSome { + Kind = LexerSymbolKind.GenericTypeParameter | LexerSymbolKind.StaticallyResolvedTypeParameter as kind + } when token.IsIdentifier -> { Kind = kind LeftColumn = token.LeftColumn - 1 MatchedLength = token.MatchedLength + 1 } // ^ operator - | Some { - Kind = LexerSymbolKind.StaticallyResolvedTypeParameter - } -> + | ValueSome { + Kind = LexerSymbolKind.StaticallyResolvedTypeParameter + } -> { Kind = LexerSymbolKind.Operator LeftColumn = token.LeftColumn - 1 MatchedLength = 1 } - | Some ({ Kind = LexerSymbolKind.ActivePattern } as ap) when + | ValueSome ({ Kind = LexerSymbolKind.ActivePattern } as ap) when wholeActivePatterns && token.Tag = FSharpTokenTag.RPAREN -> { @@ -843,7 +840,7 @@ module internal Tokenizer = MatchedLength = token.MatchedLength } - draftToken :: acc, Some draftToken) + draftToken :: acc, ValueSome draftToken) |> fst // One or two tokens that in touch with the cursor (for "let x|(g) = ()" the tokens will be "x" and "(") @@ -853,46 +850,53 @@ module internal Tokenizer = | SymbolLookupKind.Precise -> 0 | SymbolLookupKind.Greedy -> 1 - draftTokens - |> List.filter (fun x -> - x.LeftColumn <= linePos.Character - && (x.RightColumn + rightColumnCorrection) >= linePos.Character) + [ + for x in draftTokens do + if + x.LeftColumn <= linePos.Character + && (x.RightColumn + rightColumnCorrection) >= linePos.Character + then + yield x + ] // Select IDENT token. If failed, select OPERATOR token. - tokensUnderCursor - |> List.tryFind (fun token -> - match token.Kind with - | LexerSymbolKind.Ident - | LexerSymbolKind.Keyword - | LexerSymbolKind.ActivePattern - | LexerSymbolKind.GenericTypeParameter - | LexerSymbolKind.StaticallyResolvedTypeParameter -> true - | _ -> false) - |> Option.orElseWith (fun _ -> + let symbol = tokensUnderCursor - |> List.tryFind (fun token -> token.Kind = LexerSymbolKind.Operator)) - |> Option.orElseWith (fun _ -> - if allowStringToken then + |> List.tryFindV (fun token -> + match token.Kind with + | LexerSymbolKind.Ident + | LexerSymbolKind.Keyword + | LexerSymbolKind.ActivePattern + | LexerSymbolKind.GenericTypeParameter + | LexerSymbolKind.StaticallyResolvedTypeParameter -> true + | _ -> false) + |> ValueOption.orElseWith (fun _ -> tokensUnderCursor - |> List.tryFind (fun token -> token.Kind = LexerSymbolKind.String) - else - None) - |> Option.map (fun token -> - let partialName = QuickParse.GetPartialLongNameEx(lineStr, token.RightColumn) - let identStr = lineStr.Substring(token.LeftColumn, token.MatchedLength) - - { - Kind = token.Kind - Ident = - Ident( - identStr, - Range.mkRange - fileName - (Position.mkPos (linePos.Line + 1) token.LeftColumn) - (Position.mkPos (linePos.Line + 1) (token.RightColumn + 1)) - ) - FullIsland = partialName.QualifyingIdents @ [ identStr ] - }) + |> List.tryFindV (fun token -> token.Kind = LexerSymbolKind.Operator)) + |> ValueOption.orElseWith (fun _ -> + if allowStringToken then + tokensUnderCursor + |> List.tryFindV (fun token -> token.Kind = LexerSymbolKind.String) + else + ValueNone) + |> ValueOption.map (fun token -> + let partialName = QuickParse.GetPartialLongNameEx(lineStr, token.RightColumn) + let identStr = lineStr.Substring(token.LeftColumn, token.MatchedLength) + + { + Kind = token.Kind + Ident = + Ident( + identStr, + Range.mkRange + fileName + (Position.mkPos (linePos.Line + 1) token.LeftColumn) + (Position.mkPos (linePos.Line + 1) (token.RightColumn + 1)) + ) + FullIsland = partialName.QualifyingIdents @ [ identStr ] + }) + + ValueOption.toOption symbol let private getCachedSourceLineData ( @@ -921,7 +925,7 @@ module internal Tokenizer = lineData, textLinePos, contents - let tokenizeLine (documentKey, sourceText, position, fileName, defines, langVersion, strictIndentation, cancellationToken) = + let inline tokenizeLine (documentKey, sourceText, position, fileName, defines, langVersion, strictIndentation, cancellationToken) = try let lineData, _, _ = getCachedSourceLineData ( @@ -983,12 +987,15 @@ module internal Tokenizer = Assert.Exception(ex) None + [] + let private doubleBackTickDelimiter = "``" + /// Fix invalid span if it appears to have redundant suffix and prefix. let fixupSpan (sourceText: SourceText, span: TextSpan) : TextSpan = let text = sourceText.GetSubText(span).ToString() // backticked ident - if text.EndsWith "``" then - match text.LastIndexOf("``", text.Length - 3, text.Length - 2) with + if text.EndsWith doubleBackTickDelimiter then + match text.LastIndexOf(doubleBackTickDelimiter, text.Length - 3, text.Length - 2) with | -1 | 0 -> span | index -> TextSpan(span.Start + index, text.Length - index) @@ -998,8 +1005,6 @@ module internal Tokenizer = | 0 -> span | index -> TextSpan(span.Start + index + 1, text.Length - index - 1) - let private doubleBackTickDelimiter = "``" - let isDoubleBacktickIdent (s: string) = let doubledDelimiter = 2 * doubleBackTickDelimiter.Length @@ -1015,7 +1020,7 @@ module internal Tokenizer = let isValidNameForSymbol (lexerSymbolKind: LexerSymbolKind, symbol: FSharpSymbol, name: string) : bool = - let isIdentifier (ident: string) = + let inline isIdentifier (ident: string) = if isDoubleBacktickIdent ident then true else @@ -1027,21 +1032,21 @@ module internal Tokenizer = else PrettyNaming.IsIdentifierPartCharacter c) - let isFixableIdentifier (s: string) = + let inline isFixableIdentifier (s: string) = not (String.IsNullOrEmpty s) && FSharpKeywords.NormalizeIdentifierBackticks s |> isIdentifier let forbiddenChars = [| '.'; '+'; '$'; '&'; '['; ']'; '/'; '\\'; '*'; '\"' |] - let isTypeNameIdent (s: string) = + let inline isTypeNameIdent (s: string) = not (String.IsNullOrEmpty s) && s.IndexOfAny forbiddenChars = -1 && isFixableIdentifier s - let isUnionCaseIdent (s: string) = + let inline isUnionCaseIdent (s: string) = isTypeNameIdent s && Char.IsUpper(s.Replace(doubleBackTickDelimiter, "").[0]) - let isTypeParameter (prefix: char) (s: string) = + let inline isTypeParameter (prefix: char) (s: string) = s.Length >= 2 && s.[0] = prefix && isIdentifier s.[1..] let isGenericTypeParameter = isTypeParameter ''' diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index 07efc7b04fa..3da3fafa6b1 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -9,6 +9,7 @@ open Microsoft.CodeAnalysis open FSharp.Compiler open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks [] module private CheckerExtensions = @@ -32,17 +33,17 @@ module private CheckerExtensions = allowStaleResults: bool, userOpName: string ) = - async { - let! ct = Async.CancellationToken + cancellableTask { + let! ct = CancellableTask.getCancellationToken () - let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask - let! textVersion = document.GetTextVersionAsync(ct) |> Async.AwaitTask + let! sourceText = document.GetTextAsync(ct) + let! textVersion = document.GetTextVersionAsync(ct) let filePath = document.FilePath let textVersionHash = textVersion.GetHashCode() let parseAndCheckFile = - async { + cancellableTask { let! (parseResults, checkFileAnswer) = checker.ParseAndCheckFileInProject( filePath, @@ -59,7 +60,7 @@ module private CheckerExtensions = } let tryGetFreshResultsWithTimeout () = - async { + cancellableTask { let! worker = Async.StartChild( async { @@ -87,9 +88,9 @@ module private CheckerExtensions = let! results = match freshResults with - | Some x -> async.Return(Some x) + | Some x -> CancellableTask.singleton (Some x) | None -> - async { + cancellableTask { match checker.TryGetRecentCheckResultsForFile(filePath, options, userOpName = userOpName) with | Some (parseResults, checkFileResults, _) -> return Some(parseResults, checkFileResults) | None -> return! parseAndCheckFile @@ -109,7 +110,7 @@ module private CheckerExtensions = userOpName: string, ?allowStaleResults: bool ) = - async { + cancellableTask { let allowStaleResults = match allowStaleResults with | Some b -> b @@ -137,29 +138,26 @@ type Document with /// Get the FSharpParsingOptions and FSharpProjectOptions from the F# project that is associated with the given F# document. member this.GetFSharpCompilationOptionsAsync(userOpName) = - async { - if this.Project.IsFSharp then - match ProjectCache.Projects.TryGetValue(this.Project) with - | true, result -> return result - | _ -> + if not this.Project.IsFSharp then + raise (OperationCanceledException("Document is not a FSharp document.")) + else + match ProjectCache.Projects.TryGetValue(this.Project) with + | true, result -> CancellableTask.singleton result + | _ -> + cancellableTask { let service = this.Project.Solution.GetFSharpWorkspaceService() let projectOptionsManager = service.FSharpProjectOptionsManager - let! ct = Async.CancellationToken + let! ct = CancellableTask.getCancellationToken () match! projectOptionsManager.TryGetOptionsForDocumentOrProject(this, ct, userOpName) with - | None -> return raise (System.OperationCanceledException("FSharp project options not found.")) + | None -> return raise (OperationCanceledException("FSharp project options not found.")) | Some (parsingOptions, projectOptions) -> let result = (service.Checker, projectOptionsManager, parsingOptions, projectOptions) return - ProjectCache.Projects.GetValue( - this.Project, - Runtime.CompilerServices.ConditionalWeakTable<_, _>.CreateValueCallback (fun _ -> result) - ) - else - return raise (System.OperationCanceledException("Document is not a FSharp document.")) - } + 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) = @@ -216,22 +214,22 @@ type Document with /// Parses and checks the given F# document. member this.GetFSharpParseAndCheckResultsAsync(userOpName) = - async { + cancellableTask { let! checker, _, _, projectOptions = this.GetFSharpCompilationOptionsAsync(userOpName) match! checker.ParseAndCheckDocument(this, projectOptions, userOpName, allowStaleResults = false) with | Some (parseResults, _, checkResults) -> return (parseResults, checkResults) - | _ -> return raise (System.OperationCanceledException("Unable to get FSharp parse and check results.")) + | _ -> return raise (OperationCanceledException("Unable to get FSharp parse and check results.")) } /// Get the semantic classifications of the given F# document. member this.GetFSharpSemanticClassificationAsync(userOpName) = - async { + cancellableTask { let! checker, _, _, projectOptions = this.GetFSharpCompilationOptionsAsync(userOpName) match! checker.GetBackgroundSemanticClassificationForFile(this.FilePath, projectOptions) with | Some results -> return results - | _ -> return raise (System.OperationCanceledException("Unable to get FSharp semantic classification.")) + | _ -> return raise (OperationCanceledException("Unable to get FSharp semantic classification.")) } /// Find F# references in the given F# document. @@ -278,8 +276,10 @@ type Document with type Project with /// Find F# references in the given project. - member this.FindFSharpReferencesAsync(symbol: FSharpSymbol, onFound, userOpName, ct) : Task = - backgroundTask { + member this.FindFSharpReferencesAsync(symbol: FSharpSymbol, onFound, userOpName) = + cancellableTask { + + let! ct = CancellableTask.getCancellationToken () let declarationLocation = symbol.SignatureLocation @@ -292,10 +292,8 @@ type Project with let! canSkipDocuments = match declarationDocument with | Some document when this.IsFastFindReferencesEnabled && document.Project = this -> - backgroundTask { - let! _, _, _, options = - document.GetFSharpCompilationOptionsAsync(userOpName) - |> RoslynHelpers.StartAsyncAsTask ct + cancellableTask { + let! _, _, _, options = document.GetFSharpCompilationOptionsAsync(userOpName) let signatureFile = if not (document.FilePath |> isSignatureFile) then @@ -309,7 +307,7 @@ type Project with |> Seq.filter ((<>) signatureFile) |> Set } - | _ -> Task.FromResult Set.empty + | _ -> CancellableTask.singleton Set.empty let documents = this.Documents @@ -325,17 +323,20 @@ type Project with |> Task.WhenAll else for doc in documents do - do! - doc.FindFSharpReferencesAsync(symbol, (fun range -> onFound doc range), userOpName) - |> RoslynHelpers.StartAsyncAsTask ct + do! doc.FindFSharpReferencesAsync(symbol, (fun range -> onFound doc range), userOpName) } - member this.GetFSharpCompilationOptionsAsync(ct: CancellationToken) = - backgroundTask { - if this.IsFSharp then - match ProjectCache.Projects.TryGetValue(this) with - | true, result -> return result - | _ -> + member this.GetFSharpCompilationOptionsAsync() = + if not this.IsFSharp then + raise (OperationCanceledException("Project is not a FSharp project.")) + else + match ProjectCache.Projects.TryGetValue(this) with + | true, result -> CancellableTask.singleton result + | _ -> + cancellableTask { + + let! ct = CancellableTask.getCancellationToken () + let service = this.Solution.GetFSharpWorkspaceService() let projectOptionsManager = service.FSharpProjectOptionsManager @@ -346,6 +347,4 @@ type Project with (service.Checker, projectOptionsManager, parsingOptions, projectOptions) return ProjectCache.Projects.GetValue(this, ConditionalWeakTable<_, _>.CreateValueCallback (fun _ -> result)) - else - return raise (OperationCanceledException("Project is not a FSharp project.")) - } + } diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs index a681f2f9f8c..df7ebb4d7cd 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs @@ -15,6 +15,7 @@ open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.EditorServices open FSharp.Compiler.Text open Microsoft.CodeAnalysis.Text +open CancellableTasks [)>] type internal FSharpFindUsagesService [] () = @@ -61,6 +62,8 @@ type internal FSharpFindUsagesService [] () = let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpFindUsagesService)) + |> CancellableTask.start context.CancellationToken + |> Async.AwaitTask |> liftAsync let! symbolUse = @@ -127,9 +130,9 @@ type internal FSharpFindUsagesService [] () = externalDefinitionItem else definitionItems - |> List.tryFind (snd >> (=) doc.Project.FilePath) - |> Option.map fst - |> Option.defaultValue externalDefinitionItem + |> List.tryFindV (fun (_, filePath) -> doc.Project.FilePath = filePath) + |> ValueOption.map (fun (definitionItem, _) -> definitionItem) + |> ValueOption.defaultValue externalDefinitionItem let referenceItem = FSharpSourceReferenceItem(definitionItem, FSharpDocumentSpan(doc, textSpan)) diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 64e229f00cd..96f7f0d54a8 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -28,6 +28,7 @@ open FSharp.Compiler.Symbols open FSharp.Compiler.Tokenization open System.Composition open System.Text.RegularExpressions +open CancellableTasks module private Symbol = let fullName (root: ISymbol) : string = @@ -193,8 +194,12 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() let idRange = lexerSymbol.Ident.idRange + let! ct = Async.CancellationToken |> liftAsync + let! _, checkFileResults = originDocument.GetFSharpParseAndCheckResultsAsync(nameof (GoToDefinition)) + |> CancellableTask.start ct + |> Async.AwaitTask |> liftAsync let! fsSymbolUse = @@ -212,7 +217,13 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = else let! implDoc = originDocument.Project.Solution.TryGetDocumentFromPath fsfilePath let! implSourceText = implDoc.GetTextAsync() - let! _, checkFileResults = implDoc.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync + + 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) @@ -228,12 +239,15 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = member _.FindSymbolDeclarationInDocument(targetSymbolUse: FSharpSymbolUse, document: Document) = asyncMaybe { let filePath = document.FilePath + let! ct = Async.CancellationToken |> liftAsync match targetSymbolUse.Symbol.DeclarationLocation with | Some decl when decl.FileName = filePath -> return decl | _ -> let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("FindSymbolDeclarationInDocument") + |> CancellableTask.start ct + |> Async.AwaitTask |> liftAsync let symbolUses = checkFileResults.GetUsesOfSymbolInFile targetSymbolUse.Symbol @@ -251,12 +265,18 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = let fcsTextLineNumber = Line.fromZ textLinePos.Line let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() + let! ct = Async.CancellationToken |> liftAsync + 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) |> liftAsync + let! _, checkFileResults = + originDocument.GetFSharpParseAndCheckResultsAsync(userOpName) + |> CancellableTask.start ct + |> Async.AwaitTask + |> liftAsync let declarations = checkFileResults.GetDeclarationLocation( @@ -274,10 +294,10 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = | FindDeclResult.ExternalDecl (assembly, targetExternalSym) -> let projectOpt = originDocument.Project.Solution.Projects - |> Seq.tryFind (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase)) + |> Seq.tryFindV (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase)) match projectOpt with - | Some project -> + | ValueSome project -> let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, (fun _ -> true)) let roslynSymbols = @@ -501,11 +521,16 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = metadataAsSource.ShowDocument(tmpProjInfo, tmpDocInfo.FilePath, SourceText.From(text.ToString())) match tmpShownDocOpt with - | Some tmpShownDoc -> + | ValueSome tmpShownDoc -> let goToAsync = asyncMaybe { + + let! ct = Async.CancellationToken |> liftAsync + let! _, checkResults = tmpShownDoc.GetFSharpParseAndCheckResultsAsync("NavigateToExternalDeclaration") + |> CancellableTask.start ct + |> Async.AwaitTask |> liftAsync let! r = @@ -529,7 +554,7 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = // 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.tryFind (fun x -> + |> Seq.tryFindV (fun x -> match x.Symbol, targetSymbolUse.Symbol with | (:? FSharpEntity as symbol1), (:? FSharpEntity as symbol2) when x.IsFromDefinition -> symbol1.DisplayName = symbol2.DisplayName @@ -554,7 +579,8 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = symbol1.DisplayName = symbol2.DisplayName && symbol1.DeclaringEntity.CompiledName = symbol2.DeclaringEntity.CompiledName | _ -> false) - |> Option.map (fun x -> x.Range) + |> ValueOption.map (fun x -> x.Range) + |> ValueOption.toOption let span = match RoslynHelpers.TryFSharpRangeToTextSpan(tmpShownDoc.GetTextAsync(cancellationToken).Result, r) with @@ -913,7 +939,7 @@ type FSharpCrossLanguageSymbolNavigationService() = let path = FSharpCrossLanguageSymbolNavigationService.DocCommentIdToPath documentationCommentId - backgroundTask { + cancellableTask { let projects = workspace.CurrentSolution.Projects |> Seq.filter (fun p -> p.IsFSharp && p.AssemblyName = assemblyName) @@ -921,7 +947,7 @@ type FSharpCrossLanguageSymbolNavigationService() = let mutable locations = Seq.empty for project in projects do - let! checker, _, _, options = project.GetFSharpCompilationOptionsAsync(cancellationToken) + let! checker, _, _, options = project.GetFSharpCompilationOptionsAsync() let! result = checker.ParseAndCheckProject(options) match path with @@ -969,3 +995,4 @@ type FSharpCrossLanguageSymbolNavigationService() = else return Unchecked.defaultof<_> // returning null here, so Roslyn can fallback to default source-as-metadata implementation. } + |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index 0311769f4d8..1107e6f5869 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -37,7 +37,7 @@ type internal FSharpNavigateToSearchService [] let getNavigableItems (document: Document) = cancellableTask { - let! ct = CancellableTask.getCurrentCancellationToken () + let! ct = CancellableTask.getCancellationToken () let! currentVersion = document.GetTextVersionAsync(ct) match cache.TryGetValue document.Id with @@ -146,7 +146,7 @@ type internal FSharpNavigateToSearchService [] let processDocument (tryMatch: NavigableItem -> PatternMatch option) (kinds: IImmutableSet) (document: Document) = cancellableTask { - let! ct = CancellableTask.getCurrentCancellationToken () + let! ct = CancellableTask.getCancellationToken () let! sourceText = document.GetTextAsync ct @@ -189,14 +189,19 @@ type internal FSharpNavigateToSearchService [] cancellableTask { let tryMatch = createMatcherFor searchPattern - let! ct = CancellableTask.getCurrentCancellationToken () - let tasks = - Seq.map (fun doc -> processDocument tryMatch kinds doc ct) project.Documents + Seq.map (fun doc -> processDocument tryMatch kinds doc) project.Documents + + let! results = CancellableTask.whenAll tasks + + let results' = ImmutableArray.CreateBuilder() + + for navResults in results do + for navResult in navResults do + results'.Add navResult - let! results = Task.WhenAll(tasks) + return results'.ToImmutable() - return results |> Array.concat |> Array.toImmutableArray } |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs index db0f3933703..7fb51b9f83b 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs @@ -75,7 +75,7 @@ type internal FSharpAsyncQuickInfoSource static member TryGetToolTip(document: Document, position, ?width) = cancellableTask { let userOpName = "getQuickInfo" - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, true, true, userOpName) match lexerSymbol with diff --git a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs index 5b119cca8c5..a018bb52558 100644 --- a/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs +++ b/vsintegration/src/FSharp.Editor/Refactor/AddExplicitTypeToParameter.fs @@ -15,6 +15,7 @@ open FSharp.Compiler.Syntax open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeRefactorings open Microsoft.CodeAnalysis.CodeActions +open CancellableTasks [] type internal FSharpAddExplicitTypeToParameterRefactoring [] () = @@ -29,6 +30,8 @@ type internal FSharpAddExplicitTypeToParameterRefactoring [ liftAsync + let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync( position, @@ -40,6 +43,8 @@ type internal FSharpAddExplicitTypeToParameterRefactoring [ CancellableTask.start ct + |> Async.AwaitTask |> liftAsync let! symbolUse = diff --git a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs index 7a80409f505..ac35e69af22 100644 --- a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs +++ b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs @@ -155,7 +155,7 @@ type internal FSharpBlockStructureService [] () = member _.GetBlockStructureAsync(document, cancellationToken) : Task = cancellableTask { - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () let! sourceText = document.GetTextAsync(cancellationToken) diff --git a/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs b/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs index 52c486157fe..1c52afd7364 100644 --- a/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs +++ b/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs @@ -13,14 +13,19 @@ open FSharp.Compiler open System.Collections.Immutable open System.Diagnostics +open CancellableTasks [)>] type internal FSharpTaskListService [] () as this = let getDefinesAndLangVersion (doc: Microsoft.CodeAnalysis.Document) = asyncMaybe { + let! ct = Async.CancellationToken |> liftAsync + let! _, _, parsingOptions, _ = doc.GetFSharpCompilationOptionsAsync(nameof (FSharpTaskListService)) + |> CancellableTask.start ct + |> Async.AwaitTask |> liftAsync return diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs index 5c1edf09329..72ea92e53a5 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs @@ -45,7 +45,7 @@ let getRelevantDiagnostic (document: Document) = let createTestCodeFixContext (code: string) document (mode: Mode) = cancellableTask { - let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! cancellationToken = CancellableTask.getCancellationToken () let sourceText = SourceText.From code diff --git a/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs index 6fd9f08729b..cc6bdbae43c 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs @@ -9,6 +9,8 @@ open Microsoft.VisualStudio.FSharp.Editor open FSharp.Compiler.EditorServices open FSharp.Compiler.Text open FSharp.Editor.Tests.Helpers +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks +open System.Threading module GoToDefinitionServiceTests = @@ -44,7 +46,7 @@ module GoToDefinitionServiceTests = let _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(nameof (userOpName)) - |> Async.RunSynchronously + |> CancellableTask.runSynchronouslyWithoutCancellation let declarations = checkFileResults.GetDeclarationLocation( diff --git a/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs index d35b708c3e3..5c550080ed5 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs @@ -42,17 +42,19 @@ type HelpContextServiceTests() = let textLine = sourceText.Lines.GetLineFromPosition(marker) let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let classifiedSpans = - Tokenizer.getClassifiedSpans ( - documentId, - sourceText, - textLine.Span, - Some "test.fs", - [], - None, - None, - CancellationToken.None - ) + let classifiedSpans = ResizeArray<_>() + + Tokenizer.classifySpans ( + documentId, + sourceText, + textLine.Span, + Some "test.fs", + [], + None, + None, + classifiedSpans, + CancellationToken.None + ) let task = FSharpHelpContextService.GetHelpTerm(document, span, classifiedSpans) diff --git a/vsintegration/tests/FSharp.Editor.Tests/Hints/HintTestFramework.fs b/vsintegration/tests/FSharp.Editor.Tests/Hints/HintTestFramework.fs index 1ea7b95f79f..df6d3c73c0c 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/Hints/HintTestFramework.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/Hints/HintTestFramework.fs @@ -38,7 +38,7 @@ module HintTestFramework = let getHints (document: Document) hintKinds = let task = cancellableTask { - let! ct = CancellableTask.getCurrentCancellationToken () + let! ct = CancellableTask.getCancellationToken () let getTooltip hint = async { diff --git a/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs index 9b220a69f70..a7b48c70032 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs @@ -52,17 +52,19 @@ let main argv = let sourceText = SourceText.From(code) let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let classifiedSpans = - Tokenizer.getClassifiedSpans ( - documentId, - sourceText, - TextSpan.FromBounds(0, sourceText.Length), - Some(fileName), - defines, - None, - None, - CancellationToken.None - ) + let classifiedSpans = ResizeArray<_>() + + Tokenizer.classifySpans ( + documentId, + sourceText, + TextSpan.FromBounds(0, sourceText.Length), + Some(fileName), + defines, + None, + None, + classifiedSpans, + CancellationToken.None + ) let actualDataTipSpanOption = FSharpLanguageDebugInfoService.GetDataTipInformation(sourceText, searchPosition, classifiedSpans) diff --git a/vsintegration/tests/FSharp.Editor.Tests/SemanticClassificationServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/SemanticClassificationServiceTests.fs index 38a7490b552..64313dd6bd5 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/SemanticClassificationServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/SemanticClassificationServiceTests.fs @@ -10,16 +10,19 @@ open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Classification open FSharp.Editor.Tests.Helpers open FSharp.Test +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks type SemanticClassificationServiceTests() = let getRanges (source: string) : SemanticClassificationItem list = asyncMaybe { + let! ct = Async.CancellationToken |> liftAsync + let document = RoslynTestHelpers.CreateSolution(source) |> RoslynTestHelpers.GetSingleDocument let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("SemanticClassificationServiceTests") - |> liftAsync + |> CancellableTask.start ct return checkFileResults.GetSemanticClassification(None) } diff --git a/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs index 525a17eef67..f4876014cbb 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs @@ -11,6 +11,7 @@ open Microsoft.CodeAnalysis.Text open FSharp.Editor.Tests.Helpers open Microsoft.CodeAnalysis open Microsoft.IO +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks module SignatureHelpProvider = let private DefaultDocumentationProvider = @@ -25,6 +26,7 @@ module SignatureHelpProvider = let GetSignatureHelp (project: FSharpProject) (fileName: string) (caretPosition: int) = async { + let! ct = Async.CancellationToken let triggerChar = None let fileContents = File.ReadAllText(fileName) let sourceText = SourceText.From(fileContents) @@ -38,7 +40,7 @@ module SignatureHelpProvider = let parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("GetSignatureHelp") - |> Async.RunSynchronously + |> CancellableTask.runSynchronously ct let paramInfoLocations = parseResults @@ -105,7 +107,7 @@ module SignatureHelpProvider = let parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("assertSignatureHelpForMethodCalls") - |> Async.RunSynchronously + |> CancellableTask.runSynchronouslyWithoutCancellation let actual = let paramInfoLocations = @@ -152,7 +154,7 @@ module SignatureHelpProvider = let parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("assertSignatureHelpForFunctionApplication") - |> Async.RunSynchronously + |> CancellableTask.runSynchronouslyWithoutCancellation let adjustedColumnInSource = let rec loop ch pos = @@ -495,7 +497,7 @@ M.f let parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("function application in single pipeline with no additional args") - |> Async.RunSynchronously + |> CancellableTask.runSynchronouslyWithoutCancellation let adjustedColumnInSource = let rec loop ch pos = diff --git a/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs index 8b42693aded..41d25564144 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs @@ -30,17 +30,19 @@ type SyntacticClassificationServiceTests() = let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let tokens = - Tokenizer.getClassifiedSpans ( - documentId, - SourceText.From(fileContents), - textSpan, - Some(fileName), - defines, - langVersion, - None, - CancellationToken.None - ) + let tokens = ResizeArray<_>() + + Tokenizer.classifySpans ( + documentId, + SourceText.From(fileContents), + textSpan, + Some(fileName), + defines, + langVersion, + None, + tokens, + CancellationToken.None + ) let markerPosition = fileContents.IndexOf(marker) Assert.True(markerPosition >= 0, $"Cannot find marker '{marker}' in file contents") diff --git a/vsintegration/tests/UnitTests/Workspace/WorkspaceTests.fs b/vsintegration/tests/UnitTests/Workspace/WorkspaceTests.fs index 9b6d7537862..f411cfa973e 100644 --- a/vsintegration/tests/UnitTests/Workspace/WorkspaceTests.fs +++ b/vsintegration/tests/UnitTests/Workspace/WorkspaceTests.fs @@ -20,6 +20,7 @@ open Microsoft.VisualStudio.LanguageServices open Microsoft.VisualStudio.Shell open VisualFSharp.UnitTests.Editor open NUnit.Framework +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks [] module WorkspaceTests = @@ -167,14 +168,14 @@ module WorkspaceTests = let assertEmptyDocumentDiagnostics (workspace: Workspace) (filePath: string) = let doc = getDocument workspace filePath - let parseResults, checkResults = doc.GetFSharpParseAndCheckResultsAsync("assertEmptyDocumentDiagnostics") |> Async.RunSynchronously + let parseResults, checkResults = doc.GetFSharpParseAndCheckResultsAsync("assertEmptyDocumentDiagnostics") |> CancellableTask.runSynchronouslyWithoutCancellation Assert.IsEmpty(parseResults.Diagnostics) Assert.IsEmpty(checkResults.Diagnostics) let assertHasDocumentDiagnostics (workspace: Workspace) (filePath: string) = let doc = getDocument workspace filePath - let parseResults, checkResults = doc.GetFSharpParseAndCheckResultsAsync("assertHasDocumentDiagnostics") |> Async.RunSynchronously + let parseResults, checkResults = doc.GetFSharpParseAndCheckResultsAsync("assertHasDocumentDiagnostics") |> CancellableTask.runSynchronouslyWithoutCancellation Assert.IsEmpty(parseResults.Diagnostics) Assert.IsNotEmpty(checkResults.Diagnostics)