feat: label code fences so Vicinae can colour them (#10)
This commit was merged in pull request #10.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { inferLanguage, labelCodeFences } from "../lib/code";
|
||||
|
||||
describe("inferLanguage", () => {
|
||||
it("returns nothing for empty or whitespace-only code", () => {
|
||||
expect(inferLanguage("")).toBeUndefined();
|
||||
expect(inferLanguage(" \n ")).toBeUndefined();
|
||||
});
|
||||
|
||||
describe("shell", () => {
|
||||
it("recognises a command as the first word", () => {
|
||||
expect(inferLanguage("pwd")).toBe("bash");
|
||||
expect(inferLanguage("git status")).toBe("bash");
|
||||
expect(inferLanguage("brew install ripgrep")).toBe("bash");
|
||||
});
|
||||
|
||||
it("recognises an explicit prompt marker", () => {
|
||||
expect(inferLanguage("$ git status")).toBe("bash");
|
||||
expect(inferLanguage("> ls -la")).toBe("bash");
|
||||
});
|
||||
|
||||
it("recognises a home-relative path", () => {
|
||||
expect(inferLanguage("~/Documents/Code")).toBe("bash");
|
||||
});
|
||||
|
||||
it("recognises a pipeline between known commands", () => {
|
||||
expect(inferLanguage("git log --oneline | head -20")).toBe("bash");
|
||||
});
|
||||
|
||||
it("ignores comments when looking at the first word", () => {
|
||||
expect(inferLanguage("# check the branch\ngit branch --show-current")).toBe("bash");
|
||||
});
|
||||
});
|
||||
|
||||
describe("json", () => {
|
||||
it("recognises a parseable object", () => {
|
||||
expect(inferLanguage('{\n "model": "gpt-oss"\n}')).toBe("json");
|
||||
});
|
||||
|
||||
it("recognises a parseable array", () => {
|
||||
expect(inferLanguage('["a", "b"]')).toBe("json");
|
||||
});
|
||||
|
||||
it("does not claim malformed braces", () => {
|
||||
expect(inferLanguage("{ not actually json")).not.toBe("json");
|
||||
});
|
||||
});
|
||||
|
||||
describe("typescript", () => {
|
||||
it("recognises type annotations", () => {
|
||||
expect(inferLanguage("function add(a: number, b: number) {\n return a + b;\n}")).toBe("ts");
|
||||
});
|
||||
|
||||
it("recognises interfaces and type aliases", () => {
|
||||
expect(inferLanguage("interface User {\n id: string;\n}")).toBe("ts");
|
||||
expect(inferLanguage("type Id = string | number;")).toBe("ts");
|
||||
});
|
||||
|
||||
it("does not label plain JavaScript, which is ambiguous", () => {
|
||||
// Guessing wrong costs the highlighting, so this stays unlabelled.
|
||||
expect(inferLanguage("console.log('hi')")).toBeUndefined();
|
||||
expect(inferLanguage("const x = [1, 2, 3].map((n) => n * 2);")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("python", () => {
|
||||
it("recognises a function definition", () => {
|
||||
expect(inferLanguage("def greet(name):\n return f'hi {name}'")).toBe("python");
|
||||
});
|
||||
|
||||
it("recognises imports and self", () => {
|
||||
expect(inferLanguage("from pathlib import Path")).toBe("python");
|
||||
expect(inferLanguage("class A:\n def go(self):\n pass")).toBe("python");
|
||||
});
|
||||
});
|
||||
|
||||
it("leaves genuinely ambiguous content alone", () => {
|
||||
expect(inferLanguage("some plain text\nacross two lines")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("labelCodeFences", () => {
|
||||
it("labels a bare fence", () => {
|
||||
const labelled = labelCodeFences("```\ngit status\n```");
|
||||
expect(labelled).toBe("```bash\ngit status\n```");
|
||||
});
|
||||
|
||||
it("leaves an already labelled fence exactly as the model wrote it", () => {
|
||||
expect(labelCodeFences("```rust\nfn main() {}\n```")).toBe("```rust\nfn main() {}\n```");
|
||||
});
|
||||
|
||||
it("labels each block independently", () => {
|
||||
const labelled = labelCodeFences(["```", "pwd", "```", "", "```", '{"a": 1}', "```"].join("\n"));
|
||||
|
||||
expect(labelled).toContain("```bash");
|
||||
expect(labelled).toContain("```json");
|
||||
});
|
||||
|
||||
it("leaves an unlabellable block bare rather than guessing", () => {
|
||||
const input = "```\njust some words\n```";
|
||||
expect(labelCodeFences(input)).toBe(input);
|
||||
});
|
||||
|
||||
it("preserves surrounding prose", () => {
|
||||
const labelled = labelCodeFences("Before:\n\n```\nls\n```\n\nAfter.");
|
||||
|
||||
expect(labelled).toContain("Before:");
|
||||
expect(labelled).toContain("After.");
|
||||
expect(labelled).toContain("```bash");
|
||||
});
|
||||
|
||||
it("handles a fence that is never closed", () => {
|
||||
expect(labelCodeFences("```\ngit log")).toContain("```bash");
|
||||
});
|
||||
|
||||
it("is a no-op on markdown with no fences", () => {
|
||||
const input = "Just a paragraph with `inline code` in it.";
|
||||
expect(labelCodeFences(input)).toBe(input);
|
||||
});
|
||||
|
||||
it("is idempotent", () => {
|
||||
const once = labelCodeFences("```\nls\n```");
|
||||
expect(labelCodeFences(once)).toBe(once);
|
||||
});
|
||||
|
||||
it("preserves indentation of a nested fence", () => {
|
||||
expect(labelCodeFences(" ```\n ls\n ```")).toContain(" ```bash");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user