aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeander Tentrup <[email protected]>2020-04-22 14:28:35 +0100
committerLeander Tentrup <[email protected]>2020-04-22 14:28:35 +0100
commit445052f6d426043b543033f3fa4594fc1a09d7fa (patch)
tree3638c65c16b78f142101a87274edd94d647b6745
parentb2829a52161bc414f3b361c06b66633a234bba16 (diff)
Adapt format specifier highlighting to support escaped squences and unicode identifiers
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html5
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs63
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs5
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs280
4 files changed, 209 insertions, 144 deletions
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html
index d70627da0..433f2e0c5 100644
--- a/crates/ra_ide/src/snapshots/highlight_strings.html
+++ b/crates/ra_ide/src/snapshots/highlight_strings.html
@@ -74,4 +74,9 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
74 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">&gt;</span><span class="numeric_literal">8</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 right-aligned characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>); 74 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">&gt;</span><span class="numeric_literal">8</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 right-aligned characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
75 <span class="macro">println!</span>(<span class="string_literal">"Hello {{}}"</span>); 75 <span class="macro">println!</span>(<span class="string_literal">"Hello {{}}"</span>);
76 <span class="macro">println!</span>(<span class="string_literal">"{{ Hello"</span>); 76 <span class="macro">println!</span>(<span class="string_literal">"{{ Hello"</span>);
77
78 <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>);
79
80 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">\x41</span><span class="attribute">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>);
81 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">ничоси</span><span class="attribute">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>);
77}</code></pre> \ No newline at end of file 82}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 8ee3a78c6..b5fd3390f 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -245,28 +245,29 @@ pub(crate) fn highlight(
245 stack.push(); 245 stack.push();
246 if is_format_string { 246 if is_format_string {
247 string.lex_format_specifier(|piece_range, kind| { 247 string.lex_format_specifier(|piece_range, kind| {
248 let highlight = match kind { 248 if let Some(highlight) = highlight_format_specifier(kind) {
249 FormatSpecifier::Open 249 stack.add(HighlightedRange {
250 | FormatSpecifier::Close 250 range: piece_range + range.start(),
251 | FormatSpecifier::Colon 251 highlight: highlight.into(),
252 | FormatSpecifier::Fill 252 binding_hash: None,
253 | FormatSpecifier::Align 253 });
254 | FormatSpecifier::Sign 254 }
255 | FormatSpecifier::NumberSign 255 });
256 | FormatSpecifier::DollarSign 256 }
257 | FormatSpecifier::Dot 257 stack.pop();
258 | FormatSpecifier::Asterisk 258 } else if let Some(string) =
259 | FormatSpecifier::QuestionMark => HighlightTag::Attribute, 259 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
260 FormatSpecifier::Integer | FormatSpecifier::Zero => { 260 {
261 HighlightTag::NumericLiteral 261 stack.push();
262 } 262 if is_format_string {
263 FormatSpecifier::Identifier => HighlightTag::Local, 263 string.lex_format_specifier(|piece_range, kind| {
264 }; 264 if let Some(highlight) = highlight_format_specifier(kind) {
265 stack.add(HighlightedRange { 265 stack.add(HighlightedRange {
266 range: piece_range + range.start(), 266 range: piece_range + range.start(),
267 highlight: highlight.into(), 267 highlight: highlight.into(),
268 binding_hash: None, 268 binding_hash: None,
269 }); 269 });
270 }
270 }); 271 });
271 } 272 }
272 stack.pop(); 273 stack.pop();
@@ -277,6 +278,24 @@ pub(crate) fn highlight(
277 stack.flattened() 278 stack.flattened()
278} 279}
279 280
281fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
282 Some(match kind {
283 FormatSpecifier::Open
284 | FormatSpecifier::Close
285 | FormatSpecifier::Colon
286 | FormatSpecifier::Fill
287 | FormatSpecifier::Align
288 | FormatSpecifier::Sign
289 | FormatSpecifier::NumberSign
290 | FormatSpecifier::DollarSign
291 | FormatSpecifier::Dot
292 | FormatSpecifier::Asterisk
293 | FormatSpecifier::QuestionMark => HighlightTag::Attribute,
294 FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral,
295 FormatSpecifier::Identifier => HighlightTag::Local,
296 })
297}
298
280fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { 299fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
281 let path = macro_call.path()?; 300 let path = macro_call.path()?;
282 let name_ref = path.segment()?.name_ref()?; 301 let name_ref = path.segment()?.name_ref()?;
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index f198767ce..a9aae957f 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -223,6 +223,11 @@ fn main() {
223 println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56"); 223 println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
224 println!("Hello {{}}"); 224 println!("Hello {{}}");
225 println!("{{ Hello"); 225 println!("{{ Hello");
226
227 println!(r"Hello, {}!", "world");
228
229 println!("{\x41}", A = 92);
230 println!("{ничоси}", ничоси = 92);
226}"# 231}"#
227 .trim(), 232 .trim(),
228 ); 233 );
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs
index 3e5c56b19..aa34b682d 100644
--- a/crates/ra_syntax/src/ast/tokens.rs
+++ b/crates/ra_syntax/src/ast/tokens.rs
@@ -192,68 +192,76 @@ pub enum FormatSpecifier {
192} 192}
193 193
194pub trait HasFormatSpecifier: AstToken { 194pub trait HasFormatSpecifier: AstToken {
195 fn char_ranges(
196 &self,
197 ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>>;
198
195 fn lex_format_specifier<F>(&self, mut callback: F) 199 fn lex_format_specifier<F>(&self, mut callback: F)
196 where 200 where
197 F: FnMut(TextRange, FormatSpecifier), 201 F: FnMut(TextRange, FormatSpecifier),
198 { 202 {
199 let src = self.text().as_str(); 203 let char_ranges = if let Some(char_ranges) = self.char_ranges() {
200 let initial_len = src.len(); 204 char_ranges
201 let mut chars = src.chars(); 205 } else {
206 return;
207 };
208 let mut chars = char_ranges.iter().peekable();
202 209
203 while let Some(first_char) = chars.next() { 210 while let Some((range, first_char)) = chars.next() {
204 match first_char { 211 match first_char {
205 '{' => { 212 Ok('{') => {
206 // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax 213 // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax
207 if chars.clone().next() == Some('{') { 214 if let Some((_, Ok('{'))) = chars.peek() {
208 // Escaped format specifier, `{{` 215 // Escaped format specifier, `{{`
209 chars.next(); 216 chars.next();
210 continue; 217 continue;
211 } 218 }
212 219
213 let start = initial_len - chars.as_str().len() - first_char.len_utf8(); 220 callback(*range, FormatSpecifier::Open);
214 let end = initial_len - chars.as_str().len();
215 callback(
216 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
217 FormatSpecifier::Open,
218 );
219 221
220 // check for integer/identifier 222 // check for integer/identifier
221 match chars.clone().next().unwrap_or_default() { 223 match chars
224 .peek()
225 .and_then(|next| next.1.as_ref().ok())
226 .copied()
227 .unwrap_or_default()
228 {
222 '0'..='9' => { 229 '0'..='9' => {
223 // integer 230 // integer
224 read_integer(&mut chars, initial_len, &mut callback); 231 read_integer(&mut chars, &mut callback);
225 } 232 }
226 'a'..='z' | 'A'..='Z' | '_' => { 233 c if c == '_' || c.is_alphabetic() => {
227 // identifier 234 // identifier
228 read_identifier(&mut chars, initial_len, &mut callback); 235 read_identifier(&mut chars, &mut callback);
229 } 236 }
230 _ => {} 237 _ => {}
231 } 238 }
232 239
233 if chars.clone().next() == Some(':') { 240 if let Some((_, Ok(':'))) = chars.peek() {
234 skip_char_and_emit( 241 skip_char_and_emit(&mut chars, FormatSpecifier::Colon, &mut callback);
235 &mut chars,
236 initial_len,
237 FormatSpecifier::Colon,
238 &mut callback,
239 );
240 242
241 // check for fill/align 243 // check for fill/align
242 let mut cloned = chars.clone().take(2); 244 let mut cloned = chars.clone().take(2);
243 let first = cloned.next().unwrap_or_default(); 245 let first = cloned
244 let second = cloned.next().unwrap_or_default(); 246 .next()
247 .and_then(|next| next.1.as_ref().ok())
248 .copied()
249 .unwrap_or_default();
250 let second = cloned
251 .next()
252 .and_then(|next| next.1.as_ref().ok())
253 .copied()
254 .unwrap_or_default();
245 match second { 255 match second {
246 '<' | '^' | '>' => { 256 '<' | '^' | '>' => {
247 // alignment specifier, first char specifies fillment 257 // alignment specifier, first char specifies fillment
248 skip_char_and_emit( 258 skip_char_and_emit(
249 &mut chars, 259 &mut chars,
250 initial_len,
251 FormatSpecifier::Fill, 260 FormatSpecifier::Fill,
252 &mut callback, 261 &mut callback,
253 ); 262 );
254 skip_char_and_emit( 263 skip_char_and_emit(
255 &mut chars, 264 &mut chars,
256 initial_len,
257 FormatSpecifier::Align, 265 FormatSpecifier::Align,
258 &mut callback, 266 &mut callback,
259 ); 267 );
@@ -262,7 +270,6 @@ pub trait HasFormatSpecifier: AstToken {
262 '<' | '^' | '>' => { 270 '<' | '^' | '>' => {
263 skip_char_and_emit( 271 skip_char_and_emit(
264 &mut chars, 272 &mut chars,
265 initial_len,
266 FormatSpecifier::Align, 273 FormatSpecifier::Align,
267 &mut callback, 274 &mut callback,
268 ); 275 );
@@ -272,11 +279,15 @@ pub trait HasFormatSpecifier: AstToken {
272 } 279 }
273 280
274 // check for sign 281 // check for sign
275 match chars.clone().next().unwrap_or_default() { 282 match chars
283 .peek()
284 .and_then(|next| next.1.as_ref().ok())
285 .copied()
286 .unwrap_or_default()
287 {
276 '+' | '-' => { 288 '+' | '-' => {
277 skip_char_and_emit( 289 skip_char_and_emit(
278 &mut chars, 290 &mut chars,
279 initial_len,
280 FormatSpecifier::Sign, 291 FormatSpecifier::Sign,
281 &mut callback, 292 &mut callback,
282 ); 293 );
@@ -285,10 +296,9 @@ pub trait HasFormatSpecifier: AstToken {
285 } 296 }
286 297
287 // check for `#` 298 // check for `#`
288 if let Some('#') = chars.clone().next() { 299 if let Some((_, Ok('#'))) = chars.peek() {
289 skip_char_and_emit( 300 skip_char_and_emit(
290 &mut chars, 301 &mut chars,
291 initial_len,
292 FormatSpecifier::NumberSign, 302 FormatSpecifier::NumberSign,
293 &mut callback, 303 &mut callback,
294 ); 304 );
@@ -296,39 +306,39 @@ pub trait HasFormatSpecifier: AstToken {
296 306
297 // check for `0` 307 // check for `0`
298 let mut cloned = chars.clone().take(2); 308 let mut cloned = chars.clone().take(2);
299 let first = cloned.next(); 309 let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
300 let second = cloned.next(); 310 let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
301 311
302 if first == Some('0') && second != Some('$') { 312 if first == Some('0') && second != Some('$') {
303 skip_char_and_emit( 313 skip_char_and_emit(&mut chars, FormatSpecifier::Zero, &mut callback);
304 &mut chars,
305 initial_len,
306 FormatSpecifier::Zero,
307 &mut callback,
308 );
309 } 314 }
310 315
311 // width 316 // width
312 match chars.clone().next().unwrap_or_default() { 317 match chars
318 .peek()
319 .and_then(|next| next.1.as_ref().ok())
320 .copied()
321 .unwrap_or_default()
322 {
313 '0'..='9' => { 323 '0'..='9' => {
314 read_integer(&mut chars, initial_len, &mut callback); 324 read_integer(&mut chars, &mut callback);
315 if chars.clone().next() == Some('$') { 325 if let Some((_, Ok('$'))) = chars.peek() {
316 skip_char_and_emit( 326 skip_char_and_emit(
317 &mut chars, 327 &mut chars,
318 initial_len,
319 FormatSpecifier::DollarSign, 328 FormatSpecifier::DollarSign,
320 &mut callback, 329 &mut callback,
321 ); 330 );
322 } 331 }
323 } 332 }
324 'a'..='z' | 'A'..='Z' | '_' => { 333 c if c == '_' || c.is_alphabetic() => {
325 read_identifier(&mut chars, initial_len, &mut callback); 334 read_identifier(&mut chars, &mut callback);
326 if chars.clone().next() != Some('$') { 335 if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
336 != Some('$')
337 {
327 continue; 338 continue;
328 } 339 }
329 skip_char_and_emit( 340 skip_char_and_emit(
330 &mut chars, 341 &mut chars,
331 initial_len,
332 FormatSpecifier::DollarSign, 342 FormatSpecifier::DollarSign,
333 &mut callback, 343 &mut callback,
334 ); 344 );
@@ -337,42 +347,41 @@ pub trait HasFormatSpecifier: AstToken {
337 } 347 }
338 348
339 // precision 349 // precision
340 if chars.clone().next() == Some('.') { 350 if let Some((_, Ok('.'))) = chars.peek() {
341 skip_char_and_emit( 351 skip_char_and_emit(&mut chars, FormatSpecifier::Dot, &mut callback);
342 &mut chars, 352
343 initial_len, 353 match chars
344 FormatSpecifier::Dot, 354 .peek()
345 &mut callback, 355 .and_then(|next| next.1.as_ref().ok())
346 ); 356 .copied()
347 357 .unwrap_or_default()
348 match chars.clone().next().unwrap_or_default() { 358 {
349 '*' => { 359 '*' => {
350 skip_char_and_emit( 360 skip_char_and_emit(
351 &mut chars, 361 &mut chars,
352 initial_len,
353 FormatSpecifier::Asterisk, 362 FormatSpecifier::Asterisk,
354 &mut callback, 363 &mut callback,
355 ); 364 );
356 } 365 }
357 '0'..='9' => { 366 '0'..='9' => {
358 read_integer(&mut chars, initial_len, &mut callback); 367 read_integer(&mut chars, &mut callback);
359 if chars.clone().next() == Some('$') { 368 if let Some((_, Ok('$'))) = chars.peek() {
360 skip_char_and_emit( 369 skip_char_and_emit(
361 &mut chars, 370 &mut chars,
362 initial_len,
363 FormatSpecifier::DollarSign, 371 FormatSpecifier::DollarSign,
364 &mut callback, 372 &mut callback,
365 ); 373 );
366 } 374 }
367 } 375 }
368 'a'..='z' | 'A'..='Z' | '_' => { 376 c if c == '_' || c.is_alphabetic() => {
369 read_identifier(&mut chars, initial_len, &mut callback); 377 read_identifier(&mut chars, &mut callback);
370 if chars.clone().next() != Some('$') { 378 if chars.peek().and_then(|next| next.1.as_ref().ok()).copied()
379 != Some('$')
380 {
371 continue; 381 continue;
372 } 382 }
373 skip_char_and_emit( 383 skip_char_and_emit(
374 &mut chars, 384 &mut chars,
375 initial_len,
376 FormatSpecifier::DollarSign, 385 FormatSpecifier::DollarSign,
377 &mut callback, 386 &mut callback,
378 ); 387 );
@@ -384,25 +393,29 @@ pub trait HasFormatSpecifier: AstToken {
384 } 393 }
385 394
386 // type 395 // type
387 match chars.clone().next().unwrap_or_default() { 396 match chars
397 .peek()
398 .and_then(|next| next.1.as_ref().ok())
399 .copied()
400 .unwrap_or_default()
401 {
388 '?' => { 402 '?' => {
389 skip_char_and_emit( 403 skip_char_and_emit(
390 &mut chars, 404 &mut chars,
391 initial_len,
392 FormatSpecifier::QuestionMark, 405 FormatSpecifier::QuestionMark,
393 &mut callback, 406 &mut callback,
394 ); 407 );
395 } 408 }
396 'a'..='z' | 'A'..='Z' | '_' => { 409 c if c == '_' || c.is_alphabetic() => {
397 read_identifier(&mut chars, initial_len, &mut callback); 410 read_identifier(&mut chars, &mut callback);
398 } 411 }
399 _ => {} 412 _ => {}
400 } 413 }
401 } 414 }
402 415
403 let mut cloned = chars.clone().take(2); 416 let mut cloned = chars.clone().take(2);
404 let first = cloned.next(); 417 let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
405 let second = cloned.next(); 418 let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied();
406 if first != Some('}') { 419 if first != Some('}') {
407 continue; 420 continue;
408 } 421 }
@@ -410,15 +423,10 @@ pub trait HasFormatSpecifier: AstToken {
410 // Escaped format end specifier, `}}` 423 // Escaped format end specifier, `}}`
411 continue; 424 continue;
412 } 425 }
413 skip_char_and_emit( 426 skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback);
414 &mut chars,
415 initial_len,
416 FormatSpecifier::Close,
417 &mut callback,
418 );
419 } 427 }
420 _ => { 428 _ => {
421 while let Some(next_char) = chars.clone().next() { 429 while let Some((_, Ok(next_char))) = chars.peek() {
422 match next_char { 430 match next_char {
423 '{' => break, 431 '{' => break,
424 _ => {} 432 _ => {}
@@ -429,69 +437,97 @@ pub trait HasFormatSpecifier: AstToken {
429 }; 437 };
430 } 438 }
431 439
432 fn skip_char_and_emit<F>( 440 fn skip_char_and_emit<'a, I, F>(
433 chars: &mut std::str::Chars, 441 chars: &mut std::iter::Peekable<I>,
434 initial_len: usize,
435 emit: FormatSpecifier, 442 emit: FormatSpecifier,
436 callback: &mut F, 443 callback: &mut F,
437 ) where 444 ) where
445 I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
438 F: FnMut(TextRange, FormatSpecifier), 446 F: FnMut(TextRange, FormatSpecifier),
439 { 447 {
440 let start = initial_len - chars.as_str().len(); 448 let (range, _) = chars.next().unwrap();
441 chars.next(); 449 callback(*range, emit);
442 let end = initial_len - chars.as_str().len();
443 callback(
444 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
445 emit,
446 );
447 } 450 }
448 451
449 fn read_integer<F>(chars: &mut std::str::Chars, initial_len: usize, callback: &mut F) 452 fn read_integer<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
450 where 453 where
454 I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
451 F: FnMut(TextRange, FormatSpecifier), 455 F: FnMut(TextRange, FormatSpecifier),
452 { 456 {
453 let start = initial_len - chars.as_str().len(); 457 let (mut range, c) = chars.next().unwrap();
454 chars.next(); 458 assert!(c.as_ref().unwrap().is_ascii_digit());
455 while let Some(next_char) = chars.clone().next() { 459 while let Some((r, Ok(next_char))) = chars.peek() {
456 match next_char { 460 if next_char.is_ascii_digit() {
457 '0'..='9' => { 461 chars.next();
458 chars.next(); 462 range = range.extend_to(r);
459 } 463 } else {
460 _ => { 464 break;
461 break;
462 }
463 } 465 }
464 } 466 }
465 let end = initial_len - chars.as_str().len(); 467 callback(range, FormatSpecifier::Integer);
466 callback(
467 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
468 FormatSpecifier::Integer,
469 );
470 } 468 }
471 fn read_identifier<F>(chars: &mut std::str::Chars, initial_len: usize, callback: &mut F) 469
470 fn read_identifier<'a, I, F>(chars: &mut std::iter::Peekable<I>, callback: &mut F)
472 where 471 where
472 I: Iterator<Item = &'a (TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>,
473 F: FnMut(TextRange, FormatSpecifier), 473 F: FnMut(TextRange, FormatSpecifier),
474 { 474 {
475 let start = initial_len - chars.as_str().len(); 475 let (mut range, c) = chars.next().unwrap();
476 chars.next(); 476 assert!(c.as_ref().unwrap().is_alphabetic() || *c.as_ref().unwrap() == '_');
477 while let Some(next_char) = chars.clone().next() { 477 while let Some((r, Ok(next_char))) = chars.peek() {
478 match next_char { 478 if *next_char == '_' || next_char.is_ascii_digit() || next_char.is_alphabetic() {
479 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => { 479 chars.next();
480 chars.next(); 480 range = range.extend_to(r);
481 } 481 } else {
482 _ => { 482 break;
483 break;
484 }
485 } 483 }
486 } 484 }
487 let end = initial_len - chars.as_str().len(); 485 callback(range, FormatSpecifier::Identifier);
488 callback(
489 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
490 FormatSpecifier::Identifier,
491 );
492 } 486 }
493 } 487 }
494} 488}
495 489
496impl HasFormatSpecifier for String {} 490impl HasFormatSpecifier for String {
497impl HasFormatSpecifier for RawString {} 491 fn char_ranges(
492 &self,
493 ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
494 let text = self.text().as_str();
495 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
496 let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
497
498 let mut res = Vec::with_capacity(text.len());
499 rustc_lexer::unescape::unescape_str(text, &mut |range, unescaped_char| {
500 res.push((
501 TextRange::from_to(
502 TextUnit::from_usize(range.start),
503 TextUnit::from_usize(range.end),
504 ) + offset,
505 unescaped_char,
506 ))
507 });
508
509 Some(res)
510 }
511}
512
513impl HasFormatSpecifier for RawString {
514 fn char_ranges(
515 &self,
516 ) -> Option<Vec<(TextRange, Result<char, rustc_lexer::unescape::EscapeError>)>> {
517 let text = self.text().as_str();
518 let text = &text[self.text_range_between_quotes()? - self.syntax().text_range().start()];
519 let offset = self.text_range_between_quotes()?.start() - self.syntax().text_range().start();
520
521 let mut res = Vec::with_capacity(text.len());
522 for (idx, c) in text.char_indices() {
523 res.push((
524 TextRange::from_to(
525 TextUnit::from_usize(idx),
526 TextUnit::from_usize(idx + c.len_utf8()),
527 ) + offset,
528 Ok(c),
529 ));
530 }
531 Some(res)
532 }
533}