idea plugin 0.0.2-SNAPSHOT, improced, added reformat code. Formatting tools improved in lynglib. Site information added

This commit is contained in:
sergeych committed 2025-12-01 17:50:27 +01:00
1 parent 06e8e1579d
commit ec49bbbf52
19 files changed
+530 -54

No files matched your search

+23 -5
View File
@@ -21,7 +21,7 @@ plugins {
}
group = "net.sergeych.lyng"
version = "0.0.1-SNAPSHOT"
version = "0.0.2-SNAPSHOT"
kotlin {
jvmToolchain(17)
@@ -41,8 +41,10 @@ dependencies {
intellij {
type.set("IC")
// Run sandbox on IntelliJ IDEA 2024.3.x
// Build against a modern baseline. Install range is controlled by since/until below.
version.set("2024.3.1")
// We manage <idea-version> ourselves in plugin.xml to keep it open-ended (no upper cap)
updateSinceUntilBuild.set(false)
// Include only available bundled plugins for this IDE build
plugins.set(listOf(
"com.intellij.java"
@@ -51,8 +53,24 @@ intellij {
tasks {
patchPluginXml {
// Compatible with 2024.3+
sinceBuild.set("243")
untilBuild.set(null as String?)
// Keep version and other metadata patched by Gradle, but since/until are controlled in plugin.xml.
// (intellij.updateSinceUntilBuild=false prevents Gradle from injecting an until-build cap)
}
// Build an installable plugin zip and copy it to $PROJECT_ROOT/distributables
// Usage: ./gradlew :lyng-idea:buildInstallablePlugin
// It depends on buildPlugin and overwrites any existing file with the same name
register<Copy>("buildInstallablePlugin") {
dependsOn("buildPlugin")
// The Gradle IntelliJ Plugin produces: build/distributions/<project.name>-<version>.zip
val zipName = "${project.name}-${project.version}.zip"
val sourceZip = layout.buildDirectory.file("distributions/$zipName")
from(sourceZip)
into(rootProject.layout.projectDirectory.dir("distributables"))
// Overwrite if a file with the same name exists
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
}
@@ -28,6 +28,10 @@ import com.intellij.psi.PsiFile
import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.Source
import net.sergeych.lyng.binding.Binder
import net.sergeych.lyng.binding.SymbolKind
import net.sergeych.lyng.highlight.HighlightKind
import net.sergeych.lyng.highlight.SimpleLyngHighlighter
import net.sergeych.lyng.highlight.offsetOf
import net.sergeych.lyng.idea.highlight.LyngHighlighterColors
import net.sergeych.lyng.idea.util.IdeLenientImportProvider
@@ -59,7 +63,8 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
val src = Source("<ide>", text)
val provider = IdeLenientImportProvider.create()
runBlocking { Compiler.compileWithMini(src, provider, sink) }
} catch (_: Throwable) {
} catch (e: Throwable) {
if (e is com.intellij.openapi.progress.ProcessCanceledException) throw e
// Fail softly: no semantic layer this pass
return Result(collectedInfo.modStamp, emptyList())
}
@@ -67,7 +72,7 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
val mini = sink.build() ?: return Result(collectedInfo.modStamp, emptyList())
val source = Source("<ide>", text)
val out = ArrayList<Span>(64)
val out = ArrayList<Span>(256)
fun putRange(start: Int, end: Int, key: com.intellij.openapi.editor.colors.TextAttributesKey) {
if (start in 0..end && end <= text.length && start < end) out += Span(start, end, key)
@@ -85,7 +90,7 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
// Declarations
for (d in mini.declarations) {
when (d) {
is MiniFunDecl -> putName(d.nameStart, d.name, LyngHighlighterColors.FUNCTION)
is MiniFunDecl -> putName(d.nameStart, d.name, LyngHighlighterColors.FUNCTION_DECLARATION)
is MiniClassDecl -> putName(d.nameStart, d.name, LyngHighlighterColors.TYPE)
is MiniValDecl -> putName(
d.nameStart,
@@ -142,6 +147,86 @@ class LyngExternalAnnotator : ExternalAnnotator<LyngExternalAnnotator.Input, Lyn
}
ProgressManager.checkCanceled()
// Semantic usages via Binder (best-effort)
try {
val binding = Binder.bind(text, mini)
// Map declaration ranges to avoid duplicating them as usages
val declKeys = HashSet<Pair<Int, Int>>(binding.symbols.size * 2)
for (sym in binding.symbols) declKeys += (sym.declStart to sym.declEnd)
fun keyForKind(k: SymbolKind) = when (k) {
SymbolKind.Function -> LyngHighlighterColors.FUNCTION
SymbolKind.Class, SymbolKind.Enum -> LyngHighlighterColors.TYPE
SymbolKind.Param -> LyngHighlighterColors.PARAMETER
SymbolKind.Val -> LyngHighlighterColors.VALUE
SymbolKind.Var -> LyngHighlighterColors.VARIABLE
}
// Track covered ranges to not override later heuristics
val covered = HashSet<Pair<Int, Int>>()
for (ref in binding.references) {
val key = ref.start to ref.end
if (declKeys.contains(key)) continue
val sym = binding.symbols.firstOrNull { it.id == ref.symbolId } ?: continue
val color = keyForKind(sym.kind)
putRange(ref.start, ref.end, color)
covered += key
}
// Heuristics on top of binder: function call-sites and simple name-based roles
ProgressManager.checkCanceled()
val tokens = try { SimpleLyngHighlighter().highlight(text) } catch (_: Throwable) { emptyList() }
fun isFollowedByParenOrBlock(rangeEnd: Int): Boolean {
var i = rangeEnd
while (i < text.length) {
val ch = text[i]
if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { i++; continue }
return ch == '(' || ch == '{'
}
return false
}
// Build simple name -> role map for top-level vals/vars and parameters
val nameRole = HashMap<String, com.intellij.openapi.editor.colors.TextAttributesKey>(8)
for (d in mini.declarations) when (d) {
is MiniValDecl -> nameRole[d.name] = if (d.mutable) LyngHighlighterColors.VARIABLE else LyngHighlighterColors.VALUE
is MiniFunDecl -> d.params.forEach { p -> nameRole[p.name] = LyngHighlighterColors.PARAMETER }
else -> {}
}
for (s in tokens) if (s.kind == HighlightKind.Identifier) {
val start = s.range.start
val end = s.range.endExclusive
val key = start to end
if (key in covered || key in declKeys) continue
// Call-site detection first so it wins over var/param role
if (isFollowedByParenOrBlock(end)) {
putRange(start, end, LyngHighlighterColors.FUNCTION)
covered += key
continue
}
// Simple role by known names
val ident = try { text.substring(start, end) } catch (_: Throwable) { null }
if (ident != null) {
val roleKey = nameRole[ident]
if (roleKey != null) {
putRange(start, end, roleKey)
covered += key
}
}
}
} catch (e: Throwable) {
// Must rethrow cancellation; otherwise ignore binder failures (best-effort)
if (e is com.intellij.openapi.progress.ProcessCanceledException) throw e
}
return Result(collectedInfo.modStamp, out)
}
@@ -60,6 +60,7 @@ class LyngColorSettingsPage : ColorSettingsPage {
AttributesDescriptor("Variable (semantic)", LyngHighlighterColors.VARIABLE),
AttributesDescriptor("Value (semantic)", LyngHighlighterColors.VALUE),
AttributesDescriptor("Function (semantic)", LyngHighlighterColors.FUNCTION),
AttributesDescriptor("Function declaration (semantic)", LyngHighlighterColors.FUNCTION_DECLARATION),
AttributesDescriptor("Type (semantic)", LyngHighlighterColors.TYPE),
AttributesDescriptor("Namespace (semantic)", LyngHighlighterColors.NAMESPACE),
AttributesDescriptor("Parameter (semantic)", LyngHighlighterColors.PARAMETER),
@@ -46,15 +46,22 @@ object LyngHighlighterColors {
"LYNG_PUNCT", DefaultLanguageHighlighterColors.DOT
)
// Semantic layer keys (placeholders for now)
// Semantic layer keys
val VARIABLE: TextAttributesKey = TextAttributesKey.createTextAttributesKey(
"LYNG_VARIABLE", DefaultLanguageHighlighterColors.LOCAL_VARIABLE
// Use a distinctive default to ensure visibility across common themes.
// Users can still customize it separately from VALUE.
"LYNG_VARIABLE", DefaultLanguageHighlighterColors.INSTANCE_FIELD
)
val VALUE: TextAttributesKey = TextAttributesKey.createTextAttributesKey(
"LYNG_VALUE", DefaultLanguageHighlighterColors.INSTANCE_FIELD
)
val FUNCTION: TextAttributesKey = TextAttributesKey.createTextAttributesKey(
"LYNG_FUNCTION", DefaultLanguageHighlighterColors.FUNCTION_CALL
// Primary approach: make function calls as visible as declarations by default
// (users can still customize separately in the color scheme UI).
"LYNG_FUNCTION", DefaultLanguageHighlighterColors.FUNCTION_DECLARATION
)
val FUNCTION_DECLARATION: TextAttributesKey = TextAttributesKey.createTextAttributesKey(
"LYNG_FUNCTION_DECLARATION", DefaultLanguageHighlighterColors.FUNCTION_DECLARATION
)
val TYPE: TextAttributesKey = TextAttributesKey.createTextAttributesKey(
"LYNG_TYPE", DefaultLanguageHighlighterColors.CLASS_REFERENCE
@@ -16,13 +16,17 @@
-->
<idea-plugin>
<!-- Open-ended compatibility: 2024.3+ (build 243 and newer) -->
<idea-version since-build="243"/>
<id>net.sergeych.lyng.idea</id>
<name>Lyng Language Support</name>
<vendor email="real.sergeych@gmail.com">Sergeych Works</vendor>
<vendor email="real.sergeych@gmail.com">Sergey Chernov</vendor>
<description>
<![CDATA[
Basic Lyng language support: file type, syntax highlighting scaffold, and quick docs/highlighter stubs.
Basic Lyng language support: file type, syntax highlighting,
editing assistance (on Enter indent), reformatting code (indents and spaces),
and quick docs.
]]>
</description>
@@ -15,16 +15,17 @@
- limitations under the License.
-
-->
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg" aria-labelledby="title" role="img">
<title>Lyng Plugin Icon (temporary λ)</title>
<defs>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-label="Lyng favicon">
<style>
.glyph { fill: none; stroke: currentColor; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
:root { color-scheme: light dark; }
.mark { fill: currentColor; }
.math { font-family: 'STIX Two Math', 'Cambria Math', 'Times New Roman', serif; }
</style>
</defs>
<g transform="translate(2,2)">
<path class="glyph" d="M12 6 L18 22 C19.2 25.5 22.2 28 26 28 L32 28"/>
<path class="glyph" d="M18 22 L8 34"/>
</g>
<g class="mark math">
<!-- Keep favicon legible: lambda with superscript y only -->
<text x="3" y="17" font-size="19" font-weight="700"
color="#009000" stroke="#002000" stroke-width="0.3">λ</text>
<text x="11.2" y="4" font-size="16" color="#009000" stroke="#002000"
stroke-width="0.1">y</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -15,16 +15,17 @@
- limitations under the License.
-
-->
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-labelledby="title" role="img">
<title>Lyng File Icon (temporary λ)</title>
<defs>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-label="Lyng favicon">
<style>
.g { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
:root { color-scheme: light dark; }
.mark { fill: currentColor; }
.math { font-family: 'STIX Two Math', 'Cambria Math', 'Times New Roman', serif; }
</style>
</defs>
<g transform="translate(0.5,0.5)">
<path class="g" d="M4.5 2.5 L7 9.5 C7.6 11.2 9.0 12.5 11.0 12.5 L14.5 12.5"/>
<path class="g" d="M7 9.5 L2.5 14.5"/>
</g>
<g class="mark math">
<!-- Keep favicon legible: lambda with superscript y only -->
<text x="3" y="17" font-size="19" font-weight="700"
color="#009000" stroke="#002000" stroke-width="0.3">λ</text>
<text x="11.2" y="4" font-size="16" color="#009000" stroke="#002000"
stroke-width="0.1">y</text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB