@@ -22,6 +22,8 @@ use syn::{Expr, LitStr, Token, parse_macro_input};
2222/// [`CodeOptions::builder`] with [`CodeOptions::with_language`] to name the
2323/// language explicitly; otherwise it is inferred from the file extension.
2424///
25+ /// To highlight inline source instead of a file, use [`code_str!`].
26+ ///
2527/// [`CodeOptions::builder`]: https://docs.rs/dioxus-code/latest/dioxus_code/struct.CodeOptions.html#method.builder
2628/// [`CodeOptions::with_language`]: https://docs.rs/dioxus-code/latest/dioxus_code/struct.CodeOptions.html#method.with_language
2729#[ proc_macro]
@@ -34,32 +36,74 @@ pub fn code(input: TokenStream) -> TokenStream {
3436 }
3537}
3638
39+ /// Compile-time syntax highlighting of an inline source string.
40+ ///
41+ /// Parses a string literal containing source code with [`arborium`] and
42+ /// expands to the resulting span tree. Pass the source as a string literal,
43+ /// `concat!(...)`, `include_str!(...)`, or `env!(...)`. The language must be
44+ /// supplied via [`CodeOptions::builder`] with [`CodeOptions::with_language`]
45+ /// since there is no file extension to infer from.
46+ ///
47+ /// To highlight a file on disk instead, use [`code!`].
48+ ///
49+ /// [`CodeOptions::builder`]: https://docs.rs/dioxus-code/latest/dioxus_code/struct.CodeOptions.html#method.builder
50+ /// [`CodeOptions::with_language`]: https://docs.rs/dioxus-code/latest/dioxus_code/struct.CodeOptions.html#method.with_language
51+ #[ proc_macro]
52+ pub fn code_str ( input : TokenStream ) -> TokenStream {
53+ let input = parse_macro_input ! ( input as CodeStrInput ) ;
54+
55+ match expand_code_str ( input) {
56+ Ok ( tokens) => tokens. into ( ) ,
57+ Err ( error) => error. to_compile_error ( ) . into ( ) ,
58+ }
59+ }
60+
3761struct CodeInput {
3862 path : String ,
3963 options : Option < Expr > ,
4064}
4165
4266impl Parse for CodeInput {
4367 fn parse ( input : ParseStream < ' _ > ) -> syn:: Result < Self > {
44- let MacroString ( path) = input. parse ( ) ?;
45- let mut options = None ;
68+ let ( path, options) = parse_string_and_options ( input, "code macro" ) ?;
69+ Ok ( Self { path, options } )
70+ }
71+ }
72+
73+ struct CodeStrInput {
74+ source : String ,
75+ options : Option < Expr > ,
76+ }
77+
78+ impl Parse for CodeStrInput {
79+ fn parse ( input : ParseStream < ' _ > ) -> syn:: Result < Self > {
80+ let ( source, options) = parse_string_and_options ( input, "code_str macro" ) ?;
81+ Ok ( Self { source, options } )
82+ }
83+ }
4684
47- if input. peek ( Token ! [ , ] ) {
48- input. parse :: < Token ! [ , ] > ( ) ?;
85+ fn parse_string_and_options (
86+ input : ParseStream < ' _ > ,
87+ macro_label : & str ,
88+ ) -> syn:: Result < ( String , Option < Expr > ) > {
89+ let MacroString ( value) = input. parse ( ) ?;
90+ let mut options = None ;
91+
92+ if input. peek ( Token ! [ , ] ) {
93+ input. parse :: < Token ! [ , ] > ( ) ?;
94+ if !input. is_empty ( ) {
95+ let expr: Expr = input. parse ( ) ?;
96+ if input. peek ( Token ! [ , ] ) {
97+ input. parse :: < Token ! [ , ] > ( ) ?;
98+ }
4999 if !input. is_empty ( ) {
50- let expr: Expr = input. parse ( ) ?;
51- if input. peek ( Token ! [ , ] ) {
52- input. parse :: < Token ! [ , ] > ( ) ?;
53- }
54- if !input. is_empty ( ) {
55- return Err ( input. error ( "unexpected tokens after code macro options" ) ) ;
56- }
57- options = Some ( expr) ;
100+ return Err ( input. error ( format ! ( "unexpected tokens after {macro_label} options" ) ) ) ;
58101 }
102+ options = Some ( expr) ;
59103 }
60-
61- Ok ( Self { path, options } )
62104 }
105+
106+ Ok ( ( value, options) )
63107}
64108
65109fn try_extract_language ( expr : & Expr ) -> Option < String > {
@@ -238,13 +282,7 @@ fn expand_code(input: CodeInput) -> syn::Result<TokenStream2> {
238282 let absolute_path = resolve_manifest_path ( & manifest_dir, & macro_path) ;
239283 let crate_path = dioxus_code_crate_path ( ) ?;
240284
241- let options_check = input. options . as_ref ( ) . map ( |expr| {
242- quote_spanned ! { expr. span( ) =>
243- const _: fn ( ) = || {
244- let _: #crate_path:: CodeOptions = #expr;
245- } ;
246- }
247- } ) ;
285+ let options_check = options_check_tokens ( & crate_path, input. options . as_ref ( ) ) ;
248286
249287 let source = fs:: read_to_string ( & absolute_path) . map_err ( |error| {
250288 syn:: Error :: new (
@@ -268,21 +306,68 @@ fn expand_code(input: CodeInput) -> syn::Result<TokenStream2> {
268306 } } ) ;
269307 } ;
270308
309+ let absolute_lit = LitStr :: new ( & absolute_path. to_string_lossy ( ) , Span :: call_site ( ) ) ;
310+ let source_decl = quote ! { const SOURCE : & str = include_str!( #absolute_lit) ; } ;
311+
312+ expand_highlighted_source ( & crate_path, options_check, source_decl, & language, & source)
313+ }
314+
315+ fn expand_code_str ( input : CodeStrInput ) -> syn:: Result < TokenStream2 > {
316+ let crate_path = dioxus_code_crate_path ( ) ?;
317+ let options_check = options_check_tokens ( & crate_path, input. options . as_ref ( ) ) ;
318+
319+ let Some ( language) = input. options . as_ref ( ) . and_then ( try_extract_language) else {
320+ let message =
321+ "could not determine language for `code_str!`; pass `CodeOptions::builder().with_language(Language::Rust)`" ;
322+ return Ok ( quote ! { {
323+ #options_check
324+ compile_error!( #message) ;
325+ } } ) ;
326+ } ;
327+
328+ let source_lit = LitStr :: new ( & input. source , Span :: call_site ( ) ) ;
329+ let source_decl = quote ! { const SOURCE : & str = #source_lit; } ;
330+
331+ expand_highlighted_source (
332+ & crate_path,
333+ options_check,
334+ source_decl,
335+ & language,
336+ & input. source ,
337+ )
338+ }
339+
340+ fn options_check_tokens ( crate_path : & TokenStream2 , options : Option < & Expr > ) -> Option < TokenStream2 > {
341+ options. map ( |expr| {
342+ quote_spanned ! { expr. span( ) =>
343+ const _: fn ( ) = || {
344+ let _: #crate_path:: CodeOptions = #expr;
345+ } ;
346+ }
347+ } )
348+ }
349+
350+ fn expand_highlighted_source (
351+ crate_path : & TokenStream2 ,
352+ options_check : Option < TokenStream2 > ,
353+ source_decl : TokenStream2 ,
354+ language : & str ,
355+ source : & str ,
356+ ) -> syn:: Result < TokenStream2 > {
271357 let mut highlighter = arborium:: Highlighter :: new ( ) ;
272358 let spans = highlighter
273- . highlight_spans ( & language, & source)
359+ . highlight_spans ( language, source)
274360 . map_err ( |error| syn:: Error :: new ( Span :: call_site ( ) , error. to_string ( ) ) ) ?;
275361
276- let Some ( variant) = language_variant_for_slug ( & language) else {
362+ let Some ( variant) = language_variant_for_slug ( language) else {
277363 let message = format ! ( "language `{language}` has no `Language` variant" ) ;
278364 return Ok ( quote ! { {
279365 #options_check
280366 compile_error!( #message) ;
281367 } } ) ;
282368 } ;
283369 let variant_ident = Ident :: new ( variant, Span :: call_site ( ) ) ;
284- let absolute_lit = LitStr :: new ( & absolute_path. to_string_lossy ( ) , Span :: call_site ( ) ) ;
285- let spans = normalize_spans ( spans) . into_iter ( ) . map ( |span| {
370+ let span_tokens = normalize_spans ( spans) . into_iter ( ) . map ( |span| {
286371 let start = span. start ;
287372 let end = span. end ;
288373 let tag = LitStr :: new ( span. tag , Span :: call_site ( ) ) ;
@@ -294,8 +379,8 @@ fn expand_code(input: CodeInput) -> syn::Result<TokenStream2> {
294379
295380 Ok ( quote ! { {
296381 #options_check
297- const SOURCE : & str = include_str! ( #absolute_lit ) ;
298- static SPANS : & [ #crate_path:: advanced:: HighlightSpan ] = & [ #( #spans ) , * ] ;
382+ #source_decl
383+ static SPANS : & [ #crate_path:: advanced:: HighlightSpan ] = & [ #( #span_tokens ) , * ] ;
299384 #crate_path:: advanced:: HighlightedSource :: from_static_parts(
300385 SOURCE ,
301386 #crate_path:: Language :: #variant_ident,
0 commit comments