Path: blob/main/src/quarto-core/text-highlighting.ts
3562 views
/*1* text-highlighting.ts2*3* Copyright (C) 2020-2022 Posit Software, PBC4*/5import { join } from "../deno_ral/path.ts";67import { kDefaultHighlightStyle } from "../command/render/constants.ts";8import { kHighlightStyle } from "../config/constants.ts";9import { FormatPandoc } from "../config/types.ts";1011import { existsSync } from "../deno_ral/fs.ts";12import { resourcePath } from "../core/resources.ts";13import { normalizePath } from "../core/path.ts";14import { warnOnce } from "../core/log.ts";1516export interface ThemeDescriptor {17json: Record<string, unknown>;18isAdaptive: boolean;19}2021const kDarkSuffix = "dark";22const kLightSuffix = "light";2324export function textHighlightThemePath(25inputDir: string,26theme: string | Record<string, string>,27style?: "dark" | "light",28) {29let resolvedTheme: string;30if (typeof theme === "object") {31if (style && theme[style]) {32resolvedTheme = theme[style] as string;33} else {34resolvedTheme = theme[Object.keys(theme)[0]] as string;35}36} else {37resolvedTheme = theme as string;38}3940// First try the style specific version of the theme, otherwise41// fall back to the plain name42const names = [43`${resolvedTheme}-${style === "dark" ? kDarkSuffix : kLightSuffix}`,44resolvedTheme,45];4647const themePath = names.map((name) => {48return resourcePath(join("pandoc", "highlight-styles", `${name}.theme`));49}).find((path) => existsSync(path));5051if (themePath) {52// first see if this matches a built in name53return themePath;54} else {55// see if this is a path to a user theme56const userThemePath = join(inputDir, resolvedTheme);57if (existsSync(userThemePath)) {58return normalizePath(userThemePath);59}60}6162// Could find a path63return undefined;64}6566export function readHighlightingTheme(67inputDir: string,68pandoc: FormatPandoc,69style: "dark" | "light" | "default",70): ThemeDescriptor | undefined {71const theme = pandoc[kHighlightStyle] || kDefaultHighlightStyle;72if (theme) {73const themeRaw = readTheme(inputDir, theme, style);74if (themeRaw) {75return {76json: JSON.parse(themeRaw),77isAdaptive: isAdaptiveTheme(theme),78};79} else {80return undefined;81}82} else {83return undefined;84}85}8687export function hasAdaptiveTheme(pandoc: FormatPandoc) {88const theme = pandoc[kHighlightStyle] || kDefaultHighlightStyle;89return theme && isAdaptiveTheme(theme);90}9192export function hasTextHighlighting(pandoc: FormatPandoc): boolean {93const theme = pandoc[kHighlightStyle];94return theme !== null;95}9697export function isAdaptiveTheme(theme: string | Record<string, string>) {98if (typeof theme === "string") {99return [100"a11y",101"arrow",102"atom-one",103"ayu",104"breeze",105"github",106"gruvbox",107"monochrome",108].includes(109theme,110);111} else {112const keys = Object.keys(theme);113return keys.includes("dark") && keys.includes("light");114}115}116117// Reads the contents of a theme file, falling back if the style specific version isn't available118export function readTheme(119inputDir: string,120theme: string | Record<string, string>,121style: "light" | "dark" | "default",122) {123const themeFile = textHighlightThemePath(124inputDir,125theme,126style === "default" ? undefined : style,127);128if (!themeFile) {129return undefined;130}131132if (!existsSync(themeFile)) {133warnOnce(`The text highlighting theme ${themeFile} does not exist.`);134return undefined;135}136137if (Deno.statSync(themeFile).isDirectory) {138throw new Error(139`The text highlighting theme ${themeFile} is a directory. Please provide a valid theme name or path to a .theme file.`,140);141}142return Deno.readTextFileSync(themeFile);143}144145// From https://github.com/jgm/skylighting/blob/a1d02a0db6260c73aaf04aae2e6e18b569caacdc/skylighting-core/src/Skylighting/Format/HTML.hs#L117-L147146export const kAbbrevs: Record<string, string> = {147"Keyword": "kw",148"DataType": "dt",149"DecVal": "dv",150"BaseN": "bn",151"Float": "fl",152"Char": "ch",153"String": "st",154"Comment": "co",155"Other": "ot",156"Alert": "al",157"Function": "fu",158"RegionMarker": "re",159"Error": "er",160"Constant": "cn",161"SpecialChar": "sc",162"VerbatimString": "vs",163"SpecialString": "ss",164"Import": "im",165"Documentation": "do",166"Annotation": "an",167"CommentVar": "cv",168"Variable": "va",169"ControlFlow": "cf",170"Operator": "op",171"BuiltIn": "bu",172"Extension": "ex",173"Preprocessor": "pp",174"Attribute": "at",175"Information": "in",176"Warning": "wa",177"Normal": "",178};179180181