123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563 |
- """A code component."""
- from __future__ import annotations
- import dataclasses
- from typing import ClassVar, Dict, Literal, Optional, Union
- from reflex.components.component import Component, ComponentNamespace
- from reflex.components.core.cond import color_mode_cond
- from reflex.components.lucide.icon import Icon
- from reflex.components.markdown.markdown import _LANGUAGE, MarkdownComponentMap
- from reflex.components.radix.themes.components.button import Button
- from reflex.components.radix.themes.layout.box import Box
- from reflex.constants.colors import Color
- from reflex.event import set_clipboard
- from reflex.style import Style
- from reflex.utils import console, format
- from reflex.utils.imports import ImportVar
- from reflex.vars.base import LiteralVar, Var, VarData
- LiteralCodeLanguage = Literal[
- "abap",
- "abnf",
- "actionscript",
- "ada",
- "agda",
- "al",
- "antlr4",
- "apacheconf",
- "apex",
- "apl",
- "applescript",
- "aql",
- "arduino",
- "arff",
- "asciidoc",
- "asm6502",
- "asmatmel",
- "aspnet",
- "autohotkey",
- "autoit",
- "avisynth",
- "avro-idl",
- "bash",
- "basic",
- "batch",
- "bbcode",
- "bicep",
- "birb",
- "bison",
- "bnf",
- "brainfuck",
- "brightscript",
- "bro",
- "bsl",
- "c",
- "cfscript",
- "chaiscript",
- "cil",
- "clike",
- "clojure",
- "cmake",
- "cobol",
- "coffeescript",
- "concurnas",
- "coq",
- "core",
- "cpp",
- "crystal",
- "csharp",
- "cshtml",
- "csp",
- "css",
- "css-extras",
- "csv",
- "cypher",
- "d",
- "dart",
- "dataweave",
- "dax",
- "dhall",
- "diff",
- "django",
- "dns-zone-file",
- "docker",
- "dot",
- "ebnf",
- "editorconfig",
- "eiffel",
- "ejs",
- "elixir",
- "elm",
- "erb",
- "erlang",
- "etlua",
- "excel-formula",
- "factor",
- "false",
- "firestore-security-rules",
- "flow",
- "fortran",
- "fsharp",
- "ftl",
- "gap",
- "gcode",
- "gdscript",
- "gedcom",
- "gherkin",
- "git",
- "glsl",
- "gml",
- "gn",
- "go",
- "go-module",
- "graphql",
- "groovy",
- "haml",
- "handlebars",
- "haskell",
- "haxe",
- "hcl",
- "hlsl",
- "hoon",
- "hpkp",
- "hsts",
- "http",
- "ichigojam",
- "icon",
- "icu-message-format",
- "idris",
- "iecst",
- "ignore",
- "index",
- "inform7",
- "ini",
- "io",
- "j",
- "java",
- "javadoc",
- "javadoclike",
- "javascript",
- "javastacktrace",
- "jexl",
- "jolie",
- "jq",
- "js-extras",
- "js-templates",
- "jsdoc",
- "json",
- "json5",
- "jsonp",
- "jsstacktrace",
- "jsx",
- "julia",
- "keepalived",
- "keyman",
- "kotlin",
- "kumir",
- "kusto",
- "latex",
- "latte",
- "less",
- "lilypond",
- "liquid",
- "lisp",
- "livescript",
- "llvm",
- "log",
- "lolcode",
- "lua",
- "magma",
- "makefile",
- "markdown",
- "markup",
- "markup-templating",
- "matlab",
- "maxscript",
- "mel",
- "mermaid",
- "mizar",
- "mongodb",
- "monkey",
- "moonscript",
- "n1ql",
- "n4js",
- "nand2tetris-hdl",
- "naniscript",
- "nasm",
- "neon",
- "nevod",
- "nginx",
- "nim",
- "nix",
- "nsis",
- "objectivec",
- "ocaml",
- "opencl",
- "openqasm",
- "oz",
- "parigp",
- "parser",
- "pascal",
- "pascaligo",
- "pcaxis",
- "peoplecode",
- "perl",
- "php",
- "php-extras",
- "phpdoc",
- "plsql",
- "powerquery",
- "powershell",
- "processing",
- "prolog",
- "promql",
- "properties",
- "protobuf",
- "psl",
- "pug",
- "puppet",
- "pure",
- "purebasic",
- "purescript",
- "python",
- "q",
- "qml",
- "qore",
- "qsharp",
- "r",
- "racket",
- "reason",
- "regex",
- "rego",
- "renpy",
- "rest",
- "rip",
- "roboconf",
- "robotframework",
- "ruby",
- "rust",
- "sas",
- "sass",
- "scala",
- "scheme",
- "scss",
- "shell-session",
- "smali",
- "smalltalk",
- "smarty",
- "sml",
- "solidity",
- "solution-file",
- "soy",
- "sparql",
- "splunk-spl",
- "sqf",
- "sql",
- "squirrel",
- "stan",
- "stylus",
- "swift",
- "systemd",
- "t4-cs",
- "t4-templating",
- "t4-vb",
- "tap",
- "tcl",
- "textile",
- "toml",
- "tremor",
- "tsx",
- "tt2",
- "turtle",
- "twig",
- "typescript",
- "typoscript",
- "unrealscript",
- "uorazor",
- "uri",
- "v",
- "vala",
- "vbnet",
- "velocity",
- "verilog",
- "vhdl",
- "vim",
- "visual-basic",
- "warpscript",
- "wasm",
- "web-idl",
- "wiki",
- "wolfram",
- "wren",
- "xeora",
- "xml-doc",
- "xojo",
- "xquery",
- "yaml",
- "yang",
- "zig",
- ]
- def construct_theme_var(theme: str) -> Var[Theme]:
- """Construct a theme var.
- Args:
- theme: The theme to construct.
- Returns:
- The constructed theme var.
- """
- return Var(
- theme,
- _var_data=VarData(
- imports={
- f"react-syntax-highlighter/dist/cjs/styles/prism/{format.to_kebab_case(theme)}": [
- ImportVar(tag=theme, is_default=True, install=False)
- ]
- }
- ),
- )
- @dataclasses.dataclass(init=False)
- class Theme:
- """Themes for the CodeBlock component."""
- a11y_dark: ClassVar[Var[Theme]] = construct_theme_var("a11yDark")
- atom_dark: ClassVar[Var[Theme]] = construct_theme_var("atomDark")
- cb: ClassVar[Var[Theme]] = construct_theme_var("cb")
- coldark_cold: ClassVar[Var[Theme]] = construct_theme_var("coldarkCold")
- coldark_dark: ClassVar[Var[Theme]] = construct_theme_var("coldarkDark")
- coy: ClassVar[Var[Theme]] = construct_theme_var("coy")
- coy_without_shadows: ClassVar[Var[Theme]] = construct_theme_var("coyWithoutShadows")
- darcula: ClassVar[Var[Theme]] = construct_theme_var("darcula")
- dark: ClassVar[Var[Theme]] = construct_theme_var("oneDark")
- dracula: ClassVar[Var[Theme]] = construct_theme_var("dracula")
- duotone_dark: ClassVar[Var[Theme]] = construct_theme_var("duotoneDark")
- duotone_earth: ClassVar[Var[Theme]] = construct_theme_var("duotoneEarth")
- duotone_forest: ClassVar[Var[Theme]] = construct_theme_var("duotoneForest")
- duotone_light: ClassVar[Var[Theme]] = construct_theme_var("duotoneLight")
- duotone_sea: ClassVar[Var[Theme]] = construct_theme_var("duotoneSea")
- duotone_space: ClassVar[Var[Theme]] = construct_theme_var("duotoneSpace")
- funky: ClassVar[Var[Theme]] = construct_theme_var("funky")
- ghcolors: ClassVar[Var[Theme]] = construct_theme_var("ghcolors")
- gruvbox_dark: ClassVar[Var[Theme]] = construct_theme_var("gruvboxDark")
- gruvbox_light: ClassVar[Var[Theme]] = construct_theme_var("gruvboxLight")
- holi_theme: ClassVar[Var[Theme]] = construct_theme_var("holiTheme")
- hopscotch: ClassVar[Var[Theme]] = construct_theme_var("hopscotch")
- light: ClassVar[Var[Theme]] = construct_theme_var("oneLight")
- lucario: ClassVar[Var[Theme]] = construct_theme_var("lucario")
- material_dark: ClassVar[Var[Theme]] = construct_theme_var("materialDark")
- material_light: ClassVar[Var[Theme]] = construct_theme_var("materialLight")
- material_oceanic: ClassVar[Var[Theme]] = construct_theme_var("materialOceanic")
- night_owl: ClassVar[Var[Theme]] = construct_theme_var("nightOwl")
- nord: ClassVar[Var[Theme]] = construct_theme_var("nord")
- okaidia: ClassVar[Var[Theme]] = construct_theme_var("okaidia")
- one_dark: ClassVar[Var[Theme]] = construct_theme_var("oneDark")
- one_light: ClassVar[Var[Theme]] = construct_theme_var("oneLight")
- pojoaque: ClassVar[Var[Theme]] = construct_theme_var("pojoaque")
- prism: ClassVar[Var[Theme]] = construct_theme_var("prism")
- shades_of_purple: ClassVar[Var[Theme]] = construct_theme_var("shadesOfPurple")
- solarized_dark_atom: ClassVar[Var[Theme]] = construct_theme_var("solarizedDarkAtom")
- solarizedlight: ClassVar[Var[Theme]] = construct_theme_var("solarizedlight")
- synthwave84: ClassVar[Var[Theme]] = construct_theme_var("synthwave84")
- tomorrow: ClassVar[Var[Theme]] = construct_theme_var("tomorrow")
- twilight: ClassVar[Var[Theme]] = construct_theme_var("twilight")
- vs: ClassVar[Var[Theme]] = construct_theme_var("vs")
- vs_dark: ClassVar[Var[Theme]] = construct_theme_var("vsDark")
- vsc_dark_plus: ClassVar[Var[Theme]] = construct_theme_var("vscDarkPlus")
- xonokai: ClassVar[Var[Theme]] = construct_theme_var("xonokai")
- z_touch: ClassVar[Var[Theme]] = construct_theme_var("zTouch")
- for theme_name in dir(Theme):
- if theme_name.startswith("_"):
- continue
- setattr(Theme, theme_name, getattr(Theme, theme_name)._replace(_var_type=Theme))
- class CodeBlock(Component, MarkdownComponentMap):
- """A code block."""
- library = "react-syntax-highlighter@15.6.0"
- tag = "PrismAsyncLight"
- alias = "SyntaxHighlighter"
- # The theme to use ("light" or "dark").
- theme: Var[Union[Theme, str]] = Theme.one_light
- # The language to use.
- language: Var[LiteralCodeLanguage] = Var.create("python")
- # The code to display.
- code: Var[str]
- # If this is enabled line numbers will be shown next to the code block.
- show_line_numbers: Var[bool]
- # The starting line number to use.
- starting_line_number: Var[int]
- # Whether to wrap long lines.
- wrap_long_lines: Var[bool]
- # A custom style for the code block.
- custom_style: Dict[str, Union[str, Var, Color]] = {}
- # Props passed down to the code tag.
- code_tag_props: Var[Dict[str, str]]
- # Whether a copy button should appear.
- can_copy: Optional[bool] = False
- # A custom copy button to override the default one.
- copy_button: Optional[Union[bool, Component]] = None
- @classmethod
- def create(
- cls,
- *children,
- **props,
- ):
- """Create a text component.
- Args:
- *children: The children of the component.
- **props: The props to pass to the component.
- Returns:
- The text component.
- """
- # This component handles style in a special prop.
- custom_style = props.pop("custom_style", {})
- can_copy = props.pop("can_copy", False)
- copy_button = props.pop("copy_button", None)
- if "theme" not in props:
- # Default color scheme responds to global color mode.
- props["theme"] = color_mode_cond(
- light=Theme.one_light,
- dark=Theme.one_dark,
- )
- # react-syntax-highlighter doesnt have an explicit "light" or "dark" theme so we use one-light and one-dark
- # themes respectively to ensure code compatibility.
- if "theme" in props and not isinstance(props["theme"], Var):
- props["theme"] = getattr(Theme, format.to_snake_case(props["theme"])) # type: ignore
- console.deprecate(
- feature_name="theme prop as string",
- reason="Use code_block.themes instead.",
- deprecation_version="0.6.0",
- removal_version="0.7.0",
- )
- if can_copy:
- code = children[0]
- copy_button = ( # type: ignore
- copy_button
- if copy_button is not None
- else Button.create(
- Icon.create(tag="copy"),
- on_click=set_clipboard(code),
- style=Style({"position": "absolute", "top": "0.5em", "right": "0"}),
- )
- )
- custom_style.update({"padding": "1em 3.2em 1em 1em"})
- else:
- copy_button = None
- # Transfer style props to the custom style prop.
- for key, value in props.items():
- if key not in cls.get_fields():
- custom_style[key] = value
- # Carry the children (code) via props
- if children:
- props["code"] = children[0]
- if not isinstance(props["code"], Var):
- props["code"] = LiteralVar.create(props["code"])
- # Create the component.
- code_block = super().create(
- **props,
- custom_style=Style(custom_style),
- )
- if copy_button:
- return Box.create(code_block, copy_button, position="relative")
- else:
- return code_block
- def add_style(self):
- """Add style to the component."""
- self.custom_style.update(self.style)
- def _render(self):
- out = super()._render()
- theme = self.theme
- out.add_props(style=theme).remove_props("theme", "code", "language").add_props(
- children=self.code, language=_LANGUAGE
- )
- return out
- def _exclude_props(self) -> list[str]:
- return ["can_copy", "copy_button"]
- @classmethod
- def _get_language_registration_hook(cls) -> str:
- """Get the hook to register the language.
- Returns:
- The hook to register the language.
- """
- return f"""
- if ({_LANGUAGE!s}) {{
- (async () => {{
- try {{
- const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{_LANGUAGE!s}}}`);
- SyntaxHighlighter.registerLanguage({_LANGUAGE!s}, module.default);
- }} catch (error) {{
- console.error(`Error importing language module for ${{{_LANGUAGE!s}}}:`, error);
- }}
- }})();
- }}
- """
- @classmethod
- def get_component_map_custom_code(cls) -> str:
- """Get the custom code for the component.
- Returns:
- The custom code for the component.
- """
- return cls._get_language_registration_hook()
- def add_hooks(self) -> list[str | Var]:
- """Add hooks for the component.
- Returns:
- The hooks for the component.
- """
- return [
- f"const {_LANGUAGE!s} = {self.language!s}",
- self._get_language_registration_hook(),
- ]
- class CodeblockNamespace(ComponentNamespace):
- """Namespace for the CodeBlock component."""
- themes = Theme
- __call__ = CodeBlock.create
- code_block = CodeblockNamespace()
|