code.py 11 KB

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