@@ -47,6 +47,7 @@ import org.jetbrains.kotlin.types.KotlinType
47
47
import org.jetbrains.kotlin.types.TypeUtils
48
48
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
49
49
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
50
+ import org.jetbrains.kotlin.utils.addToStdlib.applyIf
50
51
import java.util.concurrent.TimeUnit
51
52
52
53
// The maximum number of completion items
@@ -159,12 +160,14 @@ data class ElementCompletionItems(val items: Sequence<CompletionItem>, val eleme
159
160
160
161
/* * Finds completions based on the element around the user's cursor. */
161
162
private fun elementCompletionItems (file : CompiledFile , cursor : Int , config : CompletionConfiguration , partial : String ): ElementCompletionItems {
162
- val surroundingElement = completableElement(file, cursor) ? : return ElementCompletionItems (emptySequence())
163
- val completions = elementCompletions(file, cursor, surroundingElement)
164
-
165
- val matchesName = completions.filter { containsCharactersInOrder(name(it), partial, caseSensitive = false ) }
166
- val sorted = matchesName.takeIf { partial.length >= MIN_SORT_LENGTH }?.sortedBy { stringDistance(name(it), partial) }
167
- ? : matchesName.sortedBy { if (name(it).startsWith(partial)) 0 else 1 }
163
+ val (surroundingElement, isGlobal) = completableElement(file, cursor) ? : return ElementCompletionItems (emptySequence())
164
+ val completions = elementCompletions(file, cursor, surroundingElement, isGlobal)
165
+ .applyIf(isGlobal) { filter { declarationIsInfix(it) } }
166
+ .applyIf(surroundingElement.endOffset == cursor) {
167
+ filter { containsCharactersInOrder(name(it), partial, caseSensitive = false ) }
168
+ }
169
+ val sorted = completions.takeIf { partial.length >= MIN_SORT_LENGTH }?.sortedBy { stringDistance(name(it), partial) }
170
+ ? : completions.sortedBy { if (name(it).startsWith(partial)) 0 else 1 }
168
171
val visible = sorted.filter(isVisible(file, cursor))
169
172
170
173
return ElementCompletionItems (visible.map { completionItem(it, surroundingElement, file, config) }, surroundingElement)
@@ -230,28 +233,59 @@ private fun isSetter(d: DeclarationDescriptor): Boolean =
230
233
d.name.identifier.matches(Regex (" set[A-Z]\\ w+" )) &&
231
234
d.valueParameters.size == 1
232
235
233
- private fun completableElement (file : CompiledFile , cursor : Int ): KtElement ? {
234
- val el = file.parseAtPoint(cursor - 1 ) ? : return null
235
- // import x.y.?
236
- return el.findParent<KtImportDirective >()
237
- // package x.y.?
238
- ? : el.findParent<KtPackageDirective >()
239
- // :?
240
- ? : el as ? KtUserType
241
- ? : el.parent as ? KtTypeElement
242
- // .?
243
- ? : el as ? KtQualifiedExpression
244
- ? : el.parent as ? KtQualifiedExpression
245
- // something::?
246
- ? : el as ? KtCallableReferenceExpression
247
- ? : el.parent as ? KtCallableReferenceExpression
248
- // something.foo() with cursor in the method
249
- ? : el.parent?.parent as ? KtQualifiedExpression
250
- // ?
251
- ? : el as ? KtNameReferenceExpression
236
+ private fun isGlobalCall (el : KtElement ) = el is KtBlockExpression || el is KtClassBody || el.parent is KtBinaryExpression
237
+
238
+ private fun asGlobalCompletable (file : CompiledFile , cursor : Int , el : KtElement ): KtElement ? {
239
+ val psi = file.parse.findElementAt(cursor) ? : return null
240
+ val element = when (val e = psi.getPrevSiblingIgnoringWhitespace() ? : psi.parent) {
241
+ is KtProperty -> e.children.lastOrNull()
242
+ is KtBinaryExpression -> el
243
+ else -> e
244
+ }
245
+ return element as ? KtReferenceExpression
246
+ ? : element as ? KtQualifiedExpression
247
+ ? : element as ? KtConstantExpression
248
+ }
249
+
250
+ private fun KtElement.asKtClass (): KtElement ? {
251
+ return this .findParent<KtImportDirective >() // import x.y.?
252
+ // package x.y.?
253
+ ? : this .findParent<KtPackageDirective >()
254
+ // :?
255
+ ? : this as ? KtUserType
256
+ ? : this .parent as ? KtTypeElement
257
+ // .?
258
+ ? : this as ? KtQualifiedExpression
259
+ ? : this .parent as ? KtQualifiedExpression
260
+ // something::?
261
+ ? : this as ? KtCallableReferenceExpression
262
+ ? : this .parent as ? KtCallableReferenceExpression
263
+ // something.foo() with cursor in the method
264
+ ? : this .parent?.parent as ? KtQualifiedExpression
265
+ // ?
266
+ ? : this as ? KtNameReferenceExpression
267
+ // x ? y (infix)
268
+ ? : this .parent as ? KtBinaryExpression
269
+ // x()
270
+ ? : this as ? KtCallExpression
271
+ // x (constant)
272
+ ? : this as ? KtConstantExpression
273
+ }
274
+
275
+ private fun completableElement (file : CompiledFile , cursor : Int ): Pair <KtElement , Boolean >? {
276
+ val parsed = file.parseAtPoint(cursor - 1 ) ? : return null
277
+ val asGlobal = isGlobalCall(parsed)
278
+ val el = (
279
+ if (asGlobal) asGlobalCompletable(file, cursor, parsed) else null
280
+ ) ? : parsed
281
+
282
+ return el.asKtClass()?.let {
283
+ Pair (it, asGlobal)
284
+ }
252
285
}
253
286
254
- private fun elementCompletions (file : CompiledFile , cursor : Int , surroundingElement : KtElement ): Sequence <DeclarationDescriptor > {
287
+ @Suppress(" LongMethod" , " ReturnCount" , " CyclomaticComplexMethod" )
288
+ private fun elementCompletions (file : CompiledFile , cursor : Int , surroundingElement : KtElement , infixCall : Boolean ): Sequence <DeclarationDescriptor > {
255
289
return when (surroundingElement) {
256
290
// import x.y.?
257
291
is KtImportDirective -> {
@@ -298,7 +332,8 @@ private fun elementCompletions(file: CompiledFile, cursor: Int, surroundingEleme
298
332
// .?
299
333
is KtQualifiedExpression -> {
300
334
LOG .info(" Completing member expression '{}'" , surroundingElement.text)
301
- completeMembers(file, cursor, surroundingElement.receiverExpression, surroundingElement is KtSafeQualifiedExpression )
335
+ val exp = if (infixCall) surroundingElement else surroundingElement.receiverExpression
336
+ completeMembers(file, cursor, exp, surroundingElement is KtSafeQualifiedExpression )
302
337
}
303
338
is KtCallableReferenceExpression -> {
304
339
// something::?
@@ -316,8 +351,21 @@ private fun elementCompletions(file: CompiledFile, cursor: Int, surroundingEleme
316
351
// ?
317
352
is KtNameReferenceExpression -> {
318
353
LOG .info(" Completing identifier '{}'" , surroundingElement.text)
319
- val scope = file.scopeAtPoint(surroundingElement.startOffset) ? : return noResult(" No scope at ${file.describePosition(cursor)} " , emptySequence())
320
- identifiers(scope)
354
+ if (infixCall) {
355
+ completeMembers(file, surroundingElement.startOffset, surroundingElement)
356
+ } else {
357
+ val scope = file.scopeAtPoint(surroundingElement.startOffset) ? : return noResult(" No scope at ${file.describePosition(cursor)} " , emptySequence())
358
+ identifiers(scope)
359
+ }
360
+ }
361
+ // x ? y (infix)
362
+ is KtBinaryExpression -> {
363
+ if (surroundingElement.operationToken == KtTokens .IDENTIFIER ) {
364
+ completeMembers(file, cursor, surroundingElement.left!! )
365
+ } else emptySequence()
366
+ }
367
+ is KtCallExpression , is KtConstantExpression -> {
368
+ completeMembers(file, cursor, surroundingElement as KtExpression )
321
369
}
322
370
else -> {
323
371
LOG .info(" {} {} didn't look like a type, a member, or an identifier" , surroundingElement::class .simpleName, surroundingElement.text)
@@ -370,6 +418,11 @@ private fun ClassDescriptor.getDescriptors(): Sequence<DeclarationDescriptor> {
370
418
371
419
}
372
420
421
+ private fun declarationIsInfix (declaration : DeclarationDescriptor ): Boolean {
422
+ val functionDescriptor = declaration as ? FunctionDescriptor ? : return false
423
+ return functionDescriptor.isInfix
424
+ }
425
+
373
426
private fun isCompanionOfEnum (kotlinType : KotlinType ): Boolean {
374
427
val classDescriptor = TypeUtils .getClassDescriptor(kotlinType)
375
428
val isCompanion = DescriptorUtils .isCompanionObject(classDescriptor)
0 commit comments