├── .editorconfig
├── .github
└── workflows
│ ├── build.yaml
│ └── test.yaml
├── .gitignore
├── CHANGELOG.md
├── Directory.Build.props
├── Directory.Build.targets
├── Directory.Packages.props
├── LICENSE
├── README.md
├── csharp-language-server.sln
├── global.json
├── src
└── CSharpLanguageServer
│ ├── CHANGELOG.md
│ ├── CSharpLanguageServer.fsproj
│ ├── Conversions.fs
│ ├── DocumentationUtil.fs
│ ├── FormatUtil.fs
│ ├── Handlers
│ ├── CSharpMetadata.fs
│ ├── CallHierarchy.fs
│ ├── CodeAction.fs
│ ├── CodeLens.fs
│ ├── Color.fs
│ ├── Completion.fs
│ ├── Declaration.fs
│ ├── Definition.fs
│ ├── Diagnostic.fs
│ ├── DocumentFormatting.fs
│ ├── DocumentHighlight.fs
│ ├── DocumentLink.fs
│ ├── DocumentOnTypeFormatting.fs
│ ├── DocumentRangeFormatting.fs
│ ├── DocumentSymbol.fs
│ ├── ExecuteCommand.fs
│ ├── FoldingRange.fs
│ ├── Hover.fs
│ ├── Implementation.fs
│ ├── Initialization.fs
│ ├── InlayHint.fs
│ ├── InlineValue.fs
│ ├── LinkedEditingRange.fs
│ ├── Moniker.fs
│ ├── References.fs
│ ├── Rename.fs
│ ├── SelectionRange.fs
│ ├── SemanticTokens.fs
│ ├── SignatureHelp.fs
│ ├── TextDocumentSync.fs
│ ├── TypeDefinition.fs
│ ├── TypeHierarchy.fs
│ ├── Workspace.fs
│ └── WorkspaceSymbol.fs
│ ├── Logging.fs
│ ├── LruCache.fs
│ ├── Lsp
│ ├── Client.fs
│ └── Server.fs
│ ├── Options.fs
│ ├── Program.fs
│ ├── ProgressReporter.fs
│ ├── README.md
│ ├── RoslynHelpers.fs
│ ├── State
│ ├── ServerRequestContext.fs
│ └── ServerState.fs
│ ├── Types.fs
│ └── Util.fs
└── tests
└── CSharpLanguageServer.Tests
├── CSharpLanguageServer.Tests.fsproj
├── CodeActionTests.fs
├── DefinitionTests.fs
├── DiagnosticTests.fs
├── DocumentFormattingTests.fs
├── DocumentationTests.fs
├── HoverTests.fs
├── InitializationTests.fs
├── InternalTests.fs
├── ReferenceTests.fs
├── TestData
├── testCodeActionOnMethodNameWorks
│ └── Project
│ │ ├── Class.cs
│ │ └── Project.csproj
├── testDefinitionWorks
│ └── Project
│ │ ├── Class.cs
│ │ └── Project.csproj
├── testDefinitionWorksInAspNetProject
│ └── Project
│ │ ├── Controllers
│ │ └── TestController.cs
│ │ ├── Models
│ │ └── Test
│ │ │ └── IndexViewModel.cs
│ │ ├── Program.cs
│ │ ├── Project.csproj
│ │ ├── Startup.cs
│ │ └── Views
│ │ ├── Test
│ │ └── Index.cshtml
│ │ ├── _ViewImports.cshtml
│ │ └── _ViewStart.cshtml
├── testEditorConfigFormatting
│ └── Project
│ │ ├── .editorconfig
│ │ ├── Class.cs
│ │ ├── ExpectedFormatting.cs.txt
│ │ └── Project.csproj
├── testHoverWorks
│ └── Project
│ │ ├── Class.cs
│ │ └── Project.csproj
├── testMultiTargetProjectLoads
│ └── Project
│ │ ├── Class.cs
│ │ └── Project.csproj
├── testPullDiagnosticsWork
│ └── Project
│ │ ├── Class.cs
│ │ └── Project.csproj
├── testPushDiagnosticsWork
│ └── Project
│ │ ├── Class.cs
│ │ └── Project.csproj
├── testReferenceWorksDotnet8
│ └── Project
│ │ ├── Class.cs
│ │ └── Project.csproj
├── testReferenceWorksDotnet9
│ └── Project
│ │ ├── Class.cs
│ │ └── Project.csproj
├── testReferenceWorksToAspNetRazorPageReferencedValue
│ └── Project
│ │ ├── Controllers
│ │ └── TestController.cs
│ │ ├── Models
│ │ └── Test
│ │ │ └── IndexViewModel.cs
│ │ ├── Program.cs
│ │ ├── Project.csproj
│ │ ├── Startup.cs
│ │ └── Views
│ │ ├── Test
│ │ └── Index.cshtml
│ │ ├── _ViewImports.cshtml
│ │ └── _ViewStart.cshtml
├── testServerRegistersCapabilitiesWithTheClient
│ └── Project
│ │ ├── Class.cs
│ │ └── Project.csproj
└── testSlnx
│ ├── Project
│ ├── Class.cs
│ └── Project.csproj
│ └── Test.slnx
└── Tooling.fs
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | max_line_length = 120
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.fs]
12 | fsharp_newline_before_multiline_computation_expression = false
13 |
--------------------------------------------------------------------------------
/.github/workflows/build.yaml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs: {}
6 |
7 | jobs:
8 | build:
9 | strategy:
10 | matrix:
11 | os: [ubuntu-24.04]
12 | dotnet: [9.0.x]
13 | runs-on: ${{ matrix.os }}
14 |
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 |
19 | - name: Setup .NET
20 | uses: actions/setup-dotnet@v4
21 | with:
22 | dotnet-version: ${{ matrix.dotnet }}
23 |
24 | - name: Restore tools
25 | run: dotnet tool restore
26 |
27 | - name: Run Build
28 | run: dotnet build
29 |
30 | - name: Run publish
31 | run: dotnet pack -c Release -o release
32 | working-directory: src/CSharpLanguageServer
33 |
34 | - name: Upload NuGet packages as artifacts
35 | uses: actions/upload-artifact@v4
36 | with:
37 | name: packages
38 | path: src/CSharpLanguageServer/release/
39 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 |
8 | jobs:
9 | build:
10 | strategy:
11 | matrix:
12 | os: [windows-latest, ubuntu-24.04]
13 | dotnet: [9.0.x]
14 | fail-fast: false
15 |
16 | runs-on: ${{ matrix.os }}
17 |
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v4
21 |
22 | - name: Setup .NET
23 | uses: actions/setup-dotnet@v4
24 | with:
25 | dotnet-version: ${{ matrix.dotnet }}
26 |
27 | - name: Restore tools
28 | run: dotnet tool restore
29 |
30 | - name: Run Build
31 | run: dotnet build
32 |
33 | - name: Run Tests
34 | run: dotnet test --no-build
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | obj
3 | .ionide/
4 | .idea/
5 | .config/
6 | nupkg/
7 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | csharp-ls
5 | csharp-ls
6 | MIT
7 | $(MSBuildThisFileDirectory)CHANGELOG.md
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 | true
6 |
7 |
8 |
9 | 17.14.8
10 | 4.14.0
11 | 1.9.1
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020-2021 Saulius Menkevičius
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Description
2 | This is a hacky Roslyn-based LSP server for C#, as an alternative to
3 | [omnisharp-roslyn](https://github.com/OmniSharp/omnisharp-roslyn).
4 |
5 | `csharp-ls` requires .NET 8 SDK to be installed. However it has been reported
6 | to work with projects using older versions of dotnet SDK, including .NET Core 3,
7 | .NET Framework 4.8 and possibly older ones too as it uses the standard
8 | Roslyn/MSBuild libs that Visual Studio & omnisharp does.
9 |
10 | See [CHANGELOG.md](CHANGELOG.md) for the list of recent improvements/fixes.
11 |
12 | # Acknowledgements
13 | - csharp-ls is not affiliated with Microsoft Corp;
14 | - csharp-ls uses LSP interface from [Ionide.LanguageServerProtocol](https://github.com/ionide/LanguageServerProtocol);
15 | - csharp-ls uses [Roslyn](https://github.com/dotnet/roslyn) to parse and update code; Roslyn maps really nicely to LSP w/relatively little impedance mismatch;
16 | - csharp-ls uses [ILSpy/ICSharpCode.Decompiler](https://github.com/icsharpcode/ILSpy) to decompile types in assemblies to C# source.
17 |
18 | # Installation
19 | `dotnet tool install --global csharp-ls`
20 |
21 | See [csharp-ls nuget page](https://www.nuget.org/packages/csharp-ls/)
22 |
23 | # Settings
24 |
25 | - `csharp.solution` - solution to load, optional
26 | - `csharp.applyFormattingOptions` - use formatting options as supplied by the client (may override `.editorconfig` values), defaults to `false`
27 |
28 | # Clients
29 |
30 | `csharp-ls` implements the standard LSP protocol to interact with your editor.
31 | However there are some features that need a non-standard implementation and this
32 | is where editor-specific plugins can be helpful.
33 |
34 | ## Emacs
35 | ### emacs/lsp-mode
36 | Supports automatic installation, go-to-metatada (can view code from nuget/compiled dlls)
37 | and some additional features.
38 |
39 | See [emacs/lsp-mode](https://github.com/emacs-lsp/lsp-mode).
40 |
41 | ## Visual Studio Code
42 | ### vytautassurvila/vscode-csharp-ls
43 | - Supports code decompilation from metadata
44 |
45 | See [csharp-ls](https://marketplace.visualstudio.com/items?itemName=vytautassurvila.csharp-ls) and [vscode-csharp-ls @ github](https://github.com/vytautassurvila/vscode-csharp-ls).
46 |
47 | ### statiolake/vscode-csharp-ls
48 | See [vscode-csharp-ls](https://marketplace.visualstudio.com/items?itemName=statiolake.vscode-csharp-ls).
49 |
50 | # Building
51 |
52 | ## On Linux/macOS
53 |
54 | ```
55 | $ dotnet build
56 | ```
57 |
58 | # FAQ
59 |
60 | ## decompile for your editor , with the example of neovim
61 |
62 | ### api
63 |
64 | The api is "csharp/metadata", in neovim ,you can request it like
65 |
66 | ```lua
67 | local result, err = client.request_sync("csharp/metadata", params, 10000)
68 | ```
69 |
70 | #### sender
71 | You need to send a uri, it is like
72 |
73 | **csharp:/metadata/projects/trainning2/assemblies/System.Console/symbols/System.Console.cs**
74 |
75 | In neovim, it will be result(s) from vim.lsp.handles["textDocument/definition"]
76 |
77 | and the key of uri is the key,
78 |
79 | The key to send is like
80 |
81 | ```lua
82 | local params = {
83 | timeout = 5000,
84 | textDocument = {
85 | uri = uri,
86 | }
87 | }
88 | ```
89 |
90 | The key of textDocument is needed. And timeout is just for neovim. It is the same if is expressed by json.
91 |
92 | ### receiver
93 |
94 | The object received is like
95 |
96 | ```lua
97 | {
98 | projectName = "csharp-test",
99 | assemblyName = "System.Runtime",
100 | symbolName = "System.String",
101 | source = "using System.Buffers;\n ...."
102 | }
103 | ```
104 |
105 | And In neovim, You receive the "result" above, you can get the decompile source from
106 |
107 | ```lua
108 |
109 | local result, err = client.request_sync("csharp/metadata", params, 10000)
110 | local source
111 | if not err then
112 | source = result.result.source
113 | end
114 | ```
115 |
116 | And there is a plugin of neovim for you to decompile it.
117 |
118 | [csharpls-extended-lsp.nvim](https://github.com/chen244/csharpls-extended-lsp.nvim)
119 |
--------------------------------------------------------------------------------
/csharp-language-server.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3ADA171B-D93F-4D34-97EA-5BB06662678B}"
7 | EndProject
8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CSharpLanguageServer", "src\CSharpLanguageServer\CSharpLanguageServer.fsproj", "{228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{35DF1A49-B49A-42CB-B43B-8E8120AD9B0E}"
11 | EndProject
12 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CSharpLanguageServer.Tests", "tests\CSharpLanguageServer.Tests\CSharpLanguageServer.Tests.fsproj", "{754F8FCE-6DC2-43E2-832D-00A8B830D192}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionProperties) = preSolution
16 | HideSolutionNode = FALSE
17 | EndGlobalSection
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Debug|Any CPU = Debug|Any CPU
20 | Debug|x64 = Debug|x64
21 | Debug|x86 = Debug|x86
22 | Release|Any CPU = Release|Any CPU
23 | Release|x64 = Release|x64
24 | Release|x86 = Release|x86
25 | EndGlobalSection
26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
27 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Debug|x64.ActiveCfg = Debug|Any CPU
30 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Debug|x64.Build.0 = Debug|Any CPU
31 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Debug|x86.ActiveCfg = Debug|Any CPU
32 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Debug|x86.Build.0 = Debug|Any CPU
33 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Release|x64.ActiveCfg = Release|Any CPU
36 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Release|x64.Build.0 = Release|Any CPU
37 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Release|x86.ActiveCfg = Release|Any CPU
38 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02}.Release|x86.Build.0 = Release|Any CPU
39 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Debug|x64.ActiveCfg = Debug|Any CPU
42 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Debug|x64.Build.0 = Debug|Any CPU
43 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Debug|x86.ActiveCfg = Debug|Any CPU
44 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Debug|x86.Build.0 = Debug|Any CPU
45 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Release|Any CPU.ActiveCfg = Release|Any CPU
46 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Release|Any CPU.Build.0 = Release|Any CPU
47 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Release|x64.ActiveCfg = Release|Any CPU
48 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Release|x64.Build.0 = Release|Any CPU
49 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Release|x86.ActiveCfg = Release|Any CPU
50 | {754F8FCE-6DC2-43E2-832D-00A8B830D192}.Release|x86.Build.0 = Release|Any CPU
51 | EndGlobalSection
52 | GlobalSection(NestedProjects) = preSolution
53 | {228A59AD-2E4C-4DF4-AC5E-1F126A4FFF02} = {3ADA171B-D93F-4D34-97EA-5BB06662678B}
54 | {754F8FCE-6DC2-43E2-832D-00A8B830D192} = {35DF1A49-B49A-42CB-B43B-8E8120AD9B0E}
55 | EndGlobalSection
56 | EndGlobal
57 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "9.0.1",
4 | "rollForward": "minor"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ../../CHANGELOG.md
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/CSharpLanguageServer.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Exe
6 | net9.0
7 | C# LSP language server
8 | true
9 | csharp-ls
10 | Saulius Menkevičius
11 | ./nupkg
12 | csharp-ls
13 | MIT
14 | csharp;lsp;roslyn
15 | https://github.com/razzmatazz/csharp-language-server
16 | true
17 | README.md
18 | CHANGELOG.md
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
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 | NU1604
89 | NU1701
90 |
91 |
92 | NU1604
93 | NU1701
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/DocumentationUtil.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer
2 |
3 | open System
4 | open System.Xml.Linq
5 |
6 | open Microsoft.CodeAnalysis
7 |
8 | open CSharpLanguageServer.Conversions
9 |
10 | module DocumentationUtil =
11 | type TripleSlashComment =
12 | { Summary: XElement list
13 | Params: (string * XElement) list
14 | Exceptions: (string * XElement) list
15 | Returns: XElement list
16 | Types: (string * XElement) list
17 | Remarks: XElement list
18 | OtherLines: XElement list }
19 | static member Default =
20 | { Summary = []
21 | Params = []
22 | Exceptions = []
23 | Returns = []
24 | Types = []
25 | Remarks = []
26 | OtherLines = [] }
27 |
28 | let parseCref (cref: string) =
29 | let parts = cref.Split(':')
30 | match parts.Length with
31 | | 1 -> cref
32 | | _ -> String.Join(":", parts |> Seq.skip 1)
33 |
34 | let normalizeWhitespace (s: string) =
35 | let mutable modified = s
36 | let mutable prevModified = ""
37 | while modified <> prevModified do
38 | prevModified <- modified
39 | modified <- modified.Replace(" ", " ").Replace("\r\n", " ").Replace("\n", " ")
40 |
41 | modified
42 |
43 | let formatTextElement (n: XElement) =
44 |
45 | let formatSeeElement (e: XElement) =
46 | let crefMaybe =
47 | e.Attribute(XName.Get("cref"))
48 | |> Option.ofObj
49 | |> Option.map (fun x -> x.Value)
50 | |> Option.map parseCref
51 | |> Option.map (fun s -> sprintf "``%s``" s)
52 | |> Option.toList
53 |
54 | let langWordMaybe =
55 | e.Attribute(XName.Get("langword"))
56 | |> Option.ofObj
57 | |> Option.map (fun x -> sprintf "``%s``" x.Value)
58 | |> Option.toList
59 |
60 | crefMaybe |> Seq.append langWordMaybe |> List.ofSeq
61 |
62 | let rec formatTextNode (subnode: XNode) =
63 | match subnode with
64 | | :? XElement as e ->
65 | match e.Name.LocalName with
66 | | "c" -> [ sprintf "``%s``" e.Value ]
67 | | "see" -> formatSeeElement e
68 | | "paramref" ->
69 | e.Attribute(XName.Get("name"))
70 | |> Option.ofObj
71 | |> Option.map (fun x -> sprintf "``%s``" x.Value)
72 | |> Option.toList
73 | | "para" ->
74 | e.Nodes()
75 | |> Seq.collect formatTextNode
76 | |> Seq.append [ "\n\n" ]
77 | |> List.ofSeq
78 | | _ -> [ e.Value ]
79 | | :? XText as t -> [ t.Value |> normalizeWhitespace ]
80 | | _ -> []
81 |
82 | n.Nodes() |> Seq.collect formatTextNode |> (fun ss -> String.Join("", ss))
83 |
84 | let extendCommentWithElement comment (n: XElement) =
85 | match n.Name.LocalName with
86 | | "summary" ->
87 | let newSummary = comment.Summary |> List.append [ n ]
88 | { comment with Summary = newSummary }
89 |
90 | | "remarks" ->
91 | let newRemarks = comment.Remarks |> List.append [ n ]
92 | { comment with Remarks = newRemarks }
93 |
94 | | "param" ->
95 | let name = n.Attribute(XName.Get("name")).Value
96 | { comment with Params = comment.Params |> List.append [ (name, n) ] }
97 |
98 | | "returns" ->
99 | { comment with Returns = comment.Returns |> List.append [ n ] }
100 |
101 | | "exception" ->
102 | let name = n.Attribute(XName.Get("cref")).Value |> parseCref
103 | { comment with
104 | Exceptions = comment.Exceptions |> List.append [ (name, n) ] }
105 |
106 | | "typeparam" ->
107 | let name = n.Attribute(XName.Get("name")).Value
108 | { comment with Types = comment.Types |> List.append [ (name, n) ] }
109 |
110 | | _ ->
111 | { comment with OtherLines = comment.OtherLines |> List.append [ n ] }
112 |
113 |
114 | let parseComment xmlDocumentation: TripleSlashComment =
115 | let doc = XDocument.Parse("" + xmlDocumentation + "")
116 |
117 | let unwrapDocRoot (root: XElement) =
118 | let elementNames (el: XElement) =
119 | el.Elements() |> Seq.map (fun e -> e.Name.LocalName) |> List.ofSeq
120 |
121 | match elementNames root with
122 | | [ "member" ] -> root.Element(XName.Get("member"))
123 | | _ -> root
124 |
125 | doc.Root
126 | |> unwrapDocRoot
127 | |> fun r -> r.Elements()
128 | |> Seq.fold extendCommentWithElement TripleSlashComment.Default
129 |
130 |
131 | let formatComment model : string list =
132 |
133 | let appendNamed name (kvs: (string * XElement) seq) markdownLines =
134 | match Seq.isEmpty kvs with
135 | | true -> markdownLines
136 | | false ->
137 | let formatItem (key, value) =
138 | sprintf "- ``%s``: %s" key (formatTextElement value)
139 |
140 | markdownLines
141 | |> List.append [ name + ":"; "" ]
142 | |> List.append (kvs |> Seq.map formatItem |> List.ofSeq)
143 |
144 | let appendFormatted name elms markdownLines =
145 | let formattedLines =
146 | elms
147 | |> List.map formatTextElement
148 | |> List.filter (not << String.IsNullOrWhiteSpace)
149 |
150 | match Seq.isEmpty formattedLines with
151 | | true -> markdownLines
152 | | false ->
153 | markdownLines
154 | |> List.append [ "" ]
155 | |> List.append (formattedLines |> List.map (fun s -> name + ": " + s))
156 |
157 | []
158 | |> List.append (model.Summary |> List.map formatTextElement)
159 | |> appendNamed "Parameters" model.Params
160 | |> appendFormatted "Returns" model.Returns
161 | |> appendNamed "Exceptions" model.Exceptions
162 | |> appendNamed "Types" model.Types
163 | |> appendFormatted "Remarks" model.Remarks
164 | |> List.append (model.OtherLines |> List.map string)
165 | |> List.rev
166 | |> List.map (fun s -> s.Trim())
167 |
168 |
169 | let formatDocXml xmlDocumentation =
170 | String.Join("\n", xmlDocumentation |> parseComment |> formatComment)
171 |
172 | let markdownDocForSymbol (sym: ISymbol) =
173 | let comment = parseComment (sym.GetDocumentationCommentXml())
174 | let formattedDocLines = formatComment comment
175 |
176 | formattedDocLines |> (fun ss -> String.Join("\n", ss))
177 |
178 | let markdownDocForSymbolWithSignature (sym: ISymbol) =
179 | let symbolName = SymbolName.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat sym
180 |
181 | let symbolInfoLines =
182 | match symbolName with
183 | | "" -> []
184 | | typeName -> [ sprintf "```csharp\n%s\n```" typeName ]
185 |
186 | let comment = parseComment (sym.GetDocumentationCommentXml())
187 | let formattedDocLines = formatComment comment
188 |
189 | formattedDocLines
190 | |> Seq.append (
191 | if symbolInfoLines.Length > 0 && formattedDocLines.Length > 0 then
192 | [ "" ]
193 | else
194 | []
195 | )
196 | |> Seq.append symbolInfoLines
197 | |> (fun ss -> String.Join("\n", ss))
198 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/FormatUtil.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer
2 |
3 | open Microsoft.CodeAnalysis
4 | open Microsoft.CodeAnalysis.CSharp
5 | open Microsoft.CodeAnalysis.CSharp.Formatting
6 | open Microsoft.CodeAnalysis.Options
7 | open Microsoft.CodeAnalysis.Text
8 | open Microsoft.CodeAnalysis.Formatting
9 | open Ionide.LanguageServerProtocol.Types
10 |
11 | open CSharpLanguageServer.Types
12 |
13 | module internal FormatUtil =
14 | let processChange (oldText: SourceText) (change: TextChange) : TextEdit =
15 | let mapToTextEdit (linePosition: LinePositionSpan, newText: string) : TextEdit =
16 | { NewText = newText
17 | Range =
18 | { Start =
19 | { Line = uint32 linePosition.Start.Line
20 | Character = uint32 linePosition.Start.Character }
21 | End =
22 | { Line = uint32 linePosition.End.Line
23 | Character = uint32 linePosition.End.Character } } }
24 |
25 | let defaultTextEdit (oldText: SourceText, change: TextChange) : TextEdit =
26 | let linePosition = oldText.Lines.GetLinePositionSpan change.Span
27 | mapToTextEdit (linePosition, change.NewText)
28 |
29 | let padLeft (span: TextSpan) : TextSpan =
30 | TextSpan.FromBounds(span.Start - 1, span.End)
31 |
32 | let padRight (span: TextSpan) : TextSpan =
33 | TextSpan.FromBounds(span.Start, span.End + 1)
34 |
35 | let rec checkSpanLineEndings (newText: string, oldText: SourceText, span: TextSpan, prefix: string) : TextEdit =
36 | if
37 | span.Start > 0
38 | && newText[0].Equals('\n')
39 | && oldText[span.Start - 1].Equals('\r')
40 | then
41 | checkSpanLineEndings (newText, oldText, padLeft (span), "\r") |> ignore
42 |
43 | if
44 | span.End < oldText.Length - 1
45 | && newText[newText.Length - 1].Equals('\r')
46 | && oldText[span.End].Equals('\n')
47 | then
48 | let linePosition = oldText.Lines.GetLinePositionSpan(padRight (span))
49 | mapToTextEdit (linePosition, (prefix + newText.ToString() + "\n"))
50 | else
51 | let linePosition = oldText.Lines.GetLinePositionSpan span
52 | mapToTextEdit (linePosition, newText.ToString())
53 |
54 | let newText = change.NewText
55 |
56 | if newText.Length > 0 then
57 | checkSpanLineEndings (newText, oldText, change.Span, "")
58 | else
59 | defaultTextEdit (oldText, change)
60 |
61 | let convert (oldText: SourceText) (changes: TextChange[]) : TextEdit[] =
62 | //why doesnt it pick up that TextSpan implements IComparable?
63 | //one of life's many mysteries
64 | let comparer (lhs: TextChange) (rhs: TextChange) : int = lhs.Span.CompareTo(rhs.Span)
65 |
66 | changes
67 | |> Seq.sortWith comparer
68 | |> Seq.map (fun x -> processChange oldText x)
69 | |> Seq.toArray
70 |
71 | let getChanges (doc: Document) (oldDoc: Document) : Async = async {
72 | let! ct = Async.CancellationToken
73 | let! changes = doc.GetTextChangesAsync(oldDoc, ct) |> Async.AwaitTask
74 | let! oldText = oldDoc.GetTextAsync(ct) |> Async.AwaitTask
75 | return convert oldText (changes |> Seq.toArray)
76 | }
77 |
78 | let getFormattingOptions (settings: ServerSettings) (doc: Document) (formattingOptions: FormattingOptions) : Async = async {
79 | let! docOptions = doc.GetOptionsAsync() |> Async.AwaitTask
80 |
81 | return
82 | match settings.ApplyFormattingOptions with
83 | | false ->
84 | docOptions
85 | | true ->
86 | docOptions
87 | |> _.WithChangedOption(FormattingOptions.IndentationSize, LanguageNames.CSharp, int formattingOptions.TabSize)
88 | |> _.WithChangedOption(FormattingOptions.UseTabs, LanguageNames.CSharp, not formattingOptions.InsertSpaces)
89 | |> match formattingOptions.InsertFinalNewline with
90 | | Some insertFinalNewline ->
91 | _.WithChangedOption(CSharpFormattingOptions.NewLineForFinally, insertFinalNewline)
92 | | None -> id
93 | |> match formattingOptions.TrimFinalNewlines with
94 | | Some trimFinalNewlines ->
95 | _.WithChangedOption(CSharpFormattingOptions.NewLineForFinally, not trimFinalNewlines)
96 | | None -> id
97 | }
98 |
99 | let rec getSyntaxNode (token: SyntaxToken) : SyntaxNode option =
100 | if token.IsKind(SyntaxKind.EndOfFileToken) then
101 | getSyntaxNode (token.GetPreviousToken())
102 | else
103 | match token.Kind() with
104 | | SyntaxKind.SemicolonToken -> token.Parent |> Some
105 | | SyntaxKind.CloseBraceToken ->
106 | let parent = token.Parent
107 | match parent.Kind() with
108 | | SyntaxKind.Block -> parent.Parent |> Some
109 | | _ -> parent |> Some
110 | | SyntaxKind.CloseParenToken ->
111 | if
112 | token.GetPreviousToken().IsKind(SyntaxKind.SemicolonToken)
113 | && token.Parent.IsKind(SyntaxKind.ForStatement)
114 | then
115 | token.Parent |> Some
116 | else
117 | None
118 | | _ -> None
119 |
120 | let findFormatTarget (root: SyntaxNode) (position: int) : SyntaxNode option =
121 | let token = root.FindToken position
122 | getSyntaxNode token
123 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/CSharpMetadata.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open Ionide.LanguageServerProtocol.Types
4 | open Ionide.LanguageServerProtocol.JsonRpc
5 |
6 | open CSharpLanguageServer.Types
7 | open CSharpLanguageServer.State
8 |
9 | []
10 | module CSharpMetadata =
11 | let handle (context: ServerRequestContext) (metadataParams: CSharpMetadataParams): AsyncLspResult = async {
12 | let uri = metadataParams.TextDocument.Uri
13 |
14 | let metadataMaybe =
15 | context.DecompiledMetadata
16 | |> Map.tryFind uri
17 | |> Option.map (fun x -> x.Metadata)
18 |
19 | return metadataMaybe |> LspResult.success
20 | }
21 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/CallHierarchy.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Microsoft.CodeAnalysis
6 | open Microsoft.CodeAnalysis.FindSymbols
7 | open Ionide.LanguageServerProtocol.Server
8 | open Ionide.LanguageServerProtocol.Types
9 | open Ionide.LanguageServerProtocol.JsonRpc
10 |
11 | open CSharpLanguageServer.Types
12 | open CSharpLanguageServer.State
13 | open CSharpLanguageServer.Conversions
14 |
15 | []
16 | module CallHierarchy =
17 | let private isCallableSymbol (symbol: ISymbol): bool =
18 | if isNull symbol then
19 | false
20 | else
21 | List.contains
22 | symbol.Kind
23 | [ Microsoft.CodeAnalysis.SymbolKind.Method
24 | Microsoft.CodeAnalysis.SymbolKind.Field
25 | Microsoft.CodeAnalysis.SymbolKind.Event
26 | Microsoft.CodeAnalysis.SymbolKind.Property ]
27 |
28 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
29 | clientCapabilities.TextDocument
30 | |> Option.bind (fun x -> x.CallHierarchy)
31 | |> Option.bind (fun x -> x.DynamicRegistration)
32 | |> Option.defaultValue false
33 |
34 | let provider (clientCapabilities: ClientCapabilities) : U3 option =
35 | match dynamicRegistration clientCapabilities with
36 | | true -> None
37 | | false -> Some (U3.C1 true)
38 |
39 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
40 | match dynamicRegistration clientCapabilities with
41 | | false -> None
42 | | true ->
43 | let registerOptions: CallHierarchyRegistrationOptions =
44 | { DocumentSelector = Some defaultDocumentSelector
45 | Id = None
46 | WorkDoneProgress = None
47 | }
48 | Some
49 | { Id = Guid.NewGuid().ToString()
50 | Method = "textDocument/prepareCallHierarchy"
51 | RegisterOptions = registerOptions |> serialize |> Some }
52 |
53 | let prepare (context: ServerRequestContext) (p: CallHierarchyPrepareParams) : AsyncLspResult = async {
54 | match! context.FindSymbol p.TextDocument.Uri p.Position with
55 | | Some symbol when isCallableSymbol symbol ->
56 | let! itemList = CallHierarchyItem.fromSymbol context.ResolveSymbolLocations symbol
57 | return
58 | itemList
59 | |> List.toArray
60 | |> Some
61 | |> LspResult.success
62 | | _ -> return None |> LspResult.success
63 | }
64 |
65 | let incomingCalls
66 | (context: ServerRequestContext)
67 | (p: CallHierarchyIncomingCallsParams)
68 | : AsyncLspResult = async {
69 | let toCallHierarchyIncomingCalls (info: SymbolCallerInfo) : CallHierarchyIncomingCall seq =
70 | let fromRanges =
71 | info.Locations
72 | |> Seq.map (fun l -> l.GetLineSpan().Span |> Range.fromLinePositionSpan)
73 | |> Seq.toArray
74 |
75 | info.CallingSymbol.Locations
76 | |> Seq.map Location.fromRoslynLocation
77 | |> Seq.filter _.IsSome
78 | |> Seq.map _.Value
79 | |> Seq.map (fun loc ->
80 | { From = CallHierarchyItem.fromSymbolAndLocation (info.CallingSymbol) loc
81 | FromRanges = fromRanges })
82 |
83 | match! context.FindSymbol p.Item.Uri p.Item.Range.Start with
84 | | None -> return None |> LspResult.success
85 | | Some symbol ->
86 | let! callers = context.FindCallers symbol
87 | // TODO: If we remove info.IsDirect, then we will get lots of false positive. But if we keep it,
88 | // we will miss many callers. Maybe it should have some change in LSP protocol.
89 | return
90 | callers
91 | |> Seq.filter (fun info -> info.IsDirect && isCallableSymbol info.CallingSymbol)
92 | |> Seq.collect toCallHierarchyIncomingCalls
93 | |> Seq.distinct
94 | |> Seq.toArray
95 | |> Some
96 | |> LspResult.success
97 | }
98 |
99 | let outgoingCalls
100 | (_context: ServerRequestContext)
101 | (_: CallHierarchyOutgoingCallsParams)
102 | : AsyncLspResult = async {
103 | // TODO: There is no memthod of SymbolFinder which can find all outgoing calls of a specific symbol.
104 | // Then how can we implement it? Parsing AST manually?
105 | return None |> LspResult.success
106 | }
107 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/CodeLens.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Microsoft.CodeAnalysis
6 | open Microsoft.CodeAnalysis.CSharp
7 | open Microsoft.CodeAnalysis.CSharp.Syntax
8 | open Microsoft.CodeAnalysis.Text
9 | open Ionide.LanguageServerProtocol.Server
10 | open Ionide.LanguageServerProtocol.Types
11 | open Ionide.LanguageServerProtocol.JsonRpc
12 |
13 | open CSharpLanguageServer.State
14 | open CSharpLanguageServer.Conversions
15 | open CSharpLanguageServer.Types
16 |
17 | type private DocumentSymbolCollectorForCodeLens(semanticModel: SemanticModel) =
18 | inherit CSharpSyntaxWalker(SyntaxWalkerDepth.Token)
19 |
20 | let mutable collectedSymbols = []
21 |
22 | let collect (node: SyntaxNode) (nameSpan: TextSpan) =
23 | let symbol = semanticModel.GetDeclaredSymbol(node)
24 | collectedSymbols <- (symbol, nameSpan) :: collectedSymbols
25 |
26 | member __.GetSymbols() =
27 | collectedSymbols |> List.rev |> Array.ofList
28 |
29 | override __.VisitEnumDeclaration(node) =
30 | collect node node.Identifier.Span
31 | base.VisitEnumDeclaration(node)
32 |
33 | override __.VisitEnumMemberDeclaration(node) =
34 | collect node node.Identifier.Span
35 |
36 | override __.VisitClassDeclaration(node) =
37 | collect node node.Identifier.Span
38 | base.VisitClassDeclaration(node)
39 |
40 | override __.VisitRecordDeclaration(node) =
41 | collect node node.Identifier.Span
42 | base.VisitRecordDeclaration(node)
43 |
44 | override __.VisitStructDeclaration(node) =
45 | collect node node.Identifier.Span
46 | base.VisitStructDeclaration(node)
47 |
48 | override __.VisitInterfaceDeclaration(node) =
49 | collect node node.Identifier.Span
50 | base.VisitInterfaceDeclaration(node)
51 |
52 | override __.VisitDelegateDeclaration(node) =
53 | collect node node.Identifier.Span
54 |
55 | override __.VisitConstructorDeclaration(node) =
56 | collect node node.Identifier.Span
57 |
58 | override __.VisitDestructorDeclaration(node) =
59 | collect node node.Identifier.Span
60 |
61 | override __.VisitOperatorDeclaration(node) =
62 | collect node node.OperatorToken.Span
63 |
64 | override __.VisitIndexerDeclaration(node) =
65 | collect node node.ThisKeyword.Span
66 |
67 | override __.VisitConversionOperatorDeclaration(node) =
68 | collect node node.Type.Span
69 |
70 | override __.VisitMethodDeclaration(node) =
71 | collect node node.Identifier.Span
72 |
73 | override __.VisitPropertyDeclaration(node) =
74 | collect node node.Identifier.Span
75 |
76 | override __.VisitVariableDeclarator(node) =
77 | let grandparent =
78 | node.Parent
79 | |> Option.ofObj
80 | |> Option.bind (fun node -> node.Parent |> Option.ofObj)
81 | // Only show field variables and ignore local variables
82 | if grandparent.IsSome && grandparent.Value :? FieldDeclarationSyntax then
83 | collect node node.Identifier.Span
84 |
85 | override __.VisitEventDeclaration(node) =
86 | collect node node.Identifier.Span
87 |
88 | []
89 | module CodeLens =
90 | type CodeLensData =
91 | { DocumentUri: string
92 | Position: Position }
93 | static member Default =
94 | { DocumentUri = ""
95 | Position = { Line = 0u; Character = 0u } }
96 |
97 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
98 | clientCapabilities.TextDocument
99 | |> Option.bind (fun x -> x.CodeLens)
100 | |> Option.bind (fun x -> x.DynamicRegistration)
101 | |> Option.defaultValue false
102 |
103 | let provider (clientCapabilities: ClientCapabilities) : CodeLensOptions option =
104 | match dynamicRegistration clientCapabilities with
105 | | true -> None
106 | | false -> Some { ResolveProvider = Some true
107 | WorkDoneProgress = None }
108 |
109 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
110 | match dynamicRegistration clientCapabilities with
111 | | false -> None
112 | | true ->
113 | let registerOptions: CodeLensRegistrationOptions =
114 | { ResolveProvider = Some true
115 | WorkDoneProgress = None
116 | DocumentSelector = Some defaultDocumentSelector }
117 |
118 | Some
119 | { Id = Guid.NewGuid().ToString()
120 | Method = "textDocument/codeLens"
121 | RegisterOptions = registerOptions |> serialize |> Some }
122 |
123 | let handle (context: ServerRequestContext) (p: CodeLensParams): AsyncLspResult = async {
124 | let docMaybe = context.GetDocument p.TextDocument.Uri
125 | match docMaybe with
126 | | None ->
127 | return None |> LspResult.success
128 | | Some doc ->
129 | let! ct = Async.CancellationToken
130 | let! semanticModel = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask
131 | let! syntaxTree = doc.GetSyntaxTreeAsync(ct) |> Async.AwaitTask
132 | let! docText = doc.GetTextAsync(ct) |> Async.AwaitTask
133 |
134 | let collector = DocumentSymbolCollectorForCodeLens(semanticModel)
135 | let! root = syntaxTree.GetRootAsync(ct) |> Async.AwaitTask
136 | collector.Visit(root)
137 |
138 | let makeCodeLens (_symbol: ISymbol, nameSpan: TextSpan) : CodeLens =
139 | let start = nameSpan.Start |> docText.Lines.GetLinePosition
140 |
141 | let lensData: CodeLensData =
142 | { DocumentUri = p.TextDocument.Uri
143 | Position = start |> Position.fromLinePosition }
144 |
145 | { Range = nameSpan |> Range.fromTextSpan docText.Lines
146 | Command = None
147 | Data = lensData |> serialize |> Some }
148 |
149 | let codeLens = collector.GetSymbols() |> Seq.map makeCodeLens
150 |
151 | return codeLens |> Array.ofSeq |> Some |> LspResult.success
152 | }
153 |
154 | let resolve (context: ServerRequestContext)
155 | (p: CodeLens)
156 | : AsyncLspResult = async {
157 | let lensData =
158 | p.Data
159 | |> Option.map (fun t -> t.ToObject())
160 | |> Option.defaultValue CodeLensData.Default
161 |
162 | match! context.FindSymbol lensData.DocumentUri lensData.Position with
163 | | None ->
164 | return p |> LspResult.success
165 | | Some symbol ->
166 | let! locations = context.FindReferences symbol false
167 | // FIXME: refNum is wrong. There are lots of false positive even if we distinct locations by
168 | // (l.SourceTree.FilePath, l.SourceSpan)
169 | let refNum =
170 | locations
171 | |> Seq.distinctBy (fun l -> (l.GetMappedLineSpan().Path, l.SourceSpan))
172 | |> Seq.length
173 |
174 | let title = sprintf "%d Reference(s)" refNum
175 |
176 | let arg: ReferenceParams =
177 | { TextDocument = { Uri = lensData.DocumentUri }
178 | Position = lensData.Position
179 | WorkDoneToken = None
180 | PartialResultToken = None
181 | Context = { IncludeDeclaration = true } }
182 | let command =
183 | { Title = title
184 | Command = "textDocument/references"
185 | Arguments = Some [| arg |> serialize |] }
186 |
187 | return { p with Command = Some command } |> LspResult.success
188 | }
189 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Color.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open Ionide.LanguageServerProtocol.Types
4 | open Ionide.LanguageServerProtocol.JsonRpc
5 |
6 | open CSharpLanguageServer.State
7 |
8 | []
9 | module Color =
10 | let provider (clientCapabilities: ClientCapabilities) = None
11 |
12 | let registration (clientCapabilities: ClientCapabilities) : Registration option = None
13 |
14 | let handle (context: ServerRequestContext) (p: DocumentColorParams) : AsyncLspResult =
15 | LspResult.notImplemented |> async.Return
16 |
17 | let present (context: ServerRequestContext) (p: ColorPresentationParams) : AsyncLspResult =
18 | LspResult.notImplemented |> async.Return
19 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Completion.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 | open System.Reflection
5 |
6 | open Ionide.LanguageServerProtocol.Server
7 | open Ionide.LanguageServerProtocol.Types
8 | open Ionide.LanguageServerProtocol.JsonRpc
9 |
10 | open CSharpLanguageServer
11 | open CSharpLanguageServer.State
12 | open CSharpLanguageServer.Util
13 | open CSharpLanguageServer.Conversions
14 | open CSharpLanguageServer.Types
15 | open CSharpLanguageServer.Logging
16 |
17 | []
18 | module Completion =
19 | let private _logger = LogProvider.getLoggerByName "Completion"
20 |
21 | let emptyRoslynOptionSet: Microsoft.CodeAnalysis.Options.OptionSet =
22 | let osType = typeof
23 | let osEmptyOptionSetField = osType.GetField("Empty", BindingFlags.Static|||BindingFlags.NonPublic)
24 | osEmptyOptionSetField.GetValue(null) :?> Microsoft.CodeAnalysis.Options.OptionSet
25 |
26 | /// the type reflects on internal class Microsoft.CodeAnalysis.Completion.CompletionOptions
27 | /// see https://github.com/dotnet/roslyn/blob/main/src/Features/Core/Portable/Completion/CompletionOptions.cs
28 | type RoslynCompletionOptions =
29 | {
30 | Object: obj
31 | CompletionOptionsType: Type
32 | }
33 | with
34 | member rco.WithBool(optionName: string, optionValue: bool) =
35 | let cloneCompletionOptionsMI = rco.CompletionOptionsType.GetMethod("$")
36 | let updatedCompletionOptions = cloneCompletionOptionsMI.Invoke(rco.Object, null)
37 | let newCo = rco.CompletionOptionsType.GetProperty(optionName)
38 | newCo.SetValue(updatedCompletionOptions, optionValue)
39 | { rco with Object = updatedCompletionOptions }
40 |
41 | static member Default() =
42 | let featuresAssembly = Assembly.Load("Microsoft.CodeAnalysis.Features")
43 | let coType = featuresAssembly.GetType("Microsoft.CodeAnalysis.Completion.CompletionOptions")
44 | let defaultCo: obj = coType.GetField("Default").GetValue()
45 | { Object = defaultCo; CompletionOptionsType = coType }
46 |
47 | type RoslynCompletionServiceWrapper(service: Microsoft.CodeAnalysis.Completion.CompletionService) =
48 | member __.GetCompletionsAsync(doc, position, completionOptions, completionTrigger, ct) : Async =
49 | let completionServiceType = service.GetType()
50 |
51 | let getCompletionsAsync7MI =
52 | completionServiceType.GetMethods(BindingFlags.Instance|||BindingFlags.NonPublic)
53 | |> Seq.filter (fun mi -> mi.Name = "GetCompletionsAsync" && mi.GetParameters().Length = 7)
54 | |> Seq.head
55 |
56 | let parameters: obj array = [| doc; position; completionOptions.Object; emptyRoslynOptionSet; completionTrigger; null; ct |]
57 |
58 | let result = getCompletionsAsync7MI.Invoke(service, parameters)
59 |
60 | (result :?> System.Threading.Tasks.Task)
61 | |> Async.AwaitTask
62 |
63 | member __.ShouldTriggerCompletion(sourceText, position, completionTrigger) =
64 | service.ShouldTriggerCompletion(sourceText, position, completionTrigger)
65 |
66 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
67 | clientCapabilities.TextDocument
68 | |> Option.bind (fun x -> x.Completion)
69 | |> Option.bind (fun x -> x.DynamicRegistration)
70 | |> Option.defaultValue false
71 |
72 | let provider (clientCapabilities: ClientCapabilities) : CompletionOptions option =
73 | match dynamicRegistration clientCapabilities with
74 | | true -> None
75 | | false ->
76 | Some { ResolveProvider = None
77 | TriggerCharacters = Some ([| "."; "'"; |])
78 | AllCommitCharacters = None
79 | WorkDoneProgress = None
80 | CompletionItem = None }
81 |
82 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
83 | match dynamicRegistration clientCapabilities with
84 | | false -> None
85 | | true ->
86 | let registerOptions: CompletionRegistrationOptions =
87 | { DocumentSelector = Some defaultDocumentSelector
88 | ResolveProvider = Some true
89 | TriggerCharacters = Some ([| "."; "'"; |])
90 | AllCommitCharacters = None
91 | CompletionItem = None
92 | WorkDoneProgress = None
93 | }
94 |
95 | Some
96 | { Id = Guid.NewGuid().ToString()
97 | Method = "textDocument/completion"
98 | RegisterOptions = registerOptions |> serialize |> Some }
99 |
100 | let private roslynTagToLspCompletion tag =
101 | match tag with
102 | | "Class" -> CompletionItemKind.Class
103 | | "Delegate" -> CompletionItemKind.Function
104 | | "Enum" -> CompletionItemKind.Enum
105 | | "EnumMember" -> CompletionItemKind.EnumMember
106 | | "Interface" -> CompletionItemKind.Interface
107 | | "Struct" -> CompletionItemKind.Struct
108 | | "Local" -> CompletionItemKind.Variable
109 | | "Parameter" -> CompletionItemKind.Variable
110 | | "RangeVariable" -> CompletionItemKind.Variable
111 | | "Const" -> CompletionItemKind.Constant
112 | | "Event" -> CompletionItemKind.Event
113 | | "Field" -> CompletionItemKind.Field
114 | | "Method" -> CompletionItemKind.Method
115 | | "Property" -> CompletionItemKind.Property
116 | | "Label" -> CompletionItemKind.Unit
117 | | "Keyword" -> CompletionItemKind.Keyword
118 | | "Namespace" -> CompletionItemKind.Module
119 | | _ -> CompletionItemKind.Property
120 |
121 | // TODO: Add parameters to label so that we can distinguish override versions?
122 | // TODO: Change parameters to snippets like clangd
123 | let private makeLspCompletionItem
124 | (item: Microsoft.CodeAnalysis.Completion.CompletionItem)
125 | (cacheKey: uint64) =
126 | { Ionide.LanguageServerProtocol.Types.CompletionItem.Create(item.DisplayText) with
127 | Kind = item.Tags |> Seq.tryHead |> Option.map roslynTagToLspCompletion
128 | SortText = item.SortText |> Option.ofString
129 | FilterText = item.FilterText |> Option.ofString
130 | Detail = item.InlineDescription |> Option.ofString
131 | TextEditText = item.DisplayTextPrefix |> Option.ofObj
132 | InsertTextFormat = Some InsertTextFormat.PlainText
133 | Data = cacheKey |> serialize |> Some }
134 |
135 | let private cache = new LruCache<(Microsoft.CodeAnalysis.Document * Microsoft.CodeAnalysis.Completion.CompletionList)>(5)
136 |
137 | let handle (context: ServerRequestContext) (p: CompletionParams) : Async option>> = async {
138 | match context.GetDocument p.TextDocument.Uri with
139 | | None ->
140 | return None |> LspResult.success
141 | | Some doc ->
142 | let! ct = Async.CancellationToken
143 | let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
144 |
145 | let position = Position.toRoslynPosition sourceText.Lines p.Position
146 |
147 | let completionService =
148 | Microsoft.CodeAnalysis.Completion.CompletionService.GetService(doc)
149 | |> RoslynCompletionServiceWrapper
150 |
151 | let completionOptions =
152 | RoslynCompletionOptions.Default()
153 | |> _.WithBool("ShowItemsFromUnimportedNamespaces", false)
154 | |> _.WithBool("ShowNameSuggestions", false)
155 |
156 | let completionTrigger = CompletionContext.toCompletionTrigger p.Context
157 | let shouldTriggerCompletion =
158 | p.Context |> Option.exists (fun x -> x.TriggerKind = CompletionTriggerKind.TriggerForIncompleteCompletions) ||
159 | completionService.ShouldTriggerCompletion(sourceText, position, completionTrigger)
160 |
161 | let! completions =
162 | if shouldTriggerCompletion then
163 | completionService.GetCompletionsAsync(doc, position, completionOptions, completionTrigger, ct)
164 | |> Async.map Option.ofObj
165 | else
166 | async.Return None
167 |
168 | return
169 | completions
170 | |> Option.map (fun completions ->
171 | let key = cache.add((doc, completions))
172 | let items =
173 | completions.ItemsList
174 | |> Seq.map (flip makeLspCompletionItem key)
175 | |> Array.ofSeq
176 | { IsIncomplete = true
177 | Items = items
178 | ItemDefaults = None })
179 | |> Option.map U2.C2
180 | |> LspResult.success
181 | }
182 |
183 | let resolve (_context: ServerRequestContext) (item: CompletionItem) : AsyncLspResult = async {
184 | match
185 | item.Data
186 | |> Option.bind deserialize
187 | |> Option.bind cache.get
188 | |> Option.bind (fun (doc, cachedItems) ->
189 | cachedItems.ItemsList
190 | |> Seq.tryFind (fun x -> x.DisplayText = item.Label && (item.SortText.IsNone || x.SortText = item.SortText.Value))
191 | |> Option.map (fun x -> (doc, x)))
192 | with
193 | | Some (doc, cachedItem) ->
194 | let completionService = Microsoft.CodeAnalysis.Completion.CompletionService.GetService(doc)
195 | let! ct = Async.CancellationToken
196 | let! description =
197 | completionService.GetDescriptionAsync(doc, cachedItem, ct)
198 | |> Async.AwaitTask
199 | |> Async.map Option.ofObj
200 | // TODO: make the doc as a markdown string instead of a plain text
201 | let itemDocumentation = description |> Option.map Documentation.fromCompletionDescription
202 | return { item with Documentation = itemDocumentation |> Option.map U2.C2 }
203 | |> LspResult.success
204 | | None ->
205 | return item |> LspResult.success
206 | }
207 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Declaration.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open Ionide.LanguageServerProtocol.Types
4 | open Ionide.LanguageServerProtocol.JsonRpc
5 |
6 | open CSharpLanguageServer.State
7 |
8 | []
9 | module Declaration =
10 | let provider (_cc: ClientCapabilities) : bool option = None
11 |
12 | let registration (_cc: ClientCapabilities) : Registration option = None
13 |
14 | let handle (_context: ServerRequestContext) (_p: DeclarationParams) : AsyncLspResult option> =
15 | LspResult.notImplemented option> |> async.Return
16 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Definition.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Ionide.LanguageServerProtocol.Server
6 | open Ionide.LanguageServerProtocol.Types
7 | open Ionide.LanguageServerProtocol.JsonRpc
8 |
9 | open CSharpLanguageServer.State
10 | open CSharpLanguageServer.Types
11 |
12 | []
13 | module Definition =
14 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
15 | clientCapabilities.TextDocument
16 | |> Option.bind (fun x -> x.Definition)
17 | |> Option.bind (fun x -> x.DynamicRegistration)
18 | |> Option.defaultValue false
19 |
20 | let provider (clientCapabilities: ClientCapabilities) : U2 option =
21 | match dynamicRegistration clientCapabilities with
22 | | true -> None
23 | | false -> Some (U2.C1 true)
24 |
25 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
26 | match dynamicRegistration clientCapabilities with
27 | | false -> None
28 | | true ->
29 | let registerOptions: DefinitionRegistrationOptions =
30 | { DocumentSelector = Some defaultDocumentSelector
31 | WorkDoneProgress = None }
32 | Some
33 | { Id = Guid.NewGuid().ToString()
34 | Method = "textDocument/definition"
35 | RegisterOptions = registerOptions |> serialize |> Some }
36 |
37 | let handle (context: ServerRequestContext) (p: DefinitionParams) : Async option>> = async {
38 | match! context.FindSymbol' p.TextDocument.Uri p.Position with
39 | | None ->
40 | return None |> LspResult.success
41 | | Some (symbol, doc) ->
42 | let! locations = context.ResolveSymbolLocations symbol (Some doc.Project)
43 | return
44 | locations
45 | |> Array.ofList
46 | |> Definition.C2
47 | |> U2.C1
48 | |> Some
49 | |> LspResult.success
50 | }
51 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Diagnostic.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Ionide.LanguageServerProtocol.Server
6 | open Ionide.LanguageServerProtocol.Types
7 | open Ionide.LanguageServerProtocol.JsonRpc
8 |
9 | open CSharpLanguageServer.Conversions
10 | open CSharpLanguageServer.State
11 | open CSharpLanguageServer.Types
12 | open CSharpLanguageServer.Logging
13 |
14 | []
15 | module Diagnostic =
16 | let private logger = LogProvider.getLoggerByName "Diagnostic"
17 |
18 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
19 | clientCapabilities.TextDocument
20 | |> Option.bind (fun x -> x.Diagnostic)
21 | |> Option.bind (fun x -> x.DynamicRegistration)
22 | |> Option.defaultValue false
23 |
24 | let provider (clientCapabilities: ClientCapabilities): U2 option =
25 | match dynamicRegistration clientCapabilities with
26 | | true -> None
27 | | false ->
28 | let diagnosticOptions: DiagnosticRegistrationOptions =
29 | { DocumentSelector = Some defaultDocumentSelector
30 | WorkDoneProgress = None
31 | Identifier = None
32 | InterFileDependencies = false
33 | WorkspaceDiagnostics = false
34 | Id = None
35 | }
36 |
37 | Some (U2.C2 diagnosticOptions)
38 |
39 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
40 | match dynamicRegistration clientCapabilities with
41 | | false -> None
42 | | true ->
43 | let registerOptions: DiagnosticRegistrationOptions =
44 | { DocumentSelector = Some defaultDocumentSelector
45 | WorkDoneProgress = None
46 | Identifier = None
47 | InterFileDependencies = false
48 | WorkspaceDiagnostics = false
49 | Id = None }
50 |
51 | Some
52 | { Id = Guid.NewGuid().ToString()
53 | Method = "textDocument/diagnostic"
54 | RegisterOptions = registerOptions |> serialize |> Some }
55 |
56 | let handle (context: ServerRequestContext) (p: DocumentDiagnosticParams) : AsyncLspResult = async {
57 | let emptyReport: RelatedFullDocumentDiagnosticReport =
58 | {
59 | Kind = "full"
60 | ResultId = None
61 | Items = [| |]
62 | RelatedDocuments = None
63 | }
64 |
65 | match context.GetDocument p.TextDocument.Uri with
66 | | None ->
67 | return emptyReport |> U2.C1 |> LspResult.success
68 |
69 | | Some doc ->
70 | let! ct = Async.CancellationToken
71 | let! semanticModelMaybe = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask
72 | match semanticModelMaybe |> Option.ofObj with
73 | | Some semanticModel ->
74 | let diagnostics =
75 | semanticModel.GetDiagnostics()
76 | |> Seq.map Diagnostic.fromRoslynDiagnostic
77 | |> Array.ofSeq
78 |
79 | return { emptyReport with Items = diagnostics }
80 | |> U2.C1
81 | |> LspResult.success
82 |
83 | | None ->
84 | return emptyReport |> U2.C1 |> LspResult.success
85 | }
86 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/DocumentFormatting.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Microsoft.CodeAnalysis.Formatting
6 | open Ionide.LanguageServerProtocol.Server
7 | open Ionide.LanguageServerProtocol.Types
8 | open Ionide.LanguageServerProtocol.JsonRpc
9 |
10 | open CSharpLanguageServer
11 | open CSharpLanguageServer.State
12 | open CSharpLanguageServer.Types
13 |
14 | []
15 | module DocumentFormatting =
16 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
17 | clientCapabilities.TextDocument
18 | |> Option.bind (fun x -> x.Formatting)
19 | |> Option.bind (fun x -> x.DynamicRegistration)
20 | |> Option.defaultValue false
21 |
22 | let provider (clientCapabilities: ClientCapabilities) : U2 option =
23 | match dynamicRegistration clientCapabilities with
24 | | true -> None
25 | | false -> Some (U2.C1 true)
26 |
27 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
28 | match dynamicRegistration clientCapabilities with
29 | | false -> None
30 | | true ->
31 | let registerOptions: DocumentFormattingRegistrationOptions =
32 | { DocumentSelector = Some defaultDocumentSelector
33 | WorkDoneProgress = None }
34 | Some
35 | { Id = Guid.NewGuid().ToString()
36 | Method = "textDocument/formatting"
37 | RegisterOptions = registerOptions |> serialize |> Some }
38 |
39 | let handle (context: ServerRequestContext) (p: DocumentFormattingParams) : AsyncLspResult = async {
40 | match context.GetUserDocument p.TextDocument.Uri with
41 | | None -> return None |> LspResult.success
42 | | Some doc ->
43 | let! ct = Async.CancellationToken
44 | let! options = FormatUtil.getFormattingOptions context.State.Settings doc p.Options
45 | let! newDoc = Formatter.FormatAsync(doc, options, cancellationToken=ct) |> Async.AwaitTask
46 | let! textEdits = FormatUtil.getChanges newDoc doc
47 | return textEdits |> Some |> LspResult.success
48 | }
49 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/DocumentHighlight.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 | open System.Collections.Immutable
5 |
6 | open Microsoft.CodeAnalysis
7 | open Microsoft.CodeAnalysis.FindSymbols
8 | open Ionide.LanguageServerProtocol.Server
9 | open Ionide.LanguageServerProtocol.Types
10 | open Ionide.LanguageServerProtocol.JsonRpc
11 |
12 | open CSharpLanguageServer.Types
13 | open CSharpLanguageServer.State
14 | open CSharpLanguageServer.Conversions
15 |
16 | []
17 | module DocumentHighlight =
18 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
19 | clientCapabilities.TextDocument
20 | |> Option.bind (fun x -> x.DocumentHighlight)
21 | |> Option.bind (fun x -> x.DynamicRegistration)
22 | |> Option.defaultValue false
23 |
24 | let provider (clientCapabilities: ClientCapabilities) : U2 option =
25 | match dynamicRegistration clientCapabilities with
26 | | true -> None
27 | | false -> Some (U2.C1 true)
28 |
29 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
30 | match dynamicRegistration clientCapabilities with
31 | | false -> None
32 | | true ->
33 | let registerOptions: DocumentHighlightRegistrationOptions =
34 | { DocumentSelector = Some defaultDocumentSelector
35 | WorkDoneProgress = None
36 | }
37 |
38 | Some
39 | { Id = Guid.NewGuid().ToString()
40 | Method = "textDocument/documentHighlight"
41 | RegisterOptions = registerOptions |> serialize |> Some }
42 |
43 | let private shouldHighlight (symbol: ISymbol) =
44 | match symbol with
45 | | :? INamespaceSymbol -> false
46 | | _ -> true
47 |
48 | let handle (context: ServerRequestContext) (p: DocumentHighlightParams) : AsyncLspResult = async {
49 | let! ct = Async.CancellationToken
50 | let filePath = Uri.toPath p.TextDocument.Uri
51 |
52 | // We only need to find references in the file (not the whole workspace), so we don't use
53 | // context.FindSymbol & context.FindReferences here.
54 | let getHighlights (symbol: ISymbol) (doc: Document) = async {
55 | let docSet = ImmutableHashSet.Create(doc)
56 | let! refs = SymbolFinder.FindReferencesAsync(symbol, doc.Project.Solution, docSet, cancellationToken=ct) |> Async.AwaitTask
57 | let! def = SymbolFinder.FindSourceDefinitionAsync(symbol, doc.Project.Solution, cancellationToken=ct) |> Async.AwaitTask
58 |
59 | let locations =
60 | refs
61 | |> Seq.collect (fun r -> r.Locations)
62 | |> Seq.map (fun rl -> rl.Location)
63 | |> Seq.filter (fun l -> l.IsInSource && l.GetMappedLineSpan().Path = filePath)
64 | |> Seq.append (def |> Option.ofObj |> Option.toList |> Seq.collect (fun sym -> sym.Locations))
65 |
66 | return
67 | locations
68 | |> Seq.map Location.fromRoslynLocation
69 | |> Seq.filter _.IsSome
70 | |> Seq.map _.Value
71 | |> Seq.map (fun l ->
72 | { Range = l.Range
73 | Kind = Some DocumentHighlightKind.Read })
74 | }
75 |
76 | match! context.FindSymbol' p.TextDocument.Uri p.Position with
77 | | None -> return None |> LspResult.success
78 | | Some (symbol, doc) ->
79 | match Option.ofObj symbol with
80 | | Some symbol when shouldHighlight symbol ->
81 | let! highlights = getHighlights symbol doc
82 | return highlights |> Seq.toArray |> Some |> LspResult.success
83 | | _ -> return None |> LspResult.success
84 | }
85 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/DocumentLink.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open Ionide.LanguageServerProtocol.Types
4 | open Ionide.LanguageServerProtocol.JsonRpc
5 |
6 | open CSharpLanguageServer.State
7 |
8 | []
9 | module DocumentLink =
10 | let provider (clientCapabilities: ClientCapabilities) : DocumentLinkOptions option = None
11 |
12 | let registration (clientCapabilities: ClientCapabilities) : Registration option = None
13 |
14 | let handle (context: ServerRequestContext) (p: DocumentLinkParams) : AsyncLspResult =
15 | LspResult.notImplemented |> async.Return
16 |
17 | let resolve (context: ServerRequestContext) (p: DocumentLink) : AsyncLspResult =
18 | LspResult.notImplemented |> async.Return
19 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/DocumentOnTypeFormatting.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Microsoft.CodeAnalysis.Text
6 | open Microsoft.CodeAnalysis.Formatting
7 | open Ionide.LanguageServerProtocol.Server
8 | open Ionide.LanguageServerProtocol.Types
9 | open Ionide.LanguageServerProtocol.JsonRpc
10 |
11 | open CSharpLanguageServer
12 | open CSharpLanguageServer.State
13 | open CSharpLanguageServer.Types
14 | open CSharpLanguageServer.Conversions
15 |
16 | []
17 | module DocumentOnTypeFormatting =
18 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
19 | clientCapabilities.TextDocument
20 | |> Option.bind (fun x -> x.OnTypeFormatting)
21 | |> Option.bind (fun x -> x.DynamicRegistration)
22 | |> Option.defaultValue false
23 |
24 | let provider (clientCapabilities: ClientCapabilities) : DocumentOnTypeFormattingOptions option =
25 | match dynamicRegistration clientCapabilities with
26 | | true -> None
27 | | false ->
28 | Some
29 | { FirstTriggerCharacter = ";"
30 | MoreTriggerCharacter = Some([| "}"; ")" |]) }
31 |
32 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
33 | match dynamicRegistration clientCapabilities with
34 | | false -> None
35 | | true ->
36 | let registerOptions: DocumentOnTypeFormattingRegistrationOptions =
37 | { FirstTriggerCharacter = ";"
38 | MoreTriggerCharacter = Some([| "}"; ")" |])
39 | DocumentSelector = Some defaultDocumentSelector
40 | }
41 | Some
42 | { Id = Guid.NewGuid().ToString()
43 | Method = "textDocument/onTypeFormatting"
44 | RegisterOptions = registerOptions |> serialize |> Some }
45 |
46 | let handle (context: ServerRequestContext) (p: DocumentOnTypeFormattingParams) : AsyncLspResult = async {
47 | match context.GetUserDocument p.TextDocument.Uri with
48 | | None ->
49 | return None |> LspResult.success
50 | | Some doc ->
51 | let! options = FormatUtil.getFormattingOptions context.State.Settings doc p.Options
52 | let! ct = Async.CancellationToken
53 | let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
54 | let pos = Position.toRoslynPosition sourceText.Lines p.Position
55 |
56 | match p.Ch with
57 | | ";"
58 | | "}"
59 | | ")" ->
60 | let! root = doc.GetSyntaxRootAsync(ct) |> Async.AwaitTask
61 | match FormatUtil.findFormatTarget root pos with
62 | | None -> return None |> LspResult.success
63 | | Some node ->
64 | let! newDoc =
65 | Formatter.FormatAsync(doc, TextSpan.FromBounds(node.FullSpan.Start, node.FullSpan.End), options, cancellationToken=ct)
66 | |> Async.AwaitTask
67 | let! textEdits = FormatUtil.getChanges newDoc doc
68 | return textEdits |> Some |> LspResult.success
69 | | _ ->
70 | return None |> LspResult.success
71 | }
72 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/DocumentRangeFormatting.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Microsoft.CodeAnalysis.Formatting
6 | open Microsoft.CodeAnalysis.Text
7 | open Ionide.LanguageServerProtocol.Server
8 | open Ionide.LanguageServerProtocol.Types
9 | open Ionide.LanguageServerProtocol.JsonRpc
10 |
11 | open CSharpLanguageServer
12 | open CSharpLanguageServer.State
13 | open CSharpLanguageServer.Conversions
14 | open CSharpLanguageServer.Types
15 |
16 | []
17 | module DocumentRangeFormatting =
18 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
19 | clientCapabilities.TextDocument
20 | |> Option.bind (fun x -> x.RangeFormatting)
21 | |> Option.bind (fun x -> x.DynamicRegistration)
22 | |> Option.defaultValue false
23 |
24 | let provider (clientCapabilities: ClientCapabilities) : U2 option =
25 | match dynamicRegistration clientCapabilities with
26 | | true -> None
27 | | false -> Some (U2.C1 true)
28 |
29 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
30 | match dynamicRegistration clientCapabilities with
31 | | false -> None
32 | | true ->
33 | let registerOptions: DocumentRangeFormattingRegistrationOptions =
34 | { DocumentSelector = Some defaultDocumentSelector
35 | WorkDoneProgress = None
36 | }
37 |
38 | Some
39 | { Id = Guid.NewGuid().ToString()
40 | Method = "textDocument/rangeFormatting"
41 | RegisterOptions = registerOptions |> serialize |> Some }
42 |
43 | let handle (context: ServerRequestContext) (p: DocumentRangeFormattingParams) : AsyncLspResult = async {
44 | match context.GetUserDocument p.TextDocument.Uri with
45 | | None ->
46 | return None |> LspResult.success
47 | | Some doc ->
48 | let! ct = Async.CancellationToken
49 | let! options = FormatUtil.getFormattingOptions context.State.Settings doc p.Options
50 | let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
51 | let startPos = Position.toRoslynPosition sourceText.Lines p.Range.Start
52 | let endPos = Position.toRoslynPosition sourceText.Lines p.Range.End
53 | let! syntaxTree = doc.GetSyntaxRootAsync(ct) |> Async.AwaitTask
54 | let tokenStart = syntaxTree.FindToken(startPos).FullSpan.Start
55 | let! newDoc =
56 | Formatter.FormatAsync(doc, TextSpan.FromBounds(tokenStart, endPos), options, cancellationToken=ct)
57 | |> Async.AwaitTask
58 | let! textEdits = FormatUtil.getChanges newDoc doc
59 | return textEdits |> Some |> LspResult.success
60 | }
61 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/ExecuteCommand.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open Ionide.LanguageServerProtocol.Types
4 | open Ionide.LanguageServerProtocol.JsonRpc
5 |
6 | open CSharpLanguageServer.State
7 |
8 | []
9 | module ExecuteCommand =
10 | let provider (_cc: ClientCapabilities) : ExecuteCommandOptions option = None
11 |
12 | let registration (_cc: ClientCapabilities) : Registration option = None
13 |
14 | let handle (_context: ServerRequestContext) (_p: ExecuteCommandParams) : AsyncLspResult =
15 | LspResult.notImplemented |> async.Return
16 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/FoldingRange.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open Ionide.LanguageServerProtocol.Types
4 | open Ionide.LanguageServerProtocol.JsonRpc
5 |
6 | open CSharpLanguageServer.State
7 |
8 | []
9 | module FoldingRange =
10 | let provider (_c: ClientCapabilities) : bool option = None
11 |
12 | let registration (_c: ClientCapabilities) : Registration option = None
13 |
14 | let handle (_c: ServerRequestContext) (_p: FoldingRangeParams) : AsyncLspResult =
15 | LspResult.notImplemented |> async.Return
16 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Hover.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Ionide.LanguageServerProtocol.Server
6 | open Ionide.LanguageServerProtocol.Types
7 | open Ionide.LanguageServerProtocol.JsonRpc
8 |
9 | open CSharpLanguageServer
10 | open CSharpLanguageServer.Types
11 | open CSharpLanguageServer.State
12 |
13 | []
14 | module Hover =
15 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
16 | clientCapabilities.TextDocument
17 | |> Option.bind (fun x -> x.Hover)
18 | |> Option.bind (fun x -> x.DynamicRegistration)
19 | |> Option.defaultValue false
20 |
21 | let provider (clientCapabilities: ClientCapabilities) : U2 option =
22 | match dynamicRegistration clientCapabilities with
23 | | true -> Some (U2.C1 false)
24 | | false -> Some (U2.C1 true)
25 |
26 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
27 | match dynamicRegistration clientCapabilities with
28 | | false -> None
29 | | true ->
30 | let registerOptions: HoverRegistrationOptions =
31 | { DocumentSelector = Some defaultDocumentSelector
32 | WorkDoneProgress = None
33 | }
34 | Some
35 | { Id = Guid.NewGuid().ToString()
36 | Method = "textDocument/hover"
37 | RegisterOptions = registerOptions |> serialize |> Some }
38 |
39 | let handle (context: ServerRequestContext) (p: HoverParams) : AsyncLspResult = async {
40 | match! context.FindSymbol' p.TextDocument.Uri p.Position with
41 | | None ->
42 | return None |> LspResult.success
43 | | Some (symbol, doc) ->
44 | let content = DocumentationUtil.markdownDocForSymbolWithSignature symbol
45 | let hover =
46 | { Contents = { Kind = MarkupKind.Markdown; Value = content } |> U3.C1
47 | // TODO: Support range
48 | Range = None }
49 | return hover |> Some |> LspResult.success
50 | }
51 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Implementation.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Ionide.LanguageServerProtocol.Server
6 | open Ionide.LanguageServerProtocol.Types
7 | open Ionide.LanguageServerProtocol.JsonRpc
8 |
9 | open CSharpLanguageServer.Types
10 | open CSharpLanguageServer.State
11 | open CSharpLanguageServer.Util
12 |
13 | []
14 | module Implementation =
15 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
16 | clientCapabilities.TextDocument
17 | |> Option.bind (fun x -> x.Implementation)
18 | |> Option.bind (fun x -> x.DynamicRegistration)
19 | |> Option.defaultValue false
20 |
21 | let provider (clientCapabilities: ClientCapabilities) : U3 option =
22 | match dynamicRegistration clientCapabilities with
23 | | true -> None
24 | | false -> Some (U3.C1 true)
25 |
26 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
27 | match dynamicRegistration clientCapabilities with
28 | | false -> None
29 | | true ->
30 | let registerOptions: ImplementationRegistrationOptions =
31 | { DocumentSelector = Some defaultDocumentSelector
32 | Id = None
33 | WorkDoneProgress = None }
34 |
35 | Some
36 | { Id = Guid.NewGuid().ToString()
37 | Method = "textDocument/implementation"
38 | RegisterOptions = registerOptions |> serialize |> Some }
39 |
40 | let handle (context: ServerRequestContext) (p: ImplementationParams) : Async option>> = async {
41 | match! context.FindSymbol p.TextDocument.Uri p.Position with
42 | | None -> return None |> LspResult.success
43 | | Some symbol ->
44 | let! impls = context.FindImplementations symbol
45 | let! locations = impls |> Seq.map (flip context.ResolveSymbolLocations None) |> Async.Parallel
46 |
47 | return
48 | locations
49 | |> Array.collect List.toArray
50 | |> Declaration.C2
51 | |> U2.C1
52 | |> Some
53 | |> LspResult.success
54 | }
55 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Initialization.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 | open System.IO
5 | open System.Reflection
6 |
7 | open Microsoft.Build.Locator
8 | open Ionide.LanguageServerProtocol
9 | open Ionide.LanguageServerProtocol.Types
10 | open Ionide.LanguageServerProtocol.Server
11 | open Ionide.LanguageServerProtocol.JsonRpc
12 |
13 | open CSharpLanguageServer.State
14 | open CSharpLanguageServer.State.ServerState
15 | open CSharpLanguageServer.Types
16 | open CSharpLanguageServer.Logging
17 |
18 | []
19 | module Initialization =
20 | let private logger = LogProvider.getLoggerByName "Initialization"
21 |
22 | let handleInitialize (lspClient: ILspClient)
23 | (setupTimer: unit -> unit)
24 | (serverCapabilities: ServerCapabilities)
25 | (context: ServerRequestContext)
26 | (p: InitializeParams)
27 | : Async> = async {
28 | // context.State.LspClient has not been initialized yet thus context.WindowShowMessage will not work
29 | let windowShowMessage m = lspClient.WindowLogMessage({ Type = MessageType.Info; Message = m })
30 |
31 | context.Emit(ClientChange (Some lspClient))
32 |
33 | let serverName = "csharp-ls"
34 | let serverVersion = Assembly.GetExecutingAssembly().GetName().Version |> string
35 | logger.info (
36 | Log.setMessage "initializing, {name} version {version}"
37 | >> Log.addContext "name" serverName
38 | >> Log.addContext "version" serverVersion
39 | )
40 |
41 | do! windowShowMessage(
42 | sprintf "csharp-ls: initializing, version %s" serverVersion)
43 |
44 | logger.info (
45 | Log.setMessage "{name} is released under MIT license and is not affiliated with Microsoft Corp.; see https://github.com/razzmatazz/csharp-language-server"
46 | >> Log.addContext "name" serverName
47 | )
48 |
49 | do! windowShowMessage(
50 | sprintf "csharp-ls: %s is released under MIT license and is not affiliated with Microsoft Corp.; see https://github.com/razzmatazz/csharp-language-server" serverName)
51 |
52 | let vsInstanceQueryOpt = VisualStudioInstanceQueryOptions.Default
53 | let vsInstanceList = MSBuildLocator.QueryVisualStudioInstances(vsInstanceQueryOpt)
54 | if Seq.isEmpty vsInstanceList then
55 | raise (InvalidOperationException("No instances of MSBuild could be detected." + Environment.NewLine + "Try calling RegisterInstance or RegisterMSBuildPath to manually register one."))
56 |
57 | // do! infoMessage "MSBuildLocator instances found:"
58 | //
59 | // for vsInstance in vsInstanceList do
60 | // do! infoMessage (sprintf "- SDK=\"%s\", Version=%s, MSBuildPath=\"%s\", DiscoveryType=%s"
61 | // vsInstance.Name
62 | // (string vsInstance.Version)
63 | // vsInstance.MSBuildPath
64 | // (string vsInstance.DiscoveryType))
65 |
66 | let vsInstance = vsInstanceList |> Seq.head
67 |
68 | logger.info(
69 | Log.setMessage "MSBuildLocator: will register \"{vsInstanceName}\", Version={vsInstanceVersion} as default instance"
70 | >> Log.addContext "vsInstanceName" vsInstance.Name
71 | >> Log.addContext "vsInstanceVersion" (string vsInstance.Version)
72 | )
73 |
74 | MSBuildLocator.RegisterInstance(vsInstance)
75 |
76 | (*
77 | logger.trace (
78 | Log.setMessage "handleInitialize: p.Capabilities={caps}"
79 | >> Log.addContext "caps" (serialize p.Capabilities)
80 | )
81 | *)
82 | context.Emit(ClientCapabilityChange p.Capabilities)
83 |
84 | // TODO use p.RootUri
85 | let rootPath = Directory.GetCurrentDirectory()
86 | context.Emit(RootPathChange rootPath)
87 |
88 | // setup timer so actors get period ticks
89 | setupTimer()
90 |
91 | let initializeResult =
92 | { InitializeResult.Default with
93 | Capabilities = serverCapabilities
94 | ServerInfo =
95 | Some
96 | { Name = "csharp-ls"
97 | Version = Some (Assembly.GetExecutingAssembly().GetName().Version.ToString()) }}
98 |
99 | return initializeResult |> LspResult.success
100 | }
101 |
102 | let handleInitialized (lspClient: ILspClient)
103 | (stateActor: MailboxProcessor)
104 | (getRegistrations: ClientCapabilities -> Registration list)
105 | (context: ServerRequestContext)
106 | (_p: unit)
107 | : Async> =
108 | async {
109 | logger.trace (
110 | Log.setMessage "handleInitialized: \"initialized\" notification received from client"
111 | )
112 |
113 | let registrationParams = { Registrations = getRegistrations context.ClientCapabilities |> List.toArray }
114 |
115 | // TODO: Retry on error?
116 | try
117 | match! lspClient.ClientRegisterCapability registrationParams with
118 | | Ok _ -> ()
119 | | Error error ->
120 | logger.warn(
121 | Log.setMessage "handleInitialized: didChangeWatchedFiles registration has failed with {error}"
122 | >> Log.addContext "error" (string error)
123 | )
124 | with
125 | | ex ->
126 | logger.warn(
127 | Log.setMessage "handleInitialized: didChangeWatchedFiles registration has failed with {error}"
128 | >> Log.addContext "error" (string ex)
129 | )
130 |
131 | //
132 | // retrieve csharp settings
133 | //
134 | try
135 | let! workspaceCSharpConfig =
136 | lspClient.WorkspaceConfiguration(
137 | { Items=[| { Section=Some "csharp"; ScopeUri=None } |] })
138 |
139 | let csharpConfigTokensMaybe =
140 | match workspaceCSharpConfig with
141 | | Ok ts -> Some ts
142 | | _ -> None
143 |
144 | let newSettingsMaybe =
145 | match csharpConfigTokensMaybe with
146 | | Some [| t |] ->
147 | let csharpSettingsMaybe = t |> deserialize
148 |
149 | match csharpSettingsMaybe with
150 | | Some csharpSettings ->
151 |
152 | match csharpSettings.solution with
153 | | Some solutionPath-> Some { context.State.Settings with SolutionPath = Some solutionPath }
154 | | _ -> None
155 |
156 | | _ -> None
157 | | _ -> None
158 |
159 | // do! logMessage (sprintf "handleInitialized: newSettingsMaybe=%s" (string newSettingsMaybe))
160 |
161 | match newSettingsMaybe with
162 | | Some newSettings ->
163 | context.Emit(SettingsChange newSettings)
164 | | _ -> ()
165 | with
166 | | ex ->
167 | logger.warn(
168 | Log.setMessage "handleInitialized: could not retrieve `csharp` workspace configuration section: {error}"
169 | >> Log.addContext "error" (ex |> string)
170 | )
171 |
172 | //
173 | // start loading the solution
174 | //
175 | stateActor.Post(SolutionReloadRequest (TimeSpan.FromMilliseconds(100)))
176 |
177 | logger.trace(
178 | Log.setMessage "handleInitialized: OK")
179 |
180 | return Ok()
181 | }
182 |
183 | let handleShutdown (context: ServerRequestContext) (_: unit) : Async> = async {
184 | context.Emit(ClientCapabilityChange emptyClientCapabilities)
185 | context.Emit(ClientChange None)
186 | return Ok()
187 | }
188 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/InlineValue.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open Ionide.LanguageServerProtocol.Types
4 | open Ionide.LanguageServerProtocol.JsonRpc
5 |
6 | open CSharpLanguageServer.State
7 |
8 | []
9 | module InlineValue =
10 | let provider (clientCapabilities: ClientCapabilities) : InlineValueOptions option = None
11 |
12 | let registration (clientCapabilities: ClientCapabilities) : Registration option = None
13 |
14 | let handle (context: ServerRequestContext) (p: InlineValueParams) : AsyncLspResult =
15 | LspResult.notImplemented |> async.Return
16 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/LinkedEditingRange.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open Ionide.LanguageServerProtocol.Types
4 | open Ionide.LanguageServerProtocol.JsonRpc
5 |
6 | open CSharpLanguageServer.State
7 |
8 | []
9 | module LinkedEditingRange =
10 | let provider (clientCapabilities: ClientCapabilities) = None
11 |
12 | let registration (clientCapabilities: ClientCapabilities) : Registration option = None
13 |
14 | let handle (context: ServerRequestContext) (def: LinkedEditingRangeParams) : AsyncLspResult =
15 | LspResult.notImplemented |> async.Return
16 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Moniker.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open Ionide.LanguageServerProtocol.Types
4 | open Ionide.LanguageServerProtocol.JsonRpc
5 |
6 | open CSharpLanguageServer.State
7 |
8 | []
9 | module Moniker =
10 | let provider (_cc: ClientCapabilities) = None
11 |
12 | let registration (_cc: ClientCapabilities) : Registration option = None
13 |
14 | let handle (_context: ServerRequestContext) (_p: MonikerParams) : AsyncLspResult =
15 | LspResult.notImplemented |> async.Return
16 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/References.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Ionide.LanguageServerProtocol.Server
6 | open Ionide.LanguageServerProtocol.Types
7 | open Ionide.LanguageServerProtocol.JsonRpc
8 |
9 | open CSharpLanguageServer.State
10 | open CSharpLanguageServer.Conversions
11 | open CSharpLanguageServer.Types
12 |
13 | []
14 | module References =
15 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
16 | clientCapabilities.TextDocument
17 | |> Option.bind (fun x -> x.References)
18 | |> Option.bind (fun x -> x.DynamicRegistration)
19 | |> Option.defaultValue false
20 |
21 | let provider (clientCapabilities: ClientCapabilities) : U2 option =
22 | match dynamicRegistration clientCapabilities with
23 | | true -> None
24 | | false -> Some (U2.C1 true)
25 |
26 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
27 | match dynamicRegistration clientCapabilities with
28 | | false -> None
29 | | true ->
30 | let registerOptions: ReferenceRegistrationOptions =
31 | {
32 | DocumentSelector = Some defaultDocumentSelector
33 | WorkDoneProgress = None
34 | }
35 |
36 | Some
37 | { Id = Guid.NewGuid().ToString()
38 | Method = "textDocument/references"
39 | RegisterOptions = registerOptions |> serialize |> Some }
40 |
41 | let handle (context: ServerRequestContext) (p: ReferenceParams) : AsyncLspResult = async {
42 | match! context.FindSymbol p.TextDocument.Uri p.Position with
43 | | None -> return None |> LspResult.success
44 | | Some symbol ->
45 | let! locations = context.FindReferences symbol p.Context.IncludeDeclaration
46 |
47 | return
48 | locations
49 | |> Seq.map Location.fromRoslynLocation
50 | |> Seq.filter _.IsSome
51 | |> Seq.map _.Value
52 | |> Seq.distinct
53 | |> Seq.toArray
54 | |> Some
55 | |> LspResult.success
56 | }
57 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Rename.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 | open System.Threading
5 |
6 | open Microsoft.CodeAnalysis
7 | open Microsoft.CodeAnalysis.CSharp.Syntax
8 | open Microsoft.CodeAnalysis.FindSymbols
9 | open Microsoft.CodeAnalysis.Rename
10 | open Ionide.LanguageServerProtocol.Server
11 | open Ionide.LanguageServerProtocol.Types
12 | open Ionide.LanguageServerProtocol.JsonRpc
13 |
14 | open CSharpLanguageServer.State
15 | open CSharpLanguageServer.Logging
16 | open CSharpLanguageServer.Conversions
17 | open CSharpLanguageServer.Types
18 | open CSharpLanguageServer.Util
19 |
20 | []
21 | module Rename =
22 | let private logger = LogProvider.getLoggerByName "Rename"
23 |
24 | let private lspDocChangesFromSolutionDiff
25 | (ct: CancellationToken)
26 | (originalSolution: Solution)
27 | (updatedSolution: Solution)
28 | (tryGetDocVersionByUri: string -> int option)
29 | : Async =
30 | let getEdits
31 | (originalSolution: Solution)
32 | (updatedSolution: Solution)
33 | (docId: DocumentId)
34 | : Async = async {
35 | let originalDoc = originalSolution.GetDocument(docId)
36 | let! originalDocText = originalDoc.GetTextAsync(ct) |> Async.AwaitTask
37 | let updatedDoc = updatedSolution.GetDocument(docId)
38 | let! docChanges = updatedDoc.GetTextChangesAsync(originalDoc, ct) |> Async.AwaitTask
39 |
40 | let diffEdits: U2 array =
41 | docChanges
42 | |> Seq.sortBy (fun c -> c.Span.Start)
43 | |> Seq.map (TextEdit.fromTextChange originalDocText.Lines)
44 | |> Seq.map U2.C1
45 | |> Array.ofSeq
46 |
47 | let uri = originalDoc.FilePath |> Path.toUri
48 | let textEditDocument =
49 | { Uri = uri
50 | Version = tryGetDocVersionByUri uri }
51 |
52 | return
53 | { TextDocument = textEditDocument
54 | Edits = diffEdits }
55 | }
56 |
57 | updatedSolution.GetChanges(originalSolution).GetProjectChanges()
58 | |> Seq.collect (fun projectChange -> projectChange.GetChangedDocuments())
59 | |> Seq.map (getEdits originalSolution updatedSolution)
60 | |> Async.Parallel
61 | |> Async.map (Seq.distinct >> Array.ofSeq)
62 |
63 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
64 | clientCapabilities.TextDocument
65 | |> Option.bind (fun x -> x.Rename)
66 | |> Option.bind (fun x -> x.DynamicRegistration)
67 | |> Option.defaultValue false
68 |
69 | let private prepareSupport (clientCapabilities: ClientCapabilities) =
70 | clientCapabilities.TextDocument
71 | |> Option.bind (fun x -> x.Rename)
72 | |> Option.bind (fun x -> x.PrepareSupport)
73 | |> Option.defaultValue false
74 |
75 | let provider (clientCapabilities: ClientCapabilities): U2 option =
76 | match dynamicRegistration clientCapabilities, prepareSupport clientCapabilities with
77 | | true, _ -> None
78 | | false, true -> Some (U2.C2 { PrepareProvider = Some true; WorkDoneProgress = None })
79 | | false, false -> Some (U2.C1 true)
80 |
81 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
82 | match dynamicRegistration clientCapabilities with
83 | | false -> None
84 | | true ->
85 | let registerOptions: RenameRegistrationOptions = {
86 | PrepareProvider = Some (prepareSupport clientCapabilities)
87 | DocumentSelector = Some defaultDocumentSelector
88 | WorkDoneProgress = None
89 | }
90 | Some
91 | { Id = Guid.NewGuid().ToString()
92 | Method = "textDocument/rename"
93 | RegisterOptions = registerOptions |> serialize |> Some }
94 |
95 | let prepare (context: ServerRequestContext)
96 | (p: PrepareRenameParams)
97 | : AsyncLspResult = async {
98 | match context.GetUserDocument p.TextDocument.Uri with
99 | | None ->
100 | return None |> LspResult.success
101 | | Some doc ->
102 | let! ct = Async.CancellationToken
103 | let! docSyntaxTree = doc.GetSyntaxTreeAsync(ct) |> Async.AwaitTask
104 | let! docText = doc.GetTextAsync(ct) |> Async.AwaitTask
105 |
106 | let position = Position.toRoslynPosition docText.Lines p.Position
107 | let! symbolMaybe = SymbolFinder.FindSymbolAtPositionAsync(doc, position, ct) |> Async.AwaitTask
108 | let symbolIsFromMetadata =
109 | symbolMaybe
110 | |> Option.ofObj
111 | |> Option.map (fun s -> s.MetadataToken <> 0)
112 | |> Option.defaultValue false
113 |
114 | let linePositionSpan =
115 | Range.toLinePositionSpan docText.Lines { Start = p.Position; End = p.Position }
116 |
117 | let textSpan = docText.Lines.GetTextSpan(linePositionSpan)
118 |
119 | let! rootNode = docSyntaxTree.GetRootAsync(ct) |> Async.AwaitTask
120 | let nodeOnPos =
121 | rootNode.FindNode(textSpan, findInsideTrivia = false, getInnermostNodeForTie = true)
122 |
123 | let spanMaybe =
124 | match nodeOnPos with
125 | | :? PropertyDeclarationSyntax as propDec -> propDec.Identifier.Span |> Some
126 | | :? MethodDeclarationSyntax as methodDec -> methodDec.Identifier.Span |> Some
127 | | :? BaseTypeDeclarationSyntax as typeDec -> typeDec.Identifier.Span |> Some
128 | | :? VariableDeclaratorSyntax as varDec -> varDec.Identifier.Span |> Some
129 | | :? EnumMemberDeclarationSyntax as enumMemDec -> enumMemDec.Identifier.Span |> Some
130 | | :? ParameterSyntax as paramSyn -> paramSyn.Identifier.Span |> Some
131 | | :? NameSyntax as nameSyn -> nameSyn.Span |> Some
132 | | :? SingleVariableDesignationSyntax as designationSyn -> designationSyn.Identifier.Span |> Some
133 | | :? ForEachStatementSyntax as forEachSyn -> forEachSyn.Identifier.Span |> Some
134 | | :? LocalFunctionStatementSyntax as localFunStSyn -> localFunStSyn.Identifier.Span |> Some
135 | | node ->
136 | logger.debug (
137 | Log.setMessage "textDocument/prepareRename: unhandled Type={type}"
138 | >> Log.addContext "type" (node.GetType().Name)
139 | )
140 | None
141 |
142 | let rangeWithPlaceholderMaybe: PrepareRenameResult option =
143 | match spanMaybe, symbolIsFromMetadata with
144 | | Some span, false ->
145 | let range = Range.fromTextSpan docText.Lines span
146 |
147 | let text = docText.ToString(span)
148 |
149 | { Range = range; Placeholder = text } |> U3.C2 |> Some
150 | | _, _ -> None
151 |
152 | return rangeWithPlaceholderMaybe |> LspResult.success
153 | }
154 |
155 | let handle
156 | (context: ServerRequestContext)
157 | (p: RenameParams)
158 | : AsyncLspResult = async {
159 | match! context.FindSymbol' p.TextDocument.Uri p.Position with
160 | | None ->
161 | return None |> LspResult.success
162 | | Some (symbol, doc) ->
163 | let! ct = Async.CancellationToken
164 | let originalSolution = doc.Project.Solution
165 |
166 | let! updatedSolution =
167 | Renamer.RenameSymbolAsync(
168 | doc.Project.Solution,
169 | symbol,
170 | SymbolRenameOptions(RenameOverloads = true, RenameFile = true),
171 | p.NewName,
172 | ct
173 | )
174 | |> Async.AwaitTask
175 |
176 | let! docTextEdit =
177 | lspDocChangesFromSolutionDiff
178 | ct
179 | originalSolution
180 | updatedSolution
181 | (fun uri -> context.OpenDocs.TryFind uri |> Option.map _.Version)
182 |
183 | return WorkspaceEdit.Create(docTextEdit, context.ClientCapabilities) |> Some |> LspResult.success
184 | }
185 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/SelectionRange.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open Ionide.LanguageServerProtocol.Types
4 | open Ionide.LanguageServerProtocol.JsonRpc
5 |
6 | open CSharpLanguageServer.State
7 |
8 | []
9 | module SelectionRange =
10 | let provider (_cc: ClientCapabilities) : bool option = None
11 |
12 | let registration (_cc: ClientCapabilities) : Registration option = None
13 |
14 | let handle (_ctx: ServerRequestContext) (_p: SelectionRangeParams) : AsyncLspResult =
15 | LspResult.notImplemented |> async.Return
16 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/SemanticTokens.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 | open System.Collections.Generic
5 |
6 | open Ionide.LanguageServerProtocol.Server
7 | open Ionide.LanguageServerProtocol.Types
8 | open Ionide.LanguageServerProtocol.JsonRpc
9 | open Microsoft.CodeAnalysis.Classification
10 | open Microsoft.CodeAnalysis.Text
11 |
12 | open CSharpLanguageServer.State
13 | open CSharpLanguageServer.Util
14 | open CSharpLanguageServer.Conversions
15 | open CSharpLanguageServer.Types
16 |
17 | []
18 | module SemanticTokens =
19 | let private classificationTypeMap = Map [
20 | (ClassificationTypeNames.ClassName, "class");
21 | (ClassificationTypeNames.Comment, "comment");
22 | (ClassificationTypeNames.ConstantName, "property");
23 | (ClassificationTypeNames.ControlKeyword, "keyword");
24 | (ClassificationTypeNames.DelegateName, "class");
25 | (ClassificationTypeNames.EnumMemberName, "enumMember");
26 | (ClassificationTypeNames.EnumName, "enum");
27 | (ClassificationTypeNames.EventName, "event");
28 | (ClassificationTypeNames.ExtensionMethodName, "method");
29 | (ClassificationTypeNames.FieldName, "property");
30 | (ClassificationTypeNames.Identifier, "variable");
31 | (ClassificationTypeNames.InterfaceName, "interface");
32 | (ClassificationTypeNames.LabelName, "variable");
33 | (ClassificationTypeNames.LocalName, "variable");
34 | (ClassificationTypeNames.Keyword, "keyword");
35 | (ClassificationTypeNames.MethodName, "method");
36 | (ClassificationTypeNames.NamespaceName, "namespace");
37 | (ClassificationTypeNames.NumericLiteral, "number");
38 | (ClassificationTypeNames.Operator, "operator");
39 | (ClassificationTypeNames.OperatorOverloaded, "operator");
40 | (ClassificationTypeNames.ParameterName, "parameter");
41 | (ClassificationTypeNames.PropertyName, "property");
42 | (ClassificationTypeNames.RecordClassName, "class");
43 | (ClassificationTypeNames.RecordStructName, "struct");
44 | (ClassificationTypeNames.RegexText, "regex");
45 | (ClassificationTypeNames.StringLiteral, "string");
46 | (ClassificationTypeNames.StructName, "struct");
47 | (ClassificationTypeNames.TypeParameterName, "typeParameter");
48 | (ClassificationTypeNames.VerbatimStringLiteral, "string")
49 | ]
50 |
51 | let private classificationModifierMap = Map [
52 | (ClassificationTypeNames.StaticSymbol, "static")
53 | ]
54 |
55 | let private semanticTokenTypeMap =
56 | classificationTypeMap
57 | |> Map.values
58 | |> Seq.distinct
59 | |> flip Seq.zip (Seq.initInfinite uint32)
60 | |> Map.ofSeq
61 |
62 | let private semanticTokenModifierMap =
63 | classificationModifierMap
64 | |> Map.values
65 | |> Seq.distinct
66 | |> flip Seq.zip (Seq.initInfinite uint32)
67 | |> Map.ofSeq
68 |
69 | let private semanticTokenTypes =
70 | semanticTokenTypeMap
71 | |> Seq.sortBy (fun kvp -> kvp.Value)
72 | |> Seq.map (fun kvp -> kvp.Key)
73 |
74 | let private semanticTokenModifiers =
75 | semanticTokenModifierMap
76 | |> Seq.sortBy (fun kvp -> kvp.Value)
77 | |> Seq.map (fun kvp -> kvp.Key)
78 |
79 | let private getSemanticTokenIdFromClassification (classification: string) =
80 | classificationTypeMap
81 | |> Map.tryFind classification
82 | |> Option.bind (flip Map.tryFind semanticTokenTypeMap)
83 |
84 | let private getSemanticTokenModifierFlagFromClassification (classification: string) =
85 | classificationModifierMap
86 | |> Map.tryFind classification
87 | |> Option.bind (flip Map.tryFind semanticTokenModifierMap)
88 | |> Option.defaultValue 0u
89 | |> int32
90 | |> (<<<) 1u
91 |
92 | let private toSemanticToken (lines: TextLineCollection) (textSpan: TextSpan, spans: IEnumerable) =
93 | let (typeId, modifiers) =
94 | spans
95 | |> Seq.fold (fun (t, m) s ->
96 | if ClassificationTypeNames.AdditiveTypeNames.Contains(s.ClassificationType) then
97 | (t, m ||| (getSemanticTokenModifierFlagFromClassification s.ClassificationType))
98 | else
99 | (getSemanticTokenIdFromClassification s.ClassificationType, m)
100 | ) (None, 0u)
101 | let pos = lines.GetLinePositionSpan(textSpan)
102 | (uint32 pos.Start.Line, uint32 pos.Start.Character, uint32 (pos.End.Character - pos.Start.Character), typeId, modifiers)
103 |
104 | let private computePosition (((pLine, pChar, _, _, _), (cLine, cChar, cLen, cToken, cModifiers)): ((uint32 * uint32 * uint32 * uint32 * uint32) * (uint32 * uint32 * uint32 * uint32 * uint32))) =
105 | let deltaLine = cLine - pLine
106 | let deltaChar =
107 | if deltaLine = 0u then
108 | cChar - pChar
109 | else
110 | cChar
111 | (deltaLine, deltaChar, cLen, cToken, cModifiers)
112 |
113 | let private getSemanticTokensRange (context: ServerRequestContext) (uri: string) (range: Range option): AsyncLspResult = async {
114 | let docMaybe = context.GetUserDocument uri
115 | match docMaybe with
116 | | None ->
117 | return None |> LspResult.success
118 | | Some doc ->
119 | let! ct = Async.CancellationToken
120 | let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
121 | let textSpan =
122 | range
123 | |> Option.map (Range.toTextSpan sourceText.Lines)
124 | |> Option.defaultValue (TextSpan(0, sourceText.Length))
125 | let! spans = Classifier.GetClassifiedSpansAsync(doc, textSpan, ct) |> Async.AwaitTask
126 | let tokens =
127 | spans
128 | |> Seq.groupBy (fun span -> span.TextSpan)
129 | |> Seq.map (toSemanticToken sourceText.Lines)
130 | |> Seq.filter (fun (_, _, _, oi, _) -> Option.isSome oi)
131 | |> Seq.map (fun (line, startChar, len, tokenId, modifiers) -> (line, startChar, len, Option.get tokenId, modifiers))
132 |
133 | let response =
134 | { Data =
135 | Seq.zip (seq {yield (0u,0u,0u,0u,0u); yield! tokens}) tokens
136 | |> Seq.map computePosition
137 | |> Seq.map (fun (a,b,c,d,e) -> [a;b;c;d;e])
138 | |> Seq.concat
139 | |> Seq.toArray
140 | ResultId = None } // TODO: add a result id after we support delta semantic tokens
141 | return Some response |> LspResult.success
142 | }
143 |
144 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
145 | clientCapabilities.TextDocument
146 | |> Option.bind (fun x -> x.SemanticTokens)
147 | |> Option.bind (fun x -> x.DynamicRegistration)
148 | |> Option.defaultValue false
149 |
150 | let provider (clientCapabilities: ClientCapabilities) : U2 option =
151 | match dynamicRegistration clientCapabilities with
152 | | true -> None
153 | | false ->
154 | let semanticTokensOptions: SemanticTokensOptions =
155 | { Legend = { TokenTypes = semanticTokenTypes |> Seq.toArray
156 | TokenModifiers = semanticTokenModifiers |> Seq.toArray }
157 | Range = Some (U2.C1 true)
158 | Full = Some (U2.C1 true)
159 | WorkDoneProgress = None
160 | }
161 |
162 | Some (U2.C1 semanticTokensOptions)
163 |
164 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
165 | match dynamicRegistration clientCapabilities with
166 | | false -> None
167 | | true ->
168 | let registerOptions: SemanticTokensRegistrationOptions =
169 | { Legend = { TokenTypes = semanticTokenTypes |> Seq.toArray
170 | TokenModifiers = semanticTokenModifiers |> Seq.toArray }
171 | Range = Some (U2.C1 true)
172 | Full = Some (U2.C1 true)
173 | Id = None
174 | WorkDoneProgress = None
175 | DocumentSelector = Some defaultDocumentSelector }
176 | Some
177 | { Id = Guid.NewGuid().ToString()
178 | Method = "textDocument/semanticTokens"
179 | RegisterOptions = registerOptions |> serialize |> Some }
180 |
181 | // TODO: Everytime the server will re-compute semantic tokens, is it possible to cache the result?
182 | let handleFull (context: ServerRequestContext) (p: SemanticTokensParams): AsyncLspResult =
183 | getSemanticTokensRange context p.TextDocument.Uri None
184 |
185 | let handleFullDelta
186 | (_context: ServerRequestContext)
187 | (_p: SemanticTokensDeltaParams)
188 | : AsyncLspResult option> =
189 | LspResult.notImplemented option>
190 | |> async.Return
191 |
192 | let handleRange (context: ServerRequestContext) (p: SemanticTokensRangeParams): AsyncLspResult =
193 | getSemanticTokensRange context p.TextDocument.Uri (Some p.Range)
194 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/SignatureHelp.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Microsoft.CodeAnalysis
6 | open Microsoft.CodeAnalysis.CSharp
7 | open Microsoft.CodeAnalysis.CSharp.Syntax
8 | open Ionide.LanguageServerProtocol.Server
9 | open Ionide.LanguageServerProtocol.Types
10 | open Ionide.LanguageServerProtocol.JsonRpc
11 |
12 | open CSharpLanguageServer
13 | open CSharpLanguageServer.State
14 | open CSharpLanguageServer.Util
15 | open CSharpLanguageServer.Conversions
16 | open CSharpLanguageServer.Types
17 |
18 | module SignatureInformation =
19 | let internal fromMethod (m: IMethodSymbol) =
20 | let parameters =
21 | m.Parameters
22 | |> Seq.map (fun p ->
23 | { Label = SymbolName.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat p |> U2.C1
24 | Documentation = None })
25 | |> Array.ofSeq
26 |
27 | let documentation =
28 | { Kind = MarkupKind.Markdown
29 | Value = DocumentationUtil.markdownDocForSymbol m }
30 | |> U2.C2
31 |
32 | { Label = SymbolName.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat m
33 | Documentation = Some documentation
34 | Parameters = Some parameters
35 | ActiveParameter = None }
36 |
37 | []
38 | module SignatureHelp =
39 | type InvocationContext =
40 | { Receiver: SyntaxNode
41 | ArgumentTypes: TypeInfo list
42 | Separators: SyntaxToken list }
43 |
44 | // Algorithm from omnisharp-roslyn (https://github.com/OmniSharp/omnisharp-roslyn/blob/2d582b05839dbd23baf6e78fa2279163723a824c/src/OmniSharp.Roslyn.CSharp/Services/Signatures/SignatureHelpService.cs#L139C1-L166C10)
45 | let private methodScore (types: TypeInfo list) (m: IMethodSymbol) =
46 | let score (invocation: TypeInfo) (definition: IParameterSymbol) =
47 | if isNull invocation.ConvertedType then
48 | 1
49 | else if SymbolEqualityComparer.Default.Equals(invocation.ConvertedType, definition.Type) then
50 | 2
51 | else
52 | 0
53 |
54 | if m.Parameters.Length < types.Length then
55 | Microsoft.FSharp.Core.int.MinValue
56 | else
57 | Seq.zip types m.Parameters |> Seq.map (uncurry score) |> Seq.sum
58 |
59 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
60 | clientCapabilities.TextDocument
61 | |> Option.bind (fun x -> x.SignatureHelp)
62 | |> Option.bind (fun x -> x.DynamicRegistration)
63 | |> Option.defaultValue false
64 |
65 | let provider (clientCapabilities: ClientCapabilities) : SignatureHelpOptions option =
66 | match dynamicRegistration clientCapabilities with
67 | | true -> None
68 | | false ->
69 | Some
70 | { TriggerCharacters = Some([| "("; ","; "<"; "{"; "[" |])
71 | WorkDoneProgress = None
72 | RetriggerCharacters = None }
73 |
74 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
75 | match dynamicRegistration clientCapabilities with
76 | | false -> None
77 | | true ->
78 | let registerOptions: SignatureHelpRegistrationOptions =
79 | { TriggerCharacters = Some([| "("; ","; "<"; "{"; "[" |])
80 | RetriggerCharacters = None
81 | WorkDoneProgress = None
82 | DocumentSelector = Some defaultDocumentSelector }
83 | Some
84 | { Id = Guid.NewGuid().ToString()
85 | Method = "textDocument/signatureHelp"
86 | RegisterOptions = registerOptions |> serialize |> Some }
87 |
88 | let handle (context: ServerRequestContext) (p: SignatureHelpParams): AsyncLspResult = async {
89 | let docMaybe = context.GetUserDocument p.TextDocument.Uri
90 | match docMaybe with
91 | | None -> return None |> LspResult.success
92 | | Some doc ->
93 | let! ct = Async.CancellationToken
94 | let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
95 | let! semanticModel = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask
96 |
97 | let position = Position.toRoslynPosition sourceText.Lines p.Position
98 |
99 | let! syntaxTree = doc.GetSyntaxTreeAsync(ct) |> Async.AwaitTask
100 | let! root = syntaxTree.GetRootAsync(ct) |> Async.AwaitTask
101 |
102 | let rec findInvocationContext (node: SyntaxNode): InvocationContext option =
103 | match node with
104 | | :? InvocationExpressionSyntax as invocation when invocation.ArgumentList.Span.Contains(position) ->
105 | Some { Receiver = invocation.Expression
106 | ArgumentTypes = invocation.ArgumentList.Arguments |> Seq.map (fun a -> semanticModel.GetTypeInfo(a.Expression)) |> List.ofSeq
107 | Separators = invocation.ArgumentList.Arguments.GetSeparators() |> List.ofSeq }
108 |
109 | | :? BaseObjectCreationExpressionSyntax as objectCreation when
110 | objectCreation.ArgumentList
111 | |> Option.ofObj
112 | |> Option.map (fun argList -> argList.Span.Contains(position))
113 | |> Option.defaultValue false
114 | ->
115 | Some { Receiver = objectCreation
116 | ArgumentTypes = objectCreation.ArgumentList.Arguments |> Seq.map (fun a -> semanticModel.GetTypeInfo(a.Expression)) |> List.ofSeq
117 | Separators = objectCreation.ArgumentList.Arguments.GetSeparators() |> List.ofSeq }
118 |
119 | | :? AttributeSyntax as attributeSyntax when
120 | attributeSyntax.ArgumentList
121 | |> Option.ofObj
122 | |> Option.map (fun argList -> argList.Span.Contains(position))
123 | |> Option.defaultValue false
124 | ->
125 | Some { Receiver = attributeSyntax
126 | ArgumentTypes = attributeSyntax.ArgumentList.Arguments |> Seq.map (fun a -> semanticModel.GetTypeInfo(a.Expression)) |> List.ofSeq
127 | Separators = attributeSyntax.ArgumentList.Arguments.GetSeparators() |> List.ofSeq }
128 |
129 | | _ ->
130 | node
131 | |> Option.ofObj
132 | |> Option.bind (fun node -> findInvocationContext node.Parent)
133 |
134 | match root.FindToken(position).Parent |> findInvocationContext with
135 | | None -> return None |> LspResult.success
136 | | Some invocation ->
137 | let methodGroup =
138 | semanticModel.GetMemberGroup(invocation.Receiver).OfType()
139 | |> List.ofSeq
140 |
141 | let matchingMethodMaybe =
142 | methodGroup
143 | |> Seq.map (fun m -> m, methodScore (invocation.ArgumentTypes) m)
144 | |> Seq.tryMaxBy snd
145 | |> Option.map fst
146 |
147 | let activeParameterMaybe =
148 | invocation.Separators
149 | |> List.tryFindIndex (fun comma -> comma.Span.Start >= position)
150 | |> Option.map uint
151 |
152 | let signatureHelpResult =
153 | { Signatures = methodGroup |> Seq.map SignatureInformation.fromMethod |> Array.ofSeq
154 | ActiveSignature = matchingMethodMaybe |> Option.map (fun m -> List.findIndex ((=) m) methodGroup |> uint32)
155 | ActiveParameter = activeParameterMaybe }
156 |
157 | return Some signatureHelpResult |> LspResult.success
158 | }
159 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/TextDocumentSync.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Microsoft.CodeAnalysis.Text
6 | open Ionide.LanguageServerProtocol.Server
7 | open Ionide.LanguageServerProtocol.Types
8 | open Ionide.LanguageServerProtocol.JsonRpc
9 |
10 | open CSharpLanguageServer
11 | open CSharpLanguageServer.Conversions
12 | open CSharpLanguageServer.State
13 | open CSharpLanguageServer.State.ServerState
14 | open CSharpLanguageServer.Types
15 | open CSharpLanguageServer.RoslynHelpers
16 | open CSharpLanguageServer.Logging
17 |
18 | []
19 | module TextDocumentSync =
20 | let private logger = LogProvider.getLoggerByName "TextDocumentSync"
21 |
22 | let private applyLspContentChangesOnRoslynSourceText
23 | (changes: TextDocumentContentChangeEvent[])
24 | (initialSourceText: SourceText) =
25 |
26 | let applyLspContentChangeOnRoslynSourceText (sourceText: SourceText) (change: TextDocumentContentChangeEvent) =
27 | match change with
28 | | U2.C1 change ->
29 | let changeTextSpan =
30 | change.Range
31 | |> Range.toLinePositionSpan sourceText.Lines
32 | |> sourceText.Lines.GetTextSpan
33 |
34 | TextChange(changeTextSpan, change.Text) |> sourceText.WithChanges
35 | | U2.C2 changeWoRange ->
36 | SourceText.From(changeWoRange.Text)
37 |
38 | changes |> Seq.fold applyLspContentChangeOnRoslynSourceText initialSourceText
39 |
40 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
41 | clientCapabilities.TextDocument
42 | |> Option.bind (fun x -> x.Synchronization)
43 | |> Option.bind (fun x -> x.DynamicRegistration)
44 | |> Option.defaultValue false
45 |
46 | let provider (clientCapabilities: ClientCapabilities) : TextDocumentSyncOptions option =
47 | match dynamicRegistration clientCapabilities with
48 | | true -> None
49 | | false ->
50 | Some
51 | { TextDocumentSyncOptions.Default with
52 | OpenClose = Some true
53 | Save = Some (U2.C2 { IncludeText = Some true })
54 | Change = Some TextDocumentSyncKind.Incremental }
55 |
56 | let didOpenRegistration (clientCapabilities: ClientCapabilities) : Registration option =
57 | match dynamicRegistration clientCapabilities with
58 | | false -> None
59 | | true ->
60 | let registerOptions = {
61 | DocumentSelector = Some defaultDocumentSelector }
62 |
63 | Some
64 | { Id = Guid.NewGuid().ToString()
65 | Method = "textDocument/didOpen"
66 | RegisterOptions = registerOptions |> serialize |> Some }
67 |
68 |
69 | let didChangeRegistration (clientCapabilities: ClientCapabilities) : Registration option =
70 | match dynamicRegistration clientCapabilities with
71 | | false -> None
72 | | true ->
73 | let registerOptions =
74 | { DocumentSelector = Some defaultDocumentSelector
75 | SyncKind = TextDocumentSyncKind.Incremental }
76 | Some
77 | { Id = Guid.NewGuid().ToString()
78 | Method = "textDocument/didChange"
79 | RegisterOptions = registerOptions|> serialize |> Some }
80 |
81 | let didSaveRegistration (clientCapabilities: ClientCapabilities) : Registration option =
82 | match dynamicRegistration clientCapabilities with
83 | | false -> None
84 | | true ->
85 | let registerOptions =
86 | { DocumentSelector = Some defaultDocumentSelector
87 | IncludeText = Some true }
88 |
89 | Some
90 | { Id = Guid.NewGuid().ToString()
91 | Method = "textDocument/didSave"
92 | RegisterOptions = registerOptions |> serialize |> Some }
93 |
94 | let didCloseRegistration (clientCapabilities: ClientCapabilities) : Registration option =
95 | match dynamicRegistration clientCapabilities with
96 | | false -> None
97 | | true ->
98 | let registerOptions = {
99 | DocumentSelector = Some defaultDocumentSelector }
100 |
101 | Some
102 | { Id = Guid.NewGuid().ToString()
103 | Method = "textDocument/didClose"
104 | RegisterOptions = registerOptions |> serialize |> Some }
105 |
106 | let willSaveRegistration (_clientCapabilities: ClientCapabilities) : Registration option = None
107 |
108 | let willSaveWaitUntilRegistration (_clientCapabilities: ClientCapabilities) : Registration option = None
109 |
110 | let didOpen (context: ServerRequestContext)
111 | (openParams: DidOpenTextDocumentParams)
112 | : Async> =
113 | match context.GetDocumentForUriOfType AnyDocument openParams.TextDocument.Uri with
114 | | Some (doc, docType) ->
115 | match docType with
116 | | UserDocument ->
117 | // we want to load the document in case it has been changed since we have the solution loaded
118 | // also, as a bonus we can recover from corrupted document view in case document in roslyn solution
119 | // went out of sync with editor
120 | let updatedDoc = SourceText.From(openParams.TextDocument.Text) |> doc.WithText
121 |
122 | context.Emit(OpenDocAdd (openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now))
123 | context.Emit(SolutionChange updatedDoc.Project.Solution)
124 |
125 | Ok() |> async.Return
126 |
127 | | _ ->
128 | Ok() |> async.Return
129 |
130 | | None ->
131 | let docFilePathMaybe = Util.tryParseFileUri openParams.TextDocument.Uri
132 |
133 | match docFilePathMaybe with
134 | | Some docFilePath -> async {
135 | // ok, this document is not in solution, register a new document
136 | let! newDocMaybe =
137 | tryAddDocument
138 | logger
139 | docFilePath
140 | openParams.TextDocument.Text
141 | context.Solution
142 |
143 | match newDocMaybe with
144 | | Some newDoc ->
145 | context.Emit(OpenDocAdd (openParams.TextDocument.Uri, openParams.TextDocument.Version, DateTime.Now))
146 | context.Emit(SolutionChange newDoc.Project.Solution)
147 |
148 | | None -> ()
149 |
150 | return Ok()
151 | }
152 |
153 | | None ->
154 | Ok() |> async.Return
155 |
156 | let didChange (context: ServerRequestContext)
157 | (changeParams: DidChangeTextDocumentParams)
158 | : Async> =
159 | async {
160 | let docMaybe = context.GetUserDocument changeParams.TextDocument.Uri
161 | match docMaybe with
162 | | None -> ()
163 | | Some doc ->
164 | let! ct = Async.CancellationToken
165 | let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
166 | //logMessage (sprintf "TextDocumentDidChange: changeParams: %s" (string changeParams))
167 | //logMessage (sprintf "TextDocumentDidChange: sourceText: %s" (string sourceText))
168 |
169 | let updatedSourceText = sourceText |> applyLspContentChangesOnRoslynSourceText changeParams.ContentChanges
170 | let updatedDoc = doc.WithText(updatedSourceText)
171 |
172 | //logMessage (sprintf "TextDocumentDidChange: newSourceText: %s" (string updatedSourceText))
173 |
174 | let updatedSolution = updatedDoc.Project.Solution
175 |
176 | context.Emit(SolutionChange updatedSolution)
177 | context.Emit(OpenDocAdd (changeParams.TextDocument.Uri, changeParams.TextDocument.Version, DateTime.Now))
178 |
179 | return Ok()
180 | }
181 |
182 | let didClose (context: ServerRequestContext)
183 | (closeParams: DidCloseTextDocumentParams)
184 | : Async> =
185 | context.Emit(OpenDocRemove closeParams.TextDocument.Uri)
186 | Ok() |> async.Return
187 |
188 | let willSave (_context: ServerRequestContext) (_p: WillSaveTextDocumentParams): Async> = async {
189 | return Ok ()
190 | }
191 |
192 | let willSaveWaitUntil (_context: ServerRequestContext) (_p: WillSaveTextDocumentParams): AsyncLspResult = async {
193 | return LspResult.notImplemented
194 | }
195 |
196 | let didSave (context: ServerRequestContext)
197 | (saveParams: DidSaveTextDocumentParams)
198 | : Async> =
199 | // we need to add this file to solution if not already
200 | let doc = context.GetDocument saveParams.TextDocument.Uri
201 |
202 | match doc with
203 | | Some _ ->
204 | Ok() |> async.Return
205 |
206 | | None -> async {
207 | let docFilePath = Util.parseFileUri saveParams.TextDocument.Uri
208 | let! newDocMaybe =
209 | tryAddDocument
210 | logger
211 | docFilePath
212 | saveParams.Text.Value
213 | context.Solution
214 |
215 | match newDocMaybe with
216 | | Some newDoc ->
217 | context.Emit(OpenDocTouch (saveParams.TextDocument.Uri, DateTime.Now))
218 | context.Emit(SolutionChange newDoc.Project.Solution)
219 |
220 | | None -> ()
221 |
222 | return Ok()
223 | }
224 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/TypeDefinition.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Microsoft.CodeAnalysis
6 | open Ionide.LanguageServerProtocol.Server
7 | open Ionide.LanguageServerProtocol.Types
8 | open Ionide.LanguageServerProtocol.JsonRpc
9 |
10 | open CSharpLanguageServer.Types
11 | open CSharpLanguageServer.State
12 | open CSharpLanguageServer.Util
13 |
14 | []
15 | module TypeDefinition =
16 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) : bool =
17 | clientCapabilities.TextDocument
18 | |> Option.bind (fun x -> x.TypeDefinition)
19 | |> Option.bind (fun x -> x.DynamicRegistration)
20 | |> Option.defaultValue false
21 |
22 | let provider (clientCapabilities: ClientCapabilities) : U3 option =
23 | match dynamicRegistration clientCapabilities with
24 | | true -> None
25 | | false -> Some (U3.C1 true)
26 |
27 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
28 | match dynamicRegistration clientCapabilities with
29 | | false -> None
30 | | true ->
31 | let registerOptions: TypeDefinitionRegistrationOptions =
32 | { DocumentSelector = Some defaultDocumentSelector
33 | Id = None
34 | WorkDoneProgress = None }
35 | Some {
36 | Id = Guid.NewGuid().ToString()
37 | Method = "textDocument/typeDefinition"
38 | RegisterOptions = registerOptions |> serialize |> Some }
39 |
40 | let handle (context: ServerRequestContext) (p: TypeDefinitionParams) : Async option>> = async {
41 | match! context.FindSymbol' p.TextDocument.Uri p.Position with
42 | | None ->
43 | return None |> LspResult.success
44 | | Some (symbol, doc) ->
45 | let typeSymbol =
46 | match symbol with
47 | | :? ILocalSymbol as localSymbol -> [localSymbol.Type]
48 | | :? IFieldSymbol as fieldSymbol -> [fieldSymbol.Type]
49 | | :? IPropertySymbol as propertySymbol -> [propertySymbol.Type]
50 | | :? IParameterSymbol as parameterSymbol -> [parameterSymbol.Type]
51 | | _ -> []
52 | let! locations =
53 | typeSymbol
54 | |> Seq.map (flip context.ResolveSymbolLocations (Some doc.Project))
55 | |> Async.Parallel
56 | |> Async.map (Seq.collect id >> Seq.toArray)
57 | return
58 | locations
59 | |> Declaration.C2
60 | |> U2.C1
61 | |> Some
62 | |> LspResult.success
63 | }
64 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/TypeHierarchy.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Microsoft.CodeAnalysis
6 | open Ionide.LanguageServerProtocol.Server
7 | open Ionide.LanguageServerProtocol.Types
8 | open Ionide.LanguageServerProtocol.JsonRpc
9 |
10 | open CSharpLanguageServer.Types
11 | open CSharpLanguageServer.State
12 | open CSharpLanguageServer.Conversions
13 | open CSharpLanguageServer.Util
14 |
15 | []
16 | module TypeHierarchy =
17 | let private isTypeSymbol (symbol: ISymbol) =
18 | match symbol with
19 | | :? INamedTypeSymbol -> true
20 | | _ -> false
21 |
22 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
23 | clientCapabilities.TextDocument
24 | |> Option.bind (fun x -> x.TypeHierarchy)
25 | |> Option.bind (fun x -> x.DynamicRegistration)
26 | |> Option.defaultValue false
27 |
28 | let provider (clientCapabilities: ClientCapabilities) : U3 option =
29 | match dynamicRegistration clientCapabilities with
30 | | true -> None
31 | | false -> Some (U3.C1 true)
32 |
33 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
34 | match dynamicRegistration clientCapabilities with
35 | | false -> None
36 | | true ->
37 | let registerOptions: TypeHierarchyRegistrationOptions =
38 | { DocumentSelector = Some defaultDocumentSelector
39 | Id = None
40 | WorkDoneProgress = None }
41 | Some
42 | { Id = Guid.NewGuid().ToString()
43 | Method = "textDocument/prepareTypeHierarchy"
44 | RegisterOptions = registerOptions |> serialize |> Some }
45 |
46 | let prepare (context: ServerRequestContext) (p: TypeHierarchyPrepareParams) : AsyncLspResult = async {
47 | match! context.FindSymbol p.TextDocument.Uri p.Position with
48 | | Some symbol when isTypeSymbol symbol ->
49 | let! itemList = TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations symbol
50 | return itemList |> List.toArray |> Some |> LspResult.success
51 | | _ ->
52 | return None |> LspResult.success
53 | }
54 |
55 | let supertypes
56 | (context: ServerRequestContext)
57 | (p: TypeHierarchySupertypesParams)
58 | : AsyncLspResult = async {
59 | match! context.FindSymbol p.Item.Uri p.Item.Range.Start with
60 | | Some symbol when isTypeSymbol symbol ->
61 | let typeSymbol = symbol :?> INamedTypeSymbol
62 | let baseType =
63 | typeSymbol.BaseType
64 | |> Option.ofObj
65 | |> Option.filter (fun sym -> sym.SpecialType = SpecialType.None)
66 | |> Option.toList
67 | let interfaces = Seq.toList typeSymbol.Interfaces
68 | let supertypes = baseType @ interfaces
69 | let! items = supertypes |> Seq.map (TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations) |> Async.Parallel
70 | return items |> Seq.collect id |> Seq.toArray |> Some |> LspResult.success
71 | | _ -> return None |> LspResult.success
72 | }
73 |
74 | let subtypes (context: ServerRequestContext) (p: TypeHierarchySubtypesParams) : AsyncLspResult = async {
75 | match! context.FindSymbol p.Item.Uri p.Item.Range.Start with
76 | | Some symbol when isTypeSymbol symbol ->
77 | let typeSymbol = symbol :?> INamedTypeSymbol
78 | // We only want immediately derived classes/interfaces/implementations here (we only need
79 | // subclasses not subclasses' subclasses)
80 | let! subtypes =
81 | [ context.FindDerivedClasses' typeSymbol false
82 | context.FindDerivedInterfaces' typeSymbol false
83 | context.FindImplementations' typeSymbol false ]
84 | |> Async.Parallel
85 | |> Async.map (Seq.collect id >> Seq.toList)
86 | let! items = subtypes |> Seq.map (TypeHierarchyItem.fromSymbol context.ResolveSymbolLocations) |> Async.Parallel
87 | return items |> Seq.collect id |> Seq.toArray |> Some |> LspResult.success
88 | | _ ->
89 | return None |> LspResult.success
90 | }
91 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/Workspace.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 | open System.IO
5 |
6 | open Ionide.LanguageServerProtocol.Types
7 | open Ionide.LanguageServerProtocol.JsonRpc
8 | open Ionide.LanguageServerProtocol.Server
9 | open Microsoft.CodeAnalysis.Text
10 |
11 | open CSharpLanguageServer
12 | open CSharpLanguageServer.State
13 | open CSharpLanguageServer.State.ServerState
14 | open CSharpLanguageServer.RoslynHelpers
15 | open CSharpLanguageServer.Logging
16 | open CSharpLanguageServer.Types
17 |
18 | []
19 | module Workspace =
20 | let private logger = LogProvider.getLoggerByName "Workspace"
21 |
22 | let dynamicRegistration (clientCapabilities: ClientCapabilities) =
23 | clientCapabilities.Workspace
24 | |> Option.bind (fun x -> x.DidChangeWatchedFiles)
25 | |> Option.bind (fun x -> x.DynamicRegistration)
26 | |> Option.defaultValue false
27 |
28 | let registration (clientCapabilities: ClientCapabilities): Registration option =
29 | match dynamicRegistration clientCapabilities with
30 | | false -> None
31 | | true ->
32 | let fileSystemWatcher =
33 | { GlobPattern = U2.C1 "**/*.{cs,csproj,sln,slnx}"
34 | Kind = Some (WatchKind.Create ||| WatchKind.Change ||| WatchKind.Delete) }
35 |
36 | let registerOptions: DidChangeWatchedFilesRegistrationOptions =
37 | { Watchers = [| fileSystemWatcher |] }
38 |
39 | Some
40 | { Id = Guid.NewGuid().ToString()
41 | Method = "workspace/didChangeWatchedFiles"
42 | RegisterOptions = registerOptions |> serialize |> Some }
43 |
44 | let private tryReloadDocumentOnUri logger (context: ServerRequestContext) uri = async {
45 | match context.GetUserDocument uri with
46 | | Some doc ->
47 | let fileText = uri |> Util.parseFileUri |> File.ReadAllText
48 | let updatedDoc = SourceText.From(fileText) |> doc.WithText
49 |
50 | context.Emit(SolutionChange updatedDoc.Project.Solution)
51 |
52 | | None ->
53 | let docFilePathMaybe = uri |> Util.tryParseFileUri
54 | match docFilePathMaybe with
55 | | Some docFilePath ->
56 | // ok, this document is not on solution, register a new one
57 | let fileText = docFilePath |> File.ReadAllText
58 | let! newDocMaybe = tryAddDocument logger
59 | docFilePath
60 | fileText
61 | context.Solution
62 | match newDocMaybe with
63 | | Some newDoc ->
64 | context.Emit(SolutionChange newDoc.Project.Solution)
65 | | None -> ()
66 | | None -> ()
67 | }
68 |
69 | let private removeDocument (context: ServerRequestContext) uri =
70 | match context.GetUserDocument uri with
71 | | Some existingDoc ->
72 | let updatedProject = existingDoc.Project.RemoveDocument(existingDoc.Id)
73 |
74 | context.Emit(SolutionChange updatedProject.Solution)
75 | context.Emit(OpenDocRemove uri)
76 | | None -> ()
77 |
78 | let didChangeWatchedFiles (context: ServerRequestContext)
79 | (p: DidChangeWatchedFilesParams)
80 | : Async> = async {
81 | for change in p.Changes do
82 | match Path.GetExtension(change.Uri) with
83 | | ".csproj" ->
84 | do! context.WindowShowMessage "change to .csproj detected, will reload solution"
85 | context.Emit(SolutionReloadRequest (TimeSpan.FromSeconds(5:int64)))
86 |
87 | | ".sln" | ".slnx" ->
88 | do! context.WindowShowMessage "change to .sln detected, will reload solution"
89 | context.Emit(SolutionReloadRequest (TimeSpan.FromSeconds(5:int64)))
90 |
91 | | ".cs" ->
92 | match change.Type with
93 | | FileChangeType.Created ->
94 | do! tryReloadDocumentOnUri logger context change.Uri
95 | | FileChangeType.Changed ->
96 | do! tryReloadDocumentOnUri logger context change.Uri
97 | | FileChangeType.Deleted ->
98 | do removeDocument context change.Uri
99 | | _ -> ()
100 |
101 | | _ -> ()
102 |
103 | return Ok()
104 | }
105 |
106 | let didChangeConfiguration (context: ServerRequestContext)
107 | (configParams: DidChangeConfigurationParams)
108 | : Async> = async {
109 |
110 | let csharpSettings =
111 | configParams.Settings
112 | |> deserialize
113 | |> (fun x -> x.csharp)
114 | |> Option.defaultValue ServerSettingsCSharpDto.Default
115 |
116 | let newServerSettings = {
117 | context.State.Settings with
118 | SolutionPath = csharpSettings.solution
119 | ApplyFormattingOptions = csharpSettings.applyFormattingOptions
120 | |> Option.defaultValue false
121 | }
122 |
123 | context.Emit(SettingsChange newServerSettings)
124 |
125 | return Ok()
126 | }
127 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Handlers/WorkspaceSymbol.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Handlers
2 |
3 | open System
4 |
5 | open Microsoft.CodeAnalysis
6 | open Ionide.LanguageServerProtocol.Server
7 | open Ionide.LanguageServerProtocol.Types
8 | open Ionide.LanguageServerProtocol.JsonRpc
9 |
10 | open CSharpLanguageServer.State
11 | open CSharpLanguageServer.Conversions
12 |
13 | []
14 | module WorkspaceSymbol =
15 | let private dynamicRegistration (clientCapabilities: ClientCapabilities) =
16 | clientCapabilities.TextDocument
17 | |> Option.bind (fun x -> x.Formatting)
18 | |> Option.bind (fun x -> x.DynamicRegistration)
19 | |> Option.defaultValue false
20 |
21 | let provider (clientCapabilities: ClientCapabilities) : U2 option =
22 | match dynamicRegistration clientCapabilities with
23 | | true -> None
24 | | false -> U2.C1 true |> Some
25 |
26 | let registration (clientCapabilities: ClientCapabilities) : Registration option =
27 | match dynamicRegistration clientCapabilities with
28 | | false -> None
29 | | true ->
30 | let registerOptions: WorkspaceSymbolRegistrationOptions =
31 | { ResolveProvider = Some true
32 | WorkDoneProgress = None
33 | }
34 |
35 | Some
36 | { Id = Guid.NewGuid().ToString()
37 | Method = "workspace/symbol"
38 | RegisterOptions = registerOptions |> serialize |> Some }
39 |
40 | let handle (context: ServerRequestContext) (p: WorkspaceSymbolParams) : AsyncLspResult option> = async {
41 | let pattern =
42 | if String.IsNullOrEmpty(p.Query) then
43 | None
44 | else
45 | Some p.Query
46 | let! symbols = context.FindSymbols pattern
47 | return
48 | symbols
49 | |> Seq.map (SymbolInformation.fromSymbol SymbolDisplayFormat.MinimallyQualifiedFormat)
50 | |> Seq.collect id
51 | // TODO: make 100 configurable?
52 | |> Seq.truncate 100
53 | |> Seq.toArray
54 | |> U2.C1
55 | |> Some
56 | |> LspResult.success
57 | }
58 |
59 | let resolve (_context: ServerRequestContext) (_p: WorkspaceSymbol) : AsyncLspResult =
60 | LspResult.notImplemented |> async.Return
61 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/LruCache.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer
2 |
3 | // It's more OO than functional, but it makes user happier than a functional & immutable version.
4 | // In LSP, there will not be lot of cocurrent requests, so a simple cache based on LRU should be enough, and the size is
5 | // usually small.
6 | type LruCache<'entry>(size: int) =
7 |
8 | let lockObj : obj = new obj()
9 |
10 | let mutable key = 0UL
11 |
12 | let mutable list : (uint64 * 'entry) list = list.Empty
13 |
14 | member this.add(entry: 'entry) : uint64 =
15 | lock lockObj (fun () ->
16 | let k = key
17 | key <- key + 1UL
18 | list <- List.append list [(k, entry)]
19 | if List.length list > size then
20 | list <- List.tail list
21 | k)
22 |
23 | member this.get(key: uint64) : 'entry option =
24 | lock lockObj (fun () -> List.tryFind (fun (k, _) -> k = key) list |> Option.map snd)
25 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Lsp/Client.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer.Lsp
2 |
3 | open Ionide.LanguageServerProtocol
4 | open Ionide.LanguageServerProtocol.Server
5 |
6 | type CSharpLspClient(sendServerNotification: ClientNotificationSender, sendServerRequest: ClientRequestSender) =
7 | inherit LspClient()
8 |
9 | override __.WindowShowMessage(p) =
10 | sendServerNotification "window/showMessage" (box p) |> Async.Ignore
11 |
12 | // TODO: Send notifications / requests to client only if client support it
13 |
14 | override __.WindowShowMessageRequest(p) =
15 | sendServerRequest.Send "window/showMessageRequest" (box p)
16 |
17 | override __.WindowLogMessage(p) =
18 | sendServerNotification "window/logMessage" (box p) |> Async.Ignore
19 |
20 | override __.TelemetryEvent(p) =
21 | sendServerNotification "telemetry/event" (box p) |> Async.Ignore
22 |
23 | override __.ClientRegisterCapability(p) =
24 | sendServerRequest.Send "client/registerCapability" (box p)
25 |
26 | override __.ClientUnregisterCapability(p) =
27 | sendServerRequest.Send "client/unregisterCapability" (box p)
28 |
29 | override __.WorkspaceWorkspaceFolders() =
30 | sendServerRequest.Send "workspace/workspaceFolders" ()
31 |
32 | override __.WorkspaceConfiguration(p) =
33 | sendServerRequest.Send "workspace/configuration" (box p)
34 |
35 | override __.WorkspaceApplyEdit(p) =
36 | sendServerRequest.Send "workspace/applyEdit" (box p)
37 |
38 | override __.WorkspaceSemanticTokensRefresh() =
39 | sendServerNotification "workspace/semanticTokens/refresh" ()
40 |
41 | override __.TextDocumentPublishDiagnostics(p) =
42 | sendServerNotification "textDocument/publishDiagnostics" (box p) |> Async.Ignore
43 |
44 | override __.WindowWorkDoneProgressCreate(createParams) =
45 | sendServerRequest.Send "window/workDoneProgress/create" (box createParams)
46 |
47 | override __.Progress(progressParams) =
48 | sendServerNotification "$/progress" (box progressParams) |> Async.Ignore
49 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Options.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer
2 |
3 | module Options =
4 | open Argu
5 |
6 | type CLIArguments =
7 | | [] Version
8 | | [] LogLevel of level:string
9 | | [] Solution of solution:string
10 | with
11 | interface IArgParserTemplate with
12 | member s.Usage =
13 | match s with
14 | | Version -> "display versioning information"
15 | | Solution _ -> ".sln file to load (relative to CWD)"
16 | | LogLevel _ -> "log level, ; default is `log`"
17 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Program.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Program
2 |
3 | open System
4 | open System.Reflection
5 |
6 | open Argu
7 | open Serilog
8 | open Serilog.Core
9 | open Serilog.Events
10 |
11 | open CSharpLanguageServer.Types
12 | open CSharpLanguageServer.Lsp
13 |
14 | []
15 | let entry args =
16 | try
17 | let argParser = ArgumentParser.Create(programName = "csharp-ls")
18 | let serverArgs = argParser.Parse args
19 |
20 | serverArgs.TryGetResult(<@ Options.CLIArguments.Version @>)
21 | |> Option.iter (fun _ -> printfn "csharp-ls, %s"
22 | (Assembly.GetExecutingAssembly().GetName().Version |> string)
23 | exit 0)
24 |
25 | let logLevelArg =
26 | serverArgs.TryGetResult(<@ Options.CLIArguments.LogLevel @>)
27 | |> Option.defaultValue "log"
28 |
29 | let logLevel =
30 | match logLevelArg with
31 | | "error" -> LogEventLevel.Error
32 | | "warning" -> LogEventLevel.Warning
33 | | "info" -> LogEventLevel.Information
34 | | "log" -> LogEventLevel.Verbose
35 | | _ -> LogEventLevel.Information
36 |
37 | let logConfig =
38 | LoggerConfiguration()
39 | .MinimumLevel.ControlledBy(LoggingLevelSwitch(logLevel))
40 | .Enrich.FromLogContext()
41 | .WriteTo.Async(fun conf ->
42 | conf.Console(
43 | outputTemplate =
44 | "[{Timestamp:HH:mm:ss.fff} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}",
45 | // Redirect all logs to stderr since stdout is used to communicate with client.
46 | standardErrorFromLevel = Nullable<_>(LogEventLevel.Verbose),
47 | theme = Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme.Code
48 | )
49 | |> ignore)
50 |
51 | Log.Logger <- logConfig.CreateLogger()
52 |
53 | let settings = {
54 | ServerSettings.Default with
55 | SolutionPath = serverArgs.TryGetResult(<@ Options.CLIArguments.Solution @>)
56 | LogLevel = logLevelArg
57 | }
58 |
59 | Server.start settings
60 | with
61 | | :? ArguParseException as ex ->
62 | printfn "%s" ex.Message
63 |
64 | match ex.ErrorCode with
65 | | ErrorCode.HelpText -> 0
66 | | _ -> 1 // Unrecognised arguments
67 |
68 | | e ->
69 | printfn "Server crashing error - %s \n %s" e.Message e.StackTrace
70 | 3
71 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/ProgressReporter.fs:
--------------------------------------------------------------------------------
1 | namespace CSharpLanguageServer
2 |
3 | open System
4 | open Ionide.LanguageServerProtocol
5 | open Ionide.LanguageServerProtocol.Server
6 | open Ionide.LanguageServerProtocol.Types
7 |
8 | type ProgressReporter(client: ILspClient) =
9 | let mutable canReport = false
10 |
11 | let mutable endSent = false
12 |
13 | member val Token = ProgressToken.C2 (Guid.NewGuid().ToString())
14 |
15 | member this.Begin(title, ?cancellable, ?message, ?percentage) = async {
16 | let! progressCreateResult = client.WindowWorkDoneProgressCreate({ Token = this.Token })
17 |
18 | match progressCreateResult with
19 | | Error _ ->
20 | canReport <- false
21 | | Ok() ->
22 | canReport <- true
23 | let param = WorkDoneProgressBegin.Create(
24 | title = title,
25 | ?cancellable = cancellable,
26 | ?message = message,
27 | ?percentage = percentage
28 | )
29 | do! client.Progress({ Token = this.Token; Value = serialize param })
30 | }
31 |
32 | member this.Report(?cancellable, ?message, ?percentage) = async {
33 | if canReport && not endSent then
34 | let param = WorkDoneProgressReport.Create(
35 | ?cancellable = cancellable,
36 | ?message = message,
37 | ?percentage = percentage
38 | )
39 | do! client.Progress({ Token = this.Token; Value = serialize param })
40 | }
41 |
42 | member this.End(?message) = async {
43 | if canReport && not endSent then
44 | endSent <- true
45 | let param = WorkDoneProgressEnd.Create(
46 | ?message = message
47 | )
48 | do! client.Progress({ Token = this.Token; Value = serialize param })
49 | }
50 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/README.md:
--------------------------------------------------------------------------------
1 | ../../README.md
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Types.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Types
2 |
3 | open Ionide.LanguageServerProtocol
4 | open Ionide.LanguageServerProtocol.Types
5 | open Ionide.LanguageServerProtocol.JsonRpc
6 |
7 | type ServerSettings =
8 | { SolutionPath: string option
9 | LogLevel: string
10 | ApplyFormattingOptions: bool
11 | }
12 | static member Default: ServerSettings =
13 | { SolutionPath = None
14 | LogLevel = "log"
15 | ApplyFormattingOptions = false
16 | }
17 |
18 | type CSharpMetadataInformation =
19 | { ProjectName: string
20 | AssemblyName: string
21 | SymbolName: string
22 | Source: string }
23 |
24 | type CSharpMetadataParams =
25 | { TextDocument: TextDocumentIdentifier }
26 |
27 | type CSharpMetadataResponse = CSharpMetadataInformation
28 |
29 | []
30 | type ICSharpLspServer =
31 | inherit ILspServer
32 | abstract CSharpMetadata: CSharpMetadataParams -> AsyncLspResult
33 |
34 | []
35 | type ICSharpLspClient =
36 | inherit ILspClient
37 | // Use a ClientCapabilitiesDTO instead of ClientCapabilities to avoid Option.map & Option.bind?
38 | // But ClientCapabilities is a complex type, write it again will be a huge work.
39 | abstract member Capabilities: ClientCapabilities option with get, set
40 |
41 | let defaultDocumentFilter: TextDocumentFilter =
42 | { Language = None
43 | Scheme = Some "file"
44 | Pattern = Some "**/*.cs" }
45 |
46 | // Type abbreviations cannot have augmentations, extensions
47 | let defaultDocumentSelector: DocumentSelector =
48 | [|
49 | defaultDocumentFilter |> U2.C1
50 | |]
51 |
52 | let emptyClientCapabilities: ClientCapabilities =
53 | {
54 | Workspace = None
55 | TextDocument = None
56 | NotebookDocument = None
57 | Window = None
58 | General = None
59 | Experimental = None
60 | }
61 |
--------------------------------------------------------------------------------
/src/CSharpLanguageServer/Util.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Util
2 |
3 | open System
4 | open System.Runtime.InteropServices
5 | open System.IO
6 |
7 | let parseFileUri s: string =
8 | Uri(s).LocalPath
9 |
10 | let tryParseFileUri s: string option =
11 | try
12 | let uri = Uri(s)
13 | Some uri.LocalPath
14 | with _ex ->
15 | None
16 |
17 | let makeFileUri (path: string): string =
18 | let fullPath = Path.GetFullPath(path)
19 |
20 | match RuntimeInformation.IsOSPlatform(OSPlatform.Windows) with
21 | | true -> "file:///" + fullPath
22 | | false -> "file://" + fullPath
23 |
24 | let unwindProtect cleanupFn op =
25 | async {
26 | try
27 | return! op
28 | finally
29 | cleanupFn ()
30 | }
31 |
32 | // TPL Task's wrap exceptions in AggregateException, -- this fn unpacks them
33 | let rec unpackException (exn : Exception) =
34 | match exn with
35 | | :? AggregateException as agg ->
36 | match Seq.tryExactlyOne agg.InnerExceptions with
37 | | Some x -> unpackException x
38 | | None -> exn
39 | | _ -> exn
40 |
41 | // flip f takes its (first) two arguments in the reverse order of f, just like
42 | // the function with the same name in Haskell.
43 | let flip f x y = f y x
44 |
45 | let curry f x y = f (x, y)
46 | let uncurry f (x, y) = f x y
47 |
48 |
49 | module Seq =
50 | let inline tryMaxBy (projection: 'T -> 'U) (source: 'T seq): 'T option =
51 | if isNull source || Seq.isEmpty source then
52 | None
53 | else
54 | Seq.maxBy projection source |> Some
55 |
56 | module Option =
57 | let inline ofString (value: string) =
58 | match String.IsNullOrWhiteSpace(value) with
59 | | true -> None
60 | | false -> Some value
61 |
62 | module Async =
63 | let map f computation =
64 | async.Bind(computation, f >> async.Return)
65 |
66 | module Map =
67 | let union map1 map2 =
68 | Map.fold (fun acc key value -> Map.add key value acc) map1 map2
69 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/CSharpLanguageServer.Tests.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 |
5 | false
6 | false
7 | FS0988
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 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/CodeActionTests.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Tests.CodeActionTests
2 |
3 | open NUnit.Framework
4 | open Ionide.LanguageServerProtocol.Types
5 |
6 | open CSharpLanguageServer.Tests.Tooling
7 |
8 | []
9 | let testCodeActionOnMethodNameWorks() =
10 | use client = setupServerClient defaultClientProfile
11 | "TestData/testCodeActionOnMethodNameWorks"
12 | client.StartAndWaitForSolutionLoad()
13 |
14 | use classFile = client.Open("Project/Class.cs")
15 |
16 | let caParams0: CodeActionParams =
17 | { TextDocument = { Uri = classFile.Uri }
18 | Range = { Start = { Line = 2u; Character = 16u }
19 | End = { Line = 2u; Character = 16u }
20 | }
21 | Context = { Diagnostics = [| |]
22 | Only = None
23 | TriggerKind = None
24 | }
25 | WorkDoneToken = None
26 | PartialResultToken = None
27 | }
28 |
29 | let caResult0 : TextDocumentCodeActionResult option =
30 | client.Request("textDocument/codeAction", caParams0)
31 |
32 | Assert.IsTrue(caResult0.IsSome)
33 |
34 | match caResult0 with
35 | | Some [| U2.C2 x |] ->
36 | Assert.AreEqual("Extract base class...", x.Title)
37 | Assert.AreEqual(None, x.Kind)
38 | Assert.AreEqual(None, x.Diagnostics)
39 | Assert.AreEqual(None, x.Disabled)
40 | Assert.IsTrue(x.Edit.IsSome)
41 |
42 | | _ -> failwith "Some [| U2.C1 x |] was expected"
43 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/DefinitionTests.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Tests.DefinitionTests
2 |
3 | open NUnit.Framework
4 | open Ionide.LanguageServerProtocol.Types
5 |
6 | open CSharpLanguageServer.Tests.Tooling
7 |
8 | []
9 | let testDefinitionWorks () =
10 | use client = setupServerClient defaultClientProfile
11 | "TestData/testDefinitionWorks"
12 | client.StartAndWaitForSolutionLoad()
13 |
14 | use classFile = client.Open("Project/Class.cs")
15 |
16 | let definitionParams0: DefinitionParams =
17 | { TextDocument = { Uri = classFile.Uri }
18 | Position = { Line = 0u; Character = 0u }
19 | WorkDoneToken = None
20 | PartialResultToken = None
21 | }
22 |
23 | let declaration0: Declaration option =
24 | client.Request("textDocument/definition", definitionParams0)
25 |
26 | Assert.IsTrue(declaration0.IsNone)
27 |
28 | let definitionParams1: DefinitionParams =
29 | { TextDocument = { Uri = classFile.Uri }
30 | Position = { Line = 2u; Character = 16u }
31 | WorkDoneToken = None
32 | PartialResultToken = None
33 | }
34 |
35 | let declaration1: Declaration option =
36 | client.Request("textDocument/definition", definitionParams1)
37 |
38 | match declaration1.Value with
39 | | U2.C1 _ -> failwith "Location[] was expected"
40 | | U2.C2 declaration1Locations ->
41 | let expectedLocations1: Location array =
42 | [|
43 | { Uri = classFile.Uri
44 | Range = { Start = { Line = 2u; Character = 16u }
45 | End = { Line = 2u; Character = 23u } }
46 | }
47 | |]
48 |
49 | Assert.AreEqual(expectedLocations1, declaration1Locations)
50 |
51 |
52 | []
53 | let testDefinitionWorksInAspNetProject () =
54 | use client = setupServerClient defaultClientProfile
55 | "TestData/testDefinitionWorksInAspNetProject"
56 | client.StartAndWaitForSolutionLoad()
57 |
58 | use testIndexViewModelCsFile = client.Open("Project/Models/Test/IndexViewModel.cs")
59 | use testControllerCsFile = client.Open("Project/Controllers/TestController.cs")
60 |
61 | let definitionParams0: DefinitionParams =
62 | { TextDocument = { Uri = testControllerCsFile.Uri }
63 | Position = { Line = 11u; Character = 12u }
64 | WorkDoneToken = None
65 | PartialResultToken = None
66 | }
67 |
68 | let definition0: Declaration option =
69 | client.Request("textDocument/definition", definitionParams0)
70 |
71 | let expectedLocations0: Location array =
72 | [|
73 | { Uri = testIndexViewModelCsFile.Uri
74 | Range = { Start = { Line = 3u; Character = 19u }
75 | End = { Line = 3u; Character = 25u } }
76 | }
77 | |]
78 |
79 | match definition0 with
80 | | Some (U2.C2 definition0Locations) ->
81 | Assert.AreEqual(expectedLocations0, definition0Locations)
82 | | _ -> failwithf "Some Location[] was expected but %s received" (string definition0)
83 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/DiagnosticTests.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Tests.DiagnosticTests
2 |
3 | open System.Threading
4 |
5 | open NUnit.Framework
6 | open Ionide.LanguageServerProtocol.Types
7 |
8 | open CSharpLanguageServer.Tests.Tooling
9 |
10 | []
11 | let testPushDiagnosticsWork () =
12 | use client = setupServerClient defaultClientProfile
13 | "TestData/testPushDiagnosticsWork"
14 | client.StartAndWaitForSolutionLoad()
15 |
16 | //
17 | // open Class.cs file and wait for diagnostics to be pushed
18 | //
19 | use classFile = client.Open("Project/Class.cs")
20 |
21 | Thread.Sleep(4000)
22 |
23 | let state = client.GetState()
24 | let version0, diagnosticList0 = state.PushDiagnostics |> Map.find classFile.Uri
25 |
26 | Assert.AreEqual(None, version0)
27 |
28 | Assert.AreEqual(3, diagnosticList0.Length)
29 |
30 | let diagnostic0 = diagnosticList0.[0]
31 | Assert.AreEqual("Identifier expected", diagnostic0.Message)
32 | Assert.AreEqual(Some DiagnosticSeverity.Error, diagnostic0.Severity)
33 | Assert.AreEqual(0, diagnostic0.Range.Start.Line)
34 | Assert.AreEqual(3, diagnostic0.Range.Start.Character)
35 |
36 | let diagnostic1 = diagnosticList0.[1]
37 | Assert.AreEqual("; expected", diagnostic1.Message)
38 |
39 | let diagnostic2 = diagnosticList0.[2]
40 | Assert.AreEqual(
41 | "The type or namespace name 'XXX' could not be found (are you missing a using directive or an assembly reference?)",
42 | diagnostic2.Message)
43 |
44 | //
45 | // now change the file to contain no content (and thus no diagnostics)
46 | //
47 | classFile.DidChange("")
48 |
49 | Thread.Sleep(4000)
50 |
51 | let state = client.GetState()
52 | let version1, diagnosticList1 = state.PushDiagnostics |> Map.find classFile.Uri
53 |
54 | Assert.AreEqual(None, version1)
55 |
56 | Assert.AreEqual(0, diagnosticList1.Length)
57 | ()
58 |
59 |
60 | []
61 | let testPullDiagnosticsWork () =
62 | use client = setupServerClient defaultClientProfile
63 | "TestData/testPullDiagnosticsWork"
64 | client.StartAndWaitForSolutionLoad()
65 |
66 | //
67 | // open Class.cs file and pull diagnostics
68 | //
69 | use classFile = client.Open("Project/Class.cs")
70 |
71 | let diagnosticParams: DocumentDiagnosticParams =
72 | { WorkDoneToken = None
73 | PartialResultToken = None
74 | TextDocument = { Uri = classFile.Uri }
75 | Identifier = None
76 | PreviousResultId = None }
77 |
78 | let report0: DocumentDiagnosticReport option =
79 | client.Request("textDocument/diagnostic", diagnosticParams)
80 |
81 | match report0 with
82 | | Some (U2.C1 report) ->
83 | Assert.AreEqual("full", report.Kind)
84 | Assert.AreEqual(None, report.ResultId)
85 | Assert.AreEqual(3, report.Items.Length)
86 |
87 | let diagnostic0 = report.Items.[0]
88 | Assert.AreEqual(0, diagnostic0.Range.Start.Line)
89 | Assert.AreEqual(3, diagnostic0.Range.Start.Character)
90 | Assert.AreEqual(Some DiagnosticSeverity.Error, diagnostic0.Severity)
91 | Assert.AreEqual("Identifier expected", diagnostic0.Message)
92 | Assert.AreEqual(
93 | "https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS1001)",
94 | diagnostic0.CodeDescription.Value.Href)
95 |
96 | let diagnostic1 = report.Items.[1]
97 | Assert.AreEqual("; expected", diagnostic1.Message)
98 |
99 | let diagnostic2 = report.Items.[2]
100 | Assert.AreEqual(
101 | "The type or namespace name 'XXX' could not be found (are you missing a using directive or an assembly reference?)",
102 | diagnostic2.Message)
103 | | _ -> failwith "U2.C1 is expected"
104 |
105 | //
106 | // now try to do the same but with file fixed to contain no content (and thus no diagnostics)
107 | //
108 | classFile.DidChange("")
109 |
110 | let report1: DocumentDiagnosticReport option =
111 | client.Request("textDocument/diagnostic", diagnosticParams)
112 |
113 | match report1 with
114 | | Some (U2.C1 report) ->
115 | Assert.AreEqual("full", report.Kind)
116 | Assert.AreEqual(None, report.ResultId)
117 | Assert.AreEqual(0, report.Items.Length)
118 | | _ -> failwith "U2.C1 is expected"
119 | ()
120 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/DocumentFormattingTests.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Tests.DocumentFormattingTests
2 |
3 | open System.IO
4 |
5 | open NUnit.Framework
6 | open Ionide.LanguageServerProtocol.Types
7 |
8 | open CSharpLanguageServer.Tests.Tooling
9 |
10 | []
11 | let testEditorConfigFormatting () =
12 | use client = setupServerClient defaultClientProfile
13 | "TestData/testEditorConfigFormatting"
14 | client.StartAndWaitForSolutionLoad()
15 |
16 | use classFile = client.Open("Project/Class.cs")
17 |
18 | let docFormattingParams0: DocumentFormattingParams =
19 | { TextDocument = { Uri = classFile.Uri }
20 | WorkDoneToken = None
21 | Options = {
22 | TabSize = 8u
23 | InsertSpaces = false
24 | TrimTrailingWhitespace = Some true
25 | InsertFinalNewline = Some true
26 | TrimFinalNewlines = Some true
27 | }
28 | }
29 |
30 | let textEdits: TextEdit[] option =
31 | client.Request("textDocument/formatting", docFormattingParams0)
32 |
33 | match textEdits with
34 | | Some tes ->
35 | let expectedClassContents = File.ReadAllText(Path.Combine(client.ProjectDir, "Project", "ExpectedFormatting.cs.txt"))
36 | Assert.AreEqual(expectedClassContents, classFile.GetFileContentsWithTextEditsApplied(tes))
37 | | None -> failwith "Some TextEdit's were expected"
38 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/DocumentationTests.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Tests.DocumentationTests
2 |
3 | open System
4 |
5 | open NUnit.Framework
6 |
7 | open CSharpLanguageServer.DocumentationUtil
8 |
9 | []
12 | [doc string",
14 | "doc string")>]
15 | [\ndoc string\n\n",
17 | "doc string")>]
18 | [doc string\n y",
20 | """doc string
21 |
22 | Parameters:
23 | - ``x``: y""")>]
24 | [Gets the standard error output stream.\n\
27 | A that represents the standard error output stream.\n\
28 | ",
29 | """Gets the standard error output stream.
30 |
31 | Returns: A ``System.IO.TextWriter`` that represents the standard error output stream."""
32 | )>]
33 | [\n\
36 | Asserts that a condition is true. If the condition is false the method throws\n\
37 | an .\n\
38 | \n\
39 | The evaluated condition\n\
40 | \n\
41 | ",
42 | """Asserts that a condition is true. If the condition is false the method throws an ``NUnit.Framework.AssertionException``.
43 |
44 | Parameters:
45 | - ``condition``: The evaluated condition"""
46 | )>]
47 | [Writes a string to the text stream, followed by a line terminator.\n\
50 | The string to write. If is , only the line terminator is written.\n\
51 | The is closed.\n\
52 | An I/O error occurs.\n\
53 | ",
54 | """Writes a string to the text stream, followed by a line terminator.
55 |
56 | Parameters:
57 | - ``value``: The string to write. If ``value`` is ``null``, only the line terminator is written.
58 |
59 | Exceptions:
60 | - ``System.ObjectDisposedException``: The ``System.IO.TextWriter`` is closed.
61 | - ``System.IO.IOException``: An I/O error occurs."""
62 | )>]
63 | [
65 |
66 | Test method.
67 | Does another thing.
68 |
69 |
70 | """,
71 | "Test method. Does another thing.")>]
72 | [test xx",
74 | "test ``xx``")>]
75 | [test contents-of-unknown-tag",
77 | "test contents-of-unknown-tag")>]
78 | [test contents-of-unknown-inline-tag",
80 | "test contents-of-unknown-inline-tag")>]
81 | [summary\n
83 | contents-of-unknown-top-level-tag",
84 | "summary\n\
85 | contents-of-unknown-top-level-tag")>]
86 | [summaryremarks",
88 | "summary\n\nRemarks: remarks")>]
89 | [A",
91 | "A")>]
92 | [yb",
94 | """
95 | Parameters:
96 | - ``x``: y
97 | - ``a``: b""")>]
98 | [descy",
100 | """desc
101 |
102 | Types:
103 | - ``x``: y""")>]
104 | [
106 |
107 | Adds a child . Nodes can have any number of children, but every child must have a unique name. Child nodes are automatically deleted when the parent node is deleted, so an entire scene can be removed by deleting its topmost node.
108 | If is , improves the readability of the added . If not named, the is renamed to its type, and if it shares with a sibling, a number is suffixed more appropriately. This operation is very slow. As such, it is recommended leaving this to , which assigns a dummy name featuring @ in both situations.
109 | If is different than , the child will be added as internal node. These nodes are ignored by methods like , unless their parameter include_internal is . The intended usage is to hide the internal nodes from the user, so the user won't accidentally delete or modify them. Used by some GUI nodes, e.g. . See for available modes.
110 | Note: If already has a parent, this method will fail. Use first to remove from its current parent. For example:
111 |
112 | Node childNode = GetChild(0);
113 | if (childNode.GetParent() != null)
114 | {
115 | childNode.GetParent().RemoveChild(childNode);
116 | }
117 | AddChild(childNode);
118 | If you need the child node to be added below a specific node in the list of children, use instead of this method.
119 | Note: If you want a child to be persisted to a , you must set in addition to calling . This is typically relevant for tool scripts and editor plugins. If is called without setting , the newly added will not be visible in the scene tree, though it will be visible in the 2D/3D view.
120 |
121 |
122 | """,
123 | """Adds a child ``node``. Nodes can have any number of children, but every child must have a unique name. Child nodes are automatically deleted when the parent node is deleted, so an entire scene can be removed by deleting its topmost node.
124 |
125 | If ``forceReadableName`` is ``true``, improves the readability of the added ``node``. If not named, the ``node`` is renamed to its type, and if it shares ``Godot.Node.Name`` with a sibling, a number is suffixed more appropriately. This operation is very slow. As such, it is recommended leaving this to ``false``, which assigns a dummy name featuring ``@`` in both situations.
126 |
127 | If ``internal`` is different than ``Godot.Node.InternalMode.Disabled``, the child will be added as internal node. These nodes are ignored by methods like ``Godot.Node.GetChildren(System.Boolean)``, unless their parameter ``include_internal`` is ``true``. The intended usage is to hide the internal nodes from the user, so the user won't accidentally delete or modify them. Used by some GUI nodes, e.g. ``Godot.ColorPicker``. See ``Godot.Node.InternalMode`` for available modes.
128 |
129 | Note: If ``node`` already has a parent, this method will fail. Use ``Godot.Node.RemoveChild(Godot.Node)`` first to remove ``node`` from its current parent. For example:
130 |
131 |
132 | Node childNode = GetChild(0);
133 | if (childNode.GetParent() != null)
134 | {
135 | childNode.GetParent().RemoveChild(childNode);
136 | }
137 | AddChild(childNode);
138 |
139 | If you need the child node to be added below a specific node in the list of children, use ``Godot.Node.AddSibling(Godot.Node,System.Boolean)`` instead of this method.
140 |
141 | Note: If you want a child to be persisted to a ``Godot.PackedScene``, you must set ``Godot.Node.Owner`` in addition to calling ``Godot.Node.AddChild(Godot.Node,System.Boolean,Godot.Node.InternalMode)``. This is typically relevant for tool scripts and editor plugins. If ``Godot.Node.AddChild(Godot.Node,System.Boolean,Godot.Node.InternalMode)`` is called without setting ``Godot.Node.Owner``, the newly added ``Godot.Node`` will not be visible in the scene tree, though it will be visible in the 2D/3D view."""
142 | )>]
143 | let testFormatDocXml (inputXml, expectedMD: string) =
144 | let resultMd = String.Join("\n", formatDocXml inputXml)
145 | Assert.AreEqual(expectedMD.Replace("\r\n", "\n"), resultMd)
146 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/HoverTests.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Tests.HoverTests
2 |
3 | open NUnit.Framework
4 | open Ionide.LanguageServerProtocol.Types
5 |
6 | open CSharpLanguageServer.Tests.Tooling
7 |
8 | []
9 | let testHoverWorks() =
10 | use client = setupServerClient defaultClientProfile
11 | "TestData/testHoverWorks"
12 | client.StartAndWaitForSolutionLoad()
13 |
14 | use classFile = client.Open("Project/Class.cs")
15 |
16 | //
17 | // check hover at method name
18 | //
19 | let hover0Params: HoverParams =
20 | { TextDocument = { Uri = classFile.Uri }
21 | Position = { Line = 2u; Character = 16u }
22 | WorkDoneToken = None
23 | }
24 |
25 | let hover0: Hover option =
26 | client.Request("textDocument/hover", hover0Params)
27 |
28 | Assert.IsTrue(hover0.IsSome)
29 |
30 | match hover0 with
31 | | Some hover ->
32 | match hover.Contents with
33 | | U3.C1 c ->
34 | Assert.AreEqual(MarkupKind.Markdown, c.Kind)
35 | Assert.AreEqual("```csharp\nvoid Class.Method(string arg)\n```", c.Value)
36 | | _ -> failwith "C1 was expected"
37 |
38 | Assert.IsTrue(hover.Range.IsNone)
39 |
40 | | _ -> failwith "Some (U3.C1 c) was expected"
41 |
42 | //
43 | // check hover on `string` value (external System.String type)
44 | //
45 | let hover1Params: HoverParams =
46 | { TextDocument = { Uri = classFile.Uri }
47 | Position = { Line = 4u; Character = 8u }
48 | WorkDoneToken = None
49 | }
50 |
51 | let hover1: Hover option =
52 | client.Request("textDocument/hover", hover1Params)
53 |
54 | Assert.IsTrue(hover1.IsSome)
55 |
56 | match hover1 with
57 | | Some hover ->
58 | match hover.Contents with
59 | | U3.C1 c ->
60 | Assert.AreEqual(MarkupKind.Markdown, c.Kind)
61 | Assert.AreEqual("""```csharp
62 | string
63 | ```
64 |
65 | Represents text as a sequence of UTF-16 code units.""", c.Value.ReplaceLineEndings())
66 | | _ -> failwith "C1 was expected"
67 |
68 | Assert.IsTrue(hover.Range.IsNone)
69 |
70 | | _ -> failwith "Some (U3.C1 c) was expected"
71 |
72 | //
73 | // check hover at beginning of the file (nothing should come up)
74 | //
75 | let hover2Params: HoverParams =
76 | { TextDocument = { Uri = classFile.Uri }
77 | Position = { Line = 0u; Character = 0u }
78 | WorkDoneToken = None
79 | }
80 |
81 | let hover2: Hover option =
82 | client.Request("textDocument/hover", hover2Params)
83 |
84 | Assert.IsTrue(hover2.IsNone)
85 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/InitializationTests.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Tests.InitializationTests
2 |
3 | open NUnit.Framework
4 |
5 | open CSharpLanguageServer.Tests.Tooling
6 | open Ionide.LanguageServerProtocol.Types
7 |
8 | let assertHoverWorks (client: ClientController) file pos expectedMarkupContent =
9 | use classFile = client.Open(file)
10 |
11 | let hover0Params: HoverParams =
12 | { TextDocument = { Uri = classFile.Uri }
13 | Position = pos
14 | WorkDoneToken = None
15 | }
16 |
17 | let hover0: Hover option = client.Request("textDocument/hover", hover0Params)
18 |
19 | match hover0 with
20 | | Some { Contents = U3.C1 markupContent; Range = None } ->
21 | Assert.AreEqual(MarkupKind.Markdown, markupContent.Kind)
22 | Assert.AreEqual(expectedMarkupContent, markupContent.Value)
23 |
24 | | x -> failwithf "'{ Contents = U3.C1 markupContent; Range = None }' was expected but '%s' received" (string x)
25 |
26 |
27 | []
28 | let testServerRegistersCapabilitiesWithTheClient () =
29 | use client = setupServerClient defaultClientProfile
30 | "TestData/testServerRegistersCapabilitiesWithTheClient"
31 | client.StartAndWaitForSolutionLoad()
32 |
33 | let serverInfo = client.GetState().ServerInfo.Value
34 | Assert.AreEqual("csharp-ls", serverInfo.Name)
35 |
36 | let serverCaps = client.GetState().ServerCapabilities.Value
37 |
38 | Assert.AreEqual(
39 | { Change = Some TextDocumentSyncKind.Incremental
40 | OpenClose = Some true
41 | Save = Some (U2.C2 { IncludeText = Some true })
42 | WillSave = None
43 | WillSaveWaitUntil = None
44 | }
45 | |> U2.C1 |> Some,
46 | serverCaps.TextDocumentSync)
47 |
48 | Assert.AreEqual(
49 | null,
50 | serverCaps.Workspace)
51 |
52 | Assert.AreEqual(
53 | true |> U2.C1 |> Some,
54 | serverCaps.HoverProvider)
55 |
56 | Assert.AreEqual(
57 | true |> U3.C1 |> Some,
58 | serverCaps.ImplementationProvider)
59 |
60 | Assert.AreEqual(
61 | true |> U2.C1 |> Some,
62 | serverCaps.DocumentSymbolProvider)
63 |
64 | Assert.AreEqual(
65 | true |> U2.C1 |> Some,
66 | serverCaps.DefinitionProvider)
67 |
68 | Assert.AreEqual(
69 | null,
70 | serverCaps.InlineValueProvider)
71 |
72 | Assert.AreEqual(
73 | { DocumentSelector = Some [|U2.C1 { Language = None
74 | Scheme = Some "file"
75 | Pattern = Some "**/*.cs" }|]
76 | WorkDoneProgress = None
77 | Identifier = None
78 | InterFileDependencies = false
79 | WorkspaceDiagnostics = false
80 | Id = None
81 | }
82 | |> U2.C2 |> Some,
83 | serverCaps.DiagnosticProvider)
84 |
85 | Assert.AreEqual(
86 | true |> U2.C1 |> Some,
87 | serverCaps.DocumentHighlightProvider)
88 |
89 | Assert.AreEqual(
90 | { WorkDoneProgress = None
91 | TriggerCharacters = Some [|"."; "'"|]
92 | AllCommitCharacters = None
93 | ResolveProvider = None
94 | CompletionItem = None }
95 | |> Some,
96 | serverCaps.CompletionProvider)
97 |
98 | Assert.AreEqual(
99 | { WorkDoneProgress = None
100 | CodeActionKinds = None
101 | ResolveProvider = Some true }
102 | |> U2.C2 |> Some,
103 | serverCaps.CodeActionProvider)
104 |
105 | Assert.AreEqual(
106 | true |> U2.C1 |> Some,
107 | serverCaps.RenameProvider)
108 |
109 | Assert.AreEqual(
110 | null,
111 | serverCaps.DeclarationProvider)
112 |
113 | Assert.AreEqual(
114 | true |> U2.C1 |> Some,
115 | serverCaps.DocumentFormattingProvider)
116 |
117 | Assert.AreEqual(
118 | true |> U2.C1 |> Some,
119 | serverCaps.ReferencesProvider)
120 |
121 | Assert.AreEqual(
122 | true |> U2.C1 |> Some,
123 | serverCaps.WorkspaceSymbolProvider)
124 |
125 | Assert.AreEqual(
126 | { WorkDoneProgress = None
127 | TriggerCharacters = Some [|"("; ","; "<"; "{"; "["|]
128 | RetriggerCharacters = None }
129 | |> Some,
130 | serverCaps.SignatureHelpProvider)
131 |
132 | Assert.AreEqual(
133 | null,
134 | serverCaps.MonikerProvider)
135 |
136 | Assert.IsTrue(client.ServerDidRespondTo "initialize")
137 | Assert.IsTrue(client.ServerDidRespondTo "initialized")
138 |
139 |
140 | []
141 | let testSlnxSolutionFileWillBeFoundAndLoaded () =
142 | use client = setupServerClient defaultClientProfile "TestData/testSlnx"
143 | client.StartAndWaitForSolutionLoad()
144 |
145 | Assert.IsTrue(client.ServerMessageLogContains(fun m -> m.Contains "1 solution(s) found"))
146 |
147 | Assert.IsTrue(client.ServerDidRespondTo "initialize")
148 | Assert.IsTrue(client.ServerDidRespondTo "initialized")
149 |
150 | assertHoverWorks
151 | client
152 | "Project/Class.cs" { Line = 2u; Character = 16u }
153 | "```csharp\nvoid Class.MethodA(string arg)\n```"
154 |
155 |
156 | []
157 | let testMultiTargetProjectLoads () =
158 | use client = setupServerClient defaultClientProfile "TestData/testMultiTargetProjectLoads"
159 | client.StartAndWaitForSolutionLoad()
160 |
161 | Assert.IsTrue(client.ServerMessageLogContains(fun m -> m.Contains "loading project"))
162 |
163 | assertHoverWorks
164 | client
165 | "Project/Class.cs" { Line = 2u; Character = 16u }
166 | "```csharp\nvoid Class.Method(string arg)\n```"
167 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/InternalTests.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Tests.InternalTests
2 |
3 | open NUnit.Framework
4 |
5 | open CSharpLanguageServer.RoslynHelpers
6 |
7 | []
8 | []
9 | []
10 | []
11 | []
12 | []
13 | let testTheMostCapableTfmIsSelected(tfmList: string, expectedTfm: string) =
14 | let selectedTfm = tfmList.Split(";") |> selectMostCapableCompatibleTfm
15 | Assert.AreEqual(expectedTfm |> Option.ofObj, selectedTfm)
16 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/ReferenceTests.fs:
--------------------------------------------------------------------------------
1 | module CSharpLanguageServer.Tests.ReferenceTests
2 |
3 | open NUnit.Framework
4 | open Ionide.LanguageServerProtocol.Types
5 |
6 | open CSharpLanguageServer.Tests.Tooling
7 |
8 | []
9 | []
10 | let testReferenceWorks(testDataDir: string) =
11 | use client = setupServerClient defaultClientProfile testDataDir
12 | client.StartAndWaitForSolutionLoad()
13 |
14 | use classFile = client.Open("Project/Class.cs")
15 |
16 | //
17 | // try references request at empty line line 1 -- should return 0 results
18 | //
19 | let referenceParams0: ReferenceParams =
20 | { TextDocument = { Uri = classFile.Uri }
21 | Position = { Line = 0u; Character = 0u }
22 | WorkDoneToken = None
23 | PartialResultToken = None
24 | Context = { IncludeDeclaration = false }
25 | }
26 |
27 | let locations0: Location[] option =
28 | client.Request("textDocument/references", referenceParams0)
29 |
30 | Assert.IsTrue(locations0.IsNone)
31 |
32 | //
33 | // try references request at MethodA declaration on line 2
34 | //
35 | let referenceParams1: ReferenceParams =
36 | { TextDocument = { Uri = classFile.Uri }
37 | Position = { Line = 2u; Character = 16u }
38 | WorkDoneToken = None
39 | PartialResultToken = None
40 | Context = { IncludeDeclaration = false }
41 | }
42 |
43 | let locations1: Location[] option =
44 | client.Request("textDocument/references", referenceParams1)
45 |
46 | let expectedLocations1: Location array =
47 | [|
48 | { Uri = classFile.Uri
49 | Range = {
50 | Start = { Line = 8u; Character = 8u }
51 | End = { Line = 8u; Character = 15u }
52 | }
53 | }
54 | |]
55 |
56 | Assert.AreEqual(expectedLocations1, locations1.Value)
57 |
58 | //
59 | // try references request at MethodA declaration on line 2
60 | // (with IncludeDeclaration=true)
61 | //
62 | let referenceParams2: ReferenceParams =
63 | { TextDocument = { Uri = classFile.Uri }
64 | Position = { Line = 2u; Character = 16u }
65 | WorkDoneToken = None
66 | PartialResultToken = None
67 | Context = { IncludeDeclaration = true }
68 | }
69 |
70 | let locations2: Location[] option =
71 | client.Request("textDocument/references", referenceParams2)
72 |
73 | let expectedLocations2: Location array =
74 | [|
75 | { Uri = classFile.Uri
76 | Range = {
77 | Start = { Line = 2u; Character = 16u }
78 | End = { Line = 2u; Character = 23u }
79 | }
80 | }
81 |
82 | { Uri = classFile.Uri
83 | Range = {
84 | Start = { Line = 8u; Character = 8u }
85 | End = { Line = 8u; Character = 15u }
86 | }
87 | }
88 | |]
89 |
90 | Assert.AreEqual(expectedLocations2, locations2.Value)
91 |
92 | []
93 | []
94 | let testReferenceWorksToAspNetRazorPageReferencedValue() =
95 | use client = setupServerClient defaultClientProfile
96 | "TestData/testReferenceWorksToAspNetRazorPageReferencedValue"
97 | client.StartAndWaitForSolutionLoad()
98 |
99 | use testIndexViewModelCsFile = client.Open("Project/Models/Test/IndexViewModel.cs")
100 | use testControllerCsFile = client.Open("Project/Controllers/TestController.cs")
101 | use viewsTestIndexCshtmlFile = client.Open("Project/Views/Test/Index.cshtml")
102 |
103 | let referenceParams0: ReferenceParams =
104 | { TextDocument = { Uri = testIndexViewModelCsFile.Uri }
105 | Position = { Line = 3u; Character = 20u }
106 | WorkDoneToken = None
107 | PartialResultToken = None
108 | Context = { IncludeDeclaration = false }
109 | }
110 |
111 | let locations0: Location[] option =
112 | client.Request("textDocument/references", referenceParams0)
113 |
114 | Assert.IsTrue(locations0.IsSome)
115 | Assert.AreEqual(2, locations0.Value.Length)
116 |
117 | let expectedLocations0: Location array =
118 | [|
119 | { Uri = testControllerCsFile.Uri
120 | Range = { Start = { Line = 11u; Character = 12u }
121 | End = { Line = 11u; Character = 18u } }
122 | }
123 |
124 | { Uri = viewsTestIndexCshtmlFile.Uri
125 | Range = { Start = { Line = 1u; Character = 7u }
126 | End = { Line = 1u; Character = 13u } }
127 | }
128 | |]
129 |
130 | Assert.AreEqual(expectedLocations0, locations0.Value)
131 |
132 | //
133 | // do same but with IncludeDeclaration=true
134 | //
135 | let referenceParams1: ReferenceParams =
136 | { TextDocument = { Uri = testIndexViewModelCsFile.Uri }
137 | Position = { Line = 3u; Character = 20u }
138 | WorkDoneToken = None
139 | PartialResultToken = None
140 | Context = { IncludeDeclaration = true }
141 | }
142 |
143 | let locations1: Location[] option =
144 | client.Request("textDocument/references", referenceParams1)
145 |
146 | Assert.IsTrue(locations1.IsSome)
147 | Assert.AreEqual(5, locations1.Value.Length)
148 |
149 | let expectedLocations1: Location array =
150 | [|
151 | { Uri = viewsTestIndexCshtmlFile.Uri
152 | Range = { Start = { Line = 1u; Character = 7u }
153 | End = { Line = 1u; Character = 13u } }
154 | }
155 |
156 | { Uri = testIndexViewModelCsFile.Uri
157 | Range = { Start = { Line = 3u; Character = 19u }
158 | End = { Line = 3u; Character = 25u } }
159 | }
160 |
161 | { Uri = testIndexViewModelCsFile.Uri
162 | Range = { Start = { Line = 3u; Character = 28u }
163 | End = { Line = 3u; Character = 31u } }
164 | }
165 |
166 | { Uri = testIndexViewModelCsFile.Uri
167 | Range = { Start = { Line = 3u; Character = 33u }
168 | End = { Line = 3u; Character = 36u } }
169 | }
170 |
171 | { Uri = testControllerCsFile.Uri
172 | Range = { Start = { Line = 11u; Character = 12u }
173 | End = { Line = 11u; Character = 18u } }
174 | }
175 | |]
176 |
177 | let sortedLocations1 =
178 | locations1.Value
179 | |> Array.sortBy (fun f -> (f.Range.Start.Line, f.Range.Start.Character))
180 |
181 | Assert.AreEqual(expectedLocations1, sortedLocations1)
182 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testCodeActionOnMethodNameWorks/Project/Class.cs:
--------------------------------------------------------------------------------
1 | class Class
2 | {
3 | public void Method(string arg)
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testCodeActionOnMethodNameWorks/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net9.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testDefinitionWorks/Project/Class.cs:
--------------------------------------------------------------------------------
1 | class Class
2 | {
3 | public void MethodA(string arg)
4 | {
5 | }
6 |
7 | public void MethodB(string arg)
8 | {
9 | MethodA(arg);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testDefinitionWorks/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net9.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testDefinitionWorksInAspNetProject/Project/Controllers/TestController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Project.Models.Test;
3 |
4 | namespace Printlog.Web.ClientPart.Controllers;
5 |
6 | public class TestController : Controller
7 | {
8 | public IActionResult Index()
9 | {
10 | var model = new IndexViewModel()
11 | {
12 | Output = "test"
13 | };
14 |
15 | return View(model);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testDefinitionWorksInAspNetProject/Project/Models/Test/IndexViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace Project.Models.Test;
2 | public class IndexViewModel
3 | {
4 | public string? Output { get; set; }
5 | }
6 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testDefinitionWorksInAspNetProject/Project/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore;
2 | using System.Threading.Tasks;
3 | using Microsoft.AspNetCore.Hosting;
4 |
5 | namespace Project;
6 |
7 | public class Program
8 | {
9 | public static async Task Main(string[] args)
10 | {
11 | await BuildWebHost(args).RunAsync();
12 | }
13 |
14 | public static IWebHost BuildWebHost(string[] args)
15 | {
16 | var builder = WebHost.CreateDefaultBuilder(args);
17 | builder = builder.UseKestrel().UseStartup();
18 | return builder.Build();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testDefinitionWorksInAspNetProject/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | enable
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testDefinitionWorksInAspNetProject/Project/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 |
6 | namespace Project;
7 |
8 | public class Startup
9 | {
10 | public Startup(IConfiguration configuration, IWebHostEnvironment env)
11 | {
12 | }
13 |
14 | public void ConfigureServices(IServiceCollection services)
15 | {
16 | services.AddOptions();
17 | }
18 |
19 | public void Configure(
20 | IApplicationBuilder app,
21 | IWebHostEnvironment env)
22 | {
23 | app.UseAuthentication();
24 | app.UseAuthorization();
25 |
26 | app.UseEndpoints(endpoints => {
27 | endpoints.MapControllers();
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testDefinitionWorksInAspNetProject/Project/Views/Test/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model Project.Models.Test.IndexViewModel
2 | @Model.Output
3 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testDefinitionWorksInAspNetProject/Project/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
2 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testDefinitionWorksInAspNetProject/Project/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testEditorConfigFormatting/Project/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.cs]
4 | csharp_space_after_keywords_in_control_flow_statements = false
5 | csharp_space_before_open_square_brackets = true
6 | csharp_space_after_cast = true
7 | csharp_new_line_before_open_brace = none
8 | csharp_indent_switch_labels = false
9 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testEditorConfigFormatting/Project/Class.cs:
--------------------------------------------------------------------------------
1 | // csharp_space_after_keywords_in_control_flow_statements = false
2 | if (true) ;
3 |
4 | // csharp_space_before_open_square_brackets = true
5 | var array = new int[10];
6 |
7 | // csharp_space_after_cast = true
8 | var num = (int)15.0;
9 |
10 | // csharp_new_line_before_open_brace = none
11 | void foo()
12 | {
13 |
14 | }
15 |
16 | // csharp_indent_switch_labels = true
17 | switch (Random.Shared.Next(3))
18 | {
19 | case 1:
20 | Console.WriteLine("Case 1");
21 | break;
22 | case 2:
23 | Console.WriteLine("Case 2");
24 | break;
25 | default:
26 | Console.WriteLine("Default case");
27 | break;
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testEditorConfigFormatting/Project/ExpectedFormatting.cs.txt:
--------------------------------------------------------------------------------
1 | // csharp_space_after_keywords_in_control_flow_statements = false
2 | if(true) ;
3 |
4 | // csharp_space_before_open_square_brackets = true
5 | var array = new int [10];
6 |
7 | // csharp_space_after_cast = true
8 | var num = (int) 15.0;
9 |
10 | // csharp_new_line_before_open_brace = none
11 | void foo() {
12 |
13 | }
14 |
15 | // csharp_indent_switch_labels = true
16 | switch(Random.Shared.Next(3)) {
17 | case 1:
18 | Console.WriteLine("Case 1");
19 | break;
20 | case 2:
21 | Console.WriteLine("Case 2");
22 | break;
23 | default:
24 | Console.WriteLine("Default case");
25 | break;
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testEditorConfigFormatting/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net9.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testHoverWorks/Project/Class.cs:
--------------------------------------------------------------------------------
1 | class Class
2 | {
3 | public void Method(string arg)
4 | {
5 | string str = "";
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testHoverWorks/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net9.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testMultiTargetProjectLoads/Project/Class.cs:
--------------------------------------------------------------------------------
1 | class Class
2 | {
3 | public void Method(string arg)
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testMultiTargetProjectLoads/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net6.0;net8.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testPullDiagnosticsWork/Project/Class.cs:
--------------------------------------------------------------------------------
1 | XXX
2 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testPullDiagnosticsWork/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net9.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testPushDiagnosticsWork/Project/Class.cs:
--------------------------------------------------------------------------------
1 | XXX
2 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testPushDiagnosticsWork/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net9.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksDotnet8/Project/Class.cs:
--------------------------------------------------------------------------------
1 | class Class
2 | {
3 | public void MethodA(string arg)
4 | {
5 | }
6 |
7 | public void MethodB(string arg)
8 | {
9 | MethodA(arg);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksDotnet8/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net8.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksDotnet9/Project/Class.cs:
--------------------------------------------------------------------------------
1 | class Class
2 | {
3 | public void MethodA(string arg)
4 | {
5 | }
6 |
7 | public void MethodB(string arg)
8 | {
9 | MethodA(arg);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksDotnet9/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net9.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksToAspNetRazorPageReferencedValue/Project/Controllers/TestController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Project.Models.Test;
3 |
4 | namespace Printlog.Web.ClientPart.Controllers;
5 |
6 | public class TestController : Controller
7 | {
8 | public IActionResult Index()
9 | {
10 | var model = new IndexViewModel()
11 | {
12 | Output = "test"
13 | };
14 |
15 | return View(model);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksToAspNetRazorPageReferencedValue/Project/Models/Test/IndexViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace Project.Models.Test;
2 | public class IndexViewModel
3 | {
4 | public string? Output { get; set; }
5 | }
6 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksToAspNetRazorPageReferencedValue/Project/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore;
2 | using System.Threading.Tasks;
3 | using Microsoft.AspNetCore.Hosting;
4 |
5 | namespace Project;
6 |
7 | public class Program
8 | {
9 | public static async Task Main(string[] args)
10 | {
11 | await BuildWebHost(args).RunAsync();
12 | }
13 |
14 | public static IWebHost BuildWebHost(string[] args)
15 | {
16 | var builder = WebHost.CreateDefaultBuilder(args);
17 | builder = builder.UseKestrel().UseStartup();
18 | return builder.Build();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksToAspNetRazorPageReferencedValue/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | enable
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksToAspNetRazorPageReferencedValue/Project/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 |
6 | namespace Project;
7 |
8 | public class Startup
9 | {
10 | public Startup(IConfiguration configuration, IWebHostEnvironment env)
11 | {
12 | }
13 |
14 | public void ConfigureServices(IServiceCollection services)
15 | {
16 | services.AddOptions();
17 | }
18 |
19 | public void Configure(
20 | IApplicationBuilder app,
21 | IWebHostEnvironment env)
22 | {
23 | app.UseAuthentication();
24 | app.UseAuthorization();
25 |
26 | app.UseEndpoints(endpoints => {
27 | endpoints.MapControllers();
28 | });
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksToAspNetRazorPageReferencedValue/Project/Views/Test/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model Project.Models.Test.IndexViewModel
2 | @Model.Output
3 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksToAspNetRazorPageReferencedValue/Project/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
2 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testReferenceWorksToAspNetRazorPageReferencedValue/Project/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testServerRegistersCapabilitiesWithTheClient/Project/Class.cs:
--------------------------------------------------------------------------------
1 | class Class {}
2 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testServerRegistersCapabilitiesWithTheClient/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net9.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testSlnx/Project/Class.cs:
--------------------------------------------------------------------------------
1 | class Class
2 | {
3 | public void MethodA(string arg)
4 | {
5 | }
6 |
7 | public void MethodB(string arg)
8 | {
9 | MethodA(arg);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testSlnx/Project/Project.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net9.0
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/CSharpLanguageServer.Tests/TestData/testSlnx/Test.slnx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------