theme.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. import { SyntaxStyle, RGBA, type TerminalColors } from "@opentui/core"
  2. import path from "path"
  3. import { createEffect, createMemo, onMount } from "solid-js"
  4. import { useSync } from "@tui/context/sync"
  5. import { createSimpleContext } from "./helper"
  6. import aura from "./theme/aura.json" with { type: "json" }
  7. import ayu from "./theme/ayu.json" with { type: "json" }
  8. import catppuccin from "./theme/catppuccin.json" with { type: "json" }
  9. import cobalt2 from "./theme/cobalt2.json" with { type: "json" }
  10. import dracula from "./theme/dracula.json" with { type: "json" }
  11. import everforest from "./theme/everforest.json" with { type: "json" }
  12. import flexoki from "./theme/flexoki.json" with { type: "json" }
  13. import github from "./theme/github.json" with { type: "json" }
  14. import gruvbox from "./theme/gruvbox.json" with { type: "json" }
  15. import kanagawa from "./theme/kanagawa.json" with { type: "json" }
  16. import material from "./theme/material.json" with { type: "json" }
  17. import matrix from "./theme/matrix.json" with { type: "json" }
  18. import monokai from "./theme/monokai.json" with { type: "json" }
  19. import nightowl from "./theme/nightowl.json" with { type: "json" }
  20. import nord from "./theme/nord.json" with { type: "json" }
  21. import onedark from "./theme/one-dark.json" with { type: "json" }
  22. import opencode from "./theme/opencode.json" with { type: "json" }
  23. import palenight from "./theme/palenight.json" with { type: "json" }
  24. import rosepine from "./theme/rosepine.json" with { type: "json" }
  25. import solarized from "./theme/solarized.json" with { type: "json" }
  26. import synthwave84 from "./theme/synthwave84.json" with { type: "json" }
  27. import tokyonight from "./theme/tokyonight.json" with { type: "json" }
  28. import vesper from "./theme/vesper.json" with { type: "json" }
  29. import zenburn from "./theme/zenburn.json" with { type: "json" }
  30. import { useKV } from "./kv"
  31. import { useRenderer } from "@opentui/solid"
  32. import { createStore, produce } from "solid-js/store"
  33. import { Global } from "@/global"
  34. import { Filesystem } from "@/util/filesystem"
  35. type Theme = {
  36. primary: RGBA
  37. secondary: RGBA
  38. accent: RGBA
  39. error: RGBA
  40. warning: RGBA
  41. success: RGBA
  42. info: RGBA
  43. text: RGBA
  44. textMuted: RGBA
  45. background: RGBA
  46. backgroundPanel: RGBA
  47. backgroundElement: RGBA
  48. border: RGBA
  49. borderActive: RGBA
  50. borderSubtle: RGBA
  51. diffAdded: RGBA
  52. diffRemoved: RGBA
  53. diffContext: RGBA
  54. diffHunkHeader: RGBA
  55. diffHighlightAdded: RGBA
  56. diffHighlightRemoved: RGBA
  57. diffAddedBg: RGBA
  58. diffRemovedBg: RGBA
  59. diffContextBg: RGBA
  60. diffLineNumber: RGBA
  61. diffAddedLineNumberBg: RGBA
  62. diffRemovedLineNumberBg: RGBA
  63. markdownText: RGBA
  64. markdownHeading: RGBA
  65. markdownLink: RGBA
  66. markdownLinkText: RGBA
  67. markdownCode: RGBA
  68. markdownBlockQuote: RGBA
  69. markdownEmph: RGBA
  70. markdownStrong: RGBA
  71. markdownHorizontalRule: RGBA
  72. markdownListItem: RGBA
  73. markdownListEnumeration: RGBA
  74. markdownImage: RGBA
  75. markdownImageText: RGBA
  76. markdownCodeBlock: RGBA
  77. syntaxComment: RGBA
  78. syntaxKeyword: RGBA
  79. syntaxFunction: RGBA
  80. syntaxVariable: RGBA
  81. syntaxString: RGBA
  82. syntaxNumber: RGBA
  83. syntaxType: RGBA
  84. syntaxOperator: RGBA
  85. syntaxPunctuation: RGBA
  86. }
  87. type HexColor = `#${string}`
  88. type RefName = string
  89. type Variant = {
  90. dark: HexColor | RefName
  91. light: HexColor | RefName
  92. }
  93. type ColorValue = HexColor | RefName | Variant | RGBA
  94. type ThemeJson = {
  95. $schema?: string
  96. defs?: Record<string, HexColor | RefName>
  97. theme: Record<keyof Theme, ColorValue>
  98. }
  99. export const DEFAULT_THEMES: Record<string, ThemeJson> = {
  100. aura,
  101. ayu,
  102. catppuccin,
  103. cobalt2,
  104. dracula,
  105. everforest,
  106. flexoki,
  107. github,
  108. gruvbox,
  109. kanagawa,
  110. material,
  111. matrix,
  112. monokai,
  113. nightowl,
  114. nord,
  115. ["one-dark"]: onedark,
  116. opencode,
  117. palenight,
  118. rosepine,
  119. solarized,
  120. synthwave84,
  121. tokyonight,
  122. vesper,
  123. zenburn,
  124. }
  125. function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
  126. const defs = theme.defs ?? {}
  127. function resolveColor(c: ColorValue): RGBA {
  128. if (c instanceof RGBA) return c
  129. if (typeof c === "string") {
  130. if (c === "transparent" || c === "none") return RGBA.fromInts(0, 0, 0, 0)
  131. if (c.startsWith("#")) return RGBA.fromHex(c)
  132. if (defs[c]) {
  133. return resolveColor(defs[c])
  134. } else if (theme.theme[c as keyof Theme]) {
  135. return resolveColor(theme.theme[c as keyof Theme])
  136. } else {
  137. throw new Error(`Color reference "${c}" not found in defs or theme`)
  138. }
  139. }
  140. return resolveColor(c[mode])
  141. }
  142. return Object.fromEntries(
  143. Object.entries(theme.theme).map(([key, value]) => {
  144. return [key, resolveColor(value)]
  145. }),
  146. ) as Theme
  147. }
  148. export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
  149. name: "Theme",
  150. init: (props: { mode: "dark" | "light" }) => {
  151. const sync = useSync()
  152. const kv = useKV()
  153. const [store, setStore] = createStore({
  154. themes: DEFAULT_THEMES,
  155. mode: props.mode,
  156. active: (sync.data.config.theme ?? kv.get("theme", "opencode")) as string,
  157. ready: false,
  158. })
  159. createEffect(async () => {
  160. const custom = await getCustomThemes()
  161. setStore(
  162. produce((draft) => {
  163. Object.assign(draft.themes, custom)
  164. draft.ready = true
  165. }),
  166. )
  167. })
  168. const renderer = useRenderer()
  169. renderer
  170. .getPalette({
  171. size: 16,
  172. })
  173. .then((colors) => {
  174. if (!colors.palette[0]) return
  175. setStore("themes", "system", generateSystem(colors, store.mode))
  176. })
  177. const values = createMemo(() => {
  178. return resolveTheme(store.themes[store.active] ?? store.themes.opencode, store.mode)
  179. })
  180. const syntax = createMemo(() => generateSyntax(values()))
  181. const subtleSyntax = createMemo(() => generateSubtleSyntax(values()))
  182. return {
  183. theme: new Proxy(values(), {
  184. get(_target, prop) {
  185. // @ts-expect-error
  186. return values()[prop]
  187. },
  188. }),
  189. get selected() {
  190. return store.active
  191. },
  192. all() {
  193. return store.themes
  194. },
  195. syntax,
  196. subtleSyntax,
  197. mode() {
  198. return store.mode
  199. },
  200. setMode(mode: "dark" | "light") {
  201. setStore("mode", mode)
  202. },
  203. set(theme: string) {
  204. setStore("active", theme)
  205. kv.set("theme", theme)
  206. },
  207. get ready() {
  208. return store.ready
  209. },
  210. }
  211. },
  212. })
  213. const CUSTOM_THEME_GLOB = new Bun.Glob("themes/*.json")
  214. async function getCustomThemes() {
  215. const directories = [
  216. Global.Path.config,
  217. ...(await Array.fromAsync(
  218. Filesystem.up({
  219. targets: [".opencode"],
  220. start: process.cwd(),
  221. }),
  222. )),
  223. ]
  224. const result: Record<string, ThemeJson> = {}
  225. for (const dir of directories) {
  226. for await (const item of CUSTOM_THEME_GLOB.scan({
  227. absolute: true,
  228. followSymlinks: true,
  229. dot: true,
  230. cwd: dir,
  231. })) {
  232. const name = path.basename(item, ".json")
  233. result[name] = await Bun.file(item).json()
  234. }
  235. }
  236. return result
  237. }
  238. function generateSystem(colors: TerminalColors, mode: "dark" | "light"): ThemeJson {
  239. const bg = RGBA.fromHex(colors.defaultBackground ?? colors.palette[0]!)
  240. const fg = RGBA.fromHex(colors.defaultForeground ?? colors.palette[7]!)
  241. const palette = colors.palette.filter((x) => x !== null).map((x) => RGBA.fromHex(x))
  242. const isDark = mode == "dark"
  243. // Generate gray scale based on terminal background
  244. const grays = generateGrayScale(bg, isDark)
  245. const textMuted = generateMutedTextColor(bg, isDark)
  246. // ANSI color references
  247. const ansiColors = {
  248. black: palette[0],
  249. red: palette[1],
  250. green: palette[2],
  251. yellow: palette[3],
  252. blue: palette[4],
  253. magenta: palette[5],
  254. cyan: palette[6],
  255. white: palette[7],
  256. }
  257. return {
  258. theme: {
  259. // Primary colors using ANSI
  260. primary: ansiColors.cyan,
  261. secondary: ansiColors.magenta,
  262. accent: ansiColors.cyan,
  263. // Status colors using ANSI
  264. error: ansiColors.red,
  265. warning: ansiColors.yellow,
  266. success: ansiColors.green,
  267. info: ansiColors.cyan,
  268. // Text colors
  269. text: fg,
  270. textMuted,
  271. // Background colors
  272. background: bg,
  273. backgroundPanel: grays[2],
  274. backgroundElement: grays[3],
  275. // Border colors
  276. borderSubtle: grays[6],
  277. border: grays[7],
  278. borderActive: grays[8],
  279. // Diff colors
  280. diffAdded: ansiColors.green,
  281. diffRemoved: ansiColors.red,
  282. diffContext: grays[7],
  283. diffHunkHeader: grays[7],
  284. diffHighlightAdded: ansiColors.green,
  285. diffHighlightRemoved: ansiColors.red,
  286. diffAddedBg: grays[2],
  287. diffRemovedBg: grays[2],
  288. diffContextBg: grays[1],
  289. diffLineNumber: grays[6],
  290. diffAddedLineNumberBg: grays[3],
  291. diffRemovedLineNumberBg: grays[3],
  292. // Markdown colors
  293. markdownText: fg,
  294. markdownHeading: fg,
  295. markdownLink: ansiColors.blue,
  296. markdownLinkText: ansiColors.cyan,
  297. markdownCode: ansiColors.green,
  298. markdownBlockQuote: ansiColors.yellow,
  299. markdownEmph: ansiColors.yellow,
  300. markdownStrong: fg,
  301. markdownHorizontalRule: grays[7],
  302. markdownListItem: ansiColors.blue,
  303. markdownListEnumeration: ansiColors.cyan,
  304. markdownImage: ansiColors.blue,
  305. markdownImageText: ansiColors.cyan,
  306. markdownCodeBlock: fg,
  307. // Syntax colors
  308. syntaxComment: textMuted,
  309. syntaxKeyword: ansiColors.magenta,
  310. syntaxFunction: ansiColors.blue,
  311. syntaxVariable: fg,
  312. syntaxString: ansiColors.green,
  313. syntaxNumber: ansiColors.yellow,
  314. syntaxType: ansiColors.cyan,
  315. syntaxOperator: ansiColors.cyan,
  316. syntaxPunctuation: fg,
  317. },
  318. }
  319. }
  320. function generateGrayScale(bg: RGBA, isDark: boolean): Record<number, RGBA> {
  321. const grays: Record<number, RGBA> = {}
  322. // RGBA stores floats in range 0-1, convert to 0-255
  323. const bgR = bg.r * 255
  324. const bgG = bg.g * 255
  325. const bgB = bg.b * 255
  326. const luminance = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB
  327. for (let i = 1; i <= 12; i++) {
  328. const factor = i / 12.0
  329. let grayValue: number
  330. let newR: number
  331. let newG: number
  332. let newB: number
  333. if (isDark) {
  334. if (luminance < 10) {
  335. grayValue = Math.floor(factor * 0.4 * 255)
  336. newR = grayValue
  337. newG = grayValue
  338. newB = grayValue
  339. } else {
  340. const newLum = luminance + (255 - luminance) * factor * 0.4
  341. const ratio = newLum / luminance
  342. newR = Math.min(bgR * ratio, 255)
  343. newG = Math.min(bgG * ratio, 255)
  344. newB = Math.min(bgB * ratio, 255)
  345. }
  346. } else {
  347. if (luminance > 245) {
  348. grayValue = Math.floor(255 - factor * 0.4 * 255)
  349. newR = grayValue
  350. newG = grayValue
  351. newB = grayValue
  352. } else {
  353. const newLum = luminance * (1 - factor * 0.4)
  354. const ratio = newLum / luminance
  355. newR = Math.max(bgR * ratio, 0)
  356. newG = Math.max(bgG * ratio, 0)
  357. newB = Math.max(bgB * ratio, 0)
  358. }
  359. }
  360. grays[i] = RGBA.fromInts(Math.floor(newR), Math.floor(newG), Math.floor(newB))
  361. }
  362. return grays
  363. }
  364. function generateMutedTextColor(bg: RGBA, isDark: boolean): RGBA {
  365. // RGBA stores floats in range 0-1, convert to 0-255
  366. const bgR = bg.r * 255
  367. const bgG = bg.g * 255
  368. const bgB = bg.b * 255
  369. const bgLum = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB
  370. let grayValue: number
  371. if (isDark) {
  372. if (bgLum < 10) {
  373. // Very dark/black background
  374. grayValue = 180 // #b4b4b4
  375. } else {
  376. // Scale up for lighter dark backgrounds
  377. grayValue = Math.min(Math.floor(160 + bgLum * 0.3), 200)
  378. }
  379. } else {
  380. if (bgLum > 245) {
  381. // Very light/white background
  382. grayValue = 75 // #4b4b4b
  383. } else {
  384. // Scale down for darker light backgrounds
  385. grayValue = Math.max(Math.floor(100 - (255 - bgLum) * 0.2), 60)
  386. }
  387. }
  388. return RGBA.fromInts(grayValue, grayValue, grayValue)
  389. }
  390. function generateSyntax(theme: Theme) {
  391. return SyntaxStyle.fromTheme(getSyntaxRules(theme))
  392. }
  393. function generateSubtleSyntax(theme: Theme) {
  394. const rules = getSyntaxRules(theme)
  395. return SyntaxStyle.fromTheme(
  396. rules.map((rule) => {
  397. if (rule.style.foreground) {
  398. const fg = rule.style.foreground
  399. return {
  400. ...rule,
  401. style: {
  402. ...rule.style,
  403. foreground: RGBA.fromInts(
  404. Math.round(fg.r * 255),
  405. Math.round(fg.g * 255),
  406. Math.round(fg.b * 255),
  407. Math.round(0.6 * 255),
  408. ),
  409. },
  410. }
  411. }
  412. return rule
  413. }),
  414. )
  415. }
  416. function getSyntaxRules(theme: Theme) {
  417. return [
  418. {
  419. scope: ["prompt"],
  420. style: {
  421. foreground: theme.accent,
  422. },
  423. },
  424. {
  425. scope: ["extmark.file"],
  426. style: {
  427. foreground: theme.warning,
  428. bold: true,
  429. },
  430. },
  431. {
  432. scope: ["extmark.agent"],
  433. style: {
  434. foreground: theme.secondary,
  435. bold: true,
  436. },
  437. },
  438. {
  439. scope: ["extmark.paste"],
  440. style: {
  441. foreground: theme.background,
  442. background: theme.warning,
  443. bold: true,
  444. },
  445. },
  446. {
  447. scope: ["comment"],
  448. style: {
  449. foreground: theme.syntaxComment,
  450. italic: true,
  451. },
  452. },
  453. {
  454. scope: ["comment.documentation"],
  455. style: {
  456. foreground: theme.syntaxComment,
  457. italic: true,
  458. },
  459. },
  460. {
  461. scope: ["string", "symbol"],
  462. style: {
  463. foreground: theme.syntaxString,
  464. },
  465. },
  466. {
  467. scope: ["number", "boolean"],
  468. style: {
  469. foreground: theme.syntaxNumber,
  470. },
  471. },
  472. {
  473. scope: ["character.special"],
  474. style: {
  475. foreground: theme.syntaxString,
  476. },
  477. },
  478. {
  479. scope: ["keyword.return", "keyword.conditional", "keyword.repeat", "keyword.coroutine"],
  480. style: {
  481. foreground: theme.syntaxKeyword,
  482. italic: true,
  483. },
  484. },
  485. {
  486. scope: ["keyword.type"],
  487. style: {
  488. foreground: theme.syntaxType,
  489. bold: true,
  490. italic: true,
  491. },
  492. },
  493. {
  494. scope: ["keyword.function", "function.method"],
  495. style: {
  496. foreground: theme.syntaxFunction,
  497. },
  498. },
  499. {
  500. scope: ["keyword"],
  501. style: {
  502. foreground: theme.syntaxKeyword,
  503. italic: true,
  504. },
  505. },
  506. {
  507. scope: ["keyword.import"],
  508. style: {
  509. foreground: theme.syntaxKeyword,
  510. },
  511. },
  512. {
  513. scope: ["operator", "keyword.operator", "punctuation.delimiter"],
  514. style: {
  515. foreground: theme.syntaxOperator,
  516. },
  517. },
  518. {
  519. scope: ["keyword.conditional.ternary"],
  520. style: {
  521. foreground: theme.syntaxOperator,
  522. },
  523. },
  524. {
  525. scope: ["variable", "variable.parameter", "function.method.call", "function.call"],
  526. style: {
  527. foreground: theme.syntaxVariable,
  528. },
  529. },
  530. {
  531. scope: ["variable.member", "function", "constructor"],
  532. style: {
  533. foreground: theme.syntaxFunction,
  534. },
  535. },
  536. {
  537. scope: ["type", "module"],
  538. style: {
  539. foreground: theme.syntaxType,
  540. },
  541. },
  542. {
  543. scope: ["constant"],
  544. style: {
  545. foreground: theme.syntaxNumber,
  546. },
  547. },
  548. {
  549. scope: ["property"],
  550. style: {
  551. foreground: theme.syntaxVariable,
  552. },
  553. },
  554. {
  555. scope: ["class"],
  556. style: {
  557. foreground: theme.syntaxType,
  558. },
  559. },
  560. {
  561. scope: ["parameter"],
  562. style: {
  563. foreground: theme.syntaxVariable,
  564. },
  565. },
  566. {
  567. scope: ["punctuation", "punctuation.bracket"],
  568. style: {
  569. foreground: theme.syntaxPunctuation,
  570. },
  571. },
  572. {
  573. scope: ["variable.builtin", "type.builtin", "function.builtin", "module.builtin", "constant.builtin"],
  574. style: {
  575. foreground: theme.error,
  576. },
  577. },
  578. {
  579. scope: ["variable.super"],
  580. style: {
  581. foreground: theme.error,
  582. },
  583. },
  584. {
  585. scope: ["string.escape", "string.regexp"],
  586. style: {
  587. foreground: theme.syntaxKeyword,
  588. },
  589. },
  590. {
  591. scope: ["keyword.directive"],
  592. style: {
  593. foreground: theme.syntaxKeyword,
  594. italic: true,
  595. },
  596. },
  597. {
  598. scope: ["punctuation.special"],
  599. style: {
  600. foreground: theme.syntaxOperator,
  601. },
  602. },
  603. {
  604. scope: ["keyword.modifier"],
  605. style: {
  606. foreground: theme.syntaxKeyword,
  607. italic: true,
  608. },
  609. },
  610. {
  611. scope: ["keyword.exception"],
  612. style: {
  613. foreground: theme.syntaxKeyword,
  614. italic: true,
  615. },
  616. },
  617. // Markdown specific styles
  618. {
  619. scope: ["markup.heading"],
  620. style: {
  621. foreground: theme.markdownHeading,
  622. bold: true,
  623. },
  624. },
  625. {
  626. scope: ["markup.heading.1"],
  627. style: {
  628. foreground: theme.markdownHeading,
  629. bold: true,
  630. },
  631. },
  632. {
  633. scope: ["markup.heading.2"],
  634. style: {
  635. foreground: theme.markdownHeading,
  636. bold: true,
  637. },
  638. },
  639. {
  640. scope: ["markup.heading.3"],
  641. style: {
  642. foreground: theme.markdownHeading,
  643. bold: true,
  644. },
  645. },
  646. {
  647. scope: ["markup.heading.4"],
  648. style: {
  649. foreground: theme.markdownHeading,
  650. bold: true,
  651. },
  652. },
  653. {
  654. scope: ["markup.heading.5"],
  655. style: {
  656. foreground: theme.markdownHeading,
  657. bold: true,
  658. },
  659. },
  660. {
  661. scope: ["markup.heading.6"],
  662. style: {
  663. foreground: theme.markdownHeading,
  664. bold: true,
  665. },
  666. },
  667. {
  668. scope: ["markup.bold", "markup.strong"],
  669. style: {
  670. foreground: theme.markdownStrong,
  671. bold: true,
  672. },
  673. },
  674. {
  675. scope: ["markup.italic"],
  676. style: {
  677. foreground: theme.markdownEmph,
  678. italic: true,
  679. },
  680. },
  681. {
  682. scope: ["markup.list"],
  683. style: {
  684. foreground: theme.markdownListItem,
  685. },
  686. },
  687. {
  688. scope: ["markup.quote"],
  689. style: {
  690. foreground: theme.markdownBlockQuote,
  691. italic: true,
  692. },
  693. },
  694. {
  695. scope: ["markup.raw", "markup.raw.block"],
  696. style: {
  697. foreground: theme.markdownCode,
  698. },
  699. },
  700. {
  701. scope: ["markup.raw.inline"],
  702. style: {
  703. foreground: theme.markdownCode,
  704. background: theme.background,
  705. },
  706. },
  707. {
  708. scope: ["markup.link"],
  709. style: {
  710. foreground: theme.markdownLink,
  711. underline: true,
  712. },
  713. },
  714. {
  715. scope: ["markup.link.label"],
  716. style: {
  717. foreground: theme.markdownLinkText,
  718. underline: true,
  719. },
  720. },
  721. {
  722. scope: ["markup.link.url"],
  723. style: {
  724. foreground: theme.markdownLink,
  725. underline: true,
  726. },
  727. },
  728. {
  729. scope: ["label"],
  730. style: {
  731. foreground: theme.markdownLinkText,
  732. },
  733. },
  734. {
  735. scope: ["spell", "nospell"],
  736. style: {
  737. foreground: theme.text,
  738. },
  739. },
  740. {
  741. scope: ["conceal"],
  742. style: {
  743. foreground: theme.textMuted,
  744. },
  745. },
  746. // Additional common highlight groups
  747. {
  748. scope: ["string.special", "string.special.url"],
  749. style: {
  750. foreground: theme.markdownLink,
  751. underline: true,
  752. },
  753. },
  754. {
  755. scope: ["character"],
  756. style: {
  757. foreground: theme.syntaxString,
  758. },
  759. },
  760. {
  761. scope: ["float"],
  762. style: {
  763. foreground: theme.syntaxNumber,
  764. },
  765. },
  766. {
  767. scope: ["comment.error"],
  768. style: {
  769. foreground: theme.error,
  770. italic: true,
  771. bold: true,
  772. },
  773. },
  774. {
  775. scope: ["comment.warning"],
  776. style: {
  777. foreground: theme.warning,
  778. italic: true,
  779. bold: true,
  780. },
  781. },
  782. {
  783. scope: ["comment.todo", "comment.note"],
  784. style: {
  785. foreground: theme.info,
  786. italic: true,
  787. bold: true,
  788. },
  789. },
  790. {
  791. scope: ["namespace"],
  792. style: {
  793. foreground: theme.syntaxType,
  794. },
  795. },
  796. {
  797. scope: ["field"],
  798. style: {
  799. foreground: theme.syntaxVariable,
  800. },
  801. },
  802. {
  803. scope: ["type.definition"],
  804. style: {
  805. foreground: theme.syntaxType,
  806. bold: true,
  807. },
  808. },
  809. {
  810. scope: ["keyword.export"],
  811. style: {
  812. foreground: theme.syntaxKeyword,
  813. },
  814. },
  815. {
  816. scope: ["attribute", "annotation"],
  817. style: {
  818. foreground: theme.warning,
  819. },
  820. },
  821. {
  822. scope: ["tag"],
  823. style: {
  824. foreground: theme.error,
  825. },
  826. },
  827. {
  828. scope: ["tag.attribute"],
  829. style: {
  830. foreground: theme.syntaxKeyword,
  831. },
  832. },
  833. {
  834. scope: ["tag.delimiter"],
  835. style: {
  836. foreground: theme.syntaxOperator,
  837. },
  838. },
  839. {
  840. scope: ["markup.strikethrough"],
  841. style: {
  842. foreground: theme.textMuted,
  843. },
  844. },
  845. {
  846. scope: ["markup.underline"],
  847. style: {
  848. foreground: theme.text,
  849. underline: true,
  850. },
  851. },
  852. {
  853. scope: ["markup.list.checked"],
  854. style: {
  855. foreground: theme.success,
  856. },
  857. },
  858. {
  859. scope: ["markup.list.unchecked"],
  860. style: {
  861. foreground: theme.textMuted,
  862. },
  863. },
  864. {
  865. scope: ["diff.plus"],
  866. style: {
  867. foreground: theme.diffAdded,
  868. background: theme.diffAddedBg,
  869. },
  870. },
  871. {
  872. scope: ["diff.minus"],
  873. style: {
  874. foreground: theme.diffRemoved,
  875. background: theme.diffRemovedBg,
  876. },
  877. },
  878. {
  879. scope: ["diff.delta"],
  880. style: {
  881. foreground: theme.diffContext,
  882. background: theme.diffContextBg,
  883. },
  884. },
  885. {
  886. scope: ["error"],
  887. style: {
  888. foreground: theme.error,
  889. bold: true,
  890. },
  891. },
  892. {
  893. scope: ["warning"],
  894. style: {
  895. foreground: theme.warning,
  896. bold: true,
  897. },
  898. },
  899. {
  900. scope: ["info"],
  901. style: {
  902. foreground: theme.info,
  903. },
  904. },
  905. {
  906. scope: ["debug"],
  907. style: {
  908. foreground: theme.textMuted,
  909. },
  910. },
  911. ]
  912. }