Skip to content

Commit f02caa1

Browse files
committed
squash
1 parent 09cf511 commit f02caa1

File tree

19 files changed

+808
-186
lines changed

19 files changed

+808
-186
lines changed

src/CSharpLanguageServer/CSharpLanguageServer.fsproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
<PackageReadmeFile>README.md</PackageReadmeFile>
1818
<ChangelogFile>CHANGELOG.md</ChangelogFile>
1919
<Nullable>enable</Nullable>
20-
<MSBuildTreatWarningsAsErrors>true</MSBuildTreatWarningsAsErrors>
2120
</PropertyGroup>
2221

2322
<ItemGroup>

src/CSharpLanguageServer/Handlers/Completion.fs

Lines changed: 137 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@ namespace CSharpLanguageServer.Handlers
33
open System
44
open System.Reflection
55

6+
open Microsoft.CodeAnalysis
7+
open Microsoft.CodeAnalysis.Text
68
open Microsoft.Extensions.Caching.Memory
79
open Ionide.LanguageServerProtocol.Server
810
open Ionide.LanguageServerProtocol.Types
911
open Ionide.LanguageServerProtocol.JsonRpc
12+
open Microsoft.Extensions.Logging
1013

1114
open CSharpLanguageServer.State
1215
open CSharpLanguageServer.Util
1316
open CSharpLanguageServer.Roslyn.Conversions
17+
open CSharpLanguageServer.Roslyn.Solution
1418
open CSharpLanguageServer.Logging
1519

20+
1621
[<RequireQualifiedAccess>]
1722
module Completion =
18-
let private _logger = Logging.getLoggerByName "Completion"
23+
let private logger = Logging.getLoggerByName "Completion"
1924

2025
let private completionItemMemoryCache = new MemoryCache(new MemoryCacheOptions())
2126

@@ -180,13 +185,113 @@ module Completion =
180185
synopsis, documentationText
181186
| _, _ -> None, None
182187

