Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Unreleased: mitmproxy_rs next

- Add JS syntax highlighting.
- Add CSS syntax highlighting.

## 30 May 2025: mitmproxy_rs 0.12.5

Expand Down
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions mitmproxy-highlight/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ workspace = true

[dependencies]
anyhow = { version = "1.0.97", features = ["backtrace"] }
tree-sitter = "0.25.5"
tree-sitter-css = "0.23.2"
tree-sitter-highlight = "0.25.5"
tree-sitter-yaml = "0.7.1"
tree-sitter-javascript = "0.23.1"
tree-sitter-xml = "0.7.0"
tree-sitter = "0.25.5"
tree-sitter-yaml = "0.7.1"

[dev-dependencies]
criterion = "0.6.0"

[[bench]]
name = "syntax_highlight"
harness = false
harness = false
2 changes: 1 addition & 1 deletion mitmproxy-highlight/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub fn highlight(
}

#[cfg(test)]
pub(super) fn test_names_ok(
pub(super) fn test_tags_ok(
language: tree_sitter::Language,
highlights_query: &str,
names: &[&str],
Expand Down
92 changes: 92 additions & 0 deletions mitmproxy-highlight/src/css.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use super::{common, Chunk, Tag};
use anyhow::Result;
use std::sync::LazyLock;
use tree_sitter_css::HIGHLIGHTS_QUERY;
use tree_sitter_css::LANGUAGE;
use tree_sitter_highlight::HighlightConfiguration;

const NAMES: &[&str] = &[
"tag", // body
"property", // font-size
"variable", // --foo-bar
"function", // calc()
"number", // 42
"string", // "foo"
"comment", // /* comment */
];
const TAGS: &[Tag] = &[
Tag::Name,
Tag::Boolean, // we only have one "Name", so this is a workaround.
Tag::Text,
Tag::Text,
Tag::Number,
Tag::String,
Tag::Comment,
];

static CONFIG: LazyLock<HighlightConfiguration> = LazyLock::new(|| {
let mut config = HighlightConfiguration::new(LANGUAGE.into(), "", HIGHLIGHTS_QUERY, "", "")
.expect("failed to build syntax highlighter");
config.configure(NAMES);
config
});

pub fn highlight(input: &[u8]) -> Result<Vec<Chunk>> {
common::highlight(&CONFIG, TAGS, input)
}

#[cfg(test)]
mod tests {
use super::*;

#[ignore]
#[test]
fn debug() {
common::debug(
LANGUAGE.into(),
HIGHLIGHTS_QUERY,
b"p > span { color: red; font-size: 42px; content: \"foo\"; margin: var(--foo) } /* foo */",
);
}

#[test]
fn test_tags_ok() {
common::test_tags_ok(LANGUAGE.into(), HIGHLIGHTS_QUERY, NAMES, TAGS);
}

#[test]
fn test_highlight() {
let input = b"\
p > span { \n\
color: red;\n\
font-size: 42px;\n\
content: \"foo\";\n\
margin: var(--foo);\n\
}\n\
/* foo */\n\
";
let chunks = highlight(input).unwrap();
assert_eq!(
chunks,
vec![
(Tag::Name, "p".to_string()),
(Tag::Text, " > ".to_string()),
(Tag::Name, "span".to_string()),
(Tag::Text, " { \n".to_string()),
(Tag::Boolean, "color".to_string()),
(Tag::Text, ": red;\n".to_string()),
(Tag::Boolean, "font-size".to_string()),
(Tag::Text, ": ".to_string()),
(Tag::Number, "42px".to_string()),
(Tag::Text, ";\n".to_string()),
(Tag::Boolean, "content".to_string()),
(Tag::Text, ": ".to_string()),
(Tag::String, "\"foo\"".to_string()),
(Tag::Text, ";\n".to_string()),
(Tag::Boolean, "margin".to_string()),
(Tag::Text, ": var(--foo);\n}\n".to_string()),
(Tag::Comment, "/* foo */\n".to_string()),
]
);
}
}
84 changes: 84 additions & 0 deletions mitmproxy-highlight/src/javascript.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use super::{common, Chunk, Tag};
use anyhow::Result;
use std::sync::LazyLock;
use tree_sitter_highlight::HighlightConfiguration;
use tree_sitter_javascript::HIGHLIGHT_QUERY as HIGHLIGHTS_QUERY;
use tree_sitter_javascript::LANGUAGE;

const NAMES: &[&str] = &[
"keyword", // let
"function", // *function* () {
"variable", // let *foo* = ...
"property", // foo.*bar* = ...
"constant", // *true*
"string", // "string"
"number", // 42
"comment", // /* comments */
];
const TAGS: &[Tag] = &[
Tag::Name,
Tag::Text,
Tag::Text,
Tag::Text,
Tag::Boolean,
Tag::String,
Tag::Number,
Tag::Comment,
];

static CONFIG: LazyLock<HighlightConfiguration> = LazyLock::new(|| {
let mut config = HighlightConfiguration::new(LANGUAGE.into(), "", HIGHLIGHTS_QUERY, "", "")
.expect("failed to build syntax highlighter");
config.configure(NAMES);
config
});

pub fn highlight(input: &[u8]) -> Result<Vec<Chunk>> {
common::highlight(&CONFIG, TAGS, input)
}

#[cfg(test)]
mod tests {
use super::*;

#[ignore]
#[test]
fn debug() {
common::debug(
LANGUAGE.into(),
HIGHLIGHTS_QUERY,
b"function foo() { let bar = true && 42 && 'qux'; foo.bar = 42; } // comment",
);
}

#[test]
fn test_tags_ok() {
common::test_tags_ok(LANGUAGE.into(), HIGHLIGHTS_QUERY, NAMES, TAGS);
}

#[test]
fn test_highlight() {
let input = b"\
function foo() {\n\
let bar = true && 42 && 'qux';\n\
} // comment\n\
";
let chunks = highlight(input).unwrap();
assert_eq!(
chunks,
vec![
(Tag::Name, "function ".to_string()),
(Tag::Text, "foo() {\n".to_string()),
(Tag::Name, "let ".to_string()),
(Tag::Text, "bar = ".to_string()),
(Tag::Boolean, "true".to_string()),
(Tag::Text, " && ".to_string()),
(Tag::Number, "42".to_string()),
(Tag::Text, " && ".to_string()),
(Tag::String, "'qux'".to_string()),
(Tag::Text, ";\n} ".to_string()),
(Tag::Comment, "// comment\n".to_string()),
]
);
}
}
29 changes: 23 additions & 6 deletions mitmproxy-highlight/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,29 @@ use anyhow::bail;
use std::str::FromStr;

pub mod common;
mod css;
mod javascript;
mod xml;
mod yaml;

pub type Chunk = (Tag, String);

pub enum Language {
Css,
JavaScript,
Xml,
Yaml,
Error,
None,
Error,
}

impl Language {
pub fn highlight(&self, input: &[u8]) -> anyhow::Result<Vec<Chunk>> {
match self {
Language::Yaml => yaml::highlight_yaml(input),
Language::Xml => xml::highlight_xml(input),
Language::Css => css::highlight(input),
Language::JavaScript => javascript::highlight(input),
Language::Xml => xml::highlight(input),
Language::Yaml => yaml::highlight(input),
Language::None => Ok(vec![(
Tag::Text,
String::from_utf8_lossy(input).to_string(),
Expand All @@ -30,14 +36,23 @@ impl Language {
}
}

pub const VALUES: [Self; 4] = [Self::Xml, Self::Yaml, Self::Error, Self::None];
pub const VALUES: [Self; 6] = [
Self::Css,
Self::JavaScript,
Self::Xml,
Self::Yaml,
Self::None,
Self::Error,
];

pub fn as_str(&self) -> &'static str {
pub const fn as_str(&self) -> &'static str {
match self {
Self::Css => "css",
Self::JavaScript => "javascript",
Self::Xml => "xml",
Self::Yaml => "yaml",
Self::Error => "error",
Self::None => "none",
Self::Error => "error",
}
}
}
Expand All @@ -47,6 +62,8 @@ impl FromStr for Language {

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"css" => Language::Css,
"javascript" => Language::JavaScript,
"xml" => Language::Xml,
"yaml" => Language::Yaml,
"none" => Language::None,
Expand Down
Loading
Loading