Refactored ObjRef evaluation logic for enhanced performance and consistency across unary, binary, and field operations. Added evalValue overrides for more streamlined value resolution.
This commit is contained in:
1 parent
827df9c8cd
commit
6b957ae6a3
28 files changed
+1339
-506
No files matched your search
+22
-10
@@ -167,7 +167,11 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
.withIcon(AllIcons.Nodes.Field)
|
||||
.let { b -> if (!ci.typeText.isNullOrBlank()) b.withTypeText(ci.typeText, true) else b }
|
||||
}
|
||||
emit(builder)
|
||||
if (ci.priority != 0.0) {
|
||||
emit(PrioritizedLookupElement.withPriority(builder, ci.priority))
|
||||
} else {
|
||||
emit(builder)
|
||||
}
|
||||
}
|
||||
// In member context, ensure stdlib extension-like methods (e.g., String.re) are present
|
||||
if (memberDotPos != null) {
|
||||
@@ -401,7 +405,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
}
|
||||
supplementPreferredBases(className)
|
||||
|
||||
fun emitGroup(map: LinkedHashMap<String, MutableList<MiniMemberDecl>>) {
|
||||
fun emitGroup(map: LinkedHashMap<String, MutableList<MiniMemberDecl>>, groupPriority: Double) {
|
||||
val keys = map.keys.sortedBy { it.lowercase() }
|
||||
for (name in keys) {
|
||||
val list = map[name] ?: continue
|
||||
@@ -428,7 +432,11 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
.withTailText(tail, true)
|
||||
.withTypeText(ret, true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
emit(builder)
|
||||
if (groupPriority != 0.0) {
|
||||
emit(PrioritizedLookupElement.withPriority(builder, groupPriority))
|
||||
} else {
|
||||
emit(builder)
|
||||
}
|
||||
}
|
||||
is MiniMemberValDecl -> {
|
||||
val icon = if (rep.mutable) AllIcons.Nodes.Variable else AllIcons.Nodes.Field
|
||||
@@ -439,7 +447,11 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
val builder = LookupElementBuilder.create(name)
|
||||
.withIcon(icon)
|
||||
.withTypeText(typeOf(chosen.type), true)
|
||||
emit(builder)
|
||||
if (groupPriority != 0.0) {
|
||||
emit(PrioritizedLookupElement.withPriority(builder, groupPriority))
|
||||
} else {
|
||||
emit(builder)
|
||||
}
|
||||
}
|
||||
is MiniInitDecl -> {}
|
||||
}
|
||||
@@ -447,8 +459,8 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
}
|
||||
|
||||
// Emit what we have first
|
||||
emitGroup(directMap)
|
||||
emitGroup(inheritedMap)
|
||||
emitGroup(directMap, 100.0)
|
||||
emitGroup(inheritedMap, 0.0)
|
||||
|
||||
// If suggestions are suspiciously sparse for known container classes,
|
||||
// try to conservatively supplement using a curated list resolved via docs registry.
|
||||
@@ -509,7 +521,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
}
|
||||
}
|
||||
emit(builder)
|
||||
emit(PrioritizedLookupElement.withPriority(builder, 50.0))
|
||||
already.add(name)
|
||||
} else {
|
||||
// Synthetic fallback: method without detailed params/types to improve UX in absence of docs
|
||||
@@ -523,7 +535,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
.withTailText("()", true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
}
|
||||
emit(builder)
|
||||
emit(PrioritizedLookupElement.withPriority(builder, 50.0))
|
||||
already.add(name)
|
||||
}
|
||||
}
|
||||
@@ -576,7 +588,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
}
|
||||
}
|
||||
emit(builder)
|
||||
emit(PrioritizedLookupElement.withPriority(builder, 50.0))
|
||||
already.add(name)
|
||||
continue
|
||||
}
|
||||
@@ -585,7 +597,7 @@ class LyngCompletionContributor : CompletionContributor() {
|
||||
.withIcon(AllIcons.Nodes.Method)
|
||||
.withTailText("()", true)
|
||||
.withInsertHandler(ParenInsertHandler)
|
||||
emit(builder)
|
||||
emit(PrioritizedLookupElement.withPriority(builder, 50.0))
|
||||
already.add(name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,25 +461,39 @@ class LyngDocumentationProvider : AbstractDocumentationProvider() {
|
||||
private fun ensureExternalDocsRegistered() { @Suppress("UNUSED_EXPRESSION") externalDocsLoaded }
|
||||
|
||||
private fun tryLoadExternalDocs(): Boolean {
|
||||
return try {
|
||||
var anyLoaded = false
|
||||
try {
|
||||
// Try known registrars; ignore failures if module is absent
|
||||
val cls = Class.forName("net.sergeych.lyngio.docs.FsBuiltinDocs")
|
||||
val m = cls.getMethod("ensure")
|
||||
m.invoke(null)
|
||||
log.info("[LYNG_DEBUG] QuickDoc: external docs loaded: net.sergeych.lyngio.docs.FsBuiltinDocs.ensure() OK")
|
||||
true
|
||||
} catch (_: Throwable) {
|
||||
anyLoaded = true
|
||||
} catch (_: Throwable) {}
|
||||
|
||||
try {
|
||||
val cls = Class.forName("net.sergeych.lyngio.docs.ProcessBuiltinDocs")
|
||||
val m = cls.getMethod("ensure")
|
||||
m.invoke(null)
|
||||
log.info("[LYNG_DEBUG] QuickDoc: external docs loaded: net.sergeych.lyngio.docs.ProcessBuiltinDocs.ensure() OK")
|
||||
anyLoaded = true
|
||||
} catch (_: Throwable) {}
|
||||
|
||||
if (!anyLoaded) {
|
||||
// Seed a minimal plugin-local fallback so Path docs still work without lyngio
|
||||
val seeded = try {
|
||||
FsDocsFallback.ensureOnce()
|
||||
ProcessDocsFallback.ensureOnce()
|
||||
true
|
||||
} catch (_: Throwable) { false }
|
||||
if (seeded) {
|
||||
log.info("[LYNG_DEBUG] QuickDoc: external docs NOT found; seeded plugin fallback for lyng.io.fs")
|
||||
log.info("[LYNG_DEBUG] QuickDoc: external docs NOT found; seeded plugin fallbacks")
|
||||
} else {
|
||||
log.info("[LYNG_DEBUG] QuickDoc: external docs NOT found (lyngio absent on classpath)")
|
||||
}
|
||||
seeded
|
||||
return seeded
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getCustomDocumentationElement(
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Minimal fallback docs seeding for `lyng.io.process` used only inside the IDEA plugin
|
||||
* when external docs module (lyngio) is not present on the classpath.
|
||||
*/
|
||||
package net.sergeych.lyng.idea.docs
|
||||
|
||||
import net.sergeych.lyng.miniast.BuiltinDocRegistry
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
|
||||
internal object ProcessDocsFallback {
|
||||
@Volatile
|
||||
private var seeded = false
|
||||
|
||||
fun ensureOnce(): Boolean {
|
||||
if (seeded) return true
|
||||
synchronized(this) {
|
||||
if (seeded) return true
|
||||
BuiltinDocRegistry.module("lyng.io.process") {
|
||||
classDoc(name = "Process", doc = "Process execution and control.") {
|
||||
method(
|
||||
name = "execute",
|
||||
doc = "Execute a process with arguments.",
|
||||
params = listOf(ParamDoc("executable", type("lyng.String")), ParamDoc("args", type("lyng.List"))),
|
||||
returns = type("RunningProcess"),
|
||||
isStatic = true
|
||||
)
|
||||
method(
|
||||
name = "shell",
|
||||
doc = "Execute a command via system shell.",
|
||||
params = listOf(ParamDoc("command", type("lyng.String"))),
|
||||
returns = type("RunningProcess"),
|
||||
isStatic = true
|
||||
)
|
||||
}
|
||||
|
||||
classDoc(name = "RunningProcess", doc = "Handle to a running process.") {
|
||||
method(name = "stdout", doc = "Get standard output stream as a Flow of lines.", returns = type("lyng.Flow"))
|
||||
method(name = "stderr", doc = "Get standard error stream as a Flow of lines.", returns = type("lyng.Flow"))
|
||||
method(name = "waitFor", doc = "Wait for the process to exit.", returns = type("lyng.Int"))
|
||||
method(name = "signal", doc = "Send a signal to the process.", params = listOf(ParamDoc("signal", type("lyng.String"))))
|
||||
method(name = "destroy", doc = "Forcefully terminate the process.")
|
||||
}
|
||||
|
||||
valDoc(name = "Process", doc = "Process execution and control.", type = type("Process"))
|
||||
valDoc(name = "RunningProcess", doc = "Handle to a running process.", type = type("RunningProcess"))
|
||||
}
|
||||
seeded = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
-12
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -54,6 +54,8 @@ class LyngSpellcheckingStrategy : SpellcheckingStrategy() {
|
||||
} catch (_: Throwable) { false }
|
||||
|
||||
override fun getTokenizer(element: PsiElement): Tokenizer<*> {
|
||||
if (element is com.intellij.psi.PsiFile) return EMPTY_TOKENIZER
|
||||
|
||||
val hasGrazie = grazieInstalled()
|
||||
val hasGrazieApi = grazieApiAvailable()
|
||||
val settings = LyngFormatterSettings.getInstance(element.project)
|
||||
@@ -63,27 +65,40 @@ class LyngSpellcheckingStrategy : SpellcheckingStrategy() {
|
||||
}
|
||||
|
||||
val file = element.containingFile ?: return EMPTY_TOKENIZER
|
||||
val index = LyngSpellIndex.getUpToDate(file) ?: run {
|
||||
// Suspend legacy spellcheck until MiniAst-based index is ready
|
||||
return EMPTY_TOKENIZER
|
||||
}
|
||||
val elRange = element.textRange ?: return EMPTY_TOKENIZER
|
||||
|
||||
fun overlaps(list: List<TextRange>) = list.any { it.intersects(elRange) }
|
||||
val et = element.node?.elementType
|
||||
val index = LyngSpellIndex.getUpToDate(file)
|
||||
|
||||
// Decide responsibility per settings
|
||||
// If Grazie is present but its public API is not available (IC-243), do NOT delegate to it.
|
||||
val preferGrazie = hasGrazie && hasGrazieApi && settings.preferGrazieForCommentsAndLiterals
|
||||
val grazieIds = hasGrazie && hasGrazieApi && settings.grazieChecksIdentifiers
|
||||
|
||||
if (index == null) {
|
||||
// Index not ready: fall back to Lexer-based token types.
|
||||
// Identifiers are safe because LyngLexer separates keywords from identifiers.
|
||||
if (et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.IDENTIFIER) {
|
||||
return if (grazieIds) EMPTY_TOKENIZER else IDENTIFIER_TOKENIZER
|
||||
}
|
||||
if (et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.LINE_COMMENT || et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.BLOCK_COMMENT) {
|
||||
return if (preferGrazie) EMPTY_TOKENIZER else COMMENT_TEXT_TOKENIZER
|
||||
}
|
||||
if (et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.STRING && settings.spellCheckStringLiterals) {
|
||||
return if (preferGrazie) EMPTY_TOKENIZER else STRING_WITH_PRINTF_EXCLUDES
|
||||
}
|
||||
return EMPTY_TOKENIZER
|
||||
}
|
||||
|
||||
val elRange = element.textRange ?: return EMPTY_TOKENIZER
|
||||
fun overlaps(list: List<TextRange>) = list.any { it.intersects(elRange) }
|
||||
|
||||
// Identifiers: only if range is within identifiers index and not delegated to Grazie
|
||||
if (overlaps(index.identifiers) && !grazieIds) return IDENTIFIER_TOKENIZER
|
||||
if (et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.IDENTIFIER && overlaps(index.identifiers) && !grazieIds) return IDENTIFIER_TOKENIZER
|
||||
|
||||
// Comments: only if not delegated to Grazie and overlapping indexed comments
|
||||
if (!preferGrazie && overlaps(index.comments)) return COMMENT_TEXT_TOKENIZER
|
||||
if ((et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.LINE_COMMENT || et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.BLOCK_COMMENT) && overlaps(index.comments) && !preferGrazie) return COMMENT_TEXT_TOKENIZER
|
||||
|
||||
// Strings: only if not delegated to Grazie, literals checking enabled, and overlapping indexed strings
|
||||
if (!preferGrazie && settings.spellCheckStringLiterals && overlaps(index.strings)) return STRING_WITH_PRINTF_EXCLUDES
|
||||
if (et == net.sergeych.lyng.idea.highlight.LyngTokenTypes.STRING && settings.spellCheckStringLiterals && overlaps(index.strings) && !preferGrazie) return STRING_WITH_PRINTF_EXCLUDES
|
||||
|
||||
return EMPTY_TOKENIZER
|
||||
}
|
||||
@@ -93,7 +108,7 @@ class LyngSpellcheckingStrategy : SpellcheckingStrategy() {
|
||||
}
|
||||
|
||||
private object IDENTIFIER_TOKENIZER : Tokenizer<PsiElement>() {
|
||||
private val splitter = PlainTextSplitter.getInstance()
|
||||
private val splitter = com.intellij.spellchecker.inspections.IdentifierSplitter.getInstance()
|
||||
override fun tokenize(element: PsiElement, consumer: TokenConsumer) {
|
||||
val text = element.text
|
||||
if (text.isNullOrEmpty()) return
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Ensure external/bundled docs are registered in BuiltinDocRegistry
|
||||
* so completion/quickdoc can resolve things like lyng.io.fs.Path.
|
||||
@@ -6,6 +23,7 @@ package net.sergeych.lyng.idea.util
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import net.sergeych.lyng.idea.docs.FsDocsFallback
|
||||
import net.sergeych.lyng.idea.docs.ProcessDocsFallback
|
||||
|
||||
object DocsBootstrap {
|
||||
private val log = Logger.getInstance(DocsBootstrap::class.java)
|
||||
@@ -20,20 +38,32 @@ object DocsBootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryLoadExternal(): Boolean = try {
|
||||
val cls = Class.forName("net.sergeych.lyngio.docs.FsBuiltinDocs")
|
||||
val m = cls.getMethod("ensure")
|
||||
m.invoke(null)
|
||||
log.info("[LYNG_DEBUG] DocsBootstrap: external docs loaded: net.sergeych.lyngio.docs.FsBuiltinDocs.ensure() OK")
|
||||
true
|
||||
} catch (_: Throwable) {
|
||||
false
|
||||
private fun tryLoadExternal(): Boolean {
|
||||
var anyLoaded = false
|
||||
try {
|
||||
val cls = Class.forName("net.sergeych.lyngio.docs.FsBuiltinDocs")
|
||||
val m = cls.getMethod("ensure")
|
||||
m.invoke(null)
|
||||
log.info("[LYNG_DEBUG] DocsBootstrap: external docs loaded: net.sergeych.lyngio.docs.FsBuiltinDocs.ensure() OK")
|
||||
anyLoaded = true
|
||||
} catch (_: Throwable) {}
|
||||
|
||||
try {
|
||||
val cls = Class.forName("net.sergeych.lyngio.docs.ProcessBuiltinDocs")
|
||||
val m = cls.getMethod("ensure")
|
||||
m.invoke(null)
|
||||
log.info("[LYNG_DEBUG] DocsBootstrap: external docs loaded: net.sergeych.lyngio.docs.ProcessBuiltinDocs.ensure() OK")
|
||||
anyLoaded = true
|
||||
} catch (_: Throwable) {}
|
||||
return anyLoaded
|
||||
}
|
||||
|
||||
private fun trySeedFallback(): Boolean = try {
|
||||
val seeded = FsDocsFallback.ensureOnce()
|
||||
val seededFs = FsDocsFallback.ensureOnce()
|
||||
val seededProcess = ProcessDocsFallback.ensureOnce()
|
||||
val seeded = seededFs || seededProcess
|
||||
if (seeded) {
|
||||
log.info("[LYNG_DEBUG] DocsBootstrap: external docs not found; seeded plugin fallback for lyng.io.fs")
|
||||
log.info("[LYNG_DEBUG] DocsBootstrap: external docs not found; seeded plugin fallback for lyng.io.fs/process")
|
||||
} else {
|
||||
log.info("[LYNG_DEBUG] DocsBootstrap: external docs not found; no fallback seeded")
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
-->
|
||||
|
||||
<idea-plugin>
|
||||
<!-- Open-ended compatibility: 2024.3+ (build 243 and newer) -->
|
||||
<idea-version since-build="243"/>
|
||||
<!-- Open-ended compatibility: 2024.1+ (build 241 and newer) -->
|
||||
<idea-version since-build="241"/>
|
||||
<id>net.sergeych.lyng.idea</id>
|
||||
<name>Lyng</name>
|
||||
<vendor email="real.sergeych@gmail.com">Sergey Chernov</vendor>
|
||||
|
||||
Reference in new issue
Block a user