183-
let handle
188+
let codeActionContextToCompletionTrigger (context: CompletionContext option) =
189+
context
190+
|> Option.bind (fun ctx ->
191+
match ctx.TriggerKind with
192+
| CompletionTriggerKind.Invoked
193+
| CompletionTriggerKind.TriggerForIncompleteCompletions ->
194+
Some Microsoft.CodeAnalysis.Completion.CompletionTrigger.Invoke
195+
| CompletionTriggerKind.TriggerCharacter ->
196+
ctx.TriggerCharacter
197+
|> Option.map Seq.head
198+
|> Option.map Microsoft.CodeAnalysis.Completion.CompletionTrigger.CreateInsertionTrigger
199+
| _ -> None)
200+
|> Option.defaultValue Microsoft.CodeAnalysis.Completion.CompletionTrigger.Invoke
201+
202+
let getCompletionsForRazorDocument
203+
(p: CompletionParams)
184204
(context: ServerRequestContext)
205+
: Async<option<Microsoft.CodeAnalysis.Completion.CompletionList * Document>> =
206+
async {
207+
match! solutionGetRazorDocumentForUri context.Solution p.TextDocument.Uri with
208+
| None -> return None
209+
| Some(project, compilation, cshtmlTree) ->
210+
let! ct = Async.CancellationToken
211+
let! sourceText = cshtmlTree.GetTextAsync() |> Async.AwaitTask
212+
213+
let razorTextDocument =
214+
context.Solution.Projects
215+
|> Seq.collect (fun p -> p.AdditionalDocuments)
216+
|> Seq.filter (fun d -> Uri(d.FilePath, UriKind.Absolute) = Uri p.TextDocument.Uri)
217+
|> Seq.head
218+
219+
let! razorSourceText = razorTextDocument.GetTextAsync() |> Async.AwaitTask
220+
221+
let posInCshtml = Position.toRoslynPosition sourceText.Lines p.Position
222+
//logger.LogInformation("posInCshtml={posInCshtml=}", posInCshtml)
223+
let pos = p.Position
224+
225+
let root = cshtmlTree.GetRoot()
226+
227+
let mutable positionAndToken: (int * SyntaxToken) option = None
228+
229+
for t in root.DescendantTokens() do
230+
let cshtmlSpan = cshtmlTree.GetMappedLineSpan(t.Span)
231+
232+
if
233+
cshtmlSpan.StartLinePosition.Line = (int pos.Line)
234+
&& cshtmlSpan.EndLinePosition.Line = (int pos.Line)
235+
&& cshtmlSpan.StartLinePosition.Character <= (int pos.Character)
236+
then
237+
let tokenStartCharacterOffset =
238+
(int pos.Character - cshtmlSpan.StartLinePosition.Character)
239+
240+
positionAndToken <- Some(t.Span.Start + tokenStartCharacterOffset, t)
241+
242+
match positionAndToken with
243+
| None -> return None
244+
| Some(position, tokenForPosition) ->
245+
246+
let newSourceText =
247+
let cshtmlPosition = Position.toRoslynPosition razorSourceText.Lines p.Position
248+
let charInCshtml: char = razorSourceText[cshtmlPosition - 1]
249+
250+
if charInCshtml = '.' && string tokenForPosition.Value <> "." then
251+
// a hack to make <span>@Model.|</span> autocompletion to work:
252+
// - force a dot if present on .cscshtml but missing on .cs
253+
sourceText.WithChanges(new TextChange(new TextSpan(position - 1, 0), "."))
254+
else
255+
sourceText
256+
257+
let cshtmlPath = Uri.toPath p.TextDocument.Uri
258+
let! doc = solutionTryAddDocument (cshtmlPath + ".cs") (newSourceText.ToString()) context.Solution
259+
260+
match doc with
261+
| None -> return None
262+
| Some doc ->
263+
let completionService =
264+
Microsoft.CodeAnalysis.Completion.CompletionService.GetService(doc)
265+
|> RoslynCompletionServiceWrapper
266+
267+
let completionOptions =
268+
RoslynCompletionOptions.Default()
269+
|> _.WithBool("ShowItemsFromUnimportedNamespaces", false)
270+
|> _.WithBool("ShowNameSuggestions", false)
271+
272+
let completionTrigger = p.Context |> codeActionContextToCompletionTrigger
273+
274+
let! roslynCompletions =
275+
completionService.GetCompletionsAsync(
276+
doc,
277+
position,
278+
completionOptions,
279+
completionTrigger,
280+
ct
281+
)
282+
|> Async.map Option.ofObj
283+
284+
return roslynCompletions |> Option.map (fun rcl -> rcl, doc)
285+
}
286+
287+
let getCompletionsForCSharpDocument
185288
(p: CompletionParams)
186-
: Async<LspResult<U2<CompletionItem array, CompletionList> option>> =
289+
(context: ServerRequestContext)
290+
: Async<option<Microsoft.CodeAnalysis.Completion.CompletionList * Document>> =
187291
async {
188292
match context.GetDocument p.TextDocument.Uri with
189-
| None -> return None |> LspResult.success
293+
| None -> return None
294+
190295
| Some doc ->
191296
let! ct = Async.CancellationToken
192297
let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
@@ -202,19 +307,7 @@ module Completion =
202307
|> _.WithBool("ShowItemsFromUnimportedNamespaces", false)
203308
|> _.WithBool("ShowNameSuggestions", false)
204309

205-
let completionTrigger =
206-
p.Context
207-
|> Option.bind (fun ctx ->
208-
match ctx.TriggerKind with
209-
| CompletionTriggerKind.Invoked
210-
| CompletionTriggerKind.TriggerForIncompleteCompletions ->
211-
Some Microsoft.CodeAnalysis.Completion.CompletionTrigger.Invoke
212-
| CompletionTriggerKind.TriggerCharacter ->
213-
ctx.TriggerCharacter
214-
|> Option.map Seq.head
215-
|> Option.map Microsoft.CodeAnalysis.Completion.CompletionTrigger.CreateInsertionTrigger
216-
| _ -> None)
217-
|> Option.defaultValue Microsoft.CodeAnalysis.Completion.CompletionTrigger.Invoke
310+
let completionTrigger = p.Context |> codeActionContextToCompletionTrigger
218311

219312
let shouldTriggerCompletion =
220313
p.Context
@@ -228,6 +321,23 @@ module Completion =
228321
else
229322
async.Return None
230323

324+
return roslynCompletions |> Option.map (fun rcl -> rcl, doc)
325+
}
326+
327+
let handle
328+
(context: ServerRequestContext)
329+
(p: CompletionParams)
330+
: Async<LspResult<U2<CompletionItem array, CompletionList> option>> =
331+
async {
332+
let getCompletions =
333+
if p.TextDocument.Uri.EndsWith(".cshtml") then
334+
getCompletionsForRazorDocument
335+
else
336+
getCompletionsForCSharpDocument
337+
338+
match! getCompletions p context with
339+
| None -> return None |> LspResult.success
340+
| Some(roslynCompletions, doc) ->
231341
let toLspCompletionItemsWithCacheInfo (completions: Microsoft.CodeAnalysis.Completion.CompletionList) =
232342
completions.ItemsList
233343
|> Seq.map (fun item -> (item, Guid.NewGuid() |> string))
@@ -244,22 +354,21 @@ module Completion =
244354
|> Array.ofSeq
245355

246356
let lspCompletionItemsWithCacheInfo =
247-
roslynCompletions |> Option.map toLspCompletionItemsWithCacheInfo
357+
roslynCompletions |> toLspCompletionItemsWithCacheInfo
248358

249359
// cache roslyn completion items
250-
for (_, cacheItemId, roslynDoc, roslynItem) in
251-
(lspCompletionItemsWithCacheInfo |> Option.defaultValue Array.empty) do
360+
for (_, cacheItemId, roslynDoc, roslynItem) in lspCompletionItemsWithCacheInfo do
252361
completionItemMemoryCacheSet cacheItemId roslynDoc roslynItem
253362

363+
let items =
364+
lspCompletionItemsWithCacheInfo |> Array.map (fun (item, _, _, _) -> item)
365+
254366
return
255-
lspCompletionItemsWithCacheInfo
256-
|> Option.map (fun itemsWithCacheInfo ->
257-
itemsWithCacheInfo |> Array.map (fun (item, _, _, _) -> item))
258-
|> Option.map (fun items ->
259-
{ IsIncomplete = true
260-
Items = items
261-
ItemDefaults = None })
262-
|> Option.map U2.C2
367+
{ IsIncomplete = true
368+
Items = items
369+
ItemDefaults = None }
370+
|> U2.C2
371+
|> Some
263372
|> LspResult.success
264373
}
265374

