improved completion

This commit is contained in:
hgranthorner
2026-08-05 16:32:40 -04:00
parent ede8522716
commit 98e25c5468
10 changed files with 832 additions and 139 deletions

View File

@@ -6,6 +6,7 @@ import com.intellij.codeInsight.completion.CompletionProvider
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.completion.CompletionType
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.patterns.PlatformPatterns
@@ -17,8 +18,11 @@ import dev.hgh.jai.lexer.JaiTokenTypes
import dev.hgh.jai.module.JaiModuleResolver
import dev.hgh.jai.module.JaiPathMode
import dev.hgh.jai.psi.JaiDirectiveExpr
import dev.hgh.jai.reference.JaiSymbolResolver
/** Directives and their adjacent comma flags before an import/load string. */
private const val COMPLETION_DEBUG_PROPERTY = "jai.completion.debug"
private val LOG = Logger.getInstance("dev.hgh.jai.completion")
private val DIRECTIVE_WITH_FLAGS =
Regex("""#(import|load)((?:\s*,\s*[A-Za-z_][A-Za-z0-9_]*)*)\s*$""")
@@ -43,6 +47,12 @@ private class JaiCompletionProvider : CompletionProvider<CompletionParameters>()
) {
val completionContext = JaiCompletionContext(parameters)
val moduleDirective = completionContext.moduleDirective
debug(
"request file=${parameters.originalFile.virtualFile?.path ?: parameters.originalFile.name} " +
"offset=${parameters.offset} token=${completionContext.debugToken} " +
"prefix='${completionContext.prefix}' module=$moduleDirective " +
"member=${completionContext.memberContext}",
)
when {
moduleDirective != null -> {
addModuleNames(parameters, moduleDirective, completionContext.prefix, resultSet)
@@ -52,12 +62,18 @@ private class JaiCompletionProvider : CompletionProvider<CompletionParameters>()
return
}
completionContext.memberContext != null -> {
val member = completionContext.memberContext!!
addMemberSymbols(parameters, member, resultSet)
}
completionContext.isDirectivePrefix -> {
addDirectives(completionContext.prefix, resultSet)
}
else -> {
addKeywords(completionContext.prefix, resultSet)
addSymbols(parameters, completionContext.prefix, resultSet)
}
}
}
@@ -100,6 +116,53 @@ private class JaiCompletionProvider : CompletionProvider<CompletionParameters>()
}
}
private fun addSymbols(
parameters: CompletionParameters,
prefix: String,
resultSet: CompletionResultSet,
) {
val candidates = JaiSymbolResolver.completionCandidates(parameters.originalFile, parameters.position)
debug("unqualified candidates=${candidates.size} names=${candidates.take(40).joinToString { it.name }}")
candidates
.filter { it.name.startsWith(prefix) }
.forEach { candidate ->
resultSet.addElement(
LookupElementBuilder
.create(candidate.name)
.withPsiElement(candidate.element)
.withTypeText(candidate.kind),
)
}
}
private fun addMemberSymbols(
parameters: CompletionParameters,
member: JaiCompletionContext.MemberContext,
resultSet: CompletionResultSet,
) {
val candidates =
JaiSymbolResolver.memberCompletionCandidates(
parameters.originalFile,
parameters.position,
member.qualifier,
)
debug("member qualifier=${member.qualifier} candidates=${candidates.size} names=${candidates.take(40).joinToString { it.name }}")
candidates
.filter { it.name.startsWith(member.prefix) }
.forEach { candidate ->
resultSet.addElement(
LookupElementBuilder
.create(candidate.name)
.withPsiElement(candidate.element)
.withTypeText(candidate.kind),
)
}
}
private fun debug(message: String) {
if (java.lang.Boolean.getBoolean(COMPLETION_DEBUG_PROPERTY)) LOG.info(message)
}
private fun addModuleNames(
parameters: CompletionParameters,
directive: String,
@@ -149,6 +212,23 @@ private class JaiCompletionContext(
val isDirectivePrefix: Boolean
get() = token?.type === JaiTokenTypes.DIRECTIVE || token?.type === JaiTokenTypes.HASH
val debugToken: String
get() = token?.let { "${it.type}@${it.start}..${it.end}" } ?: "none"
data class MemberContext(
val qualifier: String,
val prefix: String,
)
val memberContext: MemberContext?
get() =
MEMBER_PREFIX.find(text.substring(0, offset))?.let { match ->
MemberContext(
qualifier = match.groupValues[1],
prefix = match.groupValues.getOrNull(2).orEmpty(),
)
}
val prefix: String
get() {
val current = token ?: return ""
@@ -210,6 +290,11 @@ private class JaiCompletionContext(
val end: Int,
)
private companion object {
val MEMBER_PREFIX =
Regex("""(?:^|[^A-Za-z0-9_`])(`?[A-Za-z_][A-Za-z0-9_]*`?)\s*\.\s*(`?[A-Za-z_][A-Za-z0-9_]*`?)?$""")
}
private fun tokenAtCaret(
text: String,
offset: Int,