From cb6f0e5dca8b6a5fb36fd67febf6e2bf12501a71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Jun 2025 18:03:35 +0000 Subject: [PATCH 1/7] Initial plan for issue From 5f326fce6f0fa92fe2e03b70e63de107835140eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Jun 2025 18:25:51 +0000 Subject: [PATCH 2/7] Implement basic completion handler for F# LSP server Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- .../Common/CapabilitiesManager.fs | 4 + .../FSharpLanguageServerConfig.fs | 2 + .../Handlers/LanguageFeaturesHandler.fs | 76 +++++++++++++++++++ .../Protocol.fs | 34 +++++++++ 4 files changed, 116 insertions(+) diff --git a/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs b/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs index 556a1d96ed..85476b3d7c 100644 --- a/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs +++ b/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs @@ -33,6 +33,10 @@ type CapabilitiesManager(config: FSharpLanguageServerConfig, scOverrides: IServe WorkspaceDiagnostics = true )), //CompletionProvider = CompletionOptions(TriggerCharacters = [| "."; " " |], ResolveProvider = true, WorkDoneProgress = true), + CompletionProvider = + addIf + config.EnabledFeatures.Completion + (CompletionOptions(TriggerCharacters = [| "." |], ResolveProvider = false, WorkDoneProgress = false)), //HoverProvider = SumType(HoverOptions(WorkDoneProgress = true)) SemanticTokensOptions = addIf diff --git a/src/FSharp.Compiler.LanguageServer/FSharpLanguageServerConfig.fs b/src/FSharp.Compiler.LanguageServer/FSharpLanguageServerConfig.fs index a1acd2b88d..33e89b43e3 100644 --- a/src/FSharp.Compiler.LanguageServer/FSharpLanguageServerConfig.fs +++ b/src/FSharp.Compiler.LanguageServer/FSharpLanguageServerConfig.fs @@ -4,12 +4,14 @@ type FSharpLanguageServerFeatures = { Diagnostics: bool SemanticHighlighting: bool + Completion: bool } static member Default = { Diagnostics = true SemanticHighlighting = true + Completion = true } type FSharpLanguageServerConfig = diff --git a/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs b/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs index 474758924d..f356abb77b 100644 --- a/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs +++ b/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs @@ -10,6 +10,11 @@ open System.Threading open System.Collections.Generic open Microsoft.VisualStudio.FSharp.Editor +open FSharp.Compiler.EditorServices +open FSharp.Compiler.Syntax +open FSharp.Compiler.Text +open System + #nowarn "57" type LanguageFeaturesHandler() = @@ -57,3 +62,74 @@ type LanguageFeaturesHandler() = return SemanticTokens(Data = tokens) } |> CancellableTask.start cancellationToken + + interface IRequestHandler with + [] + member _.HandleRequestAsync(request: CompletionParams, context: FSharpRequestContext, cancellationToken: CancellationToken) = + cancellableTask { + let file = request.TextDocument.Uri + let position = request.Position + + let! source = context.Workspace.Query.GetSource(file) + let! parseAndCheckResults = context.Workspace.Query.GetParseAndCheckResultsForFile(file) + + match source, parseAndCheckResults with + | Some source, (Some parseResults, Some checkFileResults) -> + try + // Convert LSP position to F# position + let fcsPosition = Position.mkPos (int position.Line + 1) (int position.Character) + + // Get the line text at cursor position + let lines = source.GetLineString(position.Line) + let lineText = lines.TrimEnd([|'\r'; '\n'|]) + + // Get partial name for completion + let partialName = QuickParse.GetPartialLongNameEx(lineText, int position.Character - 1) + + // Get completion context + let completionContext = ParsedInput.TryGetCompletionContext(fcsPosition, parseResults.ParseTree, lineText) + + // Get declaration list from compiler services + let declarations = + checkFileResults.GetDeclarationListInfo( + Some(parseResults), + Line.fromZ position.Line, + lineText, + partialName, + (fun _ -> []), // getAllSymbols - simplified for now + (fcsPosition, completionContext), + false // genBodyForOverriddenMeth + ) + + // Convert F# completion items to LSP completion items + let completionItems = + declarations.Items + |> Array.mapi (fun i item -> + let kind = + match item.Kind with + | FSharp.Compiler.EditorServices.CompletionItemKind.Method _ -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Method + | FSharp.Compiler.EditorServices.CompletionItemKind.Property -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Property + | FSharp.Compiler.EditorServices.CompletionItemKind.Field -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Field + | FSharp.Compiler.EditorServices.CompletionItemKind.Event -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Event + | FSharp.Compiler.EditorServices.CompletionItemKind.Argument -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Variable + | FSharp.Compiler.EditorServices.CompletionItemKind.Other -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Value + | _ -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Value + + let completionItem = Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItem() + completionItem.Label <- item.NameInList + completionItem.Kind <- kind + completionItem.Detail <- null + completionItem.SortText <- sprintf "%06d" i + completionItem.FilterText <- item.NameInList + completionItem.InsertText <- item.NameInCode + completionItem) + + return CompletionList(IsIncomplete = false, Items = completionItems) + with + | ex -> + context.Logger.LogError("Error in completion: " + ex.Message) + return CompletionList(IsIncomplete = false, Items = [||]) + | _ -> + return CompletionList(IsIncomplete = false, Items = [||]) + } + |> CancellableTask.start cancellationToken diff --git a/tests/FSharp.Compiler.LanguageServer.Tests/Protocol.fs b/tests/FSharp.Compiler.LanguageServer.Tests/Protocol.fs index 9072fbfb46..83a9beb4b0 100644 --- a/tests/FSharp.Compiler.LanguageServer.Tests/Protocol.fs +++ b/tests/FSharp.Compiler.LanguageServer.Tests/Protocol.fs @@ -191,3 +191,37 @@ let ``Shutdown and exit`` () = do! client.JsonRpc.NotifyAsync(Methods.ExitName) } + +[] +let ``Text document completion`` () = + task { + let! client = initializeLanguageServer None + let workspace = client.Workspace + let contentOnDisk = "let x = System." + let fileOnDisk = sourceFileOnDisk contentOnDisk + let _projectIdentifier = + workspace.Projects.AddOrUpdate(ProjectConfig.Create(), [ fileOnDisk.LocalPath ]) + do! + client.JsonRpc.NotifyAsync( + Methods.TextDocumentDidOpenName, + DidOpenTextDocumentParams( + TextDocument = TextDocumentItem(Uri = fileOnDisk, LanguageId = "F#", Version = 1, Text = contentOnDisk) + ) + ) + + let! completionResponse = + client.JsonRpc.InvokeAsync( + Methods.TextDocumentCompletionName, + CompletionParams( + TextDocument = TextDocumentIdentifier(Uri = fileOnDisk), + Position = Position(Line = 0, Character = 15) // Position after "System." + ) + ) + + Assert.NotNull(completionResponse) + Assert.True(completionResponse.Items.Length > 0, "Should have completion items") + + // Check that we have some common System namespace members + let completionLabels = completionResponse.Items |> Array.map (fun item -> item.Label) + Assert.Contains("Console", completionLabels) + } From 3fdfae9f733310642667e4425f3ae196204a02fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Jun 2025 18:30:23 +0000 Subject: [PATCH 3/7] Add keyword completion support and format code Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- .../Common/CapabilitiesManager.fs | 2 +- .../Handlers/LanguageFeaturesHandler.fs | 91 +++++++++++++------ 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs b/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs index 85476b3d7c..d2beca721a 100644 --- a/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs +++ b/src/FSharp.Compiler.LanguageServer/Common/CapabilitiesManager.fs @@ -33,7 +33,7 @@ type CapabilitiesManager(config: FSharpLanguageServerConfig, scOverrides: IServe WorkspaceDiagnostics = true )), //CompletionProvider = CompletionOptions(TriggerCharacters = [| "."; " " |], ResolveProvider = true, WorkDoneProgress = true), - CompletionProvider = + CompletionProvider = addIf config.EnabledFeatures.Completion (CompletionOptions(TriggerCharacters = [| "." |], ResolveProvider = false, WorkDoneProgress = false)), diff --git a/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs b/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs index f356abb77b..74bacf4439 100644 --- a/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs +++ b/src/FSharp.Compiler.LanguageServer/Handlers/LanguageFeaturesHandler.fs @@ -14,6 +14,7 @@ open FSharp.Compiler.EditorServices open FSharp.Compiler.Syntax open FSharp.Compiler.Text open System +open FSharp.Compiler.Tokenization #nowarn "57" @@ -69,28 +70,33 @@ type LanguageFeaturesHandler() = cancellableTask { let file = request.TextDocument.Uri let position = request.Position - + let! source = context.Workspace.Query.GetSource(file) let! parseAndCheckResults = context.Workspace.Query.GetParseAndCheckResultsForFile(file) - + match source, parseAndCheckResults with | Some source, (Some parseResults, Some checkFileResults) -> try // Convert LSP position to F# position let fcsPosition = Position.mkPos (int position.Line + 1) (int position.Character) - + // Get the line text at cursor position - let lines = source.GetLineString(position.Line) - let lineText = lines.TrimEnd([|'\r'; '\n'|]) - + let lineText = + if position.Line < source.GetLineCount() then + source.GetLineString(position.Line).TrimEnd([| '\r'; '\n' |]) + else + "" + // Get partial name for completion - let partialName = QuickParse.GetPartialLongNameEx(lineText, int position.Character - 1) - + let partialName = + QuickParse.GetPartialLongNameEx(lineText, int position.Character - 1) + // Get completion context - let completionContext = ParsedInput.TryGetCompletionContext(fcsPosition, parseResults.ParseTree, lineText) - + let completionContext = + ParsedInput.TryGetCompletionContext(fcsPosition, parseResults.ParseTree, lineText) + // Get declaration list from compiler services - let declarations = + let declarations = checkFileResults.GetDeclarationListInfo( Some(parseResults), Line.fromZ position.Line, @@ -100,21 +106,27 @@ type LanguageFeaturesHandler() = (fcsPosition, completionContext), false // genBodyForOverriddenMeth ) - + // Convert F# completion items to LSP completion items - let completionItems = + let completionItems = declarations.Items |> Array.mapi (fun i item -> - let kind = + let kind = match item.Kind with - | FSharp.Compiler.EditorServices.CompletionItemKind.Method _ -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Method - | FSharp.Compiler.EditorServices.CompletionItemKind.Property -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Property - | FSharp.Compiler.EditorServices.CompletionItemKind.Field -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Field - | FSharp.Compiler.EditorServices.CompletionItemKind.Event -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Event - | FSharp.Compiler.EditorServices.CompletionItemKind.Argument -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Variable - | FSharp.Compiler.EditorServices.CompletionItemKind.Other -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Value + | FSharp.Compiler.EditorServices.CompletionItemKind.Method _ -> + Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Method + | FSharp.Compiler.EditorServices.CompletionItemKind.Property -> + Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Property + | FSharp.Compiler.EditorServices.CompletionItemKind.Field -> + Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Field + | FSharp.Compiler.EditorServices.CompletionItemKind.Event -> + Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Event + | FSharp.Compiler.EditorServices.CompletionItemKind.Argument -> + Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Variable + | FSharp.Compiler.EditorServices.CompletionItemKind.Other -> + Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Value | _ -> Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Value - + let completionItem = Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItem() completionItem.Label <- item.NameInList completionItem.Kind <- kind @@ -123,13 +135,38 @@ type LanguageFeaturesHandler() = completionItem.FilterText <- item.NameInList completionItem.InsertText <- item.NameInCode completionItem) - - return CompletionList(IsIncomplete = false, Items = completionItems) - with - | ex -> + + // Add keyword completions if appropriate + let keywordItems = + if + not declarations.IsForType + && not declarations.IsError + && List.isEmpty partialName.QualifyingIdents + then + match completionContext with + | None -> + FSharpKeywords.KeywordsWithDescription + |> List.filter (fun (keyword, _) -> not (PrettyNaming.IsOperatorDisplayName keyword)) + |> List.mapi (fun i (keyword, description) -> + let completionItem = Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItem() + completionItem.Label <- keyword + completionItem.Kind <- Microsoft.VisualStudio.LanguageServer.Protocol.CompletionItemKind.Keyword + completionItem.Detail <- description + completionItem.SortText <- sprintf "%06d" (1000000 + i) // Sort keywords after regular items + completionItem.FilterText <- keyword + completionItem.InsertText <- keyword + completionItem) + |> List.toArray + | _ -> [||] + else + [||] + + let allItems = Array.append completionItems keywordItems + + return CompletionList(IsIncomplete = false, Items = allItems) + with ex -> context.Logger.LogError("Error in completion: " + ex.Message) return CompletionList(IsIncomplete = false, Items = [||]) - | _ -> - return CompletionList(IsIncomplete = false, Items = [||]) + | _ -> return CompletionList(IsIncomplete = false, Items = [||]) } |> CancellableTask.start cancellationToken From e34736fcd051d0edf67fad052d3fdb21e3cf9b2f Mon Sep 17 00:00:00 2001 From: Adam Boniecki <20281641+abonie@users.noreply.github.com> Date: Wed, 18 Jun 2025 19:26:16 +0200 Subject: [PATCH 4/7] Update copilot-setup-steps.yml Make it so that failure of a specific setup step does not fail the setup. Useful when copilot committed code that does not compile. --- .github/workflows/copilot-setup-steps.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 40d01e7cf5..14ba96ada9 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -21,12 +21,12 @@ jobs: - name: Restore the compiler service solution env: CI: false - run: ./build.sh -c Release --verbosity quiet + run: ./build.sh -c Release --verbosity quiet || true - name: Restore the language server solution env: CI: false - run: dotnet build ./LSPSolutionSlim.sln -c Release --verbosity quiet + run: dotnet build ./LSPSolutionSlim.sln -c Release --verbosity quiet || true - name: Restore dotnet tools env: CI: false - run: dotnet tool restore + run: dotnet tool restore || true From 965e798e0b7a7df4becd76adb7d15016b20d94b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Jun 2025 18:01:06 +0000 Subject: [PATCH 5/7] Fix build error in FSharpLanguageServerProvider.cs Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- .../FSharpLanguageServerProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/FSharp.VisualStudio.Extension/FSharpLanguageServerProvider.cs b/src/FSharp.VisualStudio.Extension/FSharpLanguageServerProvider.cs index c4f90a7ca1..631e5b2cd6 100644 --- a/src/FSharp.VisualStudio.Extension/FSharpLanguageServerProvider.cs +++ b/src/FSharp.VisualStudio.Extension/FSharpLanguageServerProvider.cs @@ -292,7 +292,8 @@ await this.Extensibility.Settings().WriteAsync(batch => var serverConfig = new FSharpLanguageServerConfig( new FSharpLanguageServerFeatures( diagnostics: enabled.Contains(settingsReadResult.ValueOrDefault(FSharpExtensionSettings.GetDiagnosticsFrom, defaultValue: FSharpExtensionSettings.BOTH)), - semanticHighlighting: enabled.Contains(settingsReadResult.ValueOrDefault(FSharpExtensionSettings.GetSemanticHighlightingFrom, defaultValue: FSharpExtensionSettings.BOTH)) + semanticHighlighting: enabled.Contains(settingsReadResult.ValueOrDefault(FSharpExtensionSettings.GetSemanticHighlightingFrom, defaultValue: FSharpExtensionSettings.BOTH)), + completion: true )); var disposeToEndSubscription = From e3877794dccadb16fe4772e113e9422f4fdb18ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Jun 2025 08:53:56 +0000 Subject: [PATCH 6/7] Add FSharp.Compiler.LanguageServer.Tests to CI test execution Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- eng/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/build.sh b/eng/build.sh index 07c3889f33..9fa3b2959c 100755 --- a/eng/build.sh +++ b/eng/build.sh @@ -356,6 +356,7 @@ if [[ "$test_core_clr" == true ]]; then Test --testproject "$repo_root/tests/FSharp.Compiler.Private.Scripting.UnitTests/FSharp.Compiler.Private.Scripting.UnitTests.fsproj" --targetframework $coreclrtestframework Test --testproject "$repo_root/tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj" --targetframework $coreclrtestframework Test --testproject "$repo_root/tests/FSharp.Core.UnitTests/FSharp.Core.UnitTests.fsproj" --targetframework $coreclrtestframework + Test --testproject "$repo_root/tests/FSharp.Compiler.LanguageServer.Tests/FSharp.Compiler.LanguageServer.Tests.fsproj" --targetframework $coreclrtestframework fi if [[ "$test_compilercomponent_tests" == true ]]; then From 621da52c68a487c34cb4a4b13e1f736fe63f3892 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Jun 2025 09:02:40 +0000 Subject: [PATCH 7/7] Fix test project configuration for xUnit testing Co-authored-by: T-Gro <46543583+T-Gro@users.noreply.github.com> --- .../FSharp.Compiler.LanguageServer.Tests.fsproj | 5 +++++ tests/FSharp.Compiler.LanguageServer.Tests/xunit.runner.json | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 tests/FSharp.Compiler.LanguageServer.Tests/xunit.runner.json diff --git a/tests/FSharp.Compiler.LanguageServer.Tests/FSharp.Compiler.LanguageServer.Tests.fsproj b/tests/FSharp.Compiler.LanguageServer.Tests/FSharp.Compiler.LanguageServer.Tests.fsproj index 046357aafe..6aaecb28bd 100644 --- a/tests/FSharp.Compiler.LanguageServer.Tests/FSharp.Compiler.LanguageServer.Tests.fsproj +++ b/tests/FSharp.Compiler.LanguageServer.Tests/FSharp.Compiler.LanguageServer.Tests.fsproj @@ -19,6 +19,10 @@ + + + + + diff --git a/tests/FSharp.Compiler.LanguageServer.Tests/xunit.runner.json b/tests/FSharp.Compiler.LanguageServer.Tests/xunit.runner.json new file mode 100644 index 0000000000..f47fec5d74 --- /dev/null +++ b/tests/FSharp.Compiler.LanguageServer.Tests/xunit.runner.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "appDomain": "denied", + "parallelizeAssembly": true +} \ No newline at end of file