diff options
-rw-r--r-- | crates/ra_ide/src/snapshots/highlight_strings.html | 5 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting.rs | 63 | ||||
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting/tests.rs | 5 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/tokens.rs | 280 |
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">></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">></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 | ||
281 | fn 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 | |||
280 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 299 | fn 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 | ||
194 | pub trait HasFormatSpecifier: AstToken { | 194 | pub 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 | ||
496 | impl HasFormatSpecifier for String {} | 490 | impl HasFormatSpecifier for String { |
497 | impl 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 | |||
513 | impl 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 | } | ||