1use alloc::{borrow::Cow, str::from_utf8_unchecked, string::String, vec::Vec};
2
3pub trait ExpandTabs<'a> {
7 fn expand_tabs(self, spaces: usize) -> Cow<'a, str>;
11}
12
13fn remove_tabs(s: &str) -> Cow<'_, str> {
14 let bytes = s.as_bytes();
15 let length = bytes.len();
16
17 let mut p = 0;
18
19 loop {
20 if p == length {
21 return Cow::Borrowed(s);
22 }
23
24 if bytes[p] == b'\t' {
25 break;
26 } else {
27 p += 1;
28 }
29 }
30
31 let heading_normal_characters_end_index = p;
32
33 p += 1;
34
35 loop {
42 if p == length {
43 return Cow::Borrowed(unsafe {
46 from_utf8_unchecked(&bytes[..heading_normal_characters_end_index])
47 });
48 }
49
50 if bytes[p] == b'\t' {
51 p += 1;
52 } else {
53 break;
54 }
55 }
56
57 let following_tab_characters_end_index = p;
58
59 p += 1;
60
61 loop {
63 if p == length {
64 if heading_normal_characters_end_index == 0 {
65 return Cow::Borrowed(unsafe {
67 from_utf8_unchecked(&bytes[following_tab_characters_end_index..])
68 });
69 } else {
70 let mut new_v = Vec::with_capacity(
73 heading_normal_characters_end_index + length
74 - following_tab_characters_end_index,
75 );
76
77 new_v.extend_from_slice(bytes[..heading_normal_characters_end_index].as_ref());
78 new_v.extend_from_slice(bytes[following_tab_characters_end_index..].as_ref());
79
80 return Cow::Owned(unsafe { String::from_utf8_unchecked(new_v) });
81 }
82 }
83
84 if bytes[p] == b'\t' {
85 break;
86 } else {
87 p += 1;
88 }
89 }
90
91 let following_normal_characters_end_index = p;
92
93 if p < length {
94 p += 1;
95
96 loop {
97 if p == length {
98 return Cow::Borrowed(unsafe {
101 from_utf8_unchecked(
102 &bytes[following_tab_characters_end_index
103 ..following_normal_characters_end_index],
104 )
105 });
106 }
107
108 if bytes[p] == b'\t' {
109 p += 1;
110 } else {
111 break;
112 }
113 }
114 }
115
116 let mut new_v =
119 bytes[following_tab_characters_end_index..following_normal_characters_end_index].to_vec();
120
121 let mut start = p;
122
123 p += 1;
124
125 loop {
126 if p == length {
127 break;
128 }
129
130 if bytes[p] == b'\t' {
131 new_v.extend_from_slice(&bytes[start..p]);
132
133 start = p + 1;
134 }
135
136 p += 1;
137 }
138
139 new_v.extend_from_slice(&bytes[start..p]);
140
141 Cow::Owned(unsafe { String::from_utf8_unchecked(new_v) })
142}
143
144impl<'a> ExpandTabs<'a> for &'a str {
145 fn expand_tabs(self, spaces: usize) -> Cow<'a, str> {
146 if spaces == 0 {
147 return remove_tabs(self);
148 }
149
150 let s = self;
151 let bytes = s.as_bytes();
152 let length = bytes.len();
153
154 let mut p = 0;
155
156 loop {
157 if p == length {
158 return Cow::Borrowed(s);
159 }
160
161 if bytes[p] == b'\t' {
162 break;
163 }
164
165 p += 1;
166 }
167
168 let tab_count = bytes[p..].iter().filter(|&&b| b == b'\t').count();
169 let capacity = bytes.len() + (tab_count * (spaces - 1));
170 let mut new_v = Vec::with_capacity(capacity);
171
172 new_v.extend_from_slice(&bytes[..p]);
173 new_v.resize(new_v.len() + spaces, b' ');
174
175 p += 1;
176
177 let mut start = p;
178
179 loop {
180 if p == length {
181 break;
182 }
183
184 if bytes[p] == b'\t' {
185 new_v.extend_from_slice(&bytes[start..p]);
186 new_v.resize(new_v.len() + spaces, b' ');
187
188 p += 1;
189 start = p;
190 } else {
191 p += 1;
192 }
193 }
194
195 new_v.extend_from_slice(&bytes[start..p]);
196
197 Cow::Owned(unsafe { String::from_utf8_unchecked(new_v) })
198 }
199}
200
201impl<'a> ExpandTabs<'a> for Cow<'a, str> {
202 #[inline]
203 fn expand_tabs(self, spaces: usize) -> Cow<'a, str> {
204 match self {
205 Cow::Borrowed(s) => s.expand_tabs(spaces),
206 Cow::Owned(mut s) if spaces == 0 => {
207 if s.as_bytes().contains(&b'\t') {
208 s.retain(|c| c != '\t');
209 }
210
211 Cow::Owned(s)
212 },
213 Cow::Owned(s) => Cow::Owned(crate::cow_into_owned!(s, s.as_str().expand_tabs(spaces),)),
214 }
215 }
216}