Repository: rust-analyzer/rust-analyzer
Pull request #7196 (2021-01-07 15:21:43)

Better fixture highlight
bors r+
🤖

bors[bot]: Build failed:
  * [Rust (ubuntu-latest)](https://github.com/rust-analyzer/rust-analyzer/runs/1663278892)

Patch:
diff --git a/crates/assists/src/handlers/flip_comma.rs b/crates/assists/src/handlers/flip_comma.rs
index a48b0e450a..18cf64a34e 100644
--- a/crates/assists/src/handlers/flip_comma.rs
+++ b/crates/assists/src/handlers/flip_comma.rs
@@ -49,14 +49,14 @@ mod tests {
     fn flip_comma_works_for_function_parameters() {
         check_assist(
             flip_comma,
-            "fn foo(x: i32,$0 y: Result<(), ()>) {}",
-            "fn foo(y: Result<(), ()>, x: i32) {}",
+            r#"fn foo(x: i32,$0 y: Result<(), ()>) {}"#,
+            r#"fn foo(y: Result<(), ()>, x: i32) {}"#,
         )
     }
 
     #[test]
     fn flip_comma_target() {
-        check_assist_target(flip_comma, "fn foo(x: i32,$0 y: Result<(), ()>) {}", ",")
+        check_assist_target(flip_comma, r#"fn foo(x: i32,$0 y: Result<(), ()>) {}"#, ",")
     }
 
     #[test]
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs
index 66e6443cb1..98acd61b12 100644
--- a/crates/base_db/src/fixture.rs
+++ b/crates/base_db/src/fixture.rs
@@ -61,7 +61,9 @@ use std::{str::FromStr, sync::Arc};
 
 use cfg::CfgOptions;
 use rustc_hash::FxHashMap;
-use test_utils::{extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER};
+use test_utils::{
+    extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER, ESCAPED_CURSOR_MARKER,
+};
 use vfs::{file_set::FileSet, VfsPath};
 
 use crate::{
@@ -142,10 +144,14 @@ impl ChangeFixture {
 
         for entry in fixture {
             let text = if entry.text.contains(CURSOR_MARKER) {
-                let (range_or_offset, text) = extract_range_or_offset(&entry.text);
-                assert!(file_position.is_none());
-                file_position = Some((file_id, range_or_offset));
-                text.to_string()
+                if entry.text.contains(ESCAPED_CURSOR_MARKER) {
+                    entry.text.replace(ESCAPED_CURSOR_MARKER, CURSOR_MARKER)
+                } else {
+                    let (range_or_offset, text) = extract_range_or_offset(&entry.text);
+                    assert!(file_position.is_none());
+                    file_position = Some((file_id, range_or_offset));
+                    text.to_string()
+                }
             } else {
                 entry.text.clone()
             };
diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs
index 296893d2fa..05380f2a10 100644
--- a/crates/ide/src/join_lines.rs
+++ b/crates/ide/src/join_lines.rs
@@ -198,8 +198,8 @@ mod tests {
 
     use super::*;
 
-    fn check_join_lines(before: &str, after: &str) {
-        let (before_cursor_pos, before) = extract_offset(before);
+    fn check_join_lines(ra_fixture_before: &str, ra_fixture_after: &str) {
+        let (before_cursor_pos, before) = extract_offset(ra_fixture_before);
         let file = SourceFile::parse(&before).ok().unwrap();
 
         let range = TextRange::empty(before_cursor_pos);
@@ -214,7 +214,7 @@ mod tests {
             .apply_to_offset(before_cursor_pos)
             .expect("cursor position is affected by the edit");
         let actual = add_cursor(&actual, actual_cursor_pos);
-        assert_eq_text!(after, &actual);
+        assert_eq_text!(ra_fixture_after, &actual);
     }
 
     #[test]
@@ -604,8 +604,8 @@ fn foo() {
         );
     }
 
-    fn check_join_lines_sel(before: &str, after: &str) {
-        let (sel, before) = extract_range(before);
+    fn check_join_lines_sel(ra_fixture_before: &str, ra_fixture_after: &str) {
+        let (sel, before) = extract_range(ra_fixture_before);
         let parse = SourceFile::parse(&before);
         let result = join_lines(&parse.tree(), sel);
         let actual = {
@@ -613,7 +613,7 @@ fn foo() {
             result.apply(&mut actual);
             actual
         };
-        assert_eq_text!(after, &actual);
+        assert_eq_text!(ra_fixture_after, &actual);
     }
 
     #[test]
diff --git a/crates/ide/src/syntax_highlighting/injection.rs b/crates/ide/src/syntax_highlighting/injection.rs
index 6cbd683c69..d6be9708df 100644
--- a/crates/ide/src/syntax_highlighting/injection.rs
+++ b/crates/ide/src/syntax_highlighting/injection.rs
@@ -22,7 +22,8 @@ pub(super) fn highlight_injection(
         return None;
     }
     let value = literal.value()?;
-    let (analysis, tmp_file_id) = Analysis::from_single_file(value.into_owned());
+    let marker_info = MarkerInfo::new(&*value);
+    let (analysis, tmp_file_id) = Analysis::from_single_file(marker_info.cleaned_text.clone());
 
     if let Some(range) = literal.open_quote_text_range() {
         acc.add(HighlightedRange {
@@ -33,9 +34,10 @@ pub(super) fn highlight_injection(
     }
 
     for mut h in analysis.highlight(tmp_file_id).unwrap() {
-        if let Some(r) = literal.map_range_up(h.range) {
-            h.range = r;
-            acc.add(h)
+        let range = marker_info.map_range_up(h.range);
+        if let Some(range) = literal.map_range_up(range) {
+            h.range = range;
+            acc.add(h);
         }
     }
 
@@ -50,6 +52,52 @@ pub(super) fn highlight_injection(
     Some(())
 }
 
+/// Data to remove `$0` from string and map ranges
+#[derive(Default, Debug)]
+struct MarkerInfo {
+    cleaned_text: String,
+    markers: Vec<TextRange>,
+}
+
+impl MarkerInfo {
+    fn new(mut text: &str) -> Self {
+        let marker = "$0";
+
+        let mut res = MarkerInfo::default();
+        let mut offset: TextSize = 0.into();
+        while !text.is_empty() {
+            let idx = text.find(marker).unwrap_or(text.len());
+            let (chunk, next) = text.split_at(idx);
+            text = next;
+            res.cleaned_text.push_str(chunk);
+            offset += TextSize::of(chunk);
+
+            if let Some(next) = text.strip_prefix(marker) {
+                text = next;
+
+                let marker_len = TextSize::of(marker);
+                res.markers.push(TextRange::at(offset, marker_len));
+                offset += marker_len;
+            }
+        }
+        res
+    }
+    fn map_range_up(&self, range: TextRange) -> TextRange {
+        TextRange::new(
+            self.map_offset_up(range.start(), true),
+            self.map_offset_up(range.end(), false),
+        )
+    }
+    fn map_offset_up(&self, mut offset: TextSize, start: bool) -> TextSize {
+        for r in &self.markers {
+            if r.start() < offset || (start && r.start() == offset) {
+                offset += r.len()
+            }
+        }
+        offset
+    }
+}
+
 /// Mapping from extracted documentation code to original code
 type RangesMap = BTreeMap<TextSize, TextSize>;
 
diff --git a/docs/dev/README.md b/docs/dev/README.md
index dd2bfc493d..55527bab0a 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -77,7 +77,7 @@ Notably, this uses the usual `rust-analyzer` binary from `PATH`.
 For this, it is important to have the following in your `settings.json` file:
 ```json
 {
-    "rust-analyzer.server.path": "rust-analyzer"
+    "rust-analyzer.serverPath": "rust-analyzer"
 }
 ```
 After I am done with the fix, I use `cargo xtask install --client` to try the new extension for real.
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 990b118593..266baffa17 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -115,7 +115,7 @@ $ code --install-extension /path/to/rust-analyzer.vsix
 Copy the `rust-analyzer-{platform}` binary anywhere, then add the path to your settings.json, for example:
 [source,json]
 ----
-{ "rust-analyzer.server.path": "~/.local/bin/rust-analyzer-linux" }
+{ "rust-analyzer.serverPath": "~/.local/bin/rust-analyzer-linux" }
 ----
 
 ==== Building From Source
@@ -219,7 +219,7 @@ The are several LSP client implementations for vim or neovim:
    https://github.com/fannheyward/coc-rust-analyzer[coc-rust-analyzer],
    this extension implements _most_ of the features supported in the VSCode extension:
    * automatically install and upgrade stable/nightly releases
-   * same configurations as VSCode extension, `rust-analyzer.server.path`, `rust-analyzer.cargo.features` etc.
+   * same configurations as VSCode extension, `rust-analyzer.serverPath`, `rust-analyzer.cargo.features` etc.
    * same commands too, `rust-analyzer.analyzerStatus`, `rust-analyzer.ssr` etc.
    * inlay hints for variables and method chaining, _Neovim Only_
    * semantic highlighting is not implemented yet
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 694da9409d..4c132cabee 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -167,7 +167,6 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
         }
         return;
     };
-    if (serverPath(config) !== null) return;
 
     const now = Date.now();
     if (config.package.releaseTag === NIGHTLY_TAG) {
@@ -279,7 +278,7 @@ async function patchelf(dest: PathLike): Promise<void> {
 }
 
 async function getServer(config: Config, state: PersistentState): Promise<string | undefined> {
-    const explicitPath = serverPath(config);
+    const explicitPath = process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath;
     if (explicitPath) {
         if (explicitPath.startsWith("~/")) {
             return os.homedir() + explicitPath.slice("~".length);
@@ -352,10 +351,6 @@ async function getServer(config: Config, state: PersistentState): Promise<string
     return dest;
 }
 
-function serverPath(config: Config): string | null {
-    return process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath;
-}
-
 async function isNixOs(): Promise<boolean> {
     try {
         const contents = await fs.readFile("/etc/os-release");


Test patch:
diff --git a/crates/ide/src/syntax_highlighting/test_data/injection.html b/crates/ide/src/syntax_highlighting/test_data/injection.html
new file mode 100644
index 0000000000..a54d303b46
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/test_data/injection.html
@@ -0,0 +1,48 @@
+
+<style>
+body                { margin: 0; }
+pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime           { color: #DFAF8F; font-style: italic; }
+.label              { color: #DFAF8F; font-style: italic; }
+.comment            { color: #7F9F7F; }
+.documentation      { color: #629755; }
+.injected           { opacity: 0.65 ; }
+.struct, .enum      { color: #7CB8BB; }
+.enum_variant       { color: #BDE0F3; }
+.string_literal     { color: #CC9393; }
+.field              { color: #94BFF3; }
+.function           { color: #93E0E3; }
+.function.unsafe    { color: #BC8383; }
+.operator.unsafe    { color: #BC8383; }
+.parameter          { color: #94BFF3; }
+.text               { color: #DCDCCC; }
+.type               { color: #7CB8BB; }
+.builtin_type       { color: #8CD0D3; }
+.type_param         { color: #DFAF8F; }
+.attribute          { color: #94BFF3; }
+.numeric_literal    { color: #BFEBBF; }
+.bool_literal       { color: #BFE6EB; }
+.macro              { color: #94BFF3; }
+.module             { color: #AFD8AF; }
+.value_param        { color: #DCDCCC; }
+.variable           { color: #DCDCCC; }
+.format_specifier   { color: #CC696B; }
+.mutable            { text-decoration: underline; }
+.escape_sequence    { color: #94BFF3; }
+.keyword            { color: #F0DFAF; font-weight: bold; }
+.keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.control            { font-style: italic; }
+
+.unresolved_reference { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">fn</span> <span class="function declaration">f</span><span class="punctuation">(</span><span class="value_param declaration">ra_fixture</span><span class="punctuation">:</span> <span class="operator">&</span><span class="builtin_type">str</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
+<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
+    <span class="function">f</span><span class="punctuation">(</span><span class="string_literal">r"</span>
+<span class="keyword">fn</span> <span class="function declaration">foo</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
+    <span class="function">foo</span><span class="punctuation">(</span>$0<span class="punctuation">{</span>
+        <span class="numeric_literal">92</span>
+    <span class="punctuation">}</span>$0<span class="punctuation">)</span>
+<span class="punctuation">}</span><span class="string_literal">"</span><span class="punctuation">)</span><span class="punctuation">;</span>
+<span class="punctuation">}</span>
+    </code></pre>
\ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 30b5b648e9..9e1a3974cb 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -555,6 +555,25 @@ impl t for foo {
     )
 }
 
+#[test]
+fn test_injection() {
+    check_highlighting(
+        r##"
+fn f(ra_fixture: &str) {}
+fn main() {
+    f(r"
+fn foo() {
+    foo(\$0{
+        92
+    }\$0)
+}");
+}
+    "##,
+        expect_file!["./test_data/injection.html"],
+        false,
+    );
+}
+
 /// Highlights the code given by the `ra_fixture` argument, renders the
 /// result as HTML, and compares it with the HTML file given as `snapshot`.
 /// Note that the `snapshot` file is overwritten by the rendered HTML.
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index 05d6e8c9ed..84c1d7ebbd 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -26,6 +26,7 @@ pub use rustc_hash::FxHashMap;
 pub use crate::fixture::Fixture;
 
 pub const CURSOR_MARKER: &str = "$0";
+pub const ESCAPED_CURSOR_MARKER: &str = "\\$0";
 
 /// Asserts that two strings are equal, otherwise displays a rich diff between them.
 ///