From 08b871146e6c9d5368c1515dba1dd4f14c9b6548 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Mon, 6 May 2024 10:26:46 -0400 Subject: [PATCH] Main to nightly (#1284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Shift multiline paren contents less aggressively (#1242) * Shift multiline paren contents less aggressively * Make it actually work * Disambiguate AsSpan overload * Add some code fixes for type mismatch. (#1250) * Migrate FAKE to Fun.Build (#1256) * Migrate FAKE to Fun.Build * Add default Build pipeline. * Purge it with fire (#1255) * Bump analyzers and Fantomas (#1257) * Add empty, disabled tests for go-to-def on C# symbol scenario * fix unicode characters in F# compiler diagnostic messages (#1265) * fix unicode chars in F# compiler diagnostic messages * fix typo in ShadowedTimeouts focused tests * fixup! fix unicode chars in F# compiler diagnostic messages * remove focused tests... * remove debug prints Co-authored-by: Jimmy Byrd --------- Co-authored-by: Jimmy Byrd * - remove an ignored call to protocolRangeToRange (#1266) - remove an ignored instance of StreamJsonRpcTracingStrategy * Place XML doc lines before any attribute lists (#1267) * Don't generate params for explicit getters/setters as they are flagged invalid by the compiler (#1268) * bump ProjInfo to the next version to get support for loading broken projects and loading traversal projects (#1270) * only allow one GetProjectOptionsFromScript at a time (#1275) https://github.com/ionide/ionide-vscode-fsharp/issues/2005 * changelog for v0.72.0 * changelog for v0.72.1 * Use actualRootPath instead of p.RootPath when peeking workspaces. (#1278) This fix the issue where rootUri was ignored when using AutomaticWorkspaceInit. * changelog for v0.72.2 * Add support for Cancel a Work Done Progress (#1274) * Add support for Cancel WorkDoneProgress * Fix up saving cancellation * package the tool for .NET 8 as well (#1281) * update changelogs * Adds basic OTel Metric support to fsautocomplete (#1283) --------- Co-authored-by: Brian Rourke Boll Co-authored-by: Florian Verdonck Co-authored-by: Krzysztof Cieślak Co-authored-by: MrLuje Co-authored-by: dawe Co-authored-by: Chet Husk Co-authored-by: oupson <31827294+oupson@users.noreply.github.com> Co-authored-by: Chet Husk --- .github/workflows/release.yml | 3 +- CHANGELOG.md | 11 ++ Directory.Build.props | 2 +- paket.dependencies | 3 +- paket.lock | 4 +- .../CompilerServiceInterface.fs | 1 - .../LspServers/AdaptiveFSharpLspServer.fs | 14 ++- .../LspServers/AdaptiveServerState.fs | 114 +++++++++++++++--- .../LspServers/AdaptiveServerState.fsi | 8 ++ src/FsAutoComplete/LspServers/Common.fs | 2 +- .../LspServers/FSharpLspClient.fs | 37 ++++-- .../LspServers/FSharpLspClient.fsi | 34 +++++- src/FsAutoComplete/Parser.fs | 26 +++- src/FsAutoComplete/paket.references | 1 + .../CompletionTests.fs | 68 +++++------ .../EmptyFileTests.fs | 8 +- 16 files changed, 254 insertions(+), 82 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24794d043..14cc54943 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,6 +16,7 @@ jobs: uses: actions/setup-dotnet@v2 with: dotnet-version: | + 8.0.x 7.0.x 6.0.x include-prerelease: true @@ -29,7 +30,7 @@ jobs: - name: Run Build run: dotnet pack -c Release -o ./bin env: - BuildNet7: true + BuildNet8: true - name: Get Changelog Entry id: changelog_reader diff --git a/CHANGELOG.md b/CHANGELOG.md index ffbb4edb9..58ee17d87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [0.72.3] - 2024-05-05 + +### Added + +* [FSAC publishes a net8.0 TFM version of the tool as well, to prevent issues when running across TargetFrameworks](https://github.com/ionide/FsAutoComplete/pull/1281) +* [Long-running actions like typechecking specific files can now be cancelled by users](https://github.com/ionide/FsAutoComplete/pull/1274) (thanks @TheAngryByrd) + +### Fixed + +* [Fix restoring multiple script file NuGet dependencies in parallel](https://github.com/ionide/FsAutoComplete/pull/1275) (thanks @TheAngryByrd) + ## [0.72.2] - 2024-04-30 ### Fixed diff --git a/Directory.Build.props b/Directory.Build.props index 01998466f..ec42492dd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,7 +10,7 @@ $(NoWarn);57 $(WarnOn);1182 - + $(NoWarn);FS0044 $(WarnOn);3390 true $(MSBuildThisFileDirectory)CHANGELOG.md diff --git a/paket.dependencies b/paket.dependencies index d36ecdbed..5fe9901fc 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -52,10 +52,11 @@ nuget Expecto.Diff nuget YoloDev.Expecto.TestSdk nuget AltCover nuget GitHubActionsTestLogger -nuget Ionide.LanguageServerProtocol >= 0.4.20 +nuget Ionide.LanguageServerProtocol >= 0.4.23 nuget Microsoft.Extensions.Caching.Memory nuget OpenTelemetry.Api >= 1.3.2 nuget OpenTelemetry.Exporter.OpenTelemetryProtocol >= 1.3.2 # 1.4 bumps to 7.0 versions of System.Diagnostics libs, so can't use it +nuget OpenTelemetry.Instrumentation.Runtime nuget LinkDotNet.StringBuilder 1.18.0 nuget CommunityToolkit.HighPerformance nuget System.Security.Cryptography.Pkcs 6.0.4 diff --git a/paket.lock b/paket.lock index 426327504..b66d22963 100644 --- a/paket.lock +++ b/paket.lock @@ -119,7 +119,7 @@ NUGET System.Reflection.Metadata (>= 5.0) Ionide.Analyzers (0.10) Ionide.KeepAChangelog.Tasks (0.1.8) - copy_local: true - Ionide.LanguageServerProtocol (0.4.20) + Ionide.LanguageServerProtocol (0.4.23) FSharp.Core (>= 6.0) Newtonsoft.Json (>= 13.0.1) StreamJsonRpc (>= 2.16.36) @@ -357,6 +357,8 @@ NUGET Grpc (>= 2.44 < 3.0) - restriction: || (&& (== net6.0) (>= net462)) (&& (== net6.0) (< netstandard2.1)) (&& (== net7.0) (>= net462)) (&& (== net7.0) (< netstandard2.1)) (&& (== net8.0) (>= net462)) (&& (== net8.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net462)) Grpc.Net.Client (>= 2.43 < 3.0) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netstandard2.1)) (== netstandard2.1) OpenTelemetry (>= 1.3.2) + OpenTelemetry.Instrumentation.Runtime (1.0) + OpenTelemetry.Api (>= 1.3 < 2.0) Perfolizer (0.2.1) System.Memory (>= 4.5.3) runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.2) diff --git a/src/FsAutoComplete.Core/CompilerServiceInterface.fs b/src/FsAutoComplete.Core/CompilerServiceInterface.fs index 937d5d090..1c8cc9000 100644 --- a/src/FsAutoComplete.Core/CompilerServiceInterface.fs +++ b/src/FsAutoComplete.Core/CompilerServiceInterface.fs @@ -416,7 +416,6 @@ type FSharpCompilerServiceChecker(hasAnalyzers, typecheckCacheSize, parallelRefe } - member __.ScriptTypecheckRequirementsChanged = scriptTypecheckRequirementsChanged.Publish diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 5a7757b0a..331aca781 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -578,8 +578,8 @@ type AdaptiveFSharpLspServer // Otherwise we'll fail here and our retry logic will come into place do! match p.Context with - | Some({ triggerKind = CompletionTriggerKind.TriggerCharacter } as context) -> - volatileFile.Source.TryGetChar pos = context.triggerCharacter + | Some({ TriggerKind = CompletionTriggerKind.TriggerCharacter } as context) -> + volatileFile.Source.TryGetChar pos = context.TriggerCharacter | _ -> true |> Result.requireTrue $"TextDocumentCompletion was sent before TextDocumentDidChange" @@ -2979,25 +2979,27 @@ type AdaptiveFSharpLspServer override x.Dispose() = disposables.Dispose() - member this.WorkDoneProgressCancel(token: ProgressToken) : Async = + member this.WorkDoneProgressCancel(param: WorkDoneProgressCancelParams) : Async = async { - let tags = [ "ProgressToken", box token ] + let tags = [ "WorkDoneProgressCancelParams", box param ] use trace = fsacActivitySource.StartActivityForType(thisType, tags = tags) try logger.info ( Log.setMessage "WorkDoneProgressCancel Request: {params}" - >> Log.addContextDestructured "params" token + >> Log.addContextDestructured "params" param.token ) + state.CancelServerProgress param.token + with e -> trace |> Tracing.recordException e logException e (Log.setMessage "WorkDoneProgressCancel Request Errored {p}" - >> Log.addContextDestructured "token" token) + >> Log.addContextDestructured "token" param.token) return () } diff --git a/src/FsAutoComplete/LspServers/AdaptiveServerState.fs b/src/FsAutoComplete/LspServers/AdaptiveServerState.fs index ad0ba54f6..3d5c7f6fe 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveServerState.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveServerState.fs @@ -3,6 +3,7 @@ namespace FsAutoComplete.Lsp open System open System.IO open System.Threading +open System.Collections.Generic open FsAutoComplete open FsAutoComplete.CodeFix open FsAutoComplete.Logging @@ -39,6 +40,68 @@ open FSharp.Compiler.Syntax open FsAutoComplete.ProjectWorkspace +/// Handle tracking in-flight ServerProgressReport and allow cancellation of actions if a client decides to. +type ServerProgressLookup() = + // This is a dictionary of all the progress reports that are currently active + // Use a WeakReference to avoid memory leaks + let progressTable = Dictionary>() + + // Although it's small data, we don't want to keep it around forever + let timer = + new Timers.Timer((TimeSpan.FromMinutes 1.).TotalMilliseconds, AutoReset = true) + + let timerSub = + timer.Elapsed.Subscribe(fun _ -> + lock progressTable (fun () -> + let derefs = + progressTable + |> Seq.filter (fun (KeyValue(_, reporters)) -> + match reporters.TryGetTarget() with + | (true, _) -> false + | _ -> true) + |> Seq.toList + + for (KeyValue(tokens, _)) in derefs do + progressTable.Remove(tokens) |> ignore)) + + + /// Creates a ServerProgressReport and keeps track of it. + /// The FSharpLspClient to communicate to the client with. + /// Optional token. It will be generated otherwise. + /// Informs that the ServerProgressReport is cancellable to the client. + /// + member x.CreateProgressReport(lspClient: FSharpLspClient, ?token: ProgressToken, ?cancellable: bool) = + let progress = + new ServerProgressReport(lspClient, ?token = token, ?cancellableDefault = cancellable) + + lock progressTable (fun () -> + progressTable.Add(progress.ProgressToken, new WeakReference(progress))) + + progress + + + /// Signal a ServerProgressReport to Cancel it's CancellationTokenSource. + /// The ProgressToken used to identify the ServerProgressReport + /// + /// + /// See LSP Spec on WorkDoneProgress Cancel for more information. + /// + member x.Cancel(token: ProgressToken) = + lock progressTable (fun () -> + match progressTable.TryGetValue(token) with + | true, weakRef -> + match weakRef.TryGetTarget() with + | true, progress -> + progress.Cancel() + progressTable.Remove(token) |> ignore + | _ -> () + | _ -> ()) + + interface IDisposable with + member x.Dispose() = + timerSub.Dispose() + timer.Dispose() + [] type WorkspaceChosen = | Projs of HashSet> @@ -101,6 +164,8 @@ type AdaptiveState let logger = LogProvider.getLoggerFor () let thisType = typeof let disposables = new Disposables.CompositeDisposable() + let progressLookup = new ServerProgressLookup() + do disposables.Add progressLookup let projectSelector = cval (FindFirstProject()) @@ -324,10 +389,12 @@ type AdaptiveState let checkUnusedOpens = asyncEx { try - use progress = new ServerProgressReport(lspClient) + use progress = progressLookup.CreateProgressReport(lspClient, cancellable = true) do! progress.Begin($"Checking unused opens {fileName}...", message = filePathUntag) - let! unused = UnusedOpens.getUnusedOpens (tyRes.GetCheckResults, getSourceLine) + let! unused = + UnusedOpens.getUnusedOpens (tyRes.GetCheckResults, getSourceLine) + |> Async.withCancellation progress.CancellationToken let! ct = Async.CancellationToken notifications.Trigger(NotificationEvent.UnusedOpens(filePath, (unused |> List.toArray), file.Version), ct) @@ -338,11 +405,15 @@ type AdaptiveState let checkUnusedDeclarations = asyncEx { try - use progress = new ServerProgressReport(lspClient) + use progress = progressLookup.CreateProgressReport(lspClient, cancellable = true) do! progress.Begin($"Checking unused declarations {fileName}...", message = filePathUntag) let isScript = Utils.isAScript (filePathUntag) - let! unused = UnusedDeclarations.getUnusedDeclarations (tyRes.GetCheckResults, isScript) + + let! unused = + UnusedDeclarations.getUnusedDeclarations (tyRes.GetCheckResults, isScript) + |> Async.withCancellation progress.CancellationToken + let unused = unused |> Seq.toArray let! ct = Async.CancellationToken @@ -354,10 +425,13 @@ type AdaptiveState let checkSimplifiedNames = asyncEx { try - use progress = new ServerProgressReport(lspClient) + use progress = progressLookup.CreateProgressReport(lspClient, cancellable = true) do! progress.Begin($"Checking simplifying of names {fileName}...", message = filePathUntag) - let! simplified = SimplifyNames.getSimplifiableNames (tyRes.GetCheckResults, getSourceLine) + let! simplified = + SimplifyNames.getSimplifiableNames (tyRes.GetCheckResults, getSourceLine) + |> Async.withCancellation progress.CancellationToken + let simplified = Array.ofSeq simplified let! ct = Async.CancellationToken notifications.Trigger(NotificationEvent.SimplifyNames(filePath, simplified, file.Version), ct) @@ -368,7 +442,7 @@ type AdaptiveState let checkUnnecessaryParentheses = asyncEx { try - use progress = new ServerProgressReport(lspClient) + use progress = progressLookup.CreateProgressReport(lspClient) do! progress.Begin($"Checking for unnecessary parentheses {fileName}...", message = filePathUntag) let unnecessaryParentheses = @@ -1520,9 +1594,9 @@ type AdaptiveState >> Log.addContextDestructured "date" (file.LastTouched) ) - let! ct = Async.CancellationToken - use progressReport = new ServerProgressReport(lspClient) + use progressReport = + progressLookup.CreateProgressReport(lspClient, cancellable = true) let simpleName = Path.GetFileName(UMX.untag file.Source.FileName) do! progressReport.Begin($"Typechecking {simpleName}", message = $"{file.Source.FileName}") @@ -1542,6 +1616,8 @@ type AdaptiveState ) + let! ct = Async.CancellationToken + notifications.Trigger(NotificationEvent.FileParsed(file.Source.FileName), ct) match result with @@ -2219,8 +2295,6 @@ type AdaptiveState let! projs = getProjectOptionsForFile sourceFilePath |> AsyncAVal.forceAsync - let rootToken = sourceFilePath |> getOpenFileTokenOrDefault - let projs = projs |> Result.toOption @@ -2236,7 +2310,8 @@ type AdaptiveState let mutable checksCompleted = 0 - use progressReporter = new ServerProgressReport(lspClient) + use progressReporter = + progressLookup.CreateProgressReport(lspClient, cancellable = true) let percentage numerator denominator = if denominator = 0 then @@ -2254,6 +2329,7 @@ type AdaptiveState && file.Contains "AssemblyAttributes.fs" |> not) let checksToPerformLength = innerChecks.Length + let rootToken = sourceFilePath |> getOpenFileTokenOrDefault innerChecks |> Array.map (fun (snap, file) -> @@ -2262,14 +2338,17 @@ type AdaptiveState use joinedToken = if file = sourceFilePath then // dont reset the token for the incoming file as it would cancel the whole operation - CancellationTokenSource.CreateLinkedTokenSource(rootToken) + CancellationTokenSource.CreateLinkedTokenSource(rootToken, progressReporter.CancellationToken) else // only cancel other files // If we have multiple saves from separate root files we want only one to be running - let token = resetCancellationToken file None // Dont dispose, we're a renter not an owner + let fileToken = resetCancellationToken file None // and join with the root token as well since we want to cancel the whole operation if the root files changes - CancellationTokenSource.CreateLinkedTokenSource(rootToken, token) - // CancellationTokenSource.CreateLinkedTokenSource(rootToken) + CancellationTokenSource.CreateLinkedTokenSource( + rootToken, + fileToken, + progressReporter.CancellationToken + ) try let! _ = @@ -2440,6 +2519,9 @@ type AdaptiveState member x.GlyphToSymbolKind = glyphToSymbolKind |> AVal.force + member x.CancelServerProgress(progressToken: ProgressToken) = progressLookup.Cancel progressToken + + interface IDisposable with member this.Dispose() = diff --git a/src/FsAutoComplete/LspServers/AdaptiveServerState.fsi b/src/FsAutoComplete/LspServers/AdaptiveServerState.fsi index abb0156b2..9ee4f4a73 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveServerState.fsi +++ b/src/FsAutoComplete/LspServers/AdaptiveServerState.fsi @@ -119,4 +119,12 @@ type AdaptiveState = member GetDeclarations: filename: string -> Async> member GetAllDeclarations: unit -> Async<(string * NavigationTopLevelDeclaration array) array> member GlyphToSymbolKind: (FSharpGlyph -> SymbolKind option) + /// + /// Signals the server to cancel an operation that is associated with the given progress token. + /// + /// + /// + /// See LSP Spec on WorkDoneProgress Cancel for more information. + /// + member CancelServerProgress: progressToken: ProgressToken -> unit interface IDisposable diff --git a/src/FsAutoComplete/LspServers/Common.fs b/src/FsAutoComplete/LspServers/Common.fs index 731924011..bcbc95eb9 100644 --- a/src/FsAutoComplete/LspServers/Common.fs +++ b/src/FsAutoComplete/LspServers/Common.fs @@ -159,7 +159,7 @@ module Async = let! ct2 = Async.CancellationToken use cts = CancellationTokenSource.CreateLinkedTokenSource(ct, ct2) let tcs = new TaskCompletionSource<'a>() - use _reg = cts.Token.Register(fun () -> tcs.TrySetCanceled() |> ignore) + use _reg = cts.Token.Register(fun () -> tcs.TrySetCanceled(cts.Token) |> ignore) let a = async { diff --git a/src/FsAutoComplete/LspServers/FSharpLspClient.fs b/src/FsAutoComplete/LspServers/FSharpLspClient.fs index 477e1f6c3..08bbd6a2d 100644 --- a/src/FsAutoComplete/LspServers/FSharpLspClient.fs +++ b/src/FsAutoComplete/LspServers/FSharpLspClient.fs @@ -83,21 +83,31 @@ type FSharpLspClient(sendServerNotification: ClientNotificationSender, sendServe -type ServerProgressReport(lspClient: FSharpLspClient, ?token: ProgressToken) = +type ServerProgressReport(lspClient: FSharpLspClient, ?token: ProgressToken, ?cancellableDefault: bool) = let mutable canReportProgress = false let mutable endSent = false let locker = new SemaphoreSlim(1, 1) + let cts = new CancellationTokenSource() + + member val ProgressToken = defaultArg token (ProgressToken.Second((Guid.NewGuid().ToString()))) + + member val CancellationToken = cts.Token + + member x.Cancel() = + try + cts.Cancel() + with _ -> + () - member val Token = defaultArg token (ProgressToken.Second((Guid.NewGuid().ToString()))) member x.Begin(title, ?cancellable, ?message, ?percentage) = cancellableTask { use! __ = fun (ct: CancellationToken) -> locker.LockAsync(ct) if not endSent then - let! result = lspClient.WorkDoneProgressCreate x.Token + let! result = lspClient.WorkDoneProgressCreate x.ProgressToken match result with | Ok() -> canReportProgress <- true @@ -106,10 +116,10 @@ type ServerProgressReport(lspClient: FSharpLspClient, ?token: ProgressToken) = if canReportProgress then do! lspClient.Progress( - x.Token, + x.ProgressToken, WorkDoneProgressBegin.Create( title, - ?cancellable = cancellable, + ?cancellable = (cancellable |> Option.orElse cancellableDefault), ?message = message, ?percentage = percentage ) @@ -123,8 +133,12 @@ type ServerProgressReport(lspClient: FSharpLspClient, ?token: ProgressToken) = if canReportProgress && not endSent then do! lspClient.Progress( - x.Token, - WorkDoneProgressReport.Create(?cancellable = cancellable, ?message = message, ?percentage = percentage) + x.ProgressToken, + WorkDoneProgressReport.Create( + ?cancellable = (cancellable |> Option.orElse cancellableDefault), + ?message = message, + ?percentage = percentage + ) ) } @@ -134,12 +148,17 @@ type ServerProgressReport(lspClient: FSharpLspClient, ?token: ProgressToken) = let stillNeedsToSend = canReportProgress && not endSent if stillNeedsToSend then - do! lspClient.Progress(x.Token, WorkDoneProgressEnd.Create(?message = message)) + do! lspClient.Progress(x.ProgressToken, WorkDoneProgressEnd.Create(?message = message)) endSent <- true } interface IAsyncDisposable with - member x.DisposeAsync() = task { do! x.End () CancellationToken.None } |> ValueTask + member x.DisposeAsync() = + task { + cts.Dispose() + do! x.End () CancellationToken.None + } + |> ValueTask interface IDisposable with member x.Dispose() = (x :> IAsyncDisposable).DisposeAsync() |> ignore diff --git a/src/FsAutoComplete/LspServers/FSharpLspClient.fsi b/src/FsAutoComplete/LspServers/FSharpLspClient.fsi index 984844d7c..b9a182f97 100644 --- a/src/FsAutoComplete/LspServers/FSharpLspClient.fsi +++ b/src/FsAutoComplete/LspServers/FSharpLspClient.fsi @@ -5,6 +5,7 @@ open Ionide.LanguageServerProtocol.Server open Ionide.LanguageServerProtocol.Types open FsAutoComplete.LspHelpers open System +open System.Threading open IcedTasks type FSharpLspClient = @@ -34,11 +35,40 @@ type FSharpLspClient = override WorkDoneProgressCreate: ProgressToken -> AsyncLspResult override Progress: ProgressToken * 'Progress -> Async +/// +/// Represents a progress report that can be used to report progress to the client. +/// +/// +/// +/// This implements and to allow for the ending of the progress report without explicitly calling End. +/// +/// See LSP Spec on WorkDoneProgress for more information. +/// type ServerProgressReport = - new: lspClient: FSharpLspClient * ?token: ProgressToken -> ServerProgressReport - member Token: ProgressToken + new: lspClient: FSharpLspClient * ?token: ProgressToken * ?cancellableDefault: bool -> ServerProgressReport + /// The progress token to identify the progress report. + member ProgressToken: ProgressToken + /// A cancellation token that can be used to used to cancel actions that are associated with this progress report. + member CancellationToken: CancellationToken + /// Triggers the CancellationToken to cancel. + member Cancel: unit -> unit + /// Used to start reporting progress to the client. + /// Mandatory title of the progress operation + /// Controls if a cancel button should show to allow the user to cancel the long running operation + /// more detailed associated progress message. Contains complementary information to the `title`. + /// percentage to display (value 100 is considered 100%). If not provided infinite progress is assumed member Begin: title: string * ?cancellable: bool * ?message: string * ?percentage: uint -> CancellableTask + /// Report additional progress + /// Controls if a cancel button should show to allow the user to cancel the long running operation + /// more detailed associated progress message. Contains complementary information to the `title`. + /// percentage to display (value 100 is considered 100%). If not provided infinite progress is assumed member Report: ?cancellable: bool * ?message: string * ?percentage: uint -> CancellableTask + /// Signaling the end of a progress reporting is done. + /// more detailed associated progress message. Contains complementary information to the `title`. + /// + /// This will be called if this object is disposed either via Dispose or DisposeAsync. + /// + /// member End: ?message: string -> CancellableTask interface IAsyncDisposable interface IDisposable diff --git a/src/FsAutoComplete/Parser.fs b/src/FsAutoComplete/Parser.fs index 121cd2b6d..6184e133b 100644 --- a/src/FsAutoComplete/Parser.fs +++ b/src/FsAutoComplete/Parser.fs @@ -14,12 +14,14 @@ open FsAutoComplete.Lsp open OpenTelemetry open OpenTelemetry.Resources open OpenTelemetry.Trace +open OpenTelemetry.Metrics module Parser = open FsAutoComplete.Core open System.Diagnostics let mutable tracerProvider = Unchecked.defaultof<_> + let mutable meterProvider = Unchecked.defaultof<_> [] type Pos = { Line: int; Column: int } @@ -212,19 +214,33 @@ module Parser = let configureOTel = Invocation.InvocationMiddleware(fun ctx next -> + + if ctx.ParseResult.GetValueForOption otelTracingOption then + let serviceName = FsAutoComplete.Utils.Tracing.serviceName let version = FsAutoComplete.Utils.Version.info().Version + let resourceBuilder = + ResourceBuilder + .CreateDefault() + .AddService(serviceName = serviceName, serviceVersion = version) + + + meterProvider <- + Sdk + .CreateMeterProviderBuilder() + .AddMeter() + .AddRuntimeInstrumentation() + .SetResourceBuilder(resourceBuilder) + .AddOtlpExporter() + .Build() + tracerProvider <- Sdk .CreateTracerProviderBuilder() .AddSource(serviceName, Tracing.fscServiceName) - .SetResourceBuilder( - ResourceBuilder - .CreateDefault() - .AddService(serviceName = serviceName, serviceVersion = version) - ) + .SetResourceBuilder(resourceBuilder) .AddOtlpExporter() .Build() diff --git a/src/FsAutoComplete/paket.references b/src/FsAutoComplete/paket.references index 4b85e8259..f8bdc0908 100644 --- a/src/FsAutoComplete/paket.references +++ b/src/FsAutoComplete/paket.references @@ -18,6 +18,7 @@ System.CommandLine FSharp.Data.Adaptive Microsoft.Extensions.Caching.Memory OpenTelemetry.Exporter.OpenTelemetryProtocol +OpenTelemetry.Instrumentation.Runtime Microsoft.CodeAnalysis LinkDotNet.StringBuilder CommunityToolkit.HighPerformance diff --git a/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs b/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs index 4dd5203f3..a9a325d78 100644 --- a/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs @@ -41,8 +41,8 @@ let tests state = Position = { Line = 3; Character = 9 } // the '.' in 'Async.' Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams @@ -94,8 +94,8 @@ let tests state = Character = character + 1 } Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams do! c @@ -131,8 +131,8 @@ let tests state = Position = { Line = line; Character = character } // the '.' in 'GetDirectoryName().' Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams @@ -183,8 +183,8 @@ let tests state = Character = character + 1 } Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams do! c @@ -220,8 +220,8 @@ let tests state = Position = { Line = line; Character = character } Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams @@ -272,8 +272,8 @@ let tests state = Character = character + 1 } Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams do! c @@ -309,8 +309,8 @@ let tests state = Position = { Line = line; Character = character } Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams @@ -341,8 +341,8 @@ let tests state = Position = { Line = 6; Character = 5 } // the '.' in 'List.' Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams @@ -369,8 +369,8 @@ let tests state = Position = { Line = 8; Character = 16 } // the '.' in 'List.' Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams @@ -396,8 +396,8 @@ let tests state = Position = { Line = 11; Character = 10 } // after Lis partial type name in Id record field declaration Context = Some - { triggerKind = CompletionTriggerKind.Invoked - triggerCharacter = None } } + { TriggerKind = CompletionTriggerKind.Invoked + TriggerCharacter = None } } let! response = server.TextDocumentCompletion completionParams @@ -428,8 +428,8 @@ let tests state = Position = { Line = 8; Character = 12 } // after the 'L' in 'List.' Context = Some - { triggerKind = CompletionTriggerKind.Invoked - triggerCharacter = None } } + { TriggerKind = CompletionTriggerKind.Invoked + TriggerCharacter = None } } let! response = server.TextDocumentCompletion completionParams @@ -456,8 +456,8 @@ let tests state = Position = { Line = 8; Character = 11 } // before the 'L' in 'List.' Context = Some - { triggerKind = CompletionTriggerKind.Invoked - triggerCharacter = None } } + { TriggerKind = CompletionTriggerKind.Invoked + TriggerCharacter = None } } let! response = server.TextDocumentCompletion completionParams @@ -484,8 +484,8 @@ let tests state = Position = { Line = 3; Character = 9 } // the '.' in 'Async.' Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams |> AsyncResult.map Option.get let ctokMember = response.Items[0] @@ -514,8 +514,8 @@ let tests state = Position = { Line = 23; Character = 8 } // the '.' in 'List.' Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams @@ -542,8 +542,8 @@ let tests state = Position = { Line = 24; Character = 9 } // the '.' in 'List.' Context = Some - { triggerKind = CompletionTriggerKind.TriggerCharacter - triggerCharacter = Some '.' } } + { TriggerKind = CompletionTriggerKind.TriggerCharacter + TriggerCharacter = Some '.' } } let! response = server.TextDocumentCompletion completionParams @@ -1020,8 +1020,8 @@ let fullNameExternalAutocompleteTest state = Position = { Line = line; Character = character } Context = Some - { triggerKind = CompletionTriggerKind.Invoked - triggerCharacter = None } } + { TriggerKind = CompletionTriggerKind.Invoked + TriggerCharacter = None } } let! res = server.TextDocumentCompletion p @@ -1052,8 +1052,8 @@ let fullNameExternalAutocompleteTest state = Position = { Line = 3; Character = 4 } Context = Some - { triggerKind = CompletionTriggerKind.Invoked - triggerCharacter = None } } + { TriggerKind = CompletionTriggerKind.Invoked + TriggerCharacter = None } } let! response = server.TextDocumentCompletion p |> AsyncResult.map Option.get diff --git a/test/FsAutoComplete.Tests.Lsp/EmptyFileTests.fs b/test/FsAutoComplete.Tests.Lsp/EmptyFileTests.fs index a2315e7a1..eeec0d555 100644 --- a/test/FsAutoComplete.Tests.Lsp/EmptyFileTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/EmptyFileTests.fs @@ -54,8 +54,8 @@ let tests state = Position = { Line = 0; Character = 0 } Context = Some - { triggerKind = CompletionTriggerKind.Invoked - triggerCharacter = None } } + { TriggerKind = CompletionTriggerKind.Invoked + TriggerCharacter = None } } match! server.TextDocumentCompletion completionParams with | Ok (Some _) -> failtest "An empty file has empty completions" @@ -83,8 +83,8 @@ let tests state = Position = { Line = 0; Character = 1 } Context = Some - { triggerKind = CompletionTriggerKind.Invoked - triggerCharacter = None } + { TriggerKind = CompletionTriggerKind.Invoked + TriggerCharacter = None } } |> Async.StartChild let! compilerResults = waitForCompilerDiagnosticsForFile "EmptyFile.fsx" events |> Async.StartChild