Skip to content

Commit a062bbc

Browse files
committed
Move rendering code to backend-specific modules
1 parent b608561 commit a062bbc

File tree

5 files changed

+269
-245
lines changed

5 files changed

+269
-245
lines changed

src/formatting/renderer.rs

Lines changed: 0 additions & 245 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
//! Renderers for colourizing Technique language
22
33
use crate::language::*;
4-
use owo_colors::OwoColorize;
5-
use std::borrow::Cow;
64

75
/// Types of content that can be rendered with different styles
86
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -50,162 +48,6 @@ impl Render for Identity {
5048
}
5149
}
5250

53-
/// Embellish fragments with ANSI escapes to create syntax highlighting in
54-
/// terminal output.
55-
pub struct Terminal;
56-
57-
impl Render for Terminal {
58-
fn render(&self, syntax: Syntax, content: &str) -> String {
59-
match syntax {
60-
Syntax::Neutral => content.to_string(),
61-
Syntax::Indent => content.to_string(),
62-
Syntax::Newline => "\n".to_string(),
63-
Syntax::Header => content
64-
.color(owo_colors::Rgb(0x75, 0x50, 0x7b))
65-
.to_string(),
66-
Syntax::Declaration => content // entity.name.function - #3465a4 (blue) bold
67-
.color(owo_colors::Rgb(0x34, 0x65, 0xa4))
68-
.bold()
69-
.to_string(),
70-
Syntax::Forma => content // entity.name.type.technique - #8f5902 (brown) bold
71-
.color(owo_colors::Rgb(0x8f, 0x59, 0x02))
72-
.bold()
73-
.to_string(),
74-
Syntax::Description => content.to_string(),
75-
Syntax::StepItem => content // markup.list.numbered/unnumbered - #000000 bold
76-
.bright_white()
77-
.bold()
78-
.to_string(),
79-
Syntax::CodeBlock => content // punctuation.section.braces - #999999 bold
80-
.color(owo_colors::Rgb(153, 153, 153))
81-
.bold()
82-
.to_string(),
83-
Syntax::Variable => content // variable.parameter.technique - #729fcf (light blue) bold
84-
.color(owo_colors::Rgb(0x72, 0x9f, 0xcf))
85-
.bold()
86-
.to_string(),
87-
Syntax::Section => content // markup.heading.technique
88-
.to_string(),
89-
Syntax::String => content // string - #4e9a06 (green) bold
90-
.color(owo_colors::Rgb(0x4e, 0x9a, 0x06))
91-
.bold()
92-
.to_string(),
93-
Syntax::Numeric => content // constant.numeric - #ad7fa8 (purple) bold
94-
.color(owo_colors::Rgb(0xad, 0x7f, 0xa8))
95-
.bold()
96-
.to_string(),
97-
Syntax::Response => content // string.quoted.single.technique
98-
.color(owo_colors::Rgb(0xf5, 0x79, 0x00))
99-
.bold()
100-
.to_string(),
101-
Syntax::Invocation => content // meta.function-call.technique
102-
.color(owo_colors::Rgb(0x3b, 0x5d, 0x7d))
103-
.bold()
104-
.to_string(),
105-
Syntax::Title => content // markup.heading.technique - #000000 bold
106-
.bright_white()
107-
.bold()
108-
.to_string(),
109-
Syntax::Keyword => content // keyword.control.technique
110-
.color(owo_colors::Rgb(0x75, 0x50, 0x7b))
111-
.bold()
112-
.to_string(),
113-
Syntax::Function => content // entity.name.function.technique - #3465a4 (blue) bold
114-
.color(owo_colors::Rgb(52, 101, 164))
115-
.bold()
116-
.to_string(),
117-
Syntax::Multiline => content // string.multiline.technique - #4e9a06 (green)
118-
.color(owo_colors::Rgb(0x4e, 0x9a, 0x06))
119-
.bold()
120-
.to_string(),
121-
Syntax::Label => content // variable.other.tablet
122-
.color(owo_colors::Rgb(0x60, 0x98, 0x9a))
123-
.bold()
124-
.to_string(),
125-
Syntax::Operator => content // keyword.operator.technique - #cc0000 (red) bold
126-
.color(owo_colors::Rgb(204, 0, 0))
127-
.bold()
128-
.to_string(),
129-
Syntax::Quote => content // punctuation.technique - #999999 (grey)
130-
.color(owo_colors::Rgb(0x99, 0x99, 0x99))
131-
.bold()
132-
.to_string(),
133-
Syntax::Language => content // storage.type.embedded
134-
.color(owo_colors::Rgb(0xc4, 0xa0, 0x00))
135-
.bold()
136-
.to_string(),
137-
Syntax::Attribute => content // entity.name.tag.attribute
138-
.bright_white()
139-
.bold()
140-
.to_string(),
141-
Syntax::Structure => content
142-
.color(owo_colors::Rgb(153, 153, 153))
143-
.bold()
144-
.to_string(),
145-
}
146-
}
147-
}
148-
149-
/// Add markup around syntactic elements for use when including
150-
/// Technique source in Typst documents.
151-
pub struct Typst;
152-
153-
impl Render for Typst {
154-
fn render(&self, syntax: Syntax, content: &str) -> String {
155-
let content = escape_typst(content);
156-
match syntax {
157-
Syntax::Neutral => markup("", &content),
158-
Syntax::Indent => markup("", &content),
159-
Syntax::Newline => "\\\n".to_string(),
160-
Syntax::Header => markup("fill: rgb(0x75, 0x50, 0x7b)", &content),
161-
Syntax::Declaration => {
162-
markup("fill: rgb(0x34, 0x65, 0xa4), weight: \"bold\"", &content)
163-
}
164-
Syntax::Description => markup("", &content),
165-
Syntax::Forma => markup("fill: rgb(0x8f, 0x59, 0x02), weight: \"bold\"", &content),
166-
Syntax::StepItem => markup("weight: \"bold\"", &content),
167-
Syntax::CodeBlock => markup("fill: rgb(0x99, 0x99, 0x99), weight: \"bold\"", &content),
168-
Syntax::Variable => markup("fill: rgb(0x72, 0x9f, 0xcf), weight: \"bold\"", &content),
169-
Syntax::Section => markup("", &content),
170-
Syntax::String => markup("fill: rgb(0x4e, 0x9a, 0x06), weight: \"bold\"", &content),
171-
Syntax::Numeric => markup("fill: rgb(0xad, 0x7f, 0xa8), weight: \"bold\"", &content),
172-
Syntax::Response => markup("fill: rgb(0xf5, 0x79, 0x00), weight: \"bold\"", &content),
173-
Syntax::Invocation => markup("fill: rgb(0x3b, 0x5d, 0x7d), weight: \"bold\"", &content),
174-
Syntax::Title => markup("weight: \"bold\"", &content),
175-
Syntax::Keyword => markup("fill: rgb(0x75, 0x50, 0x7b), weight: \"bold\"", &content),
176-
Syntax::Function => markup("fill: rgb(0x34, 0x65, 0xa4), weight: \"bold\"", &content),
177-
Syntax::Multiline => markup("fill: rgb(0x4e, 0x9a, 0x06), weight: \"bold\"", &content),
178-
Syntax::Label => markup("fill: rgb(0x60, 0x98, 0x9a), weight: \"bold\"", &content),
179-
Syntax::Operator => markup("fill: red", &content),
180-
Syntax::Quote => markup("fill: rgb(0x99, 0x99, 0x99), weight: \"bold\"", &content),
181-
Syntax::Language => markup("fill: rgb(0xc4, 0xa0, 0x00), weight: \"bold\"", &content),
182-
Syntax::Attribute => markup("weight: \"bold\"", &content),
183-
Syntax::Structure => markup("fill: rgb(0x99, 0x99, 0x99), weight: \"bold\"", &content),
184-
}
185-
}
186-
}
187-
188-
fn escape_typst(content: &str) -> Cow<str> {
189-
if content.contains('"') {
190-
Cow::Owned(content.replace("\"", "\\\""))
191-
} else {
192-
Cow::Borrowed(content)
193-
}
194-
}
195-
196-
fn markup(prefix: &str, content: &Cow<str>) -> String {
197-
let mut result = String::with_capacity(6 + prefix.len() + 2 + 5 + content.len() + 3);
198-
result.push_str("#text(");
199-
if prefix.len() > 0 {
200-
result.push_str(prefix);
201-
result.push_str(", ");
202-
}
203-
result.push_str("raw(\"");
204-
result.push_str(content);
205-
result.push_str("\"))");
206-
result
207-
}
208-
20951
/// We do the code formatting in two passes. First we convert from our
21052
/// Abstract Syntax Tree types into a Vec of "fragments" (Syntax tag, String
21153
/// pairs). Then second we apply the specified renderer to each pair to result
@@ -241,90 +83,3 @@ fn render_to_string(renderer: &impl Render, fragments: Vec<(Syntax, String)>) ->
24183

