Skip to content

Commit 79a0758

Browse files
committed
squash
1 parent d13a7e9 commit 79a0758

File tree

20 files changed

+804
-187
lines changed

20 files changed

+804
-187
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
@@ -11,9 +11,7 @@ open CSharpLanguageServer.Types
1111

1212
[<RequireQualifiedAccess>]
1313
module Diagnostic =
14-
let provider
15-
(clientCapabilities: ClientCapabilities)
16-
: U2<DiagnosticOptions, DiagnosticRegistrationOptions> option =
14+
let provider (_cc: ClientCapabilities) : U2<DiagnosticOptions, DiagnosticRegistrationOptions> option =
1715
let registrationOptions: DiagnosticRegistrationOptions =
1816
{ DocumentSelector = Some defaultDocumentSelector
1917
WorkDoneProgress = None
@@ -35,24 +33,18 @@ module Diagnostic =
3533
Items = [||]
3634
RelatedDocuments = None }
3735

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

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

55-
| None -> return emptyReport |> U2.C1 |> LspResult.success
47+
return { emptyReport with Items = diagnostics } |> U2.C1 |> LspResult.success
5648
}
5749

5850
let private getWorkspaceDiagnosticReports

src/CSharpLanguageServer/Handlers/DocumentHighlight.fs

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,53 +12,53 @@ open CSharpLanguageServer.Roslyn.Conversions
1212

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

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

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

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

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

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

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

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

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

0 commit comments

Comments
 (0)