@@ -3,19 +3,23 @@ namespace CSharpLanguageServer.Handlers
33open System
44open System.Reflection
55
6+ open Microsoft.CodeAnalysis
7+ open Microsoft.CodeAnalysis .Text
68open Microsoft.Extensions .Caching .Memory
79open Ionide.LanguageServerProtocol .Server
810open Ionide.LanguageServerProtocol .Types
911open Ionide.LanguageServerProtocol .JsonRpc
12+ open Microsoft.Extensions .Logging
1013
1114open CSharpLanguageServer.State
1215open CSharpLanguageServer.Util
1316open CSharpLanguageServer.Conversions
1417open CSharpLanguageServer.Logging
18+ open CSharpLanguageServer.RoslynHelpers
1519
1620[<RequireQualifiedAccess>]
1721module Completion =
18- let private _logger = Logging.getLoggerByName " Completion"
22+ let private logger = Logging.getLoggerByName " Completion"
1923
2024 let private completionItemMemoryCache = new MemoryCache( new MemoryCacheOptions())
2125
@@ -180,13 +184,121 @@ module Completion =
180184 synopsis, documentationText
181185 | _, _ -> None, None
182186
183- let handle
187+ let getCompletionsForRazorDocument
188+ ( solution : Solution )
189+ ( p : CompletionParams )
190+ : Async < option < Microsoft.CodeAnalysis.Completion.CompletionList * Document >> =
191+ async {
192+ match ! getRazorDocumentForUri solution p.TextDocument.Uri with
193+ | None -> return None
194+ | Some( project, compilation, cshtmlPath, cshtmlTree) ->
195+ let! ct = Async.CancellationToken
196+ let! sourceText = cshtmlTree.GetTextAsync() |> Async.AwaitTask
197+
198+ let razorTextDocument =
199+ solution.Projects
200+ |> Seq.collect ( fun p -> p.AdditionalDocuments)
201+ |> Seq.filter ( fun d -> Uri( d.FilePath, UriKind.Absolute) = Uri p.TextDocument.Uri)
202+ |> Seq.head
203+
204+ let! razorSourceText = razorTextDocument.GetTextAsync() |> Async.AwaitTask
205+
206+ //logger.LogInformation("razorSourceText={0}", razorSourceText)
207+
208+ //logger.LogInformation("doc={0}", sourceText)
209+
210+ let posInCshtml = Position.toRoslynPosition sourceText.Lines p.Position
211+ //logger.LogInformation("posInCshtml={posInCshtml=}", posInCshtml)
212+ let pos = p.Position
213+
214+ let root = cshtmlTree.GetRoot()
215+
216+ let mutable position : int option = None
217+ let mutable tokenForPosition : SyntaxToken option = None
218+ let mutable debug : string option = None
219+
220+ for t in root.DescendantTokens() do
221+ let cshtmlSpan = cshtmlTree.GetMappedLineSpan( t.Span)
222+
223+ if
224+ cshtmlSpan.StartLinePosition.Line = ( int pos.Line)
225+ && cshtmlSpan.EndLinePosition.Line = ( int pos.Line)
226+ && cshtmlSpan.StartLinePosition.Character <= ( int pos.Character)
227+ then
228+ let tokenStartCharacterOffset =
229+ ( int pos.Character - cshtmlSpan.StartLinePosition.Character)
230+
231+ position <- Some( t.Span.Start + tokenStartCharacterOffset)
232+
233+ debug <-
234+ Some(
235+ String.Format(
236+ " token={0}; pos.Character={1}; cshtmlSpan.StartLinePosition.Character={2}; offset={3}" ,
237+ t,
238+ pos.Character,
239+ cshtmlSpan.StartLinePosition.Character,
240+ tokenStartCharacterOffset
241+ )
242+ )
243+
244+ tokenForPosition <- Some( t)
245+
246+ //logger.LogInformation(debug |> Option.defaultValue "")
247+
248+ //let position = Position.toRoslynPosition sourceText.Lines translatedPosition
249+ //logger.LogInformation("position in .cs={position}", position)
250+
251+ let posInCS = sourceText.Lines.GetLinePosition( position.Value)
252+ //logger.LogInformation("lineposition={x}", posInCS)
253+
254+ // a hack to make <span>@Model.|</span> autocompletion to work:
255+ // - force a dot if present on .cscshtml but missing on .cs
256+ let newSourceText =
257+ // TODO: check if the text in cshtml is '.', though!
258+ let cshtmlPosition = Position.toRoslynPosition razorSourceText.Lines p.Position
259+ let charInCshtml : char = razorSourceText[ cshtmlPosition - 1 ]
260+
261+ //logger.LogInformation("charInCshtml={0}", charInCshtml)
262+
263+ if charInCshtml = '.' && string tokenForPosition <> " ." then
264+ sourceText.WithChanges( new TextChange( new TextSpan( position.Value - 1 , 0 ), " ." ))
265+ else
266+ sourceText
267+
268+ //logger.LogInformation("newSourceText={0}", newSourceText)
269+
270+ let! doc = tryAddDocument logger ( cshtmlPath + " .cs" ) ( newSourceText.ToString()) solution
271+
272+ let doc = doc.Value
273+
274+ //logger.LogError("handle: doc={doc}", doc)
275+
276+ let completionService =
277+ Microsoft.CodeAnalysis.Completion.CompletionService.GetService( doc)
278+ |> RoslynCompletionServiceWrapper
279+
280+ let completionOptions =
281+ RoslynCompletionOptions.Default()
282+ |> _. WithBool( " ShowItemsFromUnimportedNamespaces" , false )
283+ |> _. WithBool( " ShowNameSuggestions" , false )
284+
285+ let completionTrigger = CompletionContext.toCompletionTrigger p.Context
286+
287+ let! roslynCompletions =
288+ completionService.GetCompletionsAsync( doc, position.Value, completionOptions, completionTrigger, ct)
289+ |> Async.map Option.ofObj
290+
291+ return roslynCompletions |> Option.map ( fun rcl -> rcl, doc)
292+ }
293+
294+ let getCompletionsForCSharpDocument
184295 ( context : ServerRequestContext )
185296 ( p : CompletionParams )
186- : Async < LspResult < U2 < CompletionItem array , CompletionList> option >> =
297+ : Async < option < Microsoft.CodeAnalysis.Completion. CompletionList * Document >> =
187298 async {
188299 match context.GetDocument p.TextDocument.Uri with
189- | None -> return None |> LspResult.success
300+ | None -> return None
301+
190302 | Some doc ->
191303 let! ct = Async.CancellationToken
192304 let! sourceText = doc.GetTextAsync( ct) |> Async.AwaitTask
@@ -216,6 +328,23 @@ module Completion =
216328 else
217329 async.Return None
218330
331+ return roslynCompletions |> Option.map ( fun rcl -> rcl, doc)
332+ }
333+
334+ let handle
335+ ( context : ServerRequestContext )
336+ ( p : CompletionParams )
337+ : Async < LspResult < U2 < CompletionItem array , CompletionList > option >> =
338+ async {
339+ let! roslynCompletionsAndDoc =
340+ if p.TextDocument.Uri.EndsWith( " .cshtml" ) then
341+ getCompletionsForRazorDocument context.Solution p
342+ else
343+ getCompletionsForCSharpDocument context p
344+
345+ match roslynCompletionsAndDoc with
346+ | None -> return None |> LspResult.success
347+ | Some( roslynCompletions, doc) ->
219348 let toLspCompletionItemsWithCacheInfo ( completions : Microsoft.CodeAnalysis.Completion.CompletionList ) =
220349 completions.ItemsList
221350 |> Seq.map ( fun item -> ( item, Guid.NewGuid() |> string))
@@ -232,33 +361,35 @@ module Completion =
232361 |> Array.ofSeq
233362
234363 let lspCompletionItemsWithCacheInfo =
235- roslynCompletions |> Option.map toLspCompletionItemsWithCacheInfo
364+ roslynCompletions |> toLspCompletionItemsWithCacheInfo
236365
237366 // cache roslyn completion items
238- for (_, cacheItemId, roslynDoc, roslynItem) in
239- ( lspCompletionItemsWithCacheInfo |> Option.defaultValue Array.empty) do
367+ for (_, cacheItemId, roslynDoc, roslynItem) in lspCompletionItemsWithCacheInfo do
240368 completionItemMemoryCacheSet cacheItemId roslynDoc roslynItem
241369
370+ let items =
371+ lspCompletionItemsWithCacheInfo |> Array.map ( fun ( item , _ , _ , _ ) -> item)
372+
242373 return
243- lspCompletionItemsWithCacheInfo
244- |> Option.map ( fun itemsWithCacheInfo ->
245- itemsWithCacheInfo |> Array.map ( fun ( item , _ , _ , _ ) -> item))
246- |> Option.map ( fun items ->
247- { IsIncomplete = true
248- Items = items
249- ItemDefaults = None })
250- |> Option.map U2.C2
374+ { IsIncomplete = true
375+ Items = items
376+ ItemDefaults = None }
377+ |> U2.C2
378+ |> Some
251379 |> LspResult.success
252380 }
253381
254382 let resolve ( _context : ServerRequestContext ) ( item : CompletionItem ) : AsyncLspResult < CompletionItem > = async {
383+
255384 let roslynDocAndItemMaybe =
256385 item.Data
257386 |> Option.bind deserialize< string option>
258387 |> Option.bind completionItemMemoryCacheGet
259388
260389 match roslynDocAndItemMaybe with
261390 | Some( doc, roslynCompletionItem) ->
391+ logger.LogInformation( " resolve, doc={0}, item={1}" , doc, roslynCompletionItem)
392+
262393 let completionService =
263394 Microsoft.CodeAnalysis.Completion.CompletionService.GetService( doc)
264395 |> nonNull " Microsoft.CodeAnalysis.Completion.CompletionService.GetService(doc)"
0 commit comments