diff --git a/lynglib/src/commonTest/kotlin/EvalSessionTest.kt b/lynglib/src/commonTest/kotlin/EvalSessionTest.kt index 0139265..179be67 100644 --- a/lynglib/src/commonTest/kotlin/EvalSessionTest.kt +++ b/lynglib/src/commonTest/kotlin/EvalSessionTest.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import net.sergeych.lyng.EvalSession import net.sergeych.lyng.Scope +import net.sergeych.lyng.Script import net.sergeych.lyng.obj.ObjBool import net.sergeych.lyng.obj.ObjFlow import net.sergeych.lyng.obj.ObjInt @@ -41,27 +42,51 @@ class EvalSessionTest { @Test fun sessionCancelStopsLaunchedCoroutines() = runTest { - val scope = Scope() + val scope = Script.newScope() + scope.eval("var exportedVal=0") val session = EvalSession(scope) session.eval( """ var touched = false launch { - delay(100) - touched = true + while(true) { + delay(100) + touched = true + } } """ .trimIndent() ) - session.cancel() - session.join() + session.cancelAndJoin() advanceTimeBy(150) assertFalse((scope.eval("touched") as ObjBool).value) } + @Test + fun cancelAndJoinStopsLaunchedCoroutinesForUserProvidedScriptScope() = runTest { + val scope = Script.newScope() + val session = EvalSession(scope) + + session.eval( + """ + var counter = 0 + launch { + delay(10) + counter += 1 + } + """ + .trimIndent() + ) + + session.cancelAndJoin() + advanceTimeBy(20) + + assertEquals(0L, (scope.eval("counter") as ObjInt).value) + } + @Test fun joinObservesWorkStartedByLaterEval() = runTest { val session = EvalSession(Scope()) diff --git a/site/src/jsMain/kotlin/Main.kt b/site/src/jsMain/kotlin/Main.kt index 545275a..ea626b5 100644 --- a/site/src/jsMain/kotlin/Main.kt +++ b/site/src/jsMain/kotlin/Main.kt @@ -512,8 +512,9 @@ internal fun plainFromMarkdown(md: String): String { // Use non-greedy dot-all equivalents ("[\n\r\s\S]") instead of character classes with ']' where possible. try { // Safer patterns (avoid unescaped ']' inside character classes): - val reCodeBlocks = Regex("```[\\s\\S]*?```") - val reInlineCode = Regex("`[^`]*`") + val reBacktickCodeBlocks = Regex("```[\\s\\S]*?```") + val reTildeCodeBlocks = Regex("~~~[\\s\\S]*?~~~") + val reInlineCode = Regex("`([^`]*)`") val reBlockquote = Regex("^> +", setOf(RegexOption.MULTILINE)) val reHeadings = Regex("^#+ +", setOf(RegexOption.MULTILINE)) // Images: ![alt](url) — capture alt lazily with [\s\S]*? to avoid character class pitfalls @@ -523,9 +524,10 @@ internal fun plainFromMarkdown(md: String): String { var t = md // Triple-backtick code blocks across lines - t = t.replace(reCodeBlocks, " ") - // Inline code - t = t.replace(reInlineCode, " ") + t = t.replace(reBacktickCodeBlocks, " ") + t = t.replace(reTildeCodeBlocks, " ") + // Keep inline code content so API names in prose and headings remain searchable + t = t.replace(reInlineCode, "\$1") // Strip blockquotes and headings markers t = t.replace(reBlockquote, "") t = t.replace(reHeadings, "") @@ -539,7 +541,8 @@ internal fun plainFromMarkdown(md: String): String { // Minimal safe fallback: strip code blocks and inline code, then normalize var t = md t = t.replace(Regex("```[\\s\\S]*?```"), " ") - t = t.replace(Regex("`[^`]*`"), " ") + t = t.replace(Regex("~~~[\\s\\S]*?~~~"), " ") + t = t.replace(Regex("`([^`]*)`"), "\$1") return norm(t) } } diff --git a/site/src/jsTest/kotlin/SearchScoringTest.kt b/site/src/jsTest/kotlin/SearchScoringTest.kt index d1218d3..97010ae 100644 --- a/site/src/jsTest/kotlin/SearchScoringTest.kt +++ b/site/src/jsTest/kotlin/SearchScoringTest.kt @@ -1,5 +1,23 @@ +/* + * Copyright 2026 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. + * + */ + import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertTrue class SearchScoringTest { @@ -26,4 +44,31 @@ class SearchScoringTest { val sFar = scoreQueryAdvanced(listOf("alp", "bet"), far) assertTrue(sNear > sFar, "closer terms should have higher score") } + + @Test + fun preservesInlineCodeTermsInHeadingsAndText() { + val plain = plainFromMarkdown( + """ + ### Preferred runtime: `EvalSession` + + For host applications, prefer `EvalSession` as the main way to run scripts. + """.trimIndent() + ) + + assertTrue(plain.contains("evalsession"), "inline code terms should remain in searchable text") + assertTrue(scoreQueryAdvanced(listOf("evalsession"), rec(plain)) > 0) + } + + @Test + fun stripsTildeCodeFencesLikeBacktickFences() { + val plain = plainFromMarkdown( + """ + ~~~kotlin + val session = EvalSession() + ~~~ + """.trimIndent() + ) + + assertFalse(plain.contains("evalsession"), "fenced code should not leak into the search corpus") + } }