code.py 11 KB

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