From c6e905a79f7ba083b3f97728aa3a74fb0e03661b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 25 May 2019 13:42:34 +0300 Subject: Colorize Rust code as HTML --- crates/ra_ide_api/src/lib.rs | 5 + crates/ra_ide_api/src/snapshots/highlighting.html | 45 +++++++ .../src/snapshots/tests__highlighting.snap | 146 --------------------- crates/ra_ide_api/src/syntax_highlighting.rs | 87 +++++++++++- 4 files changed, 131 insertions(+), 152 deletions(-) create mode 100644 crates/ra_ide_api/src/snapshots/highlighting.html delete mode 100644 crates/ra_ide_api/src/snapshots/tests__highlighting.snap (limited to 'crates/ra_ide_api') diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index f78348f74..d3456d5b2 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -462,6 +462,11 @@ impl Analysis { self.with_db(|db| syntax_highlighting::highlight(db, file_id)) } + /// Computes syntax highlighting for the given file. + pub fn highlight_as_html(&self, file_id: FileId) -> Cancelable { + self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id)) + } + /// Computes completions at the given position. pub fn completions(&self, position: FilePosition) -> Cancelable>> { self.with_db(|db| completion::completions(db, position).map(Into::into)) diff --git a/crates/ra_ide_api/src/snapshots/highlighting.html b/crates/ra_ide_api/src/snapshots/highlighting.html new file mode 100644 index 000000000..bfc0a67b1 --- /dev/null +++ b/crates/ra_ide_api/src/snapshots/highlighting.html @@ -0,0 +1,45 @@ + + +

+#[derive(Clone, Debug)]
+struct Foo {
+    pub x: i32,
+    pub y: i32,
+}
+
+fn foo<T>() -> T {
+    unimplemented!();
+}
+
+// comment
+fn main() {
+    println!("Hello, {}!", 92);
+
+    let mut vec = Vec::new();
+    if true {
+        vec.push(Foo { x: 0, y: 1 });
+    }
+    unsafe { vec.set_len(0); }
+}
+
\ No newline at end of file diff --git a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap b/crates/ra_ide_api/src/snapshots/tests__highlighting.snap deleted file mode 100644 index 9c60aed2a..000000000 --- a/crates/ra_ide_api/src/snapshots/tests__highlighting.snap +++ /dev/null @@ -1,146 +0,0 @@ ---- -created: "2019-05-23T22:23:35.242742395Z" -creator: insta@0.8.1 -source: crates/ra_ide_api/src/syntax_highlighting.rs -expression: result ---- -Ok( - [ - HighlightedRange { - range: [1; 24), - tag: "attribute", - }, - HighlightedRange { - range: [25; 31), - tag: "keyword", - }, - HighlightedRange { - range: [32; 35), - tag: "function", - }, - HighlightedRange { - range: [42; 45), - tag: "keyword", - }, - HighlightedRange { - range: [46; 47), - tag: "function", - }, - HighlightedRange { - range: [49; 52), - tag: "text", - }, - HighlightedRange { - range: [58; 61), - tag: "keyword", - }, - HighlightedRange { - range: [62; 63), - tag: "function", - }, - HighlightedRange { - range: [65; 68), - tag: "text", - }, - HighlightedRange { - range: [73; 75), - tag: "keyword", - }, - HighlightedRange { - range: [76; 79), - tag: "function", - }, - HighlightedRange { - range: [80; 81), - tag: "type", - }, - HighlightedRange { - range: [80; 81), - tag: "function", - }, - HighlightedRange { - range: [88; 89), - tag: "type", - }, - HighlightedRange { - range: [96; 110), - tag: "macro", - }, - HighlightedRange { - range: [117; 127), - tag: "comment", - }, - HighlightedRange { - range: [128; 130), - tag: "keyword", - }, - HighlightedRange { - range: [131; 135), - tag: "function", - }, - HighlightedRange { - range: [145; 153), - tag: "macro", - }, - HighlightedRange { - range: [154; 166), - tag: "string", - }, - HighlightedRange { - range: [168; 170), - tag: "literal", - }, - HighlightedRange { - range: [178; 181), - tag: "keyword", - }, - HighlightedRange { - range: [182; 185), - tag: "keyword", - }, - HighlightedRange { - range: [186; 189), - tag: "macro", - }, - HighlightedRange { - range: [197; 200), - tag: "macro", - }, - HighlightedRange { - range: [192; 195), - tag: "text", - }, - HighlightedRange { - range: [208; 211), - tag: "macro", - }, - HighlightedRange { - range: [212; 216), - tag: "macro", - }, - HighlightedRange { - range: [226; 227), - tag: "literal", - }, - HighlightedRange { - range: [232; 233), - tag: "literal", - }, - HighlightedRange { - range: [242; 248), - tag: "keyword.unsafe", - }, - HighlightedRange { - range: [251; 254), - tag: "text", - }, - HighlightedRange { - range: [255; 262), - tag: "text", - }, - HighlightedRange { - range: [263; 264), - tag: "literal", - }, - ], -) diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs index 7bba7a550..87e053364 100644 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ b/crates/ra_ide_api/src/syntax_highlighting.rs @@ -114,10 +114,79 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec String { + let source_file = db.parse(file_id); + + let mut ranges = highlight(db, file_id); + ranges.sort_by_key(|it| it.range.start()); + // quick non-optimal heuristic to intersect token ranges and highlighted ranges + let mut frontier = 0; + let mut could_intersect: Vec<&HighlightedRange> = Vec::new(); + + let mut buf = String::new(); + buf.push_str(&STYLE); + buf.push_str("
");
+    let tokens = source_file.syntax().descendants_with_tokens().filter_map(|it| it.as_token());
+    for token in tokens {
+        could_intersect.retain(|it| token.range().start() <= it.range.end());
+        while let Some(r) = ranges.get(frontier) {
+            if r.range.start() <= token.range().end() {
+                could_intersect.push(r);
+                frontier += 1;
+            } else {
+                break;
+            }
+        }
+        let text = html_escape(&token.text());
+        let classes = could_intersect
+            .iter()
+            .filter(|it| token.range().is_subrange(&it.range))
+            .map(|it| it.tag)
+            .collect::>();
+        if classes.is_empty() {
+            buf.push_str(&text);
+        } else {
+            let classes = classes.join(" ");
+            buf.push_str(&format!("{}", classes, text));
+        }
+    }
+    buf.push_str("
"); + buf +} + +//FIXME: like, real html escaping +fn html_escape(text: &str) -> String { + text.replace("<", "<").replace(">", ">") +} + +const STYLE: &str = " + +"; + #[cfg(test)] mod tests { - use insta::assert_debug_snapshot_matches; - + use test_utils::{project_dir, read_text, assert_eq_text}; use crate::mock_analysis::single_file; #[test] @@ -135,15 +204,21 @@ fn foo() -> T { } // comment -fn main() {} +fn main() { println!("Hello, {}!", 92); let mut vec = Vec::new(); - vec.push(Foo { x: 0, y: 1 }); + if true { + vec.push(Foo { x: 0, y: 1 }); + } unsafe { vec.set_len(0); } +} "#, ); - let result = analysis.highlight(file_id); - assert_debug_snapshot_matches!("highlighting", result); + let dst_file = project_dir().join("crates/ra_ide_api/src/snapshots/highlighting.html"); + let actual_html = &analysis.highlight_as_html(file_id).unwrap(); + let expected_html = &read_text(&dst_file); + // std::fs::write(dst_file, &actual_html).unwrap(); + assert_eq_text!(expected_html, actual_html); } } -- cgit v1.2.3