code.py 14 KB

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