plugin with type declarations, collection types and much better type tracking for autocomplete
This commit is contained in:
1 parent
aba0048a83
commit
fe5dded7af
17 files changed
+1195
-182
No files matched your search
+93
-41
@@ -102,7 +102,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
|
||||
// Delegate computation to the shared engine to keep behavior in sync with tests
|
||||
val engineItems = try {
|
||||
runBlocking { CompletionEngineLight.completeSuspend(text, caret, mini) }
|
||||
runBlocking { CompletionEngineLight.completeSuspend(text, caret, mini, binding) }
|
||||
} catch (t: Throwable) {
|
||||
if (DEBUG_COMPLETION) log.warn("[LYNG_DEBUG] Engine completion failed: ${t.message}")
|
||||
emptyList()
|
||||
@@ -185,33 +185,51 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
?: DocLookupUtils.guessReturnClassAcrossKnownCallees(text, memberDotPos, imported, mini)
|
||||
?: DocLookupUtils.guessReceiverClass(text, memberDotPos, imported, mini)
|
||||
if (!inferredClass.isNullOrBlank()) {
|
||||
val ext = BuiltinDocRegistry.extensionMemberNamesFor(inferredClass)
|
||||
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Post-engine extension check for $inferredClass: ${'$'}{ext}")
|
||||
val ext = DocLookupUtils.collectExtensionMemberNames(imported, inferredClass, mini)
|
||||
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Post-engine extension check for $inferredClass: ${ext}")
|
||||
for (name in ext) {
|
||||
if (existing.contains(name)) continue
|
||||
val resolved = DocLookupUtils.resolveMemberWithInheritance(imported, inferredClass, name, mini)
|
||||
if (resolved != null) {
|
||||
when (val member = resolved.second) {
|
||||
val m = resolved.second
|
||||
val builder = when (m) {
|
||||
is MiniMemberFunDecl -> {
|
||||
val params = member.params.joinToString(", ") { it.name }
|
||||
val ret = typeOf(member.returnType)
|
||||
val builder = LookupElementBuilder.create(name)
|
||||
val params = m.params.joinToString(", ") { it.name }
|
||||
val ret = typeOf(m.returnType)
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Method)
|
||||
.withTailText("(${ '$' }params)", true)
|
||||
.withTailText("($params)", true)
|
||||
.withTypeText(ret, true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
}
|
||||
is MiniFunDecl -> {
|
||||
val params = m.params.joinToString(", ") { it.name }
|
||||
val ret = typeOf(m.returnType)
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Method)
|
||||
.withTailText("($params)", true)
|
||||
.withTypeText(ret, true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
emit(builder)
|
||||
existing.add(name)
|
||||
}
|
||||
is MiniMemberValDecl -> {
|
||||
val builder = LookupElementBuilder.create(name)
|
||||
.withIcon(if (member.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field)
|
||||
.withTypeText(typeOf(member.type), true)
|
||||
emit(builder)
|
||||
existing.add(name)
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(if (m.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field)
|
||||
.withTypeText(typeOf(m.type), true)
|
||||
}
|
||||
is MiniValDecl -> {
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(if (m.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field)
|
||||
.withTypeText(typeOf(m.type), true)
|
||||
}
|
||||
else -> {
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Method)
|
||||
.withTailText("()", true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
}
|
||||
is MiniInitDecl -> {}
|
||||
}
|
||||
emit(builder)
|
||||
existing.add(name)
|
||||
} else {
|
||||
// Fallback: emit simple method name without detailed types
|
||||
val builder = LookupElementBuilder.create(name)
|
||||
@@ -455,27 +473,44 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
val resolved = DocLookupUtils.findMemberAcrossClasses(imported, name, mini)
|
||||
if (resolved != null) {
|
||||
val member = resolved.second
|
||||
when (member) {
|
||||
val builder = when (member) {
|
||||
is MiniMemberFunDecl -> {
|
||||
val params = member.params.joinToString(", ") { it.name }
|
||||
val ret = typeOf(member.returnType)
|
||||
val builder = LookupElementBuilder.create(name)
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Method)
|
||||
.withTailText("(${params})", true)
|
||||
.withTailText("($params)", true)
|
||||
.withTypeText(ret, true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
}
|
||||
is MiniFunDecl -> {
|
||||
val params = member.params.joinToString(", ") { it.name }
|
||||
val ret = typeOf(member.returnType)
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Method)
|
||||
.withTailText("($params)", true)
|
||||
.withTypeText(ret, true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
emit(builder)
|
||||
already.add(name)
|
||||
}
|
||||
is MiniMemberValDecl -> {
|
||||
val builder = LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Field)
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(if (member.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field)
|
||||
.withTypeText(typeOf(member.type), true)
|
||||
emit(builder)
|
||||
already.add(name)
|
||||
}
|
||||
is MiniInitDecl -> {}
|
||||
is MiniValDecl -> {
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(if (member.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field)
|
||||
.withTypeText(typeOf(member.type), true)
|
||||
}
|
||||
else -> {
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Method)
|
||||
.withTailText("()", true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
}
|
||||
}
|
||||
emit(builder)
|
||||
already.add(name)
|
||||
} else {
|
||||
// Synthetic fallback: method without detailed params/types to improve UX in absence of docs
|
||||
val isProperty = name in setOf("size", "length")
|
||||
@@ -504,29 +539,46 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
// Try to resolve full signature via registry first to get params and return type
|
||||
val resolved = DocLookupUtils.resolveMemberWithInheritance(imported, className, name, mini)
|
||||
if (resolved != null) {
|
||||
when (val member = resolved.second) {
|
||||
val m = resolved.second
|
||||
val builder = when (m) {
|
||||
is MiniMemberFunDecl -> {
|
||||
val params = member.params.joinToString(", ") { it.name }
|
||||
val ret = typeOf(member.returnType)
|
||||
val builder = LookupElementBuilder.create(name)
|
||||
val params = m.params.joinToString(", ") { it.name }
|
||||
val ret = typeOf(m.returnType)
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Method)
|
||||
.withTailText("(${params})", true)
|
||||
.withTailText("($params)", true)
|
||||
.withTypeText(ret, true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
}
|
||||
is MiniFunDecl -> {
|
||||
val params = m.params.joinToString(", ") { it.name }
|
||||
val ret = typeOf(m.returnType)
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Method)
|
||||
.withTailText("($params)", true)
|
||||
.withTypeText(ret, true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
emit(builder)
|
||||
already.add(name)
|
||||
continue
|
||||
}
|
||||
is MiniMemberValDecl -> {
|
||||
val builder = LookupElementBuilder.create(name)
|
||||
.withIcon(if (member.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field)
|
||||
.withTypeText(typeOf(member.type), true)
|
||||
emit(builder)
|
||||
already.add(name)
|
||||
continue
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(if (m.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field)
|
||||
.withTypeText(typeOf(m.type), true)
|
||||
}
|
||||
is MiniValDecl -> {
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(if (m.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field)
|
||||
.withTypeText(typeOf(m.type), true)
|
||||
}
|
||||
else -> {
|
||||
LookupElementBuilder.create(name)
|
||||
.withIcon(AllIcons.Nodes.Method)
|
||||
.withTailText("()", true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
}
|
||||
is MiniInitDecl -> {}
|
||||
}
|
||||
emit(builder)
|
||||
already.add(name)
|
||||
continue
|
||||
}
|
||||
// Fallback: emit without detailed types if we couldn't resolve
|
||||
val builder = LookupElementBuilder.create(name)
|
||||
|
||||
+40
-11
@@ -68,6 +68,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
// 1. Get merged mini-AST from Manager (handles local + .lyng.d merged declarations)
|
||||
val mini = LyngAstManager.getMiniAst(file) ?: return null
|
||||
val miniSource = mini.range.start.source
|
||||
val imported = DocLookupUtils.canonicalImportedModules(mini, text)
|
||||
|
||||
// Try resolve to: function param at position, function/class/val declaration at position
|
||||
// 1) Use unified declaration detection
|
||||
@@ -78,7 +79,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
if (d.name == name) {
|
||||
val s: Int = miniSource.offsetOf(d.nameStart)
|
||||
if (s <= offset && s + d.name.length > offset) {
|
||||
return renderDeclDoc(d)
|
||||
return renderDeclDoc(d, text, mini, imported)
|
||||
}
|
||||
}
|
||||
// Handle members if it was a member
|
||||
@@ -105,6 +106,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
name = cf.name,
|
||||
mutable = cf.mutable,
|
||||
type = cf.type,
|
||||
initRange = null,
|
||||
doc = null,
|
||||
nameStart = cf.nameStart
|
||||
)
|
||||
@@ -122,6 +124,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
name = cf.name,
|
||||
mutable = cf.mutable,
|
||||
type = cf.type,
|
||||
initRange = null,
|
||||
doc = null,
|
||||
nameStart = cf.nameStart
|
||||
)
|
||||
@@ -171,7 +174,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dsFound != null) return renderDeclDoc(dsFound)
|
||||
if (dsFound != null) return renderDeclDoc(dsFound, text, mini, imported)
|
||||
|
||||
// Check parameters
|
||||
mini.declarations.filterIsInstance<MiniFunDecl>().forEach { fn ->
|
||||
@@ -209,6 +212,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
name = cf.name,
|
||||
mutable = cf.mutable,
|
||||
type = cf.type,
|
||||
initRange = null,
|
||||
doc = null,
|
||||
nameStart = cf.nameStart
|
||||
)
|
||||
@@ -226,6 +230,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
name = cf.name,
|
||||
mutable = cf.mutable,
|
||||
type = cf.type,
|
||||
initRange = null,
|
||||
doc = null,
|
||||
nameStart = cf.nameStart
|
||||
)
|
||||
@@ -308,6 +313,10 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||
is MiniInitDecl -> null
|
||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
}
|
||||
}
|
||||
log.info("[LYNG_DEBUG] QuickDoc: resolve failed for ${className}.${ident}")
|
||||
@@ -318,7 +327,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
// 4) As a fallback, if the caret is on an identifier text that matches any declaration name, show that
|
||||
mini.declarations.firstOrNull { it.name == ident }?.let {
|
||||
log.info("[LYNG_DEBUG] QuickDoc: fallback by name '${it.name}' kind=${it::class.simpleName}")
|
||||
return renderDeclDoc(it)
|
||||
return renderDeclDoc(it, text, mini, imported)
|
||||
}
|
||||
|
||||
// 4) Consult BuiltinDocRegistry for imported modules (top-level and class members)
|
||||
@@ -338,13 +347,13 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
if (arity != null && chosen.params.size != arity && matches.size > 1) {
|
||||
return renderOverloads(ident, matches)
|
||||
}
|
||||
return renderDeclDoc(chosen)
|
||||
return renderDeclDoc(chosen, text, mini, imported)
|
||||
}
|
||||
// Also allow values/consts
|
||||
docs.filterIsInstance<MiniValDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it) }
|
||||
docs.filterIsInstance<MiniValDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it, text, mini, imported) }
|
||||
// And classes/enums
|
||||
docs.filterIsInstance<MiniClassDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it) }
|
||||
docs.filterIsInstance<MiniEnumDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it) }
|
||||
docs.filterIsInstance<MiniClassDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it, text, mini, imported) }
|
||||
docs.filterIsInstance<MiniEnumDecl>().firstOrNull { it.name == ident }?.let { return renderDeclDoc(it, text, mini, imported) }
|
||||
}
|
||||
// Defensive fallback: if nothing found and it's a well-known stdlib function, render minimal inline docs
|
||||
if (ident == "println" || ident == "print") {
|
||||
@@ -364,6 +373,10 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||
is MiniInitDecl -> null
|
||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -383,6 +396,10 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||
is MiniInitDecl -> null
|
||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -396,6 +413,10 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||
is MiniInitDecl -> null
|
||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -421,6 +442,10 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||
is MiniInitDecl -> null
|
||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -468,12 +493,16 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
return contextElement ?: file.findElementAt(targetOffset)
|
||||
}
|
||||
|
||||
private fun renderDeclDoc(d: MiniDecl): String {
|
||||
private fun renderDeclDoc(d: MiniDecl, text: String, mini: MiniScript, imported: List<String>): String {
|
||||
val title = when (d) {
|
||||
is MiniFunDecl -> "function ${d.name}${signatureOf(d)}"
|
||||
is MiniClassDecl -> "class ${d.name}"
|
||||
is MiniEnumDecl -> "enum ${d.name} { ${d.entries.joinToString(", ")} }"
|
||||
is MiniValDecl -> if (d.mutable) "var ${d.name}${typeOf(d.type)}" else "val ${d.name}${typeOf(d.type)}"
|
||||
is MiniValDecl -> {
|
||||
val t = d.type ?: DocLookupUtils.inferTypeRefForVal(d, text, imported, mini)
|
||||
val typeStr = if (t == null) ": Object?" else typeOf(t)
|
||||
if (d.mutable) "var ${d.name}${typeStr}" else "val ${d.name}${typeStr}"
|
||||
}
|
||||
}
|
||||
// Show full detailed documentation, not just the summary
|
||||
val raw = d.doc?.raw
|
||||
@@ -506,7 +535,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
}
|
||||
|
||||
private fun renderMemberValDoc(className: String, m: MiniMemberValDecl): String {
|
||||
val ts = typeOf(m.type)
|
||||
val ts = if (m.type == null) ": Object?" else typeOf(m.type)
|
||||
val kind = if (m.mutable) "var" else "val"
|
||||
val staticStr = if (m.isStatic) "static " else ""
|
||||
val title = "${staticStr}${kind} $className.${m.name}${ts}"
|
||||
@@ -527,7 +556,7 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
}
|
||||
is MiniFunctionType -> ": (..) -> ..${if (t.nullable) "?" else ""}"
|
||||
is MiniTypeVar -> ": ${t.name}${if (t.nullable) "?" else ""}"
|
||||
null -> ""
|
||||
null -> ": Object?"
|
||||
}
|
||||
|
||||
private fun signatureOf(fn: MiniFunDecl): String {
|
||||
|
||||
@@ -63,6 +63,10 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
||||
is MiniMemberFunDecl -> "Function"
|
||||
is MiniMemberValDecl -> if (member.mutable) "Variable" else "Value"
|
||||
is MiniInitDecl -> "Initializer"
|
||||
is MiniFunDecl -> "Function"
|
||||
is MiniValDecl -> if (member.mutable) "Variable" else "Value"
|
||||
is MiniClassDecl -> "Class"
|
||||
is MiniEnumDecl -> "Enum"
|
||||
}
|
||||
results.add(PsiElementResolveResult(LyngDeclarationElement(it, member.name, kind)))
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user