Improve plugin symbol accuracy in completion and docs
This commit is contained in:
parent
3f2c38c471
commit
b233d4c15f
@ -122,6 +122,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
fromText.forEach { add(it) }
|
fromText.forEach { add(it) }
|
||||||
add("lyng.stdlib")
|
add("lyng.stdlib")
|
||||||
}.toList()
|
}.toList()
|
||||||
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, memberDotPos, imported, binding)
|
||||||
|
|
||||||
// Try inferring return/receiver class around the dot
|
// Try inferring return/receiver class around the dot
|
||||||
val inferred =
|
val inferred =
|
||||||
@ -136,7 +137,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
|
|
||||||
if (inferred != null) {
|
if (inferred != null) {
|
||||||
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Fallback inferred receiver/return class='$inferred' — offering its members")
|
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Fallback inferred receiver/return class='$inferred' — offering its members")
|
||||||
offerMembers(emit, imported, inferred, sourceText = text, mini = mini)
|
offerMembers(emit, imported, inferred, staticOnly = staticOnly, sourceText = text, mini = mini)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Fallback could not infer class; keeping list empty (no globals after dot)")
|
if (DEBUG_COMPLETION) log.info("[LYNG_DEBUG] Fallback could not infer class; keeping list empty (no globals after dot)")
|
||||||
@ -295,6 +296,9 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
}
|
}
|
||||||
is MiniEnumDecl -> LookupElementBuilder.create(name)
|
is MiniEnumDecl -> LookupElementBuilder.create(name)
|
||||||
.withIcon(AllIcons.Nodes.Enum)
|
.withIcon(AllIcons.Nodes.Enum)
|
||||||
|
is MiniTypeAliasDecl -> LookupElementBuilder.create(name)
|
||||||
|
.withIcon(AllIcons.Nodes.Class)
|
||||||
|
.withTypeText(typeOf(d.target), true)
|
||||||
}
|
}
|
||||||
emit(builder)
|
emit(builder)
|
||||||
}
|
}
|
||||||
@ -372,6 +376,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
when (m) {
|
when (m) {
|
||||||
is MiniMemberFunDecl -> if (!m.isStatic) continue
|
is MiniMemberFunDecl -> if (!m.isStatic) continue
|
||||||
is MiniMemberValDecl -> if (!m.isStatic) continue
|
is MiniMemberValDecl -> if (!m.isStatic) continue
|
||||||
|
is MiniMemberTypeAliasDecl -> if (!m.isStatic) continue
|
||||||
is MiniInitDecl -> continue
|
is MiniInitDecl -> continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -461,6 +466,16 @@ class LyngCompletionContributor : CompletionContributor() {
|
|||||||
emit(builder)
|
emit(builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is MiniMemberTypeAliasDecl -> {
|
||||||
|
val builder = LookupElementBuilder.create(name)
|
||||||
|
.withIcon(AllIcons.Nodes.Class)
|
||||||
|
.withTypeText(typeOf(rep.target), true)
|
||||||
|
if (groupPriority != 0.0) {
|
||||||
|
emit(PrioritizedLookupElement.withPriority(builder, groupPriority))
|
||||||
|
} else {
|
||||||
|
emit(builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
is MiniInitDecl -> {}
|
is MiniInitDecl -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -317,7 +317,8 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
}
|
}
|
||||||
if (DEBUG_LOG) log.info("[LYNG_DEBUG] QuickDoc: memberCtx dotPos=${dotPos} chBeforeDot='${if (dotPos > 0) text[dotPos - 1] else ' '}' classGuess=${className} imports=${importedModules}")
|
if (DEBUG_LOG) log.info("[LYNG_DEBUG] QuickDoc: memberCtx dotPos=${dotPos} chBeforeDot='${if (dotPos > 0) text[dotPos - 1] else ' '}' classGuess=${className} imports=${importedModules}")
|
||||||
if (className != null) {
|
if (className != null) {
|
||||||
DocLookupUtils.resolveMemberWithInheritance(importedModules, className, ident, mini)?.let { (owner, member) ->
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, dotPos, importedModules, analysis.binding)
|
||||||
|
DocLookupUtils.resolveMemberWithInheritance(importedModules, className, ident, mini, staticOnly = staticOnly)?.let { (owner, member) ->
|
||||||
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] QuickDoc: literal/call '$ident' resolved to $owner.${member.name}")
|
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] QuickDoc: literal/call '$ident' resolved to $owner.${member.name}")
|
||||||
return when (member) {
|
return when (member) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||||
@ -380,7 +381,9 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
val lhs = previousWordBefore(text, idRange.startOffset)
|
val lhs = previousWordBefore(text, idRange.startOffset)
|
||||||
if (lhs != null && hasDotBetween(text, lhs.endOffset, idRange.startOffset)) {
|
if (lhs != null && hasDotBetween(text, lhs.endOffset, idRange.startOffset)) {
|
||||||
val className = text.substring(lhs.startOffset, lhs.endOffset)
|
val className = text.substring(lhs.startOffset, lhs.endOffset)
|
||||||
DocLookupUtils.resolveMemberWithInheritance(importedModules, className, ident, mini)?.let { (owner, member) ->
|
val dotPos = findDotLeft(text, idRange.startOffset)
|
||||||
|
val staticOnly = dotPos?.let { DocLookupUtils.isStaticReceiver(mini, text, it, importedModules, analysis.binding) } ?: false
|
||||||
|
DocLookupUtils.resolveMemberWithInheritance(importedModules, className, ident, mini, staticOnly = staticOnly)?.let { (owner, member) ->
|
||||||
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Inheritance resolved $className.$ident to $owner.${member.name}")
|
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Inheritance resolved $className.$ident to $owner.${member.name}")
|
||||||
return when (member) {
|
return when (member) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||||
@ -405,7 +408,8 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
else -> DocLookupUtils.guessClassFromCallBefore(text, dotPos, importedModules, mini)
|
else -> DocLookupUtils.guessClassFromCallBefore(text, dotPos, importedModules, mini)
|
||||||
}
|
}
|
||||||
if (guessed != null) {
|
if (guessed != null) {
|
||||||
DocLookupUtils.resolveMemberWithInheritance(importedModules, guessed, ident, mini)?.let { (owner, member) ->
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, dotPos, importedModules, analysis.binding)
|
||||||
|
DocLookupUtils.resolveMemberWithInheritance(importedModules, guessed, ident, mini, staticOnly = staticOnly)?.let { (owner, member) ->
|
||||||
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Heuristic '$guessed.$ident' resolved via inheritance to $owner.${member.name}")
|
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Heuristic '$guessed.$ident' resolved via inheritance to $owner.${member.name}")
|
||||||
return when (member) {
|
return when (member) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||||
@ -424,7 +428,8 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
run {
|
run {
|
||||||
val candidates = listOf("String", "Iterable", "Iterator", "List", "Collection", "Array", "Dict", "Regex")
|
val candidates = listOf("String", "Iterable", "Iterator", "List", "Collection", "Array", "Dict", "Regex")
|
||||||
for (c in candidates) {
|
for (c in candidates) {
|
||||||
DocLookupUtils.resolveMemberWithInheritance(importedModules, c, ident, mini)?.let { (owner, member) ->
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, dotPos, importedModules, analysis.binding)
|
||||||
|
DocLookupUtils.resolveMemberWithInheritance(importedModules, c, ident, mini, staticOnly = staticOnly)?.let { (owner, member) ->
|
||||||
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Candidate '$c.$ident' resolved via inheritance to $owner.${member.name}")
|
if (DEBUG_INHERITANCE) log.info("[LYNG_DEBUG] Candidate '$c.$ident' resolved via inheritance to $owner.${member.name}")
|
||||||
return when (member) {
|
return when (member) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||||
@ -461,11 +466,13 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
|||||||
return when (member) {
|
return when (member) {
|
||||||
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
is MiniMemberFunDecl -> renderMemberFunDoc(owner, member)
|
||||||
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
is MiniMemberValDecl -> renderMemberValDoc(owner, member)
|
||||||
|
is MiniMemberTypeAliasDecl -> renderMemberTypeAliasDoc(owner, member)
|
||||||
is MiniInitDecl -> null
|
is MiniInitDecl -> null
|
||||||
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniFunDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniValDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniClassDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
is MiniEnumDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
|
is MiniTypeAliasDecl -> renderDeclDoc(member, text, mini, importedModules)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,9 +48,10 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
if (dotPos != null) {
|
if (dotPos != null) {
|
||||||
val receiverClass = DocLookupUtils.guessReceiverClassViaMini(mini, text, dotPos, imported.toList(), binding)
|
val receiverClass = DocLookupUtils.guessReceiverClassViaMini(mini, text, dotPos, imported.toList(), binding)
|
||||||
?: DocLookupUtils.guessReceiverClass(text, dotPos, imported.toList(), mini)
|
?: DocLookupUtils.guessReceiverClass(text, dotPos, imported.toList(), mini)
|
||||||
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, dotPos, imported.toList(), binding)
|
||||||
|
|
||||||
if (receiverClass != null) {
|
if (receiverClass != null) {
|
||||||
val resolved = DocLookupUtils.resolveMemberWithInheritance(imported.toList(), receiverClass, name, mini)
|
val resolved = DocLookupUtils.resolveMemberWithInheritance(imported.toList(), receiverClass, name, mini, staticOnly = staticOnly)
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
val owner = resolved.first
|
val owner = resolved.first
|
||||||
val member = resolved.second
|
val member = resolved.second
|
||||||
|
|||||||
@ -74,29 +74,30 @@ object CompletionEngineLight {
|
|||||||
val word = DocLookupUtils.wordRangeAt(text, caret)
|
val word = DocLookupUtils.wordRangeAt(text, caret)
|
||||||
val memberDot = DocLookupUtils.findDotLeft(text, word?.first ?: caret)
|
val memberDot = DocLookupUtils.findDotLeft(text, word?.first ?: caret)
|
||||||
if (memberDot != null) {
|
if (memberDot != null) {
|
||||||
|
val staticOnly = DocLookupUtils.isStaticReceiver(mini, text, memberDot, imported, binding)
|
||||||
val inferredCls = (DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding) ?: DocLookupUtils.guessReceiverClass(text, memberDot, imported, mini))
|
val inferredCls = (DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding) ?: DocLookupUtils.guessReceiverClass(text, memberDot, imported, mini))
|
||||||
// 0) Try chained member call return type inference
|
// 0) Try chained member call return type inference
|
||||||
DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding)?.let { cls ->
|
DocLookupUtils.guessReturnClassFromMemberCallBeforeMini(mini, text, memberDot, imported, binding)?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini, staticOnly)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
DocLookupUtils.guessReturnClassFromMemberCallBefore(text, memberDot, imported, mini)?.let { cls ->
|
DocLookupUtils.guessReturnClassFromMemberCallBefore(text, memberDot, imported, mini)?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini, staticOnly)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
// 0a) Top-level call before dot
|
// 0a) Top-level call before dot
|
||||||
DocLookupUtils.guessReturnClassFromTopLevelCallBefore(text, memberDot, imported, mini)?.let { cls ->
|
DocLookupUtils.guessReturnClassFromTopLevelCallBefore(text, memberDot, imported, mini)?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini, staticOnly)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
// 0b) Across-known-callees (Iterable/Iterator/List preference)
|
// 0b) Across-known-callees (Iterable/Iterator/List preference)
|
||||||
DocLookupUtils.guessReturnClassAcrossKnownCallees(text, memberDot, imported, mini)?.let { cls ->
|
DocLookupUtils.guessReturnClassAcrossKnownCallees(text, memberDot, imported, mini)?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini, staticOnly)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
// 1) Receiver inference fallback
|
// 1) Receiver inference fallback
|
||||||
(DocLookupUtils.guessReceiverClassViaMini(mini, text, memberDot, imported, binding) ?: DocLookupUtils.guessReceiverClass(text, memberDot, imported, mini))?.let { cls ->
|
(DocLookupUtils.guessReceiverClassViaMini(mini, text, memberDot, imported, binding) ?: DocLookupUtils.guessReceiverClass(text, memberDot, imported, mini))?.let { cls ->
|
||||||
offerMembersAdd(out, prefix, imported, cls, mini)
|
offerMembersAdd(out, prefix, imported, cls, mini, staticOnly)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
// In member context and unknown receiver/return type: show nothing (no globals after dot)
|
// In member context and unknown receiver/return type: show nothing (no globals after dot)
|
||||||
@ -106,10 +107,20 @@ object CompletionEngineLight {
|
|||||||
// Global identifiers: params > local decls > imported > stdlib; Functions > Classes > Values; alphabetical
|
// Global identifiers: params > local decls > imported > stdlib; Functions > Classes > Values; alphabetical
|
||||||
offerParamsInScope(out, prefix, mini, text, caret)
|
offerParamsInScope(out, prefix, mini, text, caret)
|
||||||
|
|
||||||
val locals = DocLookupUtils.extractLocalsAt(text, caret)
|
val localsFromBinding = DocLookupUtils.collectLocalsFromBinding(mini, binding, caret)
|
||||||
for (name in locals) {
|
if (localsFromBinding.isNotEmpty()) {
|
||||||
if (name.startsWith(prefix, true)) {
|
for (sym in localsFromBinding) {
|
||||||
out.add(CompletionItem(name, Kind.Value, priority = 150.0))
|
if (sym.name.startsWith(prefix, true)) {
|
||||||
|
val t = sym.type?.let { ": $it" }
|
||||||
|
out.add(CompletionItem(sym.name, Kind.Value, typeText = t, priority = 150.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val locals = DocLookupUtils.extractLocalsAt(text, caret)
|
||||||
|
for (name in locals) {
|
||||||
|
if (name.startsWith(prefix, true)) {
|
||||||
|
out.add(CompletionItem(name, Kind.Value, priority = 150.0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +249,7 @@ object CompletionEngineLight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun offerMembersAdd(out: MutableList<CompletionItem>, prefix: String, imported: List<String>, className: String, mini: MiniScript? = null) {
|
private fun offerMembersAdd(out: MutableList<CompletionItem>, prefix: String, imported: List<String>, className: String, mini: MiniScript? = null, staticOnly: Boolean = false) {
|
||||||
val classes = DocLookupUtils.aggregateClasses(imported, mini)
|
val classes = DocLookupUtils.aggregateClasses(imported, mini)
|
||||||
val visited = mutableSetOf<String>()
|
val visited = mutableSetOf<String>()
|
||||||
val directMap = LinkedHashMap<String, MutableList<MiniMemberDecl>>()
|
val directMap = LinkedHashMap<String, MutableList<MiniMemberDecl>>()
|
||||||
@ -247,10 +258,15 @@ object CompletionEngineLight {
|
|||||||
fun addMembersOf(name: String, direct: Boolean) {
|
fun addMembersOf(name: String, direct: Boolean) {
|
||||||
val cls = classes[name] ?: return
|
val cls = classes[name] ?: return
|
||||||
val target = if (direct) directMap else inheritedMap
|
val target = if (direct) directMap else inheritedMap
|
||||||
for (cf in cls.ctorFields + cls.classFields) {
|
if (!staticOnly) {
|
||||||
target.getOrPut(cf.name) { mutableListOf() }.add(DocLookupUtils.toMemberVal(cf))
|
for (cf in cls.ctorFields + cls.classFields) {
|
||||||
|
target.getOrPut(cf.name) { mutableListOf() }.add(DocLookupUtils.toMemberVal(cf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (m in cls.members) {
|
||||||
|
if (staticOnly && !m.isStatic) continue
|
||||||
|
target.getOrPut(m.name) { mutableListOf() }.add(m)
|
||||||
}
|
}
|
||||||
for (m in cls.members) target.getOrPut(m.name) { mutableListOf() }.add(m)
|
|
||||||
for (b in cls.bases) if (visited.add(b)) addMembersOf(b, false)
|
for (b in cls.bases) if (visited.add(b)) addMembersOf(b, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +326,7 @@ object CompletionEngineLight {
|
|||||||
emitGroup(inheritedMap, 0.0)
|
emitGroup(inheritedMap, 0.0)
|
||||||
|
|
||||||
// Supplement with extension members (both stdlib and local)
|
// Supplement with extension members (both stdlib and local)
|
||||||
run {
|
if (!staticOnly) run {
|
||||||
val already = (directMap.keys + inheritedMap.keys).toMutableSet()
|
val already = (directMap.keys + inheritedMap.keys).toMutableSet()
|
||||||
val extensions = DocLookupUtils.collectExtensionMemberNames(imported, className, mini)
|
val extensions = DocLookupUtils.collectExtensionMemberNames(imported, className, mini)
|
||||||
for (name in extensions) {
|
for (name in extensions) {
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package net.sergeych.lyng.miniast
|
package net.sergeych.lyng.miniast
|
||||||
|
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.binding.BindingSnapshot
|
import net.sergeych.lyng.binding.BindingSnapshot
|
||||||
import net.sergeych.lyng.highlight.offsetOf
|
import net.sergeych.lyng.highlight.offsetOf
|
||||||
|
|
||||||
@ -301,32 +302,42 @@ object DocLookupUtils {
|
|||||||
isExtern = false
|
isExtern = false
|
||||||
)
|
)
|
||||||
|
|
||||||
fun resolveMemberWithInheritance(importedModules: List<String>, className: String, member: String, localMini: MiniScript? = null): Pair<String, MiniNamedDecl>? {
|
fun resolveMemberWithInheritance(
|
||||||
|
importedModules: List<String>,
|
||||||
|
className: String,
|
||||||
|
member: String,
|
||||||
|
localMini: MiniScript? = null,
|
||||||
|
staticOnly: Boolean = false
|
||||||
|
): Pair<String, MiniNamedDecl>? {
|
||||||
val classes = aggregateClasses(importedModules, localMini)
|
val classes = aggregateClasses(importedModules, localMini)
|
||||||
fun dfs(name: String, visited: MutableSet<String>): Pair<String, MiniNamedDecl>? {
|
fun dfs(name: String, visited: MutableSet<String>): Pair<String, MiniNamedDecl>? {
|
||||||
if (!visited.add(name)) return null
|
if (!visited.add(name)) return null
|
||||||
val cls = classes[name]
|
val cls = classes[name]
|
||||||
if (cls != null) {
|
if (cls != null) {
|
||||||
cls.members.firstOrNull { it.name == member }?.let { return name to it }
|
cls.members.firstOrNull { it.name == member && (!staticOnly || it.isStatic) }?.let { return name to it }
|
||||||
cls.ctorFields.firstOrNull { it.name == member }?.let { return name to toMemberVal(it) }
|
if (!staticOnly) {
|
||||||
cls.classFields.firstOrNull { it.name == member }?.let { return name to toMemberVal(it) }
|
cls.ctorFields.firstOrNull { it.name == member }?.let { return name to toMemberVal(it) }
|
||||||
|
cls.classFields.firstOrNull { it.name == member }?.let { return name to toMemberVal(it) }
|
||||||
|
}
|
||||||
for (baseName in cls.bases) {
|
for (baseName in cls.bases) {
|
||||||
dfs(baseName, visited)?.let { return it }
|
dfs(baseName, visited)?.let { return it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 1) local extensions in this class or bases
|
if (!staticOnly) {
|
||||||
localMini?.declarations?.firstOrNull { d ->
|
// 1) local extensions in this class or bases
|
||||||
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
localMini?.declarations?.firstOrNull { d ->
|
||||||
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
|
||||||
}?.let { return name to it as MiniNamedDecl }
|
|
||||||
|
|
||||||
// 2) built-in extensions from BuiltinDocRegistry
|
|
||||||
for (mod in importedModules) {
|
|
||||||
val decls = BuiltinDocRegistry.docsForModule(mod)
|
|
||||||
decls.firstOrNull { d ->
|
|
||||||
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
||||||
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
||||||
}?.let { return name to it as MiniNamedDecl }
|
}?.let { return name to it as MiniNamedDecl }
|
||||||
|
|
||||||
|
// 2) built-in extensions from BuiltinDocRegistry
|
||||||
|
for (mod in importedModules) {
|
||||||
|
val decls = BuiltinDocRegistry.docsForModule(mod)
|
||||||
|
decls.firstOrNull { d ->
|
||||||
|
(d is MiniFunDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member) ||
|
||||||
|
(d is MiniValDecl && d.receiver != null && simpleClassNameOf(d.receiver) == name && d.name == member)
|
||||||
|
}?.let { return name to it as MiniNamedDecl }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
@ -430,6 +441,7 @@ object DocLookupUtils {
|
|||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
val sym = binding.symbols.firstOrNull { it.id == ref.symbolId }
|
val sym = binding.symbols.firstOrNull { it.id == ref.symbolId }
|
||||||
if (sym != null) {
|
if (sym != null) {
|
||||||
|
simpleClassNameOfType(sym.type)?.let { return it }
|
||||||
val type = findTypeByRange(mini, sym.name, sym.declStart, text, imported)
|
val type = findTypeByRange(mini, sym.name, sym.declStart, text, imported)
|
||||||
simpleClassNameOf(type)?.let { return it }
|
simpleClassNameOf(type)?.let { return it }
|
||||||
}
|
}
|
||||||
@ -437,6 +449,7 @@ object DocLookupUtils {
|
|||||||
// Check if it's a declaration (e.g. static access to a class)
|
// Check if it's a declaration (e.g. static access to a class)
|
||||||
val sym = binding.symbols.firstOrNull { it.declStart == wordRange.first && it.name == ident }
|
val sym = binding.symbols.firstOrNull { it.declStart == wordRange.first && it.name == ident }
|
||||||
if (sym != null) {
|
if (sym != null) {
|
||||||
|
simpleClassNameOfType(sym.type)?.let { return it }
|
||||||
val type = findTypeByRange(mini, sym.name, sym.declStart, text, imported)
|
val type = findTypeByRange(mini, sym.name, sym.declStart, text, imported)
|
||||||
simpleClassNameOf(type)?.let { return it }
|
simpleClassNameOf(type)?.let { return it }
|
||||||
// if it's a class/enum, return its name directly
|
// if it's a class/enum, return its name directly
|
||||||
@ -1042,6 +1055,16 @@ object DocLookupUtils {
|
|||||||
is MiniTypeIntersection -> null
|
is MiniTypeIntersection -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun simpleClassNameOfType(type: String?): String? {
|
||||||
|
if (type.isNullOrBlank()) return null
|
||||||
|
var t = type.trim()
|
||||||
|
if (t.endsWith("?")) t = t.dropLast(1)
|
||||||
|
val first = t.split('|', '&').firstOrNull()?.trim() ?: return null
|
||||||
|
val base = first.substringBefore('<').trim()
|
||||||
|
val short = base.substringAfterLast('.').trim()
|
||||||
|
return short.ifBlank { null }
|
||||||
|
}
|
||||||
|
|
||||||
fun typeOf(t: MiniTypeRef?): String = when (t) {
|
fun typeOf(t: MiniTypeRef?): String = when (t) {
|
||||||
is MiniTypeName -> t.segments.joinToString(".") { it.name } + (if (t.nullable) "?" else "")
|
is MiniTypeName -> t.segments.joinToString(".") { it.name } + (if (t.nullable) "?" else "")
|
||||||
is MiniGenericType -> typeOf(t.base) + "<" + t.args.joinToString(", ") { typeOf(it) } + ">" + (if (t.nullable) "?" else "")
|
is MiniGenericType -> typeOf(t.base) + "<" + t.args.joinToString(", ") { typeOf(it) } + ">" + (if (t.nullable) "?" else "")
|
||||||
@ -1055,6 +1078,90 @@ object DocLookupUtils {
|
|||||||
null -> ""
|
null -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun collectLocalsFromBinding(mini: MiniScript?, binding: BindingSnapshot?, offset: Int): List<net.sergeych.lyng.binding.Symbol> {
|
||||||
|
if (mini == null || binding == null) return emptyList()
|
||||||
|
val src = mini.range.start.source
|
||||||
|
data class FnCtx(val nameStart: Pos, val body: MiniRange)
|
||||||
|
fun consider(nameStart: Pos, body: MiniRange?, best: FnCtx?): FnCtx? {
|
||||||
|
if (body == null) return best
|
||||||
|
val start = src.offsetOf(body.start)
|
||||||
|
val end = src.offsetOf(body.end)
|
||||||
|
if (offset < start || offset > end) return best
|
||||||
|
val len = end - start
|
||||||
|
val bestLen = best?.let { src.offsetOf(it.body.end) - src.offsetOf(it.body.start) } ?: Int.MAX_VALUE
|
||||||
|
return if (len < bestLen) FnCtx(nameStart, body) else best
|
||||||
|
}
|
||||||
|
|
||||||
|
var best: FnCtx? = null
|
||||||
|
for (d in mini.declarations) {
|
||||||
|
when (d) {
|
||||||
|
is MiniFunDecl -> best = consider(d.nameStart, d.body?.range, best)
|
||||||
|
is MiniClassDecl -> {
|
||||||
|
for (m in d.members) {
|
||||||
|
if (m is MiniMemberFunDecl) {
|
||||||
|
best = consider(m.nameStart, m.body?.range, best)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val fn = best ?: return emptyList()
|
||||||
|
val fnDeclStart = src.offsetOf(fn.nameStart)
|
||||||
|
val fnSym = binding.symbols.firstOrNull {
|
||||||
|
it.kind == net.sergeych.lyng.binding.SymbolKind.Function && it.declStart == fnDeclStart
|
||||||
|
} ?: return emptyList()
|
||||||
|
return binding.symbols.filter {
|
||||||
|
it.containerId == fnSym.id &&
|
||||||
|
(it.kind == net.sergeych.lyng.binding.SymbolKind.Parameter ||
|
||||||
|
it.kind == net.sergeych.lyng.binding.SymbolKind.Value ||
|
||||||
|
it.kind == net.sergeych.lyng.binding.SymbolKind.Variable) &&
|
||||||
|
it.declStart < offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isStaticReceiver(
|
||||||
|
mini: MiniScript?,
|
||||||
|
text: String,
|
||||||
|
dotPos: Int,
|
||||||
|
importedModules: List<String>,
|
||||||
|
binding: BindingSnapshot? = null
|
||||||
|
): Boolean {
|
||||||
|
val i = prevNonWs(text, dotPos - 1)
|
||||||
|
if (i < 0 || !isIdentChar(text[i])) return false
|
||||||
|
val wordRange = wordRangeAt(text, i + 1) ?: return false
|
||||||
|
val ident = text.substring(wordRange.first, wordRange.second)
|
||||||
|
if (ident == "this") return false
|
||||||
|
|
||||||
|
if (binding != null) {
|
||||||
|
val ref = binding.references.firstOrNull { wordRange.first >= it.start && wordRange.first < it.end }
|
||||||
|
val sym = ref?.let { r -> binding.symbols.firstOrNull { it.id == r.symbolId } }
|
||||||
|
?: binding.symbols.firstOrNull { it.declStart == wordRange.first && it.name == ident }
|
||||||
|
if (sym != null) {
|
||||||
|
return sym.kind == net.sergeych.lyng.binding.SymbolKind.Class ||
|
||||||
|
sym.kind == net.sergeych.lyng.binding.SymbolKind.Enum ||
|
||||||
|
sym.kind == net.sergeych.lyng.binding.SymbolKind.TypeAlias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mini != null) {
|
||||||
|
val src = mini.range.start.source
|
||||||
|
val decl = mini.declarations
|
||||||
|
.filter { it.name == ident && src.offsetOf(it.nameStart) < dotPos }
|
||||||
|
.maxByOrNull { src.offsetOf(it.nameStart) }
|
||||||
|
if (decl is MiniClassDecl || decl is MiniEnumDecl || decl is MiniTypeAliasDecl) return true
|
||||||
|
if (decl is MiniFunDecl || decl is MiniValDecl) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val classes = aggregateClasses(importedModules, mini)
|
||||||
|
if (classes.containsKey(ident)) return true
|
||||||
|
for (mod in importedModules) {
|
||||||
|
val aliases = BuiltinDocRegistry.docsForModule(mod).filterIsInstance<MiniTypeAliasDecl>()
|
||||||
|
if (aliases.any { it.name == ident }) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fun findDotLeft(text: String, offset: Int): Int? {
|
fun findDotLeft(text: String, offset: Int): Int? {
|
||||||
var i = (offset - 1).coerceAtLeast(0)
|
var i = (offset - 1).coerceAtLeast(0)
|
||||||
while (i >= 0 && text[i].isWhitespace()) i--
|
while (i >= 0 && text[i].isWhitespace()) i--
|
||||||
|
|||||||
@ -225,6 +225,21 @@ class MiniAstTest {
|
|||||||
assertTrue(names.contains("V2"), "Should contain V2")
|
assertTrue(names.contains("V2"), "Should contain V2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun complete_static_members_only() = runTest {
|
||||||
|
val code = """
|
||||||
|
class C {
|
||||||
|
static fun s() {}
|
||||||
|
fun i() {}
|
||||||
|
}
|
||||||
|
C.<caret>
|
||||||
|
"""
|
||||||
|
val items = CompletionEngineLight.completeAtMarkerSuspend(code)
|
||||||
|
val names = items.map { it.name }.toSet()
|
||||||
|
assertTrue(names.contains("s"), "Should contain static member")
|
||||||
|
assertTrue(!names.contains("i"), "Should not contain instance member")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun miniAst_captures_extern_docs() = runTest {
|
fun miniAst_captures_extern_docs() = runTest {
|
||||||
val code = """
|
val code = """
|
||||||
|
|||||||
@ -84,6 +84,23 @@ class LyngLanguageToolsTest {
|
|||||||
assertEquals("Box docs", doc.doc?.summary)
|
assertEquals("Box docs", doc.doc?.summary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun languageTools_completion_includes_local_types() = runTest {
|
||||||
|
val code = """
|
||||||
|
fun f() {
|
||||||
|
val local: String = "x"
|
||||||
|
<caret>
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
val caret = code.indexOf("<caret>")
|
||||||
|
val text = code.replace("<caret>", "")
|
||||||
|
val res = LyngLanguageTools.analyze(text, "locals.lyng")
|
||||||
|
val items = LyngLanguageTools.completions(text, caret, res)
|
||||||
|
val local = items.firstOrNull { it.name == "local" }
|
||||||
|
assertNotNull(local, "Completion should include local")
|
||||||
|
assertTrue(local.typeText?.contains("String") == true, "Expected type for local, got ${local.typeText}")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun languageTools_definition_and_usages() = runTest {
|
fun languageTools_definition_and_usages() = runTest {
|
||||||
val code = """
|
val code = """
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user