Skip to content

Commit 0b3fac9

Browse files
committed
squash
1 parent 0f4a53f commit 0b3fac9

File tree

19 files changed

+824
-236
lines changed

19 files changed

+824
-236
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: 139 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,25 @@ 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
open CSharpLanguageServer.Lsp.Workspace
1620

21+
1722
[<RequireQualifiedAccess>]
1823
module Completion =
19-
let private _logger = Logging.getLoggerByName "Completion"
24+
let private logger = Logging.getLoggerByName "Completion"
2025

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

@@ -181,16 +186,118 @@ module Completion =
181186
synopsis, documentationText
182187
| _, _ -> None, None
183188

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

192299
match docForUri with
193-
| None -> return None |> LspResult.success
300+
| None -> return None
194301
| Some doc ->
195302
let! ct = Async.CancellationToken
196303
let! sourceText = doc.GetTextAsync(ct) |> Async.AwaitTask
@@ -206,19 +313,7 @@ module Completion =
206313
|> _.WithBool("ShowItemsFromUnimportedNamespaces", false)
207314
|> _.WithBool("ShowNameSuggestions", false)
208315

209-
let completionTrigger =
210-
p.Context
211-
|> Option.bind (fun ctx ->
212-
match ctx.TriggerKind with
213-
| CompletionTriggerKind.Invoked
214-
| CompletionTriggerKind.TriggerForIncompleteCompletions ->
215-
Some Microsoft.CodeAnalysis.Completion.CompletionTrigger.Invoke
216-
| CompletionTriggerKind.TriggerCharacter ->
217-
ctx.TriggerCharacter
218-
|> Option.map Seq.head
219-
|> Option.map Microsoft.CodeAnalysis.Completion.CompletionTrigger.CreateInsertionTrigger
220-
| _ -> None)
221-
|> Option.defaultValue Microsoft.CodeAnalysis.Completion.CompletionTrigger.Invoke
316+
let completionTrigger = p.Context |> codeActionContextToCompletionTrigger
222317

223318
let shouldTriggerCompletion =
224319
p.Context
@@ -232,6 +327,23 @@ module Completion =
232327
else
233328
async.Return None
234329

330+
return roslynCompletions |> Option.map (fun rcl -> rcl, doc)
331+
}
332+
333+
let handle
334+
(context: ServerRequestContext)
335+
(p: CompletionParams)
336+
: Async<LspResult<U2<CompletionItem array, CompletionList> option>> =
337+
async {
338+
let getCompletions =
339+
if p.TextDocument.Uri.EndsWith(".cshtml") then
340+
getCompletionsForRazorDocument
341+
else
342+
getCompletionsForCSharpDocument
343+
344+
match! getCompletions p context with
345+
| None -> return None |> LspResult.success
346+
| Some(roslynCompletions, doc) ->
235347
let toLspCompletionItemsWithCacheInfo (completions: Microsoft.CodeAnalysis.Completion.CompletionList) =
236348
completions.ItemsList
237349
|> Seq.map (fun item -> (item, Guid.NewGuid() |> string))
@@ -248,22 +360,21 @@ module Completion =
248360
|> Array.ofSeq
249361

250362
let lspCompletionItemsWithCacheInfo =
251-
roslynCompletions |> Option.map toLspCompletionItemsWithCacheInfo
363+
roslynCompletions |> toLspCompletionItemsWithCacheInfo
252364

253365
// cache roslyn completion items
254-
for (_, cacheItemId, roslynDoc, roslynItem) in
255-
(lspCompletionItemsWithCacheInfo |> Option.defaultValue Array.empty) do
366+
for (_, cacheItemId, roslynDoc, roslynItem) in lspCompletionItemsWithCacheInfo do
256367
completionItemMemoryCacheSet cacheItemId roslynDoc roslynItem
257368

369+
let items =
370+
lspCompletionItemsWithCacheInfo |> Array.map (fun (item, _, _, _) -> item)
371+
258372
return
259-
lspCompletionItemsWithCacheInfo
260-
|> Option.map (fun itemsWithCacheInfo ->
261-
itemsWithCacheInfo |> Array.map (fun (item, _, _, _) -> item))
262-
|> Option.map (fun items ->
263-
{ IsIncomplete = true
264-
Items = items
265-
ItemDefaults = None })
266-
|> Option.map U2.C2
373+
{ IsIncomplete = true
374+
Items = items
375+
ItemDefaults = None }
376+
|> U2.C2
377+
|> Some
267378
|> LspResult.success
268379
}
269380

src/CSharpLanguageServer/Handlers/Diagnostic.fs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,9 @@ open CSharpLanguageServer.Types
1111
open CSharpLanguageServer.Util
1212
open CSharpLanguageServer.Lsp.Workspace
1313

14-
1514
[<RequireQualifiedAccess>]
1615
module Diagnostic =
17-
let provider
18-
(clientCapabilities: ClientCapabilities)
19-
: U2<DiagnosticOptions, DiagnosticRegistrationOptions> option =
16+
let provider (_cc: ClientCapabilities) : U2<DiagnosticOptions, DiagnosticRegistrationOptions> option =
2017
let registrationOptions: DiagnosticRegistrationOptions =
2118
{ DocumentSelector = Some defaultDocumentSelector
2219
WorkDoneProgress = None
@@ -38,27 +35,18 @@ module Diagnostic =
3835
Items = [||]
3936
RelatedDocuments = None }
4037

41-
let wf, docForUri =
42-
p.TextDocument.Uri |> workspaceDocument context.Workspace AnyDocument
43-
44-
match docForUri with
45-
| None -> return emptyReport |> U2.C1 |> LspResult.success
38+
let! wf, semanticModel = p.TextDocument.Uri |> workspaceDocumentSemanticModel context.Workspace
4639

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

61-
| None -> return emptyReport |> U2.C1 |> LspResult.success
49+
return { emptyReport with Items = diagnostics } |> U2.C1 |> LspResult.success
6250
}
6351

6452
let private getWorkspaceDiagnosticReports

src/CSharpLanguageServer/Handlers/DocumentHighlight.fs

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,46 +21,46 @@ module DocumentHighlight =
2121
| :? INamespaceSymbol -> false
2222
| _ -> true
2323

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

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

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

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

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

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

55+
let handle
56+
(context: ServerRequestContext)
57+
(p: DocumentHighlightParams)
58+
: AsyncLspResult<DocumentHighlight[] option> =
59+
async {
6060
match! workspaceDocumentSymbol context.Workspace AnyDocument p.TextDocument.Uri p.Position with
61-
| Some wf, Some(symbol, _, Some doc) ->
61+
| Some _wf, Some(symbol, project, docMaybe) ->
6262
if shouldHighlight symbol then
63-
let! highlights = getHighlights symbol doc
63+
let! highlights = getHighlights symbol project docMaybe (Uri.toPath p.TextDocument.Uri)
6464
return highlights |> Seq.toArray |> Some |> LspResult.success
6565
else
6666
return None |> LspResult.success

0 commit comments

Comments
 (0)