diff --git a/.fantomasignore b/.fantomasignore index 45eb387fa16..4830d0af77d 100644 --- a/.fantomasignore +++ b/.fantomasignore @@ -15,7 +15,6 @@ artifacts/ # For some reason, it tries to format files from remotes (Processing .\.git\refs\remotes\\FSComp.fsi) .git/ - # Explicitly unformatted implementation src/Compiler/Checking/AccessibilityLogic.fs src/Compiler/Checking/AttributeChecking.fs @@ -98,6 +97,7 @@ src/Compiler/Service/IncrementalBuild.fs src/Compiler/Service/ServiceAssemblyContent.fs src/Compiler/Service/ServiceDeclarationLists.fs src/Compiler/Service/ServiceErrorResolutionHints.fs +vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs # Fantomas limitations on signature files (to investigate) diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs index 79405d93b48..442e13b44cd 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationDefinitions.fs @@ -17,7 +17,6 @@ open Microsoft.VisualStudio.Text.Classification open Microsoft.VisualStudio.Utilities open Microsoft.CodeAnalysis.Classification -open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.EditorServices [] diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs index 31d69db7353..6afae8b6aaf 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs @@ -6,20 +6,17 @@ open System open System.Composition open System.Collections.Generic open System.Collections.Immutable -open System.Diagnostics open System.Threading open System.Runtime.Caching open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Classification -open Microsoft.CodeAnalysis.Editor -open Microsoft.CodeAnalysis.Host.Mef open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Classification -open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.EditorServices open FSharp.Compiler.Tokenization +open CancellableTasks open Microsoft.VisualStudio.FSharp.Editor.Telemetry // IEditorClassificationService is marked as Obsolete, but is still supported. The replacement (IClassificationService) @@ -32,49 +29,11 @@ open Microsoft.VisualStudio.FSharp.Editor.Telemetry type SemanticClassificationData = SemanticClassificationView type SemanticClassificationLookup = IReadOnlyDictionary> -[] -type DocumentCache<'Value when 'Value: not struct>() = - /// Anything under two seconds, the caching stops working, meaning it won't actually cache the item. - /// Two seconds is just enough to keep the data around long enough to handle a flood of a requests asking for the same data - /// in a short period of time. - [] - let slidingExpirationSeconds = 2. - - let cache = new MemoryCache("fsharp-cache") - - let policy = - CacheItemPolicy(SlidingExpiration = TimeSpan.FromSeconds slidingExpirationSeconds) - - member _.TryGetValueAsync(doc: Document) = - async { - let! ct = Async.CancellationToken - let! currentVersion = doc.GetTextVersionAsync ct |> Async.AwaitTask - - match cache.Get(doc.Id.ToString()) with - | null -> return ValueNone - | :? (VersionStamp * 'Value) as value -> - if fst value = currentVersion then - return ValueSome(snd value) - else - return ValueNone - | _ -> return ValueNone - } - - member _.SetAsync(doc: Document, value: 'Value) = - async { - let! ct = Async.CancellationToken - let! currentVersion = doc.GetTextVersionAsync ct |> Async.AwaitTask - cache.Set(doc.Id.ToString(), (currentVersion, value), policy) - } - - interface IDisposable with - - member _.Dispose() = cache.Dispose() - [)>] type internal FSharpClassificationService [] () = - static let getLexicalClassifications (filePath: string, defines, text: SourceText, textSpan: TextSpan, ct) = + static let getLexicalClassifications (filePath: string, defines, text: SourceText, textSpan: TextSpan, ct: CancellationToken) = + let text = text.GetSubText(textSpan) let result = ImmutableArray.CreateBuilder() @@ -144,8 +103,7 @@ type internal FSharpClassificationService [] () = | _ -> () static let toSemanticClassificationLookup (d: SemanticClassificationData) = - let lookup = - System.Collections.Generic.Dictionary>() + let lookup = Dictionary>() let f (dataItem: SemanticClassificationItem) = let items = @@ -160,9 +118,10 @@ type internal FSharpClassificationService [] () = d.ForEach(f) - System.Collections.ObjectModel.ReadOnlyDictionary lookup :> IReadOnlyDictionary<_, _> + Collections.ObjectModel.ReadOnlyDictionary lookup :> IReadOnlyDictionary<_, _> - let semanticClassificationCache = new DocumentCache() + let semanticClassificationCache = + new DocumentCache("fsharp-semantic-classification-cache") interface IFSharpClassificationService with // Do not perform classification if we don't have project options (#defines matter) @@ -175,11 +134,13 @@ type internal FSharpClassificationService [] () = result: List, cancellationToken: CancellationToken ) = - async { + cancellableTask { use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Syntactic) + let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() - let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let! sourceText = document.GetTextAsync(cancellationToken) // For closed documents, only get classification for the text within the span. // This may be inaccurate for multi-line tokens such as string literals, but this is ok for now @@ -198,7 +159,10 @@ type internal FSharpClassificationService [] () = TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.AddSyntacticCalssifications, eventProps) if not isOpenDocument then - result.AddRange(getLexicalClassifications (document.FilePath, defines, sourceText, textSpan, cancellationToken)) + let classifiedSpans = + getLexicalClassifications (document.FilePath, defines, sourceText, textSpan, cancellationToken) + + result.AddRange(classifiedSpans) else result.AddRange( Tokenizer.getClassifiedSpans ( @@ -212,7 +176,7 @@ type internal FSharpClassificationService [] () = ) ) } - |> RoslynHelpers.StartAsyncUnitAsTask cancellationToken + |> CancellableTask.startAsTask cancellationToken member _.AddSemanticClassificationsAsync ( @@ -221,10 +185,10 @@ type internal FSharpClassificationService [] () = result: List, cancellationToken: CancellationToken ) = - async { + cancellableTask { use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Semantic) - let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let! sourceText = document.GetTextAsync(cancellationToken) // If we are trying to get semantic classification for a document that is not open, get the results from the background and cache it. // We do this for find all references when it is populating results. @@ -248,9 +212,11 @@ type internal FSharpClassificationService [] () = addSemanticClassificationByLookup sourceText textSpan classificationDataLookup result | _ -> - let eventProps = + let eventProps: (string * obj) array = [| - "isOpenDocument", isOpenDocument :> obj + "context.document.project.id", document.Project.Id.Id.ToString() + "context.document.id", document.Id.Id.ToString() + "isOpenDocument", isOpenDocument "textSpanLength", textSpan.Length "cacheHit", false |] @@ -283,8 +249,7 @@ type internal FSharpClassificationService [] () = let classificationData = checkResults.GetSemanticClassification(Some targetRange) addSemanticClassification sourceText textSpan classificationData result } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask cancellationToken + |> CancellableTask.startAsTask cancellationToken // Do not perform classification if we don't have project options (#defines matter) member _.AdjustStaleClassification(_: SourceText, classifiedSpan: ClassifiedSpan) : ClassifiedSpan = classifiedSpan diff --git a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs index 88f5e5ae669..bba2e4a4cd6 100644 --- a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs @@ -14,18 +14,18 @@ open FSharp.Compiler.EditorServices open FSharp.Compiler.Syntax open FSharp.Compiler.Text open Microsoft.CodeAnalysis +open CancellableTasks [] [, FSharpConstants.FSharpLanguageName)>] type internal FSharpHelpContextService [] () = - static member GetHelpTerm(document: Document, span: TextSpan, tokens: List) : Async = - asyncMaybe { - let! _, check = - document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpHelpContextService)) - |> liftAsync + static member GetHelpTerm(document: Document, span: TextSpan, tokens: List) : CancellableTask = + cancellableTask { + let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! _, check = document.GetFSharpParseAndCheckResultsAsync(nameof (FSharpHelpContextService)) - let! sourceText = document.GetTextAsync() |> liftTaskAsync + let! sourceText = document.GetTextAsync(cancellationToken) let textLines = sourceText.Lines let lineInfo = textLines.GetLineFromPosition(span.Start) let line = lineInfo.LineNumber @@ -76,7 +76,7 @@ type internal FSharpHelpContextService [] () = | otherwise -> otherwise, col match tokenInformation with - | None -> return! None + | None -> return "" | Some token -> match token.ClassificationType with | ClassificationTypeNames.Keyword @@ -85,17 +85,22 @@ type internal FSharpHelpContextService [] () = | ClassificationTypeNames.Comment -> return "comment_FS" | ClassificationTypeNames.Identifier -> try - let! (s, colAtEndOfNames, _) = QuickParse.GetCompleteIdentifierIsland false lineText col + let island = QuickParse.GetCompleteIdentifierIsland false lineText col - if check.HasFullTypeCheckInfo then + match island with + | Some (s, colAtEndOfNames, _) when check.HasFullTypeCheckInfo -> let qualId = PrettyNaming.GetLongNameFromString s - return! check.GetF1Keyword(Line.fromZ line, colAtEndOfNames, lineText, qualId) - else - return! None + + let f1Keyword = + check.GetF1Keyword(Line.fromZ line, colAtEndOfNames, lineText, qualId) + + return Option.defaultValue "" f1Keyword + + | _ -> return "" with e -> Assert.Exception e - return! None - | _ -> return! None + return "" + | _ -> return "" } interface IHelpContextService with @@ -103,7 +108,8 @@ type internal FSharpHelpContextService [] () = member this.Product = FSharpConstants.FSharpLanguageLongName member this.GetHelpTermAsync(document, textSpan, cancellationToken) = - asyncMaybe { + cancellableTask { + let! cancellationToken = CancellableTask.getCurrentCancellationToken () let! sourceText = document.GetTextAsync(cancellationToken) let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() let textLine = sourceText.Lines.GetLineFromPosition(textSpan.Start) @@ -121,7 +127,6 @@ type internal FSharpHelpContextService [] () = return! FSharpHelpContextService.GetHelpTerm(document, textSpan, classifiedSpans) } - |> Async.map (Option.defaultValue "") - |> RoslynHelpers.StartAsyncAsTask cancellationToken + |> CancellableTask.start cancellationToken member this.FormatSymbol(_symbol) = Unchecked.defaultof<_> diff --git a/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs new file mode 100644 index 00000000000..11ef1ebe152 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/Common/CancellableTasks.fs @@ -0,0 +1,957 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. +// +// Implementation is taken from IcedTasks (https://github.com/TheAngryByrd/IcedTasks/blob/72638c8719014ae963f2662449c99f87090041d1/LICENSE.md?plain=1#L1-L21), under MIT license +// Which was originally written in 2016 by Robert Peele (humbobst@gmail.com) +// New operator-based overload resolution for F# 4.0 compatibility by Gustavo Leon in 2018. +// Revised for insertion into FSharp.Core by Microsoft, 2019. +// Revised to implement CancellationToken semantics +// +// Original notice: +// To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights +// to this software to the public domain worldwide. This software is distributed without any warranty. +// IcedTasks MIT notice (https://github.com/TheAngryByrd/IcedTasks/blob/72638c8719014ae963f2662449c99f87090041d1/LICENSE.md?plain=1#L1-L21): +// MIT License + +// Copyright (c) [year] [fullname] + +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +namespace Microsoft.VisualStudio.FSharp.Editor + +// Don't warn about the resumable code invocation +#nowarn "3513" + +module CancellableTasks = + + open System + open System.Runtime.CompilerServices + open System.Threading + open System.Threading.Tasks + open Microsoft.FSharp.Core + open Microsoft.FSharp.Core.CompilerServices + open Microsoft.FSharp.Core.CompilerServices.StateMachineHelpers + open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators + open Microsoft.FSharp.Collections + + /// A type that looks like an Awaiter + type Awaiter<'Awaiter, 'TResult + when 'Awaiter :> ICriticalNotifyCompletion + and 'Awaiter: (member IsCompleted: bool) + and 'Awaiter: (member GetResult: unit -> 'TResult)> = 'Awaiter + + /// A type that looks like an Awaitable + type Awaitable<'Awaitable, 'Awaiter, 'TResult + when 'Awaitable: (member GetAwaiter: unit -> Awaiter<'Awaiter, 'TResult>)> = 'Awaitable + + /// Functions for Awaiters + module Awaiter = + /// Gets a value that indicates whether the asynchronous task has completed + let inline isCompleted<'Awaiter, 'TResult when Awaiter<'Awaiter, 'TResult>> (x: 'Awaiter) = + x.IsCompleted + + /// Ends the wait for the completion of the asynchronous task. + let inline getResult<'Awaiter, 'TResult when Awaiter<'Awaiter, 'TResult>> (x: 'Awaiter) = + x.GetResult() + + /// Functions for Awaitables + module Awaitable = + /// Creates an awaiter for this value. + let inline getAwaiter<'Awaitable, 'Awaiter, 'TResult + when Awaitable<'Awaitable, 'Awaiter, 'TResult>> + (x: 'Awaitable) + = + x.GetAwaiter() + + /// CancellationToken -> Task<'T> + type CancellableTask<'T> = CancellationToken -> Task<'T> + + /// CancellationToken -> Task + type CancellableTask = CancellationToken -> Task + + /// The extra data stored in ResumableStateMachine for tasks + [] + type CancellableTaskStateMachineData<'T> = + [] + val mutable CancellationToken: CancellationToken + + [] + val mutable Result: 'T + + [] + val mutable MethodBuilder: AsyncTaskMethodBuilder<'T> + + member inline this.ThrowIfCancellationRequested() = + this.CancellationToken.ThrowIfCancellationRequested() + + /// This is used by the compiler as a template for creating state machine structs + and CancellableTaskStateMachine<'TOverall> = + ResumableStateMachine> + + /// Represents the runtime continuation of a CancellableTask state machine created dynamically + and CancellableTaskResumptionFunc<'TOverall> = + ResumptionFunc> + + /// Represents the runtime continuation of a CancellableTask state machine created dynamically + and CancellableTaskResumptionDynamicInfo<'TOverall> = + ResumptionDynamicInfo> + + /// A special compiler-recognised delegate type for specifying blocks of CancellableTask code with access to the state machine + and CancellableTaskCode<'TOverall, 'T> = + ResumableCode, 'T> + + + /// Contains methods to build CancellableTasks using the F# computation expression syntax + [] + type CancellableTaskBuilderBase() = + + /// Creates a CancellableTask that runs generator + /// The function to run + /// A cancellableTask that runs generator + member inline _.Delay + ([] generator: unit -> CancellableTaskCode<'TOverall, 'T>) + : CancellableTaskCode<'TOverall, 'T> = + ResumableCode.Delay(fun () -> + CancellableTaskCode(fun sm -> + sm.Data.ThrowIfCancellationRequested() + (generator ()).Invoke(&sm) + ) + ) + + + /// Creates an CancellableTask that just returns (). + /// + /// The existence of this method permits the use of empty else branches in the + /// cancellableTask { ... } computation expression syntax. + /// + /// An CancellableTask that returns (). + [] + member inline _.Zero() : CancellableTaskCode<'TOverall, unit> = ResumableCode.Zero() + + /// Creates an computation that returns the result v. + /// + /// A cancellation check is performed when the computation is executed. + /// + /// The existence of this method permits the use of return in the + /// cancellableTask { ... } computation expression syntax. + /// + /// The value to return from the computation. + /// + /// An CancellableTask that returns value when executed. + member inline _.Return(value: 'T) : CancellableTaskCode<'T, 'T> = + CancellableTaskCode<'T, _>(fun sm -> + sm.Data.ThrowIfCancellationRequested() + sm.Data.Result <- value + true + ) + + /// Creates an CancellableTask that first runs task1 + /// and then runs computation2, returning the result of computation2. + /// + /// + /// + /// The existence of this method permits the use of expression sequencing in the + /// cancellableTask { ... } computation expression syntax. + /// + /// The first part of the sequenced computation. + /// The second part of the sequenced computation. + /// + /// An CancellableTask that runs both of the computations sequentially. + member inline _.Combine + ( + task1: CancellableTaskCode<'TOverall, unit>, + task2: CancellableTaskCode<'TOverall, 'T> + ) : CancellableTaskCode<'TOverall, 'T> = + ResumableCode.Combine( + CancellableTaskCode(fun sm -> + sm.Data.ThrowIfCancellationRequested() + task1.Invoke(&sm) + ), + + CancellableTaskCode(fun sm -> + sm.Data.ThrowIfCancellationRequested() + task2.Invoke(&sm) + ) + ) + + /// Creates an CancellableTask that runs computation repeatedly + /// until guard() becomes false. + /// + /// + /// + /// The existence of this method permits the use of while in the + /// cancellableTask { ... } computation expression syntax. + /// + /// The function to determine when to stop executing computation. + /// The function to be executed. Equivalent to the body + /// of a while expression. + /// + /// An CancellableTask that behaves similarly to a while loop when run. + member inline _.While + ( + [] guard: unit -> bool, + computation: CancellableTaskCode<'TOverall, unit> + ) : CancellableTaskCode<'TOverall, unit> = + ResumableCode.While( + guard, + CancellableTaskCode(fun sm -> + sm.Data.ThrowIfCancellationRequested() + computation.Invoke(&sm) + ) + ) + + /// Creates an CancellableTask that runs computation and returns its result. + /// If an exception happens then catchHandler(exn) is called and the resulting computation executed instead. + /// + /// + /// + /// The existence of this method permits the use of try/with in the + /// cancellableTask { ... } computation expression syntax. + /// + /// The input computation. + /// The function to run when computation throws an exception. + /// + /// An CancellableTask that executes computation and calls catchHandler if an + /// exception is thrown. + member inline _.TryWith + ( + computation: CancellableTaskCode<'TOverall, 'T>, + [] catchHandler: exn -> CancellableTaskCode<'TOverall, 'T> + ) : CancellableTaskCode<'TOverall, 'T> = + ResumableCode.TryWith( + CancellableTaskCode(fun sm -> + sm.Data.ThrowIfCancellationRequested() + computation.Invoke(&sm) + ), + catchHandler + ) + + /// Creates an CancellableTask that runs computation. The action compensation is executed + /// after computation completes, whether computation exits normally or by an exception. If compensation raises an exception itself + /// the original exception is discarded and the new exception becomes the overall result of the computation. + /// + /// + /// + /// The existence of this method permits the use of try/finally in the + /// cancellableTask { ... } computation expression syntax. + /// + /// The input computation. + /// The action to be run after computation completes or raises an + /// exception (including cancellation). + /// + /// An CancellableTask that executes computation and compensation afterwards or + /// when an exception is raised. + member inline _.TryFinally + ( + computation: CancellableTaskCode<'TOverall, 'T>, + [] compensation: unit -> unit + ) : CancellableTaskCode<'TOverall, 'T> = + ResumableCode.TryFinally( + + CancellableTaskCode(fun sm -> + sm.Data.ThrowIfCancellationRequested() + computation.Invoke(&sm) + ), + ResumableCode<_, _>(fun _ -> + compensation () + true + ) + ) + + /// Creates an CancellableTask that enumerates the sequence seq + /// on demand and runs body for each element. + /// + /// A cancellation check is performed on each iteration of the loop. + /// + /// The existence of this method permits the use of for in the + /// cancellableTask { ... } computation expression syntax. + /// + /// The sequence to enumerate. + /// A function to take an item from the sequence and create + /// an CancellableTask. Can be seen as the body of the for expression. + /// + /// An CancellableTask that will enumerate the sequence and run body + /// for each element. + member inline _.For + ( + sequence: seq<'T>, + [] body: 'T -> CancellableTaskCode<'TOverall, unit> + ) : CancellableTaskCode<'TOverall, unit> = + ResumableCode.For( + sequence, + fun item -> + CancellableTaskCode(fun sm -> + sm.Data.ThrowIfCancellationRequested() + (body item).Invoke(&sm) + ) + ) + + /// Contains methods to build CancellableTasks using the F# computation expression syntax + [] + type CancellableTaskBuilder(runOnBackground: bool) = + + inherit CancellableTaskBuilderBase() + + member val IsBackground = runOnBackground + + // This is the dynamic implementation - this is not used + // for statically compiled tasks. An executor (resumptionFuncExecutor) is + // registered with the state machine, plus the initial resumption. + // The executor stays constant throughout the execution, it wraps each step + // of the execution in a try/with. The resumption is changed at each step + // to represent the continuation of the computation. + /// + /// The entry point for the dynamic implementation of the corresponding operation. Do not use directly, only used when executing quotations that involve tasks or other reflective execution of F# code. + /// + static member inline RunDynamicAux(code: CancellableTaskCode<'T, 'T>) : CancellableTask<'T> = + + let mutable sm = CancellableTaskStateMachine<'T>() + + let initialResumptionFunc = + CancellableTaskResumptionFunc<'T>(fun sm -> code.Invoke(&sm)) + + let resumptionInfo = + { new CancellableTaskResumptionDynamicInfo<'T>(initialResumptionFunc) with + member info.MoveNext(sm) = + let mutable savedExn = null + + try + sm.ResumptionDynamicInfo.ResumptionData <- null + let step = info.ResumptionFunc.Invoke(&sm) + + if step then + sm.Data.MethodBuilder.SetResult(sm.Data.Result) + else + let mutable awaiter = + sm.ResumptionDynamicInfo.ResumptionData + :?> ICriticalNotifyCompletion + + assert not (isNull awaiter) + sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm) + + with exn -> + savedExn <- exn + // Run SetException outside the stack unwind, see https://github.com/dotnet/roslyn/issues/26567 + match savedExn with + | null -> () + | exn -> sm.Data.MethodBuilder.SetException exn + + member _.SetStateMachine(sm, state) = + sm.Data.MethodBuilder.SetStateMachine(state) + } + + fun (ct) -> + if ct.IsCancellationRequested then + Task.FromCanceled<_>(ct) + else + sm.Data.CancellationToken <- ct + sm.ResumptionDynamicInfo <- resumptionInfo + sm.Data.MethodBuilder <- AsyncTaskMethodBuilder<'T>.Create() + sm.Data.MethodBuilder.Start(&sm) + sm.Data.MethodBuilder.Task + + /// + /// The entry point for the dynamic implementation of the corresponding operation. Do not use directly, only used when executing quotations that involve tasks or other reflective execution of F# code. + /// + static member inline RunDynamic(code: CancellableTaskCode<'T, 'T>, runOnBackground: bool) : CancellableTask<'T> = + // When runOnBackground is true, task escapes to a background thread where necessary + // See spec of ConfigureAwait(false) at https://devblogs.microsoft.com/dotnet/configureawait-faq/ + + if runOnBackground + && not (isNull SynchronizationContext.Current + && obj.ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default)) + then + fun (ct) -> + // Warning: this will always try to yield even if on thread pool already. + Task.Run<'T>((fun () -> CancellableTaskBuilder.RunDynamicAux (code) (ct)), ct) + else + CancellableTaskBuilder.RunDynamicAux(code) + + + /// Hosts the task code in a state machine and starts the task. + member inline this.Run(code: CancellableTaskCode<'T, 'T>) : CancellableTask<'T> = + if __useResumableCode then + __stateMachine, CancellableTask<'T>> + (MoveNextMethodImpl<_>(fun sm -> + //-- RESUMABLE CODE START + __resumeAt sm.ResumptionPoint + let mutable __stack_exn: Exception = null + + try + let __stack_code_fin = code.Invoke(&sm) + + if __stack_code_fin then + sm.Data.MethodBuilder.SetResult(sm.Data.Result) + with exn -> + __stack_exn <- exn + // Run SetException outside the stack unwind, see https://github.com/dotnet/roslyn/issues/26567 + match __stack_exn with + | null -> () + | exn -> sm.Data.MethodBuilder.SetException exn + //-- RESUMABLE CODE END + )) + (SetStateMachineMethodImpl<_>(fun sm state -> + sm.Data.MethodBuilder.SetStateMachine(state) + )) + (AfterCode<_, _>(fun sm -> + if this.IsBackground + && not (isNull SynchronizationContext.Current + && obj.ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default)) + then + + let sm = sm // copy contents of state machine so we can capture it + + fun (ct) -> + if ct.IsCancellationRequested then + Task.FromCanceled<_>(ct) + else + // Warning: this will always try to yield even if on thread pool already. + Task.Run<'T>( + (fun () -> + let mutable sm = sm // host local mutable copy of contents of state machine on this thread pool thread + sm.Data.CancellationToken <- ct + + sm.Data.MethodBuilder <- + AsyncTaskMethodBuilder<'T>.Create() + + sm.Data.MethodBuilder.Start(&sm) + sm.Data.MethodBuilder.Task + ), + ct + ) + else + let mutable sm = sm + + fun (ct) -> + if ct.IsCancellationRequested then + Task.FromCanceled<_>(ct) + else + sm.Data.CancellationToken <- ct + sm.Data.MethodBuilder <- AsyncTaskMethodBuilder<'T>.Create() + sm.Data.MethodBuilder.Start(&sm) + sm.Data.MethodBuilder.Task + )) + else + CancellableTaskBuilder.RunDynamic(code, this.IsBackground) + + /// Contains the cancellableTask computation expression builder. + [] + module CancellableTaskBuilder = + + /// + /// Builds a cancellableTask using computation expression syntax. + /// Default behaviour when binding (v)options is to return a cacnelled task. + /// + let foregroundCancellableTask = CancellableTaskBuilder(false) + + /// + /// Builds a cancellableTask using computation expression syntax which switches to execute on a background thread if not already doing so. + /// Default behaviour when binding (v)options is to return a cacnelled task. + /// + let cancellableTask = CancellableTaskBuilder(true) + + /// + [] + module LowPriority = + // Low priority extensions + type CancellableTaskBuilderBase with + + /// + /// The entry point for the dynamic implementation of the corresponding operation. Do not use directly, only used when executing quotations that involve tasks or other reflective execution of F# code. + /// + [] + static member inline BindDynamic<'TResult1, 'TResult2, 'Awaiter, 'TOverall + when Awaiter<'Awaiter, 'TResult1>> + ( + sm: byref>>, + [] getAwaiter: CancellationToken -> 'Awaiter, + [] continuation: ('TResult1 -> CancellableTaskCode<'TOverall, 'TResult2>) + ) : bool = + sm.Data.ThrowIfCancellationRequested() + + let mutable awaiter = getAwaiter sm.Data.CancellationToken + + let cont: CancellableTaskResumptionFunc<'TOverall> = + (CancellableTaskResumptionFunc<'TOverall>(fun (sm: byref>>) -> + let result: 'TResult1 = Awaiter.getResult awaiter + (continuation result).Invoke(&sm) + )) + + // shortcut to continue immediately + if Awaiter.isCompleted awaiter then + cont.Invoke(&sm) + else + sm.ResumptionDynamicInfo.ResumptionData <- + (awaiter :> ICriticalNotifyCompletion) + + sm.ResumptionDynamicInfo.ResumptionFunc <- cont + false + + /// Creates an CancellableTask that runs computation, and when + /// computation generates a result T, runs binder res. + /// + /// A cancellation check is performed when the computation is executed. + /// + /// The existence of this method permits the use of let! in the + /// cancellableTask { ... } computation expression syntax. + /// + /// The computation to provide an unbound result. + /// The function to bind the result of computation. + /// + /// An CancellableTask that performs a monadic bind on the result + /// of computation. + [] + member inline _.Bind<'TResult1, 'TResult2, 'Awaiter, 'TOverall + when Awaiter<'Awaiter, 'TResult1>> + ( + [] getAwaiter: CancellationToken -> 'Awaiter, + [] continuation: ('TResult1 -> CancellableTaskCode<'TOverall, 'TResult2>) + ) : CancellableTaskCode<'TOverall, 'TResult2> = + + CancellableTaskCode<'TOverall, _>(fun sm -> + if __useResumableCode then + //-- RESUMABLE CODE START + sm.Data.ThrowIfCancellationRequested() + // Get an awaiter from the Awaiter + let mutable awaiter = getAwaiter sm.Data.CancellationToken + + let mutable __stack_fin = true + + if not (Awaiter.isCompleted awaiter) then + // This will yield with __stack_yield_fin = false + // This will resume with __stack_yield_fin = true + let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm) + __stack_fin <- __stack_yield_fin + + if __stack_fin then + let result = + awaiter + |> Awaiter.getResult + + (continuation result).Invoke(&sm) + else + sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm) + false + else + CancellableTaskBuilderBase.BindDynamic<'TResult1, 'TResult2, 'Awaiter, 'TOverall>( + &sm, + getAwaiter, + continuation + ) + //-- RESUMABLE CODE END + ) + + + /// Delegates to the input computation. + /// + /// The existence of this method permits the use of return! in the + /// cancellableTask { ... } computation expression syntax. + /// + /// The input computation. + /// + /// The input computation. + [] + member inline this.ReturnFrom<'TResult1, 'TResult2, 'Awaiter, 'TOverall + when Awaiter<'Awaiter, 'TResult1>> + ([] getAwaiter: CancellationToken -> 'Awaiter) + : CancellableTaskCode<_, _> = + this.Bind(getAwaiter, this.Return) + + + [] + member inline this.BindReturn<'TResult1, 'TResult2, 'Awaiter, 'TOverall + when Awaiter<'Awaiter, 'TResult1>> + ( + [] getAwaiter: CancellationToken -> 'Awaiter, + f + ) : CancellableTaskCode<'TResult2, 'TResult2> = + this.Bind(getAwaiter, (fun v -> this.Return(f v))) + + /// Allows the computation expression to turn other types into CancellationToken -> 'Awaiter + /// + /// This is the identify function. + /// + /// CancellationToken -> 'Awaiter + [] + member inline _.Source<'TResult1, 'TResult2, 'Awaiter, 'TOverall + when Awaiter<'Awaiter, 'TResult1>> + ([] getAwaiter: CancellationToken -> 'Awaiter) + : CancellationToken -> 'Awaiter = + getAwaiter + + + /// Allows the computation expression to turn other types into CancellationToken -> 'Awaiter + /// + /// This is the identify function. + /// + /// CancellationToken -> 'Awaiter + [] + member inline _.Source<'TResult1, 'TResult2, 'Awaiter, 'TOverall + when Awaiter<'Awaiter, 'TResult1>> + (getAwaiter: 'Awaiter) + : CancellationToken -> 'Awaiter = + (fun _ct -> getAwaiter) + + + /// Allows the computation expression to turn other types into CancellationToken -> 'Awaiter + /// + /// This turns a 'Awaitable into a CancellationToken -> 'Awaiter. + /// + /// CancellationToken -> 'Awaiter + [] + member inline _.Source<'Awaitable, 'TResult1, 'TResult2, 'Awaiter, 'TOverall + when Awaitable<'Awaitable, 'Awaiter, 'TResult1>> + (task: 'Awaitable) + : CancellationToken -> 'Awaiter = + (fun (_ct: CancellationToken) -> + task + |> Awaitable.getAwaiter + ) + + + /// Allows the computation expression to turn other types into CancellationToken -> 'Awaiter + /// + /// This turns a CancellationToken -> 'Awaitable into a CancellationToken -> 'Awaiter. + /// + /// CancellationToken -> 'Awaiter + [] + member inline _.Source<'Awaitable, 'TResult1, 'TResult2, 'Awaiter, 'TOverall + when Awaitable<'Awaitable, 'Awaiter, 'TResult1>> + ([] task: CancellationToken -> 'Awaitable) + : CancellationToken -> 'Awaiter = + (fun ct -> + task ct + |> Awaitable.getAwaiter + ) + + + /// Allows the computation expression to turn other types into CancellationToken -> 'Awaiter + /// + /// This turns a unit -> 'Awaitable into a CancellationToken -> 'Awaiter. + /// + /// CancellationToken -> 'Awaiter + [] + member inline _.Source<'Awaitable, 'TResult1, 'TResult2, 'Awaiter, 'TOverall + when Awaitable<'Awaitable, 'Awaiter, 'TResult1>> + ([] task: unit -> 'Awaitable) + : CancellationToken -> 'Awaiter = + (fun _ct -> + task () + |> Awaitable.getAwaiter + ) + + + /// Creates an CancellableTask that runs binder(resource). + /// The action resource.Dispose() is executed as this computation yields its result + /// or if the CancellableTask exits by an exception or by cancellation. + /// + /// + /// + /// The existence of this method permits the use of use and use! in the + /// cancellableTask { ... } computation expression syntax. + /// + /// The resource to be used and disposed. + /// The function that takes the resource and returns an asynchronous + /// computation. + /// + /// An CancellableTask that binds and eventually disposes resource. + /// + member inline _.Using<'Resource, 'TOverall, 'T when 'Resource :> IDisposable> + ( + resource: 'Resource, + [] binder: 'Resource -> CancellableTaskCode<'TOverall, 'T> + ) = + ResumableCode.Using( + resource, + fun resource -> + CancellableTaskCode<'TOverall, 'T>(fun sm -> + sm.Data.ThrowIfCancellationRequested() + (binder resource).Invoke(&sm) + ) + ) + + /// + [] + module HighPriority = + + type Control.Async with + + /// Return an asynchronous computation that will wait for the given task to complete and return + /// its result. + static member inline AwaitCancellableTask(t: CancellableTask<'T>) = + async { + let! ct = Async.CancellationToken + + return! + t ct + |> Async.AwaitTask + } + + /// Return an asynchronous computation that will wait for the given task to complete and return + /// its result. + static member inline AwaitCancellableTask(t: CancellableTask) = + async { + let! ct = Async.CancellationToken + + return! + t ct + |> Async.AwaitTask + } + + /// 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) + + // High priority extensions + type CancellableTaskBuilderBase with + + /// + /// Turn option into "awaitable", will return cancelled task if None + /// + /// Option instance to bind on + (*member inline _.Source(s: 'T option) = + (fun (_ct: CancellationToken) -> + match s with + | Some x -> Task.FromResult<'T>(x).GetAwaiter() + | None -> Task.FromCanceled<'T>(CancellationToken(true)).GetAwaiter() + )*) + + /// + /// Turn a value option into "awaitable", will return cancelled task if None + /// + /// Option instance to bind on + (*member inline _.Source(s: 'T voption) = + (fun (_ct: CancellationToken) -> + match s with + | ValueSome x -> Task.FromResult<'T>(x).GetAwaiter() + | ValueNone -> Task.FromCanceled<'T>(CancellationToken(true)).GetAwaiter() + )*) + + /// Allows the computation expression to turn other types into other types + /// + /// This is the identify function for For binds. + /// + /// IEnumerable + member inline _.Source(s: #seq<_>) : #seq<_> = s + + /// Allows the computation expression to turn other types into CancellationToken -> 'Awaiter + /// + /// This turns a Task<'T> into a CancellationToken -> 'Awaiter. + /// + /// CancellationToken -> 'Awaiter + member inline _.Source(task: Task<'T>) = + (fun (_ct: CancellationToken) -> task.GetAwaiter()) + + /// Allows the computation expression to turn other types into CancellationToken -> 'Awaiter + /// + /// This turns a ColdTask<'T> into a CancellationToken -> 'Awaiter. + /// + /// CancellationToken -> 'Awaiter + member inline _.Source([] task: unit -> Task<'TResult1>) = + (fun (_ct: CancellationToken) -> (task ()).GetAwaiter()) + + /// Allows the computation expression to turn other types into CancellationToken -> 'Awaiter + /// + /// This turns a CancellableTask<'T> into a CancellationToken -> 'Awaiter. + /// + /// CancellationToken -> 'Awaiter + member inline _.Source([] task: CancellableTask<'TResult1>) = + (fun ct -> (task ct).GetAwaiter()) + + /// Allows the computation expression to turn other types into CancellationToken -> 'Awaiter + /// + /// This turns a Async<'T> into a CancellationToken -> 'Awaiter. + /// + /// CancellationToken -> 'Awaiter + member inline this.Source(computation: Async<'TResult1>) = + this.Source(Async.AsCancellableTask(computation)) + + /// Allows the computation expression to turn other types into CancellationToken -> 'Awaiter + /// + /// This turns a CancellableTask<'T> into a CancellationToken -> 'Awaiter. + /// + /// CancellationToken -> 'Awaiter + member inline _.Source(awaiter: TaskAwaiter<'TResult1>) = (fun _ct -> awaiter) + + /// + /// A set of extension methods making it possible to bind against in async computations. + /// + [] + module AsyncExtenions = + type Control.AsyncBuilder with + + member inline this.Bind(t: CancellableTask<'T>, [] binder: ('T -> Async<'U>)) : Async<'U> = + this.Bind(Async.AwaitCancellableTask t, binder) + + member inline this.ReturnFrom([] t: CancellableTask<'T>) : Async<'T> = + this.ReturnFrom(Async.AwaitCancellableTask t) + + member inline this.Bind([] t: CancellableTask, binder: (unit -> Async<'U>)) : Async<'U> = + this.Bind(Async.AwaitCancellableTask t, binder) + + member inline this.ReturnFrom([] t: CancellableTask) : Async = + this.ReturnFrom(Async.AwaitCancellableTask t) + + /// Contains a set of standard functional helper function + [] + module CancellableTask = + + /// Gets the default cancellation token for executing computations. + /// + /// The default CancellationToken. + /// + /// Cancellation and Exceptions + /// + /// + /// + /// use tokenSource = new CancellationTokenSource() + /// let primes = [ 2; 3; 5; 7; 11 ] + /// for i in primes do + /// let computation = + /// cancellableTask { + /// let! cancellationToken = CancellableTask.getCurrentCancellationToken() + /// do! Task.Delay(i * 1000, cancellationToken) + /// printfn $"{i}" + /// } + /// computation tokenSource.Token |> ignore + /// Thread.Sleep(6000) + /// tokenSource.Cancel() + /// printfn "Tasks Finished" + /// + /// 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 getCurrentCancellationToken () = + cancellableTask.Run( + CancellableTaskCode<_, _>(fun sm -> + sm.Data.Result <- sm.Data.CancellationToken + true + ) + ) + + /// Lifts an item to a CancellableTask. + /// The item to be the result of the CancellableTask. + /// A CancellableTask with the item as the result. + let inline singleton (item: 'item) : CancellableTask<'item> = fun _ -> Task.FromResult(item) + + + /// Allows chaining of CancellableTasks. + /// The continuation. + /// The value. + /// The result of the binder. + let inline bind + ([] binder: 'input -> CancellableTask<'output>) + ([] cTask: CancellableTask<'input>) + = + cancellableTask { + let! cResult = cTask + return! binder cResult + } + + /// Allows chaining of CancellableTasks. + /// The continuation. + /// The value. + /// The result of the mapper wrapped in a CancellableTasks. + let inline map + ([] mapper: 'input -> 'output) + ([] cTask: CancellableTask<'input>) + = + cancellableTask { + let! cResult = cTask + return mapper cResult + } + + /// Allows chaining of CancellableTasks. + /// A function wrapped in a CancellableTasks + /// The value. + /// The result of the applicable. + let inline apply + ([] applicable: CancellableTask<'input -> 'output>) + ([] cTask: CancellableTask<'input>) + = + cancellableTask { + let! applier = applicable + let! cResult = cTask + return applier cResult + } + + /// Takes two CancellableTasks, starts them serially in order of left to right, and returns a tuple of the pair. + /// The left value. + /// The right value. + /// A tuple of the parameters passed in + let inline zip + ([] left: CancellableTask<'left>) + ([] right: CancellableTask<'right>) + = + cancellableTask { + let! r1 = left + let! r2 = right + return r1, r2 + } + + /// Takes two CancellableTask, starts them concurrently, and returns a tuple of the pair. + /// The left value. + /// The right value. + /// A tuple of the parameters passed in. + let inline parallelZip + ([] left: CancellableTask<'left>) + ([] right: CancellableTask<'right>) + = + cancellableTask { + let! ct = getCurrentCancellationToken () + let r1 = left ct + let r2 = right ct + let! r1 = r1 + let! r2 = r2 + return r1, r2 + } + + + /// Coverts a CancellableTask to a CancellableTask\<unit\>. + /// The CancellableTask to convert. + /// a CancellableTask\<unit\>. + let inline ofUnit ([] unitCancellabletTask: CancellableTask) = + cancellableTask { + return! unitCancellabletTask + } + + /// Coverts a CancellableTask\<_\> to a CancellableTask. + /// The CancellableTask to convert. + /// A cancellation token. + /// a CancellableTask. + let inline toUnit ([] ctask: CancellableTask<_>) : CancellableTask = + fun ct -> ctask ct + + let inline getAwaiter ([] ctask: CancellableTask<_>) = + fun ct -> (ctask ct).GetAwaiter() + + let inline start ct ([] ctask: CancellableTask<_>) = ctask ct + + let inline startAsTask ct ([] ctask: CancellableTask<_>) = (ctask ct) :> Task + + /// + [] + module MergeSourcesExtensions = + + type CancellableTaskBuilderBase with + + [] + member inline _.MergeSources<'TResult1, 'TResult2, 'Awaiter1, 'Awaiter2 + when Awaiter<'Awaiter1, 'TResult1> and Awaiter<'Awaiter2, 'TResult2>> + ( + [] left: CancellationToken -> 'Awaiter1, + [] right: CancellationToken -> 'Awaiter2 + ) : CancellationToken -> TaskAwaiter<'TResult1 * 'TResult2> = + + cancellableTask { + let! ct = CancellableTask.getCurrentCancellationToken () + let leftStarted = left ct + let rightStarted = right ct + let! leftResult = leftStarted + let! rightResult = rightStarted + return leftResult, rightResult + } + |> CancellableTask.getAwaiter diff --git a/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs b/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs new file mode 100644 index 00000000000..f29e4c2e200 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/Common/DocumentCache.fs @@ -0,0 +1,42 @@ +namespace Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Runtime.Caching +open Microsoft.CodeAnalysis +open CancellableTasks + +[] +type DocumentCache<'Value when 'Value: not struct>(name: string, ?cacheItemPolicy: CacheItemPolicy) = + + [] + let defaultSlidingExpiration = 2. + + let cache = new MemoryCache(name) + + let policy = + defaultArg cacheItemPolicy (CacheItemPolicy(SlidingExpiration = (TimeSpan.FromSeconds defaultSlidingExpiration))) + + member _.TryGetValueAsync(doc: Document) = + cancellableTask { + let! ct = CancellableTask.getCurrentCancellationToken () + let! currentVersion = doc.GetTextVersionAsync ct + + match cache.Get(doc.Id.ToString()) with + | null -> return ValueNone + | :? (VersionStamp * 'Value) as value -> + if fst value = currentVersion then + return ValueSome(snd value) + else + return ValueNone + | _ -> return ValueNone + } + + member _.SetAsync(doc: Document, value: 'Value) = + cancellableTask { + let! ct = CancellableTask.getCurrentCancellationToken () + let! currentVersion = doc.GetTextVersionAsync ct + cache.Set(doc.Id.ToString(), (currentVersion, value), policy) + } + + interface IDisposable with + member _.Dispose() = cache.Dispose() diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index baac85294f9..7ca569d57b7 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -21,6 +21,7 @@ open FSharp.Compiler.EditorServices open FSharp.Compiler.Syntax open FSharp.Compiler.Text open FSharp.Compiler.Tokenization +open CancellableTasks module Logger = Microsoft.VisualStudio.FSharp.Editor.Logger @@ -104,7 +105,8 @@ type internal FSharpCompletionProvider caretPosition: int, trigger: CompletionTriggerKind, getInfo: (unit -> DocumentId * string * string list * string option), - intelliSenseOptions: IntelliSenseOptions + intelliSenseOptions: IntelliSenseOptions, + cancellationToken: CancellationToken ) = if caretPosition = 0 then false @@ -130,7 +132,15 @@ type internal FSharpCompletionProvider else let documentId, filePath, defines, langVersion = getInfo () - CompletionUtils.shouldProvideCompletion (documentId, filePath, defines, langVersion, sourceText, triggerPosition) + CompletionUtils.shouldProvideCompletion ( + documentId, + filePath, + defines, + langVersion, + sourceText, + triggerPosition, + cancellationToken + ) && (triggerChar = '.' || (intelliSenseOptions.ShowAfterCharIsTyped && CompletionUtils.isStartingNewWord (sourceText, triggerPosition))) @@ -142,13 +152,12 @@ type internal FSharpCompletionProvider getAllSymbols: FSharpCheckFileResults -> AssemblySymbol list ) = - asyncMaybe { + cancellableTask { + let! parseResults, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux") - let! parseResults, checkFileResults = - document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux") - |> liftAsync + let! ct = CancellableTask.getCurrentCancellationToken () - let! sourceText = document.GetTextAsync() + let! sourceText = document.GetTextAsync(ct) let textLines = sourceText.Lines let caretLinePos = textLines.GetLinePosition(caretPosition) let caretLine = textLines.GetLineFromPosition(caretPosition) @@ -292,13 +301,22 @@ type internal FSharpCompletionProvider let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() (documentId, document.FilePath, defines, Some langVersion) - FSharpCompletionProvider.ShouldTriggerCompletionAux(sourceText, caretPosition, trigger.Kind, getInfo, settings.IntelliSense) + FSharpCompletionProvider.ShouldTriggerCompletionAux( + sourceText, + caretPosition, + trigger.Kind, + getInfo, + settings.IntelliSense, + CancellationToken.None + ) override _.ProvideCompletionsAsync(context: Completion.CompletionContext) = - asyncMaybe { + cancellableTask { use _logBlock = Logger.LogBlockMessage context.Document.Name LogEditorFunctionId.Completion_ProvideCompletionsAsync + let! ct = CancellableTask.getCurrentCancellationToken () + let document = context.Document let eventProps: (string * obj) array = @@ -310,32 +328,33 @@ type internal FSharpCompletionProvider use _eventDuration = TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.ProvideCompletions, eventProps) - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + let! sourceText = context.Document.GetTextAsync(ct) let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() - do! - Option.guard ( - CompletionUtils.shouldProvideCompletion ( - document.Id, - document.FilePath, - defines, - Some langVersion, - sourceText, - context.Position - ) + let shouldProvideCompetion = + CompletionUtils.shouldProvideCompletion ( + document.Id, + document.FilePath, + defines, + Some langVersion, + sourceText, + context.Position, + ct ) - let getAllSymbols (fileCheckResults: FSharpCheckFileResults) = - if settings.IntelliSense.IncludeSymbolsFromUnopenedNamespacesOrModules then - assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults) - else - [] + if shouldProvideCompetion then + let getAllSymbols (fileCheckResults: FSharpCheckFileResults) = + if settings.IntelliSense.IncludeSymbolsFromUnopenedNamespacesOrModules then + assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults) + else + [] + + let! results = FSharpCompletionProvider.ProvideCompletionsAsyncAux(context.Document, context.Position, getAllSymbols) + + context.AddItems results - let! results = FSharpCompletionProvider.ProvideCompletionsAsyncAux(context.Document, context.Position, getAllSymbols) - context.AddItems(results) } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask context.CancellationToken + |> CancellableTask.startAsTask context.CancellationToken override _.GetDescriptionAsync ( @@ -343,7 +362,7 @@ type internal FSharpCompletionProvider completionItem: Completion.CompletionItem, cancellationToken: CancellationToken ) : Task = - async { + cancellableTask { use _logBlock = Logger.LogBlockMessage document.Name LogEditorFunctionId.Completion_GetDescriptionAsync @@ -376,10 +395,10 @@ type internal FSharpCompletionProvider | true, keywordDescription -> return CompletionDescription.FromText(keywordDescription) | false, _ -> return CompletionDescription.Empty } - |> RoslynHelpers.StartAsyncAsTask cancellationToken + |> CancellableTask.start cancellationToken override _.GetChangeAsync(document, item, _, cancellationToken) : Task = - async { + cancellableTask { use _logBlock = Logger.LogBlockMessage document.Name LogEditorFunctionId.Completion_GetChangeAsync @@ -405,56 +424,43 @@ type internal FSharpCompletionProvider | true, x -> x | _ -> item.DisplayText - return! - asyncMaybe { - let! ns = - match item.Properties.TryGetValue NamespaceToOpenPropName with - | true, ns -> Some ns - | _ -> None - - let! sourceText = document.GetTextAsync(cancellationToken) + match item.Properties.TryGetValue NamespaceToOpenPropName with + | false, _ -> return CompletionChange.Create(TextChange(item.Span, nameInCode)) + | true, ns -> + let! sourceText = document.GetTextAsync(cancellationToken) - let textWithItemCommitted = - sourceText.WithChanges(TextChange(item.Span, nameInCode)) + let textWithItemCommitted = + sourceText.WithChanges(TextChange(item.Span, nameInCode)) - let line = sourceText.Lines.GetLineFromPosition(item.Span.Start) + let line = sourceText.Lines.GetLineFromPosition(item.Span.Start) - let! parseResults = - document.GetFSharpParseResultsAsync(nameof (FSharpCompletionProvider)) - |> liftAsync + let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpCompletionProvider)) - let fullNameIdents = - fullName |> Option.map (fun x -> x.Split '.') |> Option.defaultValue [||] + let fullNameIdents = + fullName |> Option.map (fun x -> x.Split '.') |> Option.defaultValue [||] - let insertionPoint = - if settings.CodeFixes.AlwaysPlaceOpensAtTopLevel then - OpenStatementInsertionPoint.TopLevel - else - OpenStatementInsertionPoint.Nearest - - let ctx = - ParsedInput.FindNearestPointToInsertOpenDeclaration - line.LineNumber - parseResults.ParseTree - fullNameIdents - insertionPoint + let insertionPoint = + if settings.CodeFixes.AlwaysPlaceOpensAtTopLevel then + OpenStatementInsertionPoint.TopLevel + else + OpenStatementInsertionPoint.Nearest - let finalSourceText, changedSpanStartPos = - OpenDeclarationHelper.insertOpenDeclaration textWithItemCommitted ctx ns + let ctx = + ParsedInput.FindNearestPointToInsertOpenDeclaration line.LineNumber parseResults.ParseTree fullNameIdents insertionPoint - let fullChangingSpan = TextSpan.FromBounds(changedSpanStartPos, item.Span.End) + let finalSourceText, changedSpanStartPos = + OpenDeclarationHelper.insertOpenDeclaration textWithItemCommitted ctx ns - let changedSpan = - TextSpan.FromBounds(changedSpanStartPos, item.Span.End + (finalSourceText.Length - sourceText.Length)) + let fullChangingSpan = TextSpan.FromBounds(changedSpanStartPos, item.Span.End) - let changedText = finalSourceText.ToString(changedSpan) + let changedSpan = + TextSpan.FromBounds(changedSpanStartPos, item.Span.End + (finalSourceText.Length - sourceText.Length)) - return - CompletionChange - .Create(TextChange(fullChangingSpan, changedText)) - .WithNewPosition(Nullable(changedSpan.End)) - } - |> Async.map (Option.defaultValue (CompletionChange.Create(TextChange(item.Span, nameInCode)))) + let changedText = finalSourceText.ToString(changedSpan) + return + CompletionChange + .Create(TextChange(fullChangingSpan, changedText)) + .WithNewPosition(Nullable(changedSpan.End)) } - |> RoslynHelpers.StartAsyncAsTask cancellationToken + |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs index 4311a1e552d..d879b718b98 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs @@ -4,7 +4,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition open System.Collections.Immutable - +open System.Threading open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Completion open Microsoft.CodeAnalysis.Host @@ -58,7 +58,15 @@ type internal FSharpCompletionService let defines, langVersion = projectInfoManager.GetCompilationDefinesAndLangVersionForEditingDocument(document) - CompletionUtils.getDefaultCompletionListSpan (sourceText, caretIndex, documentId, document.FilePath, defines, Some langVersion) + CompletionUtils.getDefaultCompletionListSpan ( + sourceText, + caretIndex, + documentId, + document.FilePath, + defines, + Some langVersion, + CancellationToken.None + ) [] [, FSharpConstants.FSharpLanguageName)>] @@ -71,7 +79,7 @@ type internal FSharpCompletionServiceFactory [] interface ILanguageServiceFactory with member _.CreateLanguageService(hostLanguageServices: HostLanguageServices) : ILanguageService = upcast - new FSharpCompletionService( + FSharpCompletionService( hostLanguageServices.WorkspaceServices.Workspace, serviceProvider, assemblyContentProvider, diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs index c261cea6cc5..e2cf7b2e114 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs @@ -97,21 +97,14 @@ module internal CompletionUtils = defines: string list, langVersion: string option, sourceText: SourceText, - triggerPosition: int + triggerPosition: int, + ct: CancellationToken ) : bool = let textLines = sourceText.Lines let triggerLine = textLines.GetLineFromPosition triggerPosition let classifiedSpans = - Tokenizer.getClassifiedSpans ( - documentId, - sourceText, - triggerLine.Span, - Some filePath, - defines, - langVersion, - CancellationToken.None - ) + Tokenizer.getClassifiedSpans (documentId, sourceText, triggerLine.Span, Some filePath, defines, langVersion, ct) classifiedSpans.Count = 0 || // we should provide completion at the start of empty line, where there are no tokens at all @@ -140,7 +133,16 @@ module internal CompletionUtils = | CompletionItemKind.Method(isExtension = true) -> 7 /// Indicates the text span to be replaced by a committed completion list item. - let getDefaultCompletionListSpan (sourceText: SourceText, caretIndex, documentId, filePath, defines, langVersion) = + let getDefaultCompletionListSpan + ( + sourceText: SourceText, + caretIndex, + documentId, + filePath, + defines, + langVersion, + ct: CancellationToken + ) = // Gets connected identifier-part characters backward and forward from caret. let getIdentifierChars () = @@ -176,15 +178,7 @@ module internal CompletionUtils = // the majority of common cases. let classifiedSpans = - Tokenizer.getClassifiedSpans ( - documentId, - sourceText, - line.Span, - Some filePath, - defines, - langVersion, - CancellationToken.None - ) + Tokenizer.getClassifiedSpans (documentId, sourceText, line.Span, Some filePath, defines, langVersion, ct) let isBacktickIdentifier (classifiedSpan: ClassifiedSpan) = classifiedSpan.ClassificationType = ClassificationTypeNames.Identifier diff --git a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs index f239a1b7856..ff927ef8742 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/BreakpointResolutionService.fs @@ -16,15 +16,16 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor.Implementation.Debuggin open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Text open FSharp.Compiler.Text.Position +open CancellableTasks [)>] type internal FSharpBreakpointResolutionService [] () = static member GetBreakpointLocation(document: Document, textSpan: TextSpan) = - async { - let! ct = Async.CancellationToken + cancellableTask { + let! ct = CancellableTask.getCurrentCancellationToken () - let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask + let! sourceText = document.GetTextAsync(ct) let textLinePos = sourceText.Lines.GetLinePosition(textSpan.Start) @@ -47,21 +48,27 @@ type internal FSharpBreakpointResolutionService [] () = } interface IFSharpBreakpointResolutionService with - member this.ResolveBreakpointAsync + member _.ResolveBreakpointAsync ( document: Document, textSpan: TextSpan, cancellationToken: CancellationToken ) : Task = - asyncMaybe { + cancellableTask { let! range = FSharpBreakpointResolutionService.GetBreakpointLocation(document, textSpan) - let! sourceText = document.GetTextAsync(cancellationToken) - let! span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) - return FSharpBreakpointResolutionResult.CreateSpanResult(document, span) + + match range with + | None -> return Unchecked.defaultof<_> + | Some range -> + let! sourceText = document.GetTextAsync(cancellationToken) + let span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range) + + match span with + | None -> return Unchecked.defaultof<_> + | Some span -> return FSharpBreakpointResolutionResult.CreateSpanResult(document, span) } - |> Async.map Option.toObj - |> RoslynHelpers.StartAsyncAsTask cancellationToken + |> CancellableTask.start cancellationToken // FSROSLYNTODO: enable placing breakpoints by when user supplies fully-qualified function names - member this.ResolveBreakpointsAsync(_, _, _) : Task> = + member _.ResolveBreakpointsAsync(_, _, _) : Task> = Task.FromResult(Enumerable.Empty()) diff --git a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs index 30a3d8a148d..ce952c78ade 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs @@ -13,6 +13,7 @@ open Microsoft.CodeAnalysis.Classification open FSharp.Compiler.EditorServices open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor.Implementation.Debugging +open CancellableTasks [)>] type internal FSharpLanguageDebugInfoService [] () = @@ -45,19 +46,19 @@ type internal FSharpLanguageDebugInfoService [] () = interface IFSharpLanguageDebugInfoService with // FSROSLYNTODO: This is used to get function names in breakpoint window. It should return fully qualified function name and line offset from the start of the function. - member this.GetLocationInfoAsync(_, _, _) : Task = + member _.GetLocationInfoAsync(_, _, _) : Task = Task.FromResult(Unchecked.defaultof) - member this.GetDataTipInfoAsync + member _.GetDataTipInfoAsync ( document: Document, position: int, cancellationToken: CancellationToken ) : Task = - async { + cancellableTask { let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() - let! cancellationToken = Async.CancellationToken - let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! sourceText = document.GetTextAsync(cancellationToken) let textSpan = TextSpan.FromBounds(0, sourceText.Length) let classifiedSpans = @@ -78,4 +79,4 @@ type internal FSharpLanguageDebugInfoService [] () = return result } - |> RoslynHelpers.StartAsyncAsTask(cancellationToken) + |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs index 249a9bb25f8..38bdf8766c7 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/DocumentDiagnosticAnalyzer.fs @@ -2,7 +2,6 @@ namespace Microsoft.VisualStudio.FSharp.Editor -open System open System.Composition open System.Collections.Immutable open System.Collections.Generic @@ -13,8 +12,8 @@ open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics -open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Diagnostics +open CancellableTasks open Microsoft.VisualStudio.FSharp.Editor.Telemetry [] @@ -53,7 +52,7 @@ type internal FSharpDocumentDiagnosticAnalyzer [] () = } static member GetDiagnostics(document: Document, diagnosticType: DiagnosticsType) = - async { + cancellableTask { let eventProps: (string * obj) array = [| @@ -68,15 +67,15 @@ type internal FSharpDocumentDiagnosticAnalyzer [] () = use _eventDuration = TelemetryReporter.ReportSingleEventWithDuration(TelemetryEvents.GetDiagnosticsForDocument, eventProps) - let! ct = Async.CancellationToken + let! ct = CancellableTask.getCurrentCancellationToken () let! parseResults = document.GetFSharpParseResultsAsync("GetDiagnostics") - let! sourceText = document.GetTextAsync(ct) |> Async.AwaitTask + let! sourceText = document.GetTextAsync(ct) let filePath = document.FilePath let! errors = - async { + cancellableTask { match diagnosticType with | DiagnosticsType.Semantic -> let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync("GetDiagnostics") @@ -121,20 +120,20 @@ type internal FSharpDocumentDiagnosticAnalyzer [] () = interface IFSharpDocumentDiagnosticAnalyzer with - member this.AnalyzeSyntaxAsync(document: Document, cancellationToken: CancellationToken) : Task> = - if document.Project.IsFSharpMetadata then - Task.FromResult(ImmutableArray.Empty) - else - FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) - |> liftAsync - |> Async.map (Option.defaultValue ImmutableArray.Empty) - |> RoslynHelpers.StartAsyncAsTask cancellationToken - - member this.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken) : Task> = - if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then - Task.FromResult(ImmutableArray.Empty) - else - FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic) - |> liftAsync - |> Async.map (Option.defaultValue ImmutableArray.Empty) - |> RoslynHelpers.StartAsyncAsTask cancellationToken + 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 + + 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 diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs index cc6b9ffa0bf..f8de2140da1 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs @@ -10,6 +10,7 @@ open System.Diagnostics open Microsoft.CodeAnalysis open FSharp.Compiler.EditorServices open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics +open CancellableTasks [)>] type internal UnusedDeclarationsAnalyzer [] () = @@ -17,22 +18,20 @@ type internal UnusedDeclarationsAnalyzer [] () = interface IFSharpUnusedDeclarationsDiagnosticAnalyzer with member _.AnalyzeSemanticsAsync(descriptor, document, cancellationToken) = - if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then + if + (document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript) + || not document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled + then Threading.Tasks.Task.FromResult(ImmutableArray.Empty) else - asyncMaybe { - do! Option.guard document.Project.IsFSharpCodeFixesUnusedDeclarationsEnabled + cancellableTask { do Trace.TraceInformation("{0:n3} (start) UnusedDeclarationsAnalyzer", DateTime.Now.TimeOfDay.TotalSeconds) - let! _, checkResults = - document.GetFSharpParseAndCheckResultsAsync(nameof (UnusedDeclarationsAnalyzer)) - |> liftAsync + let! _, checkResults = document.GetFSharpParseAndCheckResultsAsync(nameof (UnusedDeclarationsAnalyzer)) - let! unusedRanges = - UnusedDeclarations.getUnusedDeclarations (checkResults, (isScriptFile document.FilePath)) - |> liftAsync + let! unusedRanges = UnusedDeclarations.getUnusedDeclarations (checkResults, (isScriptFile document.FilePath)) let! sourceText = document.GetTextAsync() @@ -41,5 +40,4 @@ type internal UnusedDeclarationsAnalyzer [] () = |> Seq.map (fun m -> Diagnostic.Create(descriptor, RoslynHelpers.RangeToLocation(m, sourceText, document.FilePath))) |> Seq.toImmutableArray } - |> Async.map (Option.defaultValue ImmutableArray.Empty) - |> RoslynHelpers.StartAsyncAsTask cancellationToken + |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index fb9f60c0658..598c72145aa 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -26,6 +26,8 @@ LegacyResolver.txt + + @@ -142,7 +144,7 @@ - + diff --git a/vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs b/vsintegration/src/FSharp.Editor/Hints/FSharpInlineHintsService.fs similarity index 56% rename from vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs rename to vsintegration/src/FSharp.Editor/Hints/FSharpInlineHintsService.fs index e24562031ab..fe30a2bd5ca 100644 --- a/vsintegration/src/FSharp.Editor/Hints/RoslynAdapter.fs +++ b/vsintegration/src/FSharp.Editor/Hints/FSharpInlineHintsService.fs @@ -7,6 +7,8 @@ open System.ComponentModel.Composition open Microsoft.CodeAnalysis.ExternalAccess.FSharp.InlineHints open Microsoft.VisualStudio.FSharp.Editor open Microsoft.VisualStudio.FSharp.Editor.Telemetry +open CancellableTasks +open System.Threading.Tasks // So the Roslyn interface is called IFSharpInlineHintsService // but our implementation is called just HintsService. @@ -14,27 +16,29 @@ open Microsoft.VisualStudio.FSharp.Editor.Telemetry // e.g. signature hints above the line, pipeline hints on the side and so on. [)>] -type internal RoslynAdapter [] (settings: EditorOptions) = +type internal FSharpInlineHintsService [] (settings: EditorOptions) = static let userOpName = "Hints" interface IFSharpInlineHintsService with member _.GetInlineHintsAsync(document, _, cancellationToken) = - async { - let hintKinds = OptionParser.getHintKinds settings.Advanced + let hintKinds = OptionParser.getHintKinds settings.Advanced - if hintKinds.IsEmpty then - return ImmutableArray.Empty - else - let hintKindsSerialized = hintKinds |> Set.map Hints.serialize |> String.concat "," - TelemetryReporter.ReportSingleEvent(TelemetryEvents.Hints, [| ("hints.kinds", hintKindsSerialized) |]) + if hintKinds.IsEmpty then + Task.FromResult ImmutableArray.Empty + else + cancellableTask { + let! cancellationToken = CancellableTask.getCurrentCancellationToken () - let! sourceText = document.GetTextAsync cancellationToken |> Async.AwaitTask - let! nativeHints = HintService.getHintsForDocument sourceText document hintKinds userOpName cancellationToken + let! sourceText = document.GetTextAsync cancellationToken + let! nativeHints = HintService.getHintsForDocument sourceText document hintKinds userOpName - let roslynHints = - nativeHints |> Seq.map (NativeToRoslynHintConverter.convert sourceText) + let tasks = + nativeHints + |> Seq.map (fun hint -> NativeToRoslynHintConverter.convert sourceText hint cancellationToken) + + let! roslynHints = Task.WhenAll(tasks) return roslynHints.ToImmutableArray() - } - |> RoslynHelpers.StartAsyncAsTask cancellationToken + } + |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Hints/HintService.fs b/vsintegration/src/FSharp.Editor/Hints/HintService.fs index d4c49ca9e07..c257ffa0102 100644 --- a/vsintegration/src/FSharp.Editor/Hints/HintService.fs +++ b/vsintegration/src/FSharp.Editor/Hints/HintService.fs @@ -2,51 +2,76 @@ namespace Microsoft.VisualStudio.FSharp.Editor.Hints +open System + open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Text open Microsoft.VisualStudio.FSharp.Editor open FSharp.Compiler.Symbols open Hints +open CancellableTasks +open Microsoft.VisualStudio.FSharp.Editor.Telemetry module HintService = + let semanticClassificationCache = + new DocumentCache("fsharp-hints-cache") + let private getHints sourceText parseResults hintKinds symbolUses (symbol: FSharpSymbol) = let getHintsPerKind hintKind = match hintKind, symbol with | HintKind.TypeHint, (:? FSharpMemberOrFunctionOrValue as symbol) -> - symbolUses |> Seq.collect (InlineTypeHints(parseResults, symbol)).getHints + symbolUses |> Seq.collect (InlineTypeHints(parseResults, symbol)).GetHints | HintKind.ReturnTypeHint, (:? FSharpMemberOrFunctionOrValue as symbol) -> - symbolUses |> Seq.collect (InlineReturnTypeHints(parseResults, symbol).getHints) + symbolUses |> Seq.collect (InlineReturnTypeHints(parseResults, symbol).GetHints) | HintKind.ParameterNameHint, (:? FSharpMemberOrFunctionOrValue as symbol) -> symbolUses - |> Seq.collect (InlineParameterNameHints(parseResults).getHintsForMemberOrFunctionOrValue sourceText symbol) + |> Seq.collect (InlineParameterNameHints(parseResults).GetHintsForMemberOrFunctionOrValue sourceText symbol) | HintKind.ParameterNameHint, (:? FSharpUnionCase as symbol) -> symbolUses - |> Seq.collect (InlineParameterNameHints(parseResults).getHintsForUnionCase symbol) + |> Seq.collect (InlineParameterNameHints(parseResults).GetHintsForUnionCase symbol) | _ -> [] - let rec getHints hintKinds acc = - match hintKinds with - | [] -> acc - | hintKind :: hintKinds -> getHintsPerKind hintKind :: acc |> getHints hintKinds - - getHints (hintKinds |> Set.toList) [] + hintKinds |> Set.toList |> List.map getHintsPerKind let private getHintsForSymbol (sourceText: SourceText) parseResults hintKinds (symbol, symbolUses) = let hints = getHints sourceText parseResults hintKinds symbolUses symbol Seq.concat hints - let getHintsForDocument sourceText (document: Document) hintKinds userOpName cancellationToken = - async { + let getHintsForDocument sourceText (document: Document) hintKinds userOpName = + cancellableTask { if isSignatureFile document.FilePath then - return [] + return List.empty else - let! parseResults, checkResults = document.GetFSharpParseAndCheckResultsAsync userOpName + let hintKindsSerialized = hintKinds |> Set.map Hints.serialize |> String.concat "," + + match! semanticClassificationCache.TryGetValueAsync document with + | ValueSome nativeHints -> + do + TelemetryReporter.ReportSingleEvent( + TelemetryEvents.Hints, + [| ("hints.kinds", hintKindsSerialized); ("cacheHit", true) |] + ) + + return nativeHints + | ValueNone -> + do + TelemetryReporter.ReportSingleEvent( + TelemetryEvents.Hints, + [| ("hints.kinds", hintKindsSerialized); ("cacheHit", false) |] + ) + + let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! parseResults, checkResults = document.GetFSharpParseAndCheckResultsAsync userOpName + + let nativeHints = + checkResults.GetAllUsesOfAllSymbolsInFile cancellationToken + |> Seq.groupBy (fun symbolUse -> symbolUse.Symbol) + |> Seq.collect (getHintsForSymbol sourceText parseResults hintKinds) + |> Seq.toList + + do! semanticClassificationCache.SetAsync(document, nativeHints) - return - checkResults.GetAllUsesOfAllSymbolsInFile cancellationToken - |> Seq.groupBy (fun symbolUse -> symbolUse.Symbol) - |> Seq.collect (getHintsForSymbol sourceText parseResults hintKinds) - |> Seq.toList + return nativeHints } diff --git a/vsintegration/src/FSharp.Editor/Hints/Hints.fs b/vsintegration/src/FSharp.Editor/Hints/Hints.fs index 9b356db9855..0dd7fbff263 100644 --- a/vsintegration/src/FSharp.Editor/Hints/Hints.fs +++ b/vsintegration/src/FSharp.Editor/Hints/Hints.fs @@ -2,12 +2,13 @@ namespace Microsoft.VisualStudio.FSharp.Editor.Hints -open System.Threading open Microsoft.CodeAnalysis open FSharp.Compiler.Text +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks module Hints = + [] type HintKind = | TypeHint | ParameterNameHint @@ -19,11 +20,11 @@ module Hints = Kind: HintKind Range: range Parts: TaggedText list - GetTooltip: Document -> Async + GetTooltip: Document -> CancellableTask } - let serialize kind = + let inline serialize kind = match kind with - | TypeHint -> "type" - | ParameterNameHint -> "parameterName" - | ReturnTypeHint -> "returnType" + | HintKind.TypeHint -> "type" + | HintKind.ParameterNameHint -> "parameterName" + | HintKind.ReturnTypeHint -> "returnType" diff --git a/vsintegration/src/FSharp.Editor/Hints/InlineParameterNameHints.fs b/vsintegration/src/FSharp.Editor/Hints/InlineParameterNameHints.fs index 22affc41a77..f428a144cfb 100644 --- a/vsintegration/src/FSharp.Editor/Hints/InlineParameterNameHints.fs +++ b/vsintegration/src/FSharp.Editor/Hints/InlineParameterNameHints.fs @@ -9,11 +9,12 @@ open FSharp.Compiler.EditorServices open FSharp.Compiler.Symbols open FSharp.Compiler.Text open Hints +open CancellableTasks type InlineParameterNameHints(parseResults: FSharpParseFileResults) = let getTooltip (symbol: FSharpSymbol) _ = - async { + cancellableTask { // This brings little value as of now. Basically just discerns fields from parameters // and fills the tooltip bubble which otherwise looks like a visual glitch. // @@ -109,7 +110,7 @@ type InlineParameterNameHints(parseResults: FSharpParseFileResults) = // If a case does not use field names, don't even bother getting applied argument ranges && symbol.Fields |> Seq.exists fieldNameExists - member _.getHintsForMemberOrFunctionOrValue + member _.GetHintsForMemberOrFunctionOrValue (sourceText: SourceText) (symbol: FSharpMemberOrFunctionOrValue) (symbolUse: FSharpSymbolUse) @@ -146,7 +147,7 @@ type InlineParameterNameHints(parseResults: FSharpParseFileResults) = else [] - member _.getHintsForUnionCase (symbol: FSharpUnionCase) (symbolUse: FSharpSymbolUse) = + member _.GetHintsForUnionCase (symbol: FSharpUnionCase) (symbolUse: FSharpSymbolUse) = if isUnionCaseValidForHint symbol symbolUse then let fields = Seq.toList symbol.Fields diff --git a/vsintegration/src/FSharp.Editor/Hints/InlineReturnTypeHints.fs b/vsintegration/src/FSharp.Editor/Hints/InlineReturnTypeHints.fs index a5796b6d196..1a984df33c7 100644 --- a/vsintegration/src/FSharp.Editor/Hints/InlineReturnTypeHints.fs +++ b/vsintegration/src/FSharp.Editor/Hints/InlineReturnTypeHints.fs @@ -7,6 +7,7 @@ open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols open FSharp.Compiler.Text open Hints +open CancellableTasks type InlineReturnTypeHints(parseFileResults: FSharpParseFileResults, symbol: FSharpMemberOrFunctionOrValue) = @@ -20,7 +21,7 @@ type InlineReturnTypeHints(parseFileResults: FSharpParseFileResults, symbol: FSh ]) let getTooltip _ = - async { + cancellableTask { let typeAsString = symbol.ReturnParameter.Type.TypeDefinition.ToString() let text = $"type {typeAsString}" return [ TaggedText(TextTag.Text, text) ] @@ -38,7 +39,7 @@ type InlineReturnTypeHints(parseFileResults: FSharpParseFileResults, symbol: FSh let isValidForHint (symbol: FSharpMemberOrFunctionOrValue) = symbol.IsFunction - member _.getHints(symbolUse: FSharpSymbolUse) = + member _.GetHints(symbolUse: FSharpSymbolUse) = [ if isValidForHint symbol then yield! diff --git a/vsintegration/src/FSharp.Editor/Hints/InlineTypeHints.fs b/vsintegration/src/FSharp.Editor/Hints/InlineTypeHints.fs index a29b2302bb7..38c6bda6a2d 100644 --- a/vsintegration/src/FSharp.Editor/Hints/InlineTypeHints.fs +++ b/vsintegration/src/FSharp.Editor/Hints/InlineTypeHints.fs @@ -8,6 +8,7 @@ open FSharp.Compiler.Symbols open FSharp.Compiler.Text open FSharp.Compiler.Text.Position open Hints +open CancellableTasks type InlineTypeHints(parseResults: FSharpParseFileResults, symbol: FSharpMemberOrFunctionOrValue) = @@ -22,7 +23,7 @@ type InlineTypeHints(parseResults: FSharpParseFileResults, symbol: FSharpMemberO | None -> [] let getTooltip _ = - async { + cancellableTask { // Done this way because I am not sure if we want to show full-blown types everywhere, // e.g. Microsoft.FSharp.Core.string instead of string. // On the other hand, for user types this could be useful. @@ -83,7 +84,7 @@ type InlineTypeHints(parseResults: FSharpParseFileResults, symbol: FSharpMemberO && isNotAfterDot && isNotTypeAlias - member _.getHints symbolUse = + member _.GetHints symbolUse = [ if isValidForHint symbolUse then getHint symbol symbolUse diff --git a/vsintegration/src/FSharp.Editor/Hints/NativeToRoslynHintConverter.fs b/vsintegration/src/FSharp.Editor/Hints/NativeToRoslynHintConverter.fs index 1eb05429ea6..61442b6b547 100644 --- a/vsintegration/src/FSharp.Editor/Hints/NativeToRoslynHintConverter.fs +++ b/vsintegration/src/FSharp.Editor/Hints/NativeToRoslynHintConverter.fs @@ -2,16 +2,15 @@ namespace Microsoft.VisualStudio.FSharp.Editor.Hints -open System open System.Collections.Immutable open System.Threading -open System.Threading.Tasks open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.ExternalAccess.FSharp.InlineHints open Microsoft.VisualStudio.FSharp.Editor open Microsoft.CodeAnalysis open FSharp.Compiler.Text open Hints +open CancellableTasks module NativeToRoslynHintConverter = @@ -25,14 +24,18 @@ module NativeToRoslynHintConverter = let text = taggedText.Text RoslynTaggedText(tag, text) - let nativeToRoslynFunc nativeFunc = - Func>>(fun doc ct -> - nativeFunc doc - |> Async.map (List.map nativeToRoslynText >> ImmutableArray.CreateRange) - |> fun comp -> Async.StartAsTask(comp, cancellationToken = ct)) - let convert sourceText hint = - let span = rangeToSpan hint.Range sourceText - let displayParts = hint.Parts |> Seq.map nativeToRoslynText - let getDescription = hint.GetTooltip |> nativeToRoslynFunc - FSharpInlineHint(span, displayParts.ToImmutableArray(), getDescription) + + let getDescriptionAsync (doc: Document) (ct: CancellationToken) = + cancellableTask { + let! taggedText = hint.GetTooltip doc + return taggedText |> List.map nativeToRoslynText |> ImmutableArray.CreateRange + } + |> CancellableTask.start ct + + cancellableTask { + let span = rangeToSpan hint.Range sourceText + let displayParts = hint.Parts |> Seq.map nativeToRoslynText + + return FSharpInlineHint(span, displayParts.ToImmutableArray(), getDescriptionAsync) + } diff --git a/vsintegration/src/FSharp.Editor/Hints/OptionParser.fs b/vsintegration/src/FSharp.Editor/Hints/OptionParser.fs index 4b318490111..437298b35db 100644 --- a/vsintegration/src/FSharp.Editor/Hints/OptionParser.fs +++ b/vsintegration/src/FSharp.Editor/Hints/OptionParser.fs @@ -7,7 +7,7 @@ open Hints module OptionParser = - let getHintKinds options = + let inline getHintKinds options = Set [ if options.IsInlineTypeHintsEnabled then diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index c1544bf78c4..d008115d892 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -14,21 +14,17 @@ open FSharp.Compiler open FSharp.Compiler.CodeAnalysis open FSharp.NativeInterop open Microsoft.VisualStudio -open Microsoft.VisualStudio.Editor open Microsoft.VisualStudio.FSharp.Editor open Microsoft.VisualStudio.LanguageServices open Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService open Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem -open Microsoft.VisualStudio.LanguageServices.ProjectSystem open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop open Microsoft.VisualStudio.Text.Outlining open Microsoft.CodeAnalysis.ExternalAccess.FSharp -open Microsoft.CodeAnalysis.Host open Microsoft.CodeAnalysis.Host.Mef -open Microsoft.VisualStudio.FSharp.Editor.WorkspaceExtensions open Microsoft.VisualStudio.FSharp.Editor.Telemetry -open System.Threading.Tasks +open CancellableTasks #nowarn "9" // NativePtr.toNativeInt #nowarn "57" // Experimental stuff @@ -50,12 +46,9 @@ type internal RoamingProfileStorageLocation(keyName: string) = unsubstitutedKeyName.Replace("%LANGUAGE%", substituteLanguageName) -[] +[] [, ServiceLayer.Default)>] -type internal FSharpWorkspaceServiceFactory [] - ( - metadataAsSourceService: FSharpMetadataAsSourceService - ) = +type internal FSharpWorkspaceServiceFactory [] (metadataAsSourceService: FSharpMetadataAsSourceService) = // We have a lock just in case if multi-threads try to create a new IFSharpWorkspaceService - // but we only want to have a single instance of the FSharpChecker regardless if there are multiple instances of IFSharpWorkspaceService. @@ -76,11 +69,7 @@ type internal FSharpWorkspaceServiceFactory [ try let md = - Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetMetadata( - workspace, - path, - timeStamp - ) + LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetMetadata(workspace, path, timeStamp) let amd = (md :?> AssemblyMetadata) let mmd = amd.GetModules().[0] @@ -118,8 +107,6 @@ type internal FSharpWorkspaceServiceFactory [ let checker = lazy - TelemetryReporter.ReportSingleEvent(TelemetryEvents.LanguageServiceStarted, [||]) - let editorOptions = workspace.Services.GetService() let enableParallelReferenceResolution = @@ -143,17 +130,55 @@ type internal FSharpWorkspaceServiceFactory [ if args.DocumentId <> null then @@ -239,19 +250,19 @@ type private FSharpSolutionEvents(projectManager: FSharpProjectOptionsManager, m member _.OnQueryUnloadProject(_, _) = VSConstants.E_NOTIMPL -[, Microsoft.CodeAnalysis.Host.Mef.ServiceLayer.Default)>] +[, ServiceLayer.Default)>] type internal FSharpSettingsFactory [] (settings: EditorOptions) = - interface Microsoft.CodeAnalysis.Host.Mef.IWorkspaceServiceFactory with + interface Host.Mef.IWorkspaceServiceFactory with member _.CreateService(_) = upcast settings [] -[, "F# Tools", "F# Interactive", 6000s, 6001s, true)>] // true = supports automation +[, "F# Tools", "F# Interactive", 6000s, 6001s, true)>] // true = supports automation [] // <-- resource ID for localised name -[, +[, Orientation = ToolWindowOrientation.Bottom, Style = VsDockStyle.Tabbed, PositionX = 0, @@ -308,16 +319,13 @@ type internal FSharpSettingsFactory [] (settin type internal FSharpPackage() as this = inherit AbstractPackage() - let mutable vfsiToolWindow = - Unchecked.defaultof + let mutable vfsiToolWindow = Unchecked.defaultof let GetToolWindowAsITestVFSI () = if vfsiToolWindow = Unchecked.defaultof<_> then - vfsiToolWindow <- - this.FindToolWindow(typeof, 0, true) - :?> Microsoft.VisualStudio.FSharp.Interactive.FsiToolWindow + vfsiToolWindow <- this.FindToolWindow(typeof, 0, true) :?> FSharp.Interactive.FsiToolWindow - vfsiToolWindow :> Microsoft.VisualStudio.FSharp.Interactive.ITestVFSI + vfsiToolWindow :> FSharp.Interactive.ITestVFSI let mutable solutionEventsOpt = None @@ -327,105 +335,92 @@ type internal FSharpPackage() as this = #endif // FSI-LINKAGE-POINT: unsited init - do Microsoft.VisualStudio.FSharp.Interactive.Hooks.fsiConsoleWindowPackageCtorUnsited (this :> Package) + do FSharp.Interactive.Hooks.fsiConsoleWindowPackageCtorUnsited (this :> Package) override this.InitializeAsync(cancellationToken: CancellationToken, progress: IProgress) : Tasks.Task = // `base.` methods can't be called in the `async` builder, so we have to cache it let baseInitializeAsync = base.InitializeAsync(cancellationToken, progress) - let task = - async { - do! baseInitializeAsync |> Async.AwaitTask - - let! commandService = this.GetServiceAsync(typeof) |> Async.AwaitTask // FSI-LINKAGE-POINT - let commandService = commandService :?> OleMenuCommandService + foregroundCancellableTask { + do! baseInitializeAsync - let packageInit () = - // FSI-LINKAGE-POINT: sited init - Microsoft.VisualStudio.FSharp.Interactive.Hooks.fsiConsoleWindowPackageInitalizeSited (this :> Package) commandService + let! commandService = this.GetServiceAsync(typeof) + let commandService = commandService :?> OleMenuCommandService - // FSI-LINKAGE-POINT: private method GetDialogPage forces fsi options to be loaded - let _fsiPropertyPage = - this.GetDialogPage(typeof) + // Switch to UI thread + do! this.JoinableTaskFactory.SwitchToMainThreadAsync() - let workspace = this.ComponentModel.GetService() + // FSI-LINKAGE-POINT: sited init + FSharp.Interactive.Hooks.fsiConsoleWindowPackageInitalizeSited (this :> Package) commandService - let _ = - this.ComponentModel.DefaultExportProvider.GetExport() + // FSI-LINKAGE-POINT: private method GetDialogPage forces fsi options to be loaded + let _fsiPropertyPage = + this.GetDialogPage(typeof) - let optionsManager = - workspace - .Services - .GetService() - .FSharpProjectOptionsManager + let workspace = this.ComponentModel.GetService() - let metadataAsSource = - this - .ComponentModel - .DefaultExportProvider - .GetExport() - .Value + let _ = + this.ComponentModel.DefaultExportProvider.GetExport() - let solution = this.GetServiceAsync(typeof).Result - let solution = solution :?> IVsSolution - let solutionEvents = FSharpSolutionEvents(optionsManager, metadataAsSource) - let rdt = this.GetServiceAsync(typeof).Result - let rdt = rdt :?> IVsRunningDocumentTable - - solutionEventsOpt <- Some(solutionEvents) - solution.AdviseSolutionEvents(solutionEvents) |> ignore + let optionsManager = + workspace + .Services + .GetService() + .FSharpProjectOptionsManager - let projectContextFactory = - this.ComponentModel.GetService() + let metadataAsSource = + this + .ComponentModel + .DefaultExportProvider + .GetExport() + .Value - let miscFilesWorkspace = - this.ComponentModel.GetService() + let! solution = this.GetServiceAsync(typeof) + let solution = solution :?> IVsSolution - let _singleFileWorkspaceMap = - new SingleFileWorkspaceMap( - FSharpMiscellaneousFileService(workspace, miscFilesWorkspace, projectContextFactory), - rdt - ) + let solutionEvents = FSharpSolutionEvents(optionsManager, metadataAsSource) - let _legacyProjectWorkspaceMap = - new LegacyProjectWorkspaceMap(solution, optionsManager, projectContextFactory) + let! rdt = this.GetServiceAsync(typeof) + let rdt = rdt :?> IVsRunningDocumentTable - () + solutionEventsOpt <- Some(solutionEvents) + solution.AdviseSolutionEvents(solutionEvents) |> ignore - let awaiter = this.JoinableTaskFactory.SwitchToMainThreadAsync().GetAwaiter() + let projectContextFactory = + this.ComponentModel.GetService() - if awaiter.IsCompleted then - packageInit () // already on the UI thread - else - awaiter.OnCompleted(fun () -> packageInit ()) + let miscFilesWorkspace = + this.ComponentModel.GetService() - } - |> Async.StartAsTask + do + SingleFileWorkspaceMap(FSharpMiscellaneousFileService(workspace, miscFilesWorkspace, projectContextFactory), rdt) + |> ignore - upcast task // convert Task to Task + } + |> CancellableTask.startAsTask cancellationToken - override this.RoslynLanguageName = FSharpConstants.FSharpLanguageName + override _.RoslynLanguageName = FSharpConstants.FSharpLanguageName (*override this.CreateWorkspace() = this.ComponentModel.GetService() *) override this.CreateLanguageService() = FSharpLanguageService(this) override this.CreateEditorFactories() = seq { yield FSharpEditorFactory(this) :> IVsEditorFactory } - override this.RegisterMiscellaneousFilesWorkspaceInformation(miscFilesWorkspace) = + override _.RegisterMiscellaneousFilesWorkspaceInformation(miscFilesWorkspace) = miscFilesWorkspace.RegisterLanguage(Guid(FSharpConstants.languageServiceGuidString), FSharpConstants.FSharpLanguageName, ".fsx") - interface Microsoft.VisualStudio.FSharp.Interactive.ITestVFSI with - member this.SendTextInteraction(s: string) = + interface FSharp.Interactive.ITestVFSI with + member _.SendTextInteraction(s: string) = GetToolWindowAsITestVFSI().SendTextInteraction(s) - member this.GetMostRecentLines(n: int) : string[] = + member _.GetMostRecentLines(n: int) : string[] = GetToolWindowAsITestVFSI().GetMostRecentLines(n) [] type internal FSharpLanguageService(package: FSharpPackage) = inherit AbstractLanguageService(package) - override this.Initialize() = + override _.Initialize() = base.Initialize() let globalOptions = @@ -459,8 +454,7 @@ type internal FSharpLanguageService(package: FSharpPackage) = override _.LanguageServiceId = new Guid(FSharpConstants.languageServiceGuidString) override _.DebuggerLanguageId = CompilerEnvironment.GetDebuggerLanguageID() - override _.CreateContext(_, _, _, _, _) = - raise (System.NotImplementedException()) + override _.CreateContext(_, _, _, _, _) = raise (NotImplementedException()) override this.SetupNewTextView(textView) = base.SetupNewTextView(textView) @@ -477,10 +471,10 @@ type internal FSharpLanguageService(package: FSharpPackage) = outliningManager.Enabled <- settings.Advanced.IsOutliningEnabled [] -[)>] -type internal HackCpsCommandLineChanges [] +[)>] +type internal HackCpsCommandLineChanges [] ( - [)>] workspace: VisualStudioWorkspace + [)>] workspace: VisualStudioWorkspace ) = static let projectDisplayNameOf projectFileName = @@ -489,7 +483,7 @@ type internal HackCpsCommandLineChanges [] + [] /// This handles commandline change notifications from the Dotnet Project-system /// Prior to VS 15.7 path contained path to project file, post 15.7 contains target binpath /// binpath is more accurate because a project file can have multiple in memory projects based on configuration @@ -512,13 +506,11 @@ type internal HackCpsCommandLineChanges [ projectId | false, _ -> - Microsoft - .CodeAnalysis - .ExternalAccess - .FSharp - .LanguageServices - .FSharpVisualStudioWorkspaceExtensions - .GetOrCreateProjectIdForPath(workspace, path, projectDisplayNameOf path) + LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetOrCreateProjectIdForPath( + workspace, + path, + projectDisplayNameOf path + ) let path = Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetProjectFilePath( diff --git a/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs b/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs index 00c85540299..ea2b41ca3d0 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs @@ -24,6 +24,7 @@ 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 diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs index 884ea193da4..1864e99c508 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindDefinitionService.fs @@ -20,5 +20,5 @@ type internal FSharpFindDefinitionService [] (metadataAsSo member _.FindDefinitionsAsync(document: Document, position: int, cancellationToken: CancellationToken) = let navigation = FSharpNavigation(metadataAsSource, document, rangeStartup) - let definitions = navigation.FindDefinitions(position, cancellationToken) + let definitions = navigation.FindDefinitions(position, cancellationToken) // TODO: probably will need to be async all the way down ImmutableArray.CreateRange(definitions) |> Task.FromResult diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index bd8c79a9a5e..0311769f4d8 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -17,6 +17,7 @@ open Microsoft.VisualStudio.LanguageServices open Microsoft.VisualStudio.Text.PatternMatching open FSharp.Compiler.EditorServices +open CancellableTasks [); Shared>] type internal FSharpNavigateToSearchService [] @@ -35,8 +36,9 @@ type internal FSharpNavigateToSearchService [] cache.Clear() let getNavigableItems (document: Document) = - async { - let! currentVersion = document.GetTextVersionAsync() |> Async.AwaitTask + cancellableTask { + let! ct = CancellableTask.getCurrentCancellationToken () + let! currentVersion = document.GetTextVersionAsync(ct) match cache.TryGetValue document.Id with | true, (version, items) when version = currentVersion -> return items @@ -143,12 +145,14 @@ type internal FSharpNavigateToSearchService [] patternMatcher.TryMatch $"{item.Container.FullName}.{name}" |> Option.ofNullable let processDocument (tryMatch: NavigableItem -> PatternMatch option) (kinds: IImmutableSet) (document: Document) = - async { - let! ct = Async.CancellationToken - let! sourceText = document.GetTextAsync ct |> Async.AwaitTask + cancellableTask { + let! ct = CancellableTask.getCurrentCancellationToken () + + let! sourceText = document.GetTextAsync ct + + let processItem (item: NavigableItem) = + asyncMaybe { // TODO: make a flat cancellable task - let processItem item = - asyncMaybe { do! Option.guard (kinds.Contains(navigateToItemKindToRoslynKind item.Kind)) let! m = tryMatch item @@ -182,14 +186,19 @@ type internal FSharpNavigateToSearchService [] kinds, cancellationToken ) : Task> = - async { + cancellableTask { let tryMatch = createMatcherFor searchPattern - let! results = project.Documents |> Seq.map (processDocument tryMatch kinds) |> Async.Parallel + let! ct = CancellableTask.getCurrentCancellationToken () + + let tasks = + Seq.map (fun doc -> processDocument tryMatch kinds doc ct) project.Documents + + let! results = Task.WhenAll(tasks) return results |> Array.concat |> Array.toImmutableArray } - |> RoslynHelpers.StartAsyncAsTask cancellationToken + |> CancellableTask.start cancellationToken member _.SearchDocumentAsync ( @@ -198,11 +207,11 @@ type internal FSharpNavigateToSearchService [] kinds, cancellationToken ) : Task> = - async { + cancellableTask { let! result = processDocument (createMatcherFor searchPattern) kinds document return result |> Array.toImmutableArray } - |> RoslynHelpers.StartAsyncAsTask cancellationToken + |> CancellableTask.start cancellationToken member _.KindsProvided = kindsProvided diff --git a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs index 137bcce687d..dd33df48d63 100644 --- a/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs +++ b/vsintegration/src/FSharp.Editor/Options/EditorOptions.fs @@ -84,6 +84,10 @@ type LanguageServicePerformanceOptions = EnableFastFindReferencesAndRename: bool EnablePartialTypeChecking: bool UseSyntaxTreeCache: bool + KeepAllBackgroundResolutions: bool + KeepAllBackgroundSymbolUses: bool + EnableBackgroundItemKeyStoreAndSemanticClassification: bool + CaptureIdentifiersWhenParsing: bool } static member Default = @@ -94,6 +98,10 @@ type LanguageServicePerformanceOptions = EnableFastFindReferencesAndRename = true EnablePartialTypeChecking = true UseSyntaxTreeCache = FSharpExperimentalFeaturesEnabledAutomatically + KeepAllBackgroundResolutions = false + KeepAllBackgroundSymbolUses = false + EnableBackgroundItemKeyStoreAndSemanticClassification = true + CaptureIdentifiersWhenParsing = true } [] @@ -116,7 +124,7 @@ type AdvancedOptions = IsInlineParameterNameHintsEnabled = false IsInlineReturnTypeHintsEnabled = false IsLiveBuffersEnabled = FSharpExperimentalFeaturesEnabledAutomatically - SendAdditionalTelemetry = FSharpExperimentalFeaturesEnabledAutomatically + SendAdditionalTelemetry = true } [] diff --git a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs index c8e893070a5..db0f3933703 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfo/QuickInfoProvider.fs @@ -18,6 +18,7 @@ open Microsoft.VisualStudio.FSharp.Editor open FSharp.Compiler.Text open Microsoft.IO open FSharp.Compiler.EditorServices +open CancellableTasks type internal FSharpAsyncQuickInfoSource ( @@ -28,7 +29,7 @@ type internal FSharpAsyncQuickInfoSource ) = let getQuickInfoItem (sourceText, (document: Document), (lexerSymbol: LexerSymbol), (ToolTipText elements)) = - asyncMaybe { + cancellableTask { let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(xmlMemberIndexService) @@ -56,72 +57,88 @@ type internal FSharpAsyncQuickInfoSource ) let content = elements |> List.map getSingleContent - do! Option.guard (not content.IsEmpty) - let! textSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lexerSymbol.Range) + if content.IsEmpty then + return None + else + let textSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, lexerSymbol.Range) - let trackingSpan = - textBuffer.CurrentSnapshot.CreateTrackingSpan(textSpan.Start, textSpan.Length, SpanTrackingMode.EdgeInclusive) + match textSpan with + | None -> return None + | Some textSpan -> + let trackingSpan = + textBuffer.CurrentSnapshot.CreateTrackingSpan(textSpan.Start, textSpan.Length, SpanTrackingMode.EdgeInclusive) - return QuickInfoItem(trackingSpan, QuickInfoViewProvider.stackWithSeparators content) + return Some(QuickInfoItem(trackingSpan, QuickInfoViewProvider.stackWithSeparators content)) } static member TryGetToolTip(document: Document, position, ?width) = - asyncMaybe { + cancellableTask { let userOpName = "getQuickInfo" - + let! cancellationToken = CancellableTask.getCurrentCancellationToken () let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, true, true, userOpName) - let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync - let! cancellationToken = Async.CancellationToken |> liftAsync - let! sourceText = document.GetTextAsync cancellationToken - let range = lexerSymbol.Range - let textLinePos = sourceText.Lines.GetLinePosition position - let fcsTextLineNumber = Line.fromZ textLinePos.Line - let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() - - let tooltip = - match lexerSymbol.Kind with - | LexerSymbolKind.Keyword -> checkFileResults.GetKeywordTooltip(lexerSymbol.FullIsland) - | LexerSymbolKind.String -> - checkFileResults.GetToolTip( - fcsTextLineNumber, - range.EndColumn, - lineText, - lexerSymbol.FullIsland, - FSharp.Compiler.Tokenization.FSharpTokenTag.String, - ?width = width - ) - | _ -> - checkFileResults.GetToolTip( - fcsTextLineNumber, - range.EndColumn, - lineText, - lexerSymbol.FullIsland, - FSharp.Compiler.Tokenization.FSharpTokenTag.IDENT, - ?width = width - ) - - return sourceText, document, lexerSymbol, tooltip + + match lexerSymbol with + | None -> return None + | Some lexerSymbol -> + let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) + let! sourceText = document.GetTextAsync cancellationToken + let range = lexerSymbol.Range + let textLinePos = sourceText.Lines.GetLinePosition position + let fcsTextLineNumber = Line.fromZ textLinePos.Line + let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() + + let tooltip = + match lexerSymbol.Kind with + | LexerSymbolKind.Keyword -> checkFileResults.GetKeywordTooltip(lexerSymbol.FullIsland) + | LexerSymbolKind.String -> + checkFileResults.GetToolTip( + fcsTextLineNumber, + range.EndColumn, + lineText, + lexerSymbol.FullIsland, + FSharp.Compiler.Tokenization.FSharpTokenTag.String, + ?width = width + ) + | _ -> + checkFileResults.GetToolTip( + fcsTextLineNumber, + range.EndColumn, + lineText, + lexerSymbol.FullIsland, + FSharp.Compiler.Tokenization.FSharpTokenTag.IDENT, + ?width = width + ) + + return Some(sourceText, document, lexerSymbol, tooltip) } interface IAsyncQuickInfoSource with override _.Dispose() = () // no cleanup necessary override _.GetQuickInfoItemAsync(session: IAsyncQuickInfoSession, cancellationToken: CancellationToken) : Task = - asyncMaybe { + cancellableTask { let document = textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges() - let! triggerPoint = session.GetTriggerPoint(textBuffer.CurrentSnapshot) |> Option.ofNullable - let position = triggerPoint.Position + let triggerPoint = session.GetTriggerPoint(textBuffer.CurrentSnapshot) + + if not triggerPoint.HasValue then + return Unchecked.defaultof<_> + else + let position = triggerPoint.Value.Position + + let! tipdata = + FSharpAsyncQuickInfoSource.TryGetToolTip(document, position, ?width = editorOptions.QuickInfo.DescriptionWidth) - let! tipdata = - FSharpAsyncQuickInfoSource.TryGetToolTip(document, position, ?width = editorOptions.QuickInfo.DescriptionWidth) + match tipdata with + | Some tipdata -> + let! tipdata = getQuickInfoItem tipdata + return Option.toObj tipdata + | None -> return Unchecked.defaultof<_> - return! getQuickInfoItem tipdata } - |> Async.map Option.toObj - |> RoslynHelpers.StartAsyncAsTask cancellationToken + |> CancellableTask.start cancellationToken [)>] [] diff --git a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs index ae00c344192..7a80409f505 100644 --- a/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs +++ b/vsintegration/src/FSharp.Editor/Structure/BlockStructureService.fs @@ -3,7 +3,6 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition -open System.Collections.Immutable open System.Threading.Tasks open Microsoft.CodeAnalysis @@ -147,6 +146,7 @@ module internal BlockStructure = | _, _ -> None) open BlockStructure +open CancellableTasks [)>] type internal FSharpBlockStructureService [] () = @@ -154,17 +154,16 @@ type internal FSharpBlockStructureService [] () = interface IFSharpBlockStructureService with member _.GetBlockStructureAsync(document, cancellationToken) : Task = - asyncMaybe { + cancellableTask { + let! cancellationToken = CancellableTask.getCurrentCancellationToken () + let! sourceText = document.GetTextAsync(cancellationToken) - let! parseResults = - document.GetFSharpParseResultsAsync(nameof (FSharpBlockStructureService)) - |> liftAsync + let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpBlockStructureService)) return createBlockSpans document.Project.IsFSharpBlockStructureEnabled sourceText parseResults.ParseTree |> Seq.toImmutableArray + |> FSharpBlockStructure } - |> Async.map (Option.defaultValue ImmutableArray<_>.Empty) - |> Async.map FSharpBlockStructure - |> RoslynHelpers.StartAsyncAsTask(cancellationToken) + |> CancellableTask.start cancellationToken diff --git a/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml b/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml index 798fe5ad1b1..554e5d76e09 100644 --- a/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml +++ b/vsintegration/src/FSharp.UIResources/LanguageServicePerformanceOptionControl.xaml @@ -70,6 +70,23 @@ Content="{x:Static local:Strings.Enable_Fast_Find_References}"/> + + + + + + + + + diff --git a/vsintegration/src/FSharp.UIResources/Strings.Designer.cs b/vsintegration/src/FSharp.UIResources/Strings.Designer.cs index 1ce713052f2..69eca550813 100644 --- a/vsintegration/src/FSharp.UIResources/Strings.Designer.cs +++ b/vsintegration/src/FSharp.UIResources/Strings.Designer.cs @@ -87,6 +87,15 @@ public static string Block_Structure { } } + /// + /// Looks up a localized string similar to Capture identifiers while parsing. + /// + public static string Capture_Identifiers_When_Parsing { + get { + return ResourceManager.GetString("Capture_Identifiers_When_Parsing", resourceCulture); + } + } + /// /// Looks up a localized string similar to Code Fixes. /// @@ -132,6 +141,15 @@ public static string Dot_underline { } } + /// + /// Looks up a localized string similar to Keep background symbol keys. + /// + public static string Enable_Background_ItemKeyStore_And_Semantic_Classification { + get { + return ResourceManager.GetString("Enable_Background_ItemKeyStore_And_Semantic_Classification", resourceCulture); + } + } + /// /// Looks up a localized string similar to Enable fast find references & rename (experimental). /// @@ -258,6 +276,24 @@ public static string IntelliSense_Performance { } } + /// + /// Looks up a localized string similar to Keep all background intermediate resolutions (increases memory usage). + /// + public static string Keep_All_Background_Resolutions { + get { + return ResourceManager.GetString("Keep_All_Background_Resolutions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Keep all background symbol uses (increases memory usage). + /// + public static string Keep_All_Background_Symbol_Uses { + get { + return ResourceManager.GetString("Keep_All_Background_Symbol_Uses", resourceCulture); + } + } + /// /// Looks up a localized string similar to Performance. /// @@ -267,6 +303,15 @@ public static string Language_Service_Performance { } } + /// + /// Looks up a localized string similar to Language service settings (advanced). + /// + public static string Language_Service_Settings { + get { + return ResourceManager.GetString("Language_Service_Settings", resourceCulture); + } + } + /// /// Looks up a localized string similar to Live Buffers (experimental). /// diff --git a/vsintegration/src/FSharp.UIResources/Strings.resx b/vsintegration/src/FSharp.UIResources/Strings.resx index 12f2ce0f947..744428afe80 100644 --- a/vsintegration/src/FSharp.UIResources/Strings.resx +++ b/vsintegration/src/FSharp.UIResources/Strings.resx @@ -231,6 +231,21 @@ Find References Performance Options + + Language service settings (advanced) + + + Keep all background intermediate resolutions (increases memory usage) + + + Keep all background symbol uses (increases memory usage) + + + Keep background symbol keys + + + Capture identifiers while parsing + Use live (unsaved) buffers for checking (restart required) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf index 1d431306de6..da3fb391af1 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.cs.xlf @@ -12,6 +12,16 @@ Vždy umístit otevřené příkazy na nejvyšší úroveň + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) Povolit odkazy rychlého hledání a přejmenování (experimentální) @@ -62,6 +72,16 @@ Povolit paralelní referenční rozlišení + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance Výkon @@ -77,6 +97,11 @@ _Tečkované podtržení + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) Živé vyrovnávací paměti (experimentální) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf index 0b4a44eaa31..2dc2d80b0d0 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.de.xlf @@ -12,6 +12,16 @@ open-Anweisungen immer an oberster Ebene platzieren + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) Schnellsuche und Umbenennen von Verweisen aktivieren (experimentell) @@ -62,6 +72,16 @@ Parallele Verweisauflösung aktivieren + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance Leistung @@ -77,6 +97,11 @@ Ge_punktete Unterstreichung + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) Livepuffer (experimentell) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf index b3b7158342b..78b806cf790 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.es.xlf @@ -12,6 +12,16 @@ Colocar siempre las instrucciones open en el nivel superior + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) Habilitar referencias de búsqueda rápida y cambio de nombre (experimental) @@ -62,6 +72,16 @@ Habilitar resolución de referencias paralelas + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance Rendimiento @@ -77,6 +97,11 @@ S_ubrayado punto + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) Búferes activos (experimental) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf index 5970e89584b..d4b627066e5 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.fr.xlf @@ -12,6 +12,16 @@ Placer toujours les instructions open au niveau supérieur + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) Activer les références de recherche rapide et renommer (expérimental) @@ -62,6 +72,16 @@ Activer la résolution de référence parallèle + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance Performances @@ -77,6 +97,11 @@ S_oulignement avec des points + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) Live Buffers (expérimental) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf index c7f7d17d24b..43a27f66f45 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.it.xlf @@ -12,6 +12,16 @@ Inserisci sempre le istruzioni OPEN al primo livello + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) Abilitare la ricerca rapida dei riferimenti e la ridenominazione (sperimentale) @@ -62,6 +72,16 @@ Abilitare risoluzione riferimenti paralleli + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance Prestazioni @@ -77,6 +97,11 @@ Sottolineatura _punteggiata + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) Buffer in tempo reale (sperimentale) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf index d699a836bcb..05f4288e8e8 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ja.xlf @@ -12,6 +12,16 @@ Open ステートメントを常に最上位に配置する + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) 高速検索参照の有効化と名前の変更 (試験段階) @@ -62,6 +72,16 @@ 並列参照解決を有効にする + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance パフォーマンス @@ -77,6 +97,11 @@ 点線の下線(_O) + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) ライブ バッファー (試験段階) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf index c603eb32700..26b4befb283 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ko.xlf @@ -12,6 +12,16 @@ 항상 최상위에 open 문 배치 + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) 빠른 찾기 참조 및 이름 바꾸기 사용(실험적) @@ -62,6 +72,16 @@ 병렬 참조 해상도 사용 + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance 성능 @@ -77,6 +97,11 @@ 점 밑줄(_O) + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) 라이브 버퍼(실험적) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf index 798997dddf3..fd8a7d9767f 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.pl.xlf @@ -12,6 +12,16 @@ Zawsze umieszczaj otwarte instrukcje na najwyższym poziomie + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) Włącz szybkie znajdowanie odwołań i zmień nazwę (eksperymentalne) @@ -62,6 +72,16 @@ Włącz równoległe rozpoznawanie odwołań + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance Wydajność @@ -77,6 +97,11 @@ P_odkreślenie z kropek + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) Aktywne bufory (eksperymentalne) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf index 110cdc0ee86..a6e151fed23 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.pt-BR.xlf @@ -12,6 +12,16 @@ Sempre coloque as instruções abertas no nível superior + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) Habilitar localizar referências rapidamente e renomear (experimental) @@ -62,6 +72,16 @@ Habilitar a resolução de referência paralela + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance Desempenho @@ -77,6 +97,11 @@ Sublinhado p_ontilhado + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) Buffers Dinâmicos (experimental) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf index fabb6126bed..1bcad7fe621 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.ru.xlf @@ -12,6 +12,16 @@ Всегда располагайте открытые операторы на верхнем уровне + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) Включить быстрый поиск ссылок и переименование (экспериментальная версия) @@ -62,6 +72,16 @@ Включить параллельное разрешение ссылок + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance Производительность @@ -77,6 +97,11 @@ Пу_нктирное подчеркивание + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) Динамические буферы (экспериментальная функция) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf index 892af3ca3e8..05935441a9a 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.tr.xlf @@ -12,6 +12,16 @@ Açık deyimleri her zaman en üst düzeye yerleştir + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) Başvuruları hızlı bulma ve yeniden adlandırmayı etkinleştir (deneysel) @@ -62,6 +72,16 @@ Paralel başvuru çözümlemeyi etkinleştir + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance Performans @@ -77,6 +97,11 @@ N_okta alt çizgi + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) Canlı Arabellekler (deneysel) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf index 6e0ff2e91df..71be5eec586 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hans.xlf @@ -12,6 +12,16 @@ 始终在顶层放置 open 语句 + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) 启用快速查找引用和重命名(实验性) @@ -62,6 +72,16 @@ 启用并行引用解析 + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance 性能 @@ -77,6 +97,11 @@ 点下划线(_O) + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) 实时缓冲区(实验性) diff --git a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf index 1ed09d1f289..e045c7812e2 100644 --- a/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf +++ b/vsintegration/src/FSharp.UIResources/xlf/Strings.zh-Hant.xlf @@ -12,6 +12,16 @@ 一律將 open 陳述式放在最上層 + + Capture identifiers while parsing + Capture identifiers while parsing + + + + Keep background symbol keys + Keep background symbol keys + + Enable fast find references & rename (experimental) 啟用快速尋找參考和重新命名 (實驗性) @@ -62,6 +72,16 @@ 啟用平行參考解析 + + Keep all background intermediate resolutions (increases memory usage) + Keep all background intermediate resolutions (increases memory usage) + + + + Keep all background symbol uses (increases memory usage) + Keep all background symbol uses (increases memory usage) + + Performance 效能 @@ -77,6 +97,11 @@ 點線底線(_O) + + Language service settings (advanced) + Language service settings (advanced) + + Live Buffers (experimental) 即時緩衝區 (實驗性) diff --git a/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs index 7322e981aba..08a73dc2816 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/BreakpointResolutionServiceTests.fs @@ -3,6 +3,7 @@ namespace FSharp.Editor.Tests open System +open System.Threading open Xunit open Microsoft.CodeAnalysis.Text open Microsoft.VisualStudio.FSharp.Editor @@ -59,8 +60,10 @@ let main argv = TextSpan.FromBounds(searchPosition, searchPosition + searchToken.Length) let actualResolutionOption = - FSharpBreakpointResolutionService.GetBreakpointLocation(document, searchSpan) - |> Async.RunSynchronously + let task = + FSharpBreakpointResolutionService.GetBreakpointLocation (document, searchSpan) CancellationToken.None + + task.Result match actualResolutionOption with | None -> Assert.True(expectedResolution.IsNone, "BreakpointResolutionService failed to resolve breakpoint position") diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index 8bcf3168379..4e52874d8b3 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -2,10 +2,13 @@ namespace FSharp.Editor.Tests +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks + module CompletionProviderTests = open System open System.Linq + open System.Threading open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Completion open Microsoft.CodeAnalysis.Text @@ -30,10 +33,11 @@ module CompletionProviderTests = |> RoslynTestHelpers.GetSingleDocument let results = - FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [])) - |> Async.RunSynchronously - |> Option.defaultValue (ResizeArray()) - |> Seq.map (fun result -> result.DisplayText) + let task = + FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [])) + |> CancellableTask.start CancellationToken.None + + task.Result |> Seq.map (fun result -> result.DisplayText) let expectedFound = expected |> List.filter results.Contains @@ -77,9 +81,11 @@ module CompletionProviderTests = |> RoslynTestHelpers.GetSingleDocument let actual = - FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [])) - |> Async.RunSynchronously - |> Option.defaultValue (ResizeArray()) + let task = + FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [])) + |> CancellableTask.start CancellationToken.None + + task.Result |> Seq.toList // sort items as Roslyn do - by `SortText` |> List.sortBy (fun x -> x.SortText) @@ -105,7 +111,7 @@ module CompletionProviderTests = let sourceText = SourceText.From(fileContents) let resultSpan = - CompletionUtils.getDefaultCompletionListSpan (sourceText, caretPosition, documentId, filePath, [], None) + CompletionUtils.getDefaultCompletionListSpan (sourceText, caretPosition, documentId, filePath, [], None, CancellationToken.None) Assert.Equal(expected, sourceText.ToString(resultSpan)) @@ -140,7 +146,8 @@ System.Console.WriteLine(x + y) caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) triggered @@ -161,7 +168,8 @@ System.Console.WriteLine(x + y) caretPosition, triggerKind, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) Assert.False(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") @@ -178,7 +186,8 @@ System.Console.WriteLine(x + y) caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) Assert.False(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") @@ -202,7 +211,8 @@ System.Console.WriteLine() caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) Assert.False(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") @@ -239,7 +249,8 @@ let z = $"abc {System.Console.WriteLine(x + y)} def" caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) triggered @@ -265,7 +276,8 @@ System.Console.WriteLine() caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) Assert.False(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger") @@ -288,7 +300,8 @@ let f() = caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) Assert.False(triggered, "FSharpCompletionProvider.ShouldTriggerCompletionAux() should not trigger on operators") @@ -311,7 +324,8 @@ module Foo = module end caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) Assert.True(triggered, "Completion should trigger on Attributes.") @@ -334,7 +348,8 @@ printfn "%d" !f caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) Assert.True(triggered, "Completion should trigger after typing an identifier that follows a dereference operator (!).") @@ -358,7 +373,8 @@ use ptr = fixed &p caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) Assert.True(triggered, "Completion should trigger after typing an identifier that follows an addressOf operator (&).") @@ -391,7 +407,8 @@ xVal**y caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) Assert.True(triggered, "Completion should trigger after typing an identifier that follows a mathematical operation") @@ -412,7 +429,8 @@ l""" caretPosition, CompletionTriggerKind.Insertion, mkGetInfo documentId, - IntelliSenseOptions.Default + IntelliSenseOptions.Default, + CancellationToken.None ) Assert.True( diff --git a/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs b/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs index f5a3bd134fe..ed6b6091e7c 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs @@ -7,22 +7,27 @@ open Microsoft.CodeAnalysis open Microsoft.VisualStudio.FSharp.Editor open FSharp.Editor.Tests.Helpers open FSharp.Test +open System.Threading +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks type DocumentDiagnosticAnalyzerTests() = let startMarker = "(*start*)" let endMarker = "(*end*)" let getDiagnostics (fileContents: string) = - async { - let document = - RoslynTestHelpers.CreateSolution(fileContents) - |> RoslynTestHelpers.GetSingleDocument - - let! syntacticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) - let! semanticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic) - return syntacticDiagnostics.AddRange(semanticDiagnostics) - } - |> Async.RunSynchronously + let task = + cancellableTask { + let document = + RoslynTestHelpers.CreateSolution(fileContents) + |> RoslynTestHelpers.GetSingleDocument + + let! syntacticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) + let! semanticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Semantic) + return syntacticDiagnostics.AddRange(semanticDiagnostics) + } + |> CancellableTask.start CancellationToken.None + + task.Result member private this.VerifyNoErrors(fileContents: string, ?additionalFlags: string[]) = let errors = getDiagnostics fileContents diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj index d21d6e3c6eb..e1aae0a95a1 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj +++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj @@ -6,6 +6,7 @@ false false true + $(NoWarn);FS3511 @@ -44,6 +45,10 @@ + + + + diff --git a/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs index 48276422bdd..49955dedbb4 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/FsxCompletionProviderTests.fs @@ -4,10 +4,12 @@ namespace FSharp.Editor.Tests open System open System.Collections.Generic +open System.Threading open Xunit open Microsoft.VisualStudio.FSharp.Editor open FSharp.Compiler.CodeAnalysis open FSharp.Editor.Tests.Helpers +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks // AppDomain helper type Worker() = @@ -31,10 +33,9 @@ type Worker() = let actual = let x = FSharpCompletionProvider.ProvideCompletionsAsyncAux(document, caretPosition, (fun _ -> [])) - |> Async.RunSynchronously + |> CancellableTask.start CancellationToken.None - x - |> Option.defaultValue (ResizeArray()) + x.Result |> Seq.toList // sort items as Roslyn do - by `SortText` |> List.sortBy (fun x -> x.SortText) diff --git a/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs index 6088e79e9e6..f52ef63b53c 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs @@ -10,6 +10,7 @@ open Microsoft.VisualStudio.FSharp.Editor open Microsoft.IO open FSharp.Editor.Tests.Helpers open Microsoft.CodeAnalysis.Text +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks type HelpContextServiceTests() = let getMarkers (source: string) = @@ -52,14 +53,18 @@ type HelpContextServiceTests() = CancellationToken.None ) - FSharpHelpContextService.GetHelpTerm(document, span, classifiedSpans) - |> Async.RunSynchronously + let task = + FSharpHelpContextService.GetHelpTerm(document, span, classifiedSpans) + |> CancellableTask.start CancellationToken.None + + task.Result ] let equalLength = (expectedKeywords.Length = res.Length) Assert.True(equalLength) for (exp, res) in List.zip expectedKeywords res do + let exp = Option.defaultValue "" exp Assert.Equal(exp, res) let TestF1Keywords (expectedKeywords, lines) = diff --git a/vsintegration/tests/FSharp.Editor.Tests/Hints/HintTestFramework.fs b/vsintegration/tests/FSharp.Editor.Tests/Hints/HintTestFramework.fs index 9a0111810dd..d252d678d2f 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/Hints/HintTestFramework.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/Hints/HintTestFramework.fs @@ -7,6 +7,8 @@ open Microsoft.VisualStudio.FSharp.Editor open Microsoft.VisualStudio.FSharp.Editor.Hints open Hints open FSharp.Editor.Tests.Helpers +open System.Threading +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks module HintTestFramework = @@ -60,21 +62,24 @@ module HintTestFramework = project.Documents let getHints (document: Document) hintKinds = - async { - let! ct = Async.CancellationToken - - let getTooltip hint = - async { - let! roslynTexts = hint.GetTooltip document - return roslynTexts |> Seq.map (fun roslynText -> roslynText.Text) |> String.concat "" - } - - let! sourceText = document.GetTextAsync ct |> Async.AwaitTask - let! hints = HintService.getHintsForDocument sourceText document hintKinds "test" ct - let! tooltips = hints |> Seq.map getTooltip |> Async.Parallel - return tooltips |> Seq.zip hints |> Seq.map convert - } - |> Async.RunSynchronously + let task = + cancellableTask { + let! ct = CancellableTask.getCurrentCancellationToken () + + let getTooltip hint = + async { + let! roslynTexts = hint.GetTooltip document + return roslynTexts |> Seq.map (fun roslynText -> roslynText.Text) |> String.concat "" + } + + let! sourceText = document.GetTextAsync ct |> Async.AwaitTask + let! hints = HintService.getHintsForDocument sourceText document hintKinds "test" ct + let! tooltips = hints |> Seq.map getTooltip |> Async.Parallel + return tooltips |> Seq.zip hints |> Seq.map convert + } + |> CancellableTask.start CancellationToken.None + + task.Result let getTypeHints document = getHints document (set [ HintKind.TypeHint ]) diff --git a/vsintegration/tests/FSharp.Editor.Tests/QuickInfoProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/QuickInfoProviderTests.fs index 91e504577f8..30d58f70965 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/QuickInfoProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/QuickInfoProviderTests.fs @@ -3,6 +3,7 @@ namespace FSharp.Editor.Tests open System +open System.Threading open Xunit open FSharp.Compiler.EditorServices open FSharp.Compiler.CodeAnalysis @@ -10,6 +11,7 @@ open Microsoft.VisualStudio.FSharp.Editor open Microsoft.VisualStudio.FSharp.Editor.QuickInfo open FSharp.Editor.Tests.Helpers open FSharp.Test +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks type public AssemblyResolverTestFixture() = @@ -94,8 +96,11 @@ module QuickInfoProviderTests = let caretPosition = programText.IndexOf(symbol) + symbol.Length - 1 let quickInfo = - FSharpAsyncQuickInfoSource.TryGetToolTip(document, caretPosition) - |> Async.RunSynchronously + let task = + FSharpAsyncQuickInfoSource.TryGetToolTip(document, caretPosition) + |> CancellableTask.start CancellationToken.None + + task.Result let actual = quickInfo diff --git a/vsintegration/tests/FSharp.Editor.Tests/QuickInfoTests.fs b/vsintegration/tests/FSharp.Editor.Tests/QuickInfoTests.fs index 706d79c59bf..e5188b5eef8 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/QuickInfoTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/QuickInfoTests.fs @@ -2,10 +2,12 @@ namespace FSharp.Editor.Tests +open System.Threading open Microsoft.VisualStudio.FSharp.Editor open Microsoft.VisualStudio.FSharp.Editor.QuickInfo open Xunit open FSharp.Editor.Tests.Helpers +open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks module QuickInfo = open FSharp.Compiler.EditorServices @@ -31,7 +33,11 @@ module QuickInfo = let document = RoslynTestHelpers.CreateSolution(code) |> RoslynTestHelpers.GetSingleDocument - let! _, _, _, tooltip = FSharpAsyncQuickInfoSource.TryGetToolTip(document, caretPosition) + let! _, _, _, tooltip = + FSharpAsyncQuickInfoSource.TryGetToolTip(document, caretPosition) + |> CancellableTask.start CancellationToken.None + |> Async.AwaitTask + return tooltip } |> Async.RunSynchronously diff --git a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj index 1ee9d2b4a4a..15c15bf400b 100644 --- a/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj +++ b/vsintegration/tests/UnitTests/VisualFSharp.UnitTests.fsproj @@ -104,6 +104,7 @@ +