From ada43cc56ac2e337cc034f8052d7e5da61268c34 Mon Sep 17 00:00:00 2001 From: Kitson Kelly Date: Wed, 27 Jan 2021 07:50:13 +1100 Subject: [PATCH] fix(lsp): handle mbc properly when formatting (#9273) --- cli/lsp/language_server.rs | 82 ++++++++++++++++++- cli/lsp/text.rs | 56 +++++++++++-- .../lsp/did_open_notification_mbc_fmt.json | 12 +++ cli/tests/lsp/formatting_request_mbc_fmt.json | 14 ++++ 4 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 cli/tests/lsp/did_open_notification_mbc_fmt.json create mode 100644 cli/tests/lsp/formatting_request_mbc_fmt.json diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 55ae92c001..b322438fab 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -669,7 +669,7 @@ impl Inner { ) })? .unwrap(); - + let line_index = self.documents.line_index(&specifier); let file_path = if let Ok(file_path) = params.text_document.uri.to_file_path() { file_path @@ -685,7 +685,9 @@ impl Inner { // TODO(@kitsonk) this could be handled better in `cli/tools/fmt.rs` in the // future. match dprint::format_text(&file_path, &file_text, &config) { - Ok(new_text) => Some(text::get_edits(&file_text, &new_text)), + Ok(new_text) => { + Some(text::get_edits(&file_text, &new_text, line_index)) + } Err(err) => { warn!("Format error: {}", err); None @@ -702,6 +704,7 @@ impl Inner { Ok(Some(text_edits)) } } else { + self.client.show_message(MessageType::Warning, format!("Unable to format \"{}\". Likely due to unrecoverable syntax errors in the file.", specifier)).await; Ok(None) } } @@ -1524,6 +1527,81 @@ mod tests { harness.run().await; } + #[tokio::test] + async fn test_format_mbc() { + let mut harness = LspTestHarness::new(vec![ + ("initialize_request.json", LspResponse::RequestAny), + ("initialized_notification.json", LspResponse::None), + ("did_open_notification_mbc_fmt.json", LspResponse::None), + ( + "formatting_request_mbc_fmt.json", + LspResponse::Request( + 2, + json!([ + { + "range": { + "start": { + "line": 0, + "character": 12 + }, + "end": { + "line": 0, + "character": 13, + } + }, + "newText": "\"" + }, + { + "range": { + "start": { + "line": 0, + "character": 21 + }, + "end": { + "line": 0, + "character": 22 + } + }, + "newText": "\";" + }, + { + "range": { + "start": { + "line": 1, + "character": 12, + }, + "end": { + "line": 1, + "character": 13, + } + }, + "newText": "\"" + }, + { + "range": { + "start": { + "line": 1, + "character": 23, + }, + "end": { + "line": 1, + "character": 25, + } + }, + "newText": "\");" + } + ]), + ), + ), + ( + "shutdown_request.json", + LspResponse::Request(3, json!(null)), + ), + ("exit_notification.json", LspResponse::None), + ]); + harness.run().await; + } + #[tokio::test] async fn test_large_doc_change() { let mut harness = LspTestHarness::new(vec![ diff --git a/cli/lsp/text.rs b/cli/lsp/text.rs index 1d350c12f8..f444d639e6 100644 --- a/cli/lsp/text.rs +++ b/cli/lsp/text.rs @@ -181,8 +181,8 @@ impl LineIndex { /// Returns a u16 position based on a u8 offset. pub fn position_utf16(&self, offset: TextSize) -> lsp_types::Position { - let line = partition_point(&self.utf8_offsets, |&it| it <= offset) - 1; - let line_start_offset = self.utf8_offsets[line]; + let line = partition_point(&self.utf16_offsets, |&it| it <= offset) - 1; + let line_start_offset = self.utf16_offsets[line]; let col = offset - line_start_offset; lsp_types::Position { @@ -208,13 +208,21 @@ impl LineIndex { /// Compare two strings and return a vector of text edit records which are /// supported by the Language Server Protocol. -pub fn get_edits(a: &str, b: &str) -> Vec { +pub fn get_edits( + a: &str, + b: &str, + maybe_line_index: Option, +) -> Vec { if a == b { return vec![]; } let chunks = diff(a, b); let mut text_edits = Vec::::new(); - let line_index = LineIndex::new(a); + let line_index = if let Some(line_index) = maybe_line_index { + line_index + } else { + LineIndex::new(a) + }; let mut iter = chunks.iter().peekable(); let mut a_pos = TextSize::from(0); loop { @@ -565,7 +573,7 @@ const C: char = \"パ パ\"; fn test_get_edits() { let a = "abcdefg"; let b = "a\nb\nchije\nfg\n"; - let actual = get_edits(a, b); + let actual = get_edits(a, b, None); assert_eq!( actual, vec![ @@ -599,6 +607,44 @@ const C: char = \"パ パ\"; ); } + #[test] + fn test_get_edits_mbc() { + let a = "const bar = \"πŸ‘πŸ‡ΊπŸ‡ΈπŸ˜ƒ\";\nconsole.log('hello deno')\n"; + let b = "const bar = \"πŸ‘πŸ‡ΊπŸ‡ΈπŸ˜ƒ\";\nconsole.log(\"hello deno\");\n"; + let actual = get_edits(a, b, None); + assert_eq!( + actual, + vec![ + TextEdit { + range: lsp_types::Range { + start: lsp_types::Position { + line: 1, + character: 12 + }, + end: lsp_types::Position { + line: 1, + character: 13 + } + }, + new_text: "\"".to_string() + }, + TextEdit { + range: lsp_types::Range { + start: lsp_types::Position { + line: 1, + character: 23 + }, + end: lsp_types::Position { + line: 1, + character: 25 + } + }, + new_text: "\");".to_string() + }, + ] + ) + } + #[test] fn test_get_range_change() { let a = "abcdefg"; diff --git a/cli/tests/lsp/did_open_notification_mbc_fmt.json b/cli/tests/lsp/did_open_notification_mbc_fmt.json new file mode 100644 index 0000000000..528dad25dd --- /dev/null +++ b/cli/tests/lsp/did_open_notification_mbc_fmt.json @@ -0,0 +1,12 @@ +{ + "jsonrpc": "2.0", + "method": "textDocument/didOpen", + "params": { + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "const bar = 'πŸ‘πŸ‡ΊπŸ‡ΈπŸ˜ƒ'\nconsole.log('hello deno')\n" + } + } +} diff --git a/cli/tests/lsp/formatting_request_mbc_fmt.json b/cli/tests/lsp/formatting_request_mbc_fmt.json new file mode 100644 index 0000000000..f20cc18e69 --- /dev/null +++ b/cli/tests/lsp/formatting_request_mbc_fmt.json @@ -0,0 +1,14 @@ +{ + "jsonrpc": "2.0", + "id": 2, + "method": "textDocument/formatting", + "params": { + "textDocument": { + "uri": "file:///a/file.ts" + }, + "options": { + "tabSize": 2, + "insertSpaces": true + } + } +}