aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ide/src/display/structure.rs428
-rw-r--r--crates/ra_ide/src/folding_ranges.rs161
-rw-r--r--crates/rust-analyzer/src/to_proto.rs31
-rw-r--r--crates/test_utils/src/lib.rs27
4 files changed, 329 insertions, 318 deletions
diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs
index aad5a8e4d..c22a5d17b 100644
--- a/crates/ra_ide/src/display/structure.rs
+++ b/crates/ra_ide/src/display/structure.rs
@@ -173,12 +173,19 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
173 173
174#[cfg(test)] 174#[cfg(test)]
175mod tests { 175mod tests {
176 use expect::{expect, Expect};
177
176 use super::*; 178 use super::*;
177 use insta::assert_debug_snapshot; 179
180 fn check(ra_fixture: &str, expect: Expect) {
181 let file = SourceFile::parse(ra_fixture).ok().unwrap();
182 let structure = file_structure(&file);
183 expect.assert_debug_eq(&structure)
184 }
178 185
179 #[test] 186 #[test]
180 fn test_file_structure() { 187 fn test_file_structure() {
181 let file = SourceFile::parse( 188 check(
182 r#" 189 r#"
183struct Foo { 190struct Foo {
184 x: i32 191 x: i32
@@ -223,216 +230,211 @@ fn obsolete() {}
223#[deprecated(note = "for awhile")] 230#[deprecated(note = "for awhile")]
224fn very_obsolete() {} 231fn very_obsolete() {}
225"#, 232"#,
226 ) 233 expect![[r#"
227 .ok() 234 [
228 .unwrap(); 235 StructureNode {
229 let structure = file_structure(&file); 236 parent: None,
230 assert_debug_snapshot!(structure, 237 label: "Foo",
231 @r###" 238 navigation_range: 8..11,
232 [ 239 node_range: 1..26,
233 StructureNode { 240 kind: STRUCT_DEF,
234 parent: None, 241 detail: None,
235 label: "Foo", 242 deprecated: false,
236 navigation_range: 8..11, 243 },
237 node_range: 1..26, 244 StructureNode {
238 kind: STRUCT_DEF, 245 parent: Some(
239 detail: None, 246 0,
240 deprecated: false, 247 ),
241 }, 248 label: "x",
242 StructureNode { 249 navigation_range: 18..19,
243 parent: Some( 250 node_range: 18..24,
244 0, 251 kind: RECORD_FIELD_DEF,
245 ), 252 detail: Some(
246 label: "x", 253 "i32",
247 navigation_range: 18..19, 254 ),
248 node_range: 18..24, 255 deprecated: false,
249 kind: RECORD_FIELD_DEF, 256 },
250 detail: Some( 257 StructureNode {
251 "i32", 258 parent: None,
252 ), 259 label: "m",
253 deprecated: false, 260 navigation_range: 32..33,
254 }, 261 node_range: 28..158,
255 StructureNode { 262 kind: MODULE,
256 parent: None, 263 detail: None,
257 label: "m", 264 deprecated: false,
258 navigation_range: 32..33, 265 },
259 node_range: 28..158, 266 StructureNode {
260 kind: MODULE, 267 parent: Some(
261 detail: None, 268 2,
262 deprecated: false, 269 ),
263 }, 270 label: "bar1",
264 StructureNode { 271 navigation_range: 43..47,
265 parent: Some( 272 node_range: 40..52,
266 2, 273 kind: FN_DEF,
267 ), 274 detail: Some(
268 label: "bar1", 275 "fn()",
269 navigation_range: 43..47, 276 ),
270 node_range: 40..52, 277 deprecated: false,
271 kind: FN_DEF, 278 },
272 detail: Some( 279 StructureNode {
273 "fn()", 280 parent: Some(
274 ), 281 2,
275 deprecated: false, 282 ),
276 }, 283 label: "bar2",
277 StructureNode { 284 navigation_range: 60..64,
278 parent: Some( 285 node_range: 57..81,
279 2, 286 kind: FN_DEF,
280 ), 287 detail: Some(
281 label: "bar2", 288 "fn<T>(t: T) -> T",
282 navigation_range: 60..64, 289 ),
283 node_range: 57..81, 290 deprecated: false,
284 kind: FN_DEF, 291 },
285 detail: Some( 292 StructureNode {
286 "fn<T>(t: T) -> T", 293 parent: Some(
287 ), 294 2,
288 deprecated: false, 295 ),
289 }, 296 label: "bar3",
290 StructureNode { 297 navigation_range: 89..93,
291 parent: Some( 298 node_range: 86..156,
292 2, 299 kind: FN_DEF,
293 ), 300 detail: Some(
294 label: "bar3", 301 "fn<A, B>(a: A, b: B) -> Vec< u32 >",
295 navigation_range: 89..93, 302 ),
296 node_range: 86..156, 303 deprecated: false,
297 kind: FN_DEF, 304 },
298 detail: Some( 305 StructureNode {
299 "fn<A, B>(a: A, b: B) -> Vec< u32 >", 306 parent: None,
300 ), 307 label: "E",
301 deprecated: false, 308 navigation_range: 165..166,
302 }, 309 node_range: 160..180,
303 StructureNode { 310 kind: ENUM_DEF,
304 parent: None, 311 detail: None,
305 label: "E", 312 deprecated: false,
306 navigation_range: 165..166, 313 },
307 node_range: 160..180, 314 StructureNode {
308 kind: ENUM_DEF, 315 parent: Some(
309 detail: None, 316 6,
310 deprecated: false, 317 ),
311 }, 318 label: "X",
312 StructureNode { 319 navigation_range: 169..170,
313 parent: Some( 320 node_range: 169..170,
314 6, 321 kind: ENUM_VARIANT,
315 ), 322 detail: None,
316 label: "X", 323 deprecated: false,
317 navigation_range: 169..170, 324 },
318 node_range: 169..170, 325 StructureNode {
319 kind: ENUM_VARIANT, 326 parent: Some(
320 detail: None, 327 6,
321 deprecated: false, 328 ),
322 }, 329 label: "Y",
323 StructureNode { 330 navigation_range: 172..173,
324 parent: Some( 331 node_range: 172..178,
325 6, 332 kind: ENUM_VARIANT,
326 ), 333 detail: None,
327 label: "Y", 334 deprecated: false,
328 navigation_range: 172..173, 335 },
329 node_range: 172..178, 336 StructureNode {
330 kind: ENUM_VARIANT, 337 parent: None,
331 detail: None, 338 label: "T",
332 deprecated: false, 339 navigation_range: 186..187,
333 }, 340 node_range: 181..193,
334 StructureNode { 341 kind: TYPE_ALIAS_DEF,
335 parent: None, 342 detail: Some(
336 label: "T", 343 "()",
337 navigation_range: 186..187, 344 ),
338 node_range: 181..193, 345 deprecated: false,
339 kind: TYPE_ALIAS_DEF, 346 },
340 detail: Some( 347 StructureNode {
341 "()", 348 parent: None,
342 ), 349 label: "S",
343 deprecated: false, 350 navigation_range: 201..202,
344 }, 351 node_range: 194..213,
345 StructureNode { 352 kind: STATIC_DEF,
346 parent: None, 353 detail: Some(
347 label: "S", 354 "i32",
348 navigation_range: 201..202, 355 ),
349 node_range: 194..213, 356 deprecated: false,
350 kind: STATIC_DEF, 357 },
351 detail: Some( 358 StructureNode {
352 "i32", 359 parent: None,
353 ), 360 label: "C",
354 deprecated: false, 361 navigation_range: 220..221,
355 }, 362 node_range: 214..232,
356 StructureNode { 363 kind: CONST_DEF,
357 parent: None, 364 detail: Some(
358 label: "C", 365 "i32",
359 navigation_range: 220..221, 366 ),
360 node_range: 214..232, 367 deprecated: false,
361 kind: CONST_DEF, 368 },
362 detail: Some( 369 StructureNode {
363 "i32", 370 parent: None,
364 ), 371 label: "impl E",
365 deprecated: false, 372 navigation_range: 239..240,
366 }, 373 node_range: 234..243,
367 StructureNode { 374 kind: IMPL_DEF,
368 parent: None, 375 detail: None,
369 label: "impl E", 376 deprecated: false,
370 navigation_range: 239..240, 377 },
371 node_range: 234..243, 378 StructureNode {
372 kind: IMPL_DEF, 379 parent: None,
373 detail: None, 380 label: "impl fmt::Debug for E",
374 deprecated: false, 381 navigation_range: 265..266,
375 }, 382 node_range: 245..269,
376 StructureNode { 383 kind: IMPL_DEF,
377 parent: None, 384 detail: None,
378 label: "impl fmt::Debug for E", 385 deprecated: false,
379 navigation_range: 265..266, 386 },
380 node_range: 245..269, 387 StructureNode {
381 kind: IMPL_DEF, 388 parent: None,
382 detail: None, 389 label: "mc",
383 deprecated: false, 390 navigation_range: 284..286,
384 }, 391 node_range: 271..303,
385 StructureNode { 392 kind: MACRO_CALL,
386 parent: None, 393 detail: None,
387 label: "mc", 394 deprecated: false,
388 navigation_range: 284..286, 395 },
389 node_range: 271..303, 396 StructureNode {
390 kind: MACRO_CALL, 397 parent: None,
391 detail: None, 398 label: "mcexp",
392 deprecated: false, 399 navigation_range: 334..339,
393 }, 400 node_range: 305..356,
394 StructureNode { 401 kind: MACRO_CALL,
395 parent: None, 402 detail: None,
396 label: "mcexp", 403 deprecated: false,
397 navigation_range: 334..339, 404 },
398 node_range: 305..356, 405 StructureNode {
399 kind: MACRO_CALL, 406 parent: None,
400 detail: None, 407 label: "mcexp",
401 deprecated: false, 408 navigation_range: 387..392,
402 }, 409 node_range: 358..409,
403 StructureNode { 410 kind: MACRO_CALL,
404 parent: None, 411 detail: None,
405 label: "mcexp", 412 deprecated: false,
406 navigation_range: 387..392, 413 },
407 node_range: 358..409, 414 StructureNode {
408 kind: MACRO_CALL, 415 parent: None,
409 detail: None, 416 label: "obsolete",
410 deprecated: false, 417 navigation_range: 428..436,
411 }, 418 node_range: 411..441,
412 StructureNode { 419 kind: FN_DEF,
413 parent: None, 420 detail: Some(
414 label: "obsolete", 421 "fn()",
415 navigation_range: 428..436, 422 ),
416 node_range: 411..441, 423 deprecated: true,
417 kind: FN_DEF, 424 },
418 detail: Some( 425 StructureNode {
419 "fn()", 426 parent: None,
420 ), 427 label: "very_obsolete",
421 deprecated: true, 428 navigation_range: 481..494,
422 }, 429 node_range: 443..499,
423 StructureNode { 430 kind: FN_DEF,
424 parent: None, 431 detail: Some(
425 label: "very_obsolete", 432 "fn()",
426 navigation_range: 481..494, 433 ),
427 node_range: 443..499, 434 deprecated: true,
428 kind: FN_DEF, 435 },
429 detail: Some( 436 ]
430 "fn()", 437 "#]],
431 ), 438 );
432 deprecated: true,
433 },
434 ]
435 "###
436 );
437 } 439 }
438} 440}
diff --git a/crates/ra_ide/src/folding_ranges.rs b/crates/ra_ide/src/folding_ranges.rs
index 8657377de..5cec689f8 100644
--- a/crates/ra_ide/src/folding_ranges.rs
+++ b/crates/ra_ide/src/folding_ranges.rs
@@ -15,6 +15,7 @@ pub enum FoldKind {
15 Imports, 15 Imports,
16 Mods, 16 Mods,
17 Block, 17 Block,
18 ArgList,
18} 19}
19 20
20#[derive(Debug)] 21#[derive(Debug)]
@@ -83,6 +84,7 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> {
83 match kind { 84 match kind {
84 COMMENT => Some(FoldKind::Comment), 85 COMMENT => Some(FoldKind::Comment),
85 USE_ITEM => Some(FoldKind::Imports), 86 USE_ITEM => Some(FoldKind::Imports),
87 ARG_LIST => Some(FoldKind::ArgList),
86 RECORD_FIELD_DEF_LIST 88 RECORD_FIELD_DEF_LIST
87 | RECORD_FIELD_PAT_LIST 89 | RECORD_FIELD_PAT_LIST
88 | ITEM_LIST 90 | ITEM_LIST
@@ -196,89 +198,85 @@ fn contiguous_range_for_comment(
196 198
197#[cfg(test)] 199#[cfg(test)]
198mod tests { 200mod tests {
201 use test_utils::extract_tags;
202
199 use super::*; 203 use super::*;
200 use test_utils::extract_ranges;
201 204
202 fn do_check(text: &str, fold_kinds: &[FoldKind]) { 205 fn check(ra_fixture: &str) {
203 let (ranges, text) = extract_ranges(text, "fold"); 206 let (ranges, text) = extract_tags(ra_fixture, "fold");
207
204 let parse = SourceFile::parse(&text); 208 let parse = SourceFile::parse(&text);
205 let folds = folding_ranges(&parse.tree()); 209 let folds = folding_ranges(&parse.tree());
206
207 assert_eq!( 210 assert_eq!(
208 folds.len(), 211 folds.len(),
209 ranges.len(), 212 ranges.len(),
210 "The amount of folds is different than the expected amount" 213 "The amount of folds is different than the expected amount"
211 ); 214 );
212 assert_eq!( 215
213 folds.len(), 216 for (fold, (range, attr)) in folds.iter().zip(ranges.into_iter()) {
214 fold_kinds.len(),
215 "The amount of fold kinds is different than the expected amount"
216 );
217 for ((fold, range), fold_kind) in
218 folds.iter().zip(ranges.into_iter()).zip(fold_kinds.iter())
219 {
220 assert_eq!(fold.range.start(), range.start()); 217 assert_eq!(fold.range.start(), range.start());
221 assert_eq!(fold.range.end(), range.end()); 218 assert_eq!(fold.range.end(), range.end());
222 assert_eq!(&fold.kind, fold_kind); 219
220 let kind = match fold.kind {
221 FoldKind::Comment => "comment",
222 FoldKind::Imports => "imports",
223 FoldKind::Mods => "mods",
224 FoldKind::Block => "block",
225 FoldKind::ArgList => "arglist",
226 };
227 assert_eq!(kind, &attr.unwrap());
223 } 228 }
224 } 229 }
225 230
226 #[test] 231 #[test]
227 fn test_fold_comments() { 232 fn test_fold_comments() {
228 let text = r#" 233 check(
229<fold>// Hello 234 r#"
235<fold comment>// Hello
230// this is a multiline 236// this is a multiline
231// comment 237// comment
232//</fold> 238//</fold>
233 239
234// But this is not 240// But this is not
235 241
236fn main() <fold>{ 242fn main() <fold block>{
237 <fold>// We should 243 <fold comment>// We should
238 // also 244 // also
239 // fold 245 // fold
240 // this one.</fold> 246 // this one.</fold>
241 <fold>//! But this one is different 247 <fold comment>//! But this one is different
242 //! because it has another flavor</fold> 248 //! because it has another flavor</fold>
243 <fold>/* As does this 249 <fold comment>/* As does this
244 multiline comment */</fold> 250 multiline comment */</fold>
245}</fold>"#; 251}</fold>"#,
246 252 );
247 let fold_kinds = &[
248 FoldKind::Comment,
249 FoldKind::Block,
250 FoldKind::Comment,
251 FoldKind::Comment,
252 FoldKind::Comment,
253 ];
254 do_check(text, fold_kinds);
255 } 253 }
256 254
257 #[test] 255 #[test]
258 fn test_fold_imports() { 256 fn test_fold_imports() {
259 let text = r#" 257 check(
260<fold>use std::<fold>{ 258 r#"
259<fold imports>use std::<fold block>{
261 str, 260 str,
262 vec, 261 vec,
263 io as iop 262 io as iop
264}</fold>;</fold> 263}</fold>;</fold>
265 264
266fn main() <fold>{ 265fn main() <fold block>{
267}</fold>"#; 266}</fold>"#,
268 267 );
269 let folds = &[FoldKind::Imports, FoldKind::Block, FoldKind::Block];
270 do_check(text, folds);
271 } 268 }
272 269
273 #[test] 270 #[test]
274 fn test_fold_mods() { 271 fn test_fold_mods() {
275 let text = r#" 272 check(
273 r#"
276 274
277pub mod foo; 275pub mod foo;
278<fold>mod after_pub; 276<fold mods>mod after_pub;
279mod after_pub_next;</fold> 277mod after_pub_next;</fold>
280 278
281<fold>mod before_pub; 279<fold mods>mod before_pub;
282mod before_pub_next;</fold> 280mod before_pub_next;</fold>
283pub mod bar; 281pub mod bar;
284 282
@@ -286,90 +284,93 @@ mod not_folding_single;
286pub mod foobar; 284pub mod foobar;
287pub not_folding_single_next; 285pub not_folding_single_next;
288 286
289<fold>#[cfg(test)] 287<fold mods>#[cfg(test)]
290mod with_attribute; 288mod with_attribute;
291mod with_attribute_next;</fold> 289mod with_attribute_next;</fold>
292 290
293fn main() <fold>{ 291fn main() <fold block>{
294}</fold>"#; 292}</fold>"#,
295 293 );
296 let folds = &[FoldKind::Mods, FoldKind::Mods, FoldKind::Mods, FoldKind::Block];
297 do_check(text, folds);
298 } 294 }
299 295
300 #[test] 296 #[test]
301 fn test_fold_import_groups() { 297 fn test_fold_import_groups() {
302 let text = r#" 298 check(
303<fold>use std::str; 299 r#"
300<fold imports>use std::str;
304use std::vec; 301use std::vec;
305use std::io as iop;</fold> 302use std::io as iop;</fold>
306 303
307<fold>use std::mem; 304<fold imports>use std::mem;
308use std::f64;</fold> 305use std::f64;</fold>
309 306
310use std::collections::HashMap; 307use std::collections::HashMap;
311// Some random comment 308// Some random comment
312use std::collections::VecDeque; 309use std::collections::VecDeque;
313 310
314fn main() <fold>{ 311fn main() <fold block>{
315}</fold>"#; 312}</fold>"#,
316 313 );
317 let folds = &[FoldKind::Imports, FoldKind::Imports, FoldKind::Block];
318 do_check(text, folds);
319 } 314 }
320 315
321 #[test] 316 #[test]
322 fn test_fold_import_and_groups() { 317 fn test_fold_import_and_groups() {
323 let text = r#" 318 check(
324<fold>use std::str; 319 r#"
320<fold imports>use std::str;
325use std::vec; 321use std::vec;
326use std::io as iop;</fold> 322use std::io as iop;</fold>
327 323
328<fold>use std::mem; 324<fold imports>use std::mem;
329use std::f64;</fold> 325use std::f64;</fold>
330 326
331<fold>use std::collections::<fold>{ 327<fold imports>use std::collections::<fold block>{
332 HashMap, 328 HashMap,
333 VecDeque, 329 VecDeque,
334}</fold>;</fold> 330}</fold>;</fold>
335// Some random comment 331// Some random comment
336 332
337fn main() <fold>{ 333fn main() <fold block>{
338}</fold>"#; 334}</fold>"#,
339 335 );
340 let folds = &[
341 FoldKind::Imports,
342 FoldKind::Imports,
343 FoldKind::Imports,
344 FoldKind::Block,
345 FoldKind::Block,
346 ];
347 do_check(text, folds);
348 } 336 }
349 337
350 #[test] 338 #[test]
351 fn test_folds_macros() { 339 fn test_folds_macros() {
352 let text = r#" 340 check(
353macro_rules! foo <fold>{ 341 r#"
342macro_rules! foo <fold block>{
354 ($($tt:tt)*) => { $($tt)* } 343 ($($tt:tt)*) => { $($tt)* }
355}</fold> 344}</fold>
356"#; 345"#,
357 346 );
358 let folds = &[FoldKind::Block];
359 do_check(text, folds);
360 } 347 }
361 348
362 #[test] 349 #[test]
363 fn test_fold_match_arms() { 350 fn test_fold_match_arms() {
364 let text = r#" 351 check(
365fn main() <fold>{ 352 r#"
366 match 0 <fold>{ 353fn main() <fold block>{
354 match 0 <fold block>{
367 0 => 0, 355 0 => 0,
368 _ => 1, 356 _ => 1,
369 }</fold> 357 }</fold>
370}</fold>"#; 358}</fold>"#,
359 );
360 }
371 361
372 let folds = &[FoldKind::Block, FoldKind::Block]; 362 #[test]
373 do_check(text, folds); 363 fn fold_big_calls() {
364 check(
365 r#"
366fn main() <fold block>{
367 frobnicate<fold arglist>(
368 1,
369 2,
370 3,
371 )</fold>
372}</fold>
373 "#,
374 )
374 } 375 }
375} 376}
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index a0a58f689..95dd8e408 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -352,7 +352,7 @@ pub(crate) fn folding_range(
352 let kind = match fold.kind { 352 let kind = match fold.kind {
353 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment), 353 FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
354 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports), 354 FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
355 FoldKind::Mods | FoldKind::Block => None, 355 FoldKind::Mods | FoldKind::Block | FoldKind::ArgList => None,
356 }; 356 };
357 357
358 let range = range(line_index, fold.range); 358 let range = range(line_index, fold.range);
@@ -685,32 +685,27 @@ pub(crate) fn runnable(
685 685
686#[cfg(test)] 686#[cfg(test)]
687mod tests { 687mod tests {
688 use test_utils::extract_ranges; 688 use ra_ide::Analysis;
689 689
690 use super::*; 690 use super::*;
691 691
692 #[test] 692 #[test]
693 fn conv_fold_line_folding_only_fixup() { 693 fn conv_fold_line_folding_only_fixup() {
694 let text = r#"<fold>mod a; 694 let text = r#"mod a;
695mod b; 695mod b;
696mod c;</fold> 696mod c;
697 697
698fn main() <fold>{ 698fn main() {
699 if cond <fold>{ 699 if cond {
700 a::do_a(); 700 a::do_a();
701 }</fold> else <fold>{ 701 } else {
702 b::do_b(); 702 b::do_b();
703 }</fold> 703 }
704}</fold>"#; 704}"#;
705 705
706 let (ranges, text) = extract_ranges(text, "fold"); 706 let (analysis, file_id) = Analysis::from_single_file(text.to_string());
707 assert_eq!(ranges.len(), 4); 707 let folds = analysis.folding_ranges(file_id).unwrap();
708 let folds = vec![ 708 assert_eq!(folds.len(), 4);
709 Fold { range: ranges[0], kind: FoldKind::Mods },
710 Fold { range: ranges[1], kind: FoldKind::Block },
711 Fold { range: ranges[2], kind: FoldKind::Block },
712 Fold { range: ranges[3], kind: FoldKind::Block },
713 ];
714 709
715 let line_index = LineIndex::new(&text); 710 let line_index = LineIndex::new(&text);
716 let converted: Vec<lsp_types::FoldingRange> = 711 let converted: Vec<lsp_types::FoldingRange> =
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index fba5f4281..e4aa894ac 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -118,8 +118,8 @@ pub fn extract_range_or_offset(text: &str) -> (RangeOrOffset, String) {
118} 118}
119 119
120/// Extracts ranges, marked with `<tag> </tag>` pairs from the `text` 120/// Extracts ranges, marked with `<tag> </tag>` pairs from the `text`
121pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec<TextRange>, String) { 121pub fn extract_tags(mut text: &str, tag: &str) -> (Vec<(TextRange, Option<String>)>, String) {
122 let open = format!("<{}>", tag); 122 let open = format!("<{}", tag);
123 let close = format!("</{}>", tag); 123 let close = format!("</{}>", tag);
124 let mut ranges = Vec::new(); 124 let mut ranges = Vec::new();
125 let mut res = String::new(); 125 let mut res = String::new();
@@ -134,22 +134,35 @@ pub fn extract_ranges(mut text: &str, tag: &str) -> (Vec<TextRange>, String) {
134 res.push_str(&text[..i]); 134 res.push_str(&text[..i]);
135 text = &text[i..]; 135 text = &text[i..];
136 if text.starts_with(&open) { 136 if text.starts_with(&open) {
137 text = &text[open.len()..]; 137 let close_open = text.find('>').unwrap();
138 let attr = text[open.len()..close_open].trim();
139 let attr = if attr.is_empty() { None } else { Some(attr.to_string()) };
140 text = &text[close_open + '>'.len_utf8()..];
138 let from = TextSize::of(&res); 141 let from = TextSize::of(&res);
139 stack.push(from); 142 stack.push((from, attr));
140 } else if text.starts_with(&close) { 143 } else if text.starts_with(&close) {
141 text = &text[close.len()..]; 144 text = &text[close.len()..];
142 let from = stack.pop().unwrap_or_else(|| panic!("unmatched </{}>", tag)); 145 let (from, attr) =
146 stack.pop().unwrap_or_else(|| panic!("unmatched </{}>", tag));
143 let to = TextSize::of(&res); 147 let to = TextSize::of(&res);
144 ranges.push(TextRange::new(from, to)); 148 ranges.push((TextRange::new(from, to), attr));
149 } else {
150 res.push('<');
151 text = &text['<'.len_utf8()..];
145 } 152 }
146 } 153 }
147 } 154 }
148 } 155 }
149 assert!(stack.is_empty(), "unmatched <{}>", tag); 156 assert!(stack.is_empty(), "unmatched <{}>", tag);
150 ranges.sort_by_key(|r| (r.start(), r.end())); 157 ranges.sort_by_key(|r| (r.0.start(), r.0.end()));
151 (ranges, res) 158 (ranges, res)
152} 159}
160#[test]
161fn test_extract_tags() {
162 let (tags, text) = extract_tags(r#"<tag fn>fn <tag>main</tag>() {}</tag>"#, "tag");
163 let actual = tags.into_iter().map(|(range, attr)| (&text[range], attr)).collect::<Vec<_>>();
164 assert_eq!(actual, vec![("fn main() {}", Some("fn".into())), ("main", None),]);
165}
153 166
154/// Inserts `<|>` marker into the `text` at `offset`. 167/// Inserts `<|>` marker into the `text` at `offset`.
155pub fn add_cursor(text: &str, offset: TextSize) -> String { 168pub fn add_cursor(text: &str, offset: TextSize) -> String {