Skip to content
This repository was archived by the owner on Dec 23, 2024. It is now read-only.

Commit 0e260bc

Browse files
cartermpnosami
authored andcommitted
Show keyword descriptions in completion + refactor (dotnet#9164)
* Completion provider refactor * Actually show keyword descriptions in tooltips for completion * Get rid of unnecessary property * Make the keyword completion list static again * Just do it in the CompletionProvider
1 parent 0a8428f commit 0e260bc

File tree

4 files changed

+113
-355
lines changed

4 files changed

+113
-355
lines changed

Completion/CompletionProvider.fs

Lines changed: 79 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,22 @@ open System.Threading.Tasks
1010

1111
open Microsoft.CodeAnalysis
1212
open Microsoft.CodeAnalysis.Completion
13+
open Microsoft.CodeAnalysis.Options
1314
open Microsoft.CodeAnalysis.Text
15+
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Completion
16+
17+
open Microsoft.VisualStudio.Shell
1418

1519
open FSharp.Compiler
1620
open FSharp.Compiler.Range
1721
open FSharp.Compiler.SourceCodeServices
18-
open Microsoft.VisualStudio.Text.Adornments
19-
open Microsoft.VisualStudio.Text.Editor
22+
2023
module Logger = Microsoft.VisualStudio.FSharp.Editor.Logger
2124

2225
type internal FSharpCompletionProvider
2326
(
2427
workspace: Workspace,
28+
serviceProvider: SVsServiceProvider,
2529
checkerProvider: FSharpCheckerProvider,
2630
projectInfoManager: FSharpProjectOptionsManager,
2731
assemblyContentProvider: AssemblyContentProvider
@@ -37,20 +41,28 @@ type internal FSharpCompletionProvider
3741
static let [<Literal>] FullNamePropName = "FullName"
3842
static let [<Literal>] IsExtensionMemberPropName = "IsExtensionMember"
3943
static let [<Literal>] NamespaceToOpenPropName = "NamespaceToOpen"
40-
static let [<Literal>] IsKeywordPropName = "IsKeyword"
4144
static let [<Literal>] IndexPropName = "Index"
45+
static let [<Literal>] KeywordDescription = "KeywordDescription"
4246

4347
static let keywordCompletionItems =
4448
Keywords.KeywordsWithDescription
4549
|> List.filter (fun (keyword, _) -> not (PrettyNaming.IsOperatorName keyword))
4650
|> List.sortBy (fun (keyword, _) -> keyword)
47-
51+
|> List.mapi (fun n (keyword, description) ->
52+
FSharpCommonCompletionItem.Create(
53+
displayText = keyword,
54+
displayTextSuffix = "",
55+
rules = CompletionItemRules.Default,
56+
glyph = Nullable Glyph.Keyword,
57+
sortText = sprintf "%06d" (1000000 + n))
58+
.AddProperty(KeywordDescription, description))
4859

4960
let checker = checkerProvider.Checker
50-
61+
5162
let settings: EditorOptions = workspace.Services.GetService()
5263

53-
let documentationBuilder = XmlDocumentation.Provider()
64+
let documentationBuilder = XmlDocumentation.CreateDocumentationBuilder(serviceProvider.XMLMemberIndexService)
65+
5466
static let noCommitOnSpaceRules =
5567
// These are important. They make sure we don't _commit_ autocompletion when people don't expect them to. Some examples:
5668
//
@@ -72,16 +84,16 @@ type internal FSharpCompletionProvider
7284

7385
static let mruItems = Dictionary<(* Item.FullName *) string, (* hints *) int>()
7486

75-
static member ShouldTriggerCompletionAux(sourceText: SourceText, caretPosition: int, trigger: Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionTrigger, getInfo: (unit -> DocumentId * string * string list), intelliSenseOptions: IntelliSenseOptions) =
87+
static member ShouldTriggerCompletionAux(sourceText: SourceText, caretPosition: int, trigger: CompletionTriggerKind, getInfo: (unit -> DocumentId * string * string list), intelliSenseOptions: IntelliSenseOptions) =
7688
if caretPosition = 0 then
7789
false
7890
else
7991
let triggerPosition = caretPosition - 1
80-
let triggerChar = trigger.Character
92+
let triggerChar = sourceText.[triggerPosition]
8193

82-
if trigger.Reason = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionTriggerReason.Deletion && intelliSenseOptions.ShowAfterCharIsDeleted then
83-
Char.IsLetterOrDigit(triggerChar) || triggerChar = '.'
84-
elif not (trigger.Reason = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionTriggerReason.Insertion || trigger.Reason = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionTriggerReason.InvokeAndCommitIfUnique) then
94+
if trigger = CompletionTriggerKind.Deletion && intelliSenseOptions.ShowAfterCharIsDeleted then
95+
Char.IsLetterOrDigit(sourceText.[triggerPosition]) || triggerChar = '.'
96+
elif not (trigger = CompletionTriggerKind.Insertion) then
8597
false
8698
else
8799
// Do not trigger completion if it's not single dot, i.e. range expression
@@ -90,10 +102,10 @@ type internal FSharpCompletionProvider
90102
else
91103
let documentId, filePath, defines = getInfo()
92104
CompletionUtils.shouldProvideCompletion(documentId, filePath, defines, sourceText, triggerPosition) &&
93-
(trigger.Reason = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionTriggerReason.InvokeAndCommitIfUnique || triggerChar = '.' || (intelliSenseOptions.ShowAfterCharIsTyped && CompletionUtils.isStartingNewWord(sourceText, triggerPosition)))
105+
(triggerChar = '.' || (intelliSenseOptions.ShowAfterCharIsTyped && CompletionUtils.isStartingNewWord(sourceText, triggerPosition)))
94106

95107

96-
static member ProvideCompletionsAsyncAux(completionSource: Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.IAsyncCompletionSource , checker: FSharpChecker, sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, filePath: string,
108+
static member ProvideCompletionsAsyncAux(checker: FSharpChecker, sourceText: SourceText, caretPosition: int, options: FSharpProjectOptions, filePath: string,
97109
textVersionHash: int, getAllSymbols: FSharpCheckFileResults -> AssemblySymbol list, languageServicePerformanceOptions: LanguageServicePerformanceOptions, intellisenseOptions: IntelliSenseOptions) =
98110
asyncMaybe {
99111
let! parseResults, _, checkFileResults = checker.ParseAndCheckDocument(filePath, textVersionHash, sourceText, options, languageServicePerformanceOptions, userOpName = userOpName)
@@ -112,7 +124,7 @@ type internal FSharpCompletionProvider
112124

113125
let! declarations = checkFileResults.GetDeclarationListInfo(Some(parseResults), fcsCaretLineNumber, caretLine.ToString(),
114126
partialName, getAllSymbols, userOpName=userOpName) |> liftAsync
115-
let results = List<Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionItem>()
127+
let results = List<Completion.CompletionItem>()
116128

117129
declarationItems <-
118130
declarations.Items
@@ -131,7 +143,6 @@ type internal FSharpCompletionProvider
131143

132144
declarationItems |> Array.iteri (fun number declarationItem ->
133145
let glyph = Tokenizer.FSharpGlyphToRoslynGlyph (declarationItem.Glyph, declarationItem.Accessibility)
134-
let image = GlyphHelper.getImageId glyph |> ImageElement
135146
let name =
136147
match declarationItem.NamespaceToOpen with
137148
| Some namespaceToOpen -> sprintf "%s (open %s)" declarationItem.Name namespaceToOpen
@@ -145,38 +156,38 @@ type internal FSharpCompletionProvider
145156
// We are passing last part of long ident as FilterText.
146157
| _, idents -> Array.last idents
147158

148-
let completionItem =
149-
let item = new Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionItem(name, completionSource, icon = image)
150-
item.Properties.AddProperty(IndexPropName, declarationItem)
151-
item
152-
159+
let completionItem =
160+
FSharpCommonCompletionItem.Create(name, null, rules = getRules intellisenseOptions.ShowAfterCharIsTyped, glyph = Nullable glyph, filterText = filterText)
161+
.AddProperty(FullNamePropName, declarationItem.FullName)
162+
153163
let completionItem =
154164
match declarationItem.Kind with
155165
| CompletionItemKind.Method (isExtension = true) ->
156-
completionItem//.AddProperty(IsExtensionMemberPropName, "")
166+
completionItem.AddProperty(IsExtensionMemberPropName, "")
157167
| _ -> completionItem
158168

159169
let completionItem =
160170
if name <> declarationItem.NameInCode then
161-
completionItem//.AddProperty(NameInCodePropName, declarationItem.NameInCode)
171+
completionItem.AddProperty(NameInCodePropName, declarationItem.NameInCode)
162172
else completionItem
163173

164174
let completionItem =
165175
match declarationItem.NamespaceToOpen with
166-
| Some ns -> completionItem//.AddProperty(NamespaceToOpenPropName, ns)
176+
| Some ns -> completionItem.AddProperty(NamespaceToOpenPropName, ns)
167177
| None -> completionItem
168178

169-
let completionItem = completionItem//.AddProperty(IndexPropName, string number)
179+
let completionItem = completionItem.AddProperty(IndexPropName, string number)
170180

171181
let priority =
172182
match mruItems.TryGetValue declarationItem.FullName with
173183
| true, hints -> maxHints - hints
174184
| _ -> number + maxHints + 1
175185

176186
let sortText = priority.ToString("D6")
177-
let completionItem = completionItem//.WithSortText(sortText)
187+
let completionItem = completionItem.WithSortText(sortText)
178188
results.Add(completionItem))
179189

190+
180191
if results.Count > 0 && not declarations.IsForType && not declarations.IsError && List.isEmpty partialName.QualifyingIdents then
181192
let lineStr = textLines.[caretLinePos.Line].ToString()
182193

@@ -185,28 +196,46 @@ type internal FSharpCompletionProvider
185196
|> Option.bind (fun parseTree ->
186197
UntypedParseImpl.TryGetCompletionContext(Pos.fromZ caretLinePos.Line caretLinePos.Character, parseTree, lineStr))
187198

188-
let image = GlyphHelper.getImageId Glyph.Keyword |> ImageElement
189-
190199
match completionContext with
191-
| None ->
192-
let keywordItemsWithSource =
193-
keywordCompletionItems
194-
|> Seq.mapi (fun n (keyword, description) ->
195-
new Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionItem
196-
(keyword, completionSource, image, ImmutableArray<Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionFilter>.Empty, "", keyword, sprintf "%06d" (1000000 + n), keyword, keyword, ImmutableArray<ImageElement>.Empty ))
197-
198-
results.AddRange(keywordItemsWithSource)
200+
| None -> results.AddRange(keywordCompletionItems)
199201
| _ -> ()
200-
202+
201203
return results
202204
}
203205

204-
override this.ProvideCompletionsAsync(context: Completion.CompletionContext) =
206+
override _.ShouldTriggerCompletion(sourceText: SourceText, caretPosition: int, trigger: CompletionTrigger, _: OptionSet) =
207+
use _logBlock = Logger.LogBlock LogEditorFunctionId.Completion_ShouldTrigger
208+
209+
let getInfo() =
210+
let documentId = workspace.GetDocumentIdInCurrentContext(sourceText.Container)
211+
let document = workspace.CurrentSolution.GetDocument(documentId)
212+
let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document)
213+
(documentId, document.FilePath, defines)
214+
215+
FSharpCompletionProvider.ShouldTriggerCompletionAux(sourceText, caretPosition, trigger.Kind, getInfo, settings.IntelliSense)
216+
217+
override _.ProvideCompletionsAsync(context: Completion.CompletionContext) =
205218
asyncMaybe {
206-
context.AddItems([])//results)
219+
use _logBlock = Logger.LogBlockMessage context.Document.Name LogEditorFunctionId.Completion_ProvideCompletionsAsync
220+
221+
let document = context.Document
222+
let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
223+
let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document)
224+
do! Option.guard (CompletionUtils.shouldProvideCompletion(document.Id, document.FilePath, defines, sourceText, context.Position))
225+
let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken)
226+
let! textVersion = context.Document.GetTextVersionAsync(context.CancellationToken)
227+
let getAllSymbols(fileCheckResults: FSharpCheckFileResults) =
228+
if settings.IntelliSense.IncludeSymbolsFromUnopenedNamespacesOrModules
229+
then assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies(fileCheckResults)
230+
else []
231+
let! results =
232+
FSharpCompletionProvider.ProvideCompletionsAsyncAux(checker, sourceText, context.Position, projectOptions, document.FilePath,
233+
textVersion.GetHashCode(), getAllSymbols, settings.LanguageServicePerformance, settings.IntelliSense)
234+
235+
context.AddItems(results)
207236
} |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask context.CancellationToken
208237