src/CSharpLanguageServer/Handlers/Diagnostic.fs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ open CSharpLanguageServer.Util
1212

1313
[<RequireQualifiedAccess>]
1414
module Diagnostic =
15-
let provider
16-
(clientCapabilities: ClientCapabilities)
17-
: U2<DiagnosticOptions, DiagnosticRegistrationOptions> option =
15+
let provider (_cc: ClientCapabilities) : U2<DiagnosticOptions, DiagnosticRegistrationOptions> option =
1816
let registrationOptions: DiagnosticRegistrationOptions =
1917
{ DocumentSelector = Some defaultDocumentSelector
2018
WorkDoneProgress = None
@@ -36,24 +34,18 @@ module Diagnostic =
3634
Items = [||]
3735
RelatedDocuments = None }
3836

39-
match context.GetDocument p.TextDocument.Uri with
40-
| None -> return emptyReport |> U2.C1 |> LspResult.success
37+
let! semanticModel = context.GetSemanticModel p.TextDocument.Uri
4138

42-
| Some doc ->
43-
let! ct = Async.CancellationToken
44-
let! semanticModelMaybe = doc.GetSemanticModelAsync(ct) |> Async.AwaitTask
45-
46-
match semanticModelMaybe |> Option.ofObj with
39+
let diagnostics =
40+
match semanticModel with
41+
| None -> [||]
4742
| Some semanticModel ->
48-
let diagnostics =
49-
semanticModel.GetDiagnostics()
50-
|> Seq.map Diagnostic.fromRoslynDiagnostic
51-
|> Seq.map fst
52-
|> Array.ofSeq
53-
54-
return { emptyReport with Items = diagnostics } |> U2.C1 |> LspResult.success
43+
semanticModel.GetDiagnostics()
44+
|> Seq.map Diagnostic.fromRoslynDiagnostic
45+
|> Seq.map fst
46+
|> Array.ofSeq
5547

56-
| None -> return emptyReport |> U2.C1 |> LspResult.success
48+
return { emptyReport with Items = diagnostics } |> U2.C1 |> LspResult.success
5749
}
5850

5951
let private getWorkspaceDiagnosticReports

src/CSharpLanguageServer/Handlers/DocumentHighlight.fs

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,53 +13,53 @@ open CSharpLanguageServer.Util
1313

