language tools and site suport

This commit is contained in:
sergeych committed 2026-02-07 05:47:18 +03:00
1 parent 78d8e546d5
commit c16c0d7ebd
16 files changed
+1402 -338

No files matched your search

+12
View File
@@ -120,6 +120,12 @@ fun ReferencePage() {
Div { Text("$kind ${d.name}$t") }
d.doc?.summary?.let { Small({ classes("text-muted") }) { Text(it) } }
}
is MiniTypeAliasDecl -> {
val params = if (d.typeParams.isEmpty()) "" else d.typeParams.joinToString(", ", "<", ">")
val target = DocLookupUtils.typeOf(d.target).ifEmpty { "Any" }
Div { Text("type ${d.name}$params = $target") }
d.doc?.summary?.let { Small({ classes("text-muted") }) { Text(it) } }
}
is MiniClassDecl -> {
Div { Text("class ${d.name}") }
d.doc?.summary?.let { Small({ classes("text-muted") }) { Text(it) } }
@@ -145,6 +151,12 @@ fun ReferencePage() {
val staticStr = if (m.isStatic) "static " else ""
Li { Text("${staticStr}${kindM} ${d.name}.${m.name}${ts}") }
}
is MiniMemberTypeAliasDecl -> {
val params = if (m.typeParams.isEmpty()) "" else m.typeParams.joinToString(", ", "<", ">")
val target = DocLookupUtils.typeOf(m.target).ifEmpty { "Any" }
val staticStr = if (m.isStatic) "static " else ""
Li { Text("${staticStr}type ${d.name}.${m.name}$params = $target") }
}
}
}
}
+182 -1
View File
@@ -20,7 +20,15 @@ import kotlinx.coroutines.launch
import net.sergeych.lyng.LyngVersion
import net.sergeych.lyng.Script
import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.highlight.TextRange
import net.sergeych.lyng.miniast.CompletionItem
import net.sergeych.lyng.tools.LyngDiagnostic
import net.sergeych.lyng.tools.LyngDiagnosticSeverity
import net.sergeych.lyng.tools.LyngSymbolInfo
import net.sergeych.lyng.tools.LyngSymbolTarget
import net.sergeych.lyngweb.EditorWithOverlay
import net.sergeych.lyngweb.LyngWebTools
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.dom.*
@Composable
@@ -52,6 +60,15 @@ fun TryLyngPage(route: String) {
var output by remember { mutableStateOf<String?>(null) }
var error by remember { mutableStateOf<String?>(null) }
var extendedError by remember { mutableStateOf<String?>(null) }
var diagnostics by remember { mutableStateOf<List<LyngDiagnostic>>(emptyList()) }
var completionItems by remember { mutableStateOf<List<CompletionItem>>(emptyList()) }
var completionOffset by remember { mutableStateOf<Int?>(null) }
var docInfo by remember { mutableStateOf<LyngSymbolInfo?>(null) }
var definitionTarget by remember { mutableStateOf<LyngSymbolTarget?>(null) }
var usageRanges by remember { mutableStateOf<List<TextRange>>(emptyList()) }
var disasmSymbol by remember { mutableStateOf<String>("") }
var disasmOutput by remember { mutableStateOf<String?>(null) }
var disasmError by remember { mutableStateOf<String?>(null) }
fun runCode() {
if (running) return
@@ -59,6 +76,14 @@ fun TryLyngPage(route: String) {
output = null
error = null
extendedError = null
completionItems = emptyList()
completionOffset = null
docInfo = null
definitionTarget = null
usageRanges = emptyList()
diagnostics = emptyList()
disasmOutput = null
disasmError = null
scope.launch {
// keep this outside try so we can show partial prints if evaluation fails
val printed = StringBuilder()
@@ -156,6 +181,22 @@ fun TryLyngPage(route: String) {
runCode()
}
},
onAnalysisReady = { analysis ->
diagnostics = analysis.diagnostics
},
onCompletionRequested = { offset, items ->
completionOffset = offset
completionItems = items
},
onDefinitionResolved = { _, target ->
definitionTarget = target
},
onUsagesResolved = { _, ranges ->
usageRanges = ranges
},
onDocRequested = { _, info ->
docInfo = info
},
// Keep current initial size but allow the editor to grow with content
autoGrow = true
)
@@ -218,10 +259,150 @@ fun TryLyngPage(route: String) {
}
}
// Language tools quick view
Div({ classes("card", "mb-3") }) {
Div({ classes("card-header", "d-flex", "align-items-center", "gap-2") }) {
I({ classes("bi", "bi-diagram-3") })
Span({ classes("fw-semibold") }) { Text("Language tools") }
}
Div({ classes("card-body") }) {
Div({ classes("mb-3") }) {
Span({ classes("fw-semibold", "me-2") }) { Text("Diagnostics") }
if (diagnostics.isEmpty()) {
Span({ classes("text-muted") }) { Text("No errors or warnings.") }
} else {
Ul({ classes("mb-0") }) {
diagnostics.forEach { d ->
Li {
val sev = when (d.severity) {
LyngDiagnosticSeverity.Error -> "Error"
LyngDiagnosticSeverity.Warning -> "Warning"
}
val range = d.range?.let { " @${it.start}-${it.endExclusive}" } ?: ""
Text("$sev: ${d.message}$range")
}
}
}
}
}
Div({ classes("mb-3") }) {
Span({ classes("fw-semibold", "me-2") }) { Text("Quick docs") }
if (docInfo == null) {
Span({ classes("text-muted") }) { Text("Press Ctrl+Q (or ⌘+Q) on a symbol.") }
} else {
val info = docInfo!!
Div({ classes("small") }) {
Text("${info.target.kind} ${info.target.name}")
info.signature?.let { sig ->
Br()
Code { Text(sig) }
}
info.doc?.summary?.let { doc ->
Br()
Text(doc)
}
}
}
}
Div({ classes("mb-3") }) {
Span({ classes("fw-semibold", "me-2") }) { Text("Definition") }
if (definitionTarget == null) {
Span({ classes("text-muted") }) { Text("Press Ctrl+B (or ⌘+B) on a symbol.") }
} else {
val def = definitionTarget!!
Span({ classes("small") }) {
Text("${def.kind} ${def.name} @${def.range.start}-${def.range.endExclusive}")
}
}
}
Div({ classes("mb-3") }) {
Span({ classes("fw-semibold", "me-2") }) { Text("Usages") }
if (usageRanges.isEmpty()) {
Span({ classes("text-muted") }) { Text("Press Ctrl+Shift+U (or ⌘+Shift+U) on a symbol.") }
} else {
Span({ classes("small") }) { Text("${usageRanges.size} usage(s) found.") }
}
}
Div({ classes("mb-0") }) {
Span({ classes("fw-semibold", "me-2") }) { Text("Completions") }
if (completionItems.isEmpty()) {
Span({ classes("text-muted") }) { Text("Press Ctrl+Space (or ⌘+Space).") }
} else {
val shown = completionItems.take(8)
Span({ classes("text-muted", "small", "ms-1") }) {
completionOffset?.let { Text("@$it") }
}
Ul({ classes("mb-0") }) {
shown.forEach { item ->
Li { Text("${item.name} (${item.kind})") }
}
}
if (completionItems.size > shown.size) {
Span({ classes("text-muted", "small") }) {
Text("…and ${completionItems.size - shown.size} more")
}
}
}
}
}
}
// Disassembly
Div({ classes("card", "mb-3") }) {
Div({ classes("card-header", "d-flex", "align-items-center", "gap-2") }) {
I({ classes("bi", "bi-braces") })
Span({ classes("fw-semibold") }) { Text("Disassembly") }
}
Div({ classes("card-body") }) {
Div({ classes("d-flex", "gap-2", "align-items-center", "mb-2") }) {
Input(type = InputType.Text, attrs = {
classes("form-control")
attr("placeholder", "Symbol (e.g., MyClass.method or topLevelFun)")
value(disasmSymbol)
onInput { ev ->
disasmSymbol = ev.value
}
})
Button(attrs = {
classes("btn", "btn-outline-primary")
if (disasmSymbol.isBlank()) attr("disabled", "disabled")
onClick {
it.preventDefault()
val symbol = disasmSymbol.trim()
if (symbol.isEmpty()) return@onClick
disasmOutput = null
disasmError = null
scope.launch {
try {
disasmOutput = LyngWebTools.disassembleSymbol(code, symbol)
} catch (t: Throwable) {
disasmError = t.message ?: t.toString()
}
}
}
}) { Text("Disassemble") }
}
if (disasmError != null) {
Div({ classes("alert", "alert-danger", "py-2", "mb-2") }) { Text(disasmError!!) }
}
if (disasmOutput != null) {
Pre({ classes("mb-0") }) { Code { Text(disasmOutput!!) } }
} else if (disasmError == null) {
Span({ classes("text-muted", "small") }) {
Text("Uses the bytecode compiler; not a dry run.")
}
}
}
}
// Tips
P({ classes("text-muted", "small") }) {
I({ classes("bi", "bi-info-circle", "me-1") })
Text("Tip: press Ctrl+Enter (or ⌘+Enter on Mac) to run.")
Text("Tip: Ctrl+Enter runs, Ctrl+Space completes, Ctrl+B jumps to definition, Ctrl+Shift+U finds usages, Ctrl+Q shows docs.")
}
}
}