209-
override this.GetDescriptionAsync(document: Document, completionItem: Completion.CompletionItem, cancellationToken: CancellationToken): Task<CompletionDescription> =
238+
override _.GetDescriptionAsync(document: Document, completionItem: Completion.CompletionItem, cancellationToken: CancellationToken): Task<CompletionDescription> =
210239
async {
211240
use _logBlock = Logger.LogBlockMessage document.Name LogEditorFunctionId.Completion_GetDescriptionAsync
212241
match completionItem.Properties.TryGetValue IndexPropName with
@@ -220,27 +249,18 @@ type internal FSharpCompletionProvider
220249
// mix main description and xmldoc by using one collector
221250
XmlDocumentation.BuildDataTipText(documentationBuilder, collector, collector, collector, collector, collector, description)
222251
return CompletionDescription.Create(documentation.ToImmutableArray())
223-
else
252+
else
253+
return CompletionDescription.Empty
254+
| _ ->
255+
// Try keyword descriptions if they exist
256+
match completionItem.Properties.TryGetValue KeywordDescription with
257+
| true, keywordDescription ->
258+
return CompletionDescription.FromText(keywordDescription)
259+
| false, _ ->
224260
return CompletionDescription.Empty
225-
| _ ->
226-
return CompletionDescription.Empty
227-
} |> RoslynHelpers.StartAsyncAsTask cancellationToken
228-
229-
member this.GetDescriptionAsync2(textView: ITextView, completionItem: Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionItem, cancellationToken: CancellationToken): Task<CompletionDescription> =
230-
async {
231-
match completionItem.Properties.TryGetProperty IndexPropName with
232-
| true, (declarationItem: FSharpDeclarationListItem) ->
233-
let! description = declarationItem.StructuredDescriptionTextAsync
234-
let documentation = List()
235-
let collector = RoslynHelpers.CollectTaggedText documentation
236-
// mix main description and xmldoc by using one collector
237-
XmlDocumentation.BuildDataTipText(documentationBuilder, collector, collector, collector, collector, collector, description)
238-
return CompletionDescription.Create(documentation.ToImmutableArray())
239-
| _ ->
240-
return CompletionDescription.Empty
241261
} |> RoslynHelpers.StartAsyncAsTask cancellationToken
242262

243-
override this.GetChangeAsync(document, item, _, cancellationToken) : Task<CompletionChange> =
263+
override _.GetChangeAsync(document, item, _, cancellationToken) : Task<CompletionChange> =
244264
async {
245265
use _logBlock = Logger.LogBlockMessage document.Name LogEditorFunctionId.Completion_GetChangeAsync
246266

@@ -249,9 +269,8 @@ type internal FSharpCompletionProvider
249269
| true, x -> Some x
250270
| _ -> None
251271

252-
// do not add extension members, keywords and not yet resolved symbols to the MRU list
253-
if not (item.Properties.ContainsKey NamespaceToOpenPropName) && not (item.Properties.ContainsKey IsExtensionMemberPropName) &&
254-
not (item.Properties.ContainsKey IsKeywordPropName) then
272+
// do not add extension members and unresolved symbols to the MRU list
273+
if not (item.Properties.ContainsKey NamespaceToOpenPropName) && not (item.Properties.ContainsKey IsExtensionMemberPropName) then
255274
match fullName with
256275
| Some fullName ->
257276
match mruItems.TryGetValue fullName with

0 commit comments

Comments
 (0)