Path: blob/main/src/quarto-core/text-highlighting.ts
12924 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, kSyntaxHighlighting } 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";2324// Resolve highlight theme from syntax-highlighting (new) or highlight-style (deprecated)25export function getHighlightTheme(26pandoc: FormatPandoc,27): string | Record<string, string> {28return pandoc[kSyntaxHighlighting] ||29pandoc[kHighlightStyle] ||30kDefaultHighlightStyle;31}3233export function textHighlightThemePath(34inputDir: string,35theme: string | Record<string, string>,36style?: "dark" | "light",37) {38let resolvedTheme: string;39if (typeof theme === "object") {40if (style && theme[style]) {41resolvedTheme = theme[style] as string;42} else {43resolvedTheme = theme[Object.keys(theme)[0]] as string;44}45} else {46resolvedTheme = theme as string;47}4849// First try the style specific version of the theme, otherwise50// fall back to the plain name51const names = [52`${resolvedTheme}-${style === "dark" ? kDarkSuffix : kLightSuffix}`,53resolvedTheme,54];5556const themePath = names.map((name) => {57return resourcePath(join("pandoc", "highlight-styles", `${name}.theme`));58}).find((path) => existsSync(path));5960if (themePath) {61// first see if this matches a built in name62return themePath;63} else {64// see if this is a path to a user theme65const userThemePath = join(inputDir, resolvedTheme);66if (existsSync(userThemePath)) {67return normalizePath(userThemePath);68}69}7071// Could find a path72return undefined;73}7475export function readHighlightingTheme(76inputDir: string,77pandoc: FormatPandoc,78style: "dark" | "light" | "default",79): ThemeDescriptor | undefined {80const theme = getHighlightTheme(pandoc);81const themeRaw = readTheme(inputDir, theme, style);82if (themeRaw) {83return {84json: JSON.parse(themeRaw),85isAdaptive: isAdaptiveTheme(theme),86};87}88return undefined;89}9091export function hasAdaptiveTheme(pandoc: FormatPandoc) {92return isAdaptiveTheme(getHighlightTheme(pandoc));93}9495export function hasTextHighlighting(pandoc: FormatPandoc): boolean {96return getHighlightTheme(pandoc) !== "none";97}9899export function isAdaptiveTheme(theme: string | Record<string, string>) {100if (typeof theme === "string") {101return [102"a11y",103"arrow",104"atom-one",105"ayu",106"breeze",107"github",108"gruvbox",109"monochrome",110].includes(111theme,112);113} else {114const keys = Object.keys(theme);115return keys.includes("dark") && keys.includes("light");116}117}118119// Reads the contents of a theme file, falling back if the style specific version isn't available120export function readTheme(121inputDir: string,122theme: string | Record<string, string>,123style: "light" | "dark" | "default",124) {125const themeFile = textHighlightThemePath(126inputDir,127theme,128style === "default" ? undefined : style,129);130if (!themeFile) {131return undefined;132}133134if (!existsSync(themeFile)) {135warnOnce(`The text highlighting theme ${themeFile} does not exist.`);136return undefined;137}138139if (Deno.statSync(themeFile).isDirectory) {140throw new Error(141`The text highlighting theme ${themeFile} is a directory. Please provide a valid theme name or path to a .theme file.`,142);143}144return Deno.readTextFileSync(themeFile);145}146147// From https://github.com/jgm/skylighting/blob/a1d02a0db6260c73aaf04aae2e6e18b569caacdc/skylighting-core/src/Skylighting/Format/HTML.hs#L117-L147148export const kAbbrevs: Record<string, string> = {149"Keyword": "kw",150"DataType": "dt",151"DecVal": "dv",152"BaseN": "bn",153"Float": "fl",154"Char": "ch",155"String": "st",156"Comment": "co",157"Other": "ot",158"Alert": "al",159"Function": "fu",160"RegionMarker": "re",161"Error": "er",162"Constant": "cn",163"SpecialChar": "sc",164"VerbatimString": "vs",165"SpecialString": "ss",166"Import": "im",167"Documentation": "do",168"Annotation": "an",169"CommentVar": "cv",170"Variable": "va",171"ControlFlow": "cf",172"Operator": "op",173"BuiltIn": "bu",174"Extension": "ex",175"Preprocessor": "pp",176"Attribute": "at",177"Information": "in",178"Warning": "wa",179"Normal": "",180};181182183