1+ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+ namespace Microsoft.VisualStudio.FSharp.Editor
4+
5+ open System
6+ open System.Composition
7+ open System.Collections .Concurrent
8+ open System.Collections .Generic
9+ open System.Collections .Immutable
10+ open System.Threading
11+ open System.Threading .Tasks
12+ open System.Linq
13+ open System.Runtime .CompilerServices
14+
15+ open Microsoft.CodeAnalysis
16+ open Microsoft.CodeAnalysis .Completion
17+ open Microsoft.CodeAnalysis .Classification
18+ open Microsoft.CodeAnalysis .Editor
19+ open Microsoft.CodeAnalysis .Editor .Implementation .Debugging
20+ open Microsoft.CodeAnalysis .Editor .Shared .Utilities
21+ open Microsoft.CodeAnalysis .Formatting
22+ open Microsoft.CodeAnalysis .Host .Mef
23+ open Microsoft.CodeAnalysis .Options
24+ open Microsoft.CodeAnalysis .Text
25+
26+ open Microsoft.VisualStudio .FSharp .LanguageService
27+ open Microsoft.VisualStudio .Text
28+ open Microsoft.VisualStudio .Text .Tagging
29+ open Microsoft.VisualStudio .Shell
30+ open Microsoft.VisualStudio .Shell .Interop
31+
32+ open Microsoft.FSharp .Compiler .Parser
33+ open Microsoft.FSharp .Compiler .Range
34+ open Microsoft.FSharp .Compiler .SourceCodeServices
35+
36+ type internal FSharpCompletionProvider ( workspace : Workspace , serviceProvider : SVsServiceProvider ) =
37+ inherit CompletionProvider()
38+
39+ static let completionTriggers = [ '.' ]
40+ static let declarationItemsCache = ConditionalWeakTable< string, FSharpDeclarationListItem>()
41+
42+ let xmlMemberIndexService = serviceProvider.GetService( typeof< IVsXMLMemberIndexService>) :?> IVsXMLMemberIndexService
43+ let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder( xmlMemberIndexService, serviceProvider.DTE)
44+
45+ static member ShouldTriggerCompletionAux ( sourceText : SourceText , caretPosition : int , trigger : CompletionTriggerKind , filePath : string , defines : string list ) =
46+ // Skip if we are at the start of a document
47+ if caretPosition = 0 then
48+ false
49+
50+ // Skip if it was triggered by an operation other than insertion
51+ else if not ( trigger = CompletionTriggerKind.Insertion) then
52+ false
53+
54+ // Skip if we are not on a completion trigger
55+ else if not ( completionTriggers |> Seq.contains( sourceText.[ caretPosition - 1 ])) then
56+ false
57+
58+ // Trigger completion if we are on a valid classification type
59+ else
60+ let triggerPosition = caretPosition - 1
61+ let textLine = sourceText.Lines.GetLineFromPosition( triggerPosition)
62+ let classifiedSpanOption =
63+ FSharpColorizationService.GetColorizationData( sourceText, textLine.Span, Some( filePath), defines, CancellationToken.None)
64+ |> Seq.tryFind( fun classifiedSpan -> classifiedSpan.TextSpan.Contains( triggerPosition))
65+
66+ match classifiedSpanOption with
67+ | None -> false
68+ | Some( classifiedSpan) ->
69+ match classifiedSpan.ClassificationType with
70+ | ClassificationTypeNames.Comment -> false
71+ | ClassificationTypeNames.StringLiteral -> false
72+ | ClassificationTypeNames.ExcludedCode -> false
73+ | _ -> true // anything else is a valid classification type
74+
75+ static member ProvideCompletionsAsyncAux ( sourceText : SourceText , caretPosition : int , options : FSharpProjectOptions , filePath : string , textVersionHash : int ) = async {
76+ let! parseResults = FSharpChecker.Instance.ParseFileInProject( filePath, sourceText.ToString(), options)
77+ let! checkFileAnswer = FSharpChecker.Instance.CheckFileInProject( parseResults, filePath, textVersionHash, sourceText.ToString(), options)
78+ let checkFileResults = match checkFileAnswer with
79+ | FSharpCheckFileAnswer.Aborted -> failwith " Compilation isn't complete yet"
80+ | FSharpCheckFileAnswer.Succeeded( results) -> results
81+
82+ let textLine = sourceText.Lines.GetLineFromPosition( caretPosition)
83+ let textLineNumber = textLine.LineNumber + 1 // Roslyn line numbers are zero-based
84+ let qualifyingNames , partialName = QuickParse.GetPartialLongNameEx( textLine.ToString(), caretPosition - textLine.Start - 1 )
85+ let! declarations = checkFileResults.GetDeclarationListInfo( Some( parseResults), textLineNumber, caretPosition, textLine.ToString(), qualifyingNames, partialName)
86+
87+ let results = List< CompletionItem>()
88+
89+ for declarationItem in declarations.Items do
90+ let completionItem = CompletionItem.Create( declarationItem.Name)
91+ declarationItemsCache.Add( completionItem.DisplayText, declarationItem)
92+ results.Add( completionItem)
93+
94+ return results
95+ }
96+
97+
98+ override this.ShouldTriggerCompletion ( sourceText : SourceText , caretPosition : int , trigger : CompletionTrigger , _ : OptionSet ) =
99+ let documentId = workspace.GetDocumentIdInCurrentContext( sourceText.Container)
100+ let document = workspace.CurrentSolution.GetDocument( documentId)
101+
102+ match FSharpLanguageService.GetOptions( document.Project.Id) with
103+ | None -> false
104+ | Some( options) ->
105+ let defines = CompilerEnvironment.GetCompilationDefinesForEditing( document.Name, options.OtherOptions |> Seq.toList)
106+ FSharpCompletionProvider.ShouldTriggerCompletionAux( sourceText, caretPosition, trigger.Kind, document.FilePath, defines)
107+
108+ override this.ProvideCompletionsAsync ( context : Microsoft.CodeAnalysis.Completion.CompletionContext ) =
109+ let computation = async {
110+ match FSharpLanguageService.GetOptions( context.Document.Project.Id) with
111+ | Some( options) ->
112+ let! sourceText = context.Document.GetTextAsync( context.CancellationToken) |> Async.AwaitTask
113+ let! textVersion = context.Document.GetTextVersionAsync( context.CancellationToken) |> Async.AwaitTask
114+ let! results = FSharpCompletionProvider.ProvideCompletionsAsyncAux( sourceText, context.Position, options, context.Document.FilePath, textVersion.GetHashCode())
115+ context.AddItems( results)
116+ | None -> ()
117+ }
118+
119+ Task.Run( CommonRoslynHelpers.GetTaskAction( computation), context.CancellationToken)
120+
121+ override this.GetDescriptionAsync ( _ : Document , completionItem : CompletionItem , cancellationToken : CancellationToken ): Task < CompletionDescription > =
122+ let computation = async {
123+ let exists , declarationItem = declarationItemsCache.TryGetValue( completionItem.DisplayText)
124+ if exists then
125+ let! description = declarationItem.DescriptionTextAsync
126+ let datatipText = XmlDocumentation.BuildDataTipText( documentationBuilder, description)
127+ return CompletionDescription.FromText( datatipText)
128+ else
129+ return CompletionDescription.Empty
130+ }
131+
132+ Async.StartAsTask( computation, TaskCreationOptions.None, cancellationToken)
133+ .ContinueWith( CommonRoslynHelpers.GetCompletedTaskResult, cancellationToken)
0 commit comments