diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d2e76b31..e2983d1d1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ fail_fast: true repos: - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.6.9 + rev: v0.7.0 hooks: - id: ruff-format args: [reflex, tests] diff --git a/poetry.lock b/poetry.lock index 2f77234d4..bbd2735ac 100644 --- a/poetry.lock +++ b/poetry.lock @@ -970,13 +970,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] [[package]] name = "mako" -version = "1.3.5" +version = "1.3.6" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" files = [ - {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, - {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, + {file = "Mako-1.3.6-py3-none-any.whl", hash = "sha256:a91198468092a2f1a0de86ca92690fb0cfc43ca90ee17e15d93662b4c04b241a"}, + {file = "mako-1.3.6.tar.gz", hash = "sha256:9ec3a1583713479fae654f83ed9fa8c9a4c16b7bb0daba0e6bbebff50c0d983d"}, ] [package.dependencies] @@ -2272,29 +2272,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruff" -version = "0.6.9" +version = "0.7.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"}, - {file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"}, - {file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"}, - {file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"}, - {file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"}, - {file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"}, - {file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"}, - {file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"}, - {file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"}, + {file = "ruff-0.7.0-py3-none-linux_armv6l.whl", hash = "sha256:0cdf20c2b6ff98e37df47b2b0bd3a34aaa155f59a11182c1303cce79be715628"}, + {file = "ruff-0.7.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:496494d350c7fdeb36ca4ef1c9f21d80d182423718782222c29b3e72b3512737"}, + {file = "ruff-0.7.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:214b88498684e20b6b2b8852c01d50f0651f3cc6118dfa113b4def9f14faaf06"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630fce3fefe9844e91ea5bbf7ceadab4f9981f42b704fae011bb8efcaf5d84be"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:211d877674e9373d4bb0f1c80f97a0201c61bcd1e9d045b6e9726adc42c156aa"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194d6c46c98c73949a106425ed40a576f52291c12bc21399eb8f13a0f7073495"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:82c2579b82b9973a110fab281860403b397c08c403de92de19568f32f7178598"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9af971fe85dcd5eaed8f585ddbc6bdbe8c217fb8fcf510ea6bca5bdfff56040e"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b641c7f16939b7d24b7bfc0be4102c56562a18281f84f635604e8a6989948914"}, + {file = "ruff-0.7.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d71672336e46b34e0c90a790afeac8a31954fd42872c1f6adaea1dff76fd44f9"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ab7d98c7eed355166f367597e513a6c82408df4181a937628dbec79abb2a1fe4"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1eb54986f770f49edb14f71d33312d79e00e629a57387382200b1ef12d6a4ef9"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:dc452ba6f2bb9cf8726a84aa877061a2462afe9ae0ea1d411c53d226661c601d"}, + {file = "ruff-0.7.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4b406c2dce5be9bad59f2de26139a86017a517e6bcd2688da515481c05a2cb11"}, + {file = "ruff-0.7.0-py3-none-win32.whl", hash = "sha256:f6c968509f767776f524a8430426539587d5ec5c662f6addb6aa25bc2e8195ec"}, + {file = "ruff-0.7.0-py3-none-win_amd64.whl", hash = "sha256:ff4aabfbaaba880e85d394603b9e75d32b0693152e16fa659a3064a85df7fce2"}, + {file = "ruff-0.7.0-py3-none-win_arm64.whl", hash = "sha256:10842f69c245e78d6adec7e1db0a7d9ddc2fff0621d730e61657b64fa36f207e"}, + {file = "ruff-0.7.0.tar.gz", hash = "sha256:47a86360cf62d9cd53ebfb0b5eb0e882193fc191c6d717e8bef4462bc3b9ea2b"}, ] [[package]] @@ -3033,4 +3033,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "edb7145394dd61f5a665b5519cc0c091c8c1628200ea1170857cff1a6bdb829e" +content-hash = "8090ccaeca173bd8612e17a0b8d157d7492618e49450abd1c8373e2976349db0" diff --git a/pyproject.toml b/pyproject.toml index d4f583189..93f3c5d50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ darglint = ">=1.8.1,<2.0" toml = ">=0.10.2,<1.0" pytest-asyncio = ">=0.24.0" pytest-cov = ">=4.0.0,<6.0" -ruff = "^0.6.9" +ruff = "^0.7.0" pandas = ">=2.1.1,<3.0" pillow = ">=10.0.0,<12.0" plotly = ">=5.13.0,<6.0" @@ -91,7 +91,7 @@ build-backend = "poetry.core.masonry.api" [tool.ruff] target-version = "py39" lint.select = ["B", "D", "E", "F", "I", "SIM", "W"] -lint.ignore = ["B008", "D203", "D205", "D213", "D401", "D406", "D407", "E501", "F403", "F405", "F541"] +lint.ignore = ["B008", "D203", "D205", "D213", "D401", "D406", "D407", "E501", "F403", "F405", "F541", "SIM115"] lint.pydocstyle.convention = "google" [tool.ruff.lint.per-file-ignores] diff --git a/reflex/.templates/web/components/shiki/code.js b/reflex/.templates/web/components/shiki/code.js new file mode 100644 index 000000000..c655c3a60 --- /dev/null +++ b/reflex/.templates/web/components/shiki/code.js @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react" +import { codeToHtml} from "shiki" + +export function Code ({code, theme, language, transformers, ...divProps}) { + const [codeResult, setCodeResult] = useState("") + useEffect(() => { + async function fetchCode() { + let final_code; + + if (Array.isArray(code)) { + final_code = code[0]; + } else { + final_code = code; + } + const result = await codeToHtml(final_code, { + lang: language, + theme, + transformers + }); + setCodeResult(result); + } + fetchCode(); + }, [code, language, theme, transformers] + + ) + return ( +
+ ) +} diff --git a/reflex/components/datadisplay/shiki_code_block.py b/reflex/components/datadisplay/shiki_code_block.py new file mode 100644 index 000000000..46199a6e4 --- /dev/null +++ b/reflex/components/datadisplay/shiki_code_block.py @@ -0,0 +1,813 @@ +"""Shiki syntax hghlighter component.""" + +from __future__ import annotations + +import re +from collections import defaultdict +from typing import Any, Literal, Optional, Union + +from reflex.base import Base +from reflex.components.component import Component, ComponentNamespace +from reflex.components.core.colors import color +from reflex.components.core.cond import color_mode_cond +from reflex.components.el.elements.forms import Button +from reflex.components.lucide.icon import Icon +from reflex.components.radix.themes.layout.box import Box +from reflex.event import call_script, set_clipboard +from reflex.style import Style +from reflex.utils.exceptions import VarTypeError +from reflex.utils.imports import ImportVar +from reflex.vars.base import LiteralVar, Var +from reflex.vars.function import FunctionStringVar +from reflex.vars.sequence import StringVar, string_replace_operation + + +def copy_script() -> Any: + """Copy script for the code block and modify the child SVG element. + + + Returns: + Any: The result of calling the script. + """ + return call_script( + f""" +// Event listener for the parent click +document.addEventListener('click', function(event) {{ + // Find the closest div (parent element) + const parent = event.target.closest('div'); + // If the parent is found + if (parent) {{ + // Find the SVG element within the parent + const svgIcon = parent.querySelector('svg'); + // If the SVG exists, proceed with the script + if (svgIcon) {{ + const originalPath = svgIcon.innerHTML; + const checkmarkPath = ''; // Checkmark SVG path + function transition(element, scale, opacity) {{ + element.style.transform = `scale(${{scale}})`; + element.style.opacity = opacity; + }} + // Animate the SVG + transition(svgIcon, 0, '0'); + setTimeout(() => {{ + svgIcon.innerHTML = checkmarkPath; // Replace content with checkmark + svgIcon.setAttribute('viewBox', '0 0 24 24'); // Adjust viewBox if necessary + transition(svgIcon, 1, '1'); + setTimeout(() => {{ + transition(svgIcon, 0, '0'); + setTimeout(() => {{ + svgIcon.innerHTML = originalPath; // Restore original SVG content + transition(svgIcon, 1, '1'); + }}, 125); + }}, 600); + }}, 125); + }} else {{ + // console.error('SVG element not found within the parent.'); + }} + }} else {{ + // console.error('Parent element not found.'); + }} +}}); +""" + ) + + +SHIKIJS_TRANSFORMER_FNS = { + "transformerNotationDiff", + "transformerNotationHighlight", + "transformerNotationWordHighlight", + "transformerNotationFocus", + "transformerNotationErrorLevel", + "transformerRenderWhitespace", + "transformerMetaHighlight", + "transformerMetaWordHighlight", + "transformerCompactLineOptions", + # TODO: this transformer when included adds a weird behavior which removes other code lines. Need to figure out why. + # "transformerRemoveLineBreak", + "transformerRemoveNotationEscape", +} +LINE_NUMBER_STYLING = { + "code": { + "counter-reset": "step", + "counter-increment": "step 0", + "display": "grid", + "line-height": "1.7", + "font-size": "0.875em", + }, + "code .line::before": { + "content": "counter(step)", + "counter-increment": "step", + "width": "1rem", + "margin-right": "1.5rem", + "display": "inline-block", + "text-align": "right", + "color": "rgba(115,138,148,.4)", + }, +} +BOX_PARENT_STYLING = { + "pre": { + "margin": "0", + "padding": "24px", + "background": "transparent", + "overflow-x": "auto", + "border-radius": "6px", + }, +} + +THEME_MAPPING = { + "light": "one-light", + "dark": "one-dark-pro", + "a11y-dark": "github-dark", +} +LANGUAGE_MAPPING = {"bash": "shellscript"} +LiteralCodeLanguage = Literal[ + "abap", + "actionscript-3", + "ada", + "angular-html", + "angular-ts", + "apache", + "apex", + "apl", + "applescript", + "ara", + "asciidoc", + "asm", + "astro", + "awk", + "ballerina", + "bat", + "beancount", + "berry", + "bibtex", + "bicep", + "blade", + "c", + "cadence", + "clarity", + "clojure", + "cmake", + "cobol", + "codeowners", + "codeql", + "coffee", + "common-lisp", + "coq", + "cpp", + "crystal", + "csharp", + "css", + "csv", + "cue", + "cypher", + "d", + "dart", + "dax", + "desktop", + "diff", + "docker", + "dotenv", + "dream-maker", + "edge", + "elixir", + "elm", + "emacs-lisp", + "erb", + "erlang", + "fennel", + "fish", + "fluent", + "fortran-fixed-form", + "fortran-free-form", + "fsharp", + "gdresource", + "gdscript", + "gdshader", + "genie", + "gherkin", + "git-commit", + "git-rebase", + "gleam", + "glimmer-js", + "glimmer-ts", + "glsl", + "gnuplot", + "go", + "graphql", + "groovy", + "hack", + "haml", + "handlebars", + "haskell", + "haxe", + "hcl", + "hjson", + "hlsl", + "html", + "html-derivative", + "http", + "hxml", + "hy", + "imba", + "ini", + "java", + "javascript", + "jinja", + "jison", + "json", + "json5", + "jsonc", + "jsonl", + "jsonnet", + "jssm", + "jsx", + "julia", + "kotlin", + "kusto", + "latex", + "lean", + "less", + "liquid", + "log", + "logo", + "lua", + "luau", + "make", + "markdown", + "marko", + "matlab", + "mdc", + "mdx", + "mermaid", + "mojo", + "move", + "narrat", + "nextflow", + "nginx", + "nim", + "nix", + "nushell", + "objective-c", + "objective-cpp", + "ocaml", + "pascal", + "perl", + "php", + "plsql", + "po", + "postcss", + "powerquery", + "powershell", + "prisma", + "prolog", + "proto", + "pug", + "puppet", + "purescript", + "python", + "qml", + "qmldir", + "qss", + "r", + "racket", + "raku", + "razor", + "reg", + "regexp", + "rel", + "riscv", + "rst", + "ruby", + "rust", + "sas", + "sass", + "scala", + "scheme", + "scss", + "shaderlab", + "shellscript", + "shellsession", + "smalltalk", + "solidity", + "soy", + "sparql", + "splunk", + "sql", + "ssh-config", + "stata", + "stylus", + "svelte", + "swift", + "system-verilog", + "systemd", + "tasl", + "tcl", + "templ", + "terraform", + "tex", + "toml", + "ts-tags", + "tsv", + "tsx", + "turtle", + "twig", + "typescript", + "typespec", + "typst", + "v", + "vala", + "vb", + "verilog", + "vhdl", + "viml", + "vue", + "vue-html", + "vyper", + "wasm", + "wenyan", + "wgsl", + "wikitext", + "wolfram", + "xml", + "xsl", + "yaml", + "zenscript", + "zig", +] +LiteralCodeTheme = Literal[ + "andromeeda", + "aurora-x", + "ayu-dark", + "catppuccin-frappe", + "catppuccin-latte", + "catppuccin-macchiato", + "catppuccin-mocha", + "dark-plus", + "dracula", + "dracula-soft", + "everforest-dark", + "everforest-light", + "github-dark", + "github-dark-default", + "github-dark-dimmed", + "github-dark-high-contrast", + "github-light", + "github-light-default", + "github-light-high-contrast", + "houston", + "laserwave", + "light-plus", + "material-theme", + "material-theme-darker", + "material-theme-lighter", + "material-theme-ocean", + "material-theme-palenight", + "min-dark", + "min-light", + "monokai", + "night-owl", + "nord", + "one-dark-pro", + "one-light", + "plain", + "plastic", + "poimandres", + "red", + "rose-pine", + "rose-pine-dawn", + "rose-pine-moon", + "slack-dark", + "slack-ochin", + "snazzy-light", + "solarized-dark", + "solarized-light", + "synthwave-84", + "tokyo-night", + "vesper", + "vitesse-black", + "vitesse-dark", + "vitesse-light", +] + + +class ShikiBaseTransformers(Base): + """Base for creating transformers.""" + + library: str + fns: list[FunctionStringVar] + style: Optional[Style] + + +class ShikiJsTransformer(ShikiBaseTransformers): + """A Wrapped shikijs transformer.""" + + library: str = "@shikijs/transformers" + fns: list[FunctionStringVar] = [ + FunctionStringVar.create(fn) for fn in SHIKIJS_TRANSFORMER_FNS + ] + style: Optional[Style] = Style( + { + "code": {"line-height": "1.7", "font-size": "0.875em", "display": "grid"}, + # Diffs + ".diff": { + "margin": "0 -24px", + "padding": "0 24px", + "width": "calc(100% + 48px)", + "display": "inline-block", + }, + ".diff.add": { + "background-color": "rgba(16, 185, 129, .14)", + "position": "relative", + }, + ".diff.remove": { + "background-color": "rgba(244, 63, 94, .14)", + "opacity": "0.7", + "position": "relative", + }, + ".diff.remove:after": { + "position": "absolute", + "left": "10px", + "content": "'-'", + "color": "#b34e52", + }, + ".diff.add:after": { + "position": "absolute", + "left": "10px", + "content": "'+'", + "color": "#18794e", + }, + # Highlight + ".highlighted": { + "background-color": "rgba(142, 150, 170, .14)", + "margin": "0 -24px", + "padding": "0 24px", + "width": "calc(100% + 48px)", + "display": "inline-block", + }, + ".highlighted.error": { + "background-color": "rgba(244, 63, 94, .14)", + }, + ".highlighted.warning": { + "background-color": "rgba(234, 179, 8, .14)", + }, + # Highlighted Word + ".highlighted-word": { + "background-color": color("gray", 2), + "border": f"1px solid {color('gray', 5)}", + "padding": "1px 3px", + "margin": "-1px -3px", + "border-radius": "4px", + }, + # Focused Lines + ".has-focused .line:not(.focused)": { + "opacity": "0.7", + "filter": "blur(0.095rem)", + "transition": "filter .35s, opacity .35s", + }, + ".has-focused:hover .line:not(.focused)": { + "opacity": "1", + "filter": "none", + }, + # White Space + # ".tab, .space": { + # "position": "relative", + # }, + # ".tab::before": { + # "content": "'⇥'", + # "position": "absolute", + # "opacity": "0.3", + # }, + # ".space::before": { + # "content": "'·'", + # "position": "absolute", + # "opacity": "0.3", + # }, + } + ) + + def __init__(self, **kwargs): + """Initialize the transformer. + + Args: + kwargs: Kwargs to initialize the props. + + """ + fns = kwargs.pop("fns", None) + style = kwargs.pop("style", None) + if fns: + kwargs["fns"] = [ + ( + FunctionStringVar.create(x) + if not isinstance(x, FunctionStringVar) + else x + ) + for x in fns + ] + + if style: + kwargs["style"] = Style(style) + super().__init__(**kwargs) + + +class ShikiCodeBlock(Component): + """A Code block.""" + + library = "/components/shiki/code" + + tag = "Code" + + alias = "ShikiCode" + + lib_dependencies: list[str] = ["shiki"] + + # The language to use. + language: Var[LiteralCodeLanguage] = Var.create("python") + + # The theme to use ("light" or "dark"). + theme: Var[LiteralCodeTheme] = Var.create("one-light") + + # The set of themes to use for different modes. + themes: Var[Union[list[dict[str, Any]], dict[str, str]]] + + # The code to display. + code: Var[str] + + # The transformers to use for the syntax highlighter. + transformers: Var[list[Union[ShikiBaseTransformers, dict[str, Any]]]] = Var.create( + [] + ) + + @classmethod + def create( + cls, + *children, + **props, + ) -> Component: + """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/). + + Args: + *children: The children of the component. + **props: The props to pass to the component. + + Returns: + The code block component. + """ + # Separate props for the code block and the wrapper + code_block_props = {} + code_wrapper_props = {} + + class_props = cls.get_props() + + # Distribute props between the code block and wrapper + for key, value in props.items(): + (code_block_props if key in class_props else code_wrapper_props)[key] = ( + value + ) + + code_block_props["code"] = children[0] + code_block = super().create(**code_block_props) + + transformer_styles = {} + # Collect styles from transformers and wrapper + for transformer in code_block.transformers._var_value: # type: ignore + if isinstance(transformer, ShikiBaseTransformers) and transformer.style: + transformer_styles.update(transformer.style) + transformer_styles.update(code_wrapper_props.pop("style", {})) + + return Box.create( + code_block, + *children[1:], + style=Style({**transformer_styles, **BOX_PARENT_STYLING}), + **code_wrapper_props, + ) + + def add_imports(self) -> dict[str, list[str]]: + """Add the necessary imports. + We add all referenced transformer functions as imports from their corresponding + libraries. + + Returns: + Imports for the component. + """ + imports = defaultdict(list) + for transformer in self.transformers._var_value: + if isinstance(transformer, ShikiBaseTransformers): + imports[transformer.library].extend( + [ImportVar(tag=str(fn)) for fn in transformer.fns] + ) + ( + self.lib_dependencies.append(transformer.library) + if transformer.library not in self.lib_dependencies + else None + ) + return imports + + @classmethod + def create_transformer(cls, library: str, fns: list[str]) -> ShikiBaseTransformers: + """Create a transformer from a third party library. + + Args: + library: The name of the library. + fns: The str names of the functions/callables to invoke from the library. + + Returns: + A transformer for the specified library. + + Raises: + ValueError: If a supplied function name is not valid str. + """ + if any(not isinstance(fn_name, str) for fn_name in fns): + raise ValueError( + f"the function names should be str names of functions in the specified transformer: {library!r}" + ) + return ShikiBaseTransformers( # type: ignore + library=library, fns=[FunctionStringVar.create(fn) for fn in fns] + ) + + def _render(self, props: dict[str, Any] | None = None): + """Renders the component with the given properties, processing transformers if present. + + Args: + props: Optional properties to pass to the render function. + + Returns: + Rendered component output. + """ + # Ensure props is initialized from class attributes if not provided + props = props or { + attr.rstrip("_"): getattr(self, attr) for attr in self.get_props() + } + + # Extract transformers and apply transformations + transformers = props.get("transformers") + if transformers is not None: + transformed_values = self._process_transformers(transformers._var_value) + props["transformers"] = LiteralVar.create(transformed_values) + + return super()._render(props) + + def _process_transformers(self, transformer_list: list) -> list: + """Processes a list of transformers, applying transformations where necessary. + + Args: + transformer_list: List of transformer objects or values. + + Returns: + list: A list of transformed values. + """ + processed = [] + + for transformer in transformer_list: + if isinstance(transformer, ShikiBaseTransformers): + processed.extend(fn.call() for fn in transformer.fns) + else: + processed.append(transformer) + + return processed + + +class ShikiHighLevelCodeBlock(ShikiCodeBlock): + """High level component for the shiki syntax highlighter.""" + + # If this is enabled, the default transformers(shikijs transformer) will be used. + use_transformers: Var[bool] + + # If this is enabled line numbers will be shown next to the code block. + show_line_numbers: Var[bool] + + # Whether a copy button should appear. + can_copy: Var[bool] = Var.create(False) + + # copy_button: A custom copy button to override the default one. + copy_button: Var[Optional[Union[Component, bool]]] = Var.create(None) + + @classmethod + def create( + cls, + *children, + **props, + ) -> Component: + """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/). + + Args: + *children: The children of the component. + **props: The props to pass to the component. + + Returns: + The code block component. + """ + use_transformers = props.pop("use_transformers", False) + show_line_numbers = props.pop("show_line_numbers", False) + language = props.pop("language", None) + can_copy = props.pop("can_copy", False) + copy_button = props.pop("copy_button", None) + + if use_transformers: + props["transformers"] = [ShikiJsTransformer()] + + if language is not None: + props["language"] = cls._map_languages(language) + + # line numbers are generated via css + if show_line_numbers: + props["style"] = {**LINE_NUMBER_STYLING, **props.get("style", {})} + + theme = props.pop("theme", None) + props["theme"] = props["theme"] = ( + cls._map_themes(theme) + if theme is not None + else color_mode_cond( # Default color scheme responds to global color mode. + light="one-light", + dark="one-dark-pro", + ) + ) + + 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", size=16, color=color("gray", 11)), + on_click=[ + set_clipboard(cls._strip_transformer_triggers(code)), # type: ignore + copy_script(), + ], + style=Style( + { + "position": "absolute", + "top": "4px", + "right": "4px", + "background": color("gray", 3), + "border": "1px solid", + "border-color": color("gray", 5), + "border-radius": "6px", + "padding": "5px", + "opacity": "1", + "cursor": "pointer", + "_hover": { + "background": color("gray", 4), + }, + "transition": "background 0.250s ease-out", + "&>svg": { + "transition": "transform 0.250s ease-out, opacity 0.250s ease-out", + }, + "_active": { + "background": color("gray", 5), + }, + } + ), + ) + ) + + if copy_button: + return ShikiCodeBlock.create( + children[0], copy_button, position="relative", **props + ) + else: + return ShikiCodeBlock.create(children[0], **props) + + @staticmethod + def _map_themes(theme: str) -> str: + if isinstance(theme, str) and theme in THEME_MAPPING: + return THEME_MAPPING[theme] + return theme + + @staticmethod + def _map_languages(language: str) -> str: + if isinstance(language, str) and language in LANGUAGE_MAPPING: + return LANGUAGE_MAPPING[language] + return language + + @staticmethod + def _strip_transformer_triggers(code: str | StringVar) -> StringVar | str: + if not isinstance(code, (StringVar, str)): + raise VarTypeError( + f"code should be string literal or a StringVar type. Got {type(code)} instead." + ) + regex_pattern = r"[\/#]+ *\[!code.*?\]" + + if isinstance(code, Var): + return string_replace_operation( + code, StringVar(_js_expr=f"/{regex_pattern}/g", _var_type=str), "" + ) + if isinstance(code, str): + return re.sub(regex_pattern, "", code) + + +class TransformerNamespace(ComponentNamespace): + """Namespace for the Transformers.""" + + shikijs = ShikiJsTransformer + + +class CodeblockNamespace(ComponentNamespace): + """Namespace for the CodeBlock component.""" + + root = staticmethod(ShikiCodeBlock.create) + create_transformer = staticmethod(ShikiCodeBlock.create_transformer) + transformers = TransformerNamespace() + __call__ = staticmethod(ShikiHighLevelCodeBlock.create) + + +code_block = CodeblockNamespace() diff --git a/reflex/components/datadisplay/shiki_code_block.pyi b/reflex/components/datadisplay/shiki_code_block.pyi new file mode 100644 index 000000000..bcf2719e9 --- /dev/null +++ b/reflex/components/datadisplay/shiki_code_block.pyi @@ -0,0 +1,2211 @@ +"""Stub file for reflex/components/datadisplay/shiki_code_block.py""" + +# ------------------- DO NOT EDIT ---------------------- +# This file was generated by `reflex/utils/pyi_generator.py`! +# ------------------------------------------------------ +from typing import Any, Dict, Literal, Optional, Union, overload + +from reflex.base import Base +from reflex.components.component import Component, ComponentNamespace +from reflex.event import EventType +from reflex.style import Style +from reflex.vars.base import Var +from reflex.vars.function import FunctionStringVar + +def copy_script() -> Any: ... + +SHIKIJS_TRANSFORMER_FNS = { + "transformerNotationDiff", + "transformerNotationHighlight", + "transformerNotationWordHighlight", + "transformerNotationFocus", + "transformerNotationErrorLevel", + "transformerRenderWhitespace", + "transformerMetaHighlight", + "transformerMetaWordHighlight", + "transformerCompactLineOptions", + "transformerRemoveNotationEscape", +} +LINE_NUMBER_STYLING = { + "code": { + "counter-reset": "step", + "counter-increment": "step 0", + "display": "grid", + "line-height": "1.7", + "font-size": "0.875em", + }, + "code .line::before": { + "content": "counter(step)", + "counter-increment": "step", + "width": "1rem", + "margin-right": "1.5rem", + "display": "inline-block", + "text-align": "right", + "color": "rgba(115,138,148,.4)", + }, +} +BOX_PARENT_STYLING = { + "pre": { + "margin": "0", + "padding": "24px", + "background": "transparent", + "overflow-x": "auto", + "border-radius": "6px", + } +} +THEME_MAPPING = { + "light": "one-light", + "dark": "one-dark-pro", + "a11y-dark": "github-dark", +} +LANGUAGE_MAPPING = {"bash": "shellscript"} +LiteralCodeLanguage = Literal[ + "abap", + "actionscript-3", + "ada", + "angular-html", + "angular-ts", + "apache", + "apex", + "apl", + "applescript", + "ara", + "asciidoc", + "asm", + "astro", + "awk", + "ballerina", + "bat", + "beancount", + "berry", + "bibtex", + "bicep", + "blade", + "c", + "cadence", + "clarity", + "clojure", + "cmake", + "cobol", + "codeowners", + "codeql", + "coffee", + "common-lisp", + "coq", + "cpp", + "crystal", + "csharp", + "css", + "csv", + "cue", + "cypher", + "d", + "dart", + "dax", + "desktop", + "diff", + "docker", + "dotenv", + "dream-maker", + "edge", + "elixir", + "elm", + "emacs-lisp", + "erb", + "erlang", + "fennel", + "fish", + "fluent", + "fortran-fixed-form", + "fortran-free-form", + "fsharp", + "gdresource", + "gdscript", + "gdshader", + "genie", + "gherkin", + "git-commit", + "git-rebase", + "gleam", + "glimmer-js", + "glimmer-ts", + "glsl", + "gnuplot", + "go", + "graphql", + "groovy", + "hack", + "haml", + "handlebars", + "haskell", + "haxe", + "hcl", + "hjson", + "hlsl", + "html", + "html-derivative", + "http", + "hxml", + "hy", + "imba", + "ini", + "java", + "javascript", + "jinja", + "jison", + "json", + "json5", + "jsonc", + "jsonl", + "jsonnet", + "jssm", + "jsx", + "julia", + "kotlin", + "kusto", + "latex", + "lean", + "less", + "liquid", + "log", + "logo", + "lua", + "luau", + "make", + "markdown", + "marko", + "matlab", + "mdc", + "mdx", + "mermaid", + "mojo", + "move", + "narrat", + "nextflow", + "nginx", + "nim", + "nix", + "nushell", + "objective-c", + "objective-cpp", + "ocaml", + "pascal", + "perl", + "php", + "plsql", + "po", + "postcss", + "powerquery", + "powershell", + "prisma", + "prolog", + "proto", + "pug", + "puppet", + "purescript", + "python", + "qml", + "qmldir", + "qss", + "r", + "racket", + "raku", + "razor", + "reg", + "regexp", + "rel", + "riscv", + "rst", + "ruby", + "rust", + "sas", + "sass", + "scala", + "scheme", + "scss", + "shaderlab", + "shellscript", + "shellsession", + "smalltalk", + "solidity", + "soy", + "sparql", + "splunk", + "sql", + "ssh-config", + "stata", + "stylus", + "svelte", + "swift", + "system-verilog", + "systemd", + "tasl", + "tcl", + "templ", + "terraform", + "tex", + "toml", + "ts-tags", + "tsv", + "tsx", + "turtle", + "twig", + "typescript", + "typespec", + "typst", + "v", + "vala", + "vb", + "verilog", + "vhdl", + "viml", + "vue", + "vue-html", + "vyper", + "wasm", + "wenyan", + "wgsl", + "wikitext", + "wolfram", + "xml", + "xsl", + "yaml", + "zenscript", + "zig", +] +LiteralCodeTheme = Literal[ + "andromeeda", + "aurora-x", + "ayu-dark", + "catppuccin-frappe", + "catppuccin-latte", + "catppuccin-macchiato", + "catppuccin-mocha", + "dark-plus", + "dracula", + "dracula-soft", + "everforest-dark", + "everforest-light", + "github-dark", + "github-dark-default", + "github-dark-dimmed", + "github-dark-high-contrast", + "github-light", + "github-light-default", + "github-light-high-contrast", + "houston", + "laserwave", + "light-plus", + "material-theme", + "material-theme-darker", + "material-theme-lighter", + "material-theme-ocean", + "material-theme-palenight", + "min-dark", + "min-light", + "monokai", + "night-owl", + "nord", + "one-dark-pro", + "one-light", + "plain", + "plastic", + "poimandres", + "red", + "rose-pine", + "rose-pine-dawn", + "rose-pine-moon", + "slack-dark", + "slack-ochin", + "snazzy-light", + "solarized-dark", + "solarized-light", + "synthwave-84", + "tokyo-night", + "vesper", + "vitesse-black", + "vitesse-dark", + "vitesse-light", +] + +class ShikiBaseTransformers(Base): + library: str + fns: list[FunctionStringVar] + style: Optional[Style] + +class ShikiJsTransformer(ShikiBaseTransformers): + library: str + fns: list[FunctionStringVar] + style: Optional[Style] + +class ShikiCodeBlock(Component): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + language: Optional[ + Union[ + Literal[ + "abap", + "actionscript-3", + "ada", + "angular-html", + "angular-ts", + "apache", + "apex", + "apl", + "applescript", + "ara", + "asciidoc", + "asm", + "astro", + "awk", + "ballerina", + "bat", + "beancount", + "berry", + "bibtex", + "bicep", + "blade", + "c", + "cadence", + "clarity", + "clojure", + "cmake", + "cobol", + "codeowners", + "codeql", + "coffee", + "common-lisp", + "coq", + "cpp", + "crystal", + "csharp", + "css", + "csv", + "cue", + "cypher", + "d", + "dart", + "dax", + "desktop", + "diff", + "docker", + "dotenv", + "dream-maker", + "edge", + "elixir", + "elm", + "emacs-lisp", + "erb", + "erlang", + "fennel", + "fish", + "fluent", + "fortran-fixed-form", + "fortran-free-form", + "fsharp", + "gdresource", + "gdscript", + "gdshader", + "genie", + "gherkin", + "git-commit", + "git-rebase", + "gleam", + "glimmer-js", + "glimmer-ts", + "glsl", + "gnuplot", + "go", + "graphql", + "groovy", + "hack", + "haml", + "handlebars", + "haskell", + "haxe", + "hcl", + "hjson", + "hlsl", + "html", + "html-derivative", + "http", + "hxml", + "hy", + "imba", + "ini", + "java", + "javascript", + "jinja", + "jison", + "json", + "json5", + "jsonc", + "jsonl", + "jsonnet", + "jssm", + "jsx", + "julia", + "kotlin", + "kusto", + "latex", + "lean", + "less", + "liquid", + "log", + "logo", + "lua", + "luau", + "make", + "markdown", + "marko", + "matlab", + "mdc", + "mdx", + "mermaid", + "mojo", + "move", + "narrat", + "nextflow", + "nginx", + "nim", + "nix", + "nushell", + "objective-c", + "objective-cpp", + "ocaml", + "pascal", + "perl", + "php", + "plsql", + "po", + "postcss", + "powerquery", + "powershell", + "prisma", + "prolog", + "proto", + "pug", + "puppet", + "purescript", + "python", + "qml", + "qmldir", + "qss", + "r", + "racket", + "raku", + "razor", + "reg", + "regexp", + "rel", + "riscv", + "rst", + "ruby", + "rust", + "sas", + "sass", + "scala", + "scheme", + "scss", + "shaderlab", + "shellscript", + "shellsession", + "smalltalk", + "solidity", + "soy", + "sparql", + "splunk", + "sql", + "ssh-config", + "stata", + "stylus", + "svelte", + "swift", + "system-verilog", + "systemd", + "tasl", + "tcl", + "templ", + "terraform", + "tex", + "toml", + "ts-tags", + "tsv", + "tsx", + "turtle", + "twig", + "typescript", + "typespec", + "typst", + "v", + "vala", + "vb", + "verilog", + "vhdl", + "viml", + "vue", + "vue-html", + "vyper", + "wasm", + "wenyan", + "wgsl", + "wikitext", + "wolfram", + "xml", + "xsl", + "yaml", + "zenscript", + "zig", + ], + Var[ + Literal[ + "abap", + "actionscript-3", + "ada", + "angular-html", + "angular-ts", + "apache", + "apex", + "apl", + "applescript", + "ara", + "asciidoc", + "asm", + "astro", + "awk", + "ballerina", + "bat", + "beancount", + "berry", + "bibtex", + "bicep", + "blade", + "c", + "cadence", + "clarity", + "clojure", + "cmake", + "cobol", + "codeowners", + "codeql", + "coffee", + "common-lisp", + "coq", + "cpp", + "crystal", + "csharp", + "css", + "csv", + "cue", + "cypher", + "d", + "dart", + "dax", + "desktop", + "diff", + "docker", + "dotenv", + "dream-maker", + "edge", + "elixir", + "elm", + "emacs-lisp", + "erb", + "erlang", + "fennel", + "fish", + "fluent", + "fortran-fixed-form", + "fortran-free-form", + "fsharp", + "gdresource", + "gdscript", + "gdshader", + "genie", + "gherkin", + "git-commit", + "git-rebase", + "gleam", + "glimmer-js", + "glimmer-ts", + "glsl", + "gnuplot", + "go", + "graphql", + "groovy", + "hack", + "haml", + "handlebars", + "haskell", + "haxe", + "hcl", + "hjson", + "hlsl", + "html", + "html-derivative", + "http", + "hxml", + "hy", + "imba", + "ini", + "java", + "javascript", + "jinja", + "jison", + "json", + "json5", + "jsonc", + "jsonl", + "jsonnet", + "jssm", + "jsx", + "julia", + "kotlin", + "kusto", + "latex", + "lean", + "less", + "liquid", + "log", + "logo", + "lua", + "luau", + "make", + "markdown", + "marko", + "matlab", + "mdc", + "mdx", + "mermaid", + "mojo", + "move", + "narrat", + "nextflow", + "nginx", + "nim", + "nix", + "nushell", + "objective-c", + "objective-cpp", + "ocaml", + "pascal", + "perl", + "php", + "plsql", + "po", + "postcss", + "powerquery", + "powershell", + "prisma", + "prolog", + "proto", + "pug", + "puppet", + "purescript", + "python", + "qml", + "qmldir", + "qss", + "r", + "racket", + "raku", + "razor", + "reg", + "regexp", + "rel", + "riscv", + "rst", + "ruby", + "rust", + "sas", + "sass", + "scala", + "scheme", + "scss", + "shaderlab", + "shellscript", + "shellsession", + "smalltalk", + "solidity", + "soy", + "sparql", + "splunk", + "sql", + "ssh-config", + "stata", + "stylus", + "svelte", + "swift", + "system-verilog", + "systemd", + "tasl", + "tcl", + "templ", + "terraform", + "tex", + "toml", + "ts-tags", + "tsv", + "tsx", + "turtle", + "twig", + "typescript", + "typespec", + "typst", + "v", + "vala", + "vb", + "verilog", + "vhdl", + "viml", + "vue", + "vue-html", + "vyper", + "wasm", + "wenyan", + "wgsl", + "wikitext", + "wolfram", + "xml", + "xsl", + "yaml", + "zenscript", + "zig", + ] + ], + ] + ] = None, + theme: Optional[ + Union[ + Literal[ + "andromeeda", + "aurora-x", + "ayu-dark", + "catppuccin-frappe", + "catppuccin-latte", + "catppuccin-macchiato", + "catppuccin-mocha", + "dark-plus", + "dracula", + "dracula-soft", + "everforest-dark", + "everforest-light", + "github-dark", + "github-dark-default", + "github-dark-dimmed", + "github-dark-high-contrast", + "github-light", + "github-light-default", + "github-light-high-contrast", + "houston", + "laserwave", + "light-plus", + "material-theme", + "material-theme-darker", + "material-theme-lighter", + "material-theme-ocean", + "material-theme-palenight", + "min-dark", + "min-light", + "monokai", + "night-owl", + "nord", + "one-dark-pro", + "one-light", + "plain", + "plastic", + "poimandres", + "red", + "rose-pine", + "rose-pine-dawn", + "rose-pine-moon", + "slack-dark", + "slack-ochin", + "snazzy-light", + "solarized-dark", + "solarized-light", + "synthwave-84", + "tokyo-night", + "vesper", + "vitesse-black", + "vitesse-dark", + "vitesse-light", + ], + Var[ + Literal[ + "andromeeda", + "aurora-x", + "ayu-dark", + "catppuccin-frappe", + "catppuccin-latte", + "catppuccin-macchiato", + "catppuccin-mocha", + "dark-plus", + "dracula", + "dracula-soft", + "everforest-dark", + "everforest-light", + "github-dark", + "github-dark-default", + "github-dark-dimmed", + "github-dark-high-contrast", + "github-light", + "github-light-default", + "github-light-high-contrast", + "houston", + "laserwave", + "light-plus", + "material-theme", + "material-theme-darker", + "material-theme-lighter", + "material-theme-ocean", + "material-theme-palenight", + "min-dark", + "min-light", + "monokai", + "night-owl", + "nord", + "one-dark-pro", + "one-light", + "plain", + "plastic", + "poimandres", + "red", + "rose-pine", + "rose-pine-dawn", + "rose-pine-moon", + "slack-dark", + "slack-ochin", + "snazzy-light", + "solarized-dark", + "solarized-light", + "synthwave-84", + "tokyo-night", + "vesper", + "vitesse-black", + "vitesse-dark", + "vitesse-light", + ] + ], + ] + ] = None, + themes: Optional[ + Union[ + Var[Union[dict[str, str], list[dict[str, Any]]]], + dict[str, str], + list[dict[str, Any]], + ] + ] = None, + code: Optional[Union[Var[str], str]] = None, + transformers: Optional[ + Union[ + Var[list[Union[ShikiBaseTransformers, dict[str, Any]]]], + list[Union[ShikiBaseTransformers, dict[str, Any]]], + ] + ] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[EventType[[]]] = None, + on_click: Optional[EventType[[]]] = None, + on_context_menu: Optional[EventType[[]]] = None, + on_double_click: Optional[EventType[[]]] = None, + on_focus: Optional[EventType[[]]] = None, + on_mount: Optional[EventType[[]]] = None, + on_mouse_down: Optional[EventType[[]]] = None, + on_mouse_enter: Optional[EventType[[]]] = None, + on_mouse_leave: Optional[EventType[[]]] = None, + on_mouse_move: Optional[EventType[[]]] = None, + on_mouse_out: Optional[EventType[[]]] = None, + on_mouse_over: Optional[EventType[[]]] = None, + on_mouse_up: Optional[EventType[[]]] = None, + on_scroll: Optional[EventType[[]]] = None, + on_unmount: Optional[EventType[[]]] = None, + **props, + ) -> "ShikiCodeBlock": + """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/). + + Args: + *children: The children of the component. + language: The language to use. + theme: The theme to use ("light" or "dark"). + themes: The set of themes to use for different modes. + code: The code to display. + transformers: The transformers to use for the syntax highlighter. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props to pass to the component. + + Returns: + The code block component. + """ + ... + + def add_imports(self) -> dict[str, list[str]]: ... + @classmethod + def create_transformer( + cls, library: str, fns: list[str] + ) -> ShikiBaseTransformers: ... + +class ShikiHighLevelCodeBlock(ShikiCodeBlock): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + use_transformers: Optional[Union[Var[bool], bool]] = None, + show_line_numbers: Optional[Union[Var[bool], bool]] = None, + can_copy: Optional[Union[Var[bool], bool]] = None, + copy_button: Optional[ + Union[Component, Var[Optional[Union[Component, bool]]], bool] + ] = None, + language: Optional[ + Union[ + Literal[ + "abap", + "actionscript-3", + "ada", + "angular-html", + "angular-ts", + "apache", + "apex", + "apl", + "applescript", + "ara", + "asciidoc", + "asm", + "astro", + "awk", + "ballerina", + "bat", + "beancount", + "berry", + "bibtex", + "bicep", + "blade", + "c", + "cadence", + "clarity", + "clojure", + "cmake", + "cobol", + "codeowners", + "codeql", + "coffee", + "common-lisp", + "coq", + "cpp", + "crystal", + "csharp", + "css", + "csv", + "cue", + "cypher", + "d", + "dart", + "dax", + "desktop", + "diff", + "docker", + "dotenv", + "dream-maker", + "edge", + "elixir", + "elm", + "emacs-lisp", + "erb", + "erlang", + "fennel", + "fish", + "fluent", + "fortran-fixed-form", + "fortran-free-form", + "fsharp", + "gdresource", + "gdscript", + "gdshader", + "genie", + "gherkin", + "git-commit", + "git-rebase", + "gleam", + "glimmer-js", + "glimmer-ts", + "glsl", + "gnuplot", + "go", + "graphql", + "groovy", + "hack", + "haml", + "handlebars", + "haskell", + "haxe", + "hcl", + "hjson", + "hlsl", + "html", + "html-derivative", + "http", + "hxml", + "hy", + "imba", + "ini", + "java", + "javascript", + "jinja", + "jison", + "json", + "json5", + "jsonc", + "jsonl", + "jsonnet", + "jssm", + "jsx", + "julia", + "kotlin", + "kusto", + "latex", + "lean", + "less", + "liquid", + "log", + "logo", + "lua", + "luau", + "make", + "markdown", + "marko", + "matlab", + "mdc", + "mdx", + "mermaid", + "mojo", + "move", + "narrat", + "nextflow", + "nginx", + "nim", + "nix", + "nushell", + "objective-c", + "objective-cpp", + "ocaml", + "pascal", + "perl", + "php", + "plsql", + "po", + "postcss", + "powerquery", + "powershell", + "prisma", + "prolog", + "proto", + "pug", + "puppet", + "purescript", + "python", + "qml", + "qmldir", + "qss", + "r", + "racket", + "raku", + "razor", + "reg", + "regexp", + "rel", + "riscv", + "rst", + "ruby", + "rust", + "sas", + "sass", + "scala", + "scheme", + "scss", + "shaderlab", + "shellscript", + "shellsession", + "smalltalk", + "solidity", + "soy", + "sparql", + "splunk", + "sql", + "ssh-config", + "stata", + "stylus", + "svelte", + "swift", + "system-verilog", + "systemd", + "tasl", + "tcl", + "templ", + "terraform", + "tex", + "toml", + "ts-tags", + "tsv", + "tsx", + "turtle", + "twig", + "typescript", + "typespec", + "typst", + "v", + "vala", + "vb", + "verilog", + "vhdl", + "viml", + "vue", + "vue-html", + "vyper", + "wasm", + "wenyan", + "wgsl", + "wikitext", + "wolfram", + "xml", + "xsl", + "yaml", + "zenscript", + "zig", + ], + Var[ + Literal[ + "abap", + "actionscript-3", + "ada", + "angular-html", + "angular-ts", + "apache", + "apex", + "apl", + "applescript", + "ara", + "asciidoc", + "asm", + "astro", + "awk", + "ballerina", + "bat", + "beancount", + "berry", + "bibtex", + "bicep", + "blade", + "c", + "cadence", + "clarity", + "clojure", + "cmake", + "cobol", + "codeowners", + "codeql", + "coffee", + "common-lisp", + "coq", + "cpp", + "crystal", + "csharp", + "css", + "csv", + "cue", + "cypher", + "d", + "dart", + "dax", + "desktop", + "diff", + "docker", + "dotenv", + "dream-maker", + "edge", + "elixir", + "elm", + "emacs-lisp", + "erb", + "erlang", + "fennel", + "fish", + "fluent", + "fortran-fixed-form", + "fortran-free-form", + "fsharp", + "gdresource", + "gdscript", + "gdshader", + "genie", + "gherkin", + "git-commit", + "git-rebase", + "gleam", + "glimmer-js", + "glimmer-ts", + "glsl", + "gnuplot", + "go", + "graphql", + "groovy", + "hack", + "haml", + "handlebars", + "haskell", + "haxe", + "hcl", + "hjson", + "hlsl", + "html", + "html-derivative", + "http", + "hxml", + "hy", + "imba", + "ini", + "java", + "javascript", + "jinja", + "jison", + "json", + "json5", + "jsonc", + "jsonl", + "jsonnet", + "jssm", + "jsx", + "julia", + "kotlin", + "kusto", + "latex", + "lean", + "less", + "liquid", + "log", + "logo", + "lua", + "luau", + "make", + "markdown", + "marko", + "matlab", + "mdc", + "mdx", + "mermaid", + "mojo", + "move", + "narrat", + "nextflow", + "nginx", + "nim", + "nix", + "nushell", + "objective-c", + "objective-cpp", + "ocaml", + "pascal", + "perl", + "php", + "plsql", + "po", + "postcss", + "powerquery", + "powershell", + "prisma", + "prolog", + "proto", + "pug", + "puppet", + "purescript", + "python", + "qml", + "qmldir", + "qss", + "r", + "racket", + "raku", + "razor", + "reg", + "regexp", + "rel", + "riscv", + "rst", + "ruby", + "rust", + "sas", + "sass", + "scala", + "scheme", + "scss", + "shaderlab", + "shellscript", + "shellsession", + "smalltalk", + "solidity", + "soy", + "sparql", + "splunk", + "sql", + "ssh-config", + "stata", + "stylus", + "svelte", + "swift", + "system-verilog", + "systemd", + "tasl", + "tcl", + "templ", + "terraform", + "tex", + "toml", + "ts-tags", + "tsv", + "tsx", + "turtle", + "twig", + "typescript", + "typespec", + "typst", + "v", + "vala", + "vb", + "verilog", + "vhdl", + "viml", + "vue", + "vue-html", + "vyper", + "wasm", + "wenyan", + "wgsl", + "wikitext", + "wolfram", + "xml", + "xsl", + "yaml", + "zenscript", + "zig", + ] + ], + ] + ] = None, + theme: Optional[ + Union[ + Literal[ + "andromeeda", + "aurora-x", + "ayu-dark", + "catppuccin-frappe", + "catppuccin-latte", + "catppuccin-macchiato", + "catppuccin-mocha", + "dark-plus", + "dracula", + "dracula-soft", + "everforest-dark", + "everforest-light", + "github-dark", + "github-dark-default", + "github-dark-dimmed", + "github-dark-high-contrast", + "github-light", + "github-light-default", + "github-light-high-contrast", + "houston", + "laserwave", + "light-plus", + "material-theme", + "material-theme-darker", + "material-theme-lighter", + "material-theme-ocean", + "material-theme-palenight", + "min-dark", + "min-light", + "monokai", + "night-owl", + "nord", + "one-dark-pro", + "one-light", + "plain", + "plastic", + "poimandres", + "red", + "rose-pine", + "rose-pine-dawn", + "rose-pine-moon", + "slack-dark", + "slack-ochin", + "snazzy-light", + "solarized-dark", + "solarized-light", + "synthwave-84", + "tokyo-night", + "vesper", + "vitesse-black", + "vitesse-dark", + "vitesse-light", + ], + Var[ + Literal[ + "andromeeda", + "aurora-x", + "ayu-dark", + "catppuccin-frappe", + "catppuccin-latte", + "catppuccin-macchiato", + "catppuccin-mocha", + "dark-plus", + "dracula", + "dracula-soft", + "everforest-dark", + "everforest-light", + "github-dark", + "github-dark-default", + "github-dark-dimmed", + "github-dark-high-contrast", + "github-light", + "github-light-default", + "github-light-high-contrast", + "houston", + "laserwave", + "light-plus", + "material-theme", + "material-theme-darker", + "material-theme-lighter", + "material-theme-ocean", + "material-theme-palenight", + "min-dark", + "min-light", + "monokai", + "night-owl", + "nord", + "one-dark-pro", + "one-light", + "plain", + "plastic", + "poimandres", + "red", + "rose-pine", + "rose-pine-dawn", + "rose-pine-moon", + "slack-dark", + "slack-ochin", + "snazzy-light", + "solarized-dark", + "solarized-light", + "synthwave-84", + "tokyo-night", + "vesper", + "vitesse-black", + "vitesse-dark", + "vitesse-light", + ] + ], + ] + ] = None, + themes: Optional[ + Union[ + Var[Union[dict[str, str], list[dict[str, Any]]]], + dict[str, str], + list[dict[str, Any]], + ] + ] = None, + code: Optional[Union[Var[str], str]] = None, + transformers: Optional[ + Union[ + Var[list[Union[ShikiBaseTransformers, dict[str, Any]]]], + list[Union[ShikiBaseTransformers, dict[str, Any]]], + ] + ] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[EventType[[]]] = None, + on_click: Optional[EventType[[]]] = None, + on_context_menu: Optional[EventType[[]]] = None, + on_double_click: Optional[EventType[[]]] = None, + on_focus: Optional[EventType[[]]] = None, + on_mount: Optional[EventType[[]]] = None, + on_mouse_down: Optional[EventType[[]]] = None, + on_mouse_enter: Optional[EventType[[]]] = None, + on_mouse_leave: Optional[EventType[[]]] = None, + on_mouse_move: Optional[EventType[[]]] = None, + on_mouse_out: Optional[EventType[[]]] = None, + on_mouse_over: Optional[EventType[[]]] = None, + on_mouse_up: Optional[EventType[[]]] = None, + on_scroll: Optional[EventType[[]]] = None, + on_unmount: Optional[EventType[[]]] = None, + **props, + ) -> "ShikiHighLevelCodeBlock": + """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/). + + Args: + *children: The children of the component. + use_transformers: If this is enabled, the default transformers(shikijs transformer) will be used. + show_line_numbers: If this is enabled line numbers will be shown next to the code block. + can_copy: Whether a copy button should appear. + copy_button: copy_button: A custom copy button to override the default one. + language: The language to use. + theme: The theme to use ("light" or "dark"). + themes: The set of themes to use for different modes. + code: The code to display. + transformers: The transformers to use for the syntax highlighter. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props to pass to the component. + + Returns: + The code block component. + """ + ... + +class TransformerNamespace(ComponentNamespace): + shikijs = ShikiJsTransformer + +class CodeblockNamespace(ComponentNamespace): + root = staticmethod(ShikiCodeBlock.create) + create_transformer = staticmethod(ShikiCodeBlock.create_transformer) + transformers = TransformerNamespace() + + @staticmethod + def __call__( + *children, + use_transformers: Optional[Union[Var[bool], bool]] = None, + show_line_numbers: Optional[Union[Var[bool], bool]] = None, + can_copy: Optional[Union[Var[bool], bool]] = None, + copy_button: Optional[ + Union[Component, Var[Optional[Union[Component, bool]]], bool] + ] = None, + language: Optional[ + Union[ + Literal[ + "abap", + "actionscript-3", + "ada", + "angular-html", + "angular-ts", + "apache", + "apex", + "apl", + "applescript", + "ara", + "asciidoc", + "asm", + "astro", + "awk", + "ballerina", + "bat", + "beancount", + "berry", + "bibtex", + "bicep", + "blade", + "c", + "cadence", + "clarity", + "clojure", + "cmake", + "cobol", + "codeowners", + "codeql", + "coffee", + "common-lisp", + "coq", + "cpp", + "crystal", + "csharp", + "css", + "csv", + "cue", + "cypher", + "d", + "dart", + "dax", + "desktop", + "diff", + "docker", + "dotenv", + "dream-maker", + "edge", + "elixir", + "elm", + "emacs-lisp", + "erb", + "erlang", + "fennel", + "fish", + "fluent", + "fortran-fixed-form", + "fortran-free-form", + "fsharp", + "gdresource", + "gdscript", + "gdshader", + "genie", + "gherkin", + "git-commit", + "git-rebase", + "gleam", + "glimmer-js", + "glimmer-ts", + "glsl", + "gnuplot", + "go", + "graphql", + "groovy", + "hack", + "haml", + "handlebars", + "haskell", + "haxe", + "hcl", + "hjson", + "hlsl", + "html", + "html-derivative", + "http", + "hxml", + "hy", + "imba", + "ini", + "java", + "javascript", + "jinja", + "jison", + "json", + "json5", + "jsonc", + "jsonl", + "jsonnet", + "jssm", + "jsx", + "julia", + "kotlin", + "kusto", + "latex", + "lean", + "less", + "liquid", + "log", + "logo", + "lua", + "luau", + "make", + "markdown", + "marko", + "matlab", + "mdc", + "mdx", + "mermaid", + "mojo", + "move", + "narrat", + "nextflow", + "nginx", + "nim", + "nix", + "nushell", + "objective-c", + "objective-cpp", + "ocaml", + "pascal", + "perl", + "php", + "plsql", + "po", + "postcss", + "powerquery", + "powershell", + "prisma", + "prolog", + "proto", + "pug", + "puppet", + "purescript", + "python", + "qml", + "qmldir", + "qss", + "r", + "racket", + "raku", + "razor", + "reg", + "regexp", + "rel", + "riscv", + "rst", + "ruby", + "rust", + "sas", + "sass", + "scala", + "scheme", + "scss", + "shaderlab", + "shellscript", + "shellsession", + "smalltalk", + "solidity", + "soy", + "sparql", + "splunk", + "sql", + "ssh-config", + "stata", + "stylus", + "svelte", + "swift", + "system-verilog", + "systemd", + "tasl", + "tcl", + "templ", + "terraform", + "tex", + "toml", + "ts-tags", + "tsv", + "tsx", + "turtle", + "twig", + "typescript", + "typespec", + "typst", + "v", + "vala", + "vb", + "verilog", + "vhdl", + "viml", + "vue", + "vue-html", + "vyper", + "wasm", + "wenyan", + "wgsl", + "wikitext", + "wolfram", + "xml", + "xsl", + "yaml", + "zenscript", + "zig", + ], + Var[ + Literal[ + "abap", + "actionscript-3", + "ada", + "angular-html", + "angular-ts", + "apache", + "apex", + "apl", + "applescript", + "ara", + "asciidoc", + "asm", + "astro", + "awk", + "ballerina", + "bat", + "beancount", + "berry", + "bibtex", + "bicep", + "blade", + "c", + "cadence", + "clarity", + "clojure", + "cmake", + "cobol", + "codeowners", + "codeql", + "coffee", + "common-lisp", + "coq", + "cpp", + "crystal", + "csharp", + "css", + "csv", + "cue", + "cypher", + "d", + "dart", + "dax", + "desktop", + "diff", + "docker", + "dotenv", + "dream-maker", + "edge", + "elixir", + "elm", + "emacs-lisp", + "erb", + "erlang", + "fennel", + "fish", + "fluent", + "fortran-fixed-form", + "fortran-free-form", + "fsharp", + "gdresource", + "gdscript", + "gdshader", + "genie", + "gherkin", + "git-commit", + "git-rebase", + "gleam", + "glimmer-js", + "glimmer-ts", + "glsl", + "gnuplot", + "go", + "graphql", + "groovy", + "hack", + "haml", + "handlebars", + "haskell", + "haxe", + "hcl", + "hjson", + "hlsl", + "html", + "html-derivative", + "http", + "hxml", + "hy", + "imba", + "ini", + "java", + "javascript", + "jinja", + "jison", + "json", + "json5", + "jsonc", + "jsonl", + "jsonnet", + "jssm", + "jsx", + "julia", + "kotlin", + "kusto", + "latex", + "lean", + "less", + "liquid", + "log", + "logo", + "lua", + "luau", + "make", + "markdown", + "marko", + "matlab", + "mdc", + "mdx", + "mermaid", + "mojo", + "move", + "narrat", + "nextflow", + "nginx", + "nim", + "nix", + "nushell", + "objective-c", + "objective-cpp", + "ocaml", + "pascal", + "perl", + "php", + "plsql", + "po", + "postcss", + "powerquery", + "powershell", + "prisma", + "prolog", + "proto", + "pug", + "puppet", + "purescript", + "python", + "qml", + "qmldir", + "qss", + "r", + "racket", + "raku", + "razor", + "reg", + "regexp", + "rel", + "riscv", + "rst", + "ruby", + "rust", + "sas", + "sass", + "scala", + "scheme", + "scss", + "shaderlab", + "shellscript", + "shellsession", + "smalltalk", + "solidity", + "soy", + "sparql", + "splunk", + "sql", + "ssh-config", + "stata", + "stylus", + "svelte", + "swift", + "system-verilog", + "systemd", + "tasl", + "tcl", + "templ", + "terraform", + "tex", + "toml", + "ts-tags", + "tsv", + "tsx", + "turtle", + "twig", + "typescript", + "typespec", + "typst", + "v", + "vala", + "vb", + "verilog", + "vhdl", + "viml", + "vue", + "vue-html", + "vyper", + "wasm", + "wenyan", + "wgsl", + "wikitext", + "wolfram", + "xml", + "xsl", + "yaml", + "zenscript", + "zig", + ] + ], + ] + ] = None, + theme: Optional[ + Union[ + Literal[ + "andromeeda", + "aurora-x", + "ayu-dark", + "catppuccin-frappe", + "catppuccin-latte", + "catppuccin-macchiato", + "catppuccin-mocha", + "dark-plus", + "dracula", + "dracula-soft", + "everforest-dark", + "everforest-light", + "github-dark", + "github-dark-default", + "github-dark-dimmed", + "github-dark-high-contrast", + "github-light", + "github-light-default", + "github-light-high-contrast", + "houston", + "laserwave", + "light-plus", + "material-theme", + "material-theme-darker", + "material-theme-lighter", + "material-theme-ocean", + "material-theme-palenight", + "min-dark", + "min-light", + "monokai", + "night-owl", + "nord", + "one-dark-pro", + "one-light", + "plain", + "plastic", + "poimandres", + "red", + "rose-pine", + "rose-pine-dawn", + "rose-pine-moon", + "slack-dark", + "slack-ochin", + "snazzy-light", + "solarized-dark", + "solarized-light", + "synthwave-84", + "tokyo-night", + "vesper", + "vitesse-black", + "vitesse-dark", + "vitesse-light", + ], + Var[ + Literal[ + "andromeeda", + "aurora-x", + "ayu-dark", + "catppuccin-frappe", + "catppuccin-latte", + "catppuccin-macchiato", + "catppuccin-mocha", + "dark-plus", + "dracula", + "dracula-soft", + "everforest-dark", + "everforest-light", + "github-dark", + "github-dark-default", + "github-dark-dimmed", + "github-dark-high-contrast", + "github-light", + "github-light-default", + "github-light-high-contrast", + "houston", + "laserwave", + "light-plus", + "material-theme", + "material-theme-darker", + "material-theme-lighter", + "material-theme-ocean", + "material-theme-palenight", + "min-dark", + "min-light", + "monokai", + "night-owl", + "nord", + "one-dark-pro", + "one-light", + "plain", + "plastic", + "poimandres", + "red", + "rose-pine", + "rose-pine-dawn", + "rose-pine-moon", + "slack-dark", + "slack-ochin", + "snazzy-light", + "solarized-dark", + "solarized-light", + "synthwave-84", + "tokyo-night", + "vesper", + "vitesse-black", + "vitesse-dark", + "vitesse-light", + ] + ], + ] + ] = None, + themes: Optional[ + Union[ + Var[Union[dict[str, str], list[dict[str, Any]]]], + dict[str, str], + list[dict[str, Any]], + ] + ] = None, + code: Optional[Union[Var[str], str]] = None, + transformers: Optional[ + Union[ + Var[list[Union[ShikiBaseTransformers, dict[str, Any]]]], + list[Union[ShikiBaseTransformers, dict[str, Any]]], + ] + ] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, str]]] = None, + on_blur: Optional[EventType[[]]] = None, + on_click: Optional[EventType[[]]] = None, + on_context_menu: Optional[EventType[[]]] = None, + on_double_click: Optional[EventType[[]]] = None, + on_focus: Optional[EventType[[]]] = None, + on_mount: Optional[EventType[[]]] = None, + on_mouse_down: Optional[EventType[[]]] = None, + on_mouse_enter: Optional[EventType[[]]] = None, + on_mouse_leave: Optional[EventType[[]]] = None, + on_mouse_move: Optional[EventType[[]]] = None, + on_mouse_out: Optional[EventType[[]]] = None, + on_mouse_over: Optional[EventType[[]]] = None, + on_mouse_up: Optional[EventType[[]]] = None, + on_scroll: Optional[EventType[[]]] = None, + on_unmount: Optional[EventType[[]]] = None, + **props, + ) -> "ShikiHighLevelCodeBlock": + """Create a code block component using [shiki syntax highlighter](https://shiki.matsu.io/). + + Args: + *children: The children of the component. + use_transformers: If this is enabled, the default transformers(shikijs transformer) will be used. + show_line_numbers: If this is enabled line numbers will be shown next to the code block. + can_copy: Whether a copy button should appear. + copy_button: copy_button: A custom copy button to override the default one. + language: The language to use. + theme: The theme to use ("light" or "dark"). + themes: The set of themes to use for different modes. + code: The code to display. + transformers: The transformers to use for the syntax highlighter. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props to pass to the component. + + Returns: + The code block component. + """ + ... + +code_block = CodeblockNamespace() diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py index 1665144fd..b790bf7a1 100644 --- a/reflex/components/markdown/markdown.py +++ b/reflex/components/markdown/markdown.py @@ -20,6 +20,8 @@ from reflex.components.tags.tag import Tag from reflex.utils import types from reflex.utils.imports import ImportDict, ImportVar from reflex.vars.base import LiteralVar, Var +from reflex.vars.function import ARRAY_ISARRAY +from reflex.vars.number import ternary_operation # Special vars used in the component map. _CHILDREN = Var(_js_expr="children", _var_type=str) @@ -199,7 +201,16 @@ class Markdown(Component): raise ValueError(f"No markdown component found for tag: {tag}.") special_props = [_PROPS_IN_TAG] - children = [_CHILDREN] + children = [ + _CHILDREN + if tag != "codeblock" + # For codeblock, the mapping for some cases returns an array of elements. Let's join them into a string. + else ternary_operation( + ARRAY_ISARRAY.call(_CHILDREN), # type: ignore + _CHILDREN.to(list).join("\n"), + _CHILDREN, + ).to(str) + ] # For certain tags, the props from the markdown renderer are not actually valid for the component. if tag in NO_PROPS_TAGS: diff --git a/reflex/config.py b/reflex/config.py index 2e16e2eb0..00f33b653 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -418,6 +418,12 @@ class Config(Base): # Number of gunicorn workers from user gunicorn_workers: Optional[int] = None + # Number of requests before a worker is restarted + gunicorn_max_requests: int = 100 + + # Variance limit for max requests; gunicorn only + gunicorn_max_requests_jitter: int = 25 + # Indicate which type of state manager to use state_manager_mode: constants.StateManagerMode = constants.StateManagerMode.DISK diff --git a/reflex/experimental/__init__.py b/reflex/experimental/__init__.py index 0c11deb85..164790fe5 100644 --- a/reflex/experimental/__init__.py +++ b/reflex/experimental/__init__.py @@ -2,6 +2,7 @@ from types import SimpleNamespace +from reflex.components.datadisplay.shiki_code_block import code_block as code_block from reflex.components.props import PropsBase from reflex.components.radix.themes.components.progress import progress as progress from reflex.components.sonner.toast import toast as toast @@ -67,4 +68,5 @@ _x = ExperimentalNamespace( layout=layout, PropsBase=PropsBase, run_in_thread=run_in_thread, + code_block=code_block, ) diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index f5521c91f..bdc9be4ae 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -337,8 +337,8 @@ def run_uvicorn_backend_prod(host, port, loglevel): app_module = get_app_module() - RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --preload --timeout {config.timeout} --log-level critical".split() - RUN_BACKEND_PROD_WINDOWS = f"uvicorn --timeout-keep-alive {config.timeout}".split() + RUN_BACKEND_PROD = f"gunicorn --worker-class {config.gunicorn_worker_class} --max-requests {config.gunicorn_max_requests} --max-requests-jitter {config.gunicorn_max_requests_jitter} --preload --timeout {config.timeout} --log-level critical".split() + RUN_BACKEND_PROD_WINDOWS = f"uvicorn --limit-max-requests {config.gunicorn_max_requests} --timeout-keep-alive {config.timeout}".split() command = ( [ *RUN_BACKEND_PROD_WINDOWS, diff --git a/reflex/vars/function.py b/reflex/vars/function.py index a1f7fb7bd..9d734a458 100644 --- a/reflex/vars/function.py +++ b/reflex/vars/function.py @@ -180,6 +180,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar): JSON_STRINGIFY = FunctionStringVar.create("JSON.stringify") +ARRAY_ISARRAY = FunctionStringVar.create("Array.isArray") PROTOTYPE_TO_STRING = FunctionStringVar.create( "((__to_string) => __to_string.toString())" ) diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index 149300028..39139ce3f 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -529,6 +529,26 @@ def array_join_operation(array: ArrayVar, sep: StringVar[Any] | str = ""): return var_operation_return(js_expression=f"{array}.join({sep})", var_type=str) +@var_operation +def string_replace_operation( + string: StringVar, search_value: StringVar | str, new_value: StringVar | str +): + """Replace a string with a value. + + Args: + string: The string. + search_value: The string to search. + new_value: The value to be replaced with. + + Returns: + The string replace operation. + """ + return var_operation_return( + js_expression=f"{string}.replace({search_value}, {new_value})", + var_type=str, + ) + + # Compile regex for finding reflex var tags. _decode_var_pattern_re = ( rf"{constants.REFLEX_VAR_OPENING_TAG}(.*?){constants.REFLEX_VAR_CLOSING_TAG}" diff --git a/tests/units/components/datadisplay/test_shiki_code.py b/tests/units/components/datadisplay/test_shiki_code.py new file mode 100644 index 000000000..eb473ba06 --- /dev/null +++ b/tests/units/components/datadisplay/test_shiki_code.py @@ -0,0 +1,172 @@ +import pytest + +from reflex.components.datadisplay.shiki_code_block import ( + ShikiBaseTransformers, + ShikiCodeBlock, + ShikiHighLevelCodeBlock, + ShikiJsTransformer, +) +from reflex.components.el.elements.forms import Button +from reflex.components.lucide.icon import Icon +from reflex.components.radix.themes.layout.box import Box +from reflex.style import Style +from reflex.vars import Var + + +@pytest.mark.parametrize( + "library, fns, expected_output, raises_exception", + [ + ("some_library", ["function_one"], ["function_one"], False), + ("some_library", [123], None, True), + ("some_library", [], [], False), + ( + "some_library", + ["function_one", "function_two"], + ["function_one", "function_two"], + False, + ), + ("", ["function_one"], ["function_one"], False), + ("some_library", ["function_one", 789], None, True), + ("", [], [], False), + ], +) +def test_create_transformer(library, fns, expected_output, raises_exception): + if raises_exception: + # Ensure ValueError is raised for invalid cases + with pytest.raises(ValueError): + ShikiCodeBlock.create_transformer(library, fns) + else: + transformer = ShikiCodeBlock.create_transformer(library, fns) + assert isinstance(transformer, ShikiBaseTransformers) + assert transformer.library == library + + # Verify that the functions are correctly wrapped in FunctionStringVar + function_names = [str(fn) for fn in transformer.fns] + assert function_names == expected_output + + +@pytest.mark.parametrize( + "code_block, children, props, expected_first_child, expected_styles", + [ + ("print('Hello')", ["print('Hello')"], {}, "print('Hello')", {}), + ( + "print('Hello')", + ["print('Hello')", "More content"], + {}, + "print('Hello')", + {}, + ), + ( + "print('Hello')", + ["print('Hello')"], + { + "transformers": [ + ShikiBaseTransformers( + library="lib", fns=[], style=Style({"color": "red"}) + ) + ] + }, + "print('Hello')", + {"color": "red"}, + ), + ( + "print('Hello')", + ["print('Hello')"], + { + "transformers": [ + ShikiBaseTransformers( + library="lib", fns=[], style=Style({"color": "red"}) + ) + ], + "style": {"background": "blue"}, + }, + "print('Hello')", + {"color": "red", "background": "blue"}, + ), + ], +) +def test_create_shiki_code_block( + code_block, children, props, expected_first_child, expected_styles +): + component = ShikiCodeBlock.create(code_block, *children, **props) + + # Test that the created component is a Box + assert isinstance(component, Box) + + # Test that the first child is the code + code_block_component = component.children[0] + assert code_block_component.code._var_value == expected_first_child # type: ignore + + applied_styles = component.style + for key, value in expected_styles.items(): + assert Var.create(applied_styles[key])._var_value == value + + +@pytest.mark.parametrize( + "children, props, expected_transformers, expected_button_type", + [ + (["print('Hello')"], {"use_transformers": True}, [ShikiJsTransformer], None), + (["print('Hello')"], {"can_copy": True}, None, Button), + ( + ["print('Hello')"], + { + "can_copy": True, + "copy_button": Button.create(Icon.create(tag="a_arrow_down")), + }, + None, + Button, + ), + ], +) +def test_create_shiki_high_level_code_block( + children, props, expected_transformers, expected_button_type +): + component = ShikiHighLevelCodeBlock.create(*children, **props) + + # Test that the created component is a Box + assert isinstance(component, Box) + + # Test that the first child is the code block component + code_block_component = component.children[0] + assert code_block_component.code._var_value == children[0] # type: ignore + + # Check if the transformer is set correctly if expected + if expected_transformers: + exp_trans_names = [t.__name__ for t in expected_transformers] + for transformer in code_block_component.transformers._var_value: # type: ignore + assert type(transformer).__name__ in exp_trans_names + + # Check if the second child is the copy button if can_copy is True + if props.get("can_copy", False): + if props.get("copy_button"): + assert isinstance(component.children[1], expected_button_type) + assert component.children[1] == props["copy_button"] + else: + assert isinstance(component.children[1], expected_button_type) + else: + assert len(component.children) == 1 + + +@pytest.mark.parametrize( + "children, props", + [ + (["print('Hello')"], {"theme": "dark"}), + (["print('Hello')"], {"language": "javascript"}), + ], +) +def test_shiki_high_level_code_block_theme_language_mapping(children, props): + component = ShikiHighLevelCodeBlock.create(*children, **props) + + # Test that the theme is mapped correctly + if "theme" in props: + assert component.children[ + 0 + ].theme._var_value == ShikiHighLevelCodeBlock._map_themes(props["theme"]) # type: ignore + + # Test that the language is mapped correctly + if "language" in props: + assert component.children[ + 0 + ].language._var_value == ShikiHighLevelCodeBlock._map_languages( # type: ignore + props["language"] + )