diff --git a/src/CSharpLanguageServer/CSharpLanguageServer.fsproj b/src/CSharpLanguageServer/CSharpLanguageServer.fsproj
index 8b44f951..36e3ee10 100644
--- a/src/CSharpLanguageServer/CSharpLanguageServer.fsproj
+++ b/src/CSharpLanguageServer/CSharpLanguageServer.fsproj
@@ -17,7 +17,6 @@
README.md
CHANGELOG.md
enable
- true
diff --git a/src/CSharpLanguageServer/Handlers/Completion.fs b/src/CSharpLanguageServer/Handlers/Completion.fs
index 899a374e..0f048d46 100644
--- a/src/CSharpLanguageServer/Handlers/Completion.fs
+++ b/src/CSharpLanguageServer/Handlers/Completion.fs
@@ -3,20 +3,25 @@ namespace CSharpLanguageServer.Handlers
open System
open System.Reflection
+open Microsoft.CodeAnalysis
+open Microsoft.CodeAnalysis.Text
open Microsoft.Extensions.Caching.Memory
open Ionide.LanguageServerProtocol.Server
open Ionide.LanguageServerProtocol.Types
open Ionide.LanguageServerProtocol.JsonRpc
+open Microsoft.Extensions.Logging
open CSharpLanguageServer.State
open CSharpLanguageServer.Util
open CSharpLanguageServer.Roslyn.Conversions
+open CSharpLanguageServer.Roslyn.Solution
open CSharpLanguageServer.Logging
open CSharpLanguageServer.Lsp.Workspace
+
[]
module Completion =
- let private _logger = Logging.getLoggerByName "Completion"
+ let private logger = Logging.getLoggerByName "Completion"
let private completionItemMemoryCache = new MemoryCache(new MemoryCacheOptions())
@@ -181,16 +186,118 @@ module Completion =
synopsis, documentationText
| _, _ -> None, None
- let handle
+ let codeActionContextToCompletionTrigger (context: CompletionContext option) =
+ context
+ |> Option.bind (fun ctx ->
+ match ctx.TriggerKind with
+ | CompletionTriggerKind.Invoked
+ | CompletionTriggerKind.TriggerForIncompleteCompletions ->
+ Some Microsoft.CodeAnalysis.Completion.CompletionTrigger.Invoke
+ | CompletionTriggerKind.TriggerCharacter ->
+ ctx.TriggerCharacter
+ |> Option.map Seq.head
+ |> Option.map Microsoft.CodeAnalysis.Completion.CompletionTrigger.CreateInsertionTrigger
+ | _ -> None)
+ |> Option.defaultValue Microsoft.CodeAnalysis.Completion.CompletionTrigger.Invoke
+
+ let getCompletionsForRazorDocument
+ (p: CompletionParams)
(context: ServerRequestContext)
+ : Async
diff --git a/tests/CSharpLanguageServer.Tests/CompletionTests.fs b/tests/CSharpLanguageServer.Tests/CompletionTests.fs
index 24a65f77..67098993 100644
--- a/tests/CSharpLanguageServer.Tests/CompletionTests.fs
+++ b/tests/CSharpLanguageServer.Tests/CompletionTests.fs
@@ -133,6 +133,94 @@ let ``completion works for extension methods`` () =
Some "(extension) string ClassForCompletionWithExtensionMethods.MethodB()"
)
- Assert.IsFalse(itemResolved.Documentation.IsSome)
+ match itemResolved.Documentation with
+ | Some(U2.C2 markup) ->
+ Assert.AreEqual(MarkupKind.PlainText, markup.Kind)
+ Assert.IsNotEmpty(markup.Value)
+ | _ -> failwith "Documentation w/ Kind=Markdown was expected"
+
+ ()
| _ -> failwith "Some U2.C1 was expected"
+
+
+[]
+let ``completion works in cshtml files`` () =
+ use client = activateFixture "aspnetProject"
+
+ use cshtmlFile = client.Open("Project/Views/Test/CompletionTests.cshtml")
+
+ let testCompletionResultContainsItem
+ line
+ character
+ expectedLabel
+ expectedCompletionItemKind
+ expectedDetail
+ documentationTestFn
+ =
+ let completionParams0: CompletionParams =
+ { TextDocument = { Uri = cshtmlFile.Uri }
+ Position = { Line = line; Character = character }
+ WorkDoneToken = None
+ PartialResultToken = None
+ Context = None }
+
+ let completion: U2 option =
+ client.Request("textDocument/completion", completionParams0)
+
+ match completion with
+ | Some(U2.C2 cl) ->
+ let expectedItem = cl.Items |> Seq.tryFind (fun i -> i.Label = expectedLabel)
+
+ match expectedItem with
+ | None -> failwithf "an item with Label '%s' was expected for completion at this position" expectedLabel
+ | Some item ->
+ Assert.AreEqual(expectedLabel, item.Label)
+ Assert.IsFalse(item.Detail.IsSome)
+ Assert.IsFalse(item.Documentation.IsSome)
+ Assert.AreEqual(Some expectedCompletionItemKind, item.Kind)
+
+ let itemResolved: CompletionItem = client.Request("completionItem/resolve", item)
+
+ Assert.AreEqual(Some expectedDetail, itemResolved.Detail)
+ Assert.IsTrue(documentationTestFn itemResolved.Documentation)
+
+ | _ -> failwith "Some U2.C1 was expected"
+
+ //
+ // 1st completion test: (@Model.|)
+ //
+ testCompletionResultContainsItem
+ 1u
+ 14u
+ "Output"
+ CompletionItemKind.Property
+ "string? Project.Models.Test.IndexViewModel.Output { get; set; }"
+ _.IsNone
+
+ //
+ // 2nd completion test: @Model.|
+ //
+ testCompletionResultContainsItem
+ 2u
+ 13u
+ "Output"
+ CompletionItemKind.Property
+ "string? Project.Models.Test.IndexViewModel.Output { get; set; }"
+ _.IsNone
+
+ //
+ // 3nd completion test: @Model.Output.|
+ //
+ testCompletionResultContainsItem 3u 13u "ToString" CompletionItemKind.Method "string? object.ToString()" _.IsSome
+
+ //
+ // 4nd completion test: x.
+ //
+ testCompletionResultContainsItem
+ 6u
+ 6u
+ "TryFormat"
+ CompletionItemKind.Method
+ "bool int.TryFormat(Span utf8Destination, out int bytesWritten, [ReadOnlySpan format = default], [IFormatProvider? provider = null])"
+ _.IsSome
diff --git a/tests/CSharpLanguageServer.Tests/DiagnosticTests.fs b/tests/CSharpLanguageServer.Tests/DiagnosticTests.fs
index 765ce983..39e13c0c 100644
--- a/tests/CSharpLanguageServer.Tests/DiagnosticTests.fs
+++ b/tests/CSharpLanguageServer.Tests/DiagnosticTests.fs
@@ -116,7 +116,37 @@ let testPullDiagnosticsWork () =
Assert.AreEqual(0, report.Items.Length)
| _ -> failwith "U2.C1 is expected"
- ()
+
+[]
+let testPullDiagnosticsWorkForRazorFiles () =
+ use client = activateFixture "aspnetProject"
+ use cshtmlFile = client.Open("Project/Views/Test/Index.cshtml")
+
+ let diagnosticParams: DocumentDiagnosticParams =
+ { WorkDoneToken = None
+ PartialResultToken = None
+ TextDocument = { Uri = cshtmlFile.Uri }
+ Identifier = None
+ PreviousResultId = None }
+
+ let report0: DocumentDiagnosticReport option =
+ client.Request("textDocument/diagnostic", diagnosticParams)
+
+ match report0 with
+ | Some(U2.C1 report) ->
+ Assert.AreEqual("full", report.Kind)
+ Assert.AreEqual(None, report.ResultId)
+ Assert.AreEqual(7, report.Items.Length)
+
+ let reportItems = report.Items |> Array.sortBy _.Range
+
+ let diagnostic0 = reportItems[0]
+ Assert.AreEqual(7, diagnostic0.Range.Start.Line)
+ Assert.AreEqual(4, diagnostic0.Range.Start.Character)
+ Assert.AreEqual(Some DiagnosticSeverity.Warning, diagnostic0.Severity)
+ Assert.AreEqual("Unnecessary using directive.", diagnostic0.Message)
+
+ | _ -> failwith "U2.C1 is expected"
[]
@@ -155,7 +185,7 @@ let testWorkspaceDiagnosticsWork () =
let testWorkspaceDiagnosticsWorkWithStreaming () =
use client = activateFixture "testDiagnosticsWork"
- Thread.Sleep(500)
+ Thread.Sleep(1000)
let partialResultToken: ProgressToken = System.Guid.NewGuid() |> string |> U2.C2
diff --git a/tests/CSharpLanguageServer.Tests/DocumentHighlightTests.fs b/tests/CSharpLanguageServer.Tests/DocumentHighlightTests.fs
index 999f1211..259d30b6 100644
--- a/tests/CSharpLanguageServer.Tests/DocumentHighlightTests.fs
+++ b/tests/CSharpLanguageServer.Tests/DocumentHighlightTests.fs
@@ -34,3 +34,26 @@ let ``test textDocument/documentHighlight works in .cs file`` () =
Kind = Some DocumentHighlightKind.Read } ]
Assert.AreEqual(Some expectedHighlights, highlights |> Option.map List.ofArray)
+
+
+[]
+let ``test textDocument/documentHighlight works in .cshtml file`` () =
+ use client = activateFixture "aspnetProject"
+ use indexCshtmlFile = client.Open("Project/Views/Test/Index.cshtml")
+
+ let highlightParams: DocumentHighlightParams =
+ { TextDocument = { Uri = indexCshtmlFile.Uri }
+ Position = { Line = 1u; Character = 1u }
+ WorkDoneToken = None
+ PartialResultToken = None }
+
+ let highlights: DocumentHighlight[] option =
+ client.Request("textDocument/documentHighlight", highlightParams)
+
+ let expectedHighlights: DocumentHighlight list =
+ [ { Range =
+ { Start = { Line = 1u; Character = 1u }
+ End = { Line = 1u; Character = 6u } }
+ Kind = Some DocumentHighlightKind.Read } ]
+
+ Assert.AreEqual(Some expectedHighlights, highlights |> Option.map List.ofArray)
diff --git a/tests/CSharpLanguageServer.Tests/Fixtures/aspnetProject/Project/Views/Test/CompletionTests.cshtml b/tests/CSharpLanguageServer.Tests/Fixtures/aspnetProject/Project/Views/Test/CompletionTests.cshtml
new file mode 100644
index 00000000..3a22cf69
--- /dev/null
+++ b/tests/CSharpLanguageServer.Tests/Fixtures/aspnetProject/Project/Views/Test/CompletionTests.cshtml
@@ -0,0 +1,8 @@
+@model Project.Models.Test.IndexViewModel
+(@Model.)
+@Model.
+@Model.Output.
+@{
+ var x = 1;
+ x.
+}
diff --git a/tests/CSharpLanguageServer.Tests/Fixtures/genericProject/Project/ClassWithExtensionMethods.cs b/tests/CSharpLanguageServer.Tests/Fixtures/genericProject/Project/ClassWithExtensionMethods.cs
new file mode 100644
index 00000000..f1ba74c2
--- /dev/null
+++ b/tests/CSharpLanguageServer.Tests/Fixtures/genericProject/Project/ClassWithExtensionMethods.cs
@@ -0,0 +1,15 @@
+class ClassForCompletionWithExtensionMethods
+{
+ public void MethodA(string arg)
+ {
+ this.
+ }
+}
+
+public static class ClassExtensions
+{
+ public static string MethodB(this ClassForCompletionWithExtensionMethods input)
+ {
+ return "ok";
+ }
+}
diff --git a/tests/CSharpLanguageServer.Tests/HoverTests.fs b/tests/CSharpLanguageServer.Tests/HoverTests.fs
index f1ce31eb..415460a2 100644
--- a/tests/CSharpLanguageServer.Tests/HoverTests.fs
+++ b/tests/CSharpLanguageServer.Tests/HoverTests.fs
@@ -47,19 +47,13 @@ let testHoverWorks () =
Assert.IsTrue(hover1.IsSome)
match hover1 with
- | Some hover ->
- match hover.Contents with
- | U3.C1 c ->
- Assert.AreEqual(MarkupKind.Markdown, c.Kind)
-
- Assert.AreEqual(
- "```csharp\nstring\n```\n\nRepresents text as a sequence of UTF-16 code units.",
- c.Value.ReplaceLineEndings("\n")
- )
- | _ -> failwith "C1 was expected"
-
- Assert.IsTrue(hover.Range.IsNone)
+ | Some { Contents = U3.C1 c } ->
+ Assert.AreEqual(MarkupKind.Markdown, c.Kind)
+ Assert.AreEqual(
+ "```csharp\nstring\n```\n\nRepresents text as a sequence of UTF-16 code units.",
+ c.Value.ReplaceLineEndings("\n")
+ )
| _ -> failwith "Some (U3.C1 c) was expected"
//
@@ -73,3 +67,26 @@ let testHoverWorks () =
let hover2: Hover option = client.Request("textDocument/hover", hover2Params)
Assert.IsTrue(hover2.IsNone)
+
+
+[]
+let testHoverWorksInRazorFile () =
+ use client = activateFixture "aspnetProject"
+
+ use indexCshtmlFile = client.Open("Project/Views/Test/Index.cshtml")
+
+ let hover0Params: HoverParams =
+ { TextDocument = { Uri = indexCshtmlFile.Uri }
+ Position = { Line = 1u; Character = 7u }
+ WorkDoneToken = None }
+
+ let hover0: Hover option = client.Request("textDocument/hover", hover0Params)
+
+ Assert.IsTrue(hover0.IsSome)
+
+ match hover0 with
+ | Some { Contents = U3.C1 c } ->
+ Assert.AreEqual(MarkupKind.Markdown, c.Kind)
+ Assert.AreEqual("```csharp\nstring? IndexViewModel.Output\n```", c.Value.ReplaceLineEndings("\n"))
+
+ | _ -> failwith "Some (U3.C1 c) was expected"
diff --git a/tests/CSharpLanguageServer.Tests/InitializationTests.fs b/tests/CSharpLanguageServer.Tests/InitializationTests.fs
index 604bb791..c64f707b 100644
--- a/tests/CSharpLanguageServer.Tests/InitializationTests.fs
+++ b/tests/CSharpLanguageServer.Tests/InitializationTests.fs
@@ -67,13 +67,18 @@ let testServerRegistersCapabilitiesWithTheClient () =
Assert.AreEqual(null, serverCaps.InlineValueProvider)
+ let expectedDocumentSelector =
+ [| U2.C1
+ { Language = Some "csharp"
+ Scheme = Some "file"
+ Pattern = Some "**/*.cs" }
+ U2.C1
+ { Language = Some "razor"
+ Scheme = Some "file"
+ Pattern = Some "**/*.cshtml" } |]
+
Assert.AreEqual(
- { DocumentSelector =
- Some
- [| U2.C1
- { Language = Some "csharp"
- Scheme = Some "file"
- Pattern = Some "**/*.cs" } |]
+ { DocumentSelector = Some expectedDocumentSelector
WorkDoneProgress = None
Identifier = None
InterFileDependencies = false
diff --git a/tests/CSharpLanguageServer.Tests/ReferenceTests.fs b/tests/CSharpLanguageServer.Tests/ReferenceTests.fs
index 07e7bba6..29d78ac9 100644
--- a/tests/CSharpLanguageServer.Tests/ReferenceTests.fs
+++ b/tests/CSharpLanguageServer.Tests/ReferenceTests.fs
@@ -142,14 +142,16 @@ let testReferenceWorksDotnet8 () =
Assert.AreEqual(expectedLocations2, locations2.Value)
-
[]
-let testReferenceWorksToAspNetRazorPageReferencedValue () =
+let testReferenceWorksToRazorPageReferencedValue () =
use client = activateFixture "aspnetProject"
- use testIndexViewModelCsFile = client.Open("Project/Models/Test/IndexViewModel.cs")
- use testControllerCsFile = client.Open("Project/Controllers/TestController.cs")
- use viewsTestIndexCshtmlFile = client.Open("Project/Views/Test/Index.cshtml")
+ use testIndexViewModelCsFile = client.Open "Project/Models/Test/IndexViewModel.cs"
+ use testControllerCsFile = client.Open "Project/Controllers/TestController.cs"
+ use indexCshtmlFile = client.Open "Project/Views/Test/Index.cshtml"
+
+ use completionTestsCshtmlFile =
+ client.Open "Project/Views/Test/CompletionTests.cshtml"
let referenceParams0: ReferenceParams =
{ TextDocument = { Uri = testIndexViewModelCsFile.Uri }
@@ -161,11 +163,21 @@ let testReferenceWorksToAspNetRazorPageReferencedValue () =
let locations0: Location[] option =
client.Request("textDocument/references", referenceParams0)
- Assert.IsTrue(locations0.IsSome)
- Assert.AreEqual(2, locations0.Value.Length)
+ Assert.IsTrue locations0.IsSome
+ Assert.AreEqual(3, locations0.Value.Length)
let expectedLocations0: Location array =
- [| { Uri = viewsTestIndexCshtmlFile.Uri
+ [| { Uri = testControllerCsFile.Uri
+ Range =
+ { Start = { Line = 11u; Character = 12u }
+ End = { Line = 11u; Character = 18u } } }
+
+ { Uri = completionTestsCshtmlFile.Uri
+ Range =
+ { Start = { Line = 3u; Character = 13u }
+ End = { Line = 3u; Character = 19u } } }
+
+ { Uri = indexCshtmlFile.Uri
Range =
{ Start = { Line = 1u; Character = 7u }
End = { Line = 1u; Character = 13u } } }
@@ -177,7 +189,7 @@ let testReferenceWorksToAspNetRazorPageReferencedValue () =
let sortedLocations0 =
locations0.Value
- |> Array.sortBy (fun f -> (f.Range.Start.Line, f.Range.Start.Character))
+ |> Array.sortBy (fun f -> f.Range.Start.Line, f.Range.Start.Character)
Assert.AreEqual(expectedLocations0, sortedLocations0)
@@ -195,14 +207,19 @@ let testReferenceWorksToAspNetRazorPageReferencedValue () =
client.Request("textDocument/references", referenceParams1)
Assert.IsTrue(locations1.IsSome)
- Assert.AreEqual(5, locations1.Value.Length)
+ Assert.AreEqual(6, locations1.Value.Length)
let expectedLocations1: Location array =
- [| { Uri = viewsTestIndexCshtmlFile.Uri
+ [| { Uri = indexCshtmlFile.Uri
Range =
{ Start = { Line = 1u; Character = 7u }
End = { Line = 1u; Character = 13u } } }
+ { Uri = completionTestsCshtmlFile.Uri
+ Range =
+ { Start = { Line = 3u; Character = 13u }
+ End = { Line = 3u; Character = 19u } } }
+
{ Uri = testIndexViewModelCsFile.Uri
Range =
{ Start = { Line = 3u; Character = 19u }
@@ -228,3 +245,65 @@ let testReferenceWorksToAspNetRazorPageReferencedValue () =
|> Array.sortBy (fun f -> (f.Range.Start.Line, f.Range.Start.Character))
Assert.AreEqual(expectedLocations1, sortedLocations1)
+
+
+[]
+let testReferenceWorksFromRazorPageReferencedValue () =
+ use client = activateFixture "aspnetProject"
+
+ use testIndexViewModelCsFile = client.Open("Project/Models/Test/IndexViewModel.cs")
+ use testControllerCsFile = client.Open("Project/Controllers/TestController.cs")
+ use indexCshtmlFile = client.Open("Project/Views/Test/Index.cshtml")
+
+ use completionTestsCshtmlFile =
+ client.Open("Project/Views/Test/CompletionTests.cshtml")
+
+ let referenceParams0: ReferenceParams =
+ { TextDocument = { Uri = indexCshtmlFile.Uri }
+ Position = { Line = 1u; Character = 7u }
+ WorkDoneToken = None
+ PartialResultToken = None
+ Context = { IncludeDeclaration = true } }
+
+ let locations0: Location[] option =
+ client.Request("textDocument/references", referenceParams0)
+
+ Assert.IsTrue(locations0.IsSome)
+ Assert.AreEqual(6, locations0.Value.Length)
+
+ let expectedLocations0: Location array =
+ [| { Uri = indexCshtmlFile.Uri
+ Range =
+ { Start = { Line = 1u; Character = 7u }
+ End = { Line = 1u; Character = 13u } } }
+
+ { Uri = completionTestsCshtmlFile.Uri
+ Range =
+ { Start = { Line = 3u; Character = 13u }
+ End = { Line = 3u; Character = 19u } } }
+
+ { Uri = testIndexViewModelCsFile.Uri
+ Range =
+ { Start = { Line = 3u; Character = 19u }
+ End = { Line = 3u; Character = 25u } } }
+
+ { Uri = testIndexViewModelCsFile.Uri
+ Range =
+ { Start = { Line = 3u; Character = 28u }
+ End = { Line = 3u; Character = 31u } } }
+
+ { Uri = testIndexViewModelCsFile.Uri
+ Range =
+ { Start = { Line = 3u; Character = 33u }
+ End = { Line = 3u; Character = 36u } } }
+
+ { Uri = testControllerCsFile.Uri
+ Range =
+ { Start = { Line = 11u; Character = 12u }
+ End = { Line = 11u; Character = 18u } } } |]
+
+ let sortedLocations0 =
+ locations0.Value
+ |> Array.sortBy (fun f -> (f.Range.Start.Line, f.Range.Start.Character))
+
+ Assert.AreEqual(expectedLocations0, sortedLocations0)
diff --git a/tests/CSharpLanguageServer.Tests/WorkspaceSymbolTests.fs b/tests/CSharpLanguageServer.Tests/WorkspaceSymbolTests.fs
index 8e1c1700..02b083f9 100644
--- a/tests/CSharpLanguageServer.Tests/WorkspaceSymbolTests.fs
+++ b/tests/CSharpLanguageServer.Tests/WorkspaceSymbolTests.fs
@@ -4,6 +4,7 @@ open NUnit.Framework
open Ionide.LanguageServerProtocol.Types
open CSharpLanguageServer.Tests.Tooling
+open System.Threading
[]
let testWorkspaceSymbolWorks () =
@@ -24,7 +25,7 @@ let testWorkspaceSymbolWorks () =
match symbols0 with
| Some(U2.C1 sis) ->
- Assert.AreEqual(4, sis.Length)
+ Assert.AreEqual(6, sis.Length)
let sym0 = sis[0]
Assert.AreEqual("Class", sym0.Name)