@@ -10,16 +10,58 @@ pub enum Shift {
1010 None ,
1111 Left ( usize ) ,
1212 Right ( usize ) ,
13+ /// Strip leftmost whitespace that is common to all lines.
14+ Auto ,
1315}
1416
15- fn shift_line ( l : & str , shift : Shift ) -> Cow < ' _ , str > {
17+ #[ derive( PartialEq , Eq , Debug , Clone , Copy ) ]
18+ enum ExplicitShift {
19+ None ,
20+ Left ( usize ) ,
21+ Right ( usize ) ,
22+ }
23+
24+ fn common_leading_ws ( lines : & [ String ] ) -> String {
25+ let mut common_ws: Option < String > = None ;
26+ for line in lines {
27+ if line. is_empty ( ) {
28+ // Don't include empty lines in the calculation.
29+ continue ;
30+ }
31+ let ws = line. chars ( ) . take_while ( |c| c. is_whitespace ( ) ) ;
32+ if let Some ( common) = common_ws {
33+ common_ws = Some (
34+ common
35+ . chars ( )
36+ . zip ( ws)
37+ . take_while ( |( a, b) | a == b)
38+ . map ( |( a, _b) | a)
39+ . collect ( ) ,
40+ ) ;
41+ } else {
42+ common_ws = Some ( ws. collect ( ) )
43+ }
44+ }
45+ common_ws. unwrap_or_else ( String :: new)
46+ }
47+
48+ fn calculate_shift ( lines : & [ String ] , shift : Shift ) -> ExplicitShift {
1649 match shift {
17- Shift :: None => Cow :: Borrowed ( l) ,
18- Shift :: Right ( shift) => {
50+ Shift :: None => ExplicitShift :: None ,
51+ Shift :: Left ( l) => ExplicitShift :: Left ( l) ,
52+ Shift :: Right ( r) => ExplicitShift :: Right ( r) ,
53+ Shift :: Auto => ExplicitShift :: Left ( common_leading_ws ( lines) . len ( ) ) ,
54+ }
55+ }
56+
57+ fn shift_line ( l : & str , shift : ExplicitShift ) -> Cow < ' _ , str > {
58+ match shift {
59+ ExplicitShift :: None => Cow :: Borrowed ( l) ,
60+ ExplicitShift :: Right ( shift) => {
1961 let indent = " " . repeat ( shift) ;
2062 Cow :: Owned ( format ! ( "{indent}{l}" ) )
2163 }
22- Shift :: Left ( skip) => {
64+ ExplicitShift :: Left ( skip) => {
2365 if l. chars ( ) . take ( skip) . any ( |c| !c. is_whitespace ( ) ) {
2466 log:: error!( "left-shifting away non-whitespace" ) ;
2567 }
@@ -30,6 +72,7 @@ fn shift_line(l: &str, shift: Shift) -> Cow<'_, str> {
3072}
3173
3274fn shift_lines ( lines : & [ String ] , shift : Shift ) -> Vec < Cow < ' _ , str > > {
75+ let shift = calculate_shift ( lines, shift) ;
3376 lines. iter ( ) . map ( |l| shift_line ( l, shift) ) . collect ( )
3477}
3578
@@ -160,20 +203,44 @@ pub fn take_rustdoc_include_anchored_lines(s: &str, anchor: &str) -> String {
160203#[ cfg( test) ]
161204mod tests {
162205 use super :: {
163- shift_line, take_anchored_lines, take_anchored_lines_with_shift, take_lines ,
164- take_lines_with_shift , take_rustdoc_include_anchored_lines , take_rustdoc_include_lines ,
165- Shift ,
206+ common_leading_ws , shift_line, take_anchored_lines, take_anchored_lines_with_shift,
207+ take_lines , take_lines_with_shift , take_rustdoc_include_anchored_lines ,
208+ take_rustdoc_include_lines , ExplicitShift , Shift ,
166209 } ;
167210
211+ #[ test]
212+ fn common_leading_ws_test ( ) {
213+ let tests = [
214+ ( [ " line1" , " line2" , " line3" ] , " " ) ,
215+ ( [ " line1" , " line2" , "line3" ] , "" ) ,
216+ ( [ "\t \t line1" , "\t \t line2" , "\t \t line3" ] , "\t \t " ) ,
217+ ( [ "\t line1" , " \t line2" , " \t \t line3" ] , "" ) ,
218+ ] ;
219+ for ( lines, want) in tests {
220+ let lines = lines. into_iter ( ) . map ( |l| l. to_string ( ) ) . collect :: < Vec < _ > > ( ) ;
221+ let got = common_leading_ws ( & lines) ;
222+ assert_eq ! ( got, want, "for input {lines:?}" ) ;
223+ }
224+ }
225+
168226 #[ test]
169227 fn shift_line_test ( ) {
170228 let s = " Line with 4 space intro" ;
171- assert_eq ! ( shift_line( s, Shift :: None ) , s) ;
172- assert_eq ! ( shift_line( s, Shift :: Left ( 4 ) ) , "Line with 4 space intro" ) ;
173- assert_eq ! ( shift_line( s, Shift :: Left ( 2 ) ) , " Line with 4 space intro" ) ;
174- assert_eq ! ( shift_line( s, Shift :: Left ( 6 ) ) , "ne with 4 space intro" ) ;
229+ assert_eq ! ( shift_line( s, ExplicitShift :: None ) , s) ;
230+ assert_eq ! (
231+ shift_line( s, ExplicitShift :: Left ( 4 ) ) ,
232+ "Line with 4 space intro"
233+ ) ;
234+ assert_eq ! (
235+ shift_line( s, ExplicitShift :: Left ( 2 ) ) ,
236+ " Line with 4 space intro"
237+ ) ;
238+ assert_eq ! (
239+ shift_line( s, ExplicitShift :: Left ( 6 ) ) ,
240+ "ne with 4 space intro"
241+ ) ;
175242 assert_eq ! (
176- shift_line( s, Shift :: Right ( 2 ) ) ,
243+ shift_line( s, ExplicitShift :: Right ( 2 ) ) ,
177244 " Line with 4 space intro"
178245 ) ;
179246 }
@@ -207,6 +274,10 @@ mod tests {
207274 take_lines_with_shift( s, 1 ..3 , Shift :: Right ( 2 ) ) ,
208275 " ipsum\n dolor"
209276 ) ;
277+ assert_eq ! (
278+ take_lines_with_shift( s, 1 ..3 , Shift :: Auto ) ,
279+ "ipsum\n dolor"
280+ ) ;
210281 assert_eq ! ( take_lines_with_shift( s, 3 .., Shift :: None ) , " sit\n amet" ) ;
211282 assert_eq ! (
212283 take_lines_with_shift( s, 3 .., Shift :: Right ( 1 ) ) ,
@@ -217,6 +288,10 @@ mod tests {
217288 take_lines_with_shift( s, ..3 , Shift :: None ) ,
218289 " Lorem\n ipsum\n dolor"
219290 ) ;
291+ assert_eq ! (
292+ take_lines_with_shift( s, ..3 , Shift :: Auto ) ,
293+ "Lorem\n ipsum\n dolor"
294+ ) ;
220295 assert_eq ! (
221296 take_lines_with_shift( s, ..3 , Shift :: Right ( 4 ) ) ,
222297 " Lorem\n ipsum\n dolor"
@@ -226,6 +301,10 @@ mod tests {
226301 "rem\n sum\n dolor"
227302 ) ;
228303 assert_eq ! ( take_lines_with_shift( s, .., Shift :: None ) , s) ;
304+ assert_eq ! (
305+ take_lines_with_shift( s, .., Shift :: Auto ) ,
306+ "Lorem\n ipsum\n dolor\n sit\n amet"
307+ ) ;
229308 // corner cases
230309 assert_eq ! ( take_lines_with_shift( s, 4 ..3 , Shift :: None ) , "" ) ;
231310 assert_eq ! ( take_lines_with_shift( s, 4 ..3 , Shift :: Left ( 2 ) ) , "" ) ;
@@ -307,6 +386,10 @@ mod tests {
307386 take_anchored_lines_with_shift( s, "test" , Shift :: Left ( 2 ) ) ,
308387 "dolor\n sit\n amet"
309388 ) ;
389+ assert_eq ! (
390+ take_anchored_lines_with_shift( s, "test" , Shift :: Auto ) ,
391+ "dolor\n sit\n amet"
392+ ) ;
310393 assert_eq ! (
311394 take_anchored_lines_with_shift( s, "something" , Shift :: None ) ,
312395 ""
@@ -333,6 +416,10 @@ mod tests {
333416 take_anchored_lines_with_shift( s, "test" , Shift :: Left ( 2 ) ) ,
334417 "dolor\n sit\n amet"
335418 ) ;
419+ assert_eq ! (
420+ take_anchored_lines_with_shift( s, "test" , Shift :: Auto ) ,
421+ "dolor\n sit\n amet"
422+ ) ;
336423 assert_eq ! (
337424 take_anchored_lines_with_shift( s, "test" , Shift :: Left ( 4 ) ) ,
338425 "lor\n t\n et"
@@ -346,18 +433,22 @@ mod tests {
346433 ""
347434 ) ;
348435
349- let s = " Lorem\n ANCHOR: test\n ipsum\n ANCHOR: test\n dolor\n sit\n amet\n ANCHOR_END: test\n lorem\n ipsum" ;
436+ let s = " Lorem\n ANCHOR: test\n ipsum\n ANCHOR: test\n dolor\n \n \n sit\n amet\n ANCHOR_END: test\n lorem\n ipsum" ;
350437 assert_eq ! (
351438 take_anchored_lines_with_shift( s, "test" , Shift :: None ) ,
352- " ipsum\n dolor\n sit\n amet"
439+ " ipsum\n dolor\n \n \n sit\n amet"
353440 ) ;
354441 assert_eq ! (
355442 take_anchored_lines_with_shift( s, "test" , Shift :: Right ( 2 ) ) ,
356- " ipsum\n dolor\n sit\n amet"
443+ " ipsum\n dolor\n \n \n sit\n amet"
357444 ) ;
358445 assert_eq ! (
359446 take_anchored_lines_with_shift( s, "test" , Shift :: Left ( 2 ) ) ,
360- "ipsum\n dolor\n sit\n amet"
447+ "ipsum\n dolor\n \n \n sit\n amet"
448+ ) ;
449+ assert_eq ! (
450+ take_anchored_lines_with_shift( s, "test" , Shift :: Auto ) ,
451+ "ipsum\n dolor\n \n \n sit\n amet"
361452 ) ;
362453 assert_eq ! (
363454 take_anchored_lines_with_shift( s, "something" , Shift :: None ) ,
@@ -371,6 +462,10 @@ mod tests {
371462 take_anchored_lines_with_shift( s, "something" , Shift :: Left ( 2 ) ) ,
372463 ""
373464 ) ;
465+ assert_eq ! (
466+ take_anchored_lines_with_shift( s, "something" , Shift :: Auto ) ,
467+ ""
468+ ) ;
374469
375470 // Include non-ASCII.
376471 let s = " Lorem\n ANCHOR: test2\n ípsum\n ANCHOR: test\n dôlor\n sit\n amet\n ANCHOR_END: test\n lorem\n ANCHOR_END:test2\n ipsum" ;
0 commit comments