Merge remote-tracking branch 'origin/main'
# Conflicts: # app/androidApp/src/main/kotlin/com/example/aiapp/Sizes.kt
This commit is contained in:
commit
e3e02d55f7
29 files changed
+2737
-479
No files matched your search
@@ -0,0 +1,87 @@
|
||||
package com.example.aiapp
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* The line arithmetic behind the file viewer.
|
||||
*
|
||||
* Worth a test rather than an eye: a line number that is one out is invisible in a short file and
|
||||
* obvious in a long one, and a colour that stops at a line break is invisible until the file has a
|
||||
* block comment in it.
|
||||
*/
|
||||
class FileLinesTest {
|
||||
@Test
|
||||
fun `a file that ends with a newline has the number of lines its author would count`() {
|
||||
assertEquals(listOf("one", "two"), FileLines.of("one\ntwo\n", null).lines)
|
||||
assertEquals(listOf("one", "two"), FileLines.of("one\ntwo", null).lines)
|
||||
// Only one is dropped: a blank line at the end of a file is a line somebody typed.
|
||||
assertEquals(listOf("one", "two", ""), FileLines.of("one\ntwo\n\n", null).lines)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an empty file is one empty line`() {
|
||||
val lines = FileLines.of("", null)
|
||||
assertEquals(1, lines.size)
|
||||
assertEquals("", lines.line(0).text)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a comment that spans lines is coloured on every line it covers`() {
|
||||
val text = "fn a() {}\n/* still\n a comment */\nfn b() {}\n"
|
||||
val lines = FileLines.of(text, Language.RUST)
|
||||
assertEquals(4, lines.size)
|
||||
val comment = catppuccinSyntax().of(Kind.COMMENT)
|
||||
// The whole of the middle line, and the part of the third up to the closer.
|
||||
assertTrue(lines.line(1).spanStyles.any { it.item.color == comment && it.start == 0 })
|
||||
val third = lines.line(2)
|
||||
assertTrue(third.spanStyles.any { it.item.color == comment && it.end == third.length })
|
||||
// And the code around it is not commented.
|
||||
assertTrue(lines.line(0).spanStyles.none { it.item.color == comment })
|
||||
assertTrue(lines.line(3).spanStyles.none { it.item.color == comment })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a span never runs past the line it was cut into`() {
|
||||
val lines = FileLines.of("val x = \"a\nb\"\nval y = 1\n", Language.KOTLIN)
|
||||
for (index in 0 until lines.size) {
|
||||
val line = lines.line(index)
|
||||
assertTrue(
|
||||
line.spanStyles.all { it.start >= 0 && it.end <= line.length },
|
||||
"line $index",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The number every row in the viewer is sized to. It has to be the widest line, because rows of
|
||||
* their natural widths scroll sideways by different amounts -- see [FileViewer].
|
||||
*/
|
||||
@Test
|
||||
fun `the column count is the widest line, counting a tab as eight`() {
|
||||
assertEquals(5, FileLines.of("one\nthree\nx\n", null).columns)
|
||||
// A tab counts up to eight, and upwards on purpose: over-estimating leaves empty space
|
||||
// past the longest line, under-estimating puts its end out of reach.
|
||||
assertEquals(9, FileLines.of("\tx\nshort\n", null).columns)
|
||||
// An empty file is one empty line, which is no columns at all rather than an error.
|
||||
assertEquals(0, FileLines.of("", null).columns)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a file with no language is plain`() {
|
||||
val lines = FileLines.of("fn main() {}\n", null)
|
||||
assertTrue(lines.line(0).spanStyles.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a language comes from the extension, and only from a real one`() {
|
||||
assertEquals(Language.KOTLIN, fileLanguage("Main.kt"))
|
||||
assertEquals(Language.KOTLIN, fileLanguage("build.gradle.kts"))
|
||||
assertEquals(Language.RUST, fileLanguage("files.rs"))
|
||||
assertEquals(Language.TOML, fileLanguage("Cargo.toml"))
|
||||
assertEquals(null, fileLanguage("Makefile"))
|
||||
assertEquals(null, fileLanguage(".bashrc"))
|
||||
assertEquals(null, fileLanguage("notes.txt"))
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,7 @@ import kotlin.test.assertTrue
|
||||
class HighlighterTest {
|
||||
/** Every span of [kind] in [code], as the text it covers. */
|
||||
private fun spans(code: String, language: Language, kind: Kind): List<String> =
|
||||
scan(code, rulesOf(language))
|
||||
.filter { it.kind == kind }
|
||||
.map { code.substring(it.start, it.end) }
|
||||
spansOf(code, language).filter { it.kind == kind }.map { code.substring(it.start, it.end) }
|
||||
|
||||
private fun assertSpans(
|
||||
code: String,
|
||||
@@ -182,6 +180,158 @@ class HighlighterTest {
|
||||
assertSpans(code, Language.RON, Kind.LITERAL, "3")
|
||||
}
|
||||
|
||||
// Markdown, which has a scanner of its own: what a character means there is decided by where
|
||||
// it sits rather than by what it is, so most of these are about the cases where it means
|
||||
// nothing at all.
|
||||
|
||||
@Test
|
||||
fun `a heading is coloured whole and a hash inside a word is not one`() {
|
||||
val code = "## Layout\nissue #12 is fixed\n#hashtag"
|
||||
assertSpans(code, Language.MARKDOWN, Kind.KEYWORD, "## Layout")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `seven hashes are not a heading`() {
|
||||
assertSpans("####### deep", Language.MARKDOWN, Kind.KEYWORD)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a fence carries its language as metadata and its body as one string`() {
|
||||
val code = "text\n```kotlin\nval x = 1\n```\nmore"
|
||||
assertSpans(code, Language.MARKDOWN, Kind.METADATA, "kotlin")
|
||||
assertSpans(code, Language.MARKDOWN, Kind.STRING, "```", "val x = 1", "```")
|
||||
}
|
||||
|
||||
/** The state that crosses a line, so the one worth asking about at both ends. */
|
||||
@Test
|
||||
fun `a longer fence is not closed by a shorter one, and a heading inside it is not a heading`() {
|
||||
val code = "````\n```\n# not a heading\n````\nafter"
|
||||
assertSpans(code, Language.MARKDOWN, Kind.KEYWORD)
|
||||
assertSpans(code, Language.MARKDOWN, Kind.STRING, "````", "```", "# not a heading", "````")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an unclosed fence runs to the end rather than throwing`() {
|
||||
assertSpans("```\nstill going", Language.MARKDOWN, Kind.STRING, "```", "still going")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `list markers and quote markers colour without their text`() {
|
||||
val code = "- one\n2. two\n> quoted"
|
||||
assertSpans(code, Language.MARKDOWN, Kind.MARK, "-", "2.", ">")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a rule and a setext underline are the same mark`() {
|
||||
assertSpans("Title\n=====\n\n---", Language.MARKDOWN, Kind.MARK, "=====", "---")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `emphasis needs something on both sides of it`() {
|
||||
assertSpans("**bold** and *thin*", Language.MARKDOWN, Kind.LITERAL, "**bold**", "*thin*")
|
||||
// The case the guards exist for: a C fragment written in a paragraph.
|
||||
assertSpans("a * b * c and *p = *q", Language.MARKDOWN, Kind.LITERAL)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an underscore inside a word emphasises nothing`() {
|
||||
assertSpans("snake_case_name and _real_", Language.MARKDOWN, Kind.LITERAL, "_real_")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a code span holds a backtick when opened with two`() {
|
||||
assertSpans("``a ` b`` and `c`", Language.MARKDOWN, Kind.STRING, "``a ` b``", "`c`")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an unclosed code span is ordinary text`() {
|
||||
assertSpans("a ` b", Language.MARKDOWN, Kind.STRING)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a link marks its brackets and colours its destination`() {
|
||||
val code = "see [the plan](PLAN.md) now"
|
||||
assertSpans(code, Language.MARKDOWN, Kind.MARK, "[", "]")
|
||||
assertSpans(code, Language.MARKDOWN, Kind.METADATA, "(PLAN.md)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a table is found by its delimiter row, and pipes elsewhere are plain`() {
|
||||
val code = "| a | b |\n|---|---|\n| 1 | 2 |\n\nrun a | b in a paragraph"
|
||||
assertSpans(
|
||||
code,
|
||||
Language.MARKDOWN,
|
||||
Kind.MARK,
|
||||
"|",
|
||||
"|",
|
||||
"|",
|
||||
"|---|---|",
|
||||
"|",
|
||||
"|",
|
||||
"|",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a table without outer pipes still colours, and the table ends with the rows`() {
|
||||
val code = "a | b\n--- | ---\nnot a row"
|
||||
assertSpans(code, Language.MARKDOWN, Kind.MARK, "|", "--- | ---")
|
||||
}
|
||||
|
||||
/**
|
||||
* The tag in the last case is not an autolink and is left plain, but the address inside it is
|
||||
* still an address and the bare-URL pass finds it. That is the intended reading: raw HTML is
|
||||
* not something this scanner knows, and a URL is a URL wherever it was written.
|
||||
*/
|
||||
@Test
|
||||
fun `an autolink colours and an HTML tag does not`() {
|
||||
val code = "<https://example.com> and <a@b.com> and <div> and <img src=\"http://x\">"
|
||||
assertSpans(
|
||||
code,
|
||||
Language.MARKDOWN,
|
||||
Kind.METADATA,
|
||||
"<https://example.com>",
|
||||
"<a@b.com>",
|
||||
"http://x",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a bare URL gives back the sentence's punctuation`() {
|
||||
assertSpans(
|
||||
"see https://example.com/a., and ssh://host/x)",
|
||||
Language.MARKDOWN,
|
||||
Kind.METADATA,
|
||||
"https://example.com/a",
|
||||
"ssh://host/x",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a bracket a URL opened itself stays in it`() {
|
||||
assertSpans(
|
||||
"https://en.wikipedia.org/wiki/A_(b) here",
|
||||
Language.MARKDOWN,
|
||||
Kind.METADATA,
|
||||
"https://en.wikipedia.org/wiki/A_(b)",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a URL inside a link destination is not coloured twice`() {
|
||||
assertSpans(
|
||||
"[x](https://example.com)",
|
||||
Language.MARKDOWN,
|
||||
Kind.METADATA,
|
||||
"(https://example.com)",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a bracket with no destination after it is left plain`() {
|
||||
assertSpans("an [aside] here", Language.MARKDOWN, Kind.MARK)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an unknown fence language is drawn plain`() {
|
||||
assertEquals(null, fenceLanguage("brainfuck"))
|
||||
@@ -189,8 +339,8 @@ class HighlighterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `every alias the fence table knows has rules`() {
|
||||
Language.entries.forEach { rulesOf(it) }
|
||||
fun `every language the fence table knows has a scanner`() {
|
||||
Language.entries.forEach { spansOf("x", it) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,11 +368,26 @@ class HighlighterTest {
|
||||
"0x",
|
||||
"1.2.3",
|
||||
"a#b//c/*d*/'e\"f",
|
||||
"```",
|
||||
"*",
|
||||
"**",
|
||||
"~~",
|
||||
"> ",
|
||||
"- ",
|
||||
"1.",
|
||||
"[x](",
|
||||
"#######",
|
||||
"|",
|
||||
"|---|",
|
||||
"<",
|
||||
"<>",
|
||||
"http://",
|
||||
"a://",
|
||||
"\n\n \n",
|
||||
)
|
||||
for (language in Language.entries) {
|
||||
for (code in nasty) {
|
||||
val spans = scan(code, rulesOf(language))
|
||||
val spans = spansOf(code, language)
|
||||
spans.forEach {
|
||||
assertTrue(
|
||||
it.start in 0..it.end && it.end <= code.length,
|
||||
|
||||
Reference in new issue
Block a user