diff options
| author | bors <bors@rust-lang.org> | 2024-02-16 18:45:43 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2024-02-16 18:45:43 +0000 |
| commit | ac998a74b3c8ff4b81c3eeb9a18811d4cc76226d (patch) | |
| tree | 8a68a5a9f2edfe6f423c3a5e7a0bb5d1043308cd | |
| parent | 0932f895867607a5f4767877d811fd43508a02b1 (diff) | |
| parent | e8457bb78b85ec4bcf0f28a3a18e4ed70cd3ccb9 (diff) | |
| download | rust-ac998a74b3c8ff4b81c3eeb9a18811d4cc76226d.tar.gz rust-ac998a74b3c8ff4b81c3eeb9a18811d4cc76226d.zip | |
Auto merge of #16579 - DropDemBits:structured-snippet-fix-with-escaped-bits-and-cr, r=Veykril
fix: Fix snippets being placed leftwards of where they should be Snippet bits were being escaped before placing snippets, shifting snippets leftwards. Snippets were also being shifted leftwards on files with CRLF line endings since they were placed done after the Unix -> DOS line ending conversion. Hoping this fixes all of the little bugs related to snippet rendering 😅
| -rw-r--r-- | crates/rust-analyzer/src/lsp/to_proto.rs | 345 |
1 files changed, 204 insertions, 141 deletions
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index 60281202f84..727007bba08 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -971,15 +971,11 @@ fn merge_text_and_snippet_edits( snippet_range }; - let range = range(line_index, snippet_range); - let new_text = format!("${snippet_index}"); - - edits.push(SnippetTextEdit { - range, - new_text, - insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), - annotation_id: None, - }) + edits.push(snippet_text_edit( + line_index, + true, + Indel { insert: format!("${snippet_index}"), delete: snippet_range }, + )) } if snippets.peek().is_some_and(|(_, range)| { @@ -1002,31 +998,44 @@ fn merge_text_and_snippet_edits( ) }); - let mut text_edit = text_edit(line_index, current_indel); + let mut new_text = current_indel.insert; - // escape out snippet text - stdx::replace(&mut text_edit.new_text, '\\', r"\\"); - stdx::replace(&mut text_edit.new_text, '$', r"\$"); + // find which snippet bits need to be escaped + let escape_places = new_text + .rmatch_indices(['\\', '$', '{', '}']) + .map(|(insert, _)| insert) + .collect_vec(); + let mut escape_places = escape_places.into_iter().peekable(); + let mut escape_prior_bits = |new_text: &mut String, up_to: usize| { + for before in escape_places.peeking_take_while(|insert| *insert >= up_to) { + new_text.insert(before, '\\'); + } + }; - // ...and apply! + // insert snippets, and escaping any needed bits along the way for (index, range) in all_snippets.iter().rev() { - let start = (range.start() - new_range.start()).into(); - let end = (range.end() - new_range.start()).into(); + let text_range = range - new_range.start(); + let (start, end) = (text_range.start().into(), text_range.end().into()); if range.is_empty() { - text_edit.new_text.insert_str(start, &format!("${index}")); + escape_prior_bits(&mut new_text, start); + new_text.insert_str(start, &format!("${index}")); } else { - text_edit.new_text.insert(end, '}'); - text_edit.new_text.insert_str(start, &format!("${{{index}:")); + escape_prior_bits(&mut new_text, end); + new_text.insert(end, '}'); + escape_prior_bits(&mut new_text, start); + new_text.insert_str(start, &format!("${{{index}:")); } } - edits.push(SnippetTextEdit { - range: text_edit.range, - new_text: text_edit.new_text, - insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), - annotation_id: None, - }) + // escape any remaining bits + escape_prior_bits(&mut new_text, 0); + + edits.push(snippet_text_edit( + line_index, + true, + Indel { insert: new_text, delete: current_indel.delete }, + )) } else { // snippet edit was beyond the current one // since it wasn't consumed, it's available for the next pass @@ -1052,15 +1061,11 @@ fn merge_text_and_snippet_edits( snippet_range }; - let range = range(line_index, snippet_range); - let new_text = format!("${snippet_index}"); - - SnippetTextEdit { - range, - new_text, - insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), - annotation_id: None, - } + snippet_text_edit( + line_index, + true, + Indel { insert: format!("${snippet_index}"), delete: snippet_range }, + ) })); edits @@ -1705,9 +1710,10 @@ fn bar(_: usize) {} expect: Expect, ) { let source = stdx::trim_indent(ra_fixture); + let endings = if source.contains('\r') { LineEndings::Dos } else { LineEndings::Unix }; let line_index = LineIndex { index: Arc::new(ide::LineIndex::new(&source)), - endings: LineEndings::Unix, + endings, encoding: PositionEncoding::Utf8, }; @@ -2144,51 +2150,71 @@ fn bar(_: usize) {} fn snippet_rendering_escape_snippet_bits() { // only needed for snippet formats let mut edit = TextEdit::builder(); - edit.insert(0.into(), r"abc\def$".to_owned()); - edit.insert(8.into(), r"ghi\jkl$".to_owned()); + edit.insert(0.into(), r"$ab{}$c\def".to_owned()); + edit.insert(8.into(), r"ghi\jk<-check_insert_here$".to_owned()); + edit.insert(10.into(), r"a\\b\\c{}$".to_owned()); let edit = edit.finish(); - let snippets = - SnippetEdit::new(vec![Snippet::Placeholder(TextRange::new(0.into(), 3.into()))]); + let snippets = SnippetEdit::new(vec![ + Snippet::Placeholder(TextRange::new(1.into(), 9.into())), + Snippet::Tabstop(25.into()), + ]); check_rendered_snippets( edit, snippets, expect![[r#" - [ - SnippetTextEdit { - range: Range { - start: Position { - line: 0, - character: 0, - }, - end: Position { - line: 0, - character: 0, + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 0, + }, + end: Position { + line: 0, + character: 0, + }, }, + new_text: "\\$${1:ab\\{\\}\\$c\\\\d}ef", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, }, - new_text: "${0:abc}\\\\def\\$", - insert_text_format: Some( - Snippet, - ), - annotation_id: None, - }, - SnippetTextEdit { - range: Range { - start: Position { - line: 0, - character: 8, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 8, + }, + end: Position { + line: 0, + character: 8, + }, }, - end: Position { - line: 0, - character: 8, + new_text: "ghi\\\\jk$0<-check_insert_here\\$", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 0, + character: 10, + }, + end: Position { + line: 0, + character: 10, + }, }, + new_text: "a\\\\b\\\\c{}$", + insert_text_format: None, + annotation_id: None, }, - new_text: "ghi\\jkl$", - insert_text_format: None, - annotation_id: None, - }, - ] - "#]], + ] + "#]], ); } @@ -2218,41 +2244,41 @@ struct ProcMacro { edit, snippets, expect![[r#" - [ - SnippetTextEdit { - range: Range { - start: Position { - line: 1, - character: 4, - }, - end: Position { - line: 1, - character: 13, - }, - }, - new_text: "let", - insert_text_format: None, - annotation_id: None, - }, - SnippetTextEdit { - range: Range { - start: Position { - line: 1, - character: 14, - }, - end: Position { - line: 3, - character: 5, - }, - }, - new_text: "$0disabled = false;\n ProcMacro {\n disabled,\n }", - insert_text_format: Some( - Snippet, - ), - annotation_id: None, - }, - ] -"#]], + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 1, + character: 4, + }, + end: Position { + line: 1, + character: 13, + }, + }, + new_text: "let", + insert_text_format: None, + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 1, + character: 14, + }, + end: Position { + line: 3, + character: 5, + }, + }, + new_text: "$0disabled = false;\n ProcMacro \\{\n disabled,\n \\}", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], ); } @@ -2282,41 +2308,41 @@ struct P { edit, snippets, expect![[r#" - [ - SnippetTextEdit { - range: Range { - start: Position { - line: 1, - character: 4, - }, - end: Position { - line: 1, - character: 5, - }, - }, - new_text: "let", - insert_text_format: None, - annotation_id: None, - }, - SnippetTextEdit { - range: Range { - start: Position { - line: 1, - character: 6, - }, - end: Position { - line: 3, - character: 5, - }, - }, - new_text: "$0disabled = false;\n ProcMacro {\n disabled,\n }", - insert_text_format: Some( - Snippet, - ), - annotation_id: None, - }, - ] -"#]], + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 1, + character: 4, + }, + end: Position { + line: 1, + character: 5, + }, + }, + new_text: "let", + insert_text_format: None, + annotation_id: None, + }, + SnippetTextEdit { + range: Range { + start: Position { + line: 1, + character: 6, + }, + end: Position { + line: 3, + character: 5, + }, + }, + new_text: "$0disabled = false;\n ProcMacro \\{\n disabled,\n \\}", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], ); } @@ -2374,7 +2400,7 @@ struct ProcMacro { character: 5, }, }, - new_text: "${0:disabled} = false;\n ProcMacro {\n disabled,\n }", + new_text: "${0:disabled} = false;\n ProcMacro \\{\n disabled,\n \\}", insert_text_format: Some( Snippet, ), @@ -2439,7 +2465,7 @@ struct P { character: 5, }, }, - new_text: "${0:disabled} = false;\n ProcMacro {\n disabled,\n }", + new_text: "${0:disabled} = false;\n ProcMacro \\{\n disabled,\n \\}", insert_text_format: Some( Snippet, ), @@ -2609,6 +2635,43 @@ struct ProcMacro { ); } + #[test] + fn snippet_rendering_handle_dos_line_endings() { + // unix -> dos conversion should be handled after placing snippets + let mut edit = TextEdit::builder(); + edit.insert(6.into(), "\n\n->".to_owned()); + + let edit = edit.finish(); + let snippets = SnippetEdit::new(vec![Snippet::Tabstop(10.into())]); + + check_rendered_snippets_in_source( + "yeah\r\n<-tabstop here", + edit, + snippets, + expect![[r#" + [ + SnippetTextEdit { + range: Range { + start: Position { + line: 1, + character: 0, + }, + end: Position { + line: 1, + character: 0, + }, + }, + new_text: "\r\n\r\n->$0", + insert_text_format: Some( + Snippet, + ), + annotation_id: None, + }, + ] + "#]], + ) + } + // `Url` is not able to parse windows paths on unix machines. #[test] #[cfg(target_os = "windows")] |
