Skip to content

Commit a968231

Browse files
[ENG-3892]Shiki codeblock support decorations (#4234)
* Shiki codeblock support decorations * add decorations to useEffect * fix pyright * validate decorations dict * Fix exception message plus unit tests * possible test fix * fix pyright * possible tests fix * cast decorations before creating codeblock * `plain` is not a valid theme * pyi fix * address PR comment
1 parent 84b0864 commit a968231

File tree

8 files changed

+193
-66
lines changed

8 files changed

+193
-66
lines changed

reflex/.templates/web/components/shiki/code.js

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
import { useEffect, useState } from "react"
22
import { codeToHtml} from "shiki"
33

4-
export function Code ({code, theme, language, transformers, ...divProps}) {
4+
/**
5+
* Code component that uses Shiki to convert code to HTML and render it.
6+
*
7+
* @param code - The code to be highlighted.
8+
* @param theme - The theme to be used for highlighting.
9+
* @param language - The language of the code.
10+
* @param transformers - The transformers to be applied to the code.
11+
* @param decorations - The decorations to be applied to the code.
12+
* @param divProps - Additional properties to be passed to the div element.
13+
* @returns The rendered code block.
14+
*/
15+
export function Code ({code, theme, language, transformers, decorations, ...divProps}) {
516
const [codeResult, setCodeResult] = useState("")
617
useEffect(() => {
718
async function fetchCode() {
8-
let final_code;
9-
10-
if (Array.isArray(code)) {
11-
final_code = code[0];
12-
} else {
13-
final_code = code;
14-
}
15-
const result = await codeToHtml(final_code, {
19+
const result = await codeToHtml(code, {
1620
lang: language,
1721
theme,
18-
transformers
22+
transformers,
23+
decorations
1924
});
2025
setCodeResult(result);
2126
}
2227
fetchCode();
23-
}, [code, language, theme, transformers]
28+
}, [code, language, theme, transformers, decorations]
2429

2530
)
2631
return (

reflex/components/datadisplay/shiki_code_block.py

+36-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from reflex.components.core.cond import color_mode_cond
1313
from reflex.components.el.elements.forms import Button
1414
from reflex.components.lucide.icon import Icon
15+
from reflex.components.props import NoExtrasAllowedProps
1516
from reflex.components.radix.themes.layout.box import Box
1617
from reflex.event import call_script, set_clipboard
1718
from reflex.style import Style
@@ -253,6 +254,7 @@ def copy_script() -> Any:
253254
"pascal",
254255
"perl",
255256
"php",
257+
"plain",
256258
"plsql",
257259
"po",
258260
"postcss",
@@ -369,10 +371,11 @@ def copy_script() -> Any:
369371
"nord",
370372
"one-dark-pro",
371373
"one-light",
372-
"plain",
373374
"plastic",
374375
"poimandres",
375376
"red",
377+
# rose-pine themes dont work with the current version of shikijs transformers
378+
# https://github.com/shikijs/shiki/issues/730
376379
"rose-pine",
377380
"rose-pine-dawn",
378381
"rose-pine-moon",
@@ -390,6 +393,23 @@ def copy_script() -> Any:
390393
]
391394

392395

396+
class Position(NoExtrasAllowedProps):
397+
"""Position of the decoration."""
398+
399+
line: int
400+
character: int
401+
402+
403+
class ShikiDecorations(NoExtrasAllowedProps):
404+
"""Decorations for the code block."""
405+
406+
start: Union[int, Position]
407+
end: Union[int, Position]
408+
tag_name: str = "span"
409+
properties: dict[str, Any] = {}
410+
always_wrap: bool = False
411+
412+
393413
class ShikiBaseTransformers(Base):
394414
"""Base for creating transformers."""
395415

@@ -537,6 +557,9 @@ class ShikiCodeBlock(Component):
537557
[]
538558
)
539559

560+
# The decorations to use for the syntax highlighter.
561+
decorations: Var[list[ShikiDecorations]] = Var.create([])
562+
540563
@classmethod
541564
def create(
542565
cls,
@@ -555,6 +578,7 @@ def create(
555578
# Separate props for the code block and the wrapper
556579
code_block_props = {}
557580
code_wrapper_props = {}
581+
decorations = props.pop("decorations", [])
558582

559583
class_props = cls.get_props()
560584

@@ -564,6 +588,15 @@ def create(
564588
value
565589
)
566590

591+
# cast decorations into ShikiDecorations.
592+
decorations = [
593+
ShikiDecorations(**decoration)
594+
if not isinstance(decoration, ShikiDecorations)
595+
else decoration
596+
for decoration in decorations
597+
]
598+
code_block_props["decorations"] = decorations
599+
567600
code_block_props["code"] = children[0]
568601
code_block = super().create(**code_block_props)
569602

@@ -676,10 +709,10 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
676709
show_line_numbers: Var[bool]
677710

678711
# Whether a copy button should appear.
679-
can_copy: Var[bool] = Var.create(False)
712+
can_copy: bool = False
680713

681714
# copy_button: A custom copy button to override the default one.
682-
copy_button: Var[Optional[Union[Component, bool]]] = Var.create(None)
715+
copy_button: Optional[Union[Component, bool]] = None
683716

684717
@classmethod
685718
def create(

reflex/components/datadisplay/shiki_code_block.pyi

+35-15
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ from typing import Any, Dict, Literal, Optional, Union, overload
77

88
from reflex.base import Base
99
from reflex.components.component import Component, ComponentNamespace
10+
from reflex.components.props import NoExtrasAllowedProps
1011
from reflex.event import EventType
1112
from reflex.style import Style
1213
from reflex.vars.base import Var
@@ -192,6 +193,7 @@ LiteralCodeLanguage = Literal[
192193
"pascal",
193194
"perl",
194195
"php",
196+
"plain",
195197
"plsql",
196198
"po",
197199
"postcss",
@@ -308,7 +310,6 @@ LiteralCodeTheme = Literal[
308310
"nord",
309311
"one-dark-pro",
310312
"one-light",
311-
"plain",
312313
"plastic",
313314
"poimandres",
314315
"red",
@@ -328,6 +329,17 @@ LiteralCodeTheme = Literal[
328329
"vitesse-light",
329330
]
330331

332+
class Position(NoExtrasAllowedProps):
333+
line: int
334+
character: int
335+
336+
class ShikiDecorations(NoExtrasAllowedProps):
337+
start: Union[int, Position]
338+
end: Union[int, Position]
339+
tag_name: str
340+
properties: dict[str, Any]
341+
always_wrap: bool
342+
331343
class ShikiBaseTransformers(Base):
332344
library: str
333345
fns: list[FunctionStringVar]
@@ -479,6 +491,7 @@ class ShikiCodeBlock(Component):
479491
"pascal",
480492
"perl",
481493
"php",
494+
"plain",
482495
"plsql",
483496
"po",
484497
"postcss",
@@ -694,6 +707,7 @@ class ShikiCodeBlock(Component):
694707
"pascal",
695708
"perl",
696709
"php",
710+
"plain",
697711
"plsql",
698712
"po",
699713
"postcss",
@@ -815,7 +829,6 @@ class ShikiCodeBlock(Component):
815829
"nord",
816830
"one-dark-pro",
817831
"one-light",
818-
"plain",
819832
"plastic",
820833
"poimandres",
821834
"red",
@@ -870,7 +883,6 @@ class ShikiCodeBlock(Component):
870883
"nord",
871884
"one-dark-pro",
872885
"one-light",
873-
"plain",
874886
"plastic",
875887
"poimandres",
876888
"red",
@@ -906,6 +918,9 @@ class ShikiCodeBlock(Component):
906918
list[Union[ShikiBaseTransformers, dict[str, Any]]],
907919
]
908920
] = None,
921+
decorations: Optional[
922+
Union[Var[list[ShikiDecorations]], list[ShikiDecorations]]
923+
] = None,
909924
style: Optional[Style] = None,
910925
key: Optional[Any] = None,
911926
id: Optional[Any] = None,
@@ -938,6 +953,7 @@ class ShikiCodeBlock(Component):
938953
themes: The set of themes to use for different modes.
939954
code: The code to display.
940955
transformers: The transformers to use for the syntax highlighter.
956+
decorations: The decorations to use for the syntax highlighter.
941957
style: The style of the component.
942958
key: A unique key for the component.
943959
id: The id for the component.
@@ -965,10 +981,8 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
965981
*children,
966982
use_transformers: Optional[Union[Var[bool], bool]] = None,
967983
show_line_numbers: Optional[Union[Var[bool], bool]] = None,
968-
can_copy: Optional[Union[Var[bool], bool]] = None,
969-
copy_button: Optional[
970-
Union[Component, Var[Optional[Union[Component, bool]]], bool]
971-
] = None,
984+
can_copy: Optional[bool] = None,
985+
copy_button: Optional[Union[Component, bool]] = None,
972986
language: Optional[
973987
Union[
974988
Literal[
@@ -1104,6 +1118,7 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
11041118
"pascal",
11051119
"perl",
11061120
"php",
1121+
"plain",
11071122
"plsql",
11081123
"po",
11091124
"postcss",
@@ -1319,6 +1334,7 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
13191334
"pascal",
13201335
"perl",
13211336
"php",
1337+
"plain",
13221338
"plsql",
13231339
"po",
13241340
"postcss",
@@ -1440,7 +1456,6 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
14401456
"nord",
14411457
"one-dark-pro",
14421458
"one-light",
1443-
"plain",
14441459
"plastic",
14451460
"poimandres",
14461461
"red",
@@ -1495,7 +1510,6 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
14951510
"nord",
14961511
"one-dark-pro",
14971512
"one-light",
1498-
"plain",
14991513
"plastic",
15001514
"poimandres",
15011515
"red",
@@ -1531,6 +1545,9 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
15311545
list[Union[ShikiBaseTransformers, dict[str, Any]]],
15321546
]
15331547
] = None,
1548+
decorations: Optional[
1549+
Union[Var[list[ShikiDecorations]], list[ShikiDecorations]]
1550+
] = None,
15341551
style: Optional[Style] = None,
15351552
key: Optional[Any] = None,
15361553
id: Optional[Any] = None,
@@ -1567,6 +1584,7 @@ class ShikiHighLevelCodeBlock(ShikiCodeBlock):
15671584
themes: The set of themes to use for different modes.
15681585
code: The code to display.
15691586
transformers: The transformers to use for the syntax highlighter.
1587+
decorations: The decorations to use for the syntax highlighter.
15701588
style: The style of the component.
15711589
key: A unique key for the component.
15721590
id: The id for the component.
@@ -1593,10 +1611,8 @@ class CodeblockNamespace(ComponentNamespace):
15931611
*children,
15941612
use_transformers: Optional[Union[Var[bool], bool]] = None,
15951613
show_line_numbers: Optional[Union[Var[bool], bool]] = None,
1596-
can_copy: Optional[Union[Var[bool], bool]] = None,
1597-
copy_button: Optional[
1598-
Union[Component, Var[Optional[Union[Component, bool]]], bool]
1599-
] = None,
1614+
can_copy: Optional[bool] = None,
1615+
copy_button: Optional[Union[Component, bool]] = None,
16001616
language: Optional[
16011617
Union[
16021618
Literal[
@@ -1732,6 +1748,7 @@ class CodeblockNamespace(ComponentNamespace):
17321748
"pascal",
17331749
"perl",
17341750
"php",
1751+
"plain",
17351752
"plsql",
17361753
"po",
17371754
"postcss",
@@ -1947,6 +1964,7 @@ class CodeblockNamespace(ComponentNamespace):
19471964
"pascal",
19481965
"perl",
19491966
"php",
1967+
"plain",
19501968
"plsql",
19511969
"po",
19521970
"postcss",
@@ -2068,7 +2086,6 @@ class CodeblockNamespace(ComponentNamespace):
20682086
"nord",
20692087
"one-dark-pro",
20702088
"one-light",
2071-
"plain",
20722089
"plastic",
20732090
"poimandres",
20742091
"red",
@@ -2123,7 +2140,6 @@ class CodeblockNamespace(ComponentNamespace):
21232140
"nord",
21242141
"one-dark-pro",
21252142
"one-light",
2126-
"plain",
21272143
"plastic",
21282144
"poimandres",
21292145
"red",
@@ -2159,6 +2175,9 @@ class CodeblockNamespace(ComponentNamespace):
21592175
list[Union[ShikiBaseTransformers, dict[str, Any]]],
21602176
]
21612177
] = None,
2178+
decorations: Optional[
2179+
Union[Var[list[ShikiDecorations]], list[ShikiDecorations]]
2180+
] = None,
21622181
style: Optional[Style] = None,
21632182
key: Optional[Any] = None,
21642183
id: Optional[Any] = None,
@@ -2195,6 +2214,7 @@ class CodeblockNamespace(ComponentNamespace):
21952214
themes: The set of themes to use for different modes.
21962215
code: The code to display.
21972216
transformers: The transformers to use for the syntax highlighter.
2217+
decorations: The decorations to use for the syntax highlighter.
21982218
style: The style of the component.
21992219
key: A unique key for the component.
22002220
id: The id for the component.

0 commit comments

Comments
 (0)