All files / src/utils code-preview.ts

100% Statements 15/15
100% Branches 14/14
100% Functions 4/4
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77      4x                                                                                   4x             32x 32x 32x 4x   28x             8x 3x 3x           4x 4x 4x    
// Maps a source file to the Prism language id Obsidian's bundled highlighter
// understands, so code source previews render as highlighted code instead of
// plain text. Extensions absent here (markdown, plain text) render as-is.
const EXTENSION_LANGUAGE: Record<string, string> = {
    py: "python",
    pyi: "python",
    ts: "typescript",
    tsx: "tsx",
    js: "javascript",
    jsx: "jsx",
    mjs: "javascript",
    cjs: "javascript",
    rs: "rust",
    go: "go",
    java: "java",
    kt: "kotlin",
    scala: "scala",
    c: "c",
    h: "c",
    cpp: "cpp",
    cc: "cpp",
    cxx: "cpp",
    hpp: "cpp",
    cs: "csharp",
    rb: "ruby",
    php: "php",
    swift: "swift",
    sh: "bash",
    bash: "bash",
    zsh: "bash",
    yaml: "yaml",
    yml: "yaml",
    toml: "toml",
    json: "json",
    jsonc: "json",
    xml: "xml",
    sql: "sql",
    lua: "lua",
    dart: "dart",
    ex: "elixir",
    exs: "elixir",
    hs: "haskell",
};
 
// Extensionless filenames that are still code.
const FILENAME_LANGUAGE: Record<string, string> = {
    dockerfile: "docker",
    makefile: "makefile",
};
 
/** Prism language id for a source path, or null when it should render as-is. */
export function languageForSource(source: string): string | null {
    const name = source.replace(/^.*[\\/]/, "").toLowerCase();
    const dot = name.lastIndexOf(".");
    if (dot <= 0) {
        return FILENAME_LANGUAGE[name] ?? null;
    }
    return EXTENSION_LANGUAGE[name.slice(dot + 1)] ?? null;
}
 
/** Pixels to scroll a rendered code block so the cited line sits near the top
 *  with a couple of lines of lead-in — the code equivalent of the PDF preview's
 *  jump to the cited page. Returns 0 when there is nothing to scroll to. */
export function citedLineScrollTop(scrollHeight: number, totalLines: number, line: number | null): number {
    if (line === null || line <= 1 || totalLines <= 0 || scrollHeight <= 0) return 0;
    const lineHeight = scrollHeight / totalLines;
    return Math.max(0, (line - 1) * lineHeight - lineHeight * 2);
}
 
/** Wrap content in a fenced code block whose fence outlives any backtick run
 *  inside it, so embedded backticks can't close the block early. */
export function toCodeFence(content: string, lang: string): string {
    const longestRun = (content.match(/`+/g) ?? []).reduce((n, run) => Math.max(n, run.length), 0);
    const fence = "`".repeat(Math.max(3, longestRun + 1));
    return `${fence}${lang}\n${content}\n${fence}`;
}