aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-07-19 18:57:33 +0100
committerbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-07-19 18:57:33 +0100
commitfabd4c4304e387ed6bcc29b39d1593666c4cbec9 (patch)
treeb18a0be7255bb070dd7e2ae3b5687bc5f35f0d41
parentd4ffbf2ae092b313b3c750adad398f3aa6fb209b (diff)
parent002529937075bd69d7f71483d798d6e4f43d1de9 (diff)
Merge #1556
1556: sane indexing in text r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--crates/ra_assists/src/remove_dbg.rs7
-rw-r--r--crates/ra_hir/src/ids.rs11
-rw-r--r--crates/ra_ide_api/src/display/structure.rs6
-rw-r--r--crates/ra_syntax/src/syntax_node.rs1
-rw-r--r--crates/ra_syntax/src/syntax_text.rs115
5 files changed, 91 insertions, 49 deletions
diff --git a/crates/ra_assists/src/remove_dbg.rs b/crates/ra_assists/src/remove_dbg.rs
index c330bc827..5657ee4b8 100644
--- a/crates/ra_assists/src/remove_dbg.rs
+++ b/crates/ra_assists/src/remove_dbg.rs
@@ -36,11 +36,10 @@ pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist>
36 36
37 let macro_content = { 37 let macro_content = {
38 let macro_args = macro_call.token_tree()?.syntax().clone(); 38 let macro_args = macro_call.token_tree()?.syntax().clone();
39 let range = macro_args.range();
40 let start = range.start() + TextUnit::of_char('(');
41 let end = range.end() - TextUnit::of_char(')');
42 39
43 macro_args.text().slice(start..end).to_string() 40 let text = macro_args.text();
41 let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')');
42 text.slice(without_parens).to_string()
44 }; 43 };
45 44
46 ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| { 45 ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| {
diff --git a/crates/ra_hir/src/ids.rs b/crates/ra_hir/src/ids.rs
index 05a18eb56..ec756f2c3 100644
--- a/crates/ra_hir/src/ids.rs
+++ b/crates/ra_hir/src/ids.rs
@@ -362,7 +362,16 @@ impl MacroCallId {
362 pub fn debug_dump(self, db: &impl AstDatabase) -> String { 362 pub fn debug_dump(self, db: &impl AstDatabase) -> String {
363 let loc = self.loc(db); 363 let loc = self.loc(db);
364 let node = loc.ast_id.to_node(db); 364 let node = loc.ast_id.to_node(db);
365 let syntax_str = node.syntax().text().chunks().collect::<Vec<_>>().join(" "); 365 let syntax_str = {
366 let mut res = String::new();
367 node.syntax().text().for_each_chunk(|chunk| {
368 if !res.is_empty() {
369 res.push(' ')
370 }
371 res.push_str(chunk)
372 });
373 res
374 };
366 375
367 // dump the file name 376 // dump the file name
368 let file_id: HirFileId = self.loc(db).ast_id.file_id(); 377 let file_id: HirFileId = self.loc(db).ast_id.file_id();
diff --git a/crates/ra_ide_api/src/display/structure.rs b/crates/ra_ide_api/src/display/structure.rs
index 2e183d2f6..0b1a8b6e6 100644
--- a/crates/ra_ide_api/src/display/structure.rs
+++ b/crates/ra_ide_api/src/display/structure.rs
@@ -83,12 +83,12 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
83 83
84 fn collapse_ws(node: &SyntaxNode, output: &mut String) { 84 fn collapse_ws(node: &SyntaxNode, output: &mut String) {
85 let mut can_insert_ws = false; 85 let mut can_insert_ws = false;
86 for chunk in node.text().chunks() { 86 node.text().for_each_chunk(|chunk| {
87 for line in chunk.lines() { 87 for line in chunk.lines() {
88 let line = line.trim(); 88 let line = line.trim();
89 if line.is_empty() { 89 if line.is_empty() {
90 if can_insert_ws { 90 if can_insert_ws {
91 output.push_str(" "); 91 output.push(' ');
92 can_insert_ws = false; 92 can_insert_ws = false;
93 } 93 }
94 } else { 94 } else {
@@ -96,7 +96,7 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
96 can_insert_ws = true; 96 can_insert_ws = true;
97 } 97 }
98 } 98 }
99 } 99 })
100 } 100 }
101 101
102 visitor() 102 visitor()
diff --git a/crates/ra_syntax/src/syntax_node.rs b/crates/ra_syntax/src/syntax_node.rs
index 62e0967b7..8fe9e5b4e 100644
--- a/crates/ra_syntax/src/syntax_node.rs
+++ b/crates/ra_syntax/src/syntax_node.rs
@@ -297,7 +297,6 @@ fn to_green_element(element: SyntaxElement) -> rowan::GreenElement {
297#[derive(Clone, PartialEq, Eq, Hash)] 297#[derive(Clone, PartialEq, Eq, Hash)]
298pub struct SyntaxToken(pub(crate) rowan::cursor::SyntaxToken); 298pub struct SyntaxToken(pub(crate) rowan::cursor::SyntaxToken);
299 299
300//FIXME: always output text
301impl fmt::Debug for SyntaxToken { 300impl fmt::Debug for SyntaxToken {
302 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 301 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
303 write!(fmt, "{:?}@{:?}", self.kind(), self.range())?; 302 write!(fmt, "{:?}@{:?}", self.kind(), self.range())?;
diff --git a/crates/ra_syntax/src/syntax_text.rs b/crates/ra_syntax/src/syntax_text.rs
index 6902a04a2..d8adf782b 100644
--- a/crates/ra_syntax/src/syntax_text.rs
+++ b/crates/ra_syntax/src/syntax_text.rs
@@ -16,26 +16,48 @@ impl<'a> SyntaxText<'a> {
16 SyntaxText { node, range: node.range() } 16 SyntaxText { node, range: node.range() }
17 } 17 }
18 18
19 pub fn chunks(&self) -> impl Iterator<Item = SmolStr> { 19 pub fn try_fold_chunks<T, F, E>(&self, init: T, mut f: F) -> Result<T, E>
20 let range = self.range; 20 where
21 self.node.descendants_with_tokens().filter_map(move |el| match el { 21 F: FnMut(T, &str) -> Result<T, E>,
22 SyntaxElement::Token(t) => { 22 {
23 let text = t.text(); 23 self.node.descendants_with_tokens().try_fold(init, move |acc, element| {
24 let range = range.intersection(&t.range())?; 24 let res = match element {
25 let res = if range == t.range() { 25 SyntaxElement::Token(token) => {
26 t.text().clone() 26 let range = match self.range.intersection(&token.range()) {
27 } else { 27 None => return Ok(acc),
28 let range = range - t.range().start(); 28 Some(it) => it,
29 text[range].into() 29 };
30 }; 30 let slice = if range == token.range() {
31 Some(res) 31 token.text()
32 } 32 } else {
33 SyntaxElement::Node(_) => None, 33 let range = range - token.range().start();
34 &token.text()[range]
35 };
36 f(acc, slice)?
37 }
38 SyntaxElement::Node(_) => acc,
39 };
40 Ok(res)
34 }) 41 })
35 } 42 }
36 43
44 pub fn try_for_each_chunk<F: FnMut(&str) -> Result<(), E>, E>(
45 &self,
46 mut f: F,
47 ) -> Result<(), E> {
48 self.try_fold_chunks((), move |(), chunk| f(chunk))
49 }
50
51 pub fn for_each_chunk<F: FnMut(&str)>(&self, mut f: F) {
52 enum Void {}
53 match self.try_for_each_chunk(|chunk| Ok::<(), Void>(f(chunk))) {
54 Ok(()) => (),
55 Err(void) => match void {},
56 }
57 }
58
37 pub fn push_to(&self, buf: &mut String) { 59 pub fn push_to(&self, buf: &mut String) {
38 self.chunks().for_each(|it| buf.push_str(it.as_str())); 60 self.for_each_chunk(|chunk| buf.push_str(chunk))
39 } 61 }
40 62
41 pub fn to_string(&self) -> String { 63 pub fn to_string(&self) -> String {
@@ -49,19 +71,20 @@ impl<'a> SyntaxText<'a> {
49 } 71 }
50 72
51 pub fn contains(&self, c: char) -> bool { 73 pub fn contains(&self, c: char) -> bool {
52 self.chunks().any(|it| it.contains(c)) 74 self.try_for_each_chunk(|chunk| if chunk.contains(c) { Err(()) } else { Ok(()) }).is_err()
53 } 75 }
54 76
55 pub fn find(&self, c: char) -> Option<TextUnit> { 77 pub fn find(&self, c: char) -> Option<TextUnit> {
56 let mut acc: TextUnit = 0.into(); 78 let mut acc: TextUnit = 0.into();
57 for chunk in self.chunks() { 79 let res = self.try_for_each_chunk(|chunk| {
58 if let Some(pos) = chunk.find(c) { 80 if let Some(pos) = chunk.find(c) {
59 let pos: TextUnit = (pos as u32).into(); 81 let pos: TextUnit = (pos as u32).into();
60 return Some(acc + pos); 82 return Err(acc + pos);
61 } 83 }
62 acc += TextUnit::of_str(chunk.as_str()); 84 acc += TextUnit::of_str(chunk);
63 } 85 Ok(())
64 None 86 });
87 found(res)
65 } 88 }
66 89
67 pub fn len(&self) -> TextUnit { 90 pub fn len(&self) -> TextUnit {
@@ -72,18 +95,21 @@ impl<'a> SyntaxText<'a> {
72 self.range.is_empty() 95 self.range.is_empty()
73 } 96 }
74 97
75 /// NB, the offsets here are absolute, and this probably doesn't make sense!
76 pub fn slice(&self, range: impl ops::RangeBounds<TextUnit>) -> SyntaxText<'a> { 98 pub fn slice(&self, range: impl ops::RangeBounds<TextUnit>) -> SyntaxText<'a> {
77 let start = match range.start_bound() { 99 let start = match range.start_bound() {
78 Bound::Included(b) => *b, 100 Bound::Included(&b) => b,
79 Bound::Excluded(b) => *b + TextUnit::from(1u32), 101 Bound::Excluded(_) => panic!("utf-aware slicing can't work this way"),
80 Bound::Unbounded => self.range.start(), 102 Bound::Unbounded => 0.into(),
81 }; 103 };
82 let end = match range.end_bound() { 104 let end = match range.end_bound() {
83 Bound::Included(b) => *b + TextUnit::from(1u32), 105 Bound::Included(_) => panic!("utf-aware slicing can't work this way"),
84 Bound::Excluded(b) => *b, 106 Bound::Excluded(&b) => b,
85 Bound::Unbounded => self.range.end(), 107 Bound::Unbounded => self.len(),
86 }; 108 };
109 assert!(start <= end);
110 let len = end - start;
111 let start = self.range.start() + start;
112 let end = start + len;
87 assert!( 113 assert!(
88 start <= end, 114 start <= end,
89 "invalid slice, range: {:?}, slice: {:?}", 115 "invalid slice, range: {:?}, slice: {:?}",
@@ -101,17 +127,25 @@ impl<'a> SyntaxText<'a> {
101 } 127 }
102 128
103 pub fn char_at(&self, offset: impl Into<TextUnit>) -> Option<char> { 129 pub fn char_at(&self, offset: impl Into<TextUnit>) -> Option<char> {
104 let mut start: TextUnit = 0.into();
105 let offset = offset.into(); 130 let offset = offset.into();
106 for chunk in self.chunks() { 131 let mut start: TextUnit = 0.into();
107 let end = start + TextUnit::of_str(chunk.as_str()); 132 let res = self.try_for_each_chunk(|chunk| {
133 let end = start + TextUnit::of_str(chunk);
108 if start <= offset && offset < end { 134 if start <= offset && offset < end {
109 let off: usize = u32::from(offset - start) as usize; 135 let off: usize = u32::from(offset - start) as usize;
110 return Some(chunk[off..].chars().next().unwrap()); 136 return Err(chunk[off..].chars().next().unwrap());
111 } 137 }
112 start = end; 138 start = end;
113 } 139 Ok(())
114 None 140 });
141 found(res)
142 }
143}
144
145fn found<T>(res: Result<(), T>) -> Option<T> {
146 match res {
147 Ok(()) => None,
148 Err(it) => Some(it),
115 } 149 }
116} 150}
117 151
@@ -135,13 +169,14 @@ impl From<SyntaxText<'_>> for String {
135 169
136impl PartialEq<str> for SyntaxText<'_> { 170impl PartialEq<str> for SyntaxText<'_> {
137 fn eq(&self, mut rhs: &str) -> bool { 171 fn eq(&self, mut rhs: &str) -> bool {
138 for chunk in self.chunks() { 172 self.try_for_each_chunk(|chunk| {
139 if !rhs.starts_with(chunk.as_str()) { 173 if !rhs.starts_with(chunk) {
140 return false; 174 return Err(());
141 } 175 }
142 rhs = &rhs[chunk.len()..]; 176 rhs = &rhs[chunk.len()..];
143 } 177 Ok(())
144 rhs.is_empty() 178 })
179 .is_ok()
145 } 180 }
146} 181}
147 182