diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..676885459 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,245 @@ +# FsAutoComplete Copilot Instructions + +## Project Overview + +FsAutoComplete (FSAC) is a Language Server Protocol (LSP) backend service that provides rich editing and intellisense features for F# development. It serves as the core engine behind F# support in various editors including Visual Studio Code (Ionide), Emacs, Neovim, Vim, Sublime Text, and Zed. + +## Supported Editors + +FsAutoComplete currently provides F# support for: +- **Visual Studio Code** (via [Ionide](https://github.com/ionide/ionide-vscode-fsharp)) +- **Emacs** (via [emacs-fsharp-mode](https://github.com/fsharp/emacs-fsharp-mode)) +- **Neovim** (via [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#fsautocomplete)) +- **Vim** (via [vim-fsharp](https://github.com/fsharp/vim-fsharp)) +- **Sublime Text** (via [LSP package](https://lsp.sublimetext.io/language_servers/#f)) +- **Zed** (via [zed-fsharp](https://github.com/nathanjcollins/zed-fsharp)) + +## Architecture + +### Core Components + +- **FsAutoComplete.Core**: Contains the core functionality, including: + - F# compiler service interfaces + - Code generation and refactoring utilities + - Symbol resolution and type checking + - Signature formatting and documentation + - File system abstractions + +- **FsAutoComplete**: Main LSP server implementation with: + - LSP protocol handlers and endpoints + - Code fixes and quick actions + - Parser for LSP requests/responses + - Program entry point + +- **FsAutoComplete.Logging**: Centralized logging infrastructure + +### Key Dependencies + +- **FSharp.Compiler.Service** (>= 43.9.300): Core F# compiler APIs for language analysis +- **Ionide.ProjInfo** (>= 0.71.2): Project and solution file parsing, with separate packages for FCS integration and project system +- **FSharpLint.Core**: Code linting and static analysis +- **Fantomas.Client** (>= 0.9): F# code formatting +- **Microsoft.Build** (>= 17.2): MSBuild integration for project loading +- **Serilog** (>= 2.10.0): Structured logging infrastructure +- **Language Server Protocol**: Communication with editors + +#### Target Frameworks +- **netstandard2.0 & netstandard2.1**: For broader compatibility +- **net8.0 & net9.0**: For latest .NET features and performance + +## Development Workflow + +### Building the Project + +Requirements: +- .NET SDK (see `global.json` for exact version - minimum >= 6.0, recommended >= 7.0) + +```bash +# Restore .NET tools (including Paket) +dotnet tool restore + +# Build the entire solution +dotnet build + +# Run tests +dotnet test + +# Run specific test project +dotnet test -f net8.0 ./test/FsAutoComplete.Tests.Lsp/FsAutoComplete.Tests.Lsp.fsproj + +# Format code +dotnet fantomas src/ test/ +``` + +#### Development Environment Options +- **DevContainer**: Use with VSCode's Remote Containers extension for stable development environment +- **Gitpod**: Web-based VSCode IDE available at https://gitpod.io/#https://github.com/fsharp/fsautocomplete + +### Project Dependencies + +This project uses **Paket** for dependency management instead of NuGet directly: +- Dependencies are declared in `paket.dependencies` +- Lock file is `paket.lock` +- Each project has its own `paket.references` file + +### Code Organization + +#### Code Fixes +- Located in `src/FsAutoComplete/CodeFixes/` +- Each code fix is typically a separate F# module +- Follow the pattern: analyze issue โ†’ generate fix โ†’ apply transformation +- **Scaffolding**: Use `dotnet fsi build.fsx -- -p ScaffoldCodeFix YourCodeFixName` to create new code fixes +- This generates implementation file, signature file, and unit test, plus updates registration files +- Examples include: `ImplementInterface.fs`, `GenerateUnionCases.fs`, `AddMissingEqualsToTypeDefinition.fs` + +#### LSP Endpoints +- Standard LSP endpoints in `src/FsAutoComplete/LspServers/` +- Key server files: `AdaptiveFSharpLspServer.fs`, `AdaptiveServerState.fs`, `ProjectWorkspace.fs` +- Custom F#-specific endpoints prefixed with `fsharp/` +- Request/response types in `CommandResponse.fs` +- Interface definitions in `IFSharpLspServer.fs` + +#### Testing +- Main test suite in `test/FsAutoComplete.Tests.Lsp/` +- Tests organized by feature area (CompletionTests, CodeFixTests, etc.) +- Uses F# testing frameworks with custom helpers in `Helpers.fs` +- Test cases often in `TestCases/` subdirectories + +## F# Language Conventions + +### Coding Style +- Follow F# community conventions +- Use `fantomas` for code formatting (configured in the project) +- Prefer immutable data structures and functional programming patterns +- Use explicit type annotations where they improve clarity + +### Module Organization +- One primary type/feature per file +- Use `.fs` and `.fsi` pairs for public APIs +- Organize related functionality into modules +- Follow naming conventions: `CamelCase` for types, `camelCase` for values + +### Error Handling +- Use F# Result types for error handling where appropriate +- Use FsToolkit.ErrorHandling for railway-oriented programming +- Prefer explicit error types over generic exceptions + +## LSP Implementation Details + +### Supported Standard LSP Features +- `textDocument/completion` with `completionItem/resolve` +- `textDocument/hover`, `textDocument/definition`, `textDocument/references` +- `textDocument/codeAction`, `textDocument/codeLens` +- `textDocument/formatting` (via Fantomas) +- `textDocument/rename`, `textDocument/signatureHelp` +- Workspace management and file watching + +### Custom F# Extensions +- `fsharp/signature`: Get formatted signature at position +- `fsharp/compile`: Compile project and return diagnostics +- `fsharp/workspacePeek`: Discover available projects/solutions +- `fsharp/workspaceLoad`: Load specific projects +- `fsproj/addFile`, `fsproj/removeFile`: Project file manipulation +- `fsharp/documentationForSymbol`: Get documentation for symbols +- `fsharp/f1Help`: F1 help functionality +- `fsharp/fsi`: F# Interactive integration + +## Testing Guidelines + +### Test Structure +- Tests are organized by feature area +- Use descriptive test names that explain the scenario +- Include both positive and negative test cases +- Test with realistic F# code examples + +### Adding New Tests +1. Identify the appropriate test file (e.g., `CompletionTests.fs` for completion features) +2. Follow existing patterns for test setup and assertions +3. Use the helpers in `Helpers.fs` for common operations +4. Include edge cases and error conditions +5. For code fixes: Run focused tests with `dotnet run -f net8.0 --project ./test/FsAutoComplete.Tests.Lsp/FsAutoComplete.Tests.Lsp.fsproj` +6. Remove focused test markers before submitting PRs (they cause CI failures) + +### Test Data +- Sample F# projects in `TestCases/` directories +- Use minimal, focused examples that demonstrate specific features +- Avoid overly complex test scenarios that are hard to debug + +## Performance Considerations + +### Memory Management +- Be mindful of memory usage in long-running language server scenarios +- Dispose of compiler service resources appropriately +- Use caching judiciously to balance performance and memory + +### Responsiveness +- LSP operations should be fast and non-blocking +- Use async/await patterns for I/O operations +- Consider cancellation tokens for long-running operations + +## Debugging and Telemetry + +### OpenTelemetry Integration +- Tracing is available with `--otel-exporter-enabled` flag +- Use Jaeger for trace visualization during development +- Activity tracing helps debug performance issues + +### Logging +- Structured logging via Serilog +- Use appropriate log levels (Debug, Info, Warning, Error) +- Include relevant context in log messages + +## Common Patterns + +### Working with FCS (F# Compiler Service) +- Always work with `FSharpCheckFileResults` and `FSharpParseFileResults` +- Handle both parsed and typed ASTs appropriately +- Be aware of file dependencies and project context + +### LSP Request Handling +- Validate input parameters +- Handle exceptions gracefully +- Return appropriate error responses for invalid requests +- Use proper JSON serialization + +### Code Generation +- Use the F# AST utilities in `TypedAstUtils.fs` and `UntypedAstUtils.fs` +- Consider both syntactic and semantic correctness +- Test generated code compiles and has expected behavior + +## Contributing Guidelines + +### Before Submitting Changes +1. Ensure all tests pass: `dotnet test` +2. Run code formatting: `dotnet fantomas src/ test/` +3. Verify the solution builds cleanly +4. Test your changes with a real F# project if possible + +### Code Review Focus Areas +- Correctness of F# language analysis +- Performance impact on language server operations +- Compatibility with different F# project types +- LSP protocol compliance +- Test coverage for new features + +## Resources + +### Core Documentation +- [FsAutoComplete GitHub Repository](https://github.com/ionide/FsAutoComplete) +- [LSP Specification](https://microsoft.github.io/language-server-protocol/) +- [F# Compiler Service Documentation](https://fsharp.github.io/FSharp.Compiler.Service/) + +### F# Development Guidelines +- [F# Style Guide](https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/) +- [F# Formatting Guidelines](https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting) +- [F# Component Design Guidelines](https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/component-design-guidelines) + +### Project-Specific Guides +- [Creating a New Code Fix Guide](./docs/Creating%20a%20new%20code%20fix.md) +- [Ionide.ProjInfo Documentation](https://github.com/ionide/proj-info) +- [Fantomas Configuration](https://fsprojects.github.io/fantomas/) + +### Related Tools +- [FSharpLint](https://github.com/fsprojects/FSharpLint/) - Static analysis tool +- [Paket](https://fsprojects.github.io/Paket/) - Dependency management +- [FAKE](https://fake.build/) - Build automation (used for scaffolding) \ No newline at end of file diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 000000000..e0861425a --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,61 @@ +name: Copilot Setup Steps + +# This workflow is designed to be used by GitHub Copilot coding agents +# to set up the environment needed to work with the FsAutoComplete repository + +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + copilot-setup-steps: + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Announce .NET version + run: dotnet --info + + - name: Restore tools + run: dotnet tool restore + + - name: Restore packages + run: dotnet restore + + - name: Build solution + run: dotnet build -c Release --no-restore + + - name: Verify Fantomas formatting + run: dotnet fantomas --check build.fsx src + env: + DOTNET_ROLL_FORWARD: LatestMajor + DOTNET_ROLL_FORWARD_TO_PRERELEASE: 1 + + - name: Run quick tests + run: dotnet test -c Release -f net8.0 --no-restore --no-build --logger "console;verbosity=minimal" -- Expecto.fail-on-focused-tests=true + working-directory: test/FsAutoComplete.Tests.Lsp + timeout-minutes: 5 + continue-on-error: true # Don't fail the setup if tests fail + + - name: Setup completed + run: | + echo "โœ… Copilot setup completed successfully!" + echo "๐Ÿ“ฆ Dependencies restored" + echo "๐Ÿ”จ Build verified" + echo "๐Ÿ“ Code formatting checked" + echo "๐Ÿงช Basic tests run" + echo "" + echo "The environment is ready for Copilot to work with FsAutoComplete." diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..676885459 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,245 @@ +# FsAutoComplete Copilot Instructions + +## Project Overview + +FsAutoComplete (FSAC) is a Language Server Protocol (LSP) backend service that provides rich editing and intellisense features for F# development. It serves as the core engine behind F# support in various editors including Visual Studio Code (Ionide), Emacs, Neovim, Vim, Sublime Text, and Zed. + +## Supported Editors + +FsAutoComplete currently provides F# support for: +- **Visual Studio Code** (via [Ionide](https://github.com/ionide/ionide-vscode-fsharp)) +- **Emacs** (via [emacs-fsharp-mode](https://github.com/fsharp/emacs-fsharp-mode)) +- **Neovim** (via [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#fsautocomplete)) +- **Vim** (via [vim-fsharp](https://github.com/fsharp/vim-fsharp)) +- **Sublime Text** (via [LSP package](https://lsp.sublimetext.io/language_servers/#f)) +- **Zed** (via [zed-fsharp](https://github.com/nathanjcollins/zed-fsharp)) + +## Architecture + +### Core Components + +- **FsAutoComplete.Core**: Contains the core functionality, including: + - F# compiler service interfaces + - Code generation and refactoring utilities + - Symbol resolution and type checking + - Signature formatting and documentation + - File system abstractions + +- **FsAutoComplete**: Main LSP server implementation with: + - LSP protocol handlers and endpoints + - Code fixes and quick actions + - Parser for LSP requests/responses + - Program entry point + +- **FsAutoComplete.Logging**: Centralized logging infrastructure + +### Key Dependencies + +- **FSharp.Compiler.Service** (>= 43.9.300): Core F# compiler APIs for language analysis +- **Ionide.ProjInfo** (>= 0.71.2): Project and solution file parsing, with separate packages for FCS integration and project system +- **FSharpLint.Core**: Code linting and static analysis +- **Fantomas.Client** (>= 0.9): F# code formatting +- **Microsoft.Build** (>= 17.2): MSBuild integration for project loading +- **Serilog** (>= 2.10.0): Structured logging infrastructure +- **Language Server Protocol**: Communication with editors + +#### Target Frameworks +- **netstandard2.0 & netstandard2.1**: For broader compatibility +- **net8.0 & net9.0**: For latest .NET features and performance + +## Development Workflow + +### Building the Project + +Requirements: +- .NET SDK (see `global.json` for exact version - minimum >= 6.0, recommended >= 7.0) + +```bash +# Restore .NET tools (including Paket) +dotnet tool restore + +# Build the entire solution +dotnet build + +# Run tests +dotnet test + +# Run specific test project +dotnet test -f net8.0 ./test/FsAutoComplete.Tests.Lsp/FsAutoComplete.Tests.Lsp.fsproj + +# Format code +dotnet fantomas src/ test/ +``` + +#### Development Environment Options +- **DevContainer**: Use with VSCode's Remote Containers extension for stable development environment +- **Gitpod**: Web-based VSCode IDE available at https://gitpod.io/#https://github.com/fsharp/fsautocomplete + +### Project Dependencies + +This project uses **Paket** for dependency management instead of NuGet directly: +- Dependencies are declared in `paket.dependencies` +- Lock file is `paket.lock` +- Each project has its own `paket.references` file + +### Code Organization + +#### Code Fixes +- Located in `src/FsAutoComplete/CodeFixes/` +- Each code fix is typically a separate F# module +- Follow the pattern: analyze issue โ†’ generate fix โ†’ apply transformation +- **Scaffolding**: Use `dotnet fsi build.fsx -- -p ScaffoldCodeFix YourCodeFixName` to create new code fixes +- This generates implementation file, signature file, and unit test, plus updates registration files +- Examples include: `ImplementInterface.fs`, `GenerateUnionCases.fs`, `AddMissingEqualsToTypeDefinition.fs` + +#### LSP Endpoints +- Standard LSP endpoints in `src/FsAutoComplete/LspServers/` +- Key server files: `AdaptiveFSharpLspServer.fs`, `AdaptiveServerState.fs`, `ProjectWorkspace.fs` +- Custom F#-specific endpoints prefixed with `fsharp/` +- Request/response types in `CommandResponse.fs` +- Interface definitions in `IFSharpLspServer.fs` + +#### Testing +- Main test suite in `test/FsAutoComplete.Tests.Lsp/` +- Tests organized by feature area (CompletionTests, CodeFixTests, etc.) +- Uses F# testing frameworks with custom helpers in `Helpers.fs` +- Test cases often in `TestCases/` subdirectories + +## F# Language Conventions + +### Coding Style +- Follow F# community conventions +- Use `fantomas` for code formatting (configured in the project) +- Prefer immutable data structures and functional programming patterns +- Use explicit type annotations where they improve clarity + +### Module Organization +- One primary type/feature per file +- Use `.fs` and `.fsi` pairs for public APIs +- Organize related functionality into modules +- Follow naming conventions: `CamelCase` for types, `camelCase` for values + +### Error Handling +- Use F# Result types for error handling where appropriate +- Use FsToolkit.ErrorHandling for railway-oriented programming +- Prefer explicit error types over generic exceptions + +## LSP Implementation Details + +### Supported Standard LSP Features +- `textDocument/completion` with `completionItem/resolve` +- `textDocument/hover`, `textDocument/definition`, `textDocument/references` +- `textDocument/codeAction`, `textDocument/codeLens` +- `textDocument/formatting` (via Fantomas) +- `textDocument/rename`, `textDocument/signatureHelp` +- Workspace management and file watching + +### Custom F# Extensions +- `fsharp/signature`: Get formatted signature at position +- `fsharp/compile`: Compile project and return diagnostics +- `fsharp/workspacePeek`: Discover available projects/solutions +- `fsharp/workspaceLoad`: Load specific projects +- `fsproj/addFile`, `fsproj/removeFile`: Project file manipulation +- `fsharp/documentationForSymbol`: Get documentation for symbols +- `fsharp/f1Help`: F1 help functionality +- `fsharp/fsi`: F# Interactive integration + +## Testing Guidelines + +### Test Structure +- Tests are organized by feature area +- Use descriptive test names that explain the scenario +- Include both positive and negative test cases +- Test with realistic F# code examples + +### Adding New Tests +1. Identify the appropriate test file (e.g., `CompletionTests.fs` for completion features) +2. Follow existing patterns for test setup and assertions +3. Use the helpers in `Helpers.fs` for common operations +4. Include edge cases and error conditions +5. For code fixes: Run focused tests with `dotnet run -f net8.0 --project ./test/FsAutoComplete.Tests.Lsp/FsAutoComplete.Tests.Lsp.fsproj` +6. Remove focused test markers before submitting PRs (they cause CI failures) + +### Test Data +- Sample F# projects in `TestCases/` directories +- Use minimal, focused examples that demonstrate specific features +- Avoid overly complex test scenarios that are hard to debug + +## Performance Considerations + +### Memory Management +- Be mindful of memory usage in long-running language server scenarios +- Dispose of compiler service resources appropriately +- Use caching judiciously to balance performance and memory + +### Responsiveness +- LSP operations should be fast and non-blocking +- Use async/await patterns for I/O operations +- Consider cancellation tokens for long-running operations + +## Debugging and Telemetry + +### OpenTelemetry Integration +- Tracing is available with `--otel-exporter-enabled` flag +- Use Jaeger for trace visualization during development +- Activity tracing helps debug performance issues + +### Logging +- Structured logging via Serilog +- Use appropriate log levels (Debug, Info, Warning, Error) +- Include relevant context in log messages + +## Common Patterns + +### Working with FCS (F# Compiler Service) +- Always work with `FSharpCheckFileResults` and `FSharpParseFileResults` +- Handle both parsed and typed ASTs appropriately +- Be aware of file dependencies and project context + +### LSP Request Handling +- Validate input parameters +- Handle exceptions gracefully +- Return appropriate error responses for invalid requests +- Use proper JSON serialization + +### Code Generation +- Use the F# AST utilities in `TypedAstUtils.fs` and `UntypedAstUtils.fs` +- Consider both syntactic and semantic correctness +- Test generated code compiles and has expected behavior + +## Contributing Guidelines + +### Before Submitting Changes +1. Ensure all tests pass: `dotnet test` +2. Run code formatting: `dotnet fantomas src/ test/` +3. Verify the solution builds cleanly +4. Test your changes with a real F# project if possible + +### Code Review Focus Areas +- Correctness of F# language analysis +- Performance impact on language server operations +- Compatibility with different F# project types +- LSP protocol compliance +- Test coverage for new features + +## Resources + +### Core Documentation +- [FsAutoComplete GitHub Repository](https://github.com/ionide/FsAutoComplete) +- [LSP Specification](https://microsoft.github.io/language-server-protocol/) +- [F# Compiler Service Documentation](https://fsharp.github.io/FSharp.Compiler.Service/) + +### F# Development Guidelines +- [F# Style Guide](https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/) +- [F# Formatting Guidelines](https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting) +- [F# Component Design Guidelines](https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/component-design-guidelines) + +### Project-Specific Guides +- [Creating a New Code Fix Guide](./docs/Creating%20a%20new%20code%20fix.md) +- [Ionide.ProjInfo Documentation](https://github.com/ionide/proj-info) +- [Fantomas Configuration](https://fsprojects.github.io/fantomas/) + +### Related Tools +- [FSharpLint](https://github.com/fsprojects/FSharpLint/) - Static analysis tool +- [Paket](https://fsprojects.github.io/Paket/) - Dependency management +- [FAKE](https://fake.build/) - Build automation (used for scaffolding) \ No newline at end of file diff --git a/FsAutoComplete.sln b/FsAutoComplete.sln index 880144ac3..994c9f56f 100644 --- a/FsAutoComplete.sln +++ b/FsAutoComplete.sln @@ -27,6 +27,10 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsAutoComplete.DependencyMa EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "benchmarks", "benchmarks\benchmarks.fsproj", "{0CD029D8-B39E-4CBE-A190-C84A7A811180}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsAutoComplete.BuildServer", "src\FsAutoComplete.BuildServer\FsAutoComplete.BuildServer.fsproj", "{20A60741-D0BB-43AE-8912-21C4D3F3817E}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsAutoComplete.BuildServerProtocol", "src\FsAutoComplete.BuildServerProtocol\FsAutoComplete.BuildServerProtocol.fsproj", "{35E025B2-B255-4E95-A5C6-B8B52361808F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -65,6 +69,14 @@ Global {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Debug|Any CPU.Build.0 = Debug|Any CPU {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Release|Any CPU.ActiveCfg = Release|Any CPU {0CD029D8-B39E-4CBE-A190-C84A7A811180}.Release|Any CPU.Build.0 = Release|Any CPU + {20A60741-D0BB-43AE-8912-21C4D3F3817E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20A60741-D0BB-43AE-8912-21C4D3F3817E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20A60741-D0BB-43AE-8912-21C4D3F3817E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20A60741-D0BB-43AE-8912-21C4D3F3817E}.Release|Any CPU.Build.0 = Release|Any CPU + {35E025B2-B255-4E95-A5C6-B8B52361808F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35E025B2-B255-4E95-A5C6-B8B52361808F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35E025B2-B255-4E95-A5C6-B8B52361808F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35E025B2-B255-4E95-A5C6-B8B52361808F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -74,6 +86,8 @@ Global {38C1F619-3E1E-4784-9833-E8A2AA95CDAE} = {BA56455D-4AEA-45FC-A569-027A68A76BA6} {14C55B44-2063-4891-98BE-8184CAB1BE87} = {443E0B8D-9AD0-436E-A331-E8CC12965F07} {C58701B0-D8E3-4B68-A7DE-8524C95F86C0} = {443E0B8D-9AD0-436E-A331-E8CC12965F07} + {20A60741-D0BB-43AE-8912-21C4D3F3817E} = {BA56455D-4AEA-45FC-A569-027A68A76BA6} + {35E025B2-B255-4E95-A5C6-B8B52361808F} = {BA56455D-4AEA-45FC-A569-027A68A76BA6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1C4EE83B-632A-4929-8C96-38F14254229E} diff --git a/paket.dependencies b/paket.dependencies index 3839a9f26..e6aad8c03 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -25,6 +25,7 @@ nuget Microsoft.CodeAnalysis nuget FSharp.Analyzers.SDK 0.32.1 nuget ICSharpCode.Decompiler nuget Mono.Cecil >= 0.11.4 +nuget Newtonsoft.Json >= 13.0 nuget FSharpLint.Core nuget Serilog >= 2.10.0 nuget Serilog.Sinks.File >= 5.0.0 diff --git a/src/FsAutoComplete.BuildServer/FsAutoComplete.BuildServer.fsproj b/src/FsAutoComplete.BuildServer/FsAutoComplete.BuildServer.fsproj new file mode 100644 index 000000000..22a1662e7 --- /dev/null +++ b/src/FsAutoComplete.BuildServer/FsAutoComplete.BuildServer.fsproj @@ -0,0 +1,19 @@ + + + net8.0 + net8.0;net9.0 + Exe + false + fsautocomplete-buildserver + + + + + + + + + + + + \ No newline at end of file diff --git a/src/FsAutoComplete.BuildServer/JsonRpcServer.fs b/src/FsAutoComplete.BuildServer/JsonRpcServer.fs new file mode 100644 index 000000000..1ae6b5514 --- /dev/null +++ b/src/FsAutoComplete.BuildServer/JsonRpcServer.fs @@ -0,0 +1,224 @@ +namespace FsAutoComplete.BuildServer + +open System +open System.IO +open System.Text +open System.Threading.Tasks +open Newtonsoft.Json +open Newtonsoft.Json.Linq +open FsAutoComplete.Logging +open FsAutoComplete.BuildServerProtocol.JsonRpc +open FsAutoComplete.BuildServerProtocol.BuildServerProtocol +open WorkspaceOperations + +/// JSON RPC server for Build Server Protocol communication +module JsonRpcServer = + + let private logger = LogProvider.getLoggerByName "JsonRpcServer" + + type RequestHandler = JsonRpcRequest -> Task + type NotificationHandler = JsonRpcNotification -> Task + + let private jsonSettings = + JsonSerializerSettings( + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore + ) + + let private serialize obj = JsonConvert.SerializeObject(obj, jsonSettings) + + let private deserialize<'T> (json: string) = JsonConvert.DeserializeObject<'T>(json, jsonSettings) + + let private tryDeserialize<'T> (token: JToken) = + try + Some(token.ToObject<'T>()) + with _ -> + None + + /// Create a successful response + let private createSuccessResponse (id: JToken option) (result: obj) = + { Id = id + Result = Some(JToken.FromObject(result)) + Error = None } + + /// Create an error response + let private createErrorResponse (id: JToken option) (code: int) (message: string) = + { Id = id + Result = None + Error = + Some + { Code = code + Message = message + Data = None } } + + /// Handle BSP requests + let private handleBspRequest (request: JsonRpcRequest) : Task = + task { + try + logger.info ( + Log.setMessage "Handling BSP request: {method}" + >> Log.addContext "method" request.Method + ) + + match request.Method with + | "build/initialize" -> + let! result = initializeWorkspace () + + match result with + | Result.Ok() -> + let capabilities = + { CompileProvider = Some true + TestProvider = None + RunProvider = None + DebugProvider = None + InverseSourcesProvider = None + DependencySourcesProvider = None + DependencyModulesProvider = None + ResourcesProvider = None + OutputPathsProvider = None + BuildTargetChangedProvider = None + JvmRunEnvironmentProvider = None + JvmTestEnvironmentProvider = None + CanReload = Some true } + + return createSuccessResponse (Some request.Id) capabilities + | Result.Error msg -> return createErrorResponse (Some request.Id) ErrorCodes.InternalError msg + + | "build/shutdown" -> + let! result = shutdown () + + match result with + | Result.Ok() -> return createSuccessResponse (Some request.Id) () + | Result.Error msg -> return createErrorResponse (Some request.Id) ErrorCodes.InternalError msg + + | "workspace/buildTargets" -> + // Return empty build targets for now + let result = { Targets = [||] } + return createSuccessResponse (Some request.Id) result + + | "fsharp/workspacePeek" -> + match request.Params with + | Some parameters -> + match tryDeserialize parameters with + | Some peekRequest -> + let! result = peekWorkspace peekRequest + + match result with + | Result.Ok response -> return createSuccessResponse (Some request.Id) response + | Result.Error msg -> return createErrorResponse (Some request.Id) ErrorCodes.InternalError msg + | None -> + return createErrorResponse (Some request.Id) ErrorCodes.InvalidParams "Invalid workspace peek parameters" + | None -> + return createErrorResponse (Some request.Id) ErrorCodes.InvalidParams "Missing workspace peek parameters" + + | "fsharp/workspaceLoad" -> + match request.Params with + | Some parameters -> + match tryDeserialize parameters with + | Some loadRequest -> + let! result = loadWorkspace loadRequest + + match result with + | Result.Ok response -> return createSuccessResponse (Some request.Id) response + | Result.Error msg -> return createErrorResponse (Some request.Id) ErrorCodes.InternalError msg + | None -> + return createErrorResponse (Some request.Id) ErrorCodes.InvalidParams "Invalid workspace load parameters" + | None -> + return createErrorResponse (Some request.Id) ErrorCodes.InvalidParams "Missing workspace load parameters" + + | _ -> + logger.warn ( + Log.setMessage "Unknown method: {method}" + >> Log.addContext "method" request.Method + ) + + return createErrorResponse (Some request.Id) ErrorCodes.MethodNotFound $"Method not found: {request.Method}" + with ex -> + logger.error ( + Log.setMessage "Error handling request: {error}" + >> Log.addContext "error" ex.Message + ) + + return createErrorResponse (Some request.Id) ErrorCodes.InternalError ex.Message + } + + /// Handle JSON RPC notifications + let private handleNotification (notification: JsonRpcNotification) : Task = + task { + try + logger.info ( + Log.setMessage "Handling notification: {method}" + >> Log.addContext "method" notification.Method + ) + + match notification.Method with + | "build/exit" -> + logger.info (Log.setMessage "Received exit notification") + Environment.Exit(0) + | _ -> + logger.warn ( + Log.setMessage "Unknown notification method: {method}" + >> Log.addContext "method" notification.Method + ) + with ex -> + logger.error ( + Log.setMessage "Error handling notification: {error}" + >> Log.addContext "error" ex.Message + ) + } + + /// Process a single JSON RPC message + let processMessage (messageText: string) : Task = + task { + try + let message = JObject.Parse(messageText) + + if message.ContainsKey("id") then + // This is a request + let request = message.ToObject() + let! response = handleBspRequest request + return Some(serialize response) + else + // This is a notification + let notification = message.ToObject() + do! handleNotification notification + return None + with ex -> + logger.error ( + Log.setMessage "Error processing message: {error}" + >> Log.addContext "error" ex.Message + ) + + let errorResponse = createErrorResponse None ErrorCodes.ParseError "Parse error" + return Some(serialize errorResponse) + } + + /// Main server loop for stdin/stdout communication + let runServer () = + task { + logger.info (Log.setMessage "Starting JSON RPC server...") + + use reader = new StreamReader(Console.OpenStandardInput()) + use writer = new StreamWriter(Console.OpenStandardOutput()) + writer.AutoFlush <- true + + let mutable keepRunning = true + + while keepRunning do + try + let! line = reader.ReadLineAsync() + + if not (isNull line) then + let! response = processMessage line + + match response with + | Some responseText -> do! writer.WriteLineAsync(responseText) + | None -> () // No response needed for notifications + else + keepRunning <- false + with ex -> + logger.error (Log.setMessage "Server loop error: {error}" >> Log.addContext "error" ex.Message) + keepRunning <- false + + logger.info (Log.setMessage "JSON RPC server stopped") + } diff --git a/src/FsAutoComplete.BuildServer/Program.fs b/src/FsAutoComplete.BuildServer/Program.fs new file mode 100644 index 000000000..0064166d3 --- /dev/null +++ b/src/FsAutoComplete.BuildServer/Program.fs @@ -0,0 +1,20 @@ +module FsAutoComplete.BuildServer.Program + +open System +open System.IO +open FsAutoComplete.Logging +open JsonRpcServer + +[] +let main _args = + // Set up basic logging + printfn "FsAutoComplete Build Server starting" + + try + // Run the JSON RPC server + let serverTask = runServer () + serverTask.Wait() + 0 + with ex -> + printfn "Build server error: %s" ex.Message + 1 diff --git a/src/FsAutoComplete.BuildServer/WorkspaceOperations.fs b/src/FsAutoComplete.BuildServer/WorkspaceOperations.fs new file mode 100644 index 000000000..3b7daf5a3 --- /dev/null +++ b/src/FsAutoComplete.BuildServer/WorkspaceOperations.fs @@ -0,0 +1,46 @@ +namespace FsAutoComplete.BuildServer + +open System +open System.IO +open System.Threading.Tasks +open FsAutoComplete.Logging +open FsAutoComplete.BuildServerProtocol.JsonRpc +open FsAutoComplete.BuildServerProtocol.BuildServerProtocol + +/// Simple workspace operations for Build Server Protocol +module WorkspaceOperations = + + let private logger = LogProvider.getLoggerByName "WorkspaceOperations" + + /// Initialize workspace - for now just log and return success + let initializeWorkspace () = + logger.info (Log.setMessage "Initializing workspace...") + Task.FromResult(Result.Ok()) + + /// Peek workspace - simplified for now + let peekWorkspace (request: WorkspacePeekRequest) = + logger.info ( + Log.setMessage "Peeking workspace at {directory}" + >> Log.addContext "directory" request.Directory + ) + + let response = { Found = [||] } + Task.FromResult(Result.Ok response) + + /// Load workspace - simplified for now + let loadWorkspace (request: WorkspaceLoadRequest) = + logger.info ( + Log.setMessage "Loading workspace with {documentCount} documents" + >> Log.addContext "documentCount" request.TextDocuments.Length + ) + + let response = + { WorkspaceRoot = Environment.CurrentDirectory + Projects = [||] } + + Task.FromResult(Result.Ok response) + + /// Shutdown workspace + let shutdown () = + logger.info (Log.setMessage "Shutting down workspace...") + Task.FromResult(Result.Ok()) diff --git a/src/FsAutoComplete.BuildServer/paket.references b/src/FsAutoComplete.BuildServer/paket.references new file mode 100644 index 000000000..0dc4ba3bb --- /dev/null +++ b/src/FsAutoComplete.BuildServer/paket.references @@ -0,0 +1,2 @@ +FSharp.Core +Newtonsoft.Json \ No newline at end of file diff --git a/src/FsAutoComplete.BuildServerProtocol/BuildServerProtocol.fs b/src/FsAutoComplete.BuildServerProtocol/BuildServerProtocol.fs new file mode 100644 index 000000000..c478dd4f9 --- /dev/null +++ b/src/FsAutoComplete.BuildServerProtocol/BuildServerProtocol.fs @@ -0,0 +1,165 @@ +namespace FsAutoComplete.BuildServerProtocol + +open System +open Newtonsoft.Json.Linq + +/// Build Server Protocol types based on https://build-server-protocol.github.io/docs/specification.html +module BuildServerProtocol = + + /// Base types for BSP + type BuildClientCapabilities = { LanguageIds: string[] } + + type BuildServerCapabilities = + { CompileProvider: bool option + TestProvider: bool option + RunProvider: bool option + DebugProvider: bool option + InverseSourcesProvider: bool option + DependencySourcesProvider: bool option + DependencyModulesProvider: bool option + ResourcesProvider: bool option + OutputPathsProvider: bool option + BuildTargetChangedProvider: bool option + JvmRunEnvironmentProvider: bool option + JvmTestEnvironmentProvider: bool option + CanReload: bool option } + + /// Workspace/project discovery and loading + + type WorkspacePeekRequest = + { Directory: string + Deep: int + ExcludedDirs: string[] } + + type WorkspacePeekResponse = { Found: WorkspaceProjectState[] } + + and WorkspaceProjectState = + { Project: ProjectDescription + Crosswalk: Crosswalk[] option + Sdk: ProjectSdkInfo option } + + and ProjectDescription = + { Project: string + Name: string + Virtual: bool option + Dependencies: string[] option } + + and Crosswalk = + { MSBuildProject: string + ProjectFile: string } + + and ProjectSdkInfo = { Type: string; Path: string option } + + type WorkspaceLoadRequest = { TextDocuments: string[] } + + type WorkspaceLoadResponse = + { WorkspaceRoot: string + Projects: ProjectDetails[] } + + and ProjectDetails = + { Project: string + Name: string + SourceFiles: string[] + ProjectReferences: string[] + PackageReferences: PackageReference[] + FrameworkVersion: string + TargetFramework: string + OutputType: string + OutputFile: string + IsTestProject: bool option + Properties: Map option } + + and PackageReference = + { Name: string + Version: string + FullPath: string option } + + /// Build target related types + + type BuildTargetIdentifier = { Uri: string } + + type BuildTarget = + { Id: BuildTargetIdentifier + DisplayName: string option + BaseDirectory: string option + Tags: string[] + Capabilities: BuildTargetCapabilities + LanguageIds: string[] + Dependencies: BuildTargetIdentifier[] + DataKind: string option + Data: JObject option } + + and BuildTargetCapabilities = + { CanCompile: bool + CanTest: bool + CanRun: bool + CanDebug: bool } + + type WorkspaceBuildTargetsResult = { Targets: BuildTarget[] } + + /// Build/compile related types + + type CompileParams = + { Targets: BuildTargetIdentifier[] + OriginId: string option + Arguments: string[] option } + + type CompileResult = + { OriginId: string option + StatusCode: int + DataKind: string option + Data: JObject option } + + /// Diagnostics and notifications + + type Diagnostic = + { Range: Range + Severity: DiagnosticSeverity option + Code: string option + CodeDescription: CodeDescription option + Source: string option + Message: string + Tags: DiagnosticTag[] option + RelatedInformation: DiagnosticRelatedInformation[] option + Data: JObject option } + + and Range = { Start: Position; End: Position } + + and Position = { Line: int; Character: int } + + and DiagnosticSeverity = + | Error = 1 + | Warning = 2 + | Information = 3 + | Hint = 4 + + and CodeDescription = { Href: string } + + and DiagnosticTag = + | Unnecessary = 1 + | Deprecated = 2 + + and DiagnosticRelatedInformation = { Location: Location; Message: string } + + and Location = { Uri: string; Range: Range } + + type PublishDiagnosticsParams = + { TextDocument: TextDocumentIdentifier + BuildTarget: BuildTargetIdentifier + OriginId: string option + Diagnostics: Diagnostic[] + Reset: bool } + + and TextDocumentIdentifier = { Uri: string } + + /// Custom FSAC extensions for F# specific functionality + + type FSharpWorkspacePeekRequest = WorkspacePeekRequest + + type FSharpWorkspaceLoadRequest = + { TextDocuments: string[] + ExcludeProjectDirectories: string[] option } + + type FSharpProjectRequest = { Project: string } + + type FSharpProjectResponse = { Project: ProjectDetails } diff --git a/src/FsAutoComplete.BuildServerProtocol/FsAutoComplete.BuildServerProtocol.fsproj b/src/FsAutoComplete.BuildServerProtocol/FsAutoComplete.BuildServerProtocol.fsproj new file mode 100644 index 000000000..27ff4e4f2 --- /dev/null +++ b/src/FsAutoComplete.BuildServerProtocol/FsAutoComplete.BuildServerProtocol.fsproj @@ -0,0 +1,14 @@ + + + + netstandard2.0;net8.0 + true + true + FS0025 + + + + + + + \ No newline at end of file diff --git a/src/FsAutoComplete.BuildServerProtocol/JsonRpc.fs b/src/FsAutoComplete.BuildServerProtocol/JsonRpc.fs new file mode 100644 index 000000000..8355a7fc4 --- /dev/null +++ b/src/FsAutoComplete.BuildServerProtocol/JsonRpc.fs @@ -0,0 +1,41 @@ +namespace FsAutoComplete.BuildServerProtocol + +open Newtonsoft.Json.Linq + +/// Core JSON RPC 2.0 protocol types for communication +module JsonRpc = + + /// JSON RPC request message + type JsonRpcRequest = + { Id: JToken + Method: string + Params: JToken option } + + /// JSON RPC response message + type JsonRpcResponse = + { Id: JToken option + Result: JToken option + Error: JsonRpcError option } + + /// JSON RPC error object + and JsonRpcError = + { Code: int + Message: string + Data: JToken option } + + /// JSON RPC notification message + type JsonRpcNotification = + { Method: string + Params: JToken option } + + /// Standard JSON RPC error codes + module ErrorCodes = + let ParseError = -32700 + let InvalidRequest = -32600 + let MethodNotFound = -32601 + let InvalidParams = -32602 + let InternalError = -32603 + + // Server error range + let ServerErrorStart = -32099 + let ServerErrorEnd = -32000 diff --git a/src/FsAutoComplete.BuildServerProtocol/paket.references b/src/FsAutoComplete.BuildServerProtocol/paket.references new file mode 100644 index 000000000..0dc4ba3bb --- /dev/null +++ b/src/FsAutoComplete.BuildServerProtocol/paket.references @@ -0,0 +1,2 @@ +FSharp.Core +Newtonsoft.Json \ No newline at end of file diff --git a/src/FsAutoComplete/BuildServerWorkspaceLoader.fs b/src/FsAutoComplete/BuildServerWorkspaceLoader.fs new file mode 100644 index 000000000..9cf9c67da --- /dev/null +++ b/src/FsAutoComplete/BuildServerWorkspaceLoader.fs @@ -0,0 +1,110 @@ +namespace FsAutoComplete.Lsp + +open System +open System.IO +open System.Diagnostics +open System.Threading.Tasks +open System.Text +open FsAutoComplete.Logging +open Ionide.ProjInfo.ProjectSystem +open Ionide.ProjInfo.Types +open Ionide.ProjInfo + +/// Build Server Protocol client for communicating with the separate build server process +module BuildServerClient = + + let logger = LogProvider.getLoggerByName "BuildServerClient" + + type BuildServerProcess = + { Process: Process + Writer: StreamWriter + Reader: StreamReader } + + let mutable private currentBuildServer: BuildServerProcess option = None + + /// Start the build server process + let startBuildServer (buildServerPath: string) : Task> = + task { + try + logger.info (Log.setMessage "Starting build server") + + let startInfo = ProcessStartInfo() + startInfo.FileName <- "dotnet" + startInfo.Arguments <- buildServerPath + startInfo.UseShellExecute <- false + startInfo.RedirectStandardInput <- true + startInfo.RedirectStandardOutput <- true + startInfo.RedirectStandardError <- true + startInfo.CreateNoWindow <- true + + let proc = Process.Start(startInfo) + + if isNull proc then + return Error "Failed to start build server process" + else + let writer = new StreamWriter(proc.StandardInput.BaseStream, Encoding.UTF8) + let reader = new StreamReader(proc.StandardOutput.BaseStream, Encoding.UTF8) + + let buildServer = + { Process = proc + Writer = writer + Reader = reader } + + currentBuildServer <- Some buildServer + logger.info (Log.setMessage "Build server started successfully") + return Ok buildServer + with ex -> + logger.error (Log.setMessage "Failed to start build server" >> Log.addExn ex) + return Error ex.Message + } + + /// Send a message to the build server + let sendMessage (buildServer: BuildServerProcess) (message: string) : Task> = + task { + try + logger.debug (Log.setMessage "Sending message to build server") + + do! buildServer.Writer.WriteLineAsync(message) + do! buildServer.Writer.FlushAsync() + + let! response = buildServer.Reader.ReadLineAsync() + + if isNull response then + return Error "Build server returned null response" + else + logger.debug (Log.setMessage "Received response from build server") + return Ok response + with ex -> + logger.error (Log.setMessage "Failed to communicate with build server" >> Log.addExn ex) + return Error ex.Message + } + + /// Stop the build server process + let stopBuildServer (buildServer: BuildServerProcess) : Task = + task { + try + logger.info (Log.setMessage "Stopping build server") + + buildServer.Writer.Dispose() + buildServer.Reader.Dispose() + + if not buildServer.Process.HasExited then + buildServer.Process.Kill() + let! _exited = buildServer.Process.WaitForExitAsync() + () + + buildServer.Process.Dispose() + + logger.info (Log.setMessage "Build server stopped") + with ex -> + logger.error (Log.setMessage "Error stopping build server" >> Log.addExn ex) + } + +/// Factory function to create a Build Server workspace loader +module BuildServerWorkspaceLoaderFactory = + + let create (toolsPath) : IWorkspaceLoader = + let logger = LogProvider.getLoggerByName "BuildServerWorkspaceLoaderFactory" + logger.info (Log.setMessage "Creating BuildServerWorkspaceLoader - falling back to regular loader for now") + // For now, fall back to the regular workspace loader until we implement the full BSP + Ionide.ProjInfo.WorkspaceLoader.Create(toolsPath, []) diff --git a/src/FsAutoComplete/FsAutoComplete.fsproj b/src/FsAutoComplete/FsAutoComplete.fsproj index d50fd1309..34074b361 100644 --- a/src/FsAutoComplete/FsAutoComplete.fsproj +++ b/src/FsAutoComplete/FsAutoComplete.fsproj @@ -22,6 +22,7 @@ + @@ -49,6 +50,7 @@ + fsautocomplete diff --git a/src/FsAutoComplete/Parser.fs b/src/FsAutoComplete/Parser.fs index 3beed7817..9ba961233 100644 --- a/src/FsAutoComplete/Parser.fs +++ b/src/FsAutoComplete/Parser.fs @@ -15,6 +15,7 @@ open OpenTelemetry open OpenTelemetry.Resources open OpenTelemetry.Trace open OpenTelemetry.Metrics +open FsAutoComplete.Lsp.BuildServerWorkspaceLoaderFactory module Parser = open FsAutoComplete.Core @@ -102,6 +103,12 @@ module Parser = "Use Transparent Compiler in FSharp.Compiler.Services. Should have better performance characteristics, but is experimental. See https://github.com/dotnet/fsharp/pull/15179 for more details." ) + let useBuildServerOption = + Option( + "--use-build-server", + "Enable the separate build server process for MSBuild evaluation (experimental)." + ) + let stateLocationOption = Option( "--state-directory", @@ -124,57 +131,62 @@ module Parser = rootCommand.AddOption stateLocationOption rootCommand.AddOption otelTracingOption rootCommand.AddOption useTransparentCompilerOption + rootCommand.AddOption useBuildServerOption // for back-compat - we removed some options and this broke some clients. rootCommand.TreatUnmatchedTokensAsErrors <- false rootCommand.SetHandler( - Func<_, _, _, _, Task>(fun projectGraphEnabled stateDirectory adaptiveLspEnabled useTransparentCompiler -> - let workspaceLoaderFactory = - fun toolsPath -> - if projectGraphEnabled then - Ionide.ProjInfo.WorkspaceLoaderViaProjectGraph.Create(toolsPath, ProjectLoader.globalProperties) + Func<_, _, _, _, _, Task> + (fun projectGraphEnabled stateDirectory adaptiveLspEnabled useTransparentCompiler useBuildServer -> + let workspaceLoaderFactory = + fun toolsPath -> + if useBuildServer then + BuildServerWorkspaceLoaderFactory.create toolsPath + elif projectGraphEnabled then + Ionide.ProjInfo.WorkspaceLoaderViaProjectGraph.Create(toolsPath, ProjectLoader.globalProperties) + else + Ionide.ProjInfo.WorkspaceLoader.Create(toolsPath, ProjectLoader.globalProperties) + + let sourceTextFactory: ISourceTextFactory = new RoslynSourceTextFactory() + + let dotnetPath = + if + Environment.ProcessPath.EndsWith("dotnet", StringComparison.Ordinal) + || Environment.ProcessPath.EndsWith("dotnet.exe", StringComparison.Ordinal) + then + // this is valid when not running as a global tool + Some(FileInfo(Environment.ProcessPath)) else - Ionide.ProjInfo.WorkspaceLoader.Create(toolsPath, ProjectLoader.globalProperties) - - let sourceTextFactory: ISourceTextFactory = new RoslynSourceTextFactory() - - let dotnetPath = - if - Environment.ProcessPath.EndsWith("dotnet", StringComparison.Ordinal) - || Environment.ProcessPath.EndsWith("dotnet.exe", StringComparison.Ordinal) - then - // this is valid when not running as a global tool - Some(FileInfo(Environment.ProcessPath)) - else - None - - let toolsPath = - Ionide.ProjInfo.Init.init (IO.DirectoryInfo Environment.CurrentDirectory) dotnetPath - - let lspFactory = - if adaptiveLspEnabled then - fun () -> - AdaptiveFSharpLspServer.startCore - toolsPath - workspaceLoaderFactory - sourceTextFactory - useTransparentCompiler - else - fun () -> - AdaptiveFSharpLspServer.startCore - toolsPath - workspaceLoaderFactory - sourceTextFactory - useTransparentCompiler - - let result = AdaptiveFSharpLspServer.start lspFactory - - Task.FromResult result), + None + + let toolsPath = + Ionide.ProjInfo.Init.init (IO.DirectoryInfo Environment.CurrentDirectory) dotnetPath + + let lspFactory = + if adaptiveLspEnabled then + fun () -> + AdaptiveFSharpLspServer.startCore + toolsPath + workspaceLoaderFactory + sourceTextFactory + useTransparentCompiler + else + fun () -> + AdaptiveFSharpLspServer.startCore + toolsPath + workspaceLoaderFactory + sourceTextFactory + useTransparentCompiler + + let result = AdaptiveFSharpLspServer.start lspFactory + + Task.FromResult result), projectGraphOption, stateLocationOption, adaptiveLspServerOption, - useTransparentCompilerOption + useTransparentCompilerOption, + useBuildServerOption ) rootCommand