code.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. """A code component."""
  2. from __future__ import annotations
  3. import dataclasses
  4. from typing import ClassVar, Dict, Literal, Optional, Union
  5. from reflex.components.component import Component, ComponentNamespace
  6. from reflex.components.core.cond import color_mode_cond
  7. from reflex.components.lucide.icon import Icon
  8. from reflex.components.markdown.markdown import _LANGUAGE, MarkdownComponentMap
  9. from reflex.components.radix.themes.components.button import Button
  10. from reflex.components.radix.themes.layout.box import Box
  11. from reflex.constants.colors import Color
  12. from reflex.event import set_clipboard
  13. from reflex.style import Style
  14. from reflex.utils import console, format
  15. from reflex.utils.imports import ImportVar
  16. from reflex.vars.base import LiteralVar, Var, VarData
  17. LiteralCodeLanguage = Literal[
  18. "abap",
  19. "abnf",
  20. "actionscript",
  21. "ada",
  22. "agda",
  23. "al",
  24. "antlr4",
  25. "apacheconf",
  26. "apex",
  27. "apl",
  28. "applescript",
  29. "aql",
  30. "arduino",
  31. "arff",
  32. "asciidoc",
  33. "asm6502",
  34. "asmatmel",
  35. "aspnet",
  36. "autohotkey",
  37. "autoit",
  38. "avisynth",
  39. "avro-idl",
  40. "bash",
  41. "basic",
  42. "batch",
  43. "bbcode",
  44. "bicep",
  45. "birb",
  46. "bison",
  47. "bnf",
  48. "brainfuck",
  49. "brightscript",
  50. "bro",
  51. "bsl",
  52. "c",
  53. "cfscript",
  54. "chaiscript",
  55. "cil",
  56. "clike",
  57. "clojure",
  58. "cmake",
  59. "cobol",
  60. "coffeescript",
  61. "concurnas",
  62. "coq",
  63. "core",
  64. "cpp",
  65. "crystal",
  66. "csharp",
  67. "cshtml",
  68. "csp",
  69. "css",
  70. "css-extras",
  71. "csv",
  72. "cypher",
  73. "d",
  74. "dart",
  75. "dataweave",
  76. "dax",
  77. "dhall",
  78. "diff",
  79. "django",
  80. "dns-zone-file",
  81. "docker",
  82. "dot",
  83. "ebnf",
  84. "editorconfig",
  85. "eiffel",
  86. "ejs",
  87. "elixir",
  88. "elm",
  89. "erb",
  90. "erlang",
  91. "etlua",
  92. "excel-formula",
  93. "factor",
  94. "false",
  95. "firestore-security-rules",
  96. "flow",
  97. "fortran",
  98. "fsharp",
  99. "ftl",
  100. "gap",
  101. "gcode",
  102. "gdscript",
  103. "gedcom",
  104. "gherkin",
  105. "git",
  106. "glsl",
  107. "gml",
  108. "gn",
  109. "go",
  110. "go-module",
  111. "graphql",
  112. "groovy",
  113. "haml",
  114. "handlebars",
  115. "haskell",
  116. "haxe",
  117. "hcl",
  118. "hlsl",
  119. "hoon",
  120. "hpkp",
  121. "hsts",
  122. "http",
  123. "ichigojam",
  124. "icon",
  125. "icu-message-format",
  126. "idris",
  127. "iecst",
  128. "ignore",
  129. "index",
  130. "inform7",
  131. "ini",
  132. "io",
  133. "j",
  134. "java",
  135. "javadoc",
  136. "javadoclike",
  137. "javascript",
  138. "javastacktrace",
  139. "jexl",
  140. "jolie",
  141. "jq",
  142. "js-extras",
  143. "js-templates",
  144. "jsdoc",
  145. "json",
  146. "json5",
  147. "jsonp",
  148. "jsstacktrace",
  149. "jsx",
  150. "julia",
  151. "keepalived",
  152. "keyman",
  153. "kotlin",
  154. "kumir",
  155. "kusto",
  156. "latex",
  157. "latte",
  158. "less",
  159. "lilypond",
  160. "liquid",
  161. "lisp",
  162. "livescript",
  163. "llvm",
  164. "log",
  165. "lolcode",
  166. "lua",
  167. "magma",
  168. "makefile",
  169. "markdown",
  170. "markup",
  171. "markup-templating",
  172. "matlab",
  173. "maxscript",
  174. "mel",
  175. "mermaid",
  176. "mizar",
  177. "mongodb",
  178. "monkey",
  179. "moonscript",
  180. "n1ql",
  181. "n4js",
  182. "nand2tetris-hdl",
  183. "naniscript",
  184. "nasm",
  185. "neon",
  186. "nevod",
  187. "nginx",
  188. "nim",
  189. "nix",
  190. "nsis",
  191. "objectivec",
  192. "ocaml",
  193. "opencl",
  194. "openqasm",
  195. "oz",
  196. "parigp",
  197. "parser",
  198. "pascal",
  199. "pascaligo",
  200. "pcaxis",
  201. "peoplecode",
  202. "perl",
  203. "php",
  204. "php-extras",
  205. "phpdoc",
  206. "plsql",
  207. "powerquery",
  208. "powershell",
  209. "processing",
  210. "prolog",
  211. "promql",
  212. "properties",
  213. "protobuf",
  214. "psl",
  215. "pug",
  216. "puppet",
  217. "pure",
  218. "purebasic",
  219. "purescript",
  220. "python",
  221. "q",
  222. "qml",
  223. "qore",
  224. "qsharp",
  225. "r",
  226. "racket",
  227. "reason",
  228. "regex",
  229. "rego",
  230. "renpy",
  231. "rest",
  232. "rip",
  233. "roboconf",
  234. "robotframework",
  235. "ruby",
  236. "rust",
  237. "sas",
  238. "sass",
  239. "scala",
  240. "scheme",
  241. "scss",
  242. "shell-session",
  243. "smali",
  244. "smalltalk",
  245. "smarty",
  246. "sml",
  247. "solidity",
  248. "solution-file",
  249. "soy",
  250. "sparql",
  251. "splunk-spl",
  252. "sqf",
  253. "sql",
  254. "squirrel",
  255. "stan",
  256. "stylus",
  257. "swift",
  258. "systemd",
  259. "t4-cs",
  260. "t4-templating",
  261. "t4-vb",
  262. "tap",
  263. "tcl",
  264. "textile",
  265. "toml",
  266. "tremor",
  267. "tsx",
  268. "tt2",
  269. "turtle",
  270. "twig",
  271. "typescript",
  272. "typoscript",
  273. "unrealscript",
  274. "uorazor",
  275. "uri",
  276. "v",
  277. "vala",
  278. "vbnet",
  279. "velocity",
  280. "verilog",
  281. "vhdl",
  282. "vim",
  283. "visual-basic",
  284. "warpscript",
  285. "wasm",
  286. "web-idl",
  287. "wiki",
  288. "wolfram",
  289. "wren",
  290. "xeora",
  291. "xml-doc",
  292. "xojo",
  293. "xquery",
  294. "yaml",
  295. "yang",
  296. "zig",
  297. ]
  298. def construct_theme_var(theme: str) -> Var[Theme]:
  299. """Construct a theme var.
  300. Args:
  301. theme: The theme to construct.
  302. Returns:
  303. The constructed theme var.
  304. """
  305. return Var(
  306. theme,
  307. _var_data=VarData(
  308. imports={
  309. f"react-syntax-highlighter/dist/cjs/styles/prism/{format.to_kebab_case(theme)}": [
  310. ImportVar(tag=theme, is_default=True, install=False)
  311. ]
  312. }
  313. ),
  314. )
  315. @dataclasses.dataclass(init=False)
  316. class Theme:
  317. """Themes for the CodeBlock component."""
  318. a11y_dark: ClassVar[Var[Theme]] = construct_theme_var("a11yDark")
  319. atom_dark: ClassVar[Var[Theme]] = construct_theme_var("atomDark")
  320. cb: ClassVar[Var[Theme]] = construct_theme_var("cb")
  321. coldark_cold: ClassVar[Var[Theme]] = construct_theme_var("coldarkCold")
  322. coldark_dark: ClassVar[Var[Theme]] = construct_theme_var("coldarkDark")
  323. coy: ClassVar[Var[Theme]] = construct_theme_var("coy")
  324. coy_without_shadows: ClassVar[Var[Theme]] = construct_theme_var("coyWithoutShadows")
  325. darcula: ClassVar[Var[Theme]] = construct_theme_var("darcula")
  326. dark: ClassVar[Var[Theme]] = construct_theme_var("oneDark")
  327. dracula: ClassVar[Var[Theme]] = construct_theme_var("dracula")
  328. duotone_dark: ClassVar[Var[Theme]] = construct_theme_var("duotoneDark")
  329. duotone_earth: ClassVar[Var[Theme]] = construct_theme_var("duotoneEarth")
  330. duotone_forest: ClassVar[Var[Theme]] = construct_theme_var("duotoneForest")
  331. duotone_light: ClassVar[Var[Theme]] = construct_theme_var("duotoneLight")
  332. duotone_sea: ClassVar[Var[Theme]] = construct_theme_var("duotoneSea")
  333. duotone_space: ClassVar[Var[Theme]] = construct_theme_var("duotoneSpace")
  334. funky: ClassVar[Var[Theme]] = construct_theme_var("funky")
  335. ghcolors: ClassVar[Var[Theme]] = construct_theme_var("ghcolors")
  336. gruvbox_dark: ClassVar[Var[Theme]] = construct_theme_var("gruvboxDark")
  337. gruvbox_light: ClassVar[Var[Theme]] = construct_theme_var("gruvboxLight")
  338. holi_theme: ClassVar[Var[Theme]] = construct_theme_var("holiTheme")
  339. hopscotch: ClassVar[Var[Theme]] = construct_theme_var("hopscotch")
  340. light: ClassVar[Var[Theme]] = construct_theme_var("oneLight")
  341. lucario: ClassVar[Var[Theme]] = construct_theme_var("lucario")
  342. material_dark: ClassVar[Var[Theme]] = construct_theme_var("materialDark")
  343. material_light: ClassVar[Var[Theme]] = construct_theme_var("materialLight")
  344. material_oceanic: ClassVar[Var[Theme]] = construct_theme_var("materialOceanic")
  345. night_owl: ClassVar[Var[Theme]] = construct_theme_var("nightOwl")
  346. nord: ClassVar[Var[Theme]] = construct_theme_var("nord")
  347. okaidia: ClassVar[Var[Theme]] = construct_theme_var("okaidia")
  348. one_dark: ClassVar[Var[Theme]] = construct_theme_var("oneDark")
  349. one_light: ClassVar[Var[Theme]] = construct_theme_var("oneLight")
  350. pojoaque: ClassVar[Var[Theme]] = construct_theme_var("pojoaque")
  351. prism: ClassVar[Var[Theme]] = construct_theme_var("prism")
  352. shades_of_purple: ClassVar[Var[Theme]] = construct_theme_var("shadesOfPurple")
  353. solarized_dark_atom: ClassVar[Var[Theme]] = construct_theme_var("solarizedDarkAtom")
  354. solarizedlight: ClassVar[Var[Theme]] = construct_theme_var("solarizedlight")
  355. synthwave84: ClassVar[Var[Theme]] = construct_theme_var("synthwave84")
  356. tomorrow: ClassVar[Var[Theme]] = construct_theme_var("tomorrow")
  357. twilight: ClassVar[Var[Theme]] = construct_theme_var("twilight")
  358. vs: ClassVar[Var[Theme]] = construct_theme_var("vs")
  359. vs_dark: ClassVar[Var[Theme]] = construct_theme_var("vsDark")
  360. vsc_dark_plus: ClassVar[Var[Theme]] = construct_theme_var("vscDarkPlus")
  361. xonokai: ClassVar[Var[Theme]] = construct_theme_var("xonokai")
  362. z_touch: ClassVar[Var[Theme]] = construct_theme_var("zTouch")
  363. for theme_name in dir(Theme):
  364. if theme_name.startswith("_"):
  365. continue
  366. setattr(Theme, theme_name, getattr(Theme, theme_name)._replace(_var_type=Theme))
  367. class CodeBlock(Component, MarkdownComponentMap):
  368. """A code block."""
  369. library = "react-syntax-highlighter@15.6.0"
  370. tag = "PrismAsyncLight"
  371. alias = "SyntaxHighlighter"
  372. # The theme to use ("light" or "dark").
  373. theme: Var[Union[Theme, str]] = Theme.one_light
  374. # The language to use.
  375. language: Var[LiteralCodeLanguage] = Var.create("python")
  376. # The code to display.
  377. code: Var[str]
  378. # If this is enabled line numbers will be shown next to the code block.
  379. show_line_numbers: Var[bool]
  380. # The starting line number to use.
  381. starting_line_number: Var[int]
  382. # Whether to wrap long lines.
  383. wrap_long_lines: Var[bool]
  384. # A custom style for the code block.
  385. custom_style: Dict[str, Union[str, Var, Color]] = {}
  386. # Props passed down to the code tag.
  387. code_tag_props: Var[Dict[str, str]]
  388. # Whether a copy button should appear.
  389. can_copy: Optional[bool] = False
  390. # A custom copy button to override the default one.
  391. copy_button: Optional[Union[bool, Component]] = None
  392. @classmethod
  393. def create(
  394. cls,
  395. *children,
  396. **props,
  397. ):
  398. """Create a text component.
  399. Args:
  400. *children: The children of the component.
  401. **props: The props to pass to the component.
  402. Returns:
  403. The text component.
  404. """
  405. # This component handles style in a special prop.
  406. custom_style = props.pop("custom_style", {})
  407. can_copy = props.pop("can_copy", False)
  408. copy_button = props.pop("copy_button", None)
  409. if "theme" not in props:
  410. # Default color scheme responds to global color mode.
  411. props["theme"] = color_mode_cond(
  412. light=Theme.one_light,
  413. dark=Theme.one_dark,
  414. )
  415. # react-syntax-highlighter doesnt have an explicit "light" or "dark" theme so we use one-light and one-dark
  416. # themes respectively to ensure code compatibility.
  417. if "theme" in props and not isinstance(props["theme"], Var):
  418. props["theme"] = getattr(Theme, format.to_snake_case(props["theme"])) # type: ignore
  419. console.deprecate(
  420. feature_name="theme prop as string",
  421. reason="Use code_block.themes instead.",
  422. deprecation_version="0.6.0",
  423. removal_version="0.7.0",
  424. )
  425. if can_copy:
  426. code = children[0]
  427. copy_button = ( # type: ignore
  428. copy_button
  429. if copy_button is not None
  430. else Button.create(
  431. Icon.create(tag="copy"),
  432. on_click=set_clipboard(code),
  433. style=Style({"position": "absolute", "top": "0.5em", "right": "0"}),
  434. )
  435. )
  436. custom_style.update({"padding": "1em 3.2em 1em 1em"})
  437. else:
  438. copy_button = None
  439. # Transfer style props to the custom style prop.
  440. for key, value in props.items():
  441. if key not in cls.get_fields():
  442. custom_style[key] = value
  443. # Carry the children (code) via props
  444. if children:
  445. props["code"] = children[0]
  446. if not isinstance(props["code"], Var):
  447. props["code"] = LiteralVar.create(props["code"])
  448. # Create the component.
  449. code_block = super().create(
  450. **props,
  451. custom_style=Style(custom_style),
  452. )
  453. if copy_button:
  454. return Box.create(code_block, copy_button, position="relative")
  455. else:
  456. return code_block
  457. def add_style(self):
  458. """Add style to the component."""
  459. self.custom_style.update(self.style)
  460. def _render(self):
  461. out = super()._render()
  462. theme = self.theme
  463. out.add_props(style=theme).remove_props("theme", "code", "language").add_props(
  464. children=self.code, language=_LANGUAGE
  465. )
  466. return out
  467. def _exclude_props(self) -> list[str]:
  468. return ["can_copy", "copy_button"]
  469. @classmethod
  470. def _get_language_registration_hook(cls) -> str:
  471. """Get the hook to register the language.
  472. Returns:
  473. The hook to register the language.
  474. """
  475. return f"""
  476. if ({_LANGUAGE!s}) {{
  477. (async () => {{
  478. try {{
  479. const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{_LANGUAGE!s}}}`);
  480. SyntaxHighlighter.registerLanguage({_LANGUAGE!s}, module.default);
  481. }} catch (error) {{
  482. console.error(`Error importing language module for ${{{_LANGUAGE!s}}}:`, error);
  483. }}
  484. }})();
  485. }}
  486. """
  487. @classmethod
  488. def get_component_map_custom_code(cls) -> str:
  489. """Get the custom code for the component.
  490. Returns:
  491. The custom code for the component.
  492. """
  493. return cls._get_language_registration_hook()
  494. def add_hooks(self) -> list[str | Var]:
  495. """Add hooks for the component.
  496. Returns:
  497. The hooks for the component.
  498. """
  499. return [
  500. f"const {_LANGUAGE!s} = {self.language!s}",
  501. self._get_language_registration_hook(),
  502. ]
  503. class CodeblockNamespace(ComponentNamespace):
  504. """Namespace for the CodeBlock component."""
  505. themes = Theme
  506. __call__ = CodeBlock.create
  507. code_block = CodeblockNamespace()