site: fixed .md/lyng code display
This commit is contained in:
parent
53a9d21a19
commit
31fac1a73c
@ -63,7 +63,7 @@ server.get("/") {
|
|||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
h3 { +"Service is running" }
|
h3 { +"Service is running" }
|
||||||
p { +("Path: ${request.path}" }
|
p { +"Path: ${request.path}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,6 +117,28 @@ private fun mergeAdjacent(spans: List<HighlightSpan>): List<HighlightSpan> {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parser expands interpolated strings into expression-like token streams whose
|
||||||
|
* synthetic tokens share source positions with the original string. Keep the
|
||||||
|
* widest source span at each offset and drop nested overlaps so renderers can
|
||||||
|
* rely on the public non-overlapping span contract.
|
||||||
|
*/
|
||||||
|
private fun removeOverlappingSpans(spans: List<HighlightSpan>): List<HighlightSpan> {
|
||||||
|
if (spans.size < 2) return spans
|
||||||
|
val sorted = spans.sortedWith(
|
||||||
|
compareBy<HighlightSpan> { it.range.start }
|
||||||
|
.thenByDescending { it.range.endExclusive - it.range.start }
|
||||||
|
)
|
||||||
|
val out = ArrayList<HighlightSpan>(sorted.size)
|
||||||
|
var coveredUntil = -1
|
||||||
|
for (span in sorted) {
|
||||||
|
if (span.range.start < coveredUntil) continue
|
||||||
|
out += span
|
||||||
|
coveredUntil = span.range.endExclusive
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
/** Simple highlighter using the existing Lyng lexer (no incremental support yet). */
|
/** Simple highlighter using the existing Lyng lexer (no incremental support yet). */
|
||||||
class SimpleLyngHighlighter : LyngHighlighter {
|
class SimpleLyngHighlighter : LyngHighlighter {
|
||||||
override fun highlight(text: String): List<HighlightSpan> {
|
override fun highlight(text: String): List<HighlightSpan> {
|
||||||
@ -170,8 +192,8 @@ class SimpleLyngHighlighter : LyngHighlighter {
|
|||||||
val overridden = applyEnumConstantHeuristics(text, src, tokens, raw)
|
val overridden = applyEnumConstantHeuristics(text, src, tokens, raw)
|
||||||
// Adjust single-line comment spans to extend till EOL to compensate for lexer offset/length quirks
|
// Adjust single-line comment spans to extend till EOL to compensate for lexer offset/length quirks
|
||||||
val adjusted = extendSingleLineCommentsToEol(text, overridden)
|
val adjusted = extendSingleLineCommentsToEol(text, overridden)
|
||||||
// Spans are in order; merge adjacent of the same kind for compactness
|
// Normalize spans, then merge adjacent spans of the same kind for compactness.
|
||||||
return mergeAdjacent(adjusted)
|
return mergeAdjacent(removeOverlappingSpans(adjusted))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,6 +18,7 @@
|
|||||||
package net.sergeych.lyng.highlight
|
package net.sergeych.lyng.highlight
|
||||||
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class HighlightMappingTest {
|
class HighlightMappingTest {
|
||||||
@ -72,6 +73,38 @@ class HighlightMappingTest {
|
|||||||
assertTrue(labeled.any { it.first == "\"s\"" && it.second == HighlightKind.String })
|
assertTrue(labeled.any { it.first == "\"s\"" && it.second == HighlightKind.String })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun interpolatedStringSpansDoNotOverlap() {
|
||||||
|
val text = """p { +"Path: ${'$'}{request.path}" }"""
|
||||||
|
val spans = SimpleLyngHighlighter().highlight(text)
|
||||||
|
spans.zipWithNext().forEach { (a, b) ->
|
||||||
|
assertTrue(
|
||||||
|
a.range.endExclusive <= b.range.start,
|
||||||
|
"Highlight spans must not overlap: $a then $b in $spans"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assertTrue(
|
||||||
|
spansToLabeled(text, spans).any {
|
||||||
|
it.first == "\"Path: ${'$'}{request.path}\"" && it.second == HighlightKind.String
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun interpolatedStringRenderingDoesNotDuplicateText() {
|
||||||
|
val text = """p { +"Path: ${'$'}{request.path}" }"""
|
||||||
|
val rendered = buildString {
|
||||||
|
var pos = 0
|
||||||
|
for (span in SimpleLyngHighlighter().highlight(text)) {
|
||||||
|
if (span.range.start > pos) append(text.substring(pos, span.range.start))
|
||||||
|
append(text.substring(span.range.start, span.range.endExclusive))
|
||||||
|
pos = span.range.endExclusive
|
||||||
|
}
|
||||||
|
if (pos < text.length) append(text.substring(pos))
|
||||||
|
}
|
||||||
|
assertEquals(text, rendered)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun commentsHighlighted() {
|
fun commentsHighlighted() {
|
||||||
val text = "// line\n/* block */"
|
val text = "// line\n/* block */"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
@ -76,4 +77,25 @@ class LyngHighlightTest {
|
|||||||
// the '<' should be escaped in HTML
|
// the '<' should be escaped in HTML
|
||||||
assertTrue(html.contains("<"), "Expected escaped < inside highlighted HTML: $html")
|
assertTrue(html.contains("<"), "Expected escaped < inside highlighted HTML: $html")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun rendersInterpolatedStringOnce() {
|
||||||
|
val md = """
|
||||||
|
```lyng
|
||||||
|
p { +"Path: ${'$'}{request.path}" }
|
||||||
|
```
|
||||||
|
""".trimIndent()
|
||||||
|
val html = renderMarkdown(md)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
1,
|
||||||
|
Regex("Path:").findAll(html).count(),
|
||||||
|
"Rendered markdown duplicated string content: $html"
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
1,
|
||||||
|
Regex("request\\.path").findAll(html).count(),
|
||||||
|
"Rendered markdown duplicated interpolation content: $html"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user