24284
output
24385
}
244-
245-
#[cfg(test)]
246-
mod tests {
247-
use super::*;
248-
249-
#[test]
250-
fn escape_typst_no_allocation_when_no_quotes() {
251-
let input = "hello world";
252-
let result = escape_typst(input);
253-
254-
// Should return borrowed reference when no quotes to escape
255-
assert!(matches!(result, Cow::Borrowed(_)));
256-
assert_eq!(result, "hello world");
257-
}
258-
259-
#[test]
260-
fn escape_typst_allocates_when_quotes_present() {
261-
let input = "hello \"world\"";
262-
let result = escape_typst(input);
263-
264-
// Should return owned string when quotes need escaping
265-
assert!(matches!(result, Cow::Owned(_)));
266-
assert_eq!(result, "hello \\\"world\\\"");
267-
}
268-
269-
#[test]
270-
fn build_typst_markup_efficiently() {
271-
// Test that build_typst_markup works correctly
272-
let content = Cow::Borrowed("test content");
273-
let result = markup("color: red", &content);
274-
assert_eq!(result, "#text(color: red, raw(\"test content\"))");
275-
276-
// Test with escaped content
277-
let content = Cow::Owned("escaped \"content\"".to_string());
278-
let result = markup("", &content);
279-
assert_eq!(result, "#text(raw(\"escaped \"content\"\"))");
280-
}
281-
282-
#[test]
283-
fn typst_newline_and_indent_rendering() {
284-
let typst = Typst;
285-
286-
// Test that newlines are rendered as Typst line breaks
287-
let newline_result = typst.render(Syntax::Newline, "\n");
288-
assert_eq!(newline_result, "\\\n");
289-
290-
// Test that indentation is rendered without raw() wrapper
291-
let indent_result = typst.render(Syntax::Indent, " ");
292-
assert_eq!(indent_result, "#text(raw(\" \"))");
293-
294-
// Test that this is different from Neutral (which would wrap newlines in raw())
295-
let neutral_result = typst.render(Syntax::Neutral, "\n ");
296-
assert_eq!(neutral_result, "#text(raw(\"\n \"))");
297-
298-
// Verify the improvement: newlines no longer wrapped in raw()
299-
assert_ne!(newline_result, "#text(raw(\"\n\"))");
300-
}
301-
302-
#[test]
303-
fn verify_typst_fragments_usage() {
304-
// Simple test to verify that our new Syntax variants are used correctly
305-
let fragments = vec![
306-
(Syntax::Header, "% technique v1".to_string()),
307-
(Syntax::Newline, "\n".to_string()),
308-
(Syntax::Indent, " ".to_string()),
309-
(Syntax::StepItem, "1".to_string()),
310-
(Syntax::Neutral, ".".to_string()),
311-
(Syntax::Newline, "\n".to_string()),
312-
];
313-
314-
let typst = Typst;
315-
let mut output = String::new();
316-
317-
for (syntax, content) in fragments {
318-
let rendered = typst.render(syntax, &content);
319-
output.push_str(&rendered);
320-
}
321-
322-
// Verify improvements:
323-
// 1. Newlines are rendered as Typst line breaks
324-
assert!(output.contains("\\\n"));
325-
// 2. Indentation is wrapped in text() but not combined with newlines
326-
assert!(output.contains("#text(raw(\" \"))"));
327-
// 3. No raw() calls containing newlines
328-
assert!(!output.contains("raw(\"\n"));
329-
}
330-
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use clap::value_parser;
22
use clap::{Arg, ArgAction, Command};
33
use owo_colors::OwoColorize;
4+
use rendering::{Terminal, Typst};
45
use std::io::IsTerminal;
56
use std::path::Path;
67
use tracing::debug;

src/rendering/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ use std::process::{Command, Stdio};
55
use tinytemplate::TinyTemplate;
66
use tracing::{debug, info};
77

8+
mod terminal;
9+
mod typst;
10+
11+
pub use terminal::Terminal;
12+
pub use typst::Typst;
13+
814
static TEMPLATE: &'static str = r#"
915
#show text: set text(font: "Inconsolata")
1016
#show raw: set block(breakable: true)

src/rendering/terminal.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//! Renderers for colourizing Technique language
2+
3+
use owo_colors::OwoColorize;
4+
use technique::formatting::*;
5+
6+
/// Embellish fragments with ANSI escapes to create syntax highlighting in
7+
/// terminal output.
8+
pub struct Terminal;
9+
10+
impl Render for Terminal {
11+
fn render(&self, syntax: Syntax, content: &str) -> String {
12+
match syntax {
13+
Syntax::Neutral => content.to_string(),
14+
Syntax::Indent => content.to_string(),
15+
Syntax::Newline => "\n".to_string(),
16+
Syntax::Header => content
17+
.color(owo_colors::Rgb(0x75, 0x50, 0x7b))
18+
.to_string(),
19+
Syntax::Declaration => content // entity.name.function - #3465a4 (blue) bold
20+
.color(owo_colors::Rgb(0x34, 0x65, 0xa4))
21+
.bold()
22+
.to_string(),
23+
Syntax::Forma => content // entity.name.type.technique - #8f5902 (brown) bold
24+
.color(owo_colors::Rgb(0x8f, 0x59, 0x02))
25+
.bold()
26+
.to_string(),
27+
Syntax::Description => content.to_string(),
28+
Syntax::StepItem => content // markup.list.numbered/unnumbered - #000000 bold
29+
.bright_white()
30+
.bold()
31+
.to_string(),
32+
Syntax::CodeBlock => content // punctuation.section.braces - #999999 bold
33+
.color(owo_colors::Rgb(153, 153, 153))
34+
.bold()
35+
.to_string(),
36+
Syntax::Variable => content // variable.parameter.technique - #729fcf (light blue) bold
37+
.color(owo_colors::Rgb(0x72, 0x9f, 0xcf))
38+
.bold()
39+
.to_string(),
40+
Syntax::Section => content // markup.heading.technique
41+
.to_string(),
42+
Syntax::String => content // string - #4e9a06 (green) bold
43+
.color(owo_colors::Rgb(0x4e, 0x9a, 0x06))
44+
.bold()
45+
.to_string(),
46+
Syntax::Numeric => content // constant.numeric - #ad7fa8 (purple) bold
47+
.color(owo_colors::Rgb(0xad, 0x7f, 0xa8))
48+
.bold()
49+
.to_string(),
50+
Syntax::Response => content // string.quoted.single.technique
51+
.color(owo_colors::Rgb(0xf5, 0x79, 0x00))
52+
.bold()
53+
.to_string(),
54+
Syntax::Invocation => content // meta.function-call.technique
55+
.color(owo_colors::Rgb(0x3b, 0x5d, 0x7d))
56+
.bold()
57+
.to_string(),
58+
Syntax::Title => content // markup.heading.technique - #000000 bold
59+
.bright_white()
60+
.bold()
61+
.to_string(),
62+
Syntax::Keyword => content // keyword.control.technique
63+
.color(owo_colors::Rgb(0x75, 0x50, 0x7b))
64+
.bold()
65+
.to_string(),
66+
Syntax::Function => content // entity.name.function.technique - #3465a4 (blue) bold
67+
.color(owo_colors::Rgb(52, 101, 164))
68+
.bold()
69+
.to_string(),
70+
Syntax::Multiline => content // string.multiline.technique - #4e9a06 (green)
71+
.color(owo_colors::Rgb(0x4e, 0x9a, 0x06))
72+
.bold()
73+
.to_string(),
74+
Syntax::Label => content // variable.other.tablet
75+
.color(owo_colors::Rgb(0x60, 0x98, 0x9a))
76+
.bold()
77+
.to_string(),
78+
Syntax::Operator => content // keyword.operator.technique - #cc0000 (red) bold
79+
.color(owo_colors::Rgb(204, 0, 0))
80+
.bold()
81+
.to_string(),
82+
Syntax::Quote => content // punctuation.technique - #999999 (grey)
83+
.color(owo_colors::Rgb(0x99, 0x99, 0x99))
84+
.bold()
85+
.to_string(),
86+
Syntax::Language => content // storage.type.embedded
87+
.color(owo_colors::Rgb(0xc4, 0xa0, 0x00))
88+
.bold()
89+
.to_string(),
90+
Syntax::Attribute => content // entity.name.tag.attribute
91+
.bright_white()
92+
.bold()
93+
.to_string(),
94+
Syntax::Structure => content
95+
.color(owo_colors::Rgb(153, 153, 153))
96+
.bold()
97+
.to_string(),
98+
}
99+
}
100+
}
101+
102+
#[cfg(test)]
103+
mod check {
104+
use super::*;
105+
106+
#[test]
107+
fn basic_handling() {
108+
let result = Terminal.render(Syntax::Neutral, "hello world");
109+
assert_eq!(result, "hello world");
110+
}
111+
}

0 commit comments

Comments
 (0)