From d1ecdca22f67c97f354dab6b9e66022ecb4b506e Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 10 Oct 2025 19:00:30 +0800 Subject: [PATCH] Migrate `raw_string` assist to use `SyntaxEditor` --- .../ide-assists/src/handlers/raw_string.rs | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs index ca6d88870cce..d6d99b8b6d9d 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs @@ -1,6 +1,8 @@ -use std::borrow::Cow; - -use syntax::{AstToken, TextRange, TextSize, ast, ast::IsString}; +use ide_db::source_change::SourceChangeBuilder; +use syntax::{ + AstToken, + ast::{self, IsString, make::tokens::literal}, +}; use crate::{ AssistContext, AssistId, Assists, @@ -35,17 +37,10 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opt target, |edit| { let hashes = "#".repeat(required_hashes(&value).max(1)); - let range = token.syntax().text_range(); let raw_prefix = token.raw_prefix(); let suffix = string_suffix(token.text()).unwrap_or_default(); - let range = TextRange::new(range.start(), range.end() - TextSize::of(suffix)); - if matches!(value, Cow::Borrowed(_)) { - // Avoid replacing the whole string to better position the cursor. - edit.insert(range.start(), format!("{raw_prefix}{hashes}")); - edit.insert(range.end(), hashes); - } else { - edit.replace(range, format!("{raw_prefix}{hashes}\"{value}\"{hashes}")); - } + let new_str = format!("{raw_prefix}{hashes}\"{value}\"{hashes}{suffix}"); + replace_literal(&token, &new_str, edit, ctx); }, ) } @@ -81,21 +76,8 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> O let escaped = value.escape_default().to_string(); let suffix = string_suffix(token.text()).unwrap_or_default(); let prefix = string_prefix(token.text()).map_or("", |s| s.trim_end_matches('r')); - if let Some(offsets) = token.quote_offsets() - && token.text()[offsets.contents - token.syntax().text_range().start()] == escaped - { - let start_quote = offsets.quotes.0; - let start_quote = - TextRange::new(start_quote.start() + TextSize::of(prefix), start_quote.end()); - let end_quote = offsets.quotes.1; - let end_quote = - TextRange::new(end_quote.start(), end_quote.end() - TextSize::of(suffix)); - edit.replace(end_quote, "\""); - edit.replace(start_quote, "\""); - return; - } - - edit.replace(token.syntax().text_range(), format!("{prefix}\"{escaped}\"{suffix}")); + let new_str = format!("{prefix}\"{escaped}\"{suffix}"); + replace_literal(&token, &new_str, edit, ctx); }, ) } @@ -120,12 +102,14 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> if !token.is_raw() { return None; } - let text_range = token.syntax().text_range(); - let target = text_range; + let target = token.syntax().text_range(); acc.add(AssistId::refactor("add_hash"), "Add #", target, |edit| { - let suffix = string_suffix(token.text()).unwrap_or_default(); - edit.insert(text_range.start() + TextSize::of(token.raw_prefix()), "#"); - edit.insert(text_range.end() - TextSize::of(suffix), "#"); + let str = token.text(); + let suffix = string_suffix(str).unwrap_or_default(); + let raw_prefix = token.raw_prefix(); + let wrap_range = raw_prefix.len()..str.len() - suffix.len(); + let new_str = [raw_prefix, "#", &str[wrap_range], "#", suffix].concat(); + replace_literal(&token, &new_str, edit, ctx); }) } @@ -165,17 +149,38 @@ pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< acc.add(AssistId::refactor_rewrite("remove_hash"), "Remove #", text_range, |edit| { let suffix = string_suffix(text).unwrap_or_default(); - edit.delete(TextRange::at( - text_range.start() + TextSize::of(token.raw_prefix()), - TextSize::of('#'), - )); - edit.delete( - TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()) - - TextSize::of(suffix), - ); + let prefix = token.raw_prefix(); + let wrap_range = prefix.len() + 1..text.len() - suffix.len() - 1; + let new_str = [prefix, &text[wrap_range], suffix].concat(); + replace_literal(&token, &new_str, edit, ctx); }) } +fn replace_literal( + token: &impl AstToken, + new: &str, + builder: &mut SourceChangeBuilder, + ctx: &AssistContext<'_>, +) { + let token = token.syntax(); + let node = token.parent().expect("no parent token"); + let mut edit = builder.make_editor(&node); + let new_literal = literal(new); + + edit.replace(token, mut_token(new_literal)); + + builder.add_file_edits(ctx.vfs_file_id(), edit); +} + +fn mut_token(token: syntax::SyntaxToken) -> syntax::SyntaxToken { + let node = token.parent().expect("no parent token"); + node.clone_for_update() + .children_with_tokens() + .filter_map(|it| it.into_token()) + .find(|it| it.text_range() == token.text_range() && it.text() == token.text()) + .unwrap() +} + #[cfg(test)] mod tests { use super::*;