Compare commits
2 Commits
017111827d
...
2d2a74656c
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d2a74656c | |||
| 80933c287d |
@ -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.
|
||||
- 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".
|
||||
- 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
|
||||
- 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
|
||||
val docText = file.viewProvider.document?.text ?: return 0
|
||||
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) {
|
||||
val text = try { docText.substring(hostRange.startOffset, hostRange.endOffset) } catch (_: Throwable) { null } ?: continue
|
||||
var seen = 0
|
||||
@ -413,7 +415,7 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
||||
val parts = splitIdentifier(token)
|
||||
for (part in parts) {
|
||||
if (part.length <= 2) continue
|
||||
if (isAllowedWord(part)) continue
|
||||
if (isAllowedWord(part, learned)) continue
|
||||
|
||||
// Map part back to original token occurrence within this hostRange
|
||||
val localStart = m.range.first + token.indexOf(part)
|
||||
@ -451,6 +453,8 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
||||
var painted = 0
|
||||
val docText = file.viewProvider.document?.text
|
||||
val tokenRegex = Regex("[A-Za-z][A-Za-z0-9_']{2,}")
|
||||
val settings = LyngFormatterSettings.getInstance(file.project)
|
||||
val learned = settings.learnedWords
|
||||
val baseWords = setOf(
|
||||
// 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"
|
||||
@ -469,7 +473,7 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
||||
for (part in parts) {
|
||||
seen++
|
||||
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 localEnd = localStart + part.length
|
||||
@ -540,29 +544,32 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
||||
private fun suggestReplacements(file: PsiFile, word: String): List<String> {
|
||||
val lower = word.lowercase()
|
||||
val fromProject = collectProjectWords(file)
|
||||
val fromTech = TechDictionary.allWords()
|
||||
val fromEnglish = EnglishDictionary.allWords()
|
||||
// Merge with priority: project (p=0), tech (p=1), english (p=2)
|
||||
|
||||
val fromSpellChecker = try {
|
||||
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>()
|
||||
all.addAll(fromProject)
|
||||
all.addAll(fromTech)
|
||||
all.addAll(fromEnglish)
|
||||
data class Cand(val w: String, val d: Int, val p: Int)
|
||||
val cands = ArrayList<Cand>(32)
|
||||
for (w in all) {
|
||||
// Add project words that are close enough
|
||||
for (w in fromProject) {
|
||||
if (w == lower) continue
|
||||
if (kotlin.math.abs(w.length - lower.length) > 2) continue
|
||||
val d = editDistance(lower, w)
|
||||
val p = when {
|
||||
w in fromProject -> 0
|
||||
w in fromTech -> 1
|
||||
else -> 2
|
||||
if (kotlin.math.abs(w.length - lower.length) <= 2 && editDistance(lower, w) <= 2) {
|
||||
all.add(w)
|
||||
}
|
||||
cands += Cand(w, d, p)
|
||||
}
|
||||
cands.sortWith(compareBy<Cand> { it.d }.thenBy { it.p }.thenBy { it.w })
|
||||
// Return a larger pool so callers can choose desired display count
|
||||
return cands.take(16).map { it.w }
|
||||
all.addAll(fromSpellChecker)
|
||||
|
||||
return all.take(16).toList()
|
||||
}
|
||||
|
||||
private fun collectProjectWords(file: PsiFile): Set<String> {
|
||||
@ -589,14 +596,19 @@ class LyngGrazieAnnotator : ExternalAnnotator<LyngGrazieAnnotator.Input, LyngGra
|
||||
return out
|
||||
}
|
||||
|
||||
private fun isAllowedWord(w: String): Boolean {
|
||||
private fun isAllowedWord(w: String, learnedWords: Set<String> = emptySet()): Boolean {
|
||||
val s = w.lowercase()
|
||||
if (s in learnedWords) return true
|
||||
return s in setOf(
|
||||
// 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",
|
||||
"abstract","closed","override",
|
||||
// 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.openapi.editor.Editor
|
||||
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.
|
||||
*/
|
||||
class LyngGotoDeclarationHandler : GotoDeclarationHandler {
|
||||
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>()
|
||||
|
||||
|
||||
@ -38,16 +38,18 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
||||
|
||||
val mini = LyngAstManager.getMiniAst(file) ?: return emptyArray()
|
||||
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)
|
||||
val dotPos = TextCtx.findDotLeft(text, offset)
|
||||
if (dotPos != null) {
|
||||
val imported = DocLookupUtils.canonicalImportedModules(mini, text)
|
||||
val receiverClass = DocLookupUtils.guessReceiverClassViaMini(mini, text, dotPos, imported, binding)
|
||||
?: DocLookupUtils.guessReceiverClass(text, dotPos, imported, mini)
|
||||
val receiverClass = DocLookupUtils.guessReceiverClassViaMini(mini, text, dotPos, imported.toList(), binding)
|
||||
?: DocLookupUtils.guessReceiverClass(text, dotPos, imported.toList(), mini)
|
||||
|
||||
if (receiverClass != null) {
|
||||
val resolved = DocLookupUtils.resolveMemberWithInheritance(imported, receiverClass, name, mini)
|
||||
val resolved = DocLookupUtils.resolveMemberWithInheritance(imported.toList(), receiverClass, name, mini)
|
||||
if (resolved != null) {
|
||||
val owner = resolved.first
|
||||
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 (results.isEmpty()) {
|
||||
results.addAll(resolveGlobally(file.project, name, membersOnly = true))
|
||||
results.addAll(resolveGlobally(file.project, name, membersOnly = true, allowedPackages = allowedPackages))
|
||||
}
|
||||
} else {
|
||||
// 2. Local resolution via Binder
|
||||
@ -94,7 +96,7 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
||||
// 3. Global project scan
|
||||
// Only search globally if we haven't found a strong local match
|
||||
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
|
||||
}
|
||||
|
||||
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? {
|
||||
val results = multiResolve(false)
|
||||
if (results.isEmpty()) return null
|
||||
@ -154,13 +166,20 @@ class LyngPsiReference(element: PsiElement) : PsiPolyVariantReferenceBase<PsiEle
|
||||
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 files = FilenameIndex.getAllFilesByExt(project, "lyng", GlobalSearchScope.projectScope(project))
|
||||
val psiManager = PsiManager.getInstance(project)
|
||||
|
||||
for (vFile in files) {
|
||||
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 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
|
||||
@ -1048,7 +1048,12 @@ class Compiler(
|
||||
}
|
||||
|
||||
// Nullable suffix after base or generic
|
||||
val isNullable = cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)
|
||||
val isNullable = if (cc.skipTokenOfType(Token.Type.QUESTION, isOptional = true)) {
|
||||
true
|
||||
} else if (cc.skipTokenOfType(Token.Type.IFNULLASSIGN, isOptional = true)) {
|
||||
cc.pushPendingAssign()
|
||||
true
|
||||
} else false
|
||||
val endPos = cc.currentPos()
|
||||
|
||||
val miniRef = buildBaseRef(if (miniArgs != null) endPos else lastEnd, miniArgs, isNullable)
|
||||
|
||||
@ -35,8 +35,9 @@ class CompilerContext(val tokens: List<Token>) {
|
||||
|
||||
var currentIndex = 0
|
||||
private var pendingGT = 0
|
||||
private var pendingAssign = false
|
||||
|
||||
fun hasNext() = currentIndex < tokens.size || pendingGT > 0
|
||||
fun hasNext() = currentIndex < tokens.size || pendingGT > 0 || pendingAssign
|
||||
fun hasPrevious() = currentIndex > 0
|
||||
fun next(): Token {
|
||||
if (pendingGT > 0) {
|
||||
@ -44,6 +45,11 @@ class CompilerContext(val tokens: List<Token>) {
|
||||
val last = tokens[currentIndex - 1]
|
||||
return Token(">", last.pos.copy(column = last.pos.column + 1), Token.Type.GT)
|
||||
}
|
||||
if (pendingAssign) {
|
||||
pendingAssign = false
|
||||
val last = tokens[currentIndex - 1]
|
||||
return Token("=", last.pos.copy(column = last.pos.column + 1), Token.Type.ASSIGN)
|
||||
}
|
||||
return if (currentIndex < tokens.size) tokens[currentIndex++]
|
||||
else Token("", tokens.last().pos, Token.Type.EOF)
|
||||
}
|
||||
@ -52,16 +58,19 @@ class CompilerContext(val tokens: List<Token>) {
|
||||
pendingGT++
|
||||
}
|
||||
|
||||
fun previous() = if (pendingGT > 0) {
|
||||
pendingGT-- // This is wrong, previous should go back.
|
||||
// But we don't really use previous() in generics parser after splitting.
|
||||
throw IllegalStateException("previous() not supported after pushPendingGT")
|
||||
fun pushPendingAssign() {
|
||||
pendingAssign = true
|
||||
}
|
||||
|
||||
fun previous() = if (pendingGT > 0 || pendingAssign) {
|
||||
throw IllegalStateException("previous() not supported after pushPending tokens")
|
||||
} else if (!hasPrevious()) throw IllegalStateException("No previous token") else tokens[--currentIndex]
|
||||
|
||||
fun savePos() = (currentIndex shl 2) or (pendingGT and 3)
|
||||
fun savePos() = (currentIndex shl 3) or (pendingGT and 3) or (if (pendingAssign) 4 else 0)
|
||||
fun restorePos(pos: Int) {
|
||||
currentIndex = pos shr 2
|
||||
currentIndex = pos shr 3
|
||||
pendingGT = pos and 3
|
||||
pendingAssign = (pos and 4) != 0
|
||||
}
|
||||
|
||||
fun ensureLabelIsValid(pos: Pos, label: String) {
|
||||
|
||||
@ -100,11 +100,16 @@ object LyngFormatter {
|
||||
fun isControlHeaderNoBrace(s: String): Boolean {
|
||||
val t = s.trim()
|
||||
if (t.isEmpty()) return false
|
||||
// match: if (...) | else if (...) | else
|
||||
val isIf = Regex("^if\\s*\\(.*\\)\\s*$").matches(t)
|
||||
val isElseIf = Regex("^else\\s+if\\s*\\(.*\\)\\s*$").matches(t)
|
||||
val isElse = t == "else"
|
||||
if (isIf || isElseIf || isElse) return true
|
||||
// match: if (...) | else if (...) | else | catch (...) | finally
|
||||
if (Regex("^if\\s*\\(.*\\)$").matches(t)) return true
|
||||
if (Regex("^else\\s+if\\s*\\(.*\\)$").matches(t)) return true
|
||||
if (t == "else") 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 =
|
||||
if (isAccessorRelated(t)) {
|
||||
@ -113,11 +118,23 @@ object LyngFormatter {
|
||||
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()) {
|
||||
val (parts, nextInBlockComment) = splitIntoParts(rawLine, inBlockComment)
|
||||
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 trimmedLine = rawLine.trim()
|
||||
val trimmedCodeAndStrings = codeAndStrings.trim()
|
||||
|
||||
// Compute effective indent level for this line
|
||||
val currentExtraIndent = extraIndents.sum()
|
||||
@ -138,9 +155,15 @@ object LyngFormatter {
|
||||
|
||||
// Single-line control header (if/else/else if) without braces: indent the next
|
||||
// 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() &&
|
||||
!trimmedStart.startsWith("else") && !trimmedStart.startsWith("}")
|
||||
if (applyAwaiting) effectiveLevel += awaitingExtraIndent
|
||||
!trimmedStart.startsWith("}")
|
||||
var actualAwaiting = 0
|
||||
if (applyAwaiting) {
|
||||
actualAwaiting = awaitingExtraIndent
|
||||
if (isContinuation) actualAwaiting = (actualAwaiting - 1).coerceAtLeast(0)
|
||||
effectiveLevel += actualAwaiting
|
||||
}
|
||||
|
||||
val firstChar = trimmedStart.firstOrNull()
|
||||
// While inside parentheses, continuation applies scaled by nesting level
|
||||
@ -194,7 +217,7 @@ object LyngFormatter {
|
||||
val newBlockLevel = blockLevel
|
||||
if (newBlockLevel > oldBlockLevel) {
|
||||
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) {
|
||||
extraIndents.add(addedThisLine)
|
||||
}
|
||||
@ -207,23 +230,29 @@ object LyngFormatter {
|
||||
inBlockComment = nextInBlockComment
|
||||
|
||||
// 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()) {
|
||||
// we have just applied it.
|
||||
val endsWithBrace = code.trimEnd().endsWith("{")
|
||||
if (!endsWithBrace && isControlHeaderNoBrace(code)) {
|
||||
if (!endsWithBrace && isControlHeaderNoBrace(trimmedCodeAndStrings)) {
|
||||
// It's another header, increment
|
||||
val isAccessorRelatedLine = isAccessor || (!inBlockComment && isAccessorRelated(code))
|
||||
awaitingExtraIndent += if (isAccessorRelatedLine) 2 else 1
|
||||
} else {
|
||||
// It's the body, reset
|
||||
val isAccessorOrDouble = isAccessor || (!inBlockComment && isAccessorRelated(code)) || isDoubleIndentHeader(trimmedCodeAndStrings)
|
||||
val increment = if (isAccessorOrDouble) 2 else 1
|
||||
awaitingExtraIndent = actualAwaiting + increment
|
||||
} else if (!nextIsContinuation) {
|
||||
// It's the body, and no continuation follows, so reset
|
||||
awaitingExtraIndent = 0
|
||||
}
|
||||
} else {
|
||||
// start awaiting if current line is a control header without '{'
|
||||
val endsWithBrace = code.trimEnd().endsWith("{")
|
||||
if (!endsWithBrace && isControlHeaderNoBrace(code)) {
|
||||
val isAccessorRelatedLine = isAccessor || (!inBlockComment && isAccessorRelated(code))
|
||||
awaitingExtraIndent = if (isAccessorRelatedLine) 2 else 1
|
||||
if (!endsWithBrace && isControlHeaderNoBrace(trimmedCodeAndStrings)) {
|
||||
val isAccessorOrDouble = isAccessor || (!inBlockComment && isAccessorRelated(code)) || isDoubleIndentHeader(trimmedCodeAndStrings)
|
||||
awaitingExtraIndent = if (isAccessorOrDouble) 2 else 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4803,7 +4803,7 @@ class ScriptTest {
|
||||
fun f(a: String = "foo") = a + "!"
|
||||
fun g(a: String? = null) = a ?: "!!"
|
||||
assertEquals(f(), "foo!")
|
||||
assertEquals(f(), "!!")
|
||||
assertEquals(g(), "!!")
|
||||
assertEquals(f("bar"), "bar!")
|
||||
class T(b: Int=42,c: String?=null)
|
||||
assertEquals(42, T().b)
|
||||
@ -4811,5 +4811,18 @@ class ScriptTest {
|
||||
""".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