1414
[<RequireQualifiedAccess>]
1515
module DocumentHighlight =
16-
let provider (_: ClientCapabilities) : U2<bool, DocumentHighlightOptions> option = Some(U2.C1 true)
16+
let provider (_cc: ClientCapabilities) : U2<bool, DocumentHighlightOptions> option = Some(U2.C1 true)
1717

1818
let private shouldHighlight (symbol: ISymbol) =
1919
match symbol with
2020
| :? INamespaceSymbol -> false
2121
| _ -> true
2222

23-
let handle
24-
(context: ServerRequestContext)
25-
(p: DocumentHighlightParams)
26-
: AsyncLspResult<DocumentHighlight[] option> =
27-
async {
28-
let! ct = Async.CancellationToken
29-
let filePath = Uri.toPath p.TextDocument.Uri
23+
// We only need to find references in the file (not the whole workspace), so we don't use
24+
// context.FindSymbol & context.FindReferences here.
25+
let private getHighlights symbol (project: Project) (docMaybe: Document option) (filePath: string) = async {
26+
let! ct = Async.CancellationToken
3027

31-
// We only need to find references in the file (not the whole workspace), so we don't use
32-
// context.FindSymbol & context.FindReferences here.
33-
let getHighlights (symbol: ISymbol) (doc: Document) = async {
34-
let docSet = ImmutableHashSet.Create(doc)
28+
let docSet: ImmutableHashSet<Document> option =
29+
docMaybe |> Option.map (fun doc -> ImmutableHashSet.Create(doc))
3530

36-
let! refs =
37-
SymbolFinder.FindReferencesAsync(symbol, doc.Project.Solution, docSet, cancellationToken = ct)
38-
|> Async.AwaitTask
31+
let! refs =
32+
SymbolFinder.FindReferencesAsync(symbol, project.Solution, docSet |> Option.toObj, cancellationToken = ct)
33+
|> Async.AwaitTask
3934

40-
let! def =
41-
SymbolFinder.FindSourceDefinitionAsync(symbol, doc.Project.Solution, cancellationToken = ct)
42-
|> Async.AwaitTask
35+
let! def =
36+
SymbolFinder.FindSourceDefinitionAsync(symbol, project.Solution, cancellationToken = ct)
37+
|> Async.AwaitTask
4338

44-
let locations =
45-
refs
46-
|> Seq.collect (fun r -> r.Locations)
47-
|> Seq.map (fun rl -> rl.Location)
48-
|> Seq.filter (fun l -> l.IsInSource && l.GetMappedLineSpan().Path = filePath)
49-
|> Seq.append (def |> Option.ofObj |> Option.toList |> Seq.collect (fun sym -> sym.Locations))
39+
let locations =
40+
refs
41+
|> Seq.collect (fun r -> r.Locations)
42+
|> Seq.map (fun rl -> rl.Location)
43+
|> Seq.filter (fun l -> l.IsInSource && l.GetMappedLineSpan().Path = filePath)
44+
|> Seq.append (def |> Option.ofObj |> Option.toList |> Seq.collect (fun sym -> sym.Locations))
5045

51-
return
52-
locations
53-
|> Seq.choose Location.fromRoslynLocation
54-
|> Seq.map (fun l ->
55-
{ Range = l.Range
56-
Kind = Some DocumentHighlightKind.Read })
57-
}
46+
return
47+
locations
48+
|> Seq.choose Location.fromRoslynLocation
49+
|> Seq.map (fun l ->
50+
{ Range = l.Range
51+
Kind = Some DocumentHighlightKind.Read })
52+
}
5853

54+
let handle
55+
(context: ServerRequestContext)
56+
(p: DocumentHighlightParams)
57+
: AsyncLspResult<DocumentHighlight[] option> =
58+
async {
5959
match! context.FindSymbol' p.TextDocument.Uri p.Position with
60-
| Some(symbol, _, Some doc) ->
60+
| Some(symbol, project, docMaybe) ->
6161
if shouldHighlight symbol then
62-
let! highlights = getHighlights symbol doc
62+
let! highlights = getHighlights symbol project docMaybe (Uri.toPath p.TextDocument.Uri)
6363
return highlights |> Seq.toArray |> Some |> LspResult.success
6464
else
6565
return None |> LspResult.success

0 commit comments

Comments
 (0)