plugin: remove flaky spell checking for 2024.*
This commit is contained in:
parent
80933c287d
commit
2d2a74656c
@ -21,6 +21,12 @@
|
|||||||
- MI Satisfaction: Abstract requirements are automatically satisfied by matching concrete members found later in the C3 MRO chain without requiring explicit proxy methods.
|
- MI Satisfaction: Abstract requirements are automatically satisfied by matching concrete members found later in the C3 MRO chain without requiring explicit proxy methods.
|
||||||
- Integration: Updated highlighters (lynglib, lyngweb, IDEA plugin), IDEA completion, and Grazie grammar checking.
|
- Integration: Updated highlighters (lynglib, lyngweb, IDEA plugin), IDEA completion, and Grazie grammar checking.
|
||||||
- Documentation: Updated `docs/OOP.md` with sections on "Abstract Classes and Members", "Interfaces", and "Overriding and Virtual Dispatch".
|
- Documentation: Updated `docs/OOP.md` with sections on "Abstract Classes and Members", "Interfaces", and "Overriding and Virtual Dispatch".
|
||||||
|
- IDEA plugin: Improved natural language support and spellchecking
|
||||||
|
- Disabled the limited built-in English and Technical dictionaries.
|
||||||
|
- Enforced usage of the platform's standard Natural Languages (Grazie) and Spellchecker components.
|
||||||
|
- Integrated `SpellCheckerManager` for word suggestions and validation, respecting users' personal and project dictionaries.
|
||||||
|
- Added project-specific "learned words" support via `Lyng Formatter` settings and quick-fixes.
|
||||||
|
- Enhanced fallback spellchecker for technical terms and Lyng-specific vocabulary.
|
||||||
|
|
||||||
- Language: Class properties with accessors
|
- Language: Class properties with accessors
|
||||||
- Support for `val` (read-only) and `var` (read-write) properties in classes.
|
- Support for `val` (read-only) and `var` (read-write) properties in classes.
|
||||||
|
|||||||
@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package net.sergeych.lyng.idea.grazie
|
|
||||||
|
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.InputStreamReader
|
|
||||||
import java.util.zip.GZIPInputStream
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Very simple English dictionary loader for offline suggestions on IC-243.
|
|
||||||
* It loads a word list from classpath resources. Supports plain text (one word per line)
|
|
||||||
* and gzipped text if the resource ends with .gz.
|
|
||||||
*/
|
|
||||||
object EnglishDictionary {
|
|
||||||
private val log = Logger.getInstance(EnglishDictionary::class.java)
|
|
||||||
|
|
||||||
@Volatile private var loaded = false
|
|
||||||
@Volatile private var words: Set<String> = emptySet()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load dictionary from bundled resources (once).
|
|
||||||
* If multiple candidates exist, the first found is used.
|
|
||||||
*/
|
|
||||||
private fun ensureLoaded() {
|
|
||||||
if (loaded) return
|
|
||||||
synchronized(this) {
|
|
||||||
if (loaded) return
|
|
||||||
val candidates = listOf(
|
|
||||||
// preferred large bundles first (add en-basic.txt.gz ~3–5MB here)
|
|
||||||
"/dictionaries/en-basic.txt.gz",
|
|
||||||
"/dictionaries/en-large.txt.gz",
|
|
||||||
// plain text fallbacks
|
|
||||||
"/dictionaries/en-basic.txt",
|
|
||||||
"/dictionaries/en-large.txt",
|
|
||||||
)
|
|
||||||
val merged = HashSet<String>(128_000)
|
|
||||||
for (res in candidates) {
|
|
||||||
try {
|
|
||||||
val stream = javaClass.getResourceAsStream(res) ?: continue
|
|
||||||
val reader = if (res.endsWith(".gz"))
|
|
||||||
BufferedReader(InputStreamReader(GZIPInputStream(stream)))
|
|
||||||
else
|
|
||||||
BufferedReader(InputStreamReader(stream))
|
|
||||||
var loadedCount = 0
|
|
||||||
reader.useLines { seq -> seq.forEach { line ->
|
|
||||||
val w = line.trim()
|
|
||||||
if (w.isNotEmpty() && !w.startsWith("#")) { merged += w.lowercase(); loadedCount++ }
|
|
||||||
} }
|
|
||||||
log.info("EnglishDictionary: loaded $loadedCount words from $res (total=${merged.size})")
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
log.info("EnglishDictionary: failed to load $res: ${t.javaClass.simpleName}: ${t.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (merged.isEmpty()) {
|
|
||||||
// Fallback minimal set
|
|
||||||
merged += setOf("comment","comments","error","errors","found","file","not","word","words","count","value","name","class","function","string")
|
|
||||||
log.info("EnglishDictionary: using minimal built-in set (${merged.size})")
|
|
||||||
}
|
|
||||||
words = merged
|
|
||||||
loaded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun allWords(): Set<String> {
|
|
||||||
ensureLoaded()
|
|
||||||
return words
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -402,6 +402,8 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
|||||||
var painted = 0
|
var painted = 0
|
||||||
val docText = file.viewProvider.document?.text ?: return 0
|
val docText = file.viewProvider.document?.text ?: return 0
|
||||||
val tokenRegex = Regex("[A-Za-z][A-Za-z0-9_']{2,}")
|
val tokenRegex = Regex("[A-Za-z][A-Za-z0-9_']{2,}")
|
||||||
|
val settings = LyngFormatterSettings.getInstance(file.project)
|
||||||
|
val learned = settings.learnedWords
|
||||||
for ((content, hostRange) in fragments) {
|
for ((content, hostRange) in fragments) {
|
||||||
val text = try { docText.substring(hostRange.startOffset, hostRange.endOffset) } catch (_: Throwable) { null } ?: continue
|
val text = try { docText.substring(hostRange.startOffset, hostRange.endOffset) } catch (_: Throwable) { null } ?: continue
|
||||||
var seen = 0
|
var seen = 0
|
||||||
@ -413,7 +415,7 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
|||||||
val parts = splitIdentifier(token)
|
val parts = splitIdentifier(token)
|
||||||
for (part in parts) {
|
for (part in parts) {
|
||||||
if (part.length <= 2) continue
|
if (part.length <= 2) continue
|
||||||
if (isAllowedWord(part)) continue
|
if (isAllowedWord(part, learned)) continue
|
||||||
|
|
||||||
// Map part back to original token occurrence within this hostRange
|
// Map part back to original token occurrence within this hostRange
|
||||||
val localStart = m.range.first + token.indexOf(part)
|
val localStart = m.range.first + token.indexOf(part)
|
||||||
@ -451,6 +453,8 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
|||||||
var painted = 0
|
var painted = 0
|
||||||
val docText = file.viewProvider.document?.text
|
val docText = file.viewProvider.document?.text
|
||||||
val tokenRegex = Regex("[A-Za-z][A-Za-z0-9_']{2,}")
|
val tokenRegex = Regex("[A-Za-z][A-Za-z0-9_']{2,}")
|
||||||
|
val settings = LyngFormatterSettings.getInstance(file.project)
|
||||||
|
val learned = settings.learnedWords
|
||||||
val baseWords = setOf(
|
val baseWords = setOf(
|
||||||
// small, common vocabulary to catch near-miss typos in typical code/comments
|
// small, common vocabulary to catch near-miss typos in typical code/comments
|
||||||
"comment","comments","error","errors","found","file","not","word","words","count","value","name","class","function","string"
|
"comment","comments","error","errors","found","file","not","word","words","count","value","name","class","function","string"
|
||||||
@ -469,7 +473,7 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
|||||||
for (part in parts) {
|
for (part in parts) {
|
||||||
seen++
|
seen++
|
||||||
val lower = part.lowercase()
|
val lower = part.lowercase()
|
||||||
if (lower.length <= 2 || isAllowedWord(part)) continue
|
if (lower.length <= 2 || isAllowedWord(part, learned)) continue
|
||||||
|
|
||||||
val localStart = m.range.first + token.indexOf(part)
|
val localStart = m.range.first + token.indexOf(part)
|
||||||
val localEnd = localStart + part.length
|
val localEnd = localStart + part.length
|
||||||
@ -540,29 +544,32 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
|||||||
private fun suggestReplacements(file: PsiFile, word: String): List<String> {
|
private fun suggestReplacements(file: PsiFile, word: String): List<String> {
|
||||||
val lower = word.lowercase()
|
val lower = word.lowercase()
|
||||||
val fromProject = collectProjectWords(file)
|
val fromProject = collectProjectWords(file)
|
||||||
val fromTech = TechDictionary.allWords()
|
|
||||||
val fromEnglish = EnglishDictionary.allWords()
|
val fromSpellChecker = try {
|
||||||
// Merge with priority: project (p=0), tech (p=1), english (p=2)
|
val mgrCls = Class.forName("com.intellij.spellchecker.SpellCheckerManager")
|
||||||
|
val getInstance = mgrCls.methods.firstOrNull { it.name == "getInstance" && it.parameterCount == 1 }
|
||||||
|
val getSuggestions = mgrCls.methods.firstOrNull { it.name == "getSuggestions" && it.parameterCount == 1 && it.parameterTypes[0] == String::class.java }
|
||||||
|
val mgr = getInstance?.invoke(null, file.project)
|
||||||
|
if (mgr != null && getSuggestions != null) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
getSuggestions.invoke(mgr, word) as? List<String>
|
||||||
|
} else null
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
null
|
||||||
|
} ?: emptyList()
|
||||||
|
|
||||||
|
// Merge with priority: project (p=0), spellchecker (p=1)
|
||||||
val all = LinkedHashSet<String>()
|
val all = LinkedHashSet<String>()
|
||||||
all.addAll(fromProject)
|
// Add project words that are close enough
|
||||||
all.addAll(fromTech)
|
for (w in fromProject) {
|
||||||
all.addAll(fromEnglish)
|
|
||||||
data class Cand(val w: String, val d: Int, val p: Int)
|
|
||||||
val cands = ArrayList<Cand>(32)
|
|
||||||
for (w in all) {
|
|
||||||
if (w == lower) continue
|
if (w == lower) continue
|
||||||
if (kotlin.math.abs(w.length - lower.length) > 2) continue
|
if (kotlin.math.abs(w.length - lower.length) <= 2 && editDistance(lower, w) <= 2) {
|
||||||
val d = editDistance(lower, w)
|
all.add(w)
|
||||||
val p = when {
|
|
||||||
w in fromProject -> 0
|
|
||||||
w in fromTech -> 1
|
|
||||||
else -> 2
|
|
||||||
}
|
}
|
||||||
cands += Cand(w, d, p)
|
|
||||||
}
|
}
|
||||||
cands.sortWith(compareBy<Cand> { it.d }.thenBy { it.p }.thenBy { it.w })
|
all.addAll(fromSpellChecker)
|
||||||
// Return a larger pool so callers can choose desired display count
|
|
||||||
return cands.take(16).map { it.w }
|
return all.take(16).toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectProjectWords(file: PsiFile): Set<String> {
|
private fun collectProjectWords(file: PsiFile): Set<String> {
|
||||||
@ -589,14 +596,19 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isAllowedWord(w: String): Boolean {
|
private fun isAllowedWord(w: String, learnedWords: Set<String> = emptySet()): Boolean {
|
||||||
val s = w.lowercase()
|
val s = w.lowercase()
|
||||||
|
if (s in learnedWords) return true
|
||||||
return s in setOf(
|
return s in setOf(
|
||||||
// common code words / language keywords to avoid noise
|
// common code words / language keywords to avoid noise
|
||||||
"val","var","fun","class","interface","enum","type","import","package","return","if","else","when","while","for","try","catch","finally","true","false","null",
|
"val","var","fun","class","interface","enum","type","import","package","return","if","else","when","while","for","try","catch","finally","true","false","null",
|
||||||
"abstract","closed","override",
|
"abstract","closed","override",
|
||||||
// very common English words
|
// very common English words
|
||||||
"the","and","or","not","with","from","into","this","that","file","found","count","name","value","object"
|
"the","and","or","not","with","from","into","this","that","file","found","count","name","value","object",
|
||||||
|
// Lyng technical/vocabulary words formerly in TechDictionary
|
||||||
|
"lyng","miniast","binder","printf","specifier","specifiers","regex","token","tokens",
|
||||||
|
"identifier","identifiers","keyword","keywords","comment","comments","string","strings",
|
||||||
|
"literal","literals","formatting","formatter","grazie","typo","typos","dictionary","dictionaries"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2025 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package net.sergeych.lyng.idea.grazie
|
|
||||||
|
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.InputStreamReader
|
|
||||||
import java.util.zip.GZIPInputStream
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lightweight technical/Lyng vocabulary dictionary.
|
|
||||||
* Loaded from classpath resources; supports .txt and .txt.gz. Merged with EnglishDictionary.
|
|
||||||
*/
|
|
||||||
object TechDictionary {
|
|
||||||
private val log = Logger.getInstance(TechDictionary::class.java)
|
|
||||||
@Volatile private var loaded = false
|
|
||||||
@Volatile private var words: Set<String> = emptySet()
|
|
||||||
|
|
||||||
private fun ensureLoaded() {
|
|
||||||
if (loaded) return
|
|
||||||
synchronized(this) {
|
|
||||||
if (loaded) return
|
|
||||||
val candidates = listOf(
|
|
||||||
"/dictionaries/tech-lyng.txt.gz",
|
|
||||||
"/dictionaries/tech-lyng.txt"
|
|
||||||
)
|
|
||||||
val merged = HashSet<String>(8_000)
|
|
||||||
for (res in candidates) {
|
|
||||||
try {
|
|
||||||
val stream = javaClass.getResourceAsStream(res) ?: continue
|
|
||||||
val reader = if (res.endsWith(".gz"))
|
|
||||||
BufferedReader(InputStreamReader(GZIPInputStream(stream)))
|
|
||||||
else
|
|
||||||
BufferedReader(InputStreamReader(stream))
|
|
||||||
var n = 0
|
|
||||||
reader.useLines { seq -> seq.forEach { line ->
|
|
||||||
val w = line.trim()
|
|
||||||
if (w.isNotEmpty() && !w.startsWith("#")) { merged += w.lowercase(); n++ }
|
|
||||||
} }
|
|
||||||
log.info("TechDictionary: loaded $n words from $res (total=${merged.size})")
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
log.info("TechDictionary: failed to load $res: ${t.javaClass.simpleName}: ${t.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (merged.isEmpty()) {
|
|
||||||
merged += setOf(
|
|
||||||
// minimal Lyng/tech seeding to avoid empty dictionary
|
|
||||||
"lyng","miniast","binder","printf","specifier","specifiers","regex","token","tokens",
|
|
||||||
"identifier","identifiers","keyword","keywords","comment","comments","string","strings",
|
|
||||||
"literal","literals","formatting","formatter","grazie","typo","typos","dictionary","dictionaries"
|
|
||||||
)
|
|
||||||
log.info("TechDictionary: using minimal built-in set (${merged.size})")
|
|
||||||
}
|
|
||||||
words = merged
|
|
||||||
loaded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun allWords(): Set<String> {
|
|
||||||
ensureLoaded()
|
|
||||||
return words
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -20,13 +20,14 @@ package net.sergeych.lyng.idea.navigation
|
|||||||
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler
|
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
|
import net.sergeych.lyng.idea.LyngLanguage
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures Ctrl+B (Go to Definition) works on Lyng identifiers by resolving through LyngPsiReference.
|
* Ensures Ctrl+B (Go to Definition) works on Lyng identifiers by resolving through LyngPsiReference.
|
||||||
*/
|
*/
|
||||||
class LyngGotoDeclarationHandler : GotoDeclarationHandler {
|
class LyngGotoDeclarationHandler : GotoDeclarationHandler {
|
||||||
override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor?): Array<PsiElement>? {
|
override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor?): Array<PsiElement>? {
|
||||||
if (sourceElement == null) return null
|
if (sourceElement == null || sourceElement.language != LyngLanguage) return null
|
||||||
|
|
||||||
val allTargets = mutableListOf<PsiElement>()
|
val allTargets = mutableListOf<PsiElement>()
|
||||||
|
|
||||||
|
|||||||
@ -38,16 +38,18 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
|
|
||||||
val mini = LyngAstManager.getMiniAst(file) ?: return emptyArray()
|
val mini = LyngAstManager.getMiniAst(file) ?: return emptyArray()
|
||||||
val binding = LyngAstManager.getBinding(file)
|
val binding = LyngAstManager.getBinding(file)
|
||||||
|
val imported = DocLookupUtils.canonicalImportedModules(mini, text).toSet()
|
||||||
|
val currentPackage = getPackageName(file)
|
||||||
|
val allowedPackages = if (currentPackage != null) imported + currentPackage else imported
|
||||||
|
|
||||||
// 1. Member resolution (obj.member)
|
// 1. Member resolution (obj.member)
|
||||||
val dotPos = TextCtx.findDotLeft(text, offset)
|
val dotPos = TextCtx.findDotLeft(text, offset)
|
||||||
if (dotPos != null) {
|
if (dotPos != null) {
|
||||||
val imported = DocLookupUtils.canonicalImportedModules(mini, text)
|
val receiverClass = DocLookupUtils.guessReceiverClassViaMini(mini, text, dotPos, imported.toList(), binding)
|
||||||
val receiverClass = DocLookupUtils.guessReceiverClassViaMini(mini, text, dotPos, imported, binding)
|
?: DocLookupUtils.guessReceiverClass(text, dotPos, imported.toList(), mini)
|
||||||
?: DocLookupUtils.guessReceiverClass(text, dotPos, imported, mini)
|
|
||||||
|
|
||||||
if (receiverClass != null) {
|
if (receiverClass != null) {
|
||||||
val resolved = DocLookupUtils.resolveMemberWithInheritance(imported, receiverClass, name, mini)
|
val resolved = DocLookupUtils.resolveMemberWithInheritance(imported.toList(), receiverClass, name, mini)
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
val owner = resolved.first
|
val owner = resolved.first
|
||||||
val member = resolved.second
|
val member = resolved.second
|
||||||
@ -75,7 +77,7 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
}
|
}
|
||||||
// If we couldn't resolve exactly, we might still want to search globally but ONLY for members
|
// If we couldn't resolve exactly, we might still want to search globally but ONLY for members
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
results.addAll(resolveGlobally(file.project, name, membersOnly = true))
|
results.addAll(resolveGlobally(file.project, name, membersOnly = true, allowedPackages = allowedPackages))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 2. Local resolution via Binder
|
// 2. Local resolution via Binder
|
||||||
@ -94,7 +96,7 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
// 3. Global project scan
|
// 3. Global project scan
|
||||||
// Only search globally if we haven't found a strong local match
|
// Only search globally if we haven't found a strong local match
|
||||||
if (results.isEmpty()) {
|
if (results.isEmpty()) {
|
||||||
results.addAll(resolveGlobally(file.project, name))
|
results.addAll(resolveGlobally(file.project, name, allowedPackages = allowedPackages))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +143,16 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getPackageName(file: PsiFile): String? {
|
||||||
|
val mini = LyngAstManager.getMiniAst(file) ?: return null
|
||||||
|
return try {
|
||||||
|
val pkg = mini.range.start.source.extractPackageName()
|
||||||
|
if (pkg.startsWith("lyng.")) pkg else "lyng.$pkg"
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun resolve(): PsiElement? {
|
override fun resolve(): PsiElement? {
|
||||||
val results = multiResolve(false)
|
val results = multiResolve(false)
|
||||||
if (results.isEmpty()) return null
|
if (results.isEmpty()) return null
|
||||||
@ -154,13 +166,20 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
|||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveGlobally(project: Project, name: String, membersOnly: Boolean = false): List<ResolveResult> {
|
private fun resolveGlobally(project: Project, name: String, membersOnly: Boolean = false, allowedPackages: Set<String>? = null): List<ResolveResult> {
|
||||||
val results = mutableListOf<ResolveResult>()
|
val results = mutableListOf<ResolveResult>()
|
||||||
val files = FilenameIndex.getAllFilesByExt(project, "lyng", GlobalSearchScope.projectScope(project))
|
val files = FilenameIndex.getAllFilesByExt(project, "lyng", GlobalSearchScope.projectScope(project))
|
||||||
val psiManager = PsiManager.getInstance(project)
|
val psiManager = PsiManager.getInstance(project)
|
||||||
|
|
||||||
for (vFile in files) {
|
for (vFile in files) {
|
||||||
val file = psiManager.findFile(vFile) ?: continue
|
val file = psiManager.findFile(vFile) ?: continue
|
||||||
|
|
||||||
|
// Filter by package if requested
|
||||||
|
if (allowedPackages != null) {
|
||||||
|
val pkg = getPackageName(file)
|
||||||
|
if (pkg == null || pkg !in allowedPackages) continue
|
||||||
|
}
|
||||||
|
|
||||||
val mini = LyngAstManager.getMiniAst(file) ?: continue
|
val mini = LyngAstManager.getMiniAst(file) ?: continue
|
||||||
val src = mini.range.start.source
|
val src = mini.range.start.source
|
||||||
|
|
||||||
|
|||||||
@ -1,466 +0,0 @@
|
|||||||
the
|
|
||||||
be
|
|
||||||
to
|
|
||||||
of
|
|
||||||
and
|
|
||||||
a
|
|
||||||
in
|
|
||||||
that
|
|
||||||
have
|
|
||||||
I
|
|
||||||
it
|
|
||||||
for
|
|
||||||
not
|
|
||||||
on
|
|
||||||
with
|
|
||||||
he
|
|
||||||
as
|
|
||||||
you
|
|
||||||
do
|
|
||||||
at
|
|
||||||
this
|
|
||||||
but
|
|
||||||
his
|
|
||||||
by
|
|
||||||
from
|
|
||||||
they
|
|
||||||
we
|
|
||||||
say
|
|
||||||
her
|
|
||||||
she
|
|
||||||
or
|
|
||||||
an
|
|
||||||
will
|
|
||||||
my
|
|
||||||
one
|
|
||||||
all
|
|
||||||
would
|
|
||||||
there
|
|
||||||
their
|
|
||||||
what
|
|
||||||
so
|
|
||||||
up
|
|
||||||
out
|
|
||||||
if
|
|
||||||
about
|
|
||||||
who
|
|
||||||
get
|
|
||||||
which
|
|
||||||
go
|
|
||||||
me
|
|
||||||
when
|
|
||||||
make
|
|
||||||
can
|
|
||||||
like
|
|
||||||
time
|
|
||||||
no
|
|
||||||
just
|
|
||||||
him
|
|
||||||
know
|
|
||||||
take
|
|
||||||
people
|
|
||||||
into
|
|
||||||
year
|
|
||||||
your
|
|
||||||
good
|
|
||||||
some
|
|
||||||
could
|
|
||||||
them
|
|
||||||
see
|
|
||||||
other
|
|
||||||
than
|
|
||||||
then
|
|
||||||
now
|
|
||||||
look
|
|
||||||
only
|
|
||||||
come
|
|
||||||
its
|
|
||||||
over
|
|
||||||
think
|
|
||||||
also
|
|
||||||
back
|
|
||||||
after
|
|
||||||
use
|
|
||||||
two
|
|
||||||
how
|
|
||||||
our
|
|
||||||
work
|
|
||||||
first
|
|
||||||
well
|
|
||||||
way
|
|
||||||
even
|
|
||||||
new
|
|
||||||
want
|
|
||||||
because
|
|
||||||
any
|
|
||||||
these
|
|
||||||
give
|
|
||||||
day
|
|
||||||
most
|
|
||||||
us
|
|
||||||
is
|
|
||||||
are
|
|
||||||
was
|
|
||||||
were
|
|
||||||
been
|
|
||||||
being
|
|
||||||
does
|
|
||||||
did
|
|
||||||
done
|
|
||||||
has
|
|
||||||
had
|
|
||||||
having
|
|
||||||
may
|
|
||||||
might
|
|
||||||
must
|
|
||||||
shall
|
|
||||||
should
|
|
||||||
ought
|
|
||||||
need
|
|
||||||
used
|
|
||||||
here
|
|
||||||
therefore
|
|
||||||
where
|
|
||||||
why
|
|
||||||
while
|
|
||||||
until
|
|
||||||
since
|
|
||||||
before
|
|
||||||
afterward
|
|
||||||
between
|
|
||||||
among
|
|
||||||
without
|
|
||||||
within
|
|
||||||
through
|
|
||||||
across
|
|
||||||
against
|
|
||||||
toward
|
|
||||||
upon
|
|
||||||
above
|
|
||||||
below
|
|
||||||
under
|
|
||||||
around
|
|
||||||
near
|
|
||||||
far
|
|
||||||
early
|
|
||||||
late
|
|
||||||
often
|
|
||||||
always
|
|
||||||
never
|
|
||||||
seldom
|
|
||||||
sometimes
|
|
||||||
usually
|
|
||||||
really
|
|
||||||
very
|
|
||||||
quite
|
|
||||||
rather
|
|
||||||
almost
|
|
||||||
already
|
|
||||||
again
|
|
||||||
still
|
|
||||||
yet
|
|
||||||
soon
|
|
||||||
today
|
|
||||||
tomorrow
|
|
||||||
yesterday
|
|
||||||
number
|
|
||||||
string
|
|
||||||
boolean
|
|
||||||
true
|
|
||||||
false
|
|
||||||
null
|
|
||||||
none
|
|
||||||
file
|
|
||||||
files
|
|
||||||
path
|
|
||||||
paths
|
|
||||||
line
|
|
||||||
lines
|
|
||||||
word
|
|
||||||
words
|
|
||||||
count
|
|
||||||
value
|
|
||||||
values
|
|
||||||
name
|
|
||||||
names
|
|
||||||
title
|
|
||||||
text
|
|
||||||
message
|
|
||||||
error
|
|
||||||
errors
|
|
||||||
warning
|
|
||||||
warnings
|
|
||||||
info
|
|
||||||
information
|
|
||||||
debug
|
|
||||||
trace
|
|
||||||
format
|
|
||||||
printf
|
|
||||||
specifier
|
|
||||||
specifiers
|
|
||||||
pattern
|
|
||||||
patterns
|
|
||||||
match
|
|
||||||
matches
|
|
||||||
regex
|
|
||||||
version
|
|
||||||
versions
|
|
||||||
module
|
|
||||||
modules
|
|
||||||
package
|
|
||||||
packages
|
|
||||||
import
|
|
||||||
imports
|
|
||||||
export
|
|
||||||
exports
|
|
||||||
class
|
|
||||||
classes
|
|
||||||
object
|
|
||||||
objects
|
|
||||||
function
|
|
||||||
functions
|
|
||||||
method
|
|
||||||
methods
|
|
||||||
parameter
|
|
||||||
parameters
|
|
||||||
argument
|
|
||||||
arguments
|
|
||||||
variable
|
|
||||||
variables
|
|
||||||
constant
|
|
||||||
constants
|
|
||||||
type
|
|
||||||
types
|
|
||||||
generic
|
|
||||||
generics
|
|
||||||
map
|
|
||||||
maps
|
|
||||||
list
|
|
||||||
lists
|
|
||||||
array
|
|
||||||
arrays
|
|
||||||
set
|
|
||||||
sets
|
|
||||||
queue
|
|
||||||
stack
|
|
||||||
graph
|
|
||||||
tree
|
|
||||||
node
|
|
||||||
nodes
|
|
||||||
edge
|
|
||||||
edges
|
|
||||||
pair
|
|
||||||
pairs
|
|
||||||
key
|
|
||||||
keys
|
|
||||||
value
|
|
||||||
values
|
|
||||||
index
|
|
||||||
indices
|
|
||||||
length
|
|
||||||
size
|
|
||||||
empty
|
|
||||||
contains
|
|
||||||
equals
|
|
||||||
compare
|
|
||||||
greater
|
|
||||||
less
|
|
||||||
minimum
|
|
||||||
maximum
|
|
||||||
average
|
|
||||||
sum
|
|
||||||
total
|
|
||||||
random
|
|
||||||
round
|
|
||||||
floor
|
|
||||||
ceil
|
|
||||||
sin
|
|
||||||
cos
|
|
||||||
tan
|
|
||||||
sqrt
|
|
||||||
abs
|
|
||||||
min
|
|
||||||
max
|
|
||||||
read
|
|
||||||
write
|
|
||||||
open
|
|
||||||
close
|
|
||||||
append
|
|
||||||
create
|
|
||||||
delete
|
|
||||||
remove
|
|
||||||
update
|
|
||||||
save
|
|
||||||
load
|
|
||||||
start
|
|
||||||
stop
|
|
||||||
run
|
|
||||||
execute
|
|
||||||
return
|
|
||||||
break
|
|
||||||
continue
|
|
||||||
try
|
|
||||||
catch
|
|
||||||
finally
|
|
||||||
throw
|
|
||||||
throws
|
|
||||||
if
|
|
||||||
else
|
|
||||||
when
|
|
||||||
while
|
|
||||||
for
|
|
||||||
loop
|
|
||||||
range
|
|
||||||
case
|
|
||||||
switch
|
|
||||||
default
|
|
||||||
optional
|
|
||||||
required
|
|
||||||
enable
|
|
||||||
disable
|
|
||||||
enabled
|
|
||||||
disabled
|
|
||||||
visible
|
|
||||||
hidden
|
|
||||||
public
|
|
||||||
private
|
|
||||||
protected
|
|
||||||
internal
|
|
||||||
external
|
|
||||||
inline
|
|
||||||
override
|
|
||||||
abstract
|
|
||||||
sealed
|
|
||||||
open
|
|
||||||
final
|
|
||||||
static
|
|
||||||
const
|
|
||||||
lazy
|
|
||||||
late
|
|
||||||
init
|
|
||||||
initialize
|
|
||||||
configuration
|
|
||||||
settings
|
|
||||||
option
|
|
||||||
options
|
|
||||||
preference
|
|
||||||
preferences
|
|
||||||
project
|
|
||||||
projects
|
|
||||||
module
|
|
||||||
modules
|
|
||||||
build
|
|
||||||
builds
|
|
||||||
compile
|
|
||||||
compiles
|
|
||||||
compiler
|
|
||||||
test
|
|
||||||
tests
|
|
||||||
testing
|
|
||||||
assert
|
|
||||||
assertion
|
|
||||||
result
|
|
||||||
results
|
|
||||||
success
|
|
||||||
failure
|
|
||||||
status
|
|
||||||
state
|
|
||||||
context
|
|
||||||
scope
|
|
||||||
scopes
|
|
||||||
token
|
|
||||||
tokens
|
|
||||||
identifier
|
|
||||||
identifiers
|
|
||||||
keyword
|
|
||||||
keywords
|
|
||||||
comment
|
|
||||||
comments
|
|
||||||
string
|
|
||||||
strings
|
|
||||||
literal
|
|
||||||
literals
|
|
||||||
formatting
|
|
||||||
formatter
|
|
||||||
spell
|
|
||||||
spelling
|
|
||||||
dictionary
|
|
||||||
dictionaries
|
|
||||||
language
|
|
||||||
languages
|
|
||||||
natural
|
|
||||||
grazie
|
|
||||||
typo
|
|
||||||
typos
|
|
||||||
suggest
|
|
||||||
suggestion
|
|
||||||
suggestions
|
|
||||||
replace
|
|
||||||
replacement
|
|
||||||
replacements
|
|
||||||
learn
|
|
||||||
learned
|
|
||||||
learns
|
|
||||||
filter
|
|
||||||
filters
|
|
||||||
exclude
|
|
||||||
excludes
|
|
||||||
include
|
|
||||||
includes
|
|
||||||
bundle
|
|
||||||
bundled
|
|
||||||
resource
|
|
||||||
resources
|
|
||||||
gzipped
|
|
||||||
plain
|
|
||||||
text
|
|
||||||
editor
|
|
||||||
editors
|
|
||||||
inspection
|
|
||||||
inspections
|
|
||||||
highlight
|
|
||||||
highlighting
|
|
||||||
underline
|
|
||||||
underlines
|
|
||||||
style
|
|
||||||
styles
|
|
||||||
range
|
|
||||||
ranges
|
|
||||||
offset
|
|
||||||
offsets
|
|
||||||
position
|
|
||||||
positions
|
|
||||||
apply
|
|
||||||
applies
|
|
||||||
provides
|
|
||||||
present
|
|
||||||
absent
|
|
||||||
available
|
|
||||||
unavailable
|
|
||||||
version
|
|
||||||
build
|
|
||||||
platform
|
|
||||||
ide
|
|
||||||
intellij
|
|
||||||
plugin
|
|
||||||
plugins
|
|
||||||
sandbox
|
|
||||||
gradle
|
|
||||||
kotlin
|
|
||||||
java
|
|
||||||
linux
|
|
||||||
macos
|
|
||||||
windows
|
|
||||||
unix
|
|
||||||
system
|
|
||||||
systems
|
|
||||||
support
|
|
||||||
supports
|
|
||||||
compatible
|
|
||||||
compatibility
|
|
||||||
fallback
|
|
||||||
native
|
|
||||||
automatic
|
|
||||||
autoswitch
|
|
||||||
switch
|
|
||||||
switches
|
|
||||||
@ -1,282 +0,0 @@
|
|||||||
# Lyng/tech vocabulary – one word per line, lowercase
|
|
||||||
lyng
|
|
||||||
miniast
|
|
||||||
binder
|
|
||||||
printf
|
|
||||||
specifier
|
|
||||||
specifiers
|
|
||||||
regex
|
|
||||||
regexp
|
|
||||||
token
|
|
||||||
tokens
|
|
||||||
lexer
|
|
||||||
parser
|
|
||||||
syntax
|
|
||||||
semantic
|
|
||||||
highlight
|
|
||||||
highlighting
|
|
||||||
underline
|
|
||||||
typo
|
|
||||||
typos
|
|
||||||
dictionary
|
|
||||||
dictionaries
|
|
||||||
grazie
|
|
||||||
natural
|
|
||||||
languages
|
|
||||||
inspection
|
|
||||||
inspections
|
|
||||||
annotation
|
|
||||||
annotator
|
|
||||||
annotations
|
|
||||||
quickfix
|
|
||||||
quickfixes
|
|
||||||
intention
|
|
||||||
intentions
|
|
||||||
replacement
|
|
||||||
replacements
|
|
||||||
identifier
|
|
||||||
identifiers
|
|
||||||
keyword
|
|
||||||
keywords
|
|
||||||
comment
|
|
||||||
comments
|
|
||||||
string
|
|
||||||
strings
|
|
||||||
literal
|
|
||||||
literals
|
|
||||||
formatting
|
|
||||||
formatter
|
|
||||||
splitter
|
|
||||||
camelcase
|
|
||||||
snakecase
|
|
||||||
pascalcase
|
|
||||||
uppercase
|
|
||||||
lowercase
|
|
||||||
titlecase
|
|
||||||
case
|
|
||||||
cases
|
|
||||||
project
|
|
||||||
module
|
|
||||||
modules
|
|
||||||
resource
|
|
||||||
resources
|
|
||||||
bundle
|
|
||||||
bundled
|
|
||||||
gzipped
|
|
||||||
plaintext
|
|
||||||
text
|
|
||||||
range
|
|
||||||
ranges
|
|
||||||
offset
|
|
||||||
offsets
|
|
||||||
position
|
|
||||||
positions
|
|
||||||
apply
|
|
||||||
applies
|
|
||||||
runtime
|
|
||||||
compile
|
|
||||||
build
|
|
||||||
artifact
|
|
||||||
artifacts
|
|
||||||
plugin
|
|
||||||
plugins
|
|
||||||
intellij
|
|
||||||
idea
|
|
||||||
sandbox
|
|
||||||
gradle
|
|
||||||
kotlin
|
|
||||||
java
|
|
||||||
jvm
|
|
||||||
coroutines
|
|
||||||
suspend
|
|
||||||
scope
|
|
||||||
scopes
|
|
||||||
context
|
|
||||||
contexts
|
|
||||||
tokenizer
|
|
||||||
tokenizers
|
|
||||||
spell
|
|
||||||
spelling
|
|
||||||
spellcheck
|
|
||||||
spellchecker
|
|
||||||
fallback
|
|
||||||
native
|
|
||||||
autoswitch
|
|
||||||
switch
|
|
||||||
switching
|
|
||||||
enable
|
|
||||||
disable
|
|
||||||
enabled
|
|
||||||
disabled
|
|
||||||
setting
|
|
||||||
settings
|
|
||||||
preference
|
|
||||||
preferences
|
|
||||||
editor
|
|
||||||
filetype
|
|
||||||
filetypes
|
|
||||||
language
|
|
||||||
languages
|
|
||||||
psi
|
|
||||||
psielement
|
|
||||||
psifile
|
|
||||||
textcontent
|
|
||||||
textdomain
|
|
||||||
stealth
|
|
||||||
stealthy
|
|
||||||
printfspec
|
|
||||||
format
|
|
||||||
formats
|
|
||||||
pattern
|
|
||||||
patterns
|
|
||||||
match
|
|
||||||
matches
|
|
||||||
group
|
|
||||||
groups
|
|
||||||
node
|
|
||||||
nodes
|
|
||||||
tree
|
|
||||||
graph
|
|
||||||
edge
|
|
||||||
edges
|
|
||||||
pair
|
|
||||||
pairs
|
|
||||||
map
|
|
||||||
maps
|
|
||||||
list
|
|
||||||
lists
|
|
||||||
array
|
|
||||||
arrays
|
|
||||||
set
|
|
||||||
sets
|
|
||||||
queue
|
|
||||||
stack
|
|
||||||
index
|
|
||||||
indices
|
|
||||||
length
|
|
||||||
size
|
|
||||||
empty
|
|
||||||
contains
|
|
||||||
equals
|
|
||||||
compare
|
|
||||||
greater
|
|
||||||
less
|
|
||||||
minimum
|
|
||||||
maximum
|
|
||||||
average
|
|
||||||
sum
|
|
||||||
total
|
|
||||||
random
|
|
||||||
round
|
|
||||||
floor
|
|
||||||
ceil
|
|
||||||
sin
|
|
||||||
cos
|
|
||||||
tan
|
|
||||||
sqrt
|
|
||||||
abs
|
|
||||||
min
|
|
||||||
max
|
|
||||||
read
|
|
||||||
write
|
|
||||||
open
|
|
||||||
close
|
|
||||||
append
|
|
||||||
create
|
|
||||||
delete
|
|
||||||
remove
|
|
||||||
update
|
|
||||||
save
|
|
||||||
load
|
|
||||||
start
|
|
||||||
stop
|
|
||||||
run
|
|
||||||
execute
|
|
||||||
return
|
|
||||||
break
|
|
||||||
continue
|
|
||||||
try
|
|
||||||
catch
|
|
||||||
finally
|
|
||||||
throw
|
|
||||||
throws
|
|
||||||
if
|
|
||||||
else
|
|
||||||
when
|
|
||||||
while
|
|
||||||
for
|
|
||||||
loop
|
|
||||||
rangeop
|
|
||||||
caseop
|
|
||||||
switchop
|
|
||||||
default
|
|
||||||
optional
|
|
||||||
required
|
|
||||||
public
|
|
||||||
private
|
|
||||||
protected
|
|
||||||
internal
|
|
||||||
external
|
|
||||||
inline
|
|
||||||
override
|
|
||||||
abstract
|
|
||||||
sealed
|
|
||||||
open
|
|
||||||
final
|
|
||||||
static
|
|
||||||
const
|
|
||||||
lazy
|
|
||||||
late
|
|
||||||
init
|
|
||||||
initialize
|
|
||||||
configuration
|
|
||||||
option
|
|
||||||
options
|
|
||||||
projectwide
|
|
||||||
workspace
|
|
||||||
crossplatform
|
|
||||||
multiplatform
|
|
||||||
commonmain
|
|
||||||
jsmain
|
|
||||||
native
|
|
||||||
platform
|
|
||||||
api
|
|
||||||
implementation
|
|
||||||
dependency
|
|
||||||
dependencies
|
|
||||||
classpath
|
|
||||||
source
|
|
||||||
sources
|
|
||||||
document
|
|
||||||
documents
|
|
||||||
logging
|
|
||||||
logger
|
|
||||||
info
|
|
||||||
debug
|
|
||||||
trace
|
|
||||||
warning
|
|
||||||
error
|
|
||||||
severity
|
|
||||||
severitylevel
|
|
||||||
intentionaction
|
|
||||||
daemon
|
|
||||||
daemoncodeanalyzer
|
|
||||||
restart
|
|
||||||
textattributes
|
|
||||||
textattributeskey
|
|
||||||
typostyle
|
|
||||||
learned
|
|
||||||
learn
|
|
||||||
tech
|
|
||||||
vocabulary
|
|
||||||
domain
|
|
||||||
term
|
|
||||||
terms
|
|
||||||
us
|
|
||||||
uk
|
|
||||||
american
|
|
||||||
british
|
|
||||||
colour
|
|
||||||
color
|
|
||||||
organisation
|
|
||||||
organization
|
|
||||||
@ -100,11 +100,16 @@ object LyngFormatter {
|
|||||||
fun isControlHeaderNoBrace(s: String): Boolean {
|
fun isControlHeaderNoBrace(s: String): Boolean {
|
||||||
val t = s.trim()
|
val t = s.trim()
|
||||||
if (t.isEmpty()) return false
|
if (t.isEmpty()) return false
|
||||||
// match: if (...) | else if (...) | else
|
// match: if (...) | else if (...) | else | catch (...) | finally
|
||||||
val isIf = Regex("^if\\s*\\(.*\\)\\s*$").matches(t)
|
if (Regex("^if\\s*\\(.*\\)$").matches(t)) return true
|
||||||
val isElseIf = Regex("^else\\s+if\\s*\\(.*\\)\\s*$").matches(t)
|
if (Regex("^else\\s+if\\s*\\(.*\\)$").matches(t)) return true
|
||||||
val isElse = t == "else"
|
if (t == "else") return true
|
||||||
if (isIf || isElseIf || isElse) return true
|
if (Regex("^catch\\s*\\(.*\\)$").matches(t)) return true
|
||||||
|
if (t == "finally") return true
|
||||||
|
|
||||||
|
// Short definition form: fun x() = or val x =
|
||||||
|
if (Regex("^(override\\s+)?(fun|fn)\\b.*=\\s*$").matches(t)) return true
|
||||||
|
if (Regex("^(private\\s+|protected\\s+|public\\s+|override\\s+)?(val|var)\\b.*=\\s*$").matches(t)) return true
|
||||||
|
|
||||||
// property accessors ending with ) or =
|
// property accessors ending with ) or =
|
||||||
if (isAccessorRelated(t)) {
|
if (isAccessorRelated(t)) {
|
||||||
@ -113,11 +118,23 @@ object LyngFormatter {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isDoubleIndentHeader(s: String): Boolean {
|
||||||
|
val t = s.trim()
|
||||||
|
if (!t.endsWith("=")) return false
|
||||||
|
// Is it a function or property definition?
|
||||||
|
if (Regex("\\b(fun|fn|val|var)\\b").containsMatchIn(t)) return true
|
||||||
|
// Is it an accessor?
|
||||||
|
if (isPropertyAccessor(t)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for ((i, rawLine) in lines.withIndex()) {
|
for ((i, rawLine) in lines.withIndex()) {
|
||||||
val (parts, nextInBlockComment) = splitIntoParts(rawLine, inBlockComment)
|
val (parts, nextInBlockComment) = splitIntoParts(rawLine, inBlockComment)
|
||||||
val code = parts.filter { it.type == PartType.Code }.joinToString("") { it.text }
|
val code = parts.filter { it.type == PartType.Code }.joinToString("") { it.text }
|
||||||
|
val codeAndStrings = parts.filter { it.type != PartType.BlockComment && it.type != PartType.LineComment }.joinToString("") { it.text }
|
||||||
val trimmedStart = code.dropWhile { it == ' ' || it == '\t' }
|
val trimmedStart = code.dropWhile { it == ' ' || it == '\t' }
|
||||||
val trimmedLine = rawLine.trim()
|
val trimmedLine = rawLine.trim()
|
||||||
|
val trimmedCodeAndStrings = codeAndStrings.trim()
|
||||||
|
|
||||||
// Compute effective indent level for this line
|
// Compute effective indent level for this line
|
||||||
val currentExtraIndent = extraIndents.sum()
|
val currentExtraIndent = extraIndents.sum()
|
||||||
@ -138,9 +155,15 @@ object LyngFormatter {
|
|||||||
|
|
||||||
// Single-line control header (if/else/else if) without braces: indent the next
|
// Single-line control header (if/else/else if) without braces: indent the next
|
||||||
// non-empty, non-'}', non-'else' line by one extra level
|
// non-empty, non-'}', non-'else' line by one extra level
|
||||||
|
val isContinuation = trimmedStart.startsWith("else") || trimmedStart.startsWith("catch") || trimmedStart.startsWith("finally")
|
||||||
val applyAwaiting = awaitingExtraIndent > 0 && trimmedStart.isNotEmpty() &&
|
val applyAwaiting = awaitingExtraIndent > 0 && trimmedStart.isNotEmpty() &&
|
||||||
!trimmedStart.startsWith("else") && !trimmedStart.startsWith("}")
|
!trimmedStart.startsWith("}")
|
||||||
if (applyAwaiting) effectiveLevel += awaitingExtraIndent
|
var actualAwaiting = 0
|
||||||
|
if (applyAwaiting) {
|
||||||
|
actualAwaiting = awaitingExtraIndent
|
||||||
|
if (isContinuation) actualAwaiting = (actualAwaiting - 1).coerceAtLeast(0)
|
||||||
|
effectiveLevel += actualAwaiting
|
||||||
|
}
|
||||||
|
|
||||||
val firstChar = trimmedStart.firstOrNull()
|
val firstChar = trimmedStart.firstOrNull()
|
||||||
// While inside parentheses, continuation applies scaled by nesting level
|
// While inside parentheses, continuation applies scaled by nesting level
|
||||||
@ -194,7 +217,7 @@ object LyngFormatter {
|
|||||||
val newBlockLevel = blockLevel
|
val newBlockLevel = blockLevel
|
||||||
if (newBlockLevel > oldBlockLevel) {
|
if (newBlockLevel > oldBlockLevel) {
|
||||||
val isAccessorRelatedLine = isAccessor || (!inBlockComment && isAccessorRelated(code))
|
val isAccessorRelatedLine = isAccessor || (!inBlockComment && isAccessorRelated(code))
|
||||||
val addedThisLine = (if (applyAwaiting) awaitingExtraIndent else 0) + (if (isAccessorRelatedLine) 1 else 0)
|
val addedThisLine = actualAwaiting + (if (isAccessorRelatedLine) 1 else 0)
|
||||||
repeat(newBlockLevel - oldBlockLevel) {
|
repeat(newBlockLevel - oldBlockLevel) {
|
||||||
extraIndents.add(addedThisLine)
|
extraIndents.add(addedThisLine)
|
||||||
}
|
}
|
||||||
@ -207,23 +230,29 @@ object LyngFormatter {
|
|||||||
inBlockComment = nextInBlockComment
|
inBlockComment = nextInBlockComment
|
||||||
|
|
||||||
// Update awaitingExtraIndent based on current line
|
// Update awaitingExtraIndent based on current line
|
||||||
|
val nextLineTrimmed = lines.getOrNull(i + 1)?.trim() ?: ""
|
||||||
|
val nextIsContinuation = nextLineTrimmed.startsWith("else") ||
|
||||||
|
nextLineTrimmed.startsWith("catch") ||
|
||||||
|
nextLineTrimmed.startsWith("finally")
|
||||||
|
|
||||||
if (applyAwaiting && trimmedStart.isNotEmpty()) {
|
if (applyAwaiting && trimmedStart.isNotEmpty()) {
|
||||||
// we have just applied it.
|
// we have just applied it.
|
||||||
val endsWithBrace = code.trimEnd().endsWith("{")
|
val endsWithBrace = code.trimEnd().endsWith("{")
|
||||||
if (!endsWithBrace && isControlHeaderNoBrace(code)) {
|
if (!endsWithBrace && isControlHeaderNoBrace(trimmedCodeAndStrings)) {
|
||||||
// It's another header, increment
|
// It's another header, increment
|
||||||
val isAccessorRelatedLine = isAccessor || (!inBlockComment && isAccessorRelated(code))
|
val isAccessorOrDouble = isAccessor || (!inBlockComment && isAccessorRelated(code)) || isDoubleIndentHeader(trimmedCodeAndStrings)
|
||||||
awaitingExtraIndent += if (isAccessorRelatedLine) 2 else 1
|
val increment = if (isAccessorOrDouble) 2 else 1
|
||||||
} else {
|
awaitingExtraIndent = actualAwaiting + increment
|
||||||
// It's the body, reset
|
} else if (!nextIsContinuation) {
|
||||||
|
// It's the body, and no continuation follows, so reset
|
||||||
awaitingExtraIndent = 0
|
awaitingExtraIndent = 0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// start awaiting if current line is a control header without '{'
|
// start awaiting if current line is a control header without '{'
|
||||||
val endsWithBrace = code.trimEnd().endsWith("{")
|
val endsWithBrace = code.trimEnd().endsWith("{")
|
||||||
if (!endsWithBrace && isControlHeaderNoBrace(code)) {
|
if (!endsWithBrace && isControlHeaderNoBrace(trimmedCodeAndStrings)) {
|
||||||
val isAccessorRelatedLine = isAccessor || (!inBlockComment && isAccessorRelated(code))
|
val isAccessorOrDouble = isAccessor || (!inBlockComment && isAccessorRelated(code)) || isDoubleIndentHeader(trimmedCodeAndStrings)
|
||||||
awaitingExtraIndent = if (isAccessorRelatedLine) 2 else 1
|
awaitingExtraIndent = if (isAccessorOrDouble) 2 else 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4811,5 +4811,18 @@ class ScriptTest {
|
|||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testArgsPriorityWithSplash() = runTest {
|
||||||
|
eval("""
|
||||||
|
class A {
|
||||||
|
val tags get() = ["foo"]
|
||||||
|
|
||||||
|
fun f1(tags...) = tags
|
||||||
|
|
||||||
|
fun f2(tags...) = f1(...tags)
|
||||||
|
}
|
||||||
|
assertEquals(["bar"], A().f2("bar"))
|
||||||
|
""")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user