diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_analysis/Cargo.toml | 6 | ||||
-rw-r--r-- | crates/ra_analysis/src/completion.rs | 17 | ||||
-rw-r--r-- | crates/ra_analysis/src/input.rs | 2 | ||||
-rw-r--r-- | crates/ra_analysis/src/lib.rs | 3 | ||||
-rw-r--r-- | crates/ra_analysis/src/mock_analysis.rs | 84 | ||||
-rw-r--r-- | crates/ra_analysis/tests/tests.rs | 105 | ||||
-rw-r--r-- | crates/test_utils/src/lib.rs | 26 |
7 files changed, 167 insertions, 76 deletions
diff --git a/crates/ra_analysis/Cargo.toml b/crates/ra_analysis/Cargo.toml index 892e34235..deddf41f0 100644 --- a/crates/ra_analysis/Cargo.toml +++ b/crates/ra_analysis/Cargo.toml | |||
@@ -9,10 +9,8 @@ log = "0.4.5" | |||
9 | relative-path = "0.4.0" | 9 | relative-path = "0.4.0" |
10 | rayon = "1.0.2" | 10 | rayon = "1.0.2" |
11 | fst = "0.3.1" | 11 | fst = "0.3.1" |
12 | ra_syntax = { path = "../ra_syntax" } | ||
13 | ra_editor = { path = "../ra_editor" } | ||
14 | salsa = "0.7.0" | 12 | salsa = "0.7.0" |
15 | rustc-hash = "1.0" | 13 | rustc-hash = "1.0" |
16 | 14 | ra_syntax = { path = "../ra_syntax" } | |
17 | [dev-dependencies] | 15 | ra_editor = { path = "../ra_editor" } |
18 | test_utils = { path = "../test_utils" } | 16 | test_utils = { path = "../test_utils" } |
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 340ae3f66..286b6c376 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs | |||
@@ -368,18 +368,15 @@ fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<Completi | |||
368 | 368 | ||
369 | #[cfg(test)] | 369 | #[cfg(test)] |
370 | mod tests { | 370 | mod tests { |
371 | use test_utils::{assert_eq_dbg, extract_offset}; | 371 | use test_utils::{assert_eq_dbg}; |
372 | 372 | ||
373 | use crate::FileId; | 373 | use crate::mock_analysis::{single_file_with_position}; |
374 | use crate::mock_analysis::MockAnalysis; | ||
375 | 374 | ||
376 | use super::*; | 375 | use super::*; |
377 | 376 | ||
378 | fn check_scope_completion(code: &str, expected_completions: &str) { | 377 | fn check_scope_completion(code: &str, expected_completions: &str) { |
379 | let (off, code) = extract_offset(&code); | 378 | let (analysis, position) = single_file_with_position(code); |
380 | let analysis = MockAnalysis::with_files(&[("/main.rs", &code)]).analysis(); | 379 | let completions = scope_completion(&analysis.imp.db, position.file_id, position.offset) |
381 | let file_id = FileId(1); | ||
382 | let completions = scope_completion(&analysis.imp.db, file_id, off) | ||
383 | .unwrap() | 380 | .unwrap() |
384 | .into_iter() | 381 | .into_iter() |
385 | .filter(|c| c.snippet.is_none()) | 382 | .filter(|c| c.snippet.is_none()) |
@@ -388,10 +385,8 @@ mod tests { | |||
388 | } | 385 | } |
389 | 386 | ||
390 | fn check_snippet_completion(code: &str, expected_completions: &str) { | 387 | fn check_snippet_completion(code: &str, expected_completions: &str) { |
391 | let (off, code) = extract_offset(&code); | 388 | let (analysis, position) = single_file_with_position(code); |
392 | let analysis = MockAnalysis::with_files(&[("/main.rs", &code)]).analysis(); | 389 | let completions = scope_completion(&analysis.imp.db, position.file_id, position.offset) |
393 | let file_id = FileId(1); | ||
394 | let completions = scope_completion(&analysis.imp.db, file_id, off) | ||
395 | .unwrap() | 390 | .unwrap() |
396 | .into_iter() | 391 | .into_iter() |
397 | .filter(|c| c.snippet.is_some()) | 392 | .filter(|c| c.snippet.is_some()) |
diff --git a/crates/ra_analysis/src/input.rs b/crates/ra_analysis/src/input.rs index fd63182c7..b89b45133 100644 --- a/crates/ra_analysis/src/input.rs +++ b/crates/ra_analysis/src/input.rs | |||
@@ -25,7 +25,7 @@ impl CrateGraph { | |||
25 | pub fn new() -> CrateGraph { | 25 | pub fn new() -> CrateGraph { |
26 | CrateGraph::default() | 26 | CrateGraph::default() |
27 | } | 27 | } |
28 | pub fn add_crate_root(&mut self, file_id: FileId) -> CrateId{ | 28 | pub fn add_crate_root(&mut self, file_id: FileId) -> CrateId { |
29 | let crate_id = CrateId(self.crate_roots.len() as u32); | 29 | let crate_id = CrateId(self.crate_roots.len() as u32); |
30 | let prev = self.crate_roots.insert(crate_id, file_id); | 30 | let prev = self.crate_roots.insert(crate_id, file_id); |
31 | assert!(prev.is_none()); | 31 | assert!(prev.is_none()); |
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index 776010281..e75411ec9 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs | |||
@@ -13,7 +13,7 @@ mod imp; | |||
13 | mod symbol_index; | 13 | mod symbol_index; |
14 | mod completion; | 14 | mod completion; |
15 | mod syntax_ptr; | 15 | mod syntax_ptr; |
16 | mod mock_analysis; | 16 | pub mod mock_analysis; |
17 | 17 | ||
18 | use std::{ | 18 | use std::{ |
19 | fmt, | 19 | fmt, |
@@ -33,7 +33,6 @@ pub use crate::{ | |||
33 | descriptors::function::FnDescriptor, | 33 | descriptors::function::FnDescriptor, |
34 | completion::CompletionItem, | 34 | completion::CompletionItem, |
35 | input::{FileId, FileResolver, CrateGraph, CrateId}, | 35 | input::{FileId, FileResolver, CrateGraph, CrateId}, |
36 | mock_analysis::MockAnalysis, | ||
37 | }; | 36 | }; |
38 | pub use ra_editor::{ | 37 | pub use ra_editor::{ |
39 | FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, | 38 | FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, |
diff --git a/crates/ra_analysis/src/mock_analysis.rs b/crates/ra_analysis/src/mock_analysis.rs index 1c1dbee7c..f72911192 100644 --- a/crates/ra_analysis/src/mock_analysis.rs +++ b/crates/ra_analysis/src/mock_analysis.rs | |||
@@ -2,11 +2,19 @@ | |||
2 | use std::sync::Arc; | 2 | use std::sync::Arc; |
3 | 3 | ||
4 | use relative_path::{RelativePath, RelativePathBuf}; | 4 | use relative_path::{RelativePath, RelativePathBuf}; |
5 | use ra_syntax::TextUnit; | ||
6 | use test_utils::{extract_offset, parse_fixture, CURSOR_MARKER}; | ||
5 | 7 | ||
6 | use crate::{ | 8 | use crate::{ |
7 | AnalysisChange, Analysis, AnalysisHost, FileId, FileResolver, | 9 | AnalysisChange, Analysis, AnalysisHost, FileId, FileResolver, |
8 | }; | 10 | }; |
9 | 11 | ||
12 | #[derive(Debug)] | ||
13 | pub struct FilePosition { | ||
14 | pub file_id: FileId, | ||
15 | pub offset: TextUnit, | ||
16 | } | ||
17 | |||
10 | /// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis | 18 | /// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis |
11 | /// from a set of in-memory files. | 19 | /// from a set of in-memory files. |
12 | #[derive(Debug, Default)] | 20 | #[derive(Debug, Default)] |
@@ -18,11 +26,57 @@ impl MockAnalysis { | |||
18 | pub fn new() -> MockAnalysis { | 26 | pub fn new() -> MockAnalysis { |
19 | MockAnalysis::default() | 27 | MockAnalysis::default() |
20 | } | 28 | } |
21 | pub fn with_files(files: &[(&str, &str)]) -> MockAnalysis { | 29 | /// Creates `MockAnalysis` using a fixture data in the following format: |
22 | let files = files.iter() | 30 | /// |
23 | .map(|it| (it.0.to_string(), it.1.to_string())) | 31 | /// ```notrust |
24 | .collect(); | 32 | /// //- /main.rs |
25 | MockAnalysis { files } | 33 | /// mod foo; |
34 | /// fn main() {} | ||
35 | /// | ||
36 | /// //- /foo.rs | ||
37 | /// struct Baz; | ||
38 | /// ``` | ||
39 | pub fn with_files(fixture: &str) -> MockAnalysis { | ||
40 | let mut res = MockAnalysis::new(); | ||
41 | for entry in parse_fixture(fixture) { | ||
42 | res.add_file(&entry.meta, &entry.text); | ||
43 | } | ||
44 | res | ||
45 | } | ||
46 | |||
47 | /// Same as `with_files`, but requires that a single file contains a `<|>` marker, | ||
48 | /// whose position is also returned. | ||
49 | pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) { | ||
50 | let mut position = None; | ||
51 | let mut res = MockAnalysis::new(); | ||
52 | for entry in parse_fixture(fixture) { | ||
53 | if entry.text.contains(CURSOR_MARKER) { | ||
54 | assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); | ||
55 | position = Some(res.add_file_with_position(&entry.meta, &entry.text)); | ||
56 | } else { | ||
57 | res.add_file(&entry.meta, &entry.text); | ||
58 | } | ||
59 | } | ||
60 | let position = position.expect("expected a marker (<|>)"); | ||
61 | (res, position) | ||
62 | } | ||
63 | |||
64 | pub fn add_file(&mut self, path: &str, text: &str) -> FileId { | ||
65 | let file_id = FileId((self.files.len() + 1) as u32); | ||
66 | self.files.push((path.to_string(), text.to_string())); | ||
67 | file_id | ||
68 | } | ||
69 | pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { | ||
70 | let (offset, text) = extract_offset(text); | ||
71 | let file_id = FileId((self.files.len() + 1) as u32); | ||
72 | self.files.push((path.to_string(), text.to_string())); | ||
73 | FilePosition { file_id, offset } | ||
74 | } | ||
75 | pub fn id_of(&self, path: &str) -> FileId { | ||
76 | let (idx, _) = self.files.iter().enumerate() | ||
77 | .find(|(_, (p, _text))| path == p) | ||
78 | .expect("no file in this mock"); | ||
79 | FileId(idx as u32 + 1) | ||
26 | } | 80 | } |
27 | pub fn analysis_host(self) -> AnalysisHost { | 81 | pub fn analysis_host(self) -> AnalysisHost { |
28 | let mut host = AnalysisHost::new(); | 82 | let mut host = AnalysisHost::new(); |
@@ -44,6 +98,26 @@ impl MockAnalysis { | |||
44 | } | 98 | } |
45 | } | 99 | } |
46 | 100 | ||
101 | /// Creates analysis from a multi-file fixture, returns positions marked with <|>. | ||
102 | pub fn analysis_and_position(fixture: &str) -> (Analysis, FilePosition) { | ||
103 | let (mock, position) = MockAnalysis::with_files_and_position(fixture); | ||
104 | (mock.analysis(), position) | ||
105 | } | ||
106 | |||
107 | /// Creates analysis for a single file. | ||
108 | pub fn single_file(code: &str) -> (Analysis, FileId) { | ||
109 | let mut mock = MockAnalysis::new(); | ||
110 | let file_id = mock.add_file("/main.rs", code); | ||
111 | (mock.analysis(), file_id) | ||
112 | } | ||
113 | |||
114 | /// Creates analysis for a single file, returns position marked with <|>. | ||
115 | pub fn single_file_with_position(code: &str) -> (Analysis, FilePosition) { | ||
116 | let mut mock = MockAnalysis::new(); | ||
117 | let pos = mock.add_file_with_position("/main.rs", code); | ||
118 | (mock.analysis(), pos) | ||
119 | } | ||
120 | |||
47 | #[derive(Debug)] | 121 | #[derive(Debug)] |
48 | struct FileMap(Vec<(FileId, RelativePathBuf)>); | 122 | struct FileMap(Vec<(FileId, RelativePathBuf)>); |
49 | 123 | ||
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs index f5683aec5..94e025677 100644 --- a/crates/ra_analysis/tests/tests.rs +++ b/crates/ra_analysis/tests/tests.rs | |||
@@ -5,38 +5,42 @@ extern crate relative_path; | |||
5 | extern crate rustc_hash; | 5 | extern crate rustc_hash; |
6 | extern crate test_utils; | 6 | extern crate test_utils; |
7 | 7 | ||
8 | use ra_syntax::TextRange; | 8 | use ra_syntax::{TextRange}; |
9 | use test_utils::{assert_eq_dbg, extract_offset}; | 9 | use test_utils::{assert_eq_dbg}; |
10 | 10 | ||
11 | use ra_analysis::{ | 11 | use ra_analysis::{ |
12 | MockAnalysis, | 12 | AnalysisChange, CrateGraph, FileId, FnDescriptor, |
13 | AnalysisChange, Analysis, CrateGraph, CrateId, FileId, FnDescriptor, | 13 | mock_analysis::{MockAnalysis, single_file, single_file_with_position, analysis_and_position}, |
14 | }; | 14 | }; |
15 | 15 | ||
16 | fn analysis(files: &[(&str, &str)]) -> Analysis { | ||
17 | MockAnalysis::with_files(files).analysis() | ||
18 | } | ||
19 | |||
20 | fn get_signature(text: &str) -> (FnDescriptor, Option<usize>) { | 16 | fn get_signature(text: &str) -> (FnDescriptor, Option<usize>) { |
21 | let (offset, code) = extract_offset(text); | 17 | let (analysis, position) = single_file_with_position(text); |
22 | let code = code.as_str(); | 18 | analysis.resolve_callable(position.file_id, position.offset).unwrap().unwrap() |
23 | |||
24 | let snap = analysis(&[("/lib.rs", code)]); | ||
25 | |||
26 | snap.resolve_callable(FileId(1), offset).unwrap().unwrap() | ||
27 | } | 19 | } |
28 | 20 | ||
29 | #[test] | 21 | #[test] |
30 | fn test_resolve_module() { | 22 | fn test_resolve_module() { |
31 | let snap = analysis(&[("/lib.rs", "mod foo;"), ("/foo.rs", "")]); | 23 | let (analysis, pos) = analysis_and_position(" |
32 | let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into()).unwrap(); | 24 | //- /lib.rs |
25 | mod <|>foo; | ||
26 | //- /foo.rs | ||
27 | // empty | ||
28 | "); | ||
29 | |||
30 | let symbols = analysis.approximately_resolve_symbol(pos.file_id, pos.offset).unwrap(); | ||
33 | assert_eq_dbg( | 31 | assert_eq_dbg( |
34 | r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#, | 32 | r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#, |
35 | &symbols, | 33 | &symbols, |
36 | ); | 34 | ); |
37 | 35 | ||
38 | let snap = analysis(&[("/lib.rs", "mod foo;"), ("/foo/mod.rs", "")]); | 36 | let (analysis, pos) = analysis_and_position(" |
39 | let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into()).unwrap(); | 37 | //- /lib.rs |
38 | mod <|>foo; | ||
39 | //- /foo/mod.rs | ||
40 | // empty | ||
41 | "); | ||
42 | |||
43 | let symbols = analysis.approximately_resolve_symbol(pos.file_id, pos.offset).unwrap(); | ||
40 | assert_eq_dbg( | 44 | assert_eq_dbg( |
41 | r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#, | 45 | r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#, |
42 | &symbols, | 46 | &symbols, |
@@ -45,8 +49,8 @@ fn test_resolve_module() { | |||
45 | 49 | ||
46 | #[test] | 50 | #[test] |
47 | fn test_unresolved_module_diagnostic() { | 51 | fn test_unresolved_module_diagnostic() { |
48 | let snap = analysis(&[("/lib.rs", "mod foo;")]); | 52 | let (analysis, file_id) = single_file("mod foo;"); |
49 | let diagnostics = snap.diagnostics(FileId(1)).unwrap(); | 53 | let diagnostics = analysis.diagnostics(file_id).unwrap(); |
50 | assert_eq_dbg( | 54 | assert_eq_dbg( |
51 | r#"[Diagnostic { | 55 | r#"[Diagnostic { |
52 | message: "unresolved module", | 56 | message: "unresolved module", |
@@ -62,15 +66,20 @@ fn test_unresolved_module_diagnostic() { | |||
62 | 66 | ||
63 | #[test] | 67 | #[test] |
64 | fn test_unresolved_module_diagnostic_no_diag_for_inline_mode() { | 68 | fn test_unresolved_module_diagnostic_no_diag_for_inline_mode() { |
65 | let snap = analysis(&[("/lib.rs", "mod foo {}")]); | 69 | let (analysis, file_id) = single_file("mod foo {}"); |
66 | let diagnostics = snap.diagnostics(FileId(1)).unwrap(); | 70 | let diagnostics = analysis.diagnostics(file_id).unwrap(); |
67 | assert_eq_dbg(r#"[]"#, &diagnostics); | 71 | assert_eq_dbg(r#"[]"#, &diagnostics); |
68 | } | 72 | } |
69 | 73 | ||
70 | #[test] | 74 | #[test] |
71 | fn test_resolve_parent_module() { | 75 | fn test_resolve_parent_module() { |
72 | let snap = analysis(&[("/lib.rs", "mod foo;"), ("/foo.rs", "")]); | 76 | let (analysis, pos) = analysis_and_position(" |
73 | let symbols = snap.parent_module(FileId(2)).unwrap(); | 77 | //- /lib.rs |
78 | mod foo; | ||
79 | //- /foo.rs | ||
80 | <|>// empty | ||
81 | "); | ||
82 | let symbols = analysis.parent_module(pos.file_id).unwrap(); | ||
74 | assert_eq_dbg( | 83 | assert_eq_dbg( |
75 | r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#, | 84 | r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#, |
76 | &symbols, | 85 | &symbols, |
@@ -79,23 +88,24 @@ fn test_resolve_parent_module() { | |||
79 | 88 | ||
80 | #[test] | 89 | #[test] |
81 | fn test_resolve_crate_root() { | 90 | fn test_resolve_crate_root() { |
82 | let mut host = MockAnalysis::with_files( | 91 | let mock = MockAnalysis::with_files(" |
83 | &[("/lib.rs", "mod foo;"), ("/foo.rs", "")] | 92 | //- /lib.rs |
84 | ).analysis_host(); | 93 | mod foo; |
85 | let snap = host.analysis(); | 94 | //- /foo.rs |
86 | assert!(snap.crate_for(FileId(2)).unwrap().is_empty()); | 95 | // emtpy <|> |
87 | 96 | "); | |
88 | let crate_graph = { | 97 | let root_file = mock.id_of("/lib.rs"); |
89 | let mut g = CrateGraph::new(); | 98 | let mod_file = mock.id_of("/foo.rs"); |
90 | g.add_crate_root(FileId(1)); | 99 | let mut host = mock.analysis_host(); |
91 | g | 100 | assert!(host.analysis().crate_for(mod_file).unwrap().is_empty()); |
92 | }; | 101 | |
102 | let mut crate_graph = CrateGraph::new(); | ||
103 | let crate_id = crate_graph.add_crate_root(root_file); | ||
93 | let mut change = AnalysisChange::new(); | 104 | let mut change = AnalysisChange::new(); |
94 | change.set_crate_graph(crate_graph); | 105 | change.set_crate_graph(crate_graph); |
95 | host.apply_change(change); | 106 | host.apply_change(change); |
96 | let snap = host.analysis(); | ||
97 | 107 | ||
98 | assert_eq!(snap.crate_for(FileId(2)).unwrap(), vec![CrateId(0)],); | 108 | assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]); |
99 | } | 109 | } |
100 | 110 | ||
101 | #[test] | 111 | #[test] |
@@ -186,12 +196,8 @@ fn bar() { | |||
186 | } | 196 | } |
187 | 197 | ||
188 | fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { | 198 | fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { |
189 | let (offset, code) = extract_offset(text); | 199 | let (analysis, position) = single_file_with_position(text); |
190 | let code = code.as_str(); | 200 | analysis.find_all_refs(position.file_id, position.offset).unwrap() |
191 | |||
192 | let snap = analysis(&[("/lib.rs", code)]); | ||
193 | |||
194 | snap.find_all_refs(FileId(1), offset).unwrap() | ||
195 | } | 201 | } |
196 | 202 | ||
197 | #[test] | 203 | #[test] |
@@ -226,11 +232,14 @@ fn test_find_all_refs_for_param_inside() { | |||
226 | 232 | ||
227 | #[test] | 233 | #[test] |
228 | fn test_complete_crate_path() { | 234 | fn test_complete_crate_path() { |
229 | let snap = analysis(&[ | 235 | let (analysis, position) = analysis_and_position(" |
230 | ("/lib.rs", "mod foo; struct Spam;"), | 236 | //- /lib.rs |
231 | ("/foo.rs", "use crate::Sp"), | 237 | mod foo; |
232 | ]); | 238 | struct Spam; |
233 | let completions = snap.completions(FileId(2), 13.into()).unwrap().unwrap(); | 239 | //- /foo.rs |
240 | use crate::Sp<|> | ||
241 | "); | ||
242 | let completions = analysis.completions(position.file_id, position.offset).unwrap().unwrap(); | ||
234 | assert_eq_dbg( | 243 | assert_eq_dbg( |
235 | r#"[CompletionItem { label: "foo", lookup: None, snippet: None }, | 244 | r#"[CompletionItem { label: "foo", lookup: None, snippet: None }, |
236 | CompletionItem { label: "Spam", lookup: None, snippet: None }]"#, | 245 | CompletionItem { label: "Spam", lookup: None, snippet: None }]"#, |
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 562dbcbb3..8980f077f 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs | |||
@@ -8,6 +8,8 @@ use text_unit::{TextRange, TextUnit}; | |||
8 | 8 | ||
9 | pub use self::difference::Changeset as __Changeset; | 9 | pub use self::difference::Changeset as __Changeset; |
10 | 10 | ||
11 | pub const CURSOR_MARKER: &str = "<|>"; | ||
12 | |||
11 | #[macro_export] | 13 | #[macro_export] |
12 | macro_rules! assert_eq_text { | 14 | macro_rules! assert_eq_text { |
13 | ($expected:expr, $actual:expr) => {{ | 15 | ($expected:expr, $actual:expr) => {{ |
@@ -45,11 +47,10 @@ pub fn extract_offset(text: &str) -> (TextUnit, String) { | |||
45 | } | 47 | } |
46 | 48 | ||
47 | pub fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> { | 49 | pub fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> { |
48 | let cursor = "<|>"; | 50 | let cursor_pos = text.find(CURSOR_MARKER)?; |
49 | let cursor_pos = text.find(cursor)?; | 51 | let mut new_text = String::with_capacity(text.len() - CURSOR_MARKER.len()); |
50 | let mut new_text = String::with_capacity(text.len() - cursor.len()); | ||
51 | new_text.push_str(&text[..cursor_pos]); | 52 | new_text.push_str(&text[..cursor_pos]); |
52 | new_text.push_str(&text[cursor_pos + cursor.len()..]); | 53 | new_text.push_str(&text[cursor_pos + CURSOR_MARKER.len()..]); |
53 | let cursor_pos = TextUnit::from(cursor_pos as u32); | 54 | let cursor_pos = TextUnit::from(cursor_pos as u32); |
54 | Some((cursor_pos, new_text)) | 55 | Some((cursor_pos, new_text)) |
55 | } | 56 | } |
@@ -116,7 +117,22 @@ pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { | |||
116 | } | 117 | } |
117 | }; | 118 | }; |
118 | }; | 119 | }; |
119 | for line in fixture.lines() { | 120 | let margin = fixture.lines() |
121 | .filter(|it| it.trim_start().starts_with("//-")) | ||
122 | .map(|it| it.len() - it.trim_start().len()) | ||
123 | .next().expect("empty fixture"); | ||
124 | let lines = fixture.lines() | ||
125 | .filter_map(|line| { | ||
126 | if line.len() >= margin { | ||
127 | assert!(line[..margin].trim().is_empty()); | ||
128 | Some(&line[margin..]) | ||
129 | } else { | ||
130 | assert!(line.trim().is_empty()); | ||
131 | None | ||
132 | } | ||
133 | }); | ||
134 | |||
135 | for line in lines { | ||
120 | if line.starts_with("//-") { | 136 | if line.starts_with("//-") { |
121 | flush!(); | 137 | flush!(); |
122 | buf.clear(); | 138 | buf.clear(); |