code.py 9.9 KB

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