From af2b63a3db0e46ac10dcca1f9c2aae2739ece335 Mon Sep 17 00:00:00 2001 From: nojaf Date: Tue, 27 Jul 2021 20:22:47 +0200 Subject: [PATCH] Add formatSelection. --- src/Fantomas.Client/Contracts.fs | 42 +++++++++-- src/Fantomas.Client/LSPFantomasService.fs | 69 +++++++++---------- .../DaemonTests.fs | 51 +++++++++++--- src/Fantomas.CoreGlobalTool/Daemon.fs | 32 ++++++++- 4 files changed, 136 insertions(+), 58 deletions(-) diff --git a/src/Fantomas.Client/Contracts.fs b/src/Fantomas.Client/Contracts.fs index 456ee39f0f..54ad2620da 100644 --- a/src/Fantomas.Client/Contracts.fs +++ b/src/Fantomas.Client/Contracts.fs @@ -12,6 +12,9 @@ module Methods = [] let FormatDocument = "fantomas/formatDocument" + [] + let FormatSelection = "fantomas/formatSelection" + [] let Configuration = "fantomas/configuration" @@ -23,10 +26,34 @@ type FormatDocumentRequest = /// Overrides the found .editorconfig. Config: IReadOnlyDictionary option } -type FormatSourceRange = - class +type FormatDocumentResponse = { Formatted: string } + +type FormatSelectionRequest = + { SourceCode: string + /// File path will be used to identify the .editorconfig options + /// Unless the configuration is passed + FilePath: string + /// Overrides the found .editorconfig. + Config: IReadOnlyDictionary option + /// Range follows the same semantics of the FSharp Compiler Range type. + Range: FormatSelectionRange } + +and FormatSelectionRange = + struct + val StartLine: int + val StartColumn: int + val EndLine: int + val EndColumn: int + + new(startLine: int, startColumn: int, endLine: int, endColumn: int) = + { StartLine = startLine + StartColumn = startColumn + EndLine = endLine + EndColumn = endColumn } end +type FormatSelectionResponse = { Formatted: string } + type FantomasOption = { Type: string; DefaultValue: string } type ConfigurationResponse = @@ -35,15 +62,16 @@ type ConfigurationResponse = type VersionResponse = { Version: string } -type FormatDocumentResponse = { Formatted: string } - type FantomasService = interface inherit IDisposable - abstract member Version : CancellationToken option -> Async + abstract member VersionAsync : ?cancellationToken: CancellationToken -> Async abstract member FormatDocumentAsync : - FormatDocumentRequest * CancellationToken option -> Async + FormatDocumentRequest * ?cancellationToken: CancellationToken -> Async + + abstract member FormatSelectionAsync : + FormatSelectionRequest * ?cancellationToken: CancellationToken -> Async - abstract member Configuration : CancellationToken option -> Async + abstract member ConfigurationAsync : ?cancellationToken: CancellationToken -> Async end diff --git a/src/Fantomas.Client/LSPFantomasService.fs b/src/Fantomas.Client/LSPFantomasService.fs index 726e669c6e..635da81941 100644 --- a/src/Fantomas.Client/LSPFantomasService.fs +++ b/src/Fantomas.Client/LSPFantomasService.fs @@ -26,50 +26,43 @@ type LSPFantomasService(daemonStartInfo: ProcessStartInfo) = client.Dispose() daemonProcess.Dispose() - member _.Version(?cancellationToken: CancellationToken) : Async = - async { - let ct = - orDefaultCancellationToken cancellationToken - - return! - client.InvokeWithCancellationAsync(Methods.Version, cancellationToken = ct) - |> Async.AwaitTask - } + member _.VersionAsync(?cancellationToken: CancellationToken) : Async = + client.InvokeWithCancellationAsync( + Methods.Version, + cancellationToken = orDefaultCancellationToken cancellationToken + ) + |> Async.AwaitTask member _.FormatDocumentAsync ( formatDocumentOptions: FormatDocumentRequest, ?cancellationToken: CancellationToken ) : Async = - async { - let ct = - orDefaultCancellationToken cancellationToken - - let! formatDocumentResponse = - client.InvokeWithParameterObjectAsync( - Methods.FormatDocument, - argument = formatDocumentOptions, - cancellationToken = ct - ) - |> Async.AwaitTask - - return formatDocumentResponse - } - - member _.Configuration(?cancellationToken: CancellationToken) : Async = - async { - let ct = - orDefaultCancellationToken cancellationToken - - let! configurationResponse = - client.InvokeWithCancellationAsync( - Methods.Configuration, - cancellationToken = ct - ) - |> Async.AwaitTask - - return configurationResponse - } + client.InvokeWithParameterObjectAsync( + Methods.FormatDocument, + argument = formatDocumentOptions, + cancellationToken = orDefaultCancellationToken cancellationToken + ) + |> Async.AwaitTask + + member _.FormatSelectionAsync + ( + formatSelectionRequest: FormatSelectionRequest, + ?cancellationToken: CancellationToken + ) = + client.InvokeWithParameterObjectAsync( + Methods.FormatSelection, + argument = formatSelectionRequest, + cancellationToken = orDefaultCancellationToken cancellationToken + ) + |> Async.AwaitTask + + member _.ConfigurationAsync(?cancellationToken: CancellationToken) : Async = + client.InvokeWithCancellationAsync( + Methods.Configuration, + cancellationToken = orDefaultCancellationToken cancellationToken + ) + |> Async.AwaitTask let createForWorkingDirectory (workingDirectory: string) : Result = if not (Directory.Exists workingDirectory) then diff --git a/src/Fantomas.CoreGlobalTool.Tests/DaemonTests.fs b/src/Fantomas.CoreGlobalTool.Tests/DaemonTests.fs index a2e3499b1b..7a65662ec5 100644 --- a/src/Fantomas.CoreGlobalTool.Tests/DaemonTests.fs +++ b/src/Fantomas.CoreGlobalTool.Tests/DaemonTests.fs @@ -7,8 +7,8 @@ open Fantomas open Fantomas.Client.Contracts open Fantomas.Client.LSPFantomasService -let private assertFormatted (response: FormatDocumentResponse) (expected: string) : unit = - String.normalizeNewLine response.Formatted +let private assertFormatted (actual: string) (expected: string) : unit = + String.normalizeNewLine actual |> should equal (String.normalizeNewLine expected) [] @@ -16,7 +16,7 @@ let ``compare the version with the public api`` () = async { let processStart = getFantomasToolStartInfo "--daemon" use client = new LSPFantomasService(processStart) - let! { Version = version } = (client :> FantomasService).Version None + let! { Version = version } = (client :> FantomasService).VersionAsync() version |> should equal (CodeFormatter.GetVersion()) @@ -37,10 +37,10 @@ let ``format document`` () = let! response = (client :> FantomasService) - .FormatDocumentAsync(request, None) + .FormatDocumentAsync(request) assertFormatted - response + response.Formatted "module Foobar " } @@ -63,10 +63,10 @@ let ``format document respecting .editorconfig file`` () = let! response = (client :> FantomasService) - .FormatDocumentAsync(request, None) + .FormatDocumentAsync(request) assertFormatted - response + response.Formatted "module Foo let a = // @@ -92,10 +92,10 @@ let ``custom configuration has precedence over .editorconfig file`` () = let! response = (client :> FantomasService) - .FormatDocumentAsync(request, None) + .FormatDocumentAsync(request) assertFormatted - response + response.Formatted "module Foo let a = // @@ -103,6 +103,36 @@ let a = // " } +[] +let ``format selection`` () = + async { + let sourceCode = + """module Foo + +let x = 4 +let y = 5 +""" + + let processStart = getFantomasToolStartInfo "--daemon" + use client = new LSPFantomasService(processStart) + use _codeFile = new TemporaryFileCodeSample(sourceCode) + + let request: FormatSelectionRequest = + let range = FormatSelectionRange(3, 0, 3, 16) + + { SourceCode = sourceCode + FilePath = "tmp.fsx" // codeFile.Filename + Config = None + Range = range } + + let! response = + (client :> FantomasService) + .FormatSelectionAsync(request) + + assertFormatted response.Formatted "let x = 4\n" + } + + [] let ``find fantomas tool from working directory`` () = async { @@ -125,8 +155,7 @@ let ``find fantomas tool from working directory`` () = .FormatDocumentAsync( { SourceCode = originalCode FilePath = filePath - Config = None }, - None + Config = None } ) let formattedCode = formattedResponse diff --git a/src/Fantomas.CoreGlobalTool/Daemon.fs b/src/Fantomas.CoreGlobalTool/Daemon.fs index 857528753b..5b3a3994ac 100644 --- a/src/Fantomas.CoreGlobalTool/Daemon.fs +++ b/src/Fantomas.CoreGlobalTool/Daemon.fs @@ -3,11 +3,13 @@ open System open System.Diagnostics open System.IO +open System.Threading open System.Threading.Tasks +open FSharp.Compiler.Text.Range +open FSharp.Compiler.Text.Pos +open StreamJsonRpc open Fantomas open Fantomas.SourceOrigin -open StreamJsonRpc -open System.Threading open Fantomas.FormatConfig open Fantomas.Extras.EditorConfig open Fantomas.Client.Contracts @@ -55,6 +57,32 @@ type FantomasDaemon(sender: Stream, reader: Stream) as this = CodeFormatterImpl.sharedChecker.Value ) + return ({ Formatted = formatted }: FormatDocumentResponse) + } + |> Async.StartAsTask + + [] + member _.FormatSelectionAsync(request: FormatSelectionRequest) : Task = + async { + let config = + match request.Config with + | Some configProperties -> parseOptionsFromEditorConfig configProperties + | None -> readConfiguration request.FilePath + + let range = + let r = request.Range + mkRange request.FilePath (mkPos r.StartLine r.StartColumn) (mkPos r.EndLine r.EndColumn) + + let! formatted = + CodeFormatter.FormatSelectionAsync( + request.FilePath, + range, + SourceString request.SourceCode, + config, + CodeFormatterImpl.createParsingOptionsFromFile request.FilePath, + CodeFormatterImpl.sharedChecker.Value + ) + return { Formatted = formatted } } |> Async.StartAsTask