From da0960bab680d8a626e91124edcd3667cabbe23d Mon Sep 17 00:00:00 2001 From: Nereuxofficial <37740907+Nereuxofficial@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:51:33 +0100 Subject: [PATCH] languages: Correctly calculate ranges in `label_for_completion` (#44925) Closes #44825 Release Notes: - Fixed a case where an incorrect match could be generated in label_for_completion --------- Co-authored-by: Kirill Bulatov --- crates/language/src/language.rs | 5 +++- crates/languages/src/rust.rs | 48 ++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index a17c93f11a8705bf477d2eceb4f7bec9315cf6d1..b0805c4ddd9d1203a1f1a3071e8640fd016c1fb1 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -2425,7 +2425,10 @@ impl CodeLabel { "invalid filter range" ); runs.iter().for_each(|(range, _)| { - assert!(text.get(range.clone()).is_some(), "invalid run range"); + assert!( + text.get(range.clone()).is_some(), + "invalid run range with inputs. Requested range {range:?} in text '{text}'", + ); }); Self { runs, diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index ee64954196f58fe03f53a9e83fbbbea3f636449a..c10f76b079bf093e71b5444934196940e7b26d6c 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -375,16 +375,20 @@ impl LspAdapter for RustLspAdapter { let start_pos = range.start as usize; let end_pos = range.end as usize; - label.push_str(&snippet.text[text_pos..end_pos]); - text_pos = end_pos; + label.push_str(&snippet.text[text_pos..start_pos]); if start_pos == end_pos { let caret_start = label.len(); label.push('…'); runs.push((caret_start..label.len(), HighlightId::TABSTOP_INSERT_ID)); } else { - runs.push((start_pos..end_pos, HighlightId::TABSTOP_REPLACE_ID)); + let label_start = label.len(); + label.push_str(&snippet.text[start_pos..end_pos]); + let label_end = label.len(); + runs.push((label_start..label_end, HighlightId::TABSTOP_REPLACE_ID)); } + + text_pos = end_pos; } label.push_str(&snippet.text[text_pos..]); @@ -1592,6 +1596,44 @@ mod tests { ], )) ); + + // Test for correct range calculation with mixed empty and non-empty tabstops.(See https://github.com/zed-industries/zed/issues/44825) + let res = adapter + .label_for_completion( + &lsp::CompletionItem { + kind: Some(lsp::CompletionItemKind::STRUCT), + label: "Particles".to_string(), + insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::default(), + new_text: "Particles { pos_x: $1, pos_y: $2, vel_x: $3, vel_y: $4, acc_x: ${5:()}, acc_y: ${6:()}, mass: $7 }$0".to_string(), + })), + ..Default::default() + }, + &language, + ) + .await + .unwrap(); + + assert_eq!( + res, + CodeLabel::new( + "Particles { pos_x: …, pos_y: …, vel_x: …, vel_y: …, acc_x: (), acc_y: (), mass: … }".to_string(), + 0..9, + vec![ + (19..22, HighlightId::TABSTOP_INSERT_ID), + (31..34, HighlightId::TABSTOP_INSERT_ID), + (43..46, HighlightId::TABSTOP_INSERT_ID), + (55..58, HighlightId::TABSTOP_INSERT_ID), + (67..69, HighlightId::TABSTOP_REPLACE_ID), + (78..80, HighlightId::TABSTOP_REPLACE_ID), + (88..91, HighlightId::TABSTOP_INSERT_ID), + (0..9, highlight_type), + (60..65, highlight_field), + (71..76, highlight_field), + ], + ) + ); } #[gpui::test]