diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index f11d0e6bd45..93aba4c704b 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs @@ -3,7 +3,7 @@ use std::io::Read; use clap::{App, Arg, SubCommand}; -use ra_ide_api::file_structure; +use ra_ide_api::{file_structure, Analysis}; use ra_syntax::{SourceFile, TreeArc, AstNode}; use flexi_logger::Logger; use ra_prof::profile; @@ -16,6 +16,7 @@ fn main() -> Result<()> { .setting(clap::AppSettings::SubcommandRequiredElseHelp) .subcommand(SubCommand::with_name("parse").arg(Arg::with_name("no-dump").long("--no-dump"))) .subcommand(SubCommand::with_name("symbols")) + .subcommand(SubCommand::with_name("highlight")) .subcommand( SubCommand::with_name("analysis-stats") .arg(Arg::with_name("verbose").short("v").long("verbose")) @@ -38,6 +39,11 @@ fn main() -> Result<()> { println!("{:?}", s); } } + ("highlight", _) => { + let (analysis, file_id) = Analysis::from_single_file(read_stdin()?); + let html = analysis.highlight_as_html(file_id).unwrap(); + println!("{}", html); + } ("analysis-stats", Some(matches)) => { let verbose = matches.is_present("verbose"); let path = matches.value_of("path").unwrap_or(""); diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index f78348f7452..d3456d5b25a 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -462,6 +462,11 @@ pub fn highlight(&self, file_id: FileId) -> Cancelable> { 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 00000000000..bfc0a67b115 --- /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 9c60aed2abf..00000000000 --- 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 7bba7a5504b..87e053364cc 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); } }