Skip to content

Commit f6d16b7

Browse files
authored
Add CSS and JS syntax highlighting (#266)
* add javascript syntax highlighting, unify all highlighters * add css syntax highlighting
1 parent 0455f05 commit f6d16b7

File tree

10 files changed

+252
-55
lines changed

10 files changed

+252
-55
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## Unreleased: mitmproxy_rs next
22

3+
- Add JS syntax highlighting.
4+
- Add CSS syntax highlighting.
35

46
## 30 May 2025: mitmproxy_rs 0.12.5
57

Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mitmproxy-highlight/Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ workspace = true
1313

1414
[dependencies]
1515
anyhow = { version = "1.0.97", features = ["backtrace"] }
16+
tree-sitter = "0.25.5"
17+
tree-sitter-css = "0.23.2"
1618
tree-sitter-highlight = "0.25.5"
17-
tree-sitter-yaml = "0.7.1"
19+
tree-sitter-javascript = "0.23.1"
1820
tree-sitter-xml = "0.7.0"
19-
tree-sitter = "0.25.5"
21+
tree-sitter-yaml = "0.7.1"
2022

2123
[dev-dependencies]
2224
criterion = "0.6.0"
2325

2426
[[bench]]
2527
name = "syntax_highlight"
26-
harness = false
28+
harness = false

mitmproxy-highlight/src/common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub fn highlight(
3939
}
4040

4141
#[cfg(test)]
42-
pub(super) fn test_names_ok(
42+
pub(super) fn test_tags_ok(
4343
language: tree_sitter::Language,
4444
highlights_query: &str,
4545
names: &[&str],

mitmproxy-highlight/src/css.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use super::{common, Chunk, Tag};
2+
use anyhow::Result;
3+
use std::sync::LazyLock;
4+
use tree_sitter_css::HIGHLIGHTS_QUERY;
5+
use tree_sitter_css::LANGUAGE;
6+
use tree_sitter_highlight::HighlightConfiguration;
7+
8+
const NAMES: &[&str] = &[
9+
"tag", // body
10+
"property", // font-size
11+
"variable", // --foo-bar
12+
"function", // calc()
13+
"number", // 42
14+
"string", // "foo"
15+
"comment", // /* comment */
16+
];
17+
const TAGS: &[Tag] = &[
18+
Tag::Name,
19+
Tag::Boolean, // we only have one "Name", so this is a workaround.
20+
Tag::Text,
21+
Tag::Text,
22+
Tag::Number,
23+
Tag::String,
24+
Tag::Comment,
25+
];
26+
27+
static CONFIG: LazyLock<HighlightConfiguration> = LazyLock::new(|| {
28+
let mut config = HighlightConfiguration::new(LANGUAGE.into(), "", HIGHLIGHTS_QUERY, "", "")
29+
.expect("failed to build syntax highlighter");
30+
config.configure(NAMES);
31+
config
32+
});
33+
34+
pub fn highlight(input: &[u8]) -> Result<Vec<Chunk>> {
35+
common::highlight(&CONFIG, TAGS, input)
36+
}
37+
38+
#[cfg(test)]
39+
mod tests {
40+
use super::*;
41+
42+
#[ignore]
43+
#[test]
44+
fn debug() {
45+
common::debug(
46+
LANGUAGE.into(),
47+
HIGHLIGHTS_QUERY,
48+
b"p > span { color: red; font-size: 42px; content: \"foo\"; margin: var(--foo) } /* foo */",
49+
);
50+
}
51+
52+
#[test]
53+
fn test_tags_ok() {
54+
common::test_tags_ok(LANGUAGE.into(), HIGHLIGHTS_QUERY, NAMES, TAGS);
55+
}
56+
57+
#[test]
58+
fn test_highlight() {
59+
let input = b"\
60+
p > span { \n\
61+
color: red;\n\
62+
font-size: 42px;\n\
63+
content: \"foo\";\n\
64+
margin: var(--foo);\n\
65+
}\n\
66+
/* foo */\n\
67+
";
68+
let chunks = highlight(input).unwrap();
69+
assert_eq!(
70+
chunks,
71+
vec![
72+
(Tag::Name, "p".to_string()),
73+
(Tag::Text, " > ".to_string()),
74+
(Tag::Name, "span".to_string()),
75+
(Tag::Text, " { \n".to_string()),
76+
(Tag::Boolean, "color".to_string()),
77+
(Tag::Text, ": red;\n".to_string()),
78+
(Tag::Boolean, "font-size".to_string()),
79+
(Tag::Text, ": ".to_string()),
80+
(Tag::Number, "42px".to_string()),
81+
(Tag::Text, ";\n".to_string()),
82+
(Tag::Boolean, "content".to_string()),
83+
(Tag::Text, ": ".to_string()),
84+
(Tag::String, "\"foo\"".to_string()),
85+
(Tag::Text, ";\n".to_string()),
86+
(Tag::Boolean, "margin".to_string()),
87+
(Tag::Text, ": var(--foo);\n}\n".to_string()),
88+
(Tag::Comment, "/* foo */\n".to_string()),
89+
]
90+
);
91+
}
92+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use super::{common, Chunk, Tag};
2+
use anyhow::Result;
3+
use std::sync::LazyLock;
4+
use tree_sitter_highlight::HighlightConfiguration;
5+
use tree_sitter_javascript::HIGHLIGHT_QUERY as HIGHLIGHTS_QUERY;
6+
use tree_sitter_javascript::LANGUAGE;
7+
8+
const NAMES: &[&str] = &[
9+
"keyword", // let
10+
"function", // *function* () {
11+
"variable", // let *foo* = ...
12+
"property", // foo.*bar* = ...
13+
"constant", // *true*
14+
"string", // "string"
15+
"number", // 42
16+
"comment", // /* comments */
17+
];
18+
const TAGS: &[Tag] = &[
19+
Tag::Name,
20+
Tag::Text,
21+
Tag::Text,
22+
Tag::Text,
23+
Tag::Boolean,
24+
Tag::String,
25+
Tag::Number,
26+
Tag::Comment,
27+
];
28+
29+
static CONFIG: LazyLock<HighlightConfiguration> = LazyLock::new(|| {
30+
let mut config = HighlightConfiguration::new(LANGUAGE.into(), "", HIGHLIGHTS_QUERY, "", "")
31+
.expect("failed to build syntax highlighter");
32+
config.configure(NAMES);
33+
config
34+
});
35+
36+
pub fn highlight(input: &[u8]) -> Result<Vec<Chunk>> {
37+
common::highlight(&CONFIG, TAGS, input)
38+
}
39+
40+
#[cfg(test)]
41+
mod tests {
42+
use super::*;
43+
44+
#[ignore]
45+
#[test]
46+
fn debug() {
47+
common::debug(
48+
LANGUAGE.into(),
49+
HIGHLIGHTS_QUERY,
50+
b"function foo() { let bar = true && 42 && 'qux'; foo.bar = 42; } // comment",
51+
);
52+
}
53+
54+
#[test]
55+
fn test_tags_ok() {
56+
common::test_tags_ok(LANGUAGE.into(), HIGHLIGHTS_QUERY, NAMES, TAGS);
57+
}
58+
59+
#[test]
60+
fn test_highlight() {
61+
let input = b"\
62+
function foo() {\n\
63+
let bar = true && 42 && 'qux';\n\
64+
} // comment\n\
65+
";
66+
let chunks = highlight(input).unwrap();
67+
assert_eq!(
68+
chunks,
69+
vec![
70+
(Tag::Name, "function ".to_string()),
71+
(Tag::Text, "foo() {\n".to_string()),
72+
(Tag::Name, "let ".to_string()),
73+
(Tag::Text, "bar = ".to_string()),
74+
(Tag::Boolean, "true".to_string()),
75+
(Tag::Text, " && ".to_string()),
76+
(Tag::Number, "42".to_string()),
77+
(Tag::Text, " && ".to_string()),
78+
(Tag::String, "'qux'".to_string()),
79+
(Tag::Text, ";\n} ".to_string()),
80+
(Tag::Comment, "// comment\n".to_string()),
81+
]
82+
);
83+
}
84+
}

mitmproxy-highlight/src/lib.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,29 @@ use anyhow::bail;
22
use std::str::FromStr;
33

44
pub mod common;
5+
mod css;
6+
mod javascript;
57
mod xml;
68
mod yaml;
79

810
pub type Chunk = (Tag, String);
911

1012
pub enum Language {
13+
Css,
14+
JavaScript,
1115
Xml,
1216
Yaml,
13-
Error,
1417
None,
18+
Error,
1519
}
1620

1721
impl Language {
1822
pub fn highlight(&self, input: &[u8]) -> anyhow::Result<Vec<Chunk>> {
1923
match self {
20-
Language::Yaml => yaml::highlight_yaml(input),
21-
Language::Xml => xml::highlight_xml(input),
24+
Language::Css => css::highlight(input),
25+
Language::JavaScript => javascript::highlight(input),
26+
Language::Xml => xml::highlight(input),
27+
Language::Yaml => yaml::highlight(input),
2228
Language::None => Ok(vec![(
2329
Tag::Text,
2430
String::from_utf8_lossy(input).to_string(),
@@ -30,14 +36,23 @@ impl Language {
3036
}
3137
}
3238

33-
pub const VALUES: [Self; 4] = [Self::Xml, Self::Yaml, Self::Error, Self::None];
39+
pub const VALUES: [Self; 6] = [
40+
Self::Css,
41+
Self::JavaScript,
42+
Self::Xml,
43+
Self::Yaml,
44+
Self::None,
45+
Self::Error,
46+
];
3447

35-
pub fn as_str(&self) -> &'static str {
48+
pub const fn as_str(&self) -> &'static str {
3649
match self {
50+
Self::Css => "css",
51+
Self::JavaScript => "javascript",
3752
Self::Xml => "xml",
3853
Self::Yaml => "yaml",
39-
Self::Error => "error",
4054
Self::None => "none",
55+
Self::Error => "error",
4156
}
4257
}
4358
}
@@ -47,6 +62,8 @@ impl FromStr for Language {
4762

4863
fn from_str(s: &str) -> Result<Self, Self::Err> {
4964
Ok(match s {
65+
"css" => Language::Css,
66+
"javascript" => Language::JavaScript,
5067
"xml" => Language::Xml,
5168
"yaml" => Language::Yaml,
5269
"none" => Language::None,

0 commit comments

Comments
 (0)