├── .config └── dotnet-tools.json ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .envrc ├── .github └── workflows │ ├── publish.yml │ └── testing.yml ├── .gitignore ├── .paket └── Paket.Restore.targets ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── Icon512.png ├── LICENSE.md ├── Notes └── Tooltips.md ├── NuGet.Config ├── README.md ├── TODO.md ├── Understanding FSAC.md ├── appveyor.yml ├── changelog.md ├── client └── extension.ts ├── fsharp-language-server.sln ├── package-lock.json ├── package.json ├── paket.dependencies ├── paket.lock ├── sample ├── BadProject │ └── BadProject.fsproj ├── CSharpProject.AssemblyName │ ├── CSharpProject.AssemblyName.csproj │ └── Class1.cs ├── CSharpProject │ ├── CSharpProject.csproj │ └── Class1.cs ├── DependsOn │ ├── DependsOn.fsproj │ └── MyLibrary.fs ├── EmptyProject │ └── EmptyProject.fsproj ├── FSharpKoans.Core │ └── FSharpKoans.Core.fsproj ├── HasLocalDll │ ├── HasLocalDll.fsproj │ └── Program.fs ├── HasPackageReference │ ├── HasPackageReference.fsproj │ └── Library.fs ├── HasTests │ ├── HasTests.fsproj │ └── MyTests.fs ├── IndirectDep │ ├── IndirectDep.fsproj │ └── IndirectLibrary.fs ├── Issue28 │ ├── Issue28.fsproj │ └── main.fs ├── MainProject │ ├── BreakParentReference.fs │ ├── BreakParentTarget.fs │ ├── CompleteInString.fs │ ├── Completions.fs │ ├── CreateTypeError.fs │ ├── DeclareSymbol.fs │ ├── Hover.fs │ ├── InterfaceInModule.fs │ ├── MainProject.fsproj │ ├── NotInFsproj.fs │ ├── Reference.fs │ ├── ReferenceDependsOn.fs │ ├── ReferenceIndirectDep.fs │ ├── RenameReference.fs │ ├── RenameTarget.fs │ ├── SignatureHelp.fs │ ├── Target.fs │ ├── UseSymbol.fs │ └── WrongType.fs ├── Net5Console │ ├── Net5Console.fsproj │ └── main.fs ├── Net6Console │ ├── Net6Console.fsproj │ └── main.fs ├── NetCoreApp3 │ ├── NetCoreApp3.fsproj │ └── main.fs ├── NotBuilt │ ├── NotBuilt.fs │ └── NotBuilt.fsproj ├── ReferenceCSharp.AssemblyName │ ├── Library.fs │ └── ReferenceCSharp.AssemblyName.fsproj ├── ReferenceCSharp │ ├── Library.fs │ └── ReferenceCSharp.fsproj ├── Script │ ├── LoadedByScript.fs │ └── MainScript.fsx ├── Signature │ ├── HasSignature.fs │ ├── HasSignature.fsi │ └── Signature.fsproj ├── SlnReferences │ ├── Common.fs │ ├── Main.fs │ ├── OrphanProject.fsproj │ ├── ReferencedProject.fsproj │ └── SlnReferences.sln └── TemplateParams │ └── TemplateParams.fsproj ├── scripts ├── build.ps1 ├── build.sh ├── debug.sh ├── install.sh ├── paketActions.sh ├── restore.sh └── test.sh ├── shell.nix ├── spacemacs └── fsharp2 │ ├── config.el │ ├── funcs.el │ └── packages.el ├── src ├── FSharpLanguageServer │ ├── Conversions.fs │ ├── DebounceCheck.fs │ ├── FSharpLanguageServer.fsproj │ ├── Goto.fs │ ├── Navigation.fs │ ├── Program.fs │ ├── ProgressBar.fs │ ├── ProjectManager.fs │ ├── Semantic.fs │ ├── SourceLink.fs │ ├── SyntaxTreeOps.fs │ ├── TipFormatter.fs │ ├── ToolTips │ │ ├── Format.fs │ │ ├── ToolTip.fs │ │ └── XmlDoc.fs │ ├── UnusedDeclarations.fs │ ├── bin │ │ └── Release │ │ │ └── netcoreapp2.0 │ │ │ └── assembly │ │ │ └── README.md │ └── paket.references ├── LSP │ ├── DocumentStore.fs │ ├── LSP.fsproj │ ├── LanguageServer.fs │ ├── Log.fs │ ├── Parser.fs │ ├── Ser.fs │ ├── Tokenizer.fs │ ├── Types │ │ ├── BaseTypes.fs │ │ ├── SemanticToken.fs │ │ └── Types.fs │ └── paket.references └── ProjectCracker │ ├── ProjectCracker.fs │ ├── ProjectCracker.fsproj │ └── paket.references ├── syntaxes ├── LICENSE.md ├── README.md ├── fsharp.configuration.json ├── fsharp.fsi.json ├── fsharp.fsx.json └── fsharp.json ├── tests ├── FSharpLanguageServer.Tests │ ├── Common.fs │ ├── FSharpLanguageServer.Tests.fsproj │ ├── FormattingTests.fs │ ├── ProjectManagerTests.fs │ ├── ServerTests.fs │ └── paket.references ├── LSP.Tests │ ├── DocumentStoreTests.fs │ ├── JsonTests.fs │ ├── LSP.Tests.fsproj │ ├── LanguageServerTests.fs │ ├── ParserTests.fs │ ├── TokenizerTests.fs │ └── paket.references ├── ProjectCracker.Tests │ ├── Common.fs │ ├── ProjectCracker.Tests.fsproj │ ├── ProjectCrackerTests.fs │ └── paket.references ├── ProjectInfo │ ├── Program.cs │ └── ProjectInfo.csproj └── Scratch │ ├── Program.fs │ ├── Scratch.fsproj │ └── paket.references ├── tsconfig.json └── videos ├── Autocomplete.mov.gif ├── DebugTest.mov.gif ├── DocumentSymbols.mov.gif ├── EmacsLspMode.gif ├── FindReferences.mov.gif ├── GoToDefinition.mov.gif ├── Hover.mov.gif ├── LSP-vs-Ionide-Warm.gif ├── RenameSymbol.mov.gif ├── ShowErrors.mov.gif ├── SignatureHelp.mov.gif ├── VimDeoplete.mov.gif └── WorkspaceSymbols.mov.gif /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "paket": { 6 | "version": "6.2.1", 7 | "commands": [ 8 | "paket" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/dotnet/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] .NET version: 6.0, 5.0, 3.1, 6.0-bullseye, 5.0-bullseye, 3.1-bullseye, 6.0-focal, 5.0-focal, 3.1-focal 4 | ARG VARIANT="6.0-bullseye-slim" 5 | FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT} 6 | 7 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 8 | ARG NODE_VERSION="none" 9 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 10 | 11 | # [Optional] Uncomment this section to install additional OS packages. 12 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 13 | # && apt-get -y install --no-install-recommends 14 | 15 | # [Optional] Uncomment this line to install global node packages. 16 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/dotnet 3 | { 4 | "name": "C# (.NET)", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | "args": { 8 | // Update 'VARIANT' to pick a .NET Core version: 3.1, 5.0, 6.0 9 | // Append -bullseye or -focal to pin to an OS version. 10 | "VARIANT": "6.0", 11 | // Options 12 | "NODE_VERSION": "lts/*" 13 | } 14 | }, 15 | 16 | // Set *default* container specific settings.json values on container create. 17 | "settings": {}, 18 | 19 | // Add the IDs of extensions you want installed when the container is created. 20 | "extensions": [ 21 | "ms-dotnettools.csharp" 22 | ], 23 | 24 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 25 | // "forwardPorts": [5000, 5001], 26 | 27 | // [Optional] To reuse of your local HTTPS dev cert: 28 | // 29 | // 1. Export it locally using this command: 30 | // * Windows PowerShell: 31 | // dotnet dev-certs https --trust; dotnet dev-certs https -ep "$env:USERPROFILE/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" 32 | // * macOS/Linux terminal: 33 | // dotnet dev-certs https --trust; dotnet dev-certs https -ep "${HOME}/.aspnet/https/aspnetapp.pfx" -p "SecurePwdGoesHere" 34 | // 35 | // 2. Uncomment these 'remoteEnv' lines: 36 | // "remoteEnv": { 37 | // "ASPNETCORE_Kestrel__Certificates__Default__Password": "SecurePwdGoesHere", 38 | // "ASPNETCORE_Kestrel__Certificates__Default__Path": "/home/vscode/.aspnet/https/aspnetapp.pfx", 39 | // }, 40 | // 41 | // 3. Do one of the following depending on your scenario: 42 | // * When using GitHub Codespaces and/or Remote - Containers: 43 | // 1. Start the container 44 | // 2. Drag ~/.aspnet/https/aspnetapp.pfx into the root of the file explorer 45 | // 3. Open a terminal in VS Code and run "mkdir -p /home/vscode/.aspnet/https && mv aspnetapp.pfx /home/vscode/.aspnet/https" 46 | // 47 | // * If only using Remote - Containers with a local container, uncomment this line instead: 48 | // "mounts": [ "source=${env:HOME}${env:USERPROFILE}/.aspnet/https,target=/home/vscode/.aspnet/https,type=bind" ], 49 | 50 | // Use 'postCreateCommand' to run commands after the container is created. 51 | // "postCreateCommand": "dotnet restore", 52 | 53 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 54 | "remoteUser": "vscode" 55 | } 56 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | eval "$(lorri direnv)" 2 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish-extension 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | name: 7 | description: 'test' 8 | required: false 9 | default: 'hi' 10 | 11 | jobs: 12 | pubBinaries: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Setupdotnet 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: 6.0.x 21 | - uses: actions/cache@v2 22 | with: 23 | path: ./paket 24 | key: ${{ runner.os }}-${{ hashFiles('**/paket.lock','**/paket.dependencies') }} 25 | - run: bash ./scripts/paketActions.sh 26 | - name: Restore dependencies 27 | run: dotnet tool restore 28 | - name: paket install 29 | run: dotnet paket install 30 | - name: publish 31 | run: dotnet publish -c Release src/FSharpLanguageServer 32 | - uses: actions/upload-artifact@v2 33 | # Upload the artifact so the MacOS runner do something with it 34 | with: 35 | name: CompiledProject 36 | path: src/FSharpLanguageServer/bin/Release/net6.0/publish 37 | #I think i need to do something wiith these to make the be accessable to the next action(upload artifact i think) 38 | publish: 39 | runs-on: ubuntu-latest 40 | needs: pubBinaries 41 | if: success() 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: actions/setup-node@v2 45 | with: 46 | node-version: '14' 47 | 48 | - run: npm install 49 | - uses: actions/download-artifact@master 50 | with: 51 | name: CompiledProject 52 | path: src/FSharpLanguageServer/bin/Release/net6.0/publish 53 | - run: npx vsce package 54 | - uses: actions/download-artifact@v2 55 | - run: npx vsce publish --packagePath $(find . -iname *.vsix) 56 | env: 57 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 58 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: testing 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | jobs: 9 | build-test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Setup .NET 15 | uses: actions/setup-dotnet@v1 16 | with: 17 | dotnet-version: 6.0.x 18 | - uses: actions/cache@v2 19 | with: 20 | path: ./paket 21 | key: ${{ runner.os }}-${{ hashFiles('**/paket.lock','**/paket.dependencies') }} 22 | - run: bash ./scripts/paketActions.sh 23 | - name: Restore dependencies 24 | run: dotnet tool restore 25 | - name: paket setup 26 | run: dotnet paket install 27 | - run: dotnet restore 28 | - name: Build 29 | run: dotnet build --no-restore 30 | - name: Test 31 | run: dotnet test --no-build --verbosity normal -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | out/ 4 | packages/ 5 | node_modules/ 6 | .idea/ 7 | build.vsix 8 | Scratch.fsx 9 | Scratch.ts 10 | fsharp-language-server.sln.DotSettings.user 11 | .vs 12 | 13 | .fake 14 | .ionide 15 | *.vsix 16 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": ".NET Core Attach", 10 | "type": "coreclr", 11 | "request": "attach", 12 | "processId": "${command:pickProcess}", 13 | "sourceLinkOptions": { 14 | "*": { 15 | "enabled": true 16 | } 17 | }, 18 | "justMyCode": false 19 | }, 20 | // { 21 | // "name": ".NET Core Attach", 22 | // "type": "coreclr", 23 | // "request": "attach", 24 | // "processId": "${command:pickProcess}" 25 | // }, 26 | { 27 | "name": "Extension", 28 | "type": "extensionHost", 29 | "request": "launch", 30 | "runtimeExecutable": "${execPath}", 31 | "args": [ 32 | "--extensionDevelopmentPath=${workspaceFolder}" 33 | ], 34 | "outFiles": [ 35 | "${workspaceFolder}/out/**/*.js" 36 | ], 37 | "preLaunchTask": "npm: compile" 38 | }, 39 | { 40 | "name": "Extension Tests", 41 | "type": "extensionHost", 42 | "request": "launch", 43 | "runtimeExecutable": "${execPath}", 44 | "args": [ 45 | "--extensionDevelopmentPath=${workspaceFolder}", 46 | "--extensionTestsPath=${workspaceFolder}/out/test" 47 | ], 48 | "outFiles": [ 49 | "${workspaceFolder}/out/test/**/*.js" 50 | ], 51 | "preLaunchTask": "npm: watch" 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | ".git/": true, 4 | ".idea/": true, 5 | "node_modules/": true, 6 | "out/": true, 7 | "**/bin/": false, 8 | "**/obj/": true 9 | }, 10 | "files.trimTrailingWhitespace": false, 11 | "nixEnvSelector.suggestion": false, 12 | "nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix", 13 | //"fsharp.useSystemDotnet": true 14 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | ] 5 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | sample/** 2 | tests/** 3 | scripts/** 4 | spacemacs/** 5 | videos/** 6 | client/** 7 | **/obj/** 8 | **.vsix 9 | .vs 10 | .vscode 11 | .paket 12 | .ionide 13 | .devcontainer 14 | tests 15 | 16 | src/** 17 | packages 18 | #!src/FSharpLanguageServer/bin/Release/net6.0/osx.10.11-x64/publish/** 19 | #!src/FSharpLanguageServer/bin/Release/net6.0/win10-x64/publish/** 20 | #!src/FSharpLanguageServer/bin/Release/net6.0/linux-x64/publish/** 21 | !src/FSharpLanguageServer/bin/Release/net6.0/assembly/README.md 22 | !src/FSharpLanguageServer/bin/Release/net6.0//publish/** -------------------------------------------------------------------------------- /Icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/Icon512.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [F# Language Server Contributors](https://github.com/fsprojects/fsharp-language-server/graphs/contributors) 4 | All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | ----- 25 | 26 | The contents of the spacemacs directory are derived from the fsharp layer in [Spacemacs](https://github.com/syl20bnr/spacemacs), and are licensed under the GPLv3. 27 | -------------------------------------------------------------------------------- /Notes/Tooltips.md: -------------------------------------------------------------------------------- 1 | # Some Notes on tooltips 2 | ## Problems 3 | ### The tooltip doesn't have highlight markdown correctly. 4 | - Sometimes becuase of the way the xml is parsed and converted to markdown for display there are edge cases where markdown strings can end up being malformed and you might get say a big section in bold or some such. -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unmaintained 2 | 3 | All of the issues I've had with the other fsharp language server extension ionide have been resolved and the fsharp team have plans to make an official language server. I see no reason for this project to exist 4 | 5 | # F# Language Server Updated 6 | 7 | 8 | 9 | **This has been forked from GeorgeWfraser's original project because that appears to be unmaintained.** 10 | 11 | **I have access to the original repository so all development is continuing there. https://github.com/fsprojects/fsharp-language-server** 12 | 13 | **This now exists only as a way to publish new versions of the extension because I don't have access to publishing the original** 14 | 15 | It has been updated to support fcs 41 and .NET 6.0. 16 | I have used code from [FSharpAutoComplete](https://github.com/fsharp/FsAutoComplete) and some adjustments to the original to add some features: 17 | 18 | - Better hover docs 19 | - Working documentation for system types 20 | - Semantic tokenization 21 | - Support semantic tokens 22 | - Have pretty multiline function signatures in hover docs 23 | 24 | 25 | I may at some point work on supporting the vim and emacs versions of these extensions but as i use neither, I have not at this time. 26 | 27 | My work here is done specificallly in reponse to this issue with Ionide [here](https://github.com/fsharp/FsAutoComplete/issues/805), which makes it unusuable for me and potentially many others. 28 | Almost all credit for this should go to georgewfraser and the guys of at FSAC. Really I've mostly just smashed stuff together, added glue and fixed some bugs 29 | 30 | ## Now the original descritption 31 | 32 | This project is an implementation of the [language server protocol](https://microsoft.github.io/language-server-protocol/) using the [F# Compiler Service](https://fsharp.github.io/FSharp.Compiler.Service/). 33 | 34 | ![https://ci.appveyor.com/api/projects/status/github/fsprojects/fsharp-language-server?svg=true](https://ci.appveyor.com/api/projects/status/github/fsprojects/fsharp-language-server?svg=true) 35 | 36 | ## Features 37 | 38 | ### Hover 39 | ![Hover](videos/Hover.mov.gif) 40 | 41 | ### Autocomplete 42 | ![Autocomplete](videos/Autocomplete.mov.gif) 43 | 44 | ### Method signature help 45 | ![Signature help](videos/SignatureHelp.mov.gif) 46 | 47 | ### Find symbols in document 48 | ![Document symbols](videos/DocumentSymbols.mov.gif) 49 | 50 | ### Find symbols in workspace 51 | ![Workspace symbols](videos/WorkspaceSymbols.mov.gif) 52 | 53 | ### Go-to-definition 54 | ![Go to definition](videos/GoToDefinition.mov.gif) 55 | 56 | ### Find references 57 | ![Find references](videos/FindReferences.mov.gif) 58 | 59 | ### Rename symbol 60 | ![Rename symbol](videos/RenameSymbol.mov.gif) 61 | 62 | ### Show errors on save 63 | ![Show errors](videos/ShowErrors.mov.gif) 64 | 65 | ### Run & Debug tests 66 | ![Debug test](videos/DebugTest.mov.gif) 67 | 68 | ## Code structure 69 | 70 | The language server protocol (LSP) is very similar to the API defined by the F# compiler service (FCS); most of the implementation is devoted to translating between the types used by FCS and the JSON representation of LSP. 71 | 72 | - client/extension.ts: Client-side VSCode launcher 73 | - sample: Example projects used by tests 74 | - scripts: Scripts for building and testing 75 | - src/LSP: Server-side implementation of [language server protocol](https://microsoft.github.io/language-server-protocol/specification) 76 | - src/ProjectCracker: Figures out [F# compiler options](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/compiler-options) using [Buildalyzer](https://github.com/daveaglick/Buildalyzer) and the MSBuild API. 77 | - src/FSharpLanguageServer: F# language server 78 | - tests/LSP.Tests 79 | - tests/ProjectCracker.Tests 80 | - tests/FSharpLanguageServer.Tests 81 | - videos: Animated GIFs on this page 82 | - 83 | ## Installation 84 | 85 | ### VSCode 86 | 87 | [Install from the VSCode extension marketplace](https://marketplace.visualstudio.com/items?itemName=faldor20.fsharp-language-server-updated) 88 | 89 | ### Neovim and Vim 90 | 91 | Clone this repo to your system and build it: 92 | 93 | ``` 94 | npm install 95 | dotnet build -c Release 96 | ``` 97 | 98 | If using a distribution based on Arch Linux, you can also install it from the [AUR](https://aur.archlinux.org/packages/fsharp-language-server/) 99 | (Still installs the old version) 100 | Install [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim) 101 | 102 | Update your vim config to point LanguageClient-neovim to the FSharp Language Server for fsharp filetypes: 103 | ``` 104 | let g:LanguageClient_serverCommands = { 105 | \ 'fsharp': ['dotnet', '/Users/name/code/fsharp-language-server/src/FSharpLanguageServer/bin/Release/netcoreapp3.0/target/FSharpLanguageServer.dll'] 106 | \ } 107 | ``` 108 | Open an fsharp file, move the cursor, and call functions. Mappings are up to you: 109 | * Hover `call LanguageClient#textDocument_hover()` 110 | * Rename: `call LanguageClient#textDocument_rename()` 111 | * Definition: `call LanguageClient#textDocument_definition()` 112 | * etc... 113 | 114 | Neovim with Deoplete completion:\ 115 | ![VimDeoplete](videos/VimDeoplete.mov.gif) 116 | 117 | (alternatively there is another vim language server plugin [vim-lsp](https://github.com/prabirshrestha/vim-lsp) but this one hasn't been tried. 118 | 119 | ### Emacs 120 | 121 | #### Spacemacs 122 | 123 | Clone this repo to your system and build it: 124 | 125 | ``` 126 | npm install 127 | 128 | # Pick the appropriate target based upon your OS 129 | dotnet publish -c Release -r linux-x64 src/FSharpLanguageServer 130 | dotnet publish -c Release -r osx.10.11-x64 src/FSharpLanguageServer 131 | dotnet publish -c Release -r win10-x64 src/FSharpLanguageServer 132 | ``` 133 | 134 | Make sure that the FSharpLanguageServer (in `src/FSharpLanguageServer/bin/Release/netcoreapp3.0/PLATFORM/publish`) is in your PATH. Alternatively, you can set the path to the server executable manually within your .spacemacs user-config: 135 | 136 | ``` 137 | (setq fsharp2-lsp-executable "/path/to/FSharpLanguageServer") 138 | ``` 139 | 140 | Since the stock fsharp layer does not currently include LSP support, you will need to use the fsharp2 layer (a fork of fsharp) which does. To use fsharp2, copy the custom layer into your Spacemacs private layers directory. In order for this layer to work, you must be on the Spacemacs **develop** branch, since the LSP layer is not yet available in Spacemacs master. 141 | 142 | ``` 143 | cp -r spacemacs/fsharp2 ~/.emacs.d/private 144 | ``` 145 | 146 | Finally, make sure that you have these layers enabled in your dotspacemacs-configuration-layers. You will need to remove the fsharp layer if you have it, since fsharp2 conflicts with it. 147 | 148 | - lsp 149 | - fsharp2 150 | - syntax-checking 151 | - auto-completion 152 | 153 | ![EmacsLspMode](videos/EmacsLspMode.gif) 154 | 155 | ## How is this project different than [Ionide](https://github.com/ionide)? 156 | Ionide is a suite of F# plugins for VSCode; F# language server is analagous to the [FSAC](https://github.com/fsharp/FsAutoComplete) component. 157 | 158 | The implementation is a thin wrapper around [F# Compiler Service](https://fsharp.github.io/FSharp.Compiler.Service/) and is heavily focused on performance. For example, autocompleting in medium-sized file in F# Language Server (left) and Ionide (right): 159 | 160 | ![Autocomplete warm](videos/LSP-vs-Ionide-Warm.gif) 161 | 162 | # Contributing 163 | 164 | Please do! 165 | 166 | Any help is very much appreciated, issues, PR's, even just asking a question about how something works. I'm happy to help and be helped. 167 | 168 | ## Building 169 | 170 | Run : 171 | 172 | - ``npm install`` to setup node deps (not needed unless you plan to build the vsix extension package) 173 | 174 | - ``dotnet tool restore`` to install paket 175 | 176 | - ``dotnet paket install`` to install all dependencies 177 | 178 | Then refer to the build scripts. 179 | 180 | Essentially you just publish the language server with ``dotnet publish -c Release src/FSharpLanguageServer`` then run ``vsce package -o build.vsix`` to package it up 181 | 182 | If you want to try your newly created extension run ``code --install-extension build.vsix`` 183 | 184 | You can also change the Fsharp.debug setting in vscode which will cause the installed extension to try to build fslsp when it launches. Very handy for testing many smaller changes without republishing the extension. 185 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Highlighting 2 | - UnionConstructor { ... } is highlighted like seq { ... } 3 | - ``some-name`` is highlighted wrong at - 4 | - ``name`` is highlighted like a function name regardless of context 5 | - "string format %d" doesn't format %d 6 | - @"\w" doesn't highlight regexes 7 | - Missing keywords: 8 | - to 9 | - downto 10 | - type 11 | - not 12 | - done 13 | 14 | # Cleanup 15 | 16 | # Bugs 17 | - Autocompleting in strings and comments 18 | - Crack FCS 19 | - Reload options when .fsx is saved 20 | - Invalidate check results when referenced .dlls are modified 21 | - Projects targeting netstandard2.0 show fake errors 22 | - Save upstream file not triggering re-lint 23 | - Unused-open is sometimes wrong??? See ProgressBar.fs 24 | - Concurrency errors; use a single thread for everything except FSharpCompilerService ops 25 | - Set --framework in test command 26 | 27 | # Optimizations 28 | - Add analyze-incrementally operation to F# compiler 29 | 30 | # Features 31 | - Allow emitting obj/FscArgs.txt as a project-cracker backup 32 | - fsharp.task.test problem-matchers 33 | - Emit .trx files from tests, and use them to highlight failed tests 34 | - Show "you need to press play" popup the first time the user debugs something 35 | - Popup to do restore, like C# 36 | - Prompt the user to build projects when no obj/ directory is present -------------------------------------------------------------------------------- /Understanding FSAC.md: -------------------------------------------------------------------------------- 1 | Goto definiton: 2 | Tries to find the assembly and filename using getDelaration 3 | if it is within the same project all good return location and go there 4 | if it is in a refernced libararry use the assembly and try to get the sourceLink file -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | 2 | image: 3 | - Visual Studio 2019 4 | - Ubuntu 5 | before_build: 6 | # Display .NET Core version 7 | - dotnet --list-sdks 8 | - dotnet --version 9 | # Restore deps 10 | - dotnet restore 11 | build_script: 12 | - dotnet build 13 | before_test: 14 | - bash scripts/restore.sh 15 | test_script: 16 | - dotnet test tests/LSP.Tests 17 | - dotnet test tests/ProjectCracker.Tests 18 | - dotnet test tests/FSharpLanguageServer.Tests 19 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## 0.1.60 2 | - Added support for any text inside a /// comment appearing in hover tooltips. 3 | - Fixed bug that inserted annoying ** into empty tooltips 4 | - Renamed Buildalyzer artifacts location 5 | ## 0.1.51 6 | - New and improved logging 7 | - MangleMaxine added improved grammars 8 | - Fixed Buildalyzer deleting build artifacts 9 | - Added paket 10 | ## 0.1.5 11 | 12 | Improved signature help and hover for methods in classes. Both now include parameter information and possible exceptions 13 | ## 0.1.41 14 | Fixed bug with finding dotnet executable on windows 15 | ## 0.1.40 16 | Switched from using binaries to publishing a netcore dependant dll. 17 | Massively reduces extension size and also reduces problems with running binaries on strange operating systems or not having certain dependencies 18 | 19 | 20 | ## 0.1.32 21 | fixed a few minor tooltip issues, including issue #1 22 | trying out publishing from linux to fix permissions problems 23 | 24 | 25 | ## 0.1.31 26 | Just little maintenance changes to readmes and icons and stuff to differentiate form fsharp language server 27 | -------------------------------------------------------------------------------- /client/extension.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 'use strict'; 6 | 7 | import * as path from 'path'; 8 | import * as fs from "fs"; 9 | import * as cp from 'child_process'; 10 | import { window, workspace, ExtensionContext, Progress, Range, commands, tasks, Task, TaskExecution, ShellExecution, Uri, TaskDefinition, debug } from 'vscode'; 11 | import { NotificationType } from 'vscode-languageclient'; 12 | import { 13 | LanguageClient, 14 | LanguageClientOptions, 15 | ServerOptions, 16 | TransportKind 17 | } from 'vscode-languageclient/node'; 18 | //import { env } from 'process'; 19 | // Run using `dotnet` instead of self-contained executable 20 | 21 | export function activate(context: ExtensionContext) { 22 | let FSLangServerFolder = Uri.joinPath(workspace.workspaceFolders[0].uri, ('src/FSharpLanguageServer')); 23 | const debugMode = workspace.getConfiguration().get("fsharp.debug.enable", false); 24 | 25 | const customCommand: string = workspace.getConfiguration().get("fsharp.customCommand", null); 26 | 27 | const customCommandArgs: string[] = workspace.getConfiguration().get("fsharp.customCommandArgs", null); 28 | 29 | let args: string[] = customCommandArgs ?? [binName()] 30 | //This always needs to be just a single command with no args. If not it will cause an error. 31 | let serverMain =customCommand ?? findInPath('dotnet')??'dotnet'; 32 | 33 | // The server is packaged as a standalone command 34 | 35 | 36 | console.log("Going to start server with command ",serverMain); 37 | 38 | // If the extension is launched in debug mode then the debug server options are used 39 | // Otherwise the run options are used 40 | let serverOptions: ServerOptions = { 41 | command: serverMain, 42 | args: args, 43 | transport: TransportKind.stdio, 44 | options: { 45 | cwd: context.extensionPath, 46 | env: { 47 | ...process.env, 48 | } 49 | 50 | } 51 | } 52 | if (debugMode) { 53 | serverOptions = { 54 | command: findInPath('dotnet')??'dotnet', 55 | args: ['run', '--project', FSLangServerFolder.fsPath], 56 | transport: TransportKind.stdio, 57 | options: { 58 | cwd: context.extensionPath, 59 | }, 60 | 61 | } 62 | } 63 | 64 | // Options to control the language client 65 | let clientOptions: LanguageClientOptions = { 66 | // Register the server for F# documents 67 | documentSelector: [{ scheme: 'file', language: 'fsharp' }], 68 | synchronize: { 69 | // Synchronize the setting section 'languageServerExample' to the server 70 | configurationSection: 'fsharp', 71 | // Notify the server about file changes to F# project files contain in the workspace 72 | // TODO: is there a way to configure this via the language server protocol? 73 | fileEvents: [ 74 | workspace.createFileSystemWatcher('**/*.fsproj'), 75 | workspace.createFileSystemWatcher('**/*.fsx'), 76 | workspace.createFileSystemWatcher('**/project.assets.json') 77 | ] 78 | } 79 | } 80 | 81 | // Create the language client and start the client. 82 | let client = new LanguageClient('fsharp', 'F# Language Server', serverOptions, clientOptions); 83 | let disposable = client.start(); 84 | 85 | // Push the disposable to the context's subscriptions so that the 86 | // client can be deactivated on extension deactivation 87 | context.subscriptions.push(disposable); 88 | 89 | // When the language client activates, register a progress-listener 90 | client.onReady().then(() => createProgressListeners(client)); 91 | 92 | // Register test-runner 93 | commands.registerCommand('fsharp.command.test.run', runTest); 94 | commands.registerCommand('fsharp.command.test.debug', debugTest); 95 | commands.registerCommand('fsharp.command.goto', goto); 96 | } 97 | 98 | function goto(file: string, startLine: number, startColumn: number, _endLine: number, _endColumn: number) { 99 | let selection = new Range(startLine, startColumn, startLine, startColumn); 100 | workspace.openTextDocument(file).then(doc => window.showTextDocument(doc, { selection })); 101 | } 102 | 103 | interface FSharpTestTask extends TaskDefinition { 104 | projectPath: string 105 | fullyQualifiedName: string 106 | } 107 | 108 | function runTest(projectPath: string, fullyQualifiedName: string): Thenable { 109 | let args = ['test', projectPath, '--filter', `FullyQualifiedName=${fullyQualifiedName}`] 110 | let kind: FSharpTestTask = { 111 | type: 'fsharp.task.test', 112 | projectPath: projectPath, 113 | fullyQualifiedName: fullyQualifiedName 114 | } 115 | let shell = new ShellExecution('dotnet', args) 116 | let workspaceFolder = workspace.getWorkspaceFolder(Uri.file(projectPath)) 117 | let task = new Task(kind, workspaceFolder, 'F# Test', 'F# Language Server', shell) 118 | return tasks.executeTask(task) 119 | } 120 | 121 | const outputChannel = window.createOutputChannel('F# Debug Tests'); 122 | 123 | function debugTest(projectPath: string, fullyQualifiedName: string): Promise { 124 | return new Promise((resolve, _reject) => { 125 | // TODO replace this with the tasks API once stdout is available 126 | // https://code.visualstudio.com/docs/extensionAPI/vscode-api#_tasks 127 | // https://github.com/Microsoft/vscode/issues/45980 128 | let cmd = 'dotnet' 129 | let args = ['test', projectPath, '--filter', `FullyQualifiedName=${fullyQualifiedName}`] 130 | let child = cp.spawn(cmd, args, { 131 | env: { 132 | ...process.env, 133 | 'VSTEST_HOST_DEBUG': '1' 134 | } 135 | }) 136 | 137 | outputChannel.clear() 138 | outputChannel.show() 139 | outputChannel.appendLine(`${cmd} ${args.join(' ')}...`) 140 | 141 | var isWaitingForDebugger = false 142 | function onStdoutLine(line: string) { 143 | if (line.trim() == 'Waiting for debugger attach...') { 144 | isWaitingForDebugger = true 145 | } 146 | if (isWaitingForDebugger) { 147 | let pattern = /^Process Id: (\d+)/ 148 | let match = line.match(pattern) 149 | if (match) { 150 | let pid = Number.parseInt(match[1]) 151 | let workspaceFolder = workspace.getWorkspaceFolder(Uri.file(projectPath)) 152 | let config = { 153 | "name": "F# Test", 154 | "type": "coreclr", 155 | "request": "attach", 156 | "processId": pid 157 | } 158 | outputChannel.appendLine(`Attaching debugger to process ${pid}...`) 159 | debug.startDebugging(workspaceFolder, config) 160 | 161 | isWaitingForDebugger = false 162 | } 163 | } 164 | } 165 | 166 | var stdoutBuffer = '' 167 | function onStdoutChunk(chunk: string | Buffer) { 168 | // Append to output channel 169 | let string = chunk.toString() 170 | outputChannel.append(string) 171 | // Send each line to onStdoutLine 172 | stdoutBuffer += string 173 | var newline = stdoutBuffer.indexOf('\n') 174 | while (newline != -1) { 175 | let line = stdoutBuffer.substring(0, newline) 176 | onStdoutLine(line) 177 | stdoutBuffer = stdoutBuffer.substring(newline + 1) 178 | newline = stdoutBuffer.indexOf('\n') 179 | } 180 | } 181 | 182 | child.stdout.on('data', onStdoutChunk); 183 | child.stderr.on('data', chunk => outputChannel.append(chunk.toString())); 184 | child.on('close', (code, _signal) => resolve(code)) 185 | }) 186 | } 187 | 188 | interface StartProgress { 189 | title: string 190 | nFiles: number 191 | } 192 | 193 | function createProgressListeners(client: LanguageClient) { 194 | // Create a "checking files" progress indicator 195 | 196 | let progressListener = new class { 197 | countChecked = 0 198 | nFiles = 0 199 | progress: Progress<{ message?: string }> 200 | resolve: (nothing: {}) => void 201 | 202 | startProgress(start: StartProgress) { 203 | // TODO implement user cancellation 204 | // TODO Change 15 to ProgressLocation.Notification 205 | window.withProgress({ title: start.title, location: 15 }, progress => new Promise((resolve, _reject) => { 206 | this.countChecked = 0; 207 | this.nFiles = start.nFiles; 208 | this.progress = progress; 209 | this.resolve = resolve; 210 | })); 211 | } 212 | 213 | private percentComplete() { 214 | return Math.floor(this.countChecked / (this.nFiles + 1) * 100); 215 | } 216 | 217 | incrementProgress(fileName: string) { 218 | if (this.progress != null) { 219 | let oldPercent = this.percentComplete(); 220 | this.countChecked++; 221 | let newPercent = this.percentComplete(); 222 | let report = { message: fileName, increment: newPercent - oldPercent }; 223 | this.progress.report(report); 224 | } 225 | } 226 | 227 | endProgress() { 228 | this.countChecked = 0 229 | this.nFiles = 0 230 | this.progress = null 231 | this.resolve({}) 232 | } 233 | } 234 | // Use custom notifications to drive progressListener 235 | client.onNotification(new NotificationType('fsharp/startProgress'), (start: StartProgress) => { 236 | progressListener.startProgress(start); 237 | }); 238 | client.onNotification(new NotificationType('fsharp/incrementProgress'), (fileName: string) => { 239 | progressListener.incrementProgress(fileName); 240 | }); 241 | client.onNotification(new NotificationType('fsharp/endProgress'), () => { 242 | progressListener.endProgress(); 243 | }); 244 | } 245 | 246 | function binName() { 247 | var baseParts = ['src', 'FSharpLanguageServer', 'bin', 'Release', 'net6.0']; 248 | var pathParts = getPathParts(); 249 | var fullParts = baseParts.concat(pathParts); 250 | 251 | return path.join(...fullParts); 252 | } 253 | 254 | 255 | 256 | function getPathParts(): string[] { 257 | /* switch (platform) { 258 | case 'win32': 259 | return ['win10-x64', 'publish', 'FSharpLanguageServer.exe']; 260 | 261 | case 'linux': 262 | return ['linux-x64', 'publish', 'FSharpLanguageServer']; 263 | 264 | case 'darwin': 265 | return ['osx.10.11-x64', 'publish', 'FSharpLanguageServer']; 266 | } 267 | 268 | throw `unsupported platform: ${platform}`; */ 269 | return ['publish', 'FSharpLanguageServer.dll']; 270 | } 271 | 272 | function findInPath(binname: string) { 273 | let pathparts = process.env['PATH'].split(path.delimiter); 274 | for (let i = 0; i < pathparts.length; i++) { 275 | let binpath = path.join(pathparts[i], binname); 276 | if (fs.existsSync(binpath)) { 277 | return binpath; 278 | } 279 | } 280 | return null; 281 | } 282 | -------------------------------------------------------------------------------- /fsharp-language-server.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{A85B8490-3570-4135-9F62-0C30DF2FB72C}" 7 | ProjectSection(SolutionItems) = preProject 8 | paket.dependencies = paket.dependencies 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C12C1289-6B1C-44FA-A500-F70B9530EA27}" 12 | EndProject 13 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpLanguageServer", "src\FSharpLanguageServer\FSharpLanguageServer.fsproj", "{56790B4C-96F9-4A15-99B3-D643EACEEB7B}" 14 | EndProject 15 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LSP", "src\LSP\LSP.fsproj", "{0D048A71-6AA3-4F50-8E27-FB6E34B4537B}" 16 | EndProject 17 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ProjectCracker", "src\ProjectCracker\ProjectCracker.fsproj", "{CAE67C77-88CA-4E1D-AB56-BA24C63A9521}" 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{6A478F23-67F9-4F5B-9159-589A05EBBFB5}" 20 | EndProject 21 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpLanguageServer.Tests", "tests\FSharpLanguageServer.Tests\FSharpLanguageServer.Tests.fsproj", "{7C5994F9-641C-4A79-ADB6-6A0412EB885B}" 22 | EndProject 23 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "LSP.Tests", "tests\LSP.Tests\LSP.Tests.fsproj", "{1D0BB852-97FA-4933-8595-A2B579B3768F}" 24 | EndProject 25 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ProjectCracker.Tests", "tests\ProjectCracker.Tests\ProjectCracker.Tests.fsproj", "{12BD685B-EFDB-4991-A9FE-2CBB28944383}" 26 | EndProject 27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectInfo", "tests\ProjectInfo\ProjectInfo.csproj", "{EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}" 28 | EndProject 29 | Global 30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 31 | Debug|Any CPU = Debug|Any CPU 32 | Debug|x64 = Debug|x64 33 | Debug|x86 = Debug|x86 34 | Release|Any CPU = Release|Any CPU 35 | Release|x64 = Release|x64 36 | Release|x86 = Release|x86 37 | EndGlobalSection 38 | GlobalSection(SolutionProperties) = preSolution 39 | HideSolutionNode = FALSE 40 | EndGlobalSection 41 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 42 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Debug|x64.ActiveCfg = Debug|x64 45 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Debug|x64.Build.0 = Debug|x64 46 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Debug|x86.ActiveCfg = Debug|x86 47 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Debug|x86.Build.0 = Debug|x86 48 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Release|x64.ActiveCfg = Release|x64 51 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Release|x64.Build.0 = Release|x64 52 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Release|x86.ActiveCfg = Release|x86 53 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B}.Release|x86.Build.0 = Release|x86 54 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Debug|x64.ActiveCfg = Debug|x64 57 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Debug|x64.Build.0 = Debug|x64 58 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Debug|x86.ActiveCfg = Debug|x86 59 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Debug|x86.Build.0 = Debug|x86 60 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Release|x64.ActiveCfg = Release|x64 63 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Release|x64.Build.0 = Release|x64 64 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Release|x86.ActiveCfg = Release|x86 65 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B}.Release|x86.Build.0 = Release|x86 66 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Debug|x64.ActiveCfg = Debug|x64 69 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Debug|x64.Build.0 = Debug|x64 70 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Debug|x86.ActiveCfg = Debug|x86 71 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Debug|x86.Build.0 = Debug|x86 72 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Release|x64.ActiveCfg = Release|x64 75 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Release|x64.Build.0 = Release|x64 76 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Release|x86.ActiveCfg = Release|x86 77 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521}.Release|x86.Build.0 = Release|x86 78 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Debug|x64.ActiveCfg = Debug|x64 81 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Debug|x64.Build.0 = Debug|x64 82 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Debug|x86.ActiveCfg = Debug|x86 83 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Debug|x86.Build.0 = Debug|x86 84 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Release|Any CPU.ActiveCfg = Release|Any CPU 85 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Release|Any CPU.Build.0 = Release|Any CPU 86 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Release|x64.ActiveCfg = Release|x64 87 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Release|x64.Build.0 = Release|x64 88 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Release|x86.ActiveCfg = Release|x86 89 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B}.Release|x86.Build.0 = Release|x86 90 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 91 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Debug|Any CPU.Build.0 = Debug|Any CPU 92 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Debug|x64.ActiveCfg = Debug|x64 93 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Debug|x64.Build.0 = Debug|x64 94 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Debug|x86.ActiveCfg = Debug|x86 95 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Debug|x86.Build.0 = Debug|x86 96 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Release|Any CPU.ActiveCfg = Release|Any CPU 97 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Release|Any CPU.Build.0 = Release|Any CPU 98 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Release|x64.ActiveCfg = Release|x64 99 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Release|x64.Build.0 = Release|x64 100 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Release|x86.ActiveCfg = Release|x86 101 | {1D0BB852-97FA-4933-8595-A2B579B3768F}.Release|x86.Build.0 = Release|x86 102 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 103 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Debug|Any CPU.Build.0 = Debug|Any CPU 104 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Debug|x64.ActiveCfg = Debug|x64 105 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Debug|x64.Build.0 = Debug|x64 106 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Debug|x86.ActiveCfg = Debug|x86 107 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Debug|x86.Build.0 = Debug|x86 108 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Release|Any CPU.ActiveCfg = Release|Any CPU 109 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Release|Any CPU.Build.0 = Release|Any CPU 110 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Release|x64.ActiveCfg = Release|x64 111 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Release|x64.Build.0 = Release|x64 112 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Release|x86.ActiveCfg = Release|x86 113 | {12BD685B-EFDB-4991-A9FE-2CBB28944383}.Release|x86.Build.0 = Release|x86 114 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 115 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Debug|Any CPU.Build.0 = Debug|Any CPU 116 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Debug|x64.ActiveCfg = Debug|Any CPU 117 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Debug|x64.Build.0 = Debug|Any CPU 118 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Debug|x86.ActiveCfg = Debug|Any CPU 119 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Debug|x86.Build.0 = Debug|Any CPU 120 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Release|Any CPU.ActiveCfg = Release|Any CPU 121 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Release|Any CPU.Build.0 = Release|Any CPU 122 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Release|x64.ActiveCfg = Release|Any CPU 123 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Release|x64.Build.0 = Release|Any CPU 124 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Release|x86.ActiveCfg = Release|Any CPU 125 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F}.Release|x86.Build.0 = Release|Any CPU 126 | EndGlobalSection 127 | GlobalSection(NestedProjects) = preSolution 128 | {56790B4C-96F9-4A15-99B3-D643EACEEB7B} = {C12C1289-6B1C-44FA-A500-F70B9530EA27} 129 | {0D048A71-6AA3-4F50-8E27-FB6E34B4537B} = {C12C1289-6B1C-44FA-A500-F70B9530EA27} 130 | {CAE67C77-88CA-4E1D-AB56-BA24C63A9521} = {C12C1289-6B1C-44FA-A500-F70B9530EA27} 131 | {7C5994F9-641C-4A79-ADB6-6A0412EB885B} = {6A478F23-67F9-4F5B-9159-589A05EBBFB5} 132 | {1D0BB852-97FA-4933-8595-A2B579B3768F} = {6A478F23-67F9-4F5B-9159-589A05EBBFB5} 133 | {12BD685B-EFDB-4991-A9FE-2CBB28944383} = {6A478F23-67F9-4F5B-9159-589A05EBBFB5} 134 | {EB22E80A-4C8C-4B5D-B114-BC0CDA57207F} = {6A478F23-67F9-4F5B-9159-589A05EBBFB5} 135 | EndGlobalSection 136 | EndGlobal 137 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fsharp-language-server-updated", 3 | "displayName": "F# Language Server updated", 4 | "description": "F# Language Support using FSharp Compiler Services updated to support net6.0 and fcs 41", 5 | "author": "George Fraser and Eli Dowling", 6 | "license": "MIT", 7 | "icon": "Icon512.png", 8 | "version": "0.1.60", 9 | "preview": false, 10 | "publisher": "faldor20", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/faldor20/fsharp-language-server" 14 | }, 15 | "engines": { 16 | "vscode": "^1.52.0" 17 | }, 18 | "categories": [ 19 | "Programming Languages", 20 | "Linters" 21 | ], 22 | "activationEvents": [ 23 | "onLanguage:fsharp" 24 | ], 25 | "main": "./out/client/extension", 26 | "files": [ 27 | "src/FSharpLanguageServer/bin/Release/net6.0/publish/" 28 | ], 29 | "contributes": { 30 | "languages": [ 31 | { 32 | "id": "fsharp", 33 | "aliases": [ 34 | "F#", 35 | "FSharp", 36 | "fsharp" 37 | ], 38 | "extensions": [ 39 | ".fs", 40 | ".fsx", 41 | ".fsi" 42 | ], 43 | "configuration": "./syntaxes/fsharp.configuration.json" 44 | } 45 | ], 46 | "grammars": [ 47 | { 48 | "language": "fsharp", 49 | "scopeName": "source.fsharp", 50 | "path": "./syntaxes/fsharp.json" 51 | }, 52 | { 53 | "language": "fsharp", 54 | "scopeName": "source.fsharp.fsx", 55 | "path": "./syntaxes/fsharp.fsx.json" 56 | }, 57 | { 58 | "language": "fsharp", 59 | "scopeName": "source.fsharp.fsi", 60 | "path": "./syntaxes/fsharp.fsi.json" 61 | } 62 | ], 63 | "configuration": { 64 | "type": "object", 65 | "title": "FSharp configuration", 66 | "properties": { 67 | "fsharp.trace.server": { 68 | "scope": "window", 69 | "type": "string", 70 | "enum": [ 71 | "off", 72 | "messages", 73 | "verbose" 74 | ], 75 | "default": "off", 76 | "description": "Traces the communication between VSCode and the language server." 77 | }, 78 | "fsharp.debug.enable": { 79 | "scope": "window", 80 | "type": "boolean", 81 | "default": false, 82 | "description": "Sets whether to use dotnet run to run a debug build of FSlanguageserver" 83 | }, 84 | "fsharp.customCommand": { 85 | "scope": "window", 86 | "type": "string", 87 | "default": null, 88 | "description": "Allows you to set a custom command to run the langserver, good for testing custom version \n This must be just the command to run, eg: 'dotnet' put any args in customCommandArgs" 89 | }, 90 | "fsharp.customCommandArgs": { 91 | "scope": "window", 92 | "type": "array", 93 | "default": null, 94 | "description": "Allows you to set a custom command args to run the langserver, good for testing custom version or passing special arguments" 95 | } 96 | } 97 | }, 98 | "taskDefinitions": [ 99 | { 100 | "type": "fsharp.task.test", 101 | "required": [ 102 | "projectPath", 103 | "fullyQualifiedName" 104 | ], 105 | "properties": { 106 | "projectPath": { 107 | "type": "string" 108 | }, 109 | "fullyQualifiedName": { 110 | "type": "string" 111 | } 112 | } 113 | } 114 | ], 115 | "breakpoints": [ 116 | { 117 | "language": "fsharp" 118 | } 119 | ] 120 | }, 121 | "scripts": { 122 | "vscode:prepublish": "npm run compile", 123 | "compile": "tsc -p ./", 124 | "watch": "tsc -watch -p ./", 125 | "test": "npm run compile && node ./node_modules/vscode/bin/test" 126 | }, 127 | "extensionDependencies": [ 128 | "ms-dotnettools.csharp" 129 | ], 130 | "dependencies": { 131 | "vscode-languageclient": "^7.0.0" 132 | }, 133 | "devDependencies": { 134 | "@types/vscode": "^1.52.0", 135 | "typescript": "^4.4.4", 136 | "vsce": "^2.3.0", 137 | "@types/node": "^16.11.7", 138 | "vscode-test": "^1.6.1" 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://api.nuget.org/v3/index.json 2 | 3 | nuget FSharp.Core 4 | nuget Buildalyzer 3.2.3 5 | nuget FSharp.Compiler.Service 41.0.1 6 | nuget FSharp.Data 4.2.5 7 | nuget FSharp.UMX 8 | nuget HtmlAgilityPack 1.11.39 9 | nuget logary 4.2.1 10 | nuget Microsoft.Build 11 | nuget Microsoft.Build.Framework 12 | nuget Microsoft.Build.Runtime 13 | nuget Microsoft.Build.Tasks.Core 14 | nuget Microsoft.Build.Utilities.Core 15 | nuget Microsoft.NET.Sdk.Functions 16 | nuget Microsoft.NET.Test.Sdk 17.0.0 17 | nuget NUnit 3.13.2 18 | nuget NUnit3TestAdapter 4.1.0 19 | nuget Serilog 2.10.0 20 | nuget Serilog.Sinks.Console 4.0.1 21 | nuget Serilog.Sinks.File 5.0.1-dev-00947 prerelease 22 | nuget System.IO.FileSystem 4.3.0 23 | nuget System.IO.FileSystem.Primitives 4.3.0 24 | nuget System.Net.NameResolution 4.3.0 25 | nuget System.Security.Principal 4.3.0 26 | nuget System.Threading.ThreadPool 4.3.0 -------------------------------------------------------------------------------- /sample/BadProject/BadProject.fsproj: -------------------------------------------------------------------------------- 1 | This is not actually a project file -------------------------------------------------------------------------------- /sample/CSharpProject.AssemblyName/CSharpProject.AssemblyName.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | CSharpProject.AssemblyName.Modified 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/CSharpProject.AssemblyName/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CSharpProject.AssemblyName 4 | { 5 | public class Class1 6 | { 7 | public static String name() { 8 | return "CSharp"; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/CSharpProject/CSharpProject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sample/CSharpProject/Class1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CSharpProject 4 | { 5 | public class Class1 6 | { 7 | public static String name() { 8 | return "CSharp"; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/DependsOn/DependsOn.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/DependsOn/MyLibrary.fs: -------------------------------------------------------------------------------- 1 | module MyLibrary 2 | 3 | let myInt: int = 1 -------------------------------------------------------------------------------- /sample/EmptyProject/EmptyProject.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 6 | -------------------------------------------------------------------------------- /sample/FSharpKoans.Core/FSharpKoans.Core.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | Library 5 | FSharpKoans.Core 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /sample/HasLocalDll/HasLocalDll.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/HasLocalDll/Program.fs: -------------------------------------------------------------------------------- 1 | module HasLocalDll 2 | 3 | [] 4 | let main(argv) = 5 | printf "Hello, %d" IndirectLibrary.myInt 6 | 0 -------------------------------------------------------------------------------- /sample/HasPackageReference/HasPackageReference.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/HasPackageReference/Library.fs: -------------------------------------------------------------------------------- 1 | namespace HasPackageReference 2 | 3 | module Say = 4 | let hello name = 5 | printfn "Hello %s" name 6 | -------------------------------------------------------------------------------- /sample/HasTests/HasTests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/HasTests/MyTests.fs: -------------------------------------------------------------------------------- 1 | module MyTests 2 | 3 | open NUnit.Framework 4 | 5 | [] 6 | let ``my great test``() = 7 | () -------------------------------------------------------------------------------- /sample/IndirectDep/IndirectDep.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/IndirectDep/IndirectLibrary.fs: -------------------------------------------------------------------------------- 1 | module IndirectLibrary 2 | 3 | let myInt: int = 1 -------------------------------------------------------------------------------- /sample/Issue28/Issue28.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /sample/Issue28/main.fs: -------------------------------------------------------------------------------- 1 | module Lib 2 | 3 | open System.Net 4 | 5 | ServicePointManager.DefaultConnectionLimit <- 20 -------------------------------------------------------------------------------- /sample/MainProject/BreakParentReference.fs: -------------------------------------------------------------------------------- 1 | module BreakParentReference 2 | 3 | let referenceInt: int = BreakParentTarget.publicInt -------------------------------------------------------------------------------- /sample/MainProject/BreakParentTarget.fs: -------------------------------------------------------------------------------- 1 | module BreakParentTarget 2 | 3 | let publicInt = 1 -------------------------------------------------------------------------------- /sample/MainProject/CompleteInString.fs: -------------------------------------------------------------------------------- 1 | module CompleteInString 2 | 3 | let x = "List." -------------------------------------------------------------------------------- /sample/MainProject/Completions.fs: -------------------------------------------------------------------------------- 1 | module Completions 2 | 3 | let private completeListModule() = 4 | List. 5 | 6 | let private completeParens() = 7 | Some("foo") 8 | 9 | let private ``name with space``() = 10 | "" 11 | 12 | let private completeSpace() = 13 | na -------------------------------------------------------------------------------- /sample/MainProject/CreateTypeError.fs: -------------------------------------------------------------------------------- 1 | module CreateTypeError 2 | 3 | let private createTypeError () = 4 | let x: int = 1 5 | x -------------------------------------------------------------------------------- /sample/MainProject/DeclareSymbol.fs: -------------------------------------------------------------------------------- 1 | module DeclareSymbol 2 | 3 | let x = 1 -------------------------------------------------------------------------------- /sample/MainProject/Hover.fs: -------------------------------------------------------------------------------- 1 | module Hover 2 | open System.Net 3 | let private myFun(): int = 1 4 | 5 | let private testFun() = 6 | eprintfn "%d" (myFun()) 7 | 8 | module private InternalHover = 9 | let internalFun(): int = 1 10 | 11 | let private testInternalFun() = 12 | eprintfn "%d" (InternalHover.internalFun()) 13 | 14 | let private systemFuncHover=List.fold 15 | 16 | type intFunc= int->int 17 | let multiply a b c = 18 | a*b*c 19 | let aliasedFunc:intFunc = (multiply 1 2) 20 | ///This function has documentation 21 | ///``a``: a thing 22 | ///``b``: b thing 23 | let docedFunction a b= 24 | a+b 25 | let methodTest=Authorization("a") -------------------------------------------------------------------------------- /sample/MainProject/InterfaceInModule.fs: -------------------------------------------------------------------------------- 1 | module InterfaceInModule 2 | 3 | type IMyInterface = 4 | abstract member MyMethod: unit -> string -------------------------------------------------------------------------------- /sample/MainProject/MainProject.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sample/MainProject/NotInFsproj.fs: -------------------------------------------------------------------------------- 1 | module NotInFsproj 2 | 3 | let myInt = 1 -------------------------------------------------------------------------------- /sample/MainProject/Reference.fs: -------------------------------------------------------------------------------- 1 | module Reference 2 | 3 | let targetX: string = Target.x -------------------------------------------------------------------------------- /sample/MainProject/ReferenceDependsOn.fs: -------------------------------------------------------------------------------- 1 | module ReferenceDependsOn 2 | 3 | let x: string = MyLibrary.myInt -------------------------------------------------------------------------------- /sample/MainProject/ReferenceIndirectDep.fs: -------------------------------------------------------------------------------- 1 | module ReferenceIndirectDep 2 | 3 | let x: string = IndirectLibrary.myInt -------------------------------------------------------------------------------- /sample/MainProject/RenameReference.fs: -------------------------------------------------------------------------------- 1 | module RenameReference 2 | 3 | let referenceToRenamedSymbol = RenameTarget.symbolToRename -------------------------------------------------------------------------------- /sample/MainProject/RenameTarget.fs: -------------------------------------------------------------------------------- 1 | module RenameTarget 2 | 3 | let symbolToRename = 1 -------------------------------------------------------------------------------- /sample/MainProject/SignatureHelp.fs: -------------------------------------------------------------------------------- 1 | module SignatureHelp 2 | 3 | let private signatureHelp() = "foo".Substring() -------------------------------------------------------------------------------- /sample/MainProject/Target.fs: -------------------------------------------------------------------------------- 1 | module Target 2 | 3 | let x: int = 1 -------------------------------------------------------------------------------- /sample/MainProject/UseSymbol.fs: -------------------------------------------------------------------------------- 1 | module UseSymbol 2 | 3 | let useX = DeclareSymbol.x -------------------------------------------------------------------------------- /sample/MainProject/WrongType.fs: -------------------------------------------------------------------------------- 1 | module WrongType 2 | 3 | let private wrongType () = 4 | let x: int = "1" 5 | x -------------------------------------------------------------------------------- /sample/Net5Console/Net5Console.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/Net5Console/main.fs: -------------------------------------------------------------------------------- 1 | open System 2 | 3 | [] 4 | let main _ = 5 | 0 6 | -------------------------------------------------------------------------------- /sample/Net6Console/Net6Console.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/Net6Console/main.fs: -------------------------------------------------------------------------------- 1 | open System 2 | 3 | [] 4 | let main _ = 5 | 0 6 | -------------------------------------------------------------------------------- /sample/NetCoreApp3/NetCoreApp3.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/NetCoreApp3/main.fs: -------------------------------------------------------------------------------- 1 | open System 2 | 3 | [] 4 | let main _ = 5 | 0 6 | -------------------------------------------------------------------------------- /sample/NotBuilt/NotBuilt.fs: -------------------------------------------------------------------------------- 1 | module NotBuilt 2 | 3 | let x = 1 -------------------------------------------------------------------------------- /sample/NotBuilt/NotBuilt.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/ReferenceCSharp.AssemblyName/Library.fs: -------------------------------------------------------------------------------- 1 | namespace ReferenceCSharp.AssemblyName 2 | 3 | module Say = 4 | let hello () = 5 | let csharp = CSharpProject.AssemblyName.Class1.name() 6 | printfn "Hello %s" csharp 7 | -------------------------------------------------------------------------------- /sample/ReferenceCSharp.AssemblyName/ReferenceCSharp.AssemblyName.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/ReferenceCSharp/Library.fs: -------------------------------------------------------------------------------- 1 | namespace ReferenceCSharp 2 | 3 | module Say = 4 | let hello () = 5 | let csharp = CSharpProject.Class1.name() 6 | printfn "Hello %s" csharp 7 | -------------------------------------------------------------------------------- /sample/ReferenceCSharp/ReferenceCSharp.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/Script/LoadedByScript.fs: -------------------------------------------------------------------------------- 1 | module LoadedByScript 2 | let x = 1 -------------------------------------------------------------------------------- /sample/Script/MainScript.fsx: -------------------------------------------------------------------------------- 1 | #load "LoadedByScript.fs" 2 | 3 | let _y = LoadedByScript.x + 1 -------------------------------------------------------------------------------- /sample/Signature/HasSignature.fs: -------------------------------------------------------------------------------- 1 | module Foo 2 | 3 | let bar() = "bar" 4 | 5 | module Nested = 6 | let nestedBar() = "nested!" 7 | 8 | type Class() = 9 | member this.overloadedMethod(i: int) = sprintf "%d" i 10 | member this.overloadedMethod(i: string) = i -------------------------------------------------------------------------------- /sample/Signature/HasSignature.fsi: -------------------------------------------------------------------------------- 1 | module Foo 2 | 3 | val bar: unit -> string 4 | 5 | module Nested = 6 | val nestedBar: unit -> string 7 | val missingImplementation: unit -> string 8 | 9 | [] 10 | type Class = 11 | member overloadedMethod: int -> string 12 | member overloadedMethod: string -> string -------------------------------------------------------------------------------- /sample/Signature/Signature.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/SlnReferences/Common.fs: -------------------------------------------------------------------------------- 1 | module Common 2 | 3 | let referenced = 1 -------------------------------------------------------------------------------- /sample/SlnReferences/Main.fs: -------------------------------------------------------------------------------- 1 | module Main 2 | 3 | let reference = Common.referenced -------------------------------------------------------------------------------- /sample/SlnReferences/OrphanProject.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/SlnReferences/ReferencedProject.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/SlnReferences/SlnReferences.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ReferencedProject", "ReferencedProject.fsproj", "{A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Debug|x64.ActiveCfg = Debug|Any CPU 24 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Debug|x64.Build.0 = Debug|Any CPU 25 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Debug|x86.ActiveCfg = Debug|Any CPU 26 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Debug|x86.Build.0 = Debug|Any CPU 27 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Release|x64.ActiveCfg = Release|Any CPU 30 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Release|x64.Build.0 = Release|Any CPU 31 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Release|x86.ActiveCfg = Release|Any CPU 32 | {A8CA8A97-3C42-4BA9-AED1-39C9AB043EB9}.Release|x86.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /sample/TemplateParams/TemplateParams.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net45;netstandard2.0 4 | 5 | 6 | 7 | 8 | $(MSBuildProjectDirectory)\..\..\src 9 | $(TargetFramework)\ 10 | 11 | 12 | 13 | 14 | 15 | Utilities/QueueList.fs 16 | 17 | 18 | ParserAndUntypedAST/pars.fs 19 | 20 | 21 | -------------------------------------------------------------------------------- /scripts/build.ps1: -------------------------------------------------------------------------------- 1 | #dotnet clean 2 | dotnet publish -c Release src/FSharpLanguageServer 3 | #dotnet publish -c Release -r osx.10.11-x64 src/FSharpLanguageServer 4 | #dotnet publish -c Release -r linux-x64 src/FSharpLanguageServer 5 | 6 | # Build vsix 7 | vsce package -o build.vsix 8 | code --install-extension build.vsix -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Builds the plugin as build.vsix 3 | # You will need dotnet core and vsce to run this script 4 | set -e 5 | 6 | # Needed once 7 | npm install 8 | 9 | # Build self-contained archives for windows, mac and linux 10 | dotnet clean 11 | dotnet publish -c Release src/FSharpLanguageServer 12 | 13 | # Build vsix 14 | vsce package -o build.vsix 15 | code --install-extension build.vsix 16 | -------------------------------------------------------------------------------- /scripts/debug.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # Delete build outputs 5 | rm -rf src/*/bin src/*/obj tests/*/bin tests/*/obj node_modules 6 | 7 | # Build js 8 | npm install 9 | npm run-script compile 10 | 11 | # Build src/FSharpLanguageServer/bin/Release/netcoreapp3.0/osx.10.11-x64/publish/FSharpLanguageServer 12 | dotnet publish -c Release src/FSharpLanguageServer 13 | echo 'Press F5 to debug the new build of F# language server' 14 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | artifact=build.vsix 6 | package=faldor20.fsharp-language-server 7 | 8 | if [ ! -z `code --list-extensions | grep $package` ]; then 9 | code --uninstall-extension $package 10 | fi 11 | 12 | code --install-extension $artifact 13 | -------------------------------------------------------------------------------- /scripts/paketActions.sh: -------------------------------------------------------------------------------- 1 | #used to make paket use local package cache for github actions so we can cache that folder 2 | sed '2 i storage:local' ./paket.dependencies -------------------------------------------------------------------------------- /scripts/restore.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Restore test projects 3 | 4 | echo 'Restoring sample projects...' 5 | dotnet restore sample/EmptyProject/EmptyProject.fsproj 6 | dotnet restore sample/FSharpKoans.Core/FSharpKoans.Core.fsproj 7 | dotnet restore sample/HasLocalDll/HasLocalDll.fsproj 8 | dotnet restore sample/HasPackageReference/HasPackageReference.fsproj 9 | dotnet restore sample/HasTests/HasTests.fsproj 10 | dotnet restore sample/Issue28/Issue28.fsproj 11 | dotnet restore sample/MainProject/MainProject.fsproj 12 | dotnet restore sample/ReferenceCSharp.AssemblyName/ReferenceCSharp.AssemblyName.fsproj 13 | dotnet restore sample/ReferenceCSharp/ReferenceCSharp.fsproj 14 | dotnet restore sample/Signature/Signature.fsproj 15 | dotnet restore sample/SlnReferences/ReferencedProject.fsproj 16 | dotnet restore sample/TemplateParams/TemplateParams.fsproj 17 | dotnet restore sample/NetCoreApp3/NetCoreApp3.fsproj 18 | # These need to be built, not restored 19 | dotnet build sample/CSharpProject/CSharpProject.csproj 20 | dotnet build sample/CSharpProject.AssemblyName/CSharpProject.AssemblyName.csproj 21 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Restore test projects and run all tests 3 | 4 | source scripts/restore.sh 5 | 6 | echo 'Running tests...' 7 | set -e 8 | dotnet test tests/LSP.Tests 9 | dotnet test tests/ProjectCracker.Tests 10 | dotnet test tests/FSharpLanguageServer.Tests -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: 2 | 3 | let 4 | buildDotnet = with pkgs.dotnetCorePackages; combinePackages [ 5 | sdk_6_0 6 | ]; 7 | in 8 | pkgs.mkShell { 9 | buildInputs = [ 10 | pkgs.hello 11 | pkgs.openssl.dev 12 | pkgs.openssl 13 | pkgs.openssl.out 14 | pkgs.pkg-config 15 | # keep this line if you use bash 16 | pkgs.bashInteractive 17 | pkgs.nodejs 18 | pkgs.nodePackages.npm 19 | 20 | ]; 21 | nativeBuildInputs=[ 22 | buildDotnet 23 | pkgs.openssl 24 | pkgs.openssl.dev 25 | pkgs.openssl.out 26 | pkgs.pkg-config 27 | ]; 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /spacemacs/fsharp2/config.el: -------------------------------------------------------------------------------- 1 | ;;; config.el --- fsharp Layer config File for Spacemacs 2 | ;; 3 | ;; Copyright (c) 2012-2018 Sylvain Benner & Contributors 4 | ;; 5 | ;; Author: Chris Marchetti 6 | ;; URL: https://github.com/syl20bnr/spacemacs 7 | ;; 8 | ;; This file is not part of GNU Emacs. 9 | ;; 10 | ;;; License: GPLv3 11 | (spacemacs|define-jump-handlers fsharp-mode) 12 | 13 | (defvar fsharp2-backend 'lsp 14 | "The backend to use for IDE features. Possible values are `fsac' and `lsp'") 15 | 16 | (defvar fsharp2-lsp-executable "FSharpLanguageServer" 17 | "The location of the FSharpLanguageServer executable") 18 | -------------------------------------------------------------------------------- /spacemacs/fsharp2/funcs.el: -------------------------------------------------------------------------------- 1 | ;;; funcs.el --- fsharp2 Layer config File for Spacemacs 2 | ;; 3 | ;; Copyright (c) 2012-2018 Sylvain Benner & Contributors 4 | ;; 5 | ;; Author: Chris Marchetti 6 | ;; URL: https://github.com/syl20bnr/spacemacs 7 | ;; 8 | ;; This file is not part of GNU Emacs. 9 | ;; 10 | ;;; License: GPLv3 11 | 12 | (defun spacemacs//fsharp2-setup-intellisense () 13 | "Conditionally enable fsharp-mode's built-in intellisense" 14 | (pcase fsharp2-backend 15 | (`lsp 16 | (setq fsharp-ac-intellisense-enabled nil)) 17 | (`fsac 18 | (setq fsharp-ac-intellisense-enabled t)))) 19 | 20 | (defun spacemacs//fsharp2-setup-backend () 21 | "Conditionally setup fsharp backend" 22 | (pcase fsharp2-backend 23 | (`lsp 24 | (require 'lsp) 25 | ;; Required to avoid issues with lsp-mode's built-in F# client; even though 26 | ;; we're using our mode instead, lsp-mode can't build the LSP client 27 | ;; without this value defined 28 | (setq lsp-fsharp-server-path "") 29 | (lsp-register-client 30 | (make-lsp-client 31 | :new-connection (lsp-stdio-connection fsharp2-lsp-executable) 32 | :major-modes '(fsharp-mode) 33 | :server-id 'fsharp-lsp 34 | :notification-handlers (ht ("fsharp/startProgress" #'ignore) 35 | ("fsharp/incrementProgress" #'ignore) 36 | ("fsharp/endProgress" #'ignore)) 37 | :priority 1)) 38 | (lsp)))) 39 | 40 | (defun spacemacs//fsharp2-setup-bindings () 41 | "Conditionally setup fsharp bindings" 42 | (pcase fsharp2-backend 43 | (`fsac 44 | (spacemacs/declare-prefix-for-mode 'fsharp-mode "mf" "find") 45 | (spacemacs/declare-prefix-for-mode 'fsharp-mode "ms" "interpreter") 46 | (spacemacs/declare-prefix-for-mode 'fsharp-mode "mx" "executable") 47 | (spacemacs/declare-prefix-for-mode 'fsharp-mode "mc" "compile") 48 | (spacemacs/declare-prefix-for-mode 'fsharp-mode "mg" "goto") 49 | (spacemacs/declare-prefix-for-mode 'fsharp-mode "mh" "hint")))) 50 | 51 | (defun spacemacs//fsharp2-setup-company () 52 | "Conditionally setup company mode" 53 | (pcase fsharp2-backend 54 | (`lsp 55 | (spacemacs|add-company-backends 56 | :backends company-lsp 57 | :modes fsharp-mode 58 | :append-hooks nil 59 | :call-hooks t) 60 | (company-mode)))) 61 | -------------------------------------------------------------------------------- /spacemacs/fsharp2/packages.el: -------------------------------------------------------------------------------- 1 | ;;; packages.el --- F# Layer packages File for Spacemacs 2 | ;; 3 | ;; Copyright (c) 2012-2018 Sylvain Benner & Contributors 4 | ;; 5 | ;; Author: Sylvain Benner 6 | ;; URL: https://github.com/syl20bnr/spacemacs 7 | ;; 8 | ;; This file is not part of GNU Emacs. 9 | ;; 10 | ;;; License: GPLv3 11 | 12 | (setq fsharp2-packages 13 | '( 14 | fsharp-mode 15 | ggtags 16 | company 17 | counsel-gtags 18 | helm-gtags 19 | )) 20 | 21 | (defun fsharp2/init-fsharp-mode () 22 | (use-package fsharp-mode 23 | :defer t 24 | :init 25 | (progn 26 | (setq fsharp-doc-idle-delay .2) 27 | (spacemacs/register-repl 'fsharp-mode 'fsharp-show-subshell "F#") 28 | (spacemacs//fsharp2-setup-intellisense) 29 | (spacemacs/add-to-hook 'fsharp-mode-hook 30 | '(spacemacs//fsharp2-setup-backend)) 31 | :config 32 | (progn 33 | (defun spacemacs/fsharp-load-buffer-file-focus () 34 | "Send the current buffer to REPL and switch to the REPL in 35 | `insert state'." 36 | (interactive) 37 | (fsharp-load-buffer-file) 38 | (switch-to-buffer-other-window inferior-fsharp-buffer-name) 39 | (evil-insert-state)) 40 | 41 | (defun spacemacs/fsharp-eval-phrase-focus () 42 | "Send the current phrase to REPL and switch to the REPL in 43 | `insert state'." 44 | (interactive) 45 | (fsharp-eval-phrase) 46 | (switch-to-buffer-other-window inferior-fsharp-buffer-name) 47 | (evil-insert-state)) 48 | 49 | (defun spacemacs/fsharp-eval-region-focus (start end) 50 | "Send the current phrase to REPL and switch to the REPL in 51 | `insert state'." 52 | (interactive "r") 53 | (fsharp-eval-region start end) 54 | (switch-to-buffer-other-window inferior-fsharp-buffer-name) 55 | (evil-insert-state)) 56 | 57 | (spacemacs//fsharp2-setup-bindings) 58 | (spacemacs/set-leader-keys-for-major-mode 'fsharp-mode 59 | ;; Compile 60 | "cc" 'compile 61 | 62 | "fa" 'fsharp-find-alternate-file 63 | 64 | "ht" 'fsharp-ac/show-tooltip-at-point 65 | 66 | "'" 'fsharp-show-subshell 67 | "sb" 'fsharp-load-buffer-file 68 | "sB" 'spacemacs/fsharp-load-buffer-file-focus 69 | "si" 'fsharp-show-subshell 70 | "sp" 'fsharp-eval-phrase 71 | "sP" 'spacemacs/fsharp-eval-phrase-focus 72 | "sr" 'fsharp-eval-region 73 | "sR" 'spacemacs/fsharp-eval-region-focus 74 | "ss" 'fsharp-show-subshell 75 | 76 | "xf" 'fsharp-run-executable-file))))) 77 | 78 | (defun fsharp2/post-init-ggtags () 79 | (add-hook 'fsharp-mode-local-vars-hook #'spacemacs/ggtags-mode-enable)) 80 | 81 | (defun fsharp2/post-init-counsel-gtags () 82 | (spacemacs/counsel-gtags-define-keys-for-mode 'fsharp-mode)) 83 | 84 | (defun fsharp2/post-init-helm-gtags () 85 | (spacemacs/helm-gtags-define-keys-for-mode 'fsharp-mode)) 86 | 87 | (defun fsharp2/post-init-company () 88 | (spacemacs//fsharp2-setup-company)) 89 | -------------------------------------------------------------------------------- /src/FSharpLanguageServer/DebounceCheck.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpLanguageServer 2 | 3 | open System.IO 4 | open System.Collections.Concurrent 5 | open System.Threading 6 | 7 | type DebounceCheck(check: FileInfo -> Async, delayMs: int) = 8 | let todo = new ConcurrentDictionary() 9 | let mutable cancel = new CancellationTokenSource() 10 | let doCheck(file: FileInfo) = 11 | async { 12 | do! check(file) 13 | todo.TryRemove(file.FullName) |> ignore 14 | } 15 | let doCheckAll() = 16 | async { 17 | for file in todo.Values do 18 | do! doCheck(file) 19 | } 20 | member this.CheckLater(file: FileInfo) = 21 | // Add this file to the todo list 22 | todo.TryAdd(file.FullName, file) |> ignore 23 | // Reset the check-countdown 24 | cancel.Cancel() 25 | cancel <- new CancellationTokenSource() 26 | // Start a new check-countdown 27 | Async.Start(async { 28 | do! Async.Sleep(delayMs) 29 | do! doCheckAll() 30 | }, cancel.Token) 31 | member this.CheckNow() = 32 | cancel.Cancel() 33 | doCheckAll() 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/FSharpLanguageServer/FSharpLanguageServer.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | Exe 6 | net6.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/FSharpLanguageServer/Goto.fs: -------------------------------------------------------------------------------- 1 | module FSharpLanguageServer.Goto 2 | let _TryFindIdentifierDeclaration (pos: Pos) (lineStr: LineStr) = 3 | match Lexer.findLongIdents(pos.Column, lineStr) with 4 | | None -> async.Return (ResultOrString.Error "Could not find ident at this location") 5 | | Some(col, identIsland) -> 6 | let identIsland = Array.toList identIsland 7 | let declarations = checkResults.GetDeclarationLocation(pos.Line, col, lineStr, identIsland, preferFlag = false) 8 | 9 | let decompile assembly externalSym = 10 | match Decompiler.tryFindExternalDeclaration checkResults (assembly, externalSym) with 11 | | Ok extDec -> ResultOrString.Ok (FindDeclarationResult.ExternalDeclaration extDec) 12 | | Error(Decompiler.FindExternalDeclarationError.ReferenceHasNoFileName assy) -> ResultOrString.Error (sprintf "External declaration assembly '%s' missing file name" assy.SimpleName) 13 | | Error(Decompiler.FindExternalDeclarationError.ReferenceNotFound assy) -> ResultOrString.Error (sprintf "External declaration assembly '%s' not found" assy) 14 | | Error(Decompiler.FindExternalDeclarationError.DecompileError (Decompiler.Exception(symbol, file, exn))) -> 15 | Error (sprintf "Error while decompiling symbol '%A' in file '%s': %s\n%s" symbol file exn.Message exn.StackTrace) 16 | 17 | /// these are all None because you can't easily get the source file from the external symbol information here. 18 | let tryGetSourceRangeForSymbol (sym: FSharpExternalSymbol): (string * Pos) option = 19 | match sym with 20 | | FSharpExternalSymbol.Type name -> None 21 | | FSharpExternalSymbol.Constructor(typeName, args) -> None 22 | | FSharpExternalSymbol.Method(typeName, name, paramSyms, genericArity) -> None 23 | | FSharpExternalSymbol.Field(typeName, name) -> None 24 | | FSharpExternalSymbol.Event(typeName, name) -> None 25 | | FSharpExternalSymbol.Property(typeName, name) -> None 26 | 27 | // attempts to manually discover symbol use and externalsymbol information for a range that doesn't exist in a local file 28 | // bugfix/workaround for FCS returning invalid declfound for f# members. 29 | let tryRecoverExternalSymbolForNonexistentDecl (rangeInNonexistentFile: Range): ResultOrString * string> = 30 | match Lexer.findLongIdents(pos.Column - 1, lineStr) with 31 | | None -> ResultOrString.Error (sprintf "Range for nonexistent file found, no ident found: %s" rangeInNonexistentFile.FileName) 32 | | Some (col, identIsland) -> 33 | let identIsland = Array.toList identIsland 34 | let symbolUse = checkResults.GetSymbolUseAtLocation(pos.Line, col, lineStr, identIsland) 35 | match symbolUse with 36 | | None -> ResultOrString.Error (sprintf "Range for nonexistent file found, no symboluse found: %s" rangeInNonexistentFile.FileName) 37 | | Some sym -> 38 | match sym.Symbol.Assembly.FileName with 39 | | Some fullFilePath -> 40 | Ok (UMX.tag fullFilePath, UMX.tag rangeInNonexistentFile.FileName) 41 | | None -> 42 | ResultOrString.Error (sprintf "Assembly '%s' declaring symbol '%s' has no location on disk" sym.Symbol.Assembly.QualifiedName sym.Symbol.DisplayName) 43 | 44 | async { 45 | match declarations with 46 | | FSharpFindDeclResult.DeclNotFound reason -> 47 | let elaboration = 48 | match reason with 49 | | FSharpFindDeclFailureReason.NoSourceCode -> "No source code was found for the declaration" 50 | | FSharpFindDeclFailureReason.ProvidedMember m -> sprintf "Go-to-declaration is not available for Type Provider-provided member %s" m 51 | | FSharpFindDeclFailureReason.ProvidedType t -> sprintf "Go-to-declaration is not available from Type Provider-provided type %s" t 52 | | FSharpFindDeclFailureReason.Unknown r -> r 53 | return ResultOrString.Error (sprintf "Could not find declaration. %s" elaboration) 54 | | FSharpFindDeclResult.DeclFound range when range.FileName.EndsWith(Range.rangeStartup.FileName) -> return ResultOrString.Error "Could not find declaration" 55 | | FSharpFindDeclResult.DeclFound range when System.IO.File.Exists range.FileName -> 56 | let rangeStr = range.ToString() 57 | logger.info (Log.setMessage "Got a declresult of {range} that supposedly exists" >> Log.addContextDestructured "range" rangeStr) 58 | return Ok (FindDeclarationResult.Range range) 59 | | FSharpFindDeclResult.DeclFound rangeInNonexistentFile -> 60 | let range = rangeInNonexistentFile.ToString() 61 | logger.warn (Log.setMessage "Got a declresult of {range} that doesn't exist" >> Log.addContextDestructured "range" range) 62 | match tryRecoverExternalSymbolForNonexistentDecl rangeInNonexistentFile with 63 | | Ok (assemblyFile, sourceFile) -> 64 | match! Sourcelink.tryFetchSourcelinkFile assemblyFile sourceFile with 65 | | Ok localFilePath -> 66 | return ResultOrString.Ok (FindDeclarationResult.ExternalDeclaration { File = UMX.untag localFilePath; Position = rangeInNonexistentFile.Start }) 67 | | Error reason -> 68 | return ResultOrString.Error (sprintf "%A" reason) 69 | | Error e -> return Error e 70 | | FSharpFindDeclResult.ExternalDecl (assembly, externalSym) -> 71 | // not enough info on external symbols to get a range-like thing :( 72 | match tryGetSourceRangeForSymbol externalSym with 73 | | Some (sourceFile, pos) -> 74 | match! Sourcelink.tryFetchSourcelinkFile (UMX.tag assembly) sourceFile with 75 | | Ok localFilePath -> 76 | return ResultOrString.Ok (FindDeclarationResult.ExternalDeclaration { File = UMX.untag localFilePath; Position = pos }) 77 | | Error reason -> 78 | logger.info (Log.setMessage "no sourcelink info for {assembly}, decompiling instead" >> Log.addContextDestructured "assembly" assembly) 79 | return decompile assembly externalSym 80 | | None -> 81 | return decompile assembly externalSym 82 | } -------------------------------------------------------------------------------- /src/FSharpLanguageServer/ProgressBar.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpLanguageServer 2 | 3 | open System 4 | open System.IO 5 | open LSP.Types 6 | open FSharp.Data 7 | 8 | /// When we check a long series of files, create a progress bar 9 | type ProgressBar(nFiles: int, title: string, client: ILanguageClient, ?hide: bool) = 10 | let hide = defaultArg hide false 11 | let message = JsonValue.Record [| "title", JsonValue.String(title) 12 | "nFiles", JsonValue.Number(decimal(nFiles)) |] 13 | do if not hide then 14 | client.CustomNotification("fsharp/startProgress", message) 15 | /// Increment the progress bar and change the displayed message to the current file name 16 | member this.Increment(sourceFile: FileInfo) = 17 | if not hide then 18 | client.CustomNotification("fsharp/incrementProgress", JsonValue.String(sourceFile.Name)) 19 | /// Close the progress bar 20 | interface IDisposable with 21 | member this.Dispose() = 22 | if not hide then 23 | client.CustomNotification("fsharp/endProgress", JsonValue.Null) -------------------------------------------------------------------------------- /src/FSharpLanguageServer/Semantic.fs: -------------------------------------------------------------------------------- 1 | module FSharpLanguageServer.SemanticTokenization 2 | open System 3 | open LSP.SemanticToken 4 | open LSP 5 | open FSharp.Compiler.EditorServices 6 | 7 | open FSharp.Compiler.Text 8 | open FSharp.Compiler.CodeAnalysis 9 | open LSP.Types 10 | 11 | // See https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide#semantic-token-scope-map for the built-in scopes 12 | // if new token-type strings are added here, make sure to update the 'legend' in any downstream consumers. 13 | let map (t: SemanticClassificationType) : string = 14 | match t with 15 | | SemanticClassificationType.Operator -> "operator" 16 | | SemanticClassificationType.ReferenceType 17 | | SemanticClassificationType.Type 18 | | SemanticClassificationType.TypeDef 19 | | SemanticClassificationType.ConstructorForReferenceType -> "type" 20 | | SemanticClassificationType.ValueType 21 | | SemanticClassificationType.ConstructorForValueType -> "struct" 22 | | SemanticClassificationType.UnionCase 23 | | SemanticClassificationType.UnionCaseField -> "enumMember" 24 | | SemanticClassificationType.Function 25 | | SemanticClassificationType.Method 26 | | SemanticClassificationType.ExtensionMethod -> "function" 27 | | SemanticClassificationType.Property -> "property" 28 | | SemanticClassificationType.MutableVar 29 | | SemanticClassificationType.MutableRecordField -> "mutable" 30 | | SemanticClassificationType.Module 31 | | SemanticClassificationType.Namespace -> "namespace" 32 | | SemanticClassificationType.Printf -> "regexp" 33 | | SemanticClassificationType.ComputationExpression -> "cexpr" 34 | | SemanticClassificationType.IntrinsicFunction -> "function" 35 | | SemanticClassificationType.Enumeration -> "enum" 36 | | SemanticClassificationType.Interface -> "interface" 37 | | SemanticClassificationType.TypeArgument -> "typeParameter" 38 | | SemanticClassificationType.DisposableTopLevelValue 39 | | SemanticClassificationType.DisposableLocalValue 40 | | SemanticClassificationType.DisposableType -> "disposable" 41 | | SemanticClassificationType.Literal -> "variable.readonly.defaultLibrary" 42 | | SemanticClassificationType.RecordField 43 | | SemanticClassificationType.RecordFieldAsFunction -> "property.readonly" 44 | | SemanticClassificationType.Exception 45 | | SemanticClassificationType.Field 46 | | SemanticClassificationType.Event 47 | | SemanticClassificationType.Delegate 48 | | SemanticClassificationType.NamedArgument -> "member" 49 | | SemanticClassificationType.Value 50 | | SemanticClassificationType.LocalValue -> "variable" 51 | | SemanticClassificationType.Plaintext -> "text" 52 | 53 | 54 | ///Converts FCS token information to LSP Token information 55 | let private convertToken (tokens:SemanticClassificationItem[] option)= 56 | match tokens with 57 | | None -> 58 | None 59 | | Some rangesAndHighlights -> 60 | let lspTypedRanges = 61 | rangesAndHighlights 62 | |> Array.map (fun {Range=fcsRange;Type= fcsTokenType} -> 63 | 64 | let ty, mods = SemanticToken.map fcsTokenType 65 | struct(Conversions.asRange fcsRange, ty, mods) 66 | ) 67 | match SemanticToken.encodeSemanticHighlightRanges lspTypedRanges with 68 | | None -> 69 | None 70 | | Some encoded -> 71 | (Some { data = encoded|>Array.toList; resultId = None }) // TODO: provide a resultId when we support delta ranges 72 | 73 | 74 | let posEq (p1: pos) (p2: pos) = p1 = p2 75 | //All taken from FSAC semnantic token code 76 | 77 | /// given an enveloping range and the sub-ranges it overlaps, split out the enveloping range into a 78 | /// set of range segments that are non-overlapping with the children 79 | let private segmentRanges (parentRange: Range) (childRanges: Range []): Range [] = 80 | let firstSegment = Range.mkRange parentRange.FileName parentRange.Start childRanges.[0].Start // from start of parent to start of first child 81 | let lastSegment = Range.mkRange parentRange.FileName (Array.last childRanges).End parentRange.End // from end of last child to end of parent 82 | // now we can go pairwise, emitting a new range for the area between each end and start 83 | let innerSegments = 84 | childRanges |> Array.pairwise |> Array.map (fun (left, right) -> Range.mkRange parentRange.FileName left.End right.Start) 85 | 86 | [| 87 | // note that the first and last segments can be zero-length. 88 | // in that case we should not emit them because it confuses the 89 | // encoding algorithm 90 | if posEq firstSegment.Start firstSegment.End then () else firstSegment 91 | yield! innerSegments 92 | if posEq lastSegment.Start lastSegment.End then () else lastSegment 93 | |] 94 | 95 | /// TODO: LSP technically does now know how to handle overlapping, nested and multiline ranges, but 96 | /// as of 3 February 2021 there are no good examples of this that I've found, so we still do this 97 | /// because LSP doesn't know how to handle overlapping/nested ranges, we have to dedupe them here 98 | let private scrubRanges (highlights: SemanticClassificationItem array): SemanticClassificationItem array = 99 | let startToken = fun( {Range=m}:SemanticClassificationItem) -> m.Start.Line, m.Start.Column 100 | highlights 101 | |> Array.sortBy startToken 102 | |> Array.groupBy (fun {Range=r} -> r.StartLine) 103 | |> Array.collect (fun (_, highlights) -> 104 | 105 | // split out any ranges that contain other ranges on this line into the non-overlapping portions of that range 106 | let expandParents ({Range=parentRange;Type=tokenType}:SemanticClassificationItem as p) = 107 | let children = 108 | highlights 109 | |> Array.except [p] 110 | |> Array.choose (fun {Range=childRange} -> if Range.rangeContainsRange parentRange childRange then Some childRange else None) 111 | match children with 112 | | [||] -> [| p |] 113 | | children -> 114 | let sortedChildren = children |> Array.sortBy (fun r -> r.Start.Line, r.Start.Column) 115 | segmentRanges parentRange sortedChildren 116 | |> Array.map (fun subRange -> SemanticClassificationItem((subRange,tokenType)) ) 117 | 118 | highlights 119 | |> Array.collect expandParents 120 | ) 121 | |> Array.sortBy startToken 122 | 123 | ///Gets the tokens for semantic tokenization 124 | ///This is done by getting typecheck data and then getting the individual semntic clasifcations of the tokens in that data 125 | ///The checking can be done only in a certain range using the Range attribute. I'm not sure if this is really much faster or not 126 | //TODO: benchmark this process to find out whether only doing a certian range is actually faster 127 | let getSemanticTokens range (typeCheckResults:Result<(FSharpParseFileResults * FSharpCheckFileResults),Diagnostic list>)= 128 | async{ 129 | let tokens= 130 | match typeCheckResults with 131 | |Ok(parse,check)-> 132 | let ranges=check.GetSemanticClassification range 133 | //If we don't do the crubbing heaps of the tokesn don't show up properly in the edditor 134 | let filteredRanges = scrubRanges ranges 135 | Some filteredRanges 136 | |_->None 137 | return convertToken tokens 138 | } -------------------------------------------------------------------------------- /src/FSharpLanguageServer/SyntaxTreeOps.fs: -------------------------------------------------------------------------------- 1 | module FSharpLanguageServer.SyntaxOps 2 | 3 | // Forked from https://github.com/dotnet/fsharp/blob/60a2fa663a3c4aed3f03c8bfc6f5e05b04284f23/src/fsharp/range.fs 4 | 5 | open FSharp.Compiler.Syntax 6 | open FSharp.Compiler.Text.Range 7 | 8 | let ident (s, r) = Ident(s, r) 9 | 10 | let textOfId (id: Ident) = id.idText 11 | 12 | let pathOfLid lid = List.map textOfId lid 13 | 14 | let arrPathOfLid lid = Array.ofList (pathOfLid lid) 15 | 16 | let textOfPath path = String.concat "." path 17 | 18 | let textOfLid lid = textOfPath (pathOfLid lid) 19 | 20 | let rangeOfLid (lid: Ident list) = 21 | match lid with 22 | | [] -> failwith "rangeOfLid" 23 | | [id] -> id.idRange 24 | | h :: t -> unionRanges h.idRange (List.last t).idRange 25 | -------------------------------------------------------------------------------- /src/FSharpLanguageServer/ToolTips/Format.fs: -------------------------------------------------------------------------------- 1 | module private FSharpLanguageServer.ToolTips.Formatting 2 | 3 | open System 4 | open System.Text.RegularExpressions 5 | 6 | let inline nl<'T> = Environment.NewLine 7 | 8 | let tagPattern (tagName: string) = 9 | sprintf 10 | """(?'void_element'<%s(?'void_attributes'\s+[^\/>]+)?\/>)|(?'non_void_element'<%s(?'non_void_attributes'\s+[^>]+)?>(?'non_void_innerText'(?:(?!<%s>)(?!<\/%s>)[\s\S])*)<\/%s\s*>)""" 11 | tagName 12 | tagName 13 | tagName 14 | tagName 15 | tagName 16 | 17 | type TagInfo = 18 | | VoidElement of attributes: Map 19 | | NonVoidElement of innerText: string * attributes: Map 20 | 21 | type FormatterInfo = 22 | { TagName: string 23 | Formatter: TagInfo -> string option } 24 | 25 | let private extractTextFromQuote (quotedText: string) = 26 | quotedText.Substring(1, quotedText.Length - 2) 27 | 28 | 29 | let extractMemberText (text: string) = 30 | let pattern = 31 | "(?'member_type'[a-z]{1}:)?(?'member_text'.*)" 32 | 33 | let m = 34 | Regex.Match(text, pattern, RegexOptions.IgnoreCase) 35 | 36 | if m.Groups.["member_text"].Success then 37 | m.Groups.["member_text"].Value 38 | else 39 | text 40 | 41 | let getAttributes (attributes: Group) = 42 | if attributes.Success then 43 | let pattern = 44 | """(?'key'\S+)=(?'value''[^']*'|"[^"]*")""" 45 | 46 | Regex.Matches(attributes.Value, pattern, RegexOptions.IgnoreCase) 47 | |> Seq.cast 48 | |> Seq.map (fun m -> m.Groups.["key"].Value, extractTextFromQuote m.Groups.["value"].Value) 49 | |> Map.ofSeq 50 | else 51 | Map.empty 52 | 53 | type AttrLookup = Map -> Option 54 | 55 | let cref: AttrLookup = Map.tryFind "cref" 56 | let langword: AttrLookup = Map.tryFind "langword" 57 | let href: AttrLookup = Map.tryFind "href" 58 | let lang: AttrLookup = Map.tryFind "lang" 59 | let name: AttrLookup = Map.tryFind "name" 60 | 61 | let rec applyFormatter (info: FormatterInfo) text = 62 | let pattern = tagPattern info.TagName 63 | 64 | match Regex.Match(text, pattern, RegexOptions.IgnoreCase) with 65 | | m when m.Success -> 66 | if m.Groups.["void_element"].Success then 67 | let attributes = 68 | getAttributes m.Groups.["void_attributes"] 69 | 70 | let replacement = VoidElement attributes |> info.Formatter 71 | 72 | match replacement with 73 | | Some replacement -> 74 | text.Replace(m.Groups.["void_element"].Value, replacement) 75 | // Re-apply the formatter, because perhaps there is more 76 | // of the current tag to convert 77 | |> applyFormatter info 78 | 79 | | None -> 80 | // The formatter wasn't able to convert the tag 81 | // Return as it is and don't re-apply the formatter 82 | // otherwise it will create an infinity loop 83 | text 84 | 85 | else if m.Groups.["non_void_element"].Success then 86 | let innerText = m.Groups.["non_void_innerText"].Value 87 | 88 | let attributes = 89 | getAttributes m.Groups.["non_void_attributes"] 90 | 91 | let replacement = 92 | NonVoidElement(innerText, attributes) 93 | |> info.Formatter 94 | 95 | match replacement with 96 | | Some replacement -> 97 | // Re-apply the formatter, because perhaps there is more 98 | // of the current tag to convert 99 | text.Replace(m.Groups.["non_void_element"].Value, replacement) 100 | |> applyFormatter info 101 | 102 | | None -> 103 | // The formatter wasn't able to convert the tag 104 | // Return as it is and don't re-apply the formatter 105 | // otherwise it will create an infinity loop 106 | text 107 | else 108 | // Should not happend but like that we are sure to handle all possible cases 109 | text 110 | | _ -> text 111 | 112 | let codeBlock = 113 | { TagName = "code" 114 | Formatter = 115 | function 116 | | VoidElement _ -> None 117 | 118 | | NonVoidElement (innerText, attributes) -> 119 | let lang = 120 | match lang attributes with 121 | | Some lang -> lang 122 | 123 | | None -> "forceNoHighlight" 124 | 125 | let formattedText = 126 | if innerText.Contains("\n") then 127 | 128 | if innerText.StartsWith("\n") then 129 | 130 | sprintf "```%s%s\n```" lang innerText 131 | 132 | else 133 | sprintf "```%s\n%s\n```" lang innerText 134 | 135 | else 136 | sprintf "`%s`" innerText 137 | 138 | Some formattedText 139 | 140 | } 141 | |> applyFormatter 142 | 143 | let codeInline = 144 | { TagName = "c" 145 | Formatter = 146 | function 147 | | VoidElement _ -> None 148 | | NonVoidElement (innerText, _) -> "`" + innerText + "`" |> Some } 149 | |> applyFormatter 150 | 151 | let link text uri = $"[`%s{text}`](%s{uri})" 152 | let code text = $"`%s{text}`" 153 | 154 | let anchor = 155 | { TagName = "a" 156 | Formatter = 157 | function 158 | | VoidElement attributes -> 159 | match href attributes with 160 | | Some href -> Some(link href href) 161 | | None -> None 162 | 163 | | NonVoidElement (innerText, attributes) -> 164 | match href attributes with 165 | | Some href -> Some(link innerText href) 166 | | None -> Some(code innerText) } 167 | |> applyFormatter 168 | 169 | let paragraph = 170 | { TagName = "para" 171 | Formatter = 172 | function 173 | | VoidElement _ -> None 174 | 175 | | NonVoidElement (innerText, _) -> nl + innerText + nl |> Some } 176 | |> applyFormatter 177 | 178 | let block = 179 | { TagName = "block" 180 | Formatter = 181 | function 182 | | VoidElement _ -> None 183 | 184 | | NonVoidElement (innerText, _) -> nl + innerText + nl |> Some } 185 | |> applyFormatter 186 | 187 | let see = 188 | let formatFromAttributes (attrs: Map) = 189 | match cref attrs with 190 | // crefs can have backticks in them, which mess with formatting. 191 | // for safety we can just double-backtick and markdown is ok with that. 192 | | Some cref -> Some $"``{extractMemberText cref}``" 193 | | None -> 194 | match langword attrs with 195 | | Some langword -> Some(code langword) 196 | | None -> None 197 | 198 | { TagName = "see" 199 | Formatter = 200 | function 201 | | VoidElement attributes -> formatFromAttributes attributes 202 | | NonVoidElement (innerText, attributes) -> 203 | if String.IsNullOrWhiteSpace innerText then 204 | formatFromAttributes attributes 205 | else 206 | match href attributes with 207 | | Some externalUrl -> Some(link innerText externalUrl) 208 | | None -> Some $"`{innerText}`" } 209 | |> applyFormatter 210 | 211 | let xref = 212 | { TagName = "xref" 213 | Formatter = 214 | function 215 | | VoidElement attributes -> 216 | match href attributes with 217 | | Some href -> Some(link href href) 218 | | None -> None 219 | 220 | | NonVoidElement (innerText, attributes) -> 221 | if String.IsNullOrWhiteSpace innerText then 222 | match href attributes with 223 | | Some href -> Some(link innerText href) 224 | | None -> None 225 | else 226 | Some(code innerText) } 227 | |> applyFormatter 228 | 229 | let paramRef = 230 | { TagName = "paramref" 231 | Formatter = 232 | function 233 | | VoidElement attributes -> 234 | match name attributes with 235 | | Some name -> Some(code name) 236 | | None -> None 237 | 238 | | NonVoidElement (innerText, attributes) -> 239 | if String.IsNullOrWhiteSpace innerText then 240 | match name attributes with 241 | | Some name -> 242 | // TODO: Add config to generates command 243 | Some(code name) 244 | | None -> None 245 | else 246 | Some(code innerText) 247 | 248 | } 249 | |> applyFormatter 250 | 251 | let typeParamRef = 252 | { TagName = "typeparamref" 253 | Formatter = 254 | function 255 | | VoidElement attributes -> 256 | match name attributes with 257 | | Some name -> Some(code name) 258 | | None -> None 259 | 260 | | NonVoidElement (innerText, attributes) -> 261 | if String.IsNullOrWhiteSpace innerText then 262 | match name attributes with 263 | | Some name -> 264 | // TODO: Add config to generates command 265 | Some(code name) 266 | | None -> None 267 | else 268 | Some(code innerText) } 269 | |> applyFormatter 270 | 271 | let fixPortableClassLibrary (text: string) = 272 | text.Replace( 273 | "~/docs/standard/cross-platform/cross-platform-development-with-the-portable-class-library.md", 274 | "https://docs.microsoft.com/en-gb/dotnet/standard/cross-platform/cross-platform-development-with-the-portable-class-library" 275 | ) 276 | -------------------------------------------------------------------------------- /src/FSharpLanguageServer/ToolTips/ToolTip.fs: -------------------------------------------------------------------------------- 1 | module FSharpLanguageServer.ToolTips.ToolTip 2 | open System 3 | open System.IO 4 | open System.Xml 5 | open System.Collections.Generic 6 | open System.Text.RegularExpressions 7 | open FSharp.Compiler.EditorServices 8 | open FSharp.Compiler.Symbols 9 | open FSharp.Compiler.Text 10 | open Formatting 11 | open XmlDoc 12 | open LSP.Log 13 | 14 | [] 15 | type FormatCommentStyle = 16 | | Legacy 17 | | FullEnhanced 18 | | SummaryOnly 19 | | Documentation 20 | 21 | // -------------------------------------------------------------------------------------- 22 | // Formatting of tool-tip information displayed in F# IntelliSense 23 | // -------------------------------------------------------------------------------------- 24 | let private buildFormatComment cmt (formatStyle: FormatCommentStyle) (typeDoc: string option) = 25 | match cmt with 26 | | FSharpXmlDoc.FromXmlText xmldoc -> 27 | try 28 | let document = xmldoc.GetXmlText() 29 | // We create a "fake" XML document in order to use the same parser for both libraries and user code 30 | let xml = sprintf "%s" document 31 | let doc = XmlDocument() 32 | doc.LoadXml(xml) 33 | 34 | // This try to mimic how we found the indentation size when working a real XML file 35 | let rec findIndentationSize (lines: string list) = 36 | match lines with 37 | | head :: tail -> 38 | let lesserThanIndex = head.IndexOf("<") 39 | 40 | if lesserThanIndex <> -1 then 41 | lesserThanIndex 42 | else 43 | findIndentationSize tail 44 | | [] -> 0 45 | 46 | let indentationSize = 47 | xmldoc.GetElaboratedXmlLines() 48 | |> Array.toList 49 | |> findIndentationSize 50 | 51 | let xmlDoc = XmlDocMember(doc, indentationSize, 0) 52 | 53 | match formatStyle with 54 | | FormatCommentStyle.Legacy -> xmlDoc.ToString() 55 | | FormatCommentStyle.SummaryOnly -> xmlDoc.ToSummaryOnlyString() 56 | | FormatCommentStyle.FullEnhanced -> xmlDoc.ToFullEnhancedString() 57 | | FormatCommentStyle.Documentation -> xmlDoc.ToDocumentationString() 58 | 59 | with 60 | | ex -> 61 | 62 | (lgWarn "TipFormatter - Error while parsing the doc comment {err}" ex) 63 | sprintf 64 | "An error occured when parsing the doc comment, please check that your doc comment is valid.\n\nMore info can be found LSP output" 65 | 66 | | FSharpXmlDoc.FromXmlFile (dllFile, memberName) -> 67 | match getXmlDoc dllFile with 68 | | Some doc when doc.ContainsKey memberName -> 69 | let typeDoc = 70 | match typeDoc with 71 | | Some s when doc.ContainsKey s -> 72 | match formatStyle with 73 | | FormatCommentStyle.Legacy -> doc.[s].ToString() 74 | | FormatCommentStyle.SummaryOnly -> doc.[s].ToSummaryOnlyString() 75 | | FormatCommentStyle.FullEnhanced -> doc.[s].ToFullEnhancedString() 76 | | FormatCommentStyle.Documentation -> doc.[s].ToDocumentationString() 77 | | _ -> "" 78 | 79 | match formatStyle with 80 | | FormatCommentStyle.Legacy -> 81 | doc.[memberName].ToString() 82 | + (if typeDoc <> "" then 83 | "\n\n" + typeDoc 84 | else 85 | "") 86 | | FormatCommentStyle.SummaryOnly -> 87 | doc.[memberName].ToSummaryOnlyString() 88 | + (if typeDoc <> "" then 89 | "\n\n" + typeDoc 90 | else 91 | "") 92 | | FormatCommentStyle.FullEnhanced -> 93 | doc.[memberName].ToFullEnhancedString() 94 | + (if typeDoc <> "" then 95 | "\n\n" + typeDoc 96 | else 97 | "") 98 | | FormatCommentStyle.Documentation -> 99 | doc.[memberName].ToDocumentationString() 100 | + (if typeDoc <> "" then 101 | "\n\n" + typeDoc 102 | else 103 | "") 104 | | _ -> "" 105 | | _ -> "" 106 | 107 | let formatTaggedText (t: TaggedText) : string = 108 | match t.Tag with 109 | | TextTag.ActivePatternResult 110 | | TextTag.UnionCase 111 | | TextTag.Delegate 112 | | TextTag.Field 113 | | TextTag.Keyword 114 | | TextTag.LineBreak 115 | | TextTag.Local 116 | | TextTag.RecordField 117 | | TextTag.Method 118 | | TextTag.Member 119 | | TextTag.ModuleBinding 120 | | TextTag.Function 121 | | TextTag.Module 122 | | TextTag.Namespace 123 | | TextTag.NumericLiteral 124 | | TextTag.Operator 125 | | TextTag.Parameter 126 | | TextTag.Property 127 | | TextTag.Space 128 | | TextTag.StringLiteral 129 | | TextTag.Text 130 | | TextTag.Punctuation 131 | | TextTag.UnknownType 132 | | TextTag.UnknownEntity -> t.Text 133 | | TextTag.Enum 134 | | TextTag.Event 135 | | TextTag.ActivePatternCase 136 | | TextTag.Struct 137 | | TextTag.Alias 138 | | TextTag.Class 139 | | TextTag.Union 140 | | TextTag.Interface 141 | | TextTag.Record 142 | | TextTag.TypeParameter -> $"`{t.Text}`" 143 | 144 | let formatTaggedTexts = Array.map formatTaggedText >> String.concat "" 145 | 146 | let formatGenericParameters (typeMappings: TaggedText [] list) = 147 | typeMappings 148 | |> List.map (fun typeMap -> $"* {formatTaggedTexts typeMap}") 149 | |> String.concat nl 150 | 151 | let formatTip (ToolTipText tips) : (string * string) list list = 152 | tips 153 | |> List.choose (function 154 | | ToolTipElement.Group items -> 155 | let getRemarks (it: ToolTipElementData) = 156 | it.Remarks 157 | |> Option.map formatTaggedTexts 158 | |> Option.defaultValue "" 159 | 160 | let makeTooltip (tipElement: ToolTipElementData) = 161 | let header = 162 | formatTaggedTexts tipElement.MainDescription 163 | + getRemarks tipElement 164 | 165 | let body = buildFormatComment tipElement.XmlDoc FormatCommentStyle.Legacy None 166 | header, body 167 | 168 | items |> List.map makeTooltip |> Some 169 | 170 | | ToolTipElement.CompositionError (error) -> Some [ ("", error) ] 171 | | _ -> None) 172 | 173 | let formatTipEnhanced 174 | (ToolTipText tips) 175 | (signature: string) 176 | (footer: string) 177 | (typeDoc: string option) 178 | (formatCommentStyle: FormatCommentStyle) 179 | : (string * string * string) list list = 180 | tips 181 | |> List.choose (function 182 | | ToolTipElement.Group items -> 183 | Some( 184 | items 185 | |> List.map (fun i -> 186 | let comment = 187 | if i.TypeMapping.IsEmpty then 188 | buildFormatComment i.XmlDoc formatCommentStyle typeDoc 189 | else 190 | buildFormatComment i.XmlDoc formatCommentStyle typeDoc 191 | + nl 192 | + nl 193 | + "**Generic Parameters**" 194 | + nl 195 | + nl 196 | + formatGenericParameters i.TypeMapping 197 | 198 | (signature, comment, footer)) 199 | ) 200 | | ToolTipElement.CompositionError (error) -> Some [ ("", error, "") ] 201 | | _ -> None) 202 | 203 | let formatDocumentation 204 | (ToolTipText tips) 205 | ((signature, (constructors, fields, functions, interfaces, attrs, ts)): string * (string [] * string [] * string [] * string [] * string [] * string [])) 206 | (footer: string) 207 | (cn: string) 208 | = 209 | tips 210 | |> List.choose (function 211 | | ToolTipElement.Group items -> 212 | Some( 213 | items 214 | |> List.map (fun i -> 215 | let comment = 216 | if i.TypeMapping.IsEmpty then 217 | buildFormatComment i.XmlDoc FormatCommentStyle.Documentation None 218 | else 219 | buildFormatComment i.XmlDoc FormatCommentStyle.Documentation None 220 | + nl 221 | + nl 222 | + "**Generic Parameters**" 223 | + nl 224 | + nl 225 | + formatGenericParameters i.TypeMapping 226 | 227 | (signature, constructors, fields, functions, interfaces, attrs, ts, comment, footer, cn)) 228 | ) 229 | | ToolTipElement.CompositionError (error) -> Some [ ("", [||], [||], [||], [||], [||], [||], error, "", "") ] 230 | | _ -> None) 231 | 232 | let formatDocumentationFromXmlSig 233 | (xmlSig: string) 234 | (assembly: string) 235 | ((signature, (constructors, fields, functions, interfaces, attrs, ts)): string * (string [] * string [] * string [] * string [] * string [] * string [])) 236 | (footer: string) 237 | (cn: string) 238 | = 239 | let xmlDoc = FSharpXmlDoc.FromXmlFile(assembly, xmlSig) 240 | let comment = buildFormatComment xmlDoc FormatCommentStyle.Documentation None 241 | [ [ (signature, constructors, fields, functions, interfaces, attrs, ts, comment, footer, cn) ] ] 242 | 243 | /// use this when you want the raw text strings, for example in fsharp/signature calls 244 | let unformattedTexts (t: TaggedText []) = t |> Array.map (fun t -> t.Text) |> String.concat "" 245 | 246 | let extractSignature (ToolTipText tips) = 247 | let getSignature (t: TaggedText []) = 248 | let str = unformattedTexts t 249 | let nlpos = str.IndexOfAny([| '\r'; '\n' |]) 250 | 251 | let firstLine = 252 | if nlpos > 0 then 253 | str.[0..nlpos - 1] 254 | else 255 | str 256 | 257 | if firstLine.StartsWith("type ", StringComparison.Ordinal) then 258 | let index = firstLine.LastIndexOf("=", StringComparison.Ordinal) 259 | 260 | if index > 0 then 261 | firstLine.[0..index - 1] 262 | else 263 | firstLine 264 | else 265 | firstLine 266 | 267 | let firstResult x = 268 | match x with 269 | | ToolTipElement.Group gs -> 270 | List.tryPick 271 | (fun (t: ToolTipElementData) -> 272 | if not (Array.isEmpty t.MainDescription) then 273 | Some t.MainDescription 274 | else 275 | None) 276 | gs 277 | | _ -> None 278 | 279 | tips 280 | |> Seq.tryPick firstResult 281 | |> Option.map getSignature 282 | |> Option.defaultValue "" 283 | 284 | /// extracts any generic parameters present in this tooltip, rendering them as plain text 285 | let extractGenericParameters (ToolTipText tips) = 286 | let firstResult x = 287 | match x with 288 | | ToolTipElement.Group gs -> 289 | List.tryPick 290 | (fun (t: ToolTipElementData) -> 291 | if not (t.TypeMapping.IsEmpty) then 292 | Some t.TypeMapping 293 | else 294 | None) 295 | gs 296 | | _ -> None 297 | 298 | tips 299 | |> Seq.tryPick firstResult 300 | |> Option.defaultValue [] 301 | |> List.map unformattedTexts -------------------------------------------------------------------------------- /src/FSharpLanguageServer/UnusedDeclarations.fs: -------------------------------------------------------------------------------- 1 | module FSharpLanguageServer.UnusedDeclarations 2 | 3 | // From https://github.com/Microsoft/visualfsharp/blob/master/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs 4 | 5 | open System.Collections.Generic 6 | open FSharp.Compiler.EditorServices 7 | open FSharp.Compiler.Symbols 8 | open FSharp.Compiler.CodeAnalysis 9 | 10 | type FSharpSymbol with 11 | member this.IsPrivateToFile = 12 | match this with 13 | | :? FSharpMemberOrFunctionOrValue as m -> not m.IsModuleValueOrMember 14 | | :? FSharpEntity as m -> m.Accessibility.IsPrivate 15 | | :? FSharpGenericParameter -> true 16 | | :? FSharpUnionCase as m -> m.Accessibility.IsPrivate 17 | | :? FSharpField as m -> m.Accessibility.IsPrivate 18 | | _ -> false 19 | member this.IsInternalToProject = 20 | match this with 21 | | :? FSharpParameter -> true 22 | | :? FSharpMemberOrFunctionOrValue as m -> not m.IsModuleValueOrMember || not m.Accessibility.IsPublic 23 | | :? FSharpEntity as m -> not m.Accessibility.IsPublic 24 | | :? FSharpGenericParameter -> true 25 | | :? FSharpUnionCase as m -> not m.Accessibility.IsPublic 26 | | :? FSharpField as m -> not m.Accessibility.IsPublic 27 | | _ -> false 28 | 29 | type FSharpSymbolUse with 30 | member this.IsPrivateToFile = 31 | let isPrivate = 32 | match this.Symbol with 33 | | :? FSharpMemberOrFunctionOrValue as m -> not m.IsModuleValueOrMember || m.Accessibility.IsPrivate 34 | | :? FSharpEntity as m -> m.Accessibility.IsPrivate 35 | | :? FSharpGenericParameter -> true 36 | | :? FSharpUnionCase as m -> m.Accessibility.IsPrivate 37 | | :? FSharpField as m -> m.Accessibility.IsPrivate 38 | | _ -> false 39 | let declarationLocation = 40 | match this.Symbol.SignatureLocation with 41 | | Some x -> Some x 42 | | _ -> 43 | match this.Symbol.DeclarationLocation with 44 | | Some x -> Some x 45 | | _ -> this.Symbol.ImplementationLocation 46 | let declaredInTheFile = 47 | match declarationLocation with 48 | | Some declRange -> declRange.FileName = this.Range.FileName 49 | | _ -> false 50 | isPrivate && declaredInTheFile 51 | 52 | let private isPotentiallyUnusedDeclaration(symbol: FSharpSymbol) : bool = 53 | match symbol with 54 | // Determining that a record, DU or module is used anywhere requires inspecting all their enclosed entities (fields, cases and func / vals) 55 | // for usages, which is too expensive to do. Hence we never gray them out. 56 | | :? FSharpEntity as e when e.IsFSharpRecord || e.IsFSharpUnion || e.IsInterface || e.IsFSharpModule || e.IsClass || e.IsNamespace -> false 57 | // FCS returns inconsistent results for override members; we're skipping these symbols. 58 | | :? FSharpMemberOrFunctionOrValue as f when 59 | f.IsOverrideOrExplicitInterfaceImplementation || 60 | f.IsBaseValue || 61 | f.IsConstructor -> false 62 | // Usage of DU case parameters does not give any meaningful feedback; we never gray them out. 63 | | :? FSharpParameter -> false 64 | | _ -> true 65 | 66 | let getUnusedDeclarationRanges(symbolsUses: FSharpSymbolUse[], isScript: bool) = 67 | let definitions = 68 | symbolsUses 69 | |> Array.filter (fun su -> 70 | su.IsFromDefinition && 71 | su.Symbol.DeclarationLocation.IsSome && 72 | (isScript || su.IsPrivateToFile) && 73 | not (su.Symbol.DisplayName.StartsWith "_") && 74 | isPotentiallyUnusedDeclaration su.Symbol) 75 | let usages = 76 | let usages = 77 | symbolsUses 78 | |> Array.filter (fun su -> not su.IsFromDefinition) 79 | |> Array.choose (fun su -> su.Symbol.DeclarationLocation) 80 | HashSet(usages) 81 | let unusedRanges = 82 | definitions 83 | |> Array.map (fun defSu -> defSu, usages.Contains defSu.Symbol.DeclarationLocation.Value) 84 | |> Array.groupBy (fun (defSu, _) -> defSu.Range) 85 | |> Array.filter (fun (_, defSus) -> defSus |> Array.forall (fun (_, isUsed) -> not isUsed)) 86 | |> Array.map (fun (m, _) -> m) 87 | unusedRanges -------------------------------------------------------------------------------- /src/FSharpLanguageServer/bin/Release/netcoreapp2.0/assembly/README.md: -------------------------------------------------------------------------------- 1 | This directory is a workaround of https://github.com/georgewfraser/fsharp-language-server/issues/29 2 | -------------------------------------------------------------------------------- /src/FSharpLanguageServer/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Compiler.Service 2 | HtmlAgilityPack 3 | FSharp.UMX 4 | FSharp.Core 5 | Newtonsoft.Json -------------------------------------------------------------------------------- /src/LSP/DocumentStore.fs: -------------------------------------------------------------------------------- 1 | namespace LSP 2 | 3 | open LSP.Log 4 | open System 5 | open System.IO 6 | open System.Collections.Generic 7 | open System.Text 8 | open Types 9 | open BaseTypes 10 | type private Version = { 11 | text: StringBuilder 12 | mutable version: int 13 | } 14 | 15 | module DocumentStoreUtils = 16 | let findRange(text: StringBuilder, range: Range): int * int = 17 | let mutable line = 0 18 | let mutable char = 0 19 | let mutable startOffset = 0 20 | let mutable endOffset = 0 21 | for offset = 0 to text.Length do 22 | if line = range.start.line && char = range.start.character then 23 | startOffset <- offset 24 | if line = range.``end``.line && char = range.``end``.character then 25 | endOffset <- offset 26 | if offset < text.Length then 27 | let c = text.[offset] 28 | if c = '\n' then 29 | line <- line + 1 30 | char <- 0 31 | else 32 | char <- char + 1 33 | (startOffset, endOffset) 34 | 35 | open DocumentStoreUtils 36 | 37 | type DocumentStore() = 38 | /// All open documents, organized by absolute path 39 | let activeDocuments = new Dictionary() 40 | /// Replace a section of an open file 41 | let patch(doc: VersionedTextDocumentIdentifier, range: Range, text: string): unit = 42 | let file = FileInfo(doc.uri.LocalPath) 43 | let existing = activeDocuments.[file.FullName] 44 | let startOffset, endOffset = findRange(existing.text, range) 45 | existing.text.Remove(startOffset, endOffset - startOffset) |> ignore 46 | existing.text.Insert(startOffset, text) |> ignore 47 | existing.version <- doc.version 48 | /// Replace the entire contents of an open file 49 | let replace(doc: VersionedTextDocumentIdentifier, text: string): unit = 50 | let file = FileInfo(doc.uri.LocalPath) 51 | let existing = activeDocuments.[file.FullName] 52 | existing.text.Clear() |> ignore 53 | existing.text.Append(text) |> ignore 54 | existing.version <- doc.version 55 | 56 | member this.Open(doc: DidOpenTextDocumentParams): unit = 57 | let file = FileInfo(doc.textDocument.uri.LocalPath) 58 | let text = StringBuilder(doc.textDocument.text) 59 | let version = {text = text; version = doc.textDocument.version} 60 | activeDocuments.[file.FullName] <- version 61 | 62 | member this.Change(doc: DidChangeTextDocumentParams): unit = 63 | let file = FileInfo(doc.textDocument.uri.LocalPath) 64 | let existing = activeDocuments.[file.FullName] 65 | if doc.textDocument.version <= existing.version then 66 | let oldVersion = existing.version 67 | let newVersion = doc.textDocument.version 68 | lgInfo3 "Changed version: {newVersion} of doc {name} is earlier than existing version {oldVersion}" newVersion file.Name oldVersion 69 | else 70 | for change in doc.contentChanges do 71 | match change.range with 72 | | Some range -> patch(doc.textDocument, range, change.text) 73 | | None -> replace(doc.textDocument, change.text) 74 | 75 | member this.GetText(file: FileInfo): string option = 76 | let found, value = activeDocuments.TryGetValue(file.FullName) 77 | if found then Some(value.text.ToString()) else None 78 | 79 | member this.GetVersion(file: FileInfo): int option = 80 | let found, value = activeDocuments.TryGetValue(file.FullName) 81 | if found then Some(value.version) else None 82 | 83 | member this.Get(file: FileInfo): option = 84 | let found, value = activeDocuments.TryGetValue(file.FullName) 85 | if found then Some(value.text.ToString(), value.version) else None 86 | 87 | member this.Close(doc: DidCloseTextDocumentParams): unit = 88 | let file = FileInfo(doc.textDocument.uri.LocalPath) 89 | activeDocuments.Remove(file.FullName) |> ignore 90 | 91 | member this.OpenFiles(): FileInfo list = 92 | [for file in activeDocuments.Keys do yield FileInfo(file)] -------------------------------------------------------------------------------- /src/LSP/LSP.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/LSP/Log.fs: -------------------------------------------------------------------------------- 1 | module LSP.Log 2 | 3 | open Serilog 4 | open Microsoft.FSharp.Core.Printf 5 | open System 6 | open System.Collections.Generic 7 | open System.IO 8 | open Serilog 9 | open Serilog 10 | open Serilog.Core 11 | open Serilog.Events 12 | let diagnosticsLog = ref stderr 13 | /// Print to LSP.Log.diagnosticsLog, which is stderr by default but can be redirected 14 | let dprintfn(fmt: Printf.TextWriterFormat<'T>): 'T = 15 | Printf.fprintfn diagnosticsLog.Value fmt 16 | 17 | 18 | let lgInfof fmt= 19 | ksprintf (Log.Information )fmt 20 | let lgWarnf fmt= 21 | ksprintf (Log.Warning )fmt 22 | let lgErrorf fmt= 23 | ksprintf (Log.Error )fmt 24 | let lgVerbosef fmt= 25 | ksprintf (Log.Verbose )fmt 26 | let lgDebugf fmt= 27 | ksprintf (Log.Debug )fmt 28 | 29 | let lgInfo (message:string) (data:obj)= 30 | Log.Information(message, data) 31 | let lgInfo2 (message:string) (data:obj) (data2:obj)= 32 | Log.Information(message, data,data2) 33 | let lgInfo3 (message:string) (data:obj) (data2:obj) (data3:obj)= 34 | Log.Information(message, data,data2,data3) 35 | 36 | let lgError (message:string) (data:obj)= 37 | Log.Error(message, data) 38 | let lgError2 (message:string) ( data:obj ) ( data2:obj )= 39 | Log.Error(message, data,data2) 40 | let lgError3 (message:string) (data:obj) ( data2:obj ) (data3:obj)= 41 | Log.Error(message, data,data2,data3) 42 | 43 | let lgWarn (message:string) (data:obj)= 44 | Log.Warning(message, data) 45 | let lgWarn2 (message:string) (data:obj) (data2:obj)= 46 | Log.Warning(message, data,data2) 47 | let lgWarn3 (message:string) (data:obj) (data2:obj) (data3:obj)= 48 | Log.Warning(message, data,data2,data3) 49 | 50 | let lgDebug (message:string) (data:obj)= 51 | Log.Debug(message, data) 52 | let lgDebug2 (message:string) (data:obj) (data2:obj)= 53 | Log.Debug(message, data,data2) 54 | let lgDebug3 (message:string) (data:obj) (data2:obj) (data3:obj)= 55 | Log.Debug(message, data,data2,data3) 56 | 57 | let lgVerb (message:string) (data:obj)= 58 | Log.Verbose(message, data) 59 | let lgVerb2 (message:string) (data:obj) (data2:obj)= 60 | Log.Verbose(message, data,data2) 61 | let lgVerb3 (message:string) (data:obj) (data2:obj) (data3:obj)= 62 | Log.Verbose(message, data,data2,data3) 63 | 64 | 65 | 66 | 67 | let startTime=DateTime.Now 68 | let createLogger logPath = 69 | let logName=sprintf "%sdebugLog-%i-%i_%i;%i-%is--.log" logPath startTime.Month startTime.Day startTime.Hour startTime.Minute startTime.Second 70 | dprintfn "%s"logName 71 | 72 | let logger= 73 | Serilog.LoggerConfiguration() 74 | .MinimumLevel.Verbose() 75 | .WriteTo.File(logName ,Serilog.Events.LogEventLevel.Verbose,rollingInterval=RollingInterval.Day,fileSizeLimitBytes=(int64 (1000*1000))) 76 | .WriteTo.File(logPath+"simpleLog-.log",Serilog.Events.LogEventLevel.Information,rollingInterval=RollingInterval.Day,fileSizeLimitBytes=(int64 (1000*1000))) 77 | .WriteTo.Console(theme=Sinks.SystemConsole.Themes.SystemConsoleTheme.Literate,restrictedToMinimumLevel=Serilog.Events.LogEventLevel.Information, standardErrorFromLevel=Serilog.Events.LogEventLevel.Information) 78 | .CreateLogger() 79 | (* let logger2= 80 | Serilog.LoggerConfiguration() 81 | .WriteTo.Async( 82 | fun c -> c.Console(outputTemplate = outputTemplate, standardErrorFromLevel = Nullable<_>(LogEventLevel.Verbose), theme = Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme.Code) |> ignore 83 | ) // make it so that every console log is logged to stderr 84 | *) 85 | Serilog.Log.Logger<-logger 86 | dprintfn "logger created" 87 | -------------------------------------------------------------------------------- /src/LSP/Ser.fs: -------------------------------------------------------------------------------- 1 | module LSP.Json.Ser 2 | 3 | open System 4 | open System.Reflection 5 | open Microsoft.FSharp.Reflection 6 | open Microsoft.FSharp.Reflection.FSharpReflectionExtensions 7 | open System.Text.RegularExpressions 8 | open FSharp.Data 9 | open LSP.Log 10 | let private escapeChars = Regex("[\t\n\r\"\\\\]", RegexOptions.Compiled) 11 | let private replaceChars = 12 | MatchEvaluator(fun m -> 13 | match m.Value with 14 | | "\\" -> "\\\\" 15 | | "\t" -> "\\t" 16 | | "\n" -> "\\n" 17 | | "\r" -> "\\r" 18 | | "\"" -> "\\\"" 19 | | v -> v) 20 | let private escapeStr(text:string) = 21 | let escaped = escapeChars.Replace(text, replaceChars) 22 | sprintf "\"%s\"" escaped 23 | 24 | let private isOption(t: Type) = 25 | t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<_ option> 26 | 27 | let private isSeq(t: Type) = 28 | t.IsGenericType && t.GetGenericTypeDefinition() = typedefof> 29 | let private implementsSeq(t: Type) = 30 | let is = t.GetInterfaces() 31 | Seq.exists isSeq is 32 | 33 | let private isList(t: Type) = 34 | t.IsGenericType && t.GetGenericTypeDefinition() = typedefof> 35 | 36 | let private isMap(t: Type) = 37 | t.IsGenericType && t.GetGenericTypeDefinition() = typedefof> 38 | 39 | type JsonWriteOptions = { 40 | customWriters: obj list 41 | } 42 | 43 | let defaultJsonWriteOptions: JsonWriteOptions = { 44 | customWriters = [] 45 | } 46 | 47 | let private matchWriter(t: Type, w: obj): bool = 48 | let domain, _ = w.GetType() |> FSharpType.GetFunctionElements 49 | domain.IsAssignableFrom(t) 50 | 51 | let private findWriter(t: Type, customWriters: obj list): obj option = 52 | let matchT(w: obj) = matchWriter(t, w) 53 | Seq.tryFind matchT customWriters 54 | 55 | let asFun(w: obj): obj -> obj = 56 | let invoke = w.GetType().GetMethod("Invoke") 57 | fun x -> invoke.Invoke(w, [|x|]) 58 | 59 | type MakeHelpers = 60 | static member MakeList<'T> (items: obj seq): 'T list = 61 | [ for i in items do yield i :?> 'T ] 62 | static member MakeMap<'T> (items: (string * obj) seq): Map = 63 | let castV(k: string, v: obj) = (k, v :?> 'T) 64 | let castItems = Seq.map castV items 65 | Map.ofSeq(castItems) 66 | static member MakeOption<'T> (item: obj option): 'T option = 67 | match item with 68 | | None -> None 69 | | Some(i) -> Some(i :?> 'T) 70 | 71 | let private makeList(t: Type, items: obj seq) = 72 | typeof.GetMethod("MakeList").MakeGenericMethod([|t|]).Invoke(null, [|items|]) 73 | 74 | let private makeMap(t: Type, kvs: (string * obj) seq) = 75 | typeof.GetMethod("MakeMap").MakeGenericMethod([|t|]).Invoke(null, [|kvs|]) 76 | 77 | let private makeOption(t: Type, item: obj option) = 78 | typeof.GetMethod("MakeOption").MakeGenericMethod([|t|]).Invoke(null, [|item|]) 79 | 80 | let rec private serializer (options: JsonWriteOptions, t: Type): obj -> string = 81 | 82 | let custom = findWriter(t, options.customWriters) 83 | if custom.IsSome then 84 | let fObj = custom.Value 85 | let fType = fObj.GetType() 86 | let _, range = FSharpType.GetFunctionElements(fType) 87 | let serialize = serializer(options, range) 88 | let transform = asFun(fObj) 89 | fun o -> serialize(transform(o)) 90 | elif t = typeof then 91 | fun o -> sprintf "%b" (unbox o) 92 | elif t = typeof then 93 | fun o -> sprintf "%d" (unbox o) 94 | elif t = typeof then 95 | fun o -> sprintf "%d" (unbox o) 96 | elif t = typeof then 97 | fun o -> sprintf "%c" (unbox o) |> escapeStr 98 | elif t = typeof then 99 | fun o -> escapeStr (o :?> string) 100 | elif t = typeof then 101 | fun o -> 102 | let uri = o :?> Uri 103 | escapeStr(uri.ToString()) 104 | elif t = typeof then 105 | fun o -> 106 | let asJson = o :?> JsonValue 107 | asJson.ToString(JsonSaveOptions.DisableFormatting) 108 | elif FSharpType.IsRecord t then 109 | let fields = FSharpType.GetRecordFields(t) 110 | let serializers = [|for f in fields do yield fieldSerializer(options, f)|] 111 | fun outer -> 112 | let fieldStrings = [|for f in serializers do yield f(outer)|] 113 | let innerString = String.concat "," fieldStrings 114 | sprintf "{%s}" innerString 115 | elif implementsSeq t then 116 | let innerType = t.GetGenericArguments().[0] 117 | let serializeInner = serializer(options, innerType) 118 | fun outer -> 119 | let asEnum = outer :?> System.Collections.IEnumerable 120 | let asSeq = Seq.cast(asEnum) 121 | let inners = Seq.map serializeInner asSeq 122 | let join = String.Join(',', inners) 123 | sprintf "[%s]" join 124 | elif isOption t then 125 | let innerType = t.GetGenericArguments().[0] 126 | let isSomeProp = t.GetProperty("IsSome") 127 | let isSome outer = isSomeProp.GetValue(None, [|outer|]) :?> bool 128 | let valueProp = t.GetProperty("Value") 129 | let serializeInner = serializer(options, innerType) 130 | fun outer -> 131 | if isSome outer then 132 | let value = valueProp.GetValue outer 133 | serializeInner(value) 134 | else "null" 135 | else 136 | raise (Exception (sprintf "Don't know how to serialize %s to JSON" (t.ToString()))) 137 | and fieldSerializer (options: JsonWriteOptions, field: PropertyInfo): obj -> string = 138 | // dprintfn "Serializing feild %s" (field.ToString()) 139 | let name = escapeStr(field.Name) 140 | let innerSerializer = serializer(options, field.PropertyType) 141 | fun outer -> 142 | let value = field.GetValue(outer) 143 | let json = innerSerializer(value) 144 | sprintf "%s:%s" name json 145 | 146 | let serializerFactory<'T> (options: JsonWriteOptions): 'T -> string = serializer(options, typeof<'T>) 147 | 148 | type JsonReadOptions = { 149 | customReaders: obj list 150 | } 151 | 152 | let defaultJsonReadOptions: JsonReadOptions = { 153 | customReaders = [] 154 | } 155 | 156 | let private matchReader(t: Type, w: obj): bool = 157 | let _, range = w.GetType() |> FSharpType.GetFunctionElements 158 | t.IsAssignableFrom(range) 159 | 160 | let private findReader(t: Type, customReaders: obj list): obj option = 161 | let matchT(reader: obj) = matchReader(t, reader) 162 | Seq.tryFind matchT customReaders 163 | 164 | let rec private deserializer<'T> (options: JsonReadOptions, t: Type): JsonValue -> obj = 165 | let custom = findReader(t, options.customReaders) 166 | if custom.IsSome then 167 | let domain, _ = FSharpType.GetFunctionElements(custom.Value.GetType()) 168 | let deserializeDomain = deserializer(options, domain) 169 | let deserializeInner = asFun(custom.Value) 170 | fun j -> deserializeInner(deserializeDomain(j)) 171 | elif t = typeof then 172 | fun j -> box(j.AsBoolean()) 173 | elif t = typeof then 174 | fun j -> box(j.AsInteger()) 175 | elif t = typeof then 176 | fun j -> 177 | let s = j.AsString() 178 | if s.Length = 1 then box(s.[0]) 179 | else raise(Exception(sprintf "Expected char but found '%s'" s)) 180 | elif t = typeof then 181 | fun j -> box(j.AsString()) 182 | elif t = typeof then 183 | fun j -> 184 | // It seems that the Uri(_) constructor assumes the string has already been unescaped 185 | let escaped = j.AsString() 186 | let unescaped = Uri.UnescapeDataString(escaped) 187 | // This is pretty hacky but I couldn't figure out a better way 188 | // VSCode escapes # only once, but Uri(_) expects an unescaped string 189 | // It seems like either VSCode should be escaping # twice, or Uri(_) should be accepting escaped input 190 | let partlyEscaped = unescaped.Replace("?", "%3F").Replace("#", "%23") 191 | box(Uri(partlyEscaped)) 192 | elif t = typeof then 193 | fun j -> box(j) 194 | elif isList t then 195 | let innerType = t.GetGenericArguments().[0] 196 | let deserializeInner = deserializer(options, innerType) 197 | fun j -> 198 | let array = j.AsArray() 199 | let parse = Seq.map deserializeInner array 200 | let list = makeList(innerType, parse) 201 | box(list) 202 | elif isMap t then 203 | let genericArguments = t.GetGenericArguments() 204 | let stringType = genericArguments.[0] 205 | let valueType = genericArguments.[1] 206 | if stringType <> typeof then raise (Exception (sprintf "Keys of %A are not strings" t)) 207 | let deserializeInner = deserializer(options, valueType) 208 | fun j -> 209 | let props = j.Properties() 210 | let parse = Seq.map (fun (k, v) -> k, deserializeInner v) props 211 | makeMap(valueType, parse) 212 | elif isOption t then 213 | let innerType = t.GetGenericArguments().[0] 214 | let deserializeInner = deserializer(options, innerType) 215 | fun j -> 216 | if j = JsonValue.Null then 217 | box(makeOption(innerType, None)) 218 | else 219 | let parse = deserializeInner j 220 | box(makeOption(innerType, Some parse)) 221 | elif FSharpType.IsRecord t then 222 | let fields = FSharpType.GetRecordFields(t) 223 | let readers = [|for f in fields do yield fieldDeserializer(options, f)|] 224 | fun j -> 225 | let array = [| for field, reader in readers do 226 | yield reader j |] 227 | FSharpValue.MakeRecord(t, array) 228 | else 229 | raise (Exception (sprintf "Don't know how to deserialize %A from JSON" t)) 230 | and fieldDeserializer (options: JsonReadOptions, field: PropertyInfo): string * (JsonValue -> obj) = 231 | let deserializeInner = deserializer(options, field.PropertyType) 232 | let deserializeField(j: JsonValue) = 233 | let value = match j.TryGetProperty(field.Name) with Some v -> v | None -> JsonValue.Null 234 | box(deserializeInner(value)) 235 | field.Name, deserializeField 236 | 237 | let deserializerFactory<'T>(options: JsonReadOptions): JsonValue -> 'T = 238 | let d = deserializer(options, typeof<'T>) 239 | fun s -> d(s) :?> 'T 240 | -------------------------------------------------------------------------------- /src/LSP/Tokenizer.fs: -------------------------------------------------------------------------------- 1 | module LSP.Tokenizer 2 | 3 | open System 4 | open System.IO 5 | open System.Text 6 | open Log 7 | 8 | type Header = ContentLength of int | EmptyHeader | OtherHeader 9 | 10 | let parseHeader(header: string): Header = 11 | let contentLength = "Content-Length: " 12 | if header.StartsWith(contentLength) then 13 | let tail = header.Substring(contentLength.Length) 14 | let length = Int32.Parse(tail) 15 | ContentLength(length) 16 | elif header = "" then EmptyHeader 17 | else OtherHeader 18 | 19 | let rec private eatWhitespace(client: BinaryReader): char = 20 | let c = client.ReadChar() 21 | if Char.IsWhiteSpace(c) then 22 | eatWhitespace(client) 23 | else 24 | c 25 | 26 | let readLength(byteLength: int, client: BinaryReader): string = 27 | // Somehow, we are getting extra \r\n sequences, only when we compile to a standalone executable 28 | let head = eatWhitespace(client) 29 | let tail = client.ReadBytes(byteLength - 1) 30 | let string = Encoding.UTF8.GetString(tail) 31 | Convert.ToString(head) + string 32 | 33 | let readLine(client: BinaryReader): string option = 34 | let buffer = StringBuilder() 35 | try 36 | let mutable endOfLine = false 37 | while not endOfLine do 38 | let nextChar = client.ReadChar() 39 | if nextChar = '\n' then do 40 | endOfLine <- true 41 | elif nextChar = '\r' then do 42 | assert(client.ReadChar() = '\n') 43 | endOfLine <- true 44 | else do 45 | buffer.Append(nextChar) |> ignore 46 | Some(buffer.ToString()) 47 | with 48 | | :? EndOfStreamException -> 49 | if buffer.Length > 0 then 50 | Some(buffer.ToString()) 51 | else 52 | None 53 | 54 | let tokenize(client: BinaryReader): seq = 55 | seq { 56 | let mutable contentLength = -1 57 | let mutable endOfInput = false 58 | while not endOfInput do 59 | let maybeHeader = readLine(client) 60 | let next = Option.map parseHeader maybeHeader 61 | match next with 62 | | None -> endOfInput <- true 63 | | Some(ContentLength l) -> contentLength <- l 64 | | Some(EmptyHeader) -> yield readLength(contentLength, client) 65 | | _ -> () 66 | } 67 | -------------------------------------------------------------------------------- /src/LSP/Types/BaseTypes.fs: -------------------------------------------------------------------------------- 1 | 2 | [] 3 | module LSP.BaseTypes 4 | open System 5 | type Position = { 6 | line: int 7 | character: int 8 | } 9 | 10 | type Range = { 11 | start: Position 12 | ``end``: Position 13 | } 14 | type WorkspaceFolder = { 15 | uri: Uri 16 | name: string 17 | } 18 | type TextDocumentIdentifier = { 19 | uri: Uri 20 | } 21 | -------------------------------------------------------------------------------- /src/LSP/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Compiler.Service 2 | Serilog 3 | FSharp.Data 4 | Serilog.Sinks.Console 5 | Serilog.Sinks.File 6 | FSharp.Core -------------------------------------------------------------------------------- /src/ProjectCracker/ProjectCracker.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/ProjectCracker/paket.references: -------------------------------------------------------------------------------- 1 | Buildalyzer 2 | System.Threading.ThreadPool 3 | System.Net.NameResolution 4 | System.Security.Principal 5 | System.IO.FileSystem.Primitives 6 | System.IO.FileSystem 7 | FSharp.Core -------------------------------------------------------------------------------- /syntaxes/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Krzysztof Cieslak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /syntaxes/README.md: -------------------------------------------------------------------------------- 1 | From https://github.com/ionide/ionide-fsgrammar/tree/master/grammar and https://github.com/ionide/ionide-vscode-fsharp/blob/master/release/language-configuration.json -------------------------------------------------------------------------------- /syntaxes/fsharp.configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "comments": { 4 | "lineComment": "//", 5 | "blockComment": [ "(*", "*)" ] 6 | }, 7 | 8 | "autoClosingPairs": [ 9 | ["[", "]"], 10 | ["(", ")"], 11 | ["{", "}"], 12 | ["<@", "@>"], 13 | ["``", "``"], 14 | ["\"", "\""], 15 | ["(|", "|)"], 16 | ["[<",">]"], 17 | ["[|", "|]"], 18 | ["(*", "*)" ], 19 | ["<@@", "@@>"] 20 | ], 21 | 22 | "surroundingPairs": [ 23 | ["<@@", "@@>"], 24 | ["(|", "|)"], 25 | ["[<",">]"], 26 | ["[|", "|]"], 27 | ["{", "}"], 28 | ["[", "]"], 29 | ["(", ")"], 30 | ["\"", "\""], 31 | ["\"\"\"", "\"\"\""], 32 | ["(*", "*)"], 33 | ["<@", "@>"], 34 | ["'", "'"] 35 | ], 36 | 37 | "brackets": [ 38 | ["(", ")"], 39 | ["(*", "*)"], 40 | ["{", "}"], 41 | ["[", "]"], 42 | ["[|", "|]"], 43 | ["<@", "@>"], 44 | ["<@@", "@@>"] 45 | ], 46 | 47 | "wordPattern": "((\\w*')(\\w'?)*)|(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\$\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\\/\\s]+)" 48 | 49 | } 50 | -------------------------------------------------------------------------------- /syntaxes/fsharp.fsi.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fsharp.fsi", 3 | "scopeName": "source.fsharp.fsi", 4 | "fileTypes": [ 5 | "fsi" 6 | ], 7 | "foldingStartMarker": "", 8 | "foldingStopMarker": "", 9 | "patterns": [ 10 | { 11 | "include": "source.fsharp" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /syntaxes/fsharp.fsx.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fsharp.fsx", 3 | "scopeName": "source.fsharp.fsx", 4 | "fileTypes": [ 5 | "fsx" 6 | ], 7 | "foldingStartMarker": "", 8 | "foldingStopMarker": "", 9 | "patterns": [ 10 | { 11 | "include": "source.fsharp" 12 | }, 13 | { 14 | "name": "preprocessor.source.fsharp.fsx", 15 | "match": "^#(load|r|I|time)" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /tests/FSharpLanguageServer.Tests/Common.fs: -------------------------------------------------------------------------------- 1 | module FSharpLanguageServer.Tests.Common 2 | 3 | open System 4 | open System.IO 5 | 6 | let private findProjectRoot(start: DirectoryInfo): DirectoryInfo = 7 | seq { 8 | let mutable dir = start 9 | while dir <> dir.Root do 10 | for _ in dir.GetFiles "fsharp-language-server.sln" do 11 | yield dir 12 | dir <- dir.Parent 13 | } |> Seq.head 14 | let private testDirectory = DirectoryInfo(Directory.GetCurrentDirectory()) 15 | 16 | /// The root of the project folder 17 | let projectRoot = findProjectRoot(testDirectory) -------------------------------------------------------------------------------- /tests/FSharpLanguageServer.Tests/FSharpLanguageServer.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/FSharpLanguageServer.Tests/FormattingTests.fs: -------------------------------------------------------------------------------- 1 | module FSharpLanguageServer.Tests.FormattingTests 2 | 3 | open FSharp.Compiler 4 | open FSharp.Compiler.EditorServices 5 | open FSharpLanguageServer.Tests.Common 6 | open FSharpLanguageServer 7 | open System 8 | open System.IO 9 | open NUnit.Framework 10 | open LSP.Types 11 | open FSharp.Data 12 | open FSharp.Compiler.CodeAnalysis 13 | open ServerTests 14 | [] 15 | let ``hover over function With alias type``() = 16 | let client, server = createServerAndReadFile("MainProject", "Hover.fs") 17 | match server.Hover(textDocumentPosition("MainProject", "Hover.fs", 19, 6)) |> Async.RunSynchronously with 18 | | None -> Assert.Fail("No hover") 19 | | Some hover -> if List.isEmpty hover.contents then Assert.Fail("Hover list is empty") 20 | [] 21 | let hover_over_System_function() = 22 | let client, server = createServerAndReadFile("MainProject", "Hover.fs") 23 | 24 | match server.Hover(textDocumentPosition("MainProject", "Hover.fs", 14, 36)) |> Async.RunSynchronously with 25 | | None -> Assert.Fail("No hover") 26 | | Some hover -> 27 | if List.isEmpty hover.contents then Assert.Fail("Hover list is empty") 28 | let matches=hover.contents|>List.filter(fun x-> 29 | let doc= 30 | match x with 31 | |PlainString(s)->s 32 | |HighlightedString(s,_)->s 33 | Assert.False(doc.Contains "","Documentation contains xml tag meaning it was not correctly formatted with xml tags removed") 34 | doc.Contains("Applies a function to each element of the collection, threading an accumulator argument") 35 | ) 36 | Assert.True(matches.Length>0,"List does not contain required System function doc string") 37 | [] 38 | let hoverMethodParams() = 39 | let client, server = createServerAndReadFile("MainProject", "Hover.fs") 40 | 41 | match server.Hover(textDocumentPosition("MainProject", "Hover.fs", 25, 19)) |> Async.RunSynchronously with 42 | | None -> Assert.Fail("No hover") 43 | | Some hover -> 44 | if List.isEmpty hover.contents then Assert.Fail("Hover list is empty") 45 | let matches=hover.contents|>List.filter(fun x-> 46 | let doc= 47 | match x with 48 | |PlainString(s)->s 49 | |HighlightedString(s,_)->s 50 | doc.Contains("The encrypted authorization message expected by the server.") 51 | ) 52 | Assert.True(matches.Length>0,"List does not contain required function doc string") 53 | [] 54 | let hoverMDDocs() = 55 | let client, server = createServerAndReadFile("MainProject", "Hover.fs") 56 | 57 | match server.Hover(textDocumentPosition("MainProject", "Hover.fs", 23, 10)) |> Async.RunSynchronously with 58 | | None -> Assert.Fail("No hover") 59 | | Some hover -> 60 | if List.isEmpty hover.contents then Assert.Fail("Hover list is empty") 61 | let matches=hover.contents|>List.filter(fun x-> 62 | let doc= 63 | match x with 64 | |PlainString(s)->s 65 | |HighlightedString(s,_)->s 66 | doc.Contains("This function has documentation") 67 | ) 68 | Assert.True(matches.Length>0,"List does not contain required function doc string from a non-xml comment") 69 | let a=0 -------------------------------------------------------------------------------- /tests/FSharpLanguageServer.Tests/ProjectManagerTests.fs: -------------------------------------------------------------------------------- 1 | module FSharpLanguageServer.Tests.ProjectManagerTests 2 | 3 | open FSharp.Compiler 4 | open FSharp.Compiler.EditorServices 5 | open FSharpLanguageServer.Tests.Common 6 | open FSharpLanguageServer 7 | open System 8 | open System.IO 9 | open NUnit.Framework 10 | open LSP.Types 11 | open FSharp.Data 12 | open FSharp.Compiler.CodeAnalysis 13 | 14 | type MockClient() = 15 | member val Diagnostics = System.Collections.Generic.List() 16 | interface ILanguageClient with 17 | member this.PublishDiagnostics(p: PublishDiagnosticsParams): unit = 18 | () 19 | member this.ShowMessage(p: ShowMessageParams): unit = 20 | () 21 | member this.RegisterCapability(p: RegisterCapability): unit = 22 | () 23 | member this.CustomNotification(method: string, p: JsonValue): unit = 24 | () 25 | 26 | [] 27 | let setup() = 28 | LSP.Log.diagnosticsLog := stdout 29 | 30 | [] 31 | let ``find project file``() = 32 | let projects = ProjectManager(FSharpChecker.Create()) 33 | let root = Path.Combine [|projectRoot.FullName; "sample"; "MainProject"|] |> DirectoryInfo 34 | Async.RunSynchronously(projects.AddWorkspaceRoot(root)) 35 | let file = FileInfo(Path.Combine [|projectRoot.FullName; "sample"; "MainProject"; "Hover.fs"|]) 36 | let project = projects.FindProjectOptions(file) 37 | match project with 38 | | Error(m) -> Assert.Fail(sprintf "%A" m) 39 | | Ok(f) -> if not(f.ProjectFileName.EndsWith "MainProject.fsproj") then Assert.Fail(sprintf "%A" f) 40 | 41 | [] 42 | let ``choose fsproj referenced by sln``() = 43 | let projects = ProjectManager(FSharpChecker.Create()) 44 | let root = Path.Combine [|projectRoot.FullName; "sample"; "SlnReferences"|] |> DirectoryInfo 45 | Async.RunSynchronously(projects.AddWorkspaceRoot(root)) 46 | let file = FileInfo(Path.Combine [|projectRoot.FullName; "sample"; "SlnReferences"; "Main.fs"|]) 47 | let project = projects.FindProjectOptions(file) 48 | match project with 49 | | Error(m) -> Assert.Fail(sprintf "%A" m) 50 | | Ok(f) -> if not(f.ProjectFileName.EndsWith "ReferencedProject.fsproj") then Assert.Fail(sprintf "%A" f) 51 | 52 | [] 53 | let ``find script file``() = 54 | let projects = ProjectManager(FSharpChecker.Create()) 55 | let root = Path.Combine [|projectRoot.FullName; "sample"; "Script"|] |> DirectoryInfo 56 | Async.RunSynchronously(projects.AddWorkspaceRoot(root)) 57 | let file = FileInfo(Path.Combine [|projectRoot.FullName; "sample"; "Script"; "LoadedByScript.fs"|]) 58 | let project = projects.FindProjectOptions(file) 59 | match project with 60 | | Error(m) -> Assert.Fail(sprintf "%A" m) 61 | | Ok(f) -> if not(f.ProjectFileName.EndsWith("MainScript.fsx.fsproj")) then Assert.Fail(sprintf "%A" f) 62 | 63 | [] 64 | let ``find an local dll``() = 65 | let projects = ProjectManager(FSharpChecker.Create()) 66 | let root = Path.Combine [|projectRoot.FullName; "sample"; "HasLocalDll"|] |> DirectoryInfo 67 | Async.RunSynchronously(projects.AddWorkspaceRoot(root)) 68 | let file = FileInfo(Path.Combine [|projectRoot.FullName; "sample"; "HasLocalDll"; "Program.fs"|]) 69 | match projects.FindProjectOptions(file) with 70 | | Error(m) -> Assert.Fail(sprintf "%A" m) 71 | | Ok(parsed) -> 72 | let isLocalDll(s: string) = s.EndsWith("IndirectDep.dll") 73 | if not (Seq.exists isLocalDll parsed.OtherOptions) then Assert.Fail(sprintf "%A" parsed.OtherOptions) 74 | 75 | [] 76 | let ``project-file-not-found``() = 77 | let projects = ProjectManager(FSharpChecker.Create()) 78 | let file = FileInfo(Path.Combine [|projectRoot.FullName; "sample"; "MainProject"; "Hover.fs"|]) 79 | let project = projects.FindProjectOptions file 80 | match project with 81 | | Ok(f) -> Assert.Fail(sprintf "Shouldn't have found project file %s" f.ProjectFileName) 82 | | Error(_) -> () 83 | 84 | [] 85 | let ``bad project file``() = 86 | let projects = ProjectManager(FSharpChecker.Create()) 87 | let root = Path.Combine [|projectRoot.FullName; "sample"; "BadProject"|] |> DirectoryInfo 88 | Async.RunSynchronously(projects.AddWorkspaceRoot root) 89 | 90 | [] 91 | let ``get script options``() = 92 | let projects = ProjectManager(FSharpChecker.Create()) 93 | let script = Path.Combine [|projectRoot.FullName; "sample"; "Script"; "MainScript.fsx"|] |> FileInfo 94 | projects.NewProjectFile(script) 95 | match projects.FindProjectOptions(script) with 96 | | Error(m) -> Assert.Fail(sprintf "%A" m) 97 | | Ok(options) -> 98 | let references = [for o in options.OtherOptions do if o.StartsWith("-r:") then yield FileInfo(o.Substring("-r:".Length)).Name] 99 | CollectionAssert.Contains(references, "FSharp.Core.dll") 100 | CollectionAssert.Contains(references, "System.Runtime.dll") -------------------------------------------------------------------------------- /tests/FSharpLanguageServer.Tests/paket.references: -------------------------------------------------------------------------------- 1 | Microsoft.NET.Test.Sdk 2 | NUnit 3 | NUnit3TestAdapter 4 | FSharp.Core -------------------------------------------------------------------------------- /tests/LSP.Tests/DocumentStoreTests.fs: -------------------------------------------------------------------------------- 1 | module LSP.DocumentStoreTests 2 | 3 | open System 4 | open System.Text 5 | open System.IO 6 | open Types 7 | open NUnit.Framework 8 | 9 | [] 10 | let setup() = 11 | LSP.Log.diagnosticsLog := stdout 12 | 13 | [] 14 | let ``convert prefix Range to offsets``() = 15 | let text = "foo\r\n\ 16 | bar\r\n\ 17 | doh" 18 | let textBuilder = new StringBuilder(text) 19 | let range = 20 | { start = {line = 0; character = 0} 21 | ``end`` = {line = 0; character = 3} } 22 | let found = DocumentStoreUtils.findRange(textBuilder, range) 23 | Assert.AreEqual((0, 3), found) 24 | 25 | [] 26 | let ``convert suffix Range to offsets``() = 27 | let text = "foo\r\n\ 28 | bar\r\n\ 29 | doh" 30 | let textBuilder = new StringBuilder(text) 31 | let range = 32 | { start = {line = 2; character = 1} 33 | ``end`` = {line = 2; character = 3} } 34 | let found = DocumentStoreUtils.findRange(textBuilder, range) 35 | Assert.AreEqual((11, 13), found) 36 | 37 | [] 38 | let ``convert line-spanning Range to offsets``() = 39 | let text = "foo\r\n\ 40 | bar\r\n\ 41 | doh" 42 | let textBuilder = new StringBuilder(text) 43 | let range = 44 | { start = {line = 1; character = 2} 45 | ``end`` = {line = 2; character = 1} } 46 | let found = DocumentStoreUtils.findRange(textBuilder, range) 47 | Assert.AreEqual((7, 11), found) 48 | 49 | let exampleUri = Uri("file://" + Directory.GetCurrentDirectory() + "example.txt") 50 | 51 | [] 52 | let ``open document``() = 53 | let store = DocumentStore() 54 | let exampleUri = exampleUri 55 | let helloWorld = "Hello world!" 56 | let openDoc: DidOpenTextDocumentParams = 57 | { textDocument = 58 | { uri = exampleUri 59 | languageId = "plaintext" 60 | version = 1 61 | text = helloWorld } } 62 | store.Open(openDoc) 63 | let found = store.GetText(FileInfo(exampleUri.LocalPath)) 64 | Assert.AreEqual(Some(helloWorld), found) 65 | 66 | let helloStore() = 67 | let store = DocumentStore() 68 | let helloWorld = "Hello world!" 69 | let openDoc: DidOpenTextDocumentParams = 70 | { textDocument = 71 | { uri = exampleUri 72 | languageId = "plaintext" 73 | version = 1 74 | text = helloWorld } } 75 | store.Open(openDoc) 76 | store 77 | 78 | [] 79 | let ``replace a document``() = 80 | let store = helloStore() 81 | let newText = "Replaced everything" 82 | let replaceAll: DidChangeTextDocumentParams = 83 | { textDocument = 84 | { uri = exampleUri 85 | version = 2 } 86 | contentChanges = 87 | [ { range = None 88 | rangeLength = None 89 | text = newText } ] } 90 | store.Change(replaceAll) 91 | let found = store.GetText(FileInfo(exampleUri.LocalPath)) 92 | Assert.AreEqual(Some(newText), found) 93 | 94 | [] 95 | let ``patch a document``() = 96 | let store = helloStore() 97 | let newText = "George" 98 | let replaceAll: DidChangeTextDocumentParams = 99 | { textDocument = 100 | { uri = exampleUri 101 | version = 2 } 102 | contentChanges = 103 | [ { range = Some { start = {line = 0; character = 6} 104 | ``end`` = {line = 0; character = 11} } 105 | rangeLength = None 106 | text = newText } ] } 107 | store.Change(replaceAll) 108 | let found = store.GetText(FileInfo(exampleUri.LocalPath)) 109 | Assert.AreEqual(Some("Hello George!"), found) -------------------------------------------------------------------------------- /tests/LSP.Tests/JsonTests.fs: -------------------------------------------------------------------------------- 1 | module LSP.JsonTests 2 | 3 | open System 4 | open System.Text.RegularExpressions 5 | open FSharp.Data 6 | open LSP.Json.Ser 7 | open NUnit.Framework 8 | 9 | [] 10 | let setup() = 11 | LSP.Log.diagnosticsLog := stdout 12 | 13 | let removeSpace(expected: string) = 14 | Regex.Replace(expected, @"\s", "") 15 | 16 | [] 17 | let ``remove space from string`` () = 18 | let found = removeSpace("foo bar") 19 | Assert.AreEqual("foobar", found) 20 | 21 | [] 22 | let ``remove newline from string`` () = 23 | let actual = """foo 24 | bar""" 25 | let found = removeSpace(actual) 26 | Assert.AreEqual("foobar", found) 27 | 28 | [] 29 | let ``serialize primitive types to JSON`` () = 30 | let found = serializerFactory defaultJsonWriteOptions true 31 | Assert.AreEqual("true", found) 32 | let found = serializerFactory defaultJsonWriteOptions 1 33 | Assert.AreEqual("1", found) 34 | let found = serializerFactory defaultJsonWriteOptions "foo" 35 | Assert.AreEqual("\"foo\"", found) 36 | let found = serializerFactory defaultJsonWriteOptions 'f' 37 | Assert.AreEqual("\"f\"", found) 38 | 39 | [] 40 | let ``serialize URI to JSON`` () = 41 | let example = Uri("https://google.com") 42 | let found = serializerFactory defaultJsonWriteOptions example 43 | Assert.AreEqual("\"https://google.com/\"", found) 44 | 45 | [] 46 | let ``serialize JsonValue to JSON`` () = 47 | let example = JsonValue.Parse "{}" 48 | let found = serializerFactory defaultJsonWriteOptions example 49 | Assert.AreEqual("{}", found) 50 | 51 | [] 52 | let ``serialize option to JSON`` () = 53 | let found = serializerFactory defaultJsonWriteOptions (Some 1) 54 | Assert.AreEqual("1", found) 55 | let found = serializerFactory defaultJsonWriteOptions (None) 56 | Assert.AreEqual("null", found) 57 | 58 | type SimpleRecord = {simpleMember: int} 59 | 60 | [] 61 | let ``serialize record to JSON`` () = 62 | let record = {simpleMember = 1} 63 | let found = serializerFactory defaultJsonWriteOptions record 64 | Assert.AreEqual("""{"simpleMember":1}""", found) 65 | 66 | [] 67 | let ``serialize list of ints to JSON`` () = 68 | let example = [1; 2] 69 | let found = serializerFactory defaultJsonWriteOptions example 70 | Assert.AreEqual("""[1,2]""", found) 71 | 72 | [] 73 | let ``serialize list of strings to JSON`` () = 74 | let example = ["foo"; "bar"] 75 | let found = serializerFactory defaultJsonWriteOptions example 76 | Assert.AreEqual("""["foo","bar"]""", found) 77 | 78 | [] 79 | let ``serialize a record with a custom writer`` () = 80 | let record = {simpleMember = 1} 81 | let customWriter(r: SimpleRecord): string = sprintf "simpleMember=%d" r.simpleMember 82 | let options = {defaultJsonWriteOptions with customWriters = [customWriter]} 83 | let found = serializerFactory options record 84 | Assert.AreEqual("\"simpleMember=1\"", found) 85 | 86 | type Foo = Bar | Doh 87 | type FooRecord = {foo: Foo} 88 | 89 | [] 90 | let ``serialize a union with a custom writer`` () = 91 | let record = {foo = Bar} 92 | let customWriter = function 93 | | Bar -> 10 94 | | Doh -> 20 95 | let options = {defaultJsonWriteOptions with customWriters = [customWriter]} 96 | let found = serializerFactory options record 97 | Assert.AreEqual("""{"foo":10}""", found) 98 | 99 | // type UnionWithFields = 100 | // | OptionA of A: string 101 | // | OptionB of int 102 | 103 | // [] 104 | // let ``serialize union with fields`` () = 105 | // let options = defaultJsonReadOptions 106 | // let serializer = serializerFactory 107 | // let found = serializer options (OptionA "foo") 108 | // Assert.AreEqual("""{"A":"foo"}""", found) 109 | // let serializer = serializerFactory 110 | // let found = serializer options (OptionB 1) 111 | // Assert.AreEqual("""{"A":[1]}""", found) 112 | 113 | type IFoo = 114 | abstract member Foo: unit -> string 115 | type MyFoo() = 116 | interface IFoo with 117 | member this.Foo() = "foo" 118 | 119 | [] 120 | let ``serialize an interface with a custom writer`` () = 121 | let customWriter(foo: IFoo): string = 122 | foo.Foo() 123 | let options = {defaultJsonWriteOptions with customWriters = [customWriter]} 124 | let example = MyFoo() 125 | let found = serializerFactory options example 126 | Assert.AreEqual("\"foo\"", found) 127 | let found = serializerFactory options example 128 | Assert.AreEqual("\"foo\"", found) 129 | 130 | type SimpleTypes = { 131 | b: bool 132 | i: int 133 | c: char 134 | s: string 135 | webUri: Uri 136 | fileUri: Uri 137 | } 138 | 139 | [] 140 | let ``deserialize simple types`` () = 141 | let sample = """ 142 | { 143 | "b": true, 144 | "i": 1, 145 | "c": "x", 146 | "s": "foo", 147 | "webUri": "https://github.com", 148 | "fileUri": "file:///d%3A/foo.txt" 149 | }""" 150 | let options = defaultJsonReadOptions 151 | let found = deserializerFactory options (JsonValue.Parse sample) 152 | Assert.AreEqual(true, found.b) 153 | Assert.AreEqual(1, found.i) 154 | Assert.AreEqual('x', found.c) 155 | Assert.AreEqual("foo", found.s) 156 | Assert.AreEqual(Uri("https://github.com"), found.webUri) 157 | Assert.AreEqual("d:\\foo.txt", found.fileUri.LocalPath) 158 | 159 | type NestedField = { 160 | oneField: int 161 | } 162 | 163 | type ComplexTypes = { 164 | nested: NestedField 165 | intList: int list 166 | stringAsInt: int 167 | intOptionPresent: int option 168 | intOptionAbsent: int option 169 | } 170 | 171 | [] 172 | let ``deserialize complex types`` () = 173 | let sample = """ 174 | { 175 | "nested": { 176 | "oneField": 1 177 | }, 178 | "intList": [1], 179 | "stringAsInt": "1", 180 | "intOptionPresent": 1, 181 | "intOptionAbsent": null 182 | }""" 183 | let options = defaultJsonReadOptions 184 | let found = deserializerFactory options (JsonValue.Parse sample) 185 | Assert.AreEqual({oneField=1}, found.nested) 186 | Assert.AreEqual(1, found.stringAsInt) 187 | Assert.AreEqual([1], found.intList) 188 | Assert.AreEqual(Some 1, found.intOptionPresent) 189 | Assert.AreEqual(None, found.intOptionAbsent) 190 | 191 | type TestOptionalRead = { 192 | optionField: int option 193 | } 194 | 195 | [] 196 | let ``deserialize optional types`` () = 197 | let options = defaultJsonReadOptions 198 | let found = deserializerFactory options (JsonValue.Parse """{"optionField":1}""") 199 | Assert.AreEqual({optionField=Some 1}, found) 200 | let found = deserializerFactory options (JsonValue.Parse """{"optionField":null}""") 201 | Assert.AreEqual({optionField=None}, found) 202 | let found = deserializerFactory options (JsonValue.Parse """{}""") 203 | Assert.AreEqual({optionField=None}, found) 204 | let found = deserializerFactory options (JsonValue.Parse """[1]""") 205 | Assert.AreEqual([Some 1], found) 206 | let found = deserializerFactory options (JsonValue.Parse """[null]""") 207 | Assert.AreEqual([None], found) 208 | 209 | [] 210 | let ``deserialize map`` () = 211 | let options = defaultJsonReadOptions 212 | let found = deserializerFactory> options (JsonValue.Parse """{"k":1}""") 213 | let map = Map.add "k" 1 Map.empty 214 | Assert.AreEqual(map, found) 215 | 216 | type TestEnum = One | Two 217 | 218 | let deserializeTestEnum(i: int) = 219 | match i with 220 | | 1 -> One 221 | | 2 -> Two 222 | 223 | type ContainsEnum = { 224 | e: TestEnum 225 | } 226 | 227 | [] 228 | let ``deserialize enum`` () = 229 | let options = { defaultJsonReadOptions with customReaders = [deserializeTestEnum]} 230 | let found = deserializerFactory options (JsonValue.Parse """{"e":1}""") 231 | Assert.AreEqual(One, found.e) -------------------------------------------------------------------------------- /tests/LSP.Tests/LSP.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/LSP.Tests/LanguageServerTests.fs: -------------------------------------------------------------------------------- 1 | module LSP.LanguageServerTests 2 | 3 | open System 4 | open System.IO 5 | open System.Text 6 | open FSharp.Data 7 | open LSP.Types 8 | open LSP.SemanticToken 9 | open NUnit.Framework 10 | open SemanticToken 11 | 12 | [] 13 | let setup() = 14 | LSP.Log.diagnosticsLog := stdout 15 | 16 | let binaryWriter() = 17 | let stream = new MemoryStream() 18 | let writer = new BinaryWriter(stream) 19 | let toString() = 20 | let bytes = stream.ToArray() 21 | Encoding.UTF8.GetString(bytes) 22 | writer, toString 23 | 24 | [] 25 | let ``write text``() = 26 | let writer, toString = binaryWriter() 27 | writer.Write(Encoding.UTF8.GetBytes "foo") 28 | let found = toString() 29 | Assert.AreEqual("foo", found) 30 | 31 | [] 32 | let ``write response``() = 33 | let writer, toString = binaryWriter() 34 | LanguageServer.respond(writer, 1, "2") 35 | let expected = "Content-Length: 35\r\n\r\n\ 36 | {\"id\":1,\"jsonrpc\":\"2.0\",\"result\":2}" 37 | let found = toString() 38 | Assert.AreEqual(expected, found) 39 | 40 | [] 41 | let ``write multibyte characters``() = 42 | let writer, toString = binaryWriter() 43 | LanguageServer.respond(writer, 1, "🔥") 44 | let expected = "Content-Length: 38\r\n\r\n\ 45 | {\"id\":1,\"jsonrpc\":\"2.0\",\"result\":🔥}" 46 | let found = toString() 47 | Assert.AreEqual(expected, found) 48 | 49 | let TODO() = raise (Exception "TODO") 50 | 51 | type MockServer() = 52 | interface ILanguageServer with 53 | member this.Initialize(p: InitializeParams): Async = 54 | async { 55 | return { capabilities = defaultServerCapabilities } 56 | } 57 | member this.Initialized(): Async = TODO() 58 | member this.Shutdown(): Async = TODO() 59 | member this.DidChangeConfiguration(p: DidChangeConfigurationParams): Async = TODO() 60 | member this.DidOpenTextDocument(p: DidOpenTextDocumentParams): Async = TODO() 61 | member this.DidChangeTextDocument(p: DidChangeTextDocumentParams): Async = TODO() 62 | member this.WillSaveTextDocument(p: WillSaveTextDocumentParams): Async = TODO() 63 | member this.WillSaveWaitUntilTextDocument(p: WillSaveTextDocumentParams): Async = TODO() 64 | member this.DidSaveTextDocument(p: DidSaveTextDocumentParams): Async = TODO() 65 | member this.DidCloseTextDocument(p: DidCloseTextDocumentParams): Async = TODO() 66 | member this.DidChangeWatchedFiles(p: DidChangeWatchedFilesParams): Async = TODO() 67 | member this.Completion(p: TextDocumentPositionParams): Async = TODO() 68 | member this.Hover(p: TextDocumentPositionParams): Async = TODO() 69 | member this.ResolveCompletionItem(p: CompletionItem): Async = TODO() 70 | member this.SignatureHelp(p: TextDocumentPositionParams): Async = TODO() 71 | member this.GotoDefinition(p: TextDocumentPositionParams): Async = TODO() 72 | member this.FindReferences(p: ReferenceParams): Async = TODO() 73 | member this.DocumentHighlight(p: TextDocumentPositionParams): Async = TODO() 74 | member this.DocumentSymbols(p: DocumentSymbolParams): Async = TODO() 75 | member this.WorkspaceSymbols(p: WorkspaceSymbolParams): Async = TODO() 76 | member this.CodeActions(p: CodeActionParams): Async = TODO() 77 | member this.CodeLens(p: CodeLensParams): Async = TODO() 78 | member this.ResolveCodeLens(p: CodeLens): Async = TODO() 79 | member this.DocumentLink(p: DocumentLinkParams): Async = TODO() 80 | member this.ResolveDocumentLink(p: DocumentLink): Async = TODO() 81 | member this.DocumentFormatting(p: DocumentFormattingParams): Async = TODO() 82 | member this.DocumentRangeFormatting(p: DocumentRangeFormattingParams): Async = TODO() 83 | member this.DocumentOnTypeFormatting(p: DocumentOnTypeFormattingParams): Async = TODO() 84 | member this.Rename(p: RenameParams): Async = TODO() 85 | member this.ExecuteCommand(p: ExecuteCommandParams): Async = TODO() 86 | member this.DidChangeWorkspaceFolders(p: DidChangeWorkspaceFoldersParams): Async = TODO() 87 | member this.SemanticTokensFull (p: SemanticTokensParams) : Async=TODO() 88 | member this.SemanticTokensFullDelta (p: SemanticTokensDeltaParams): Async=TODO() 89 | 90 | member this.SemanticTokensRange (p: SemanticTokensRangeParams): Async=TODO() 91 | let messageStream(messages: string list): BinaryReader = 92 | let stdin = new MemoryStream() 93 | for m in messages do 94 | let trim = m.Trim() 95 | let length = Encoding.UTF8.GetByteCount(trim) 96 | let wrapper = sprintf "Content-Length: %d\r\n\r\n%s" length trim 97 | let bytes = Encoding.UTF8.GetBytes(wrapper) 98 | stdin.Write(bytes, 0, bytes.Length) 99 | stdin.Seek(int64 0, SeekOrigin.Begin) |> ignore 100 | new BinaryReader(stdin) 101 | 102 | let initializeMessage = """ 103 | { 104 | "jsonrpc": "2.0", 105 | "id": 1, 106 | "method": "initialize", 107 | "params": {} 108 | } 109 | """ 110 | 111 | [] 112 | let ``read messages from a stream``() = 113 | let stdin = messageStream [initializeMessage] 114 | let messages = LanguageServer.readMessages(stdin) 115 | let found = Seq.toList(messages) 116 | Assert.AreEqual([Parser.RequestMessage(1, "initialize", JsonValue.Parse "{}")], found) 117 | 118 | let exitMessage = """ 119 | { 120 | "jsonrpc": "2.0", 121 | "method": "exit" 122 | } 123 | """ 124 | 125 | [] 126 | let ``exit message terminates stream``() = 127 | let stdin = messageStream [initializeMessage; exitMessage; initializeMessage] 128 | let messages = LanguageServer.readMessages(stdin) 129 | let found = Seq.toList messages 130 | Assert.AreEqual([Parser.RequestMessage(1, "initialize", JsonValue.Parse "{}")], found) 131 | 132 | [] 133 | let ``end of bytes terminates stream``() = 134 | let stdin = messageStream [initializeMessage] 135 | let messages = LanguageServer.readMessages(stdin) 136 | let found = Seq.toList messages 137 | Assert.AreEqual([Parser.RequestMessage(1, "initialize", JsonValue.Parse "{}")], found) 138 | 139 | let mock(server: ILanguageServer) (messages: string list): string = 140 | let stdout = new MemoryStream() 141 | let writeOut = new BinaryWriter(stdout) 142 | let readIn = messageStream(messages) 143 | let serverFactory = fun _ -> server 144 | LanguageServer.connect(serverFactory, readIn, writeOut) 145 | Encoding.UTF8.GetString(stdout.ToArray()) 146 | 147 | [] 148 | let ``send Initialize``() = 149 | let message = """ 150 | { 151 | "jsonrpc": "2.0", 152 | "id": 1, 153 | "method": "initialize", 154 | "params": {"processId": null,"rootUri":null,"capabilities":{}} 155 | } 156 | """ 157 | let server = MockServer() 158 | let result = mock server [message] 159 | if not (result.Contains("capabilities")) then Assert.Fail(sprintf "%A does not contain capabilities" result) 160 | -------------------------------------------------------------------------------- /tests/LSP.Tests/TokenizerTests.fs: -------------------------------------------------------------------------------- 1 | module LSP.TokenizerTests 2 | 3 | open System.IO 4 | open System.Text 5 | open NUnit.Framework 6 | 7 | [] 8 | let setup() = 9 | LSP.Log.diagnosticsLog := stdout 10 | 11 | [] 12 | let ``parse content length header`` () = 13 | let sample = "Content-Length: 10" 14 | let found = Tokenizer.parseHeader sample 15 | Assert.AreEqual((Tokenizer.ContentLength 10), found) 16 | 17 | [] 18 | let ``parse content type header`` () = 19 | let sample = "Content-Type: application/vscode-jsonrpc; charset=utf-8" 20 | let found = Tokenizer.parseHeader sample 21 | Assert.AreEqual(Tokenizer.OtherHeader, found) 22 | 23 | [] 24 | let ``parse empty line indicating start of message`` () = 25 | let found = Tokenizer.parseHeader "" 26 | Assert.AreEqual(Tokenizer.EmptyHeader, found) 27 | 28 | let binaryReader(sample: string): BinaryReader = 29 | let bytes = Encoding.UTF8.GetBytes(sample) 30 | let stream = new MemoryStream(bytes) 31 | new BinaryReader(stream, Encoding.UTF8) 32 | 33 | [] 34 | let ``take header token`` () = 35 | let sample = "Line 1\r\n\ 36 | Line 2" 37 | let found = Tokenizer.readLine (binaryReader sample) 38 | Assert.AreEqual((Some "Line 1"), found) 39 | 40 | [] 41 | let ``allow newline without carriage-return`` () = 42 | let sample = "Line 1\n\ 43 | Line 2" 44 | let found = Tokenizer.readLine (binaryReader sample) 45 | Assert.AreEqual((Some "Line 1"), found) 46 | 47 | [] 48 | let ``take message token`` () = 49 | let sample = "{}\r\n\ 50 | next line..." 51 | let found = Tokenizer.readLength(2, binaryReader(sample)) 52 | Assert.AreEqual("{}", found) 53 | 54 | [] 55 | let ``tokenize stream`` () = 56 | let sample = "Content-Length: 2\r\n\ 57 | \r\n\ 58 | {}\ 59 | Content-Length: 1\r\n\ 60 | \r\n\ 61 | 1" 62 | let found = Tokenizer.tokenize (binaryReader sample) |> Seq.toList 63 | Assert.AreEqual(["{}"; "1"], found) 64 | 65 | [] 66 | let ``tokenize stream with multibyte characters`` () = 67 | let sample = "Content-Length: 5\r\n\ 68 | \r\n\ 69 | _🔥\ 70 | Content-Length: 5\r\n\ 71 | \r\n\ 72 | _🐼" 73 | let found = Tokenizer.tokenize (binaryReader sample) |> Seq.toList 74 | Assert.AreEqual(["_🔥"; "_🐼"], found) 75 | -------------------------------------------------------------------------------- /tests/LSP.Tests/paket.references: -------------------------------------------------------------------------------- 1 | Microsoft.NET.Test.Sdk 2 | NUnit 3 | NUnit3TestAdapter 4 | FSharp.Core -------------------------------------------------------------------------------- /tests/ProjectCracker.Tests/Common.fs: -------------------------------------------------------------------------------- 1 | module ProjectCrackerTestsCommon 2 | 3 | open System 4 | open System.IO 5 | 6 | let private findProjectRoot (start: DirectoryInfo): DirectoryInfo = 7 | seq { 8 | let mutable dir = start 9 | while dir <> dir.Root do 10 | for _ in dir.GetFiles "fsharp-language-server.sln" do 11 | yield dir 12 | dir <- dir.Parent 13 | } |> Seq.head 14 | let private testDirectory = DirectoryInfo(Directory.GetCurrentDirectory()) 15 | let projectRoot = findProjectRoot testDirectory -------------------------------------------------------------------------------- /tests/ProjectCracker.Tests/ProjectCracker.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/ProjectCracker.Tests/ProjectCrackerTests.fs: -------------------------------------------------------------------------------- 1 | module ProjectCrackerTests 2 | 3 | open ProjectCracker 4 | open ProjectCrackerTestsCommon 5 | open LSP.Log 6 | open System 7 | open System.IO 8 | open System.Text.RegularExpressions 9 | open System.Diagnostics 10 | open Microsoft.Build 11 | open Microsoft.Build.Evaluation 12 | open Microsoft.Build.Utilities 13 | open Microsoft.Build.Framework 14 | open Microsoft.Build.Logging 15 | open Buildalyzer 16 | open NUnit.Framework 17 | 18 | [] 19 | let setup() = 20 | LSP.Log.diagnosticsLog := stdout 21 | 22 | let containsFileName(name: string, files: FileInfo list) = 23 | let test(f: FileInfo) = name = f.Name 24 | List.exists test files 25 | 26 | 27 | [] 28 | let ``crack a project file``() = 29 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "MainProject"; "MainProject.fsproj"|] |> FileInfo 30 | let cracked = ProjectCracker.crack(fsproj) 31 | // Direct project reference 32 | let projectNames = [for f in cracked.projectReferences do yield f.Name] 33 | if not(List.contains "DependsOn.fsproj" projectNames) then 34 | Assert.Fail(sprintf "No DependsOn.fsproj in %A" cracked.projectReferences) 35 | // Transitive dependency 36 | if not(List.contains "IndirectDep.fsproj" projectNames) then 37 | Assert.Fail(sprintf "No IndirectDep.fsproj in %A" cracked.projectReferences) 38 | // Output dll 39 | Assert.AreEqual("MainProject.dll", cracked.target.Name) 40 | 41 | [] 42 | let ``crack a project file with case insensitive package references`` () = 43 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "HasPackageReference"; "HasPackageReference.fsproj" |] |> FileInfo 44 | let cracked = ProjectCracker.crack(fsproj) 45 | CollectionAssert.Contains([for f in cracked.packageReferences do yield f.Name], "Logary.dll") 46 | 47 | [] 48 | let ``find compile sources``() = 49 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "IndirectDep"; "IndirectDep.fsproj"|] |> FileInfo 50 | let cracked = ProjectCracker.crack(fsproj) 51 | CollectionAssert.AreEquivalent(["IndirectLibrary.fs"], [for f in cracked.sources do yield f.Name]) 52 | 53 | [] 54 | let ``find reference includes``() = 55 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "HasLocalDll"; "HasLocalDll.fsproj"|] |> FileInfo 56 | let cracked = ProjectCracker.crack(fsproj) 57 | CollectionAssert.AreEquivalent(["IndirectDep.dll"], [for f in cracked.directReferences do yield f.Name]) 58 | 59 | [] 60 | let ``find CSharp reference``() = 61 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "ReferenceCSharp"; "ReferenceCSharp.fsproj"|] |> FileInfo 62 | let cracked = ProjectCracker.crack(fsproj) 63 | CollectionAssert.AreEquivalent(["CSharpProject.dll"], [for f in cracked.otherProjectReferences do yield f.Name]) 64 | 65 | [] 66 | let ``find CSharp reference with modified AssemblyName``() = 67 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "ReferenceCSharp.AssemblyName"; "ReferenceCSharp.AssemblyName.fsproj"|] |> FileInfo 68 | let cracked = ProjectCracker.crack(fsproj) 69 | CollectionAssert.AreEquivalent(["CSharpProject.AssemblyName.Modified.dll"], [for f in cracked.otherProjectReferences do yield f.Name]) 70 | 71 | [] 72 | let ``resolve template params``() = 73 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "TemplateParams"; "TemplateParams.fsproj"|] |> FileInfo 74 | let cracked = ProjectCracker.crack(fsproj) 75 | let expected = [ 76 | Path.Combine([|projectRoot.FullName; "src"; "fsharp"; "QueueList.fs"|]); 77 | Path.Combine([|projectRoot.FullName; "sample"; "TemplateParams"; "netstandard2.0"; "pars.fs"|]) 78 | ] 79 | let actual = [for f in cracked.sources do yield f.FullName] 80 | CollectionAssert.AreEquivalent(expected, actual) 81 | 82 | // Check that project.assets.json-based ProjectCracker finds same .dlls as MSBuild 83 | 84 | let clean(fsproj: FileInfo) = 85 | let args = sprintf "clean %s" fsproj.FullName 86 | let info = 87 | ProcessStartInfo( 88 | UseShellExecute = false, 89 | FileName = "dotnet", 90 | Arguments = args 91 | ) 92 | let p = Process.Start(info) 93 | p.WaitForExit() 94 | 95 | let msbuild(fsproj: FileInfo): string list = 96 | // Clean project so `dotnet build` actually generates output 97 | clean(fsproj) 98 | // Invoke `dotnet build` 99 | let args = sprintf "build %s -v d" fsproj.FullName 100 | let info = 101 | ProcessStartInfo( 102 | RedirectStandardOutput = true, 103 | RedirectStandardError = true, 104 | UseShellExecute = false, 105 | FileName = "dotnet", 106 | Arguments = args 107 | ) 108 | let p = Process.Start(info) 109 | // Collect all lines of stdout 110 | let lines = System.Collections.Generic.List() 111 | p.OutputDataReceived.Add(fun args -> if args.Data <> null then lines.Add(args.Data)) 112 | p.ErrorDataReceived.Add(fun args -> if args.Data <> null then dprintfn "Build: %A" args.Data) 113 | if not(p.Start()) then 114 | failwithf "Failed dotnet %s" args 115 | p.BeginOutputReadLine() 116 | p.BeginErrorReadLine() 117 | p.WaitForExit() 118 | // Search for lines that start with '-r:' 119 | let references = System.Collections.Generic.List() 120 | for line in lines do 121 | if line.EndsWith("Task \"Fsc\"") then 122 | references.Clear() 123 | if line.Trim().StartsWith("-r:") then 124 | references.Add(line.Trim().Substring("-r:".Length)) 125 | // Filter out project-to-project references, these are handled separately by ProjectCracker 126 | [ for r in references do 127 | if not(r.Contains("bin/Debug/netcoreapp")) then 128 | yield r ] 129 | 130 | let cracker(fsproj: FileInfo): string list = 131 | let cracked = ProjectCracker.crack(fsproj) 132 | [ for f in cracked.packageReferences do 133 | yield f.FullName ] 134 | 135 | [] 136 | let ``find package references in EmptyProject``() = 137 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "EmptyProject"; "EmptyProject.fsproj"|] |> FileInfo 138 | CollectionAssert.AreEquivalent(msbuild(fsproj), cracker(fsproj)) 139 | 140 | [] 141 | let ``find package references in FSharpKoans``() = 142 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "FSharpKoans.Core"; "FSharpKoans.Core.fsproj"|] |> FileInfo 143 | CollectionAssert.AreEquivalent(msbuild(fsproj), cracker(fsproj)) 144 | 145 | [] 146 | let ``issue 28``() = 147 | // NETStandard.Library is autoReferenced=true, but it is also an indirect dependency of dependencies that are not autoReferenced 148 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "Issue28"; "Issue28.fsproj"|] |> FileInfo 149 | CollectionAssert.AreEquivalent(msbuild(fsproj), cracker(fsproj)) 150 | 151 | [] 152 | let ``build unbuilt project``() = 153 | let bin = Path.Combine [|projectRoot.FullName; "sample"; "NotBuilt"; "bin"|] 154 | let obj = Path.Combine [|projectRoot.FullName; "sample"; "NotBuilt"; "obj"|] 155 | if Directory.Exists(bin) then Directory.Delete(bin, true) 156 | if Directory.Exists(obj) then Directory.Delete(obj, true) 157 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "NotBuilt"; "NotBuilt.fsproj"|] |> FileInfo 158 | let cracked = ProjectCracker.crack(fsproj) 159 | if cracked.error.IsSome then Assert.Fail(cracked.error.Value) 160 | CollectionAssert.AreEquivalent(["NotBuilt.fs"], [for f in cracked.sources do yield f.Name]) 161 | CollectionAssert.IsNotEmpty(cracked.packageReferences) 162 | 163 | [] 164 | let ``find implicit references with netcoreapp3``() = 165 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "NetCoreApp3"; "NetCoreApp3.fsproj"|] |> FileInfo 166 | let cracked = ProjectCracker.crack(fsproj) 167 | CollectionAssert.Contains([for f in cracked.packageReferences do yield f.Name], "System.Core.dll") 168 | 169 | [] 170 | let ``find implicit references with net5``() = 171 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "Net5Console"; "Net5Console.fsproj"|] |> FileInfo 172 | let cracked = ProjectCracker.crack(fsproj) 173 | CollectionAssert.Contains([for f in cracked.packageReferences do yield f.Name], "System.Core.dll") 174 | [] 175 | let ``find implicit references with net6``() = 176 | let fsproj = Path.Combine [|projectRoot.FullName; "sample"; "Net6Console"; "Net6Console.fsproj"|] |> FileInfo 177 | let cracked = ProjectCracker.crack(fsproj) 178 | CollectionAssert.Contains([for f in cracked.packageReferences do yield f.Name], "System.Core.dll") -------------------------------------------------------------------------------- /tests/ProjectCracker.Tests/paket.references: -------------------------------------------------------------------------------- 1 | Microsoft.NET.Test.Sdk 2 | NUnit 3 | NUnit3TestAdapter 4 | FSharp.Core -------------------------------------------------------------------------------- /tests/ProjectInfo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Microsoft.Build.Evaluation; 5 | using Microsoft.Build.Logging; 6 | using Microsoft.Build.Framework; 7 | using System.Reflection; 8 | 9 | namespace FSharpLanguageServer { 10 | class ProjectInfo { 11 | static void Main(string[] args) { 12 | var basePath = "/usr/local/share/dotnet/sdk/2.1.300"; 13 | Environment.SetEnvironmentVariable("MSBuildSDKsPath", Path.Combine(basePath, "Sdks")); 14 | Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", Path.Combine(basePath, "MSBuild.dll")); 15 | var globalProperties = new Dictionary(); 16 | globalProperties.Add("DesignTimeBuild", "true"); 17 | globalProperties.Add("BuildingInsideVisualStudio", "true"); 18 | globalProperties.Add("BuildProjectReferences", "false"); 19 | globalProperties.Add("_ResolveReferenceDependencies", "true"); 20 | globalProperties.Add("SolutionDir", "/Users/georgefraser/Documents/fsharp-language-server/sample"); 21 | // Setting this property will cause any XAML markup compiler tasks to run in the 22 | // current AppDomain, rather than creating a new one. This is important because 23 | // our AppDomain.AssemblyResolve handler for MSBuild will not be connected to 24 | // the XAML markup compiler's AppDomain, causing the task not to be able to find 25 | // MSBuild. 26 | globalProperties.Add("AlwaysCompileMarkupFilesInSeparateDomain", "false"); 27 | // This properties allow the design-time build to handle the Compile target without actually invoking the compiler. 28 | // See https://github.com/dotnet/roslyn/pull/4604 for details. 29 | globalProperties.Add("ProvideCommandLineArgs", "true"); 30 | globalProperties.Add("SkipCompilerExecution", "true" ); 31 | var projectCollection = new ProjectCollection(globalProperties); 32 | var project = projectCollection.LoadProject("/Users/georgefraser/Documents/fsharp-language-server/sample/ReferenceCSharp/ReferenceCSharp.fsproj"); 33 | var projectInstance = project.CreateProjectInstance(); 34 | var buildResult = projectInstance.Build(new string[] { "Compile", "CoreCompile" }, new ILogger[]{ new ConsoleLogger() }); 35 | String type = ""; 36 | foreach (var item in projectInstance.Items) { 37 | if (type != item.ItemType) { 38 | type = item.ItemType; 39 | Console.WriteLine(type); 40 | } 41 | Console.WriteLine(" " + item.EvaluatedInclude); 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /tests/ProjectInfo/ProjectInfo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | AnyCPU 6 | Exe 7 | 8 | 9 | 10 | 15.6.82 11 | 4.3.0 12 | 2.8.0 13 | 2.3.1 14 | 15 | 0.2.0 16 | 0.6.0 17 | 0.6.0 18 | 1.1.0 19 | 1.1.0 20 | 1.1.0 21 | 1.1.0 22 | $(MSBuildPackageVersion) 23 | $(MSBuildPackageVersion) 24 | $(MSBuildPackageVersion) 25 | $(MSBuildPackageVersion) 26 | $(RoslynPackageVersion) 27 | $(RoslynPackageVersion) 28 | $(RoslynPackageVersion) 29 | $(RoslynPackageVersion) 30 | $(RoslynPackageVersion) 31 | $(RoslynPackageVersion) 32 | 1.1.0 33 | 1.1.0 34 | 1.1.0 35 | 1.1.0 36 | 1.1.0 37 | 1.1.0 38 | 1.1.0 39 | 1.1.0 40 | 2.0.0 41 | 1.1.0 42 | 1.1.0 43 | 1.1.0 44 | 1.1.0 45 | 1.1.0 46 | 1.1.0 47 | 1.1.0 48 | 15.0.0 49 | 15.3.0 50 | 1.14.114 51 | 15.0.12 52 | 9.0.1 53 | $(NuGetPackageVersion) 54 | $(NuGetPackageVersion) 55 | $(NuGetPackageVersion) 56 | $(NuGetPackageVersion) 57 | 1.4.0 58 | 1.0.31 59 | 1.4.2 60 | 4.6.0 61 | 4.3.0 62 | $(XunitPackageVersion) 63 | $(XunitPackageVersion) 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /tests/Scratch/Program.fs: -------------------------------------------------------------------------------- 1 | module ProjectCrackerMain 2 | 3 | open System 4 | open System.IO 5 | open System.Collections.Generic 6 | open Microsoft.Build 7 | open Microsoft.Build.Evaluation 8 | open Microsoft.Build.Framework 9 | open Microsoft.Build.Logging 10 | 11 | [] 12 | let main(argv: array): int = 13 | let basePath = "/usr/local/share/dotnet/sdk/2.1.300" 14 | Environment.SetEnvironmentVariable("MSBuildSDKsPath", Path.Combine(basePath, "Sdks")) 15 | Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", Path.Combine(basePath, "MSBuild.dll")) 16 | let globalProperties = System.Collections.Generic.Dictionary() 17 | globalProperties.Add("DesignTimeBuild", "true") 18 | globalProperties.Add("BuildingInsideVisualStudio", "true") 19 | globalProperties.Add("BuildProjectReferences", "false") 20 | globalProperties.Add("_ResolveReferenceDependencies", "true") 21 | globalProperties.Add("SolutionDir", "/Users/georgefraser/Documents/fsharp-language-server/sample") 22 | // Setting this property will cause any XAML markup compiler tasks to run in the 23 | // current AppDomain, rather than creating a new one. This is important because 24 | // our AppDomain.AssemblyResolve handler for MSBuild will not be connected to 25 | // the XAML markup compiler's AppDomain, causing the task not to be able to find 26 | // MSBuild. 27 | globalProperties.Add("AlwaysCompileMarkupFilesInSeparateDomain", "false") 28 | // This properties allow the design-time build to handle the Compile target without actually invoking the compiler. 29 | // See https://github.com/dotnet/roslyn/pull/4604 for details. 30 | globalProperties.Add("ProvideCommandLineArgs", "true") 31 | globalProperties.Add("SkipCompilerExecution", "true" ) 32 | let projectCollection = new ProjectCollection(globalProperties) 33 | let project = projectCollection.LoadProject("/Users/georgefraser/Documents/fsharp-language-server/sample/ReferenceCSharp/ReferenceCSharp.fsproj") 34 | let instance = project.CreateProjectInstance() 35 | instance.Build([|"Compile"; "CoreCompile"|], [|ConsoleLogger() :> ILogger|]) |> ignore 36 | // Dump all items 37 | let mutable t = "" 38 | for i in instance.Items do 39 | if i.ItemType <> t then 40 | printfn "%s" i.ItemType 41 | t <- i.ItemType 42 | printfn " %A" i.EvaluatedInclude 43 | 0 44 | -------------------------------------------------------------------------------- /tests/Scratch/Scratch.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | AnyCPU 6 | Exe 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/Scratch/paket.references: -------------------------------------------------------------------------------- 1 | Microsoft.Build 2 | Microsoft.Build.Framework 3 | Microsoft.Build.Runtime 4 | Microsoft.Build.Tasks.Core 5 | Microsoft.Build.Utilities.Core 6 | FSharp.Core -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noUnusedLocals": true, 4 | "noUnusedParameters": true, 5 | "noImplicitAny": true, 6 | "noImplicitReturns": true, 7 | "target": "es2019", 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "rootDir": ".", 11 | "outDir": "out", 12 | "lib": [ "es2019" ], 13 | "sourceMap": true 14 | }, 15 | "exclude": [ 16 | "node_modules", 17 | "server", 18 | "Scratch.ts" 19 | ] 20 | } -------------------------------------------------------------------------------- /videos/Autocomplete.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/Autocomplete.mov.gif -------------------------------------------------------------------------------- /videos/DebugTest.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/DebugTest.mov.gif -------------------------------------------------------------------------------- /videos/DocumentSymbols.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/DocumentSymbols.mov.gif -------------------------------------------------------------------------------- /videos/EmacsLspMode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/EmacsLspMode.gif -------------------------------------------------------------------------------- /videos/FindReferences.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/FindReferences.mov.gif -------------------------------------------------------------------------------- /videos/GoToDefinition.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/GoToDefinition.mov.gif -------------------------------------------------------------------------------- /videos/Hover.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/Hover.mov.gif -------------------------------------------------------------------------------- /videos/LSP-vs-Ionide-Warm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/LSP-vs-Ionide-Warm.gif -------------------------------------------------------------------------------- /videos/RenameSymbol.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/RenameSymbol.mov.gif -------------------------------------------------------------------------------- /videos/ShowErrors.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/ShowErrors.mov.gif -------------------------------------------------------------------------------- /videos/SignatureHelp.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/SignatureHelp.mov.gif -------------------------------------------------------------------------------- /videos/VimDeoplete.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/VimDeoplete.mov.gif -------------------------------------------------------------------------------- /videos/WorkspaceSymbols.mov.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faldor20/fsharp-language-server/c4be0b5d8ba3a30c59fe274b196aeb94a6f1d8f8/videos/WorkspaceSymbols.mov.gif --------------------------------------------------------------------------------