ansi_align/lib.rs
1//! # ansi-align
2//!
3//! A Rust library for aligning text with proper support for ANSI escape sequences and Unicode characters.
4//!
5//! This crate provides functions to align text in various ways (left, center, right) while correctly
6//! handling ANSI escape sequences (like color codes) and Unicode characters with varying display widths.
7//!
8//! ## Features
9//!
10//! - **ANSI-aware alignment**: Correctly handles text containing ANSI escape sequences
11//! - **Unicode support**: Properly calculates display width for Unicode characters including CJK
12//! - **Multiple alignment options**: Left, center, and right alignment
13//! - **Customizable**: Configure split strings and padding characters
14//! - **Performance optimized**: Single-pass processing with efficient memory usage
15//! - **Type-safe**: Uses a [`Width`] type for display width values
16//!
17//! ## Quick Start
18//!
19//! ```rust
20//! use ansi_align::{ansi_align, center, left, right};
21//!
22//! // Basic alignment (defaults to center)
23//! let text = "hello\nworld";
24//! let centered = ansi_align(text);
25//!
26//! // Specific alignment functions
27//! let left_aligned = left("short\nlonger line");
28//! let centered = center("short\nlonger line");
29//! let right_aligned = right("short\nlonger line");
30//! ```
31//!
32//! ## Advanced Usage
33//!
34//! ```rust
35//! use ansi_align::{ansi_align_with_options, Alignment, AlignOptions};
36//!
37//! let text = "line1|line2|line3";
38//! let options = AlignOptions::new(Alignment::Right)
39//! .with_split("|")
40//! .with_pad('.');
41//!
42//! let result = ansi_align_with_options(text, &options);
43//! ```
44
45#![deny(missing_docs)]
46#![warn(clippy::all)]
47#![warn(clippy::pedantic)]
48#![warn(clippy::nursery)]
49
50use std::fmt;
51use std::ops::{Add, Div, Mul, Sub};
52use string_width::string_width;
53
54/// Specifies the alignment direction for text.
55///
56/// This enum defines the three possible alignment options that can be used
57/// with the alignment functions.
58///
59/// # Examples
60///
61/// ```rust
62/// use ansi_align::{Alignment, AlignOptions, ansi_align_with_options};
63///
64/// let text = "hello\nworld";
65///
66/// // Center alignment
67/// let opts = AlignOptions::new(Alignment::Center);
68/// let result = ansi_align_with_options(text, &opts);
69///
70/// // Right alignment
71/// let opts = AlignOptions::new(Alignment::Right);
72/// let result = ansi_align_with_options(text, &opts);
73/// ```
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum Alignment {
76 /// Align text to the left (no padding added to the left side)
77 Left,
78 /// Align text to the center (padding distributed evenly on both sides)
79 Center,
80 /// Align text to the right (padding added to the left side)
81 Right,
82}
83
84/// A type-safe wrapper for display width values.
85///
86/// This type represents the visual width of text as it would appear on screen,
87/// taking into account ANSI escape sequences and Unicode character widths.
88/// It provides type safety to prevent confusion between byte lengths and display widths.
89///
90/// # Examples
91///
92/// ```rust
93/// use ansi_align::Width;
94///
95/// let width = Width::new(42);
96/// assert_eq!(width.get(), 42);
97///
98/// // Convert from usize
99/// let width: Width = 24.into();
100/// assert_eq!(width.get(), 24);
101///
102/// // Ordering works as expected
103/// assert!(Width::new(10) < Width::new(20));
104/// ```
105#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
106pub struct Width(usize);
107
108impl Width {
109 /// Creates a new `Width` value from a `usize`.
110 ///
111 /// # Arguments
112 ///
113 /// * `value` - The display width value
114 ///
115 /// # Examples
116 ///
117 /// ```rust
118 /// use ansi_align::Width;
119 ///
120 /// let width = Width::new(10);
121 /// assert_eq!(width.get(), 10);
122 /// ```
123 #[must_use]
124 pub const fn new(value: usize) -> Self {
125 Self(value)
126 }
127
128 /// Returns the underlying `usize` value.
129 ///
130 /// # Examples
131 ///
132 /// ```rust
133 /// use ansi_align::Width;
134 ///
135 /// let width = Width::new(42);
136 /// assert_eq!(width.get(), 42);
137 /// ```
138 #[must_use]
139 pub const fn get(self) -> usize {
140 self.0
141 }
142
143 /// Saturating subtraction that doesn't underflow
144 ///
145 /// # Examples
146 ///
147 /// ```rust
148 /// use ansi_align::Width;
149 ///
150 /// let width = Width::new(5);
151 /// let result = width.saturating_sub(Width::new(10));
152 /// assert_eq!(result.get(), 0); // Would underflow, but saturates to 0
153 /// ```
154 #[must_use]
155 pub const fn saturating_sub(self, other: Self) -> Self {
156 Self(self.0.saturating_sub(other.0))
157 }
158
159 /// Check if width is zero
160 ///
161 /// # Examples
162 ///
163 /// ```rust
164 /// use ansi_align::Width;
165 ///
166 /// assert!(Width::new(0).is_zero());
167 /// assert!(!Width::new(5).is_zero());
168 /// ```
169 #[must_use]
170 pub const fn is_zero(self) -> bool {
171 self.0 == 0
172 }
173}
174
175impl From<usize> for Width {
176 fn from(value: usize) -> Self {
177 Self(value)
178 }
179}
180
181impl Add for Width {
182 type Output = Self;
183 fn add(self, other: Self) -> Self {
184 Self(self.0 + other.0)
185 }
186}
187
188impl Sub for Width {
189 type Output = Self;
190 fn sub(self, other: Self) -> Self {
191 Self(self.0 - other.0)
192 }
193}
194
195impl Mul for Width {
196 type Output = Self;
197 fn mul(self, other: Self) -> Self {
198 Self(self.0 * other.0)
199 }
200}
201
202impl Div for Width {
203 type Output = Self;
204 fn div(self, other: Self) -> Self {
205 Self(self.0 / other.0)
206 }
207}
208
209impl fmt::Display for Width {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 write!(f, "{}", self.0)
212 }
213}
214
215/// Configuration options for text alignment operations.
216///
217/// This struct allows you to customize how text alignment is performed,
218/// including the alignment direction, line separator, and padding character.
219///
220/// # Examples
221///
222/// ```rust
223/// use ansi_align::{AlignOptions, Alignment, ansi_align_with_options};
224///
225/// // Basic usage with default settings
226/// let opts = AlignOptions::new(Alignment::Center);
227///
228/// // Customized options
229/// let opts = AlignOptions::new(Alignment::Right)
230/// .with_split("|")
231/// .with_pad('.');
232///
233/// let text = "short|longer text";
234/// let result = ansi_align_with_options(text, &opts);
235/// ```
236#[derive(Debug, Clone)]
237pub struct AlignOptions {
238 /// The alignment type (left, center, right)
239 pub align: Alignment,
240 /// The string to split lines on (default: "\n")
241 pub split: String,
242 /// The padding character to use (default: " ")
243 pub pad: char,
244}
245
246impl Default for AlignOptions {
247 fn default() -> Self {
248 Self {
249 align: Alignment::Center,
250 split: "\n".to_string(),
251 pad: ' ',
252 }
253 }
254}
255
256impl AlignOptions {
257 /// Creates new alignment options with the specified alignment direction.
258 ///
259 /// Uses default values for split string (`"\n"`) and padding character (`' '`).
260 ///
261 /// # Arguments
262 ///
263 /// * `align` - The alignment direction to use
264 ///
265 /// # Examples
266 ///
267 /// ```rust
268 /// use ansi_align::{AlignOptions, Alignment};
269 ///
270 /// let opts = AlignOptions::new(Alignment::Center);
271 /// ```
272 #[must_use]
273 pub fn new(align: Alignment) -> Self {
274 Self {
275 align,
276 ..Default::default()
277 }
278 }
279
280 /// Sets the string used to split lines using the builder pattern.
281 ///
282 /// By default, lines are split on `"\n"`, but you can specify any string
283 /// as a line separator.
284 ///
285 /// # Arguments
286 ///
287 /// * `split` - The string to use as a line separator
288 ///
289 /// # Examples
290 ///
291 /// ```rust
292 /// use ansi_align::{AlignOptions, Alignment};
293 ///
294 /// let opts = AlignOptions::new(Alignment::Center)
295 /// .with_split("|")
296 /// .with_split("<->"); // Multi-character separators work too
297 /// ```
298 #[must_use]
299 pub fn with_split<S: Into<String>>(mut self, split: S) -> Self {
300 self.split = split.into();
301 self
302 }
303
304 /// Sets the character used for padding using the builder pattern.
305 ///
306 /// By default, spaces (`' '`) are used for padding, but you can specify
307 /// any character.
308 ///
309 /// # Arguments
310 ///
311 /// * `pad` - The character to use for padding
312 ///
313 /// # Examples
314 ///
315 /// ```rust
316 /// use ansi_align::{AlignOptions, Alignment};
317 ///
318 /// let opts = AlignOptions::new(Alignment::Right)
319 /// .with_pad('.');
320 /// ```
321 #[must_use]
322 pub const fn with_pad(mut self, pad: char) -> Self {
323 self.pad = pad;
324 self
325 }
326}
327
328/// Efficiently create padding string for alignment
329fn create_padding(pad_char: char, count: usize) -> String {
330 match count {
331 0 => String::new(),
332 1 => pad_char.to_string(),
333 // Use repeat for small counts (more efficient)
334 2..=16 => pad_char.to_string().repeat(count),
335 // Use with_capacity + push for larger counts to avoid reallocations
336 _ => {
337 let mut padding = String::with_capacity(count * pad_char.len_utf8());
338 for _ in 0..count {
339 padding.push(pad_char);
340 }
341 padding
342 }
343 }
344}
345
346/// Align text with center alignment (default behavior)
347#[must_use]
348pub fn ansi_align(text: &str) -> String {
349 ansi_align_with_options(text, &AlignOptions::default())
350}
351
352/// Calculate display widths for lines
353fn calculate_line_widths<'a>(text: &'a str, split: &str) -> Vec<(&'a str, Width)> {
354 text.split(split)
355 .map(|line| (line, Width::from(string_width(line))))
356 .collect()
357}
358
359/// Find the maximum width among lines
360fn find_max_width(line_data: &[(&str, Width)]) -> Width {
361 line_data
362 .iter()
363 .map(|(_, width)| *width)
364 .max()
365 .unwrap_or(Width::new(0))
366}
367
368/// Apply alignment to a single line
369fn align_line(
370 line: &str,
371 line_width: Width,
372 max_width: Width,
373 alignment: Alignment,
374 pad_char: char,
375) -> String {
376 let padding_needed = match alignment {
377 Alignment::Left => 0,
378 Alignment::Center => (max_width.get() - line_width.get()) / 2,
379 Alignment::Right => max_width.get() - line_width.get(),
380 };
381
382 if padding_needed == 0 {
383 line.to_string()
384 } else {
385 let mut result = create_padding(pad_char, padding_needed);
386 result.push_str(line);
387 result
388 }
389}
390
391/// Align text with support for ANSI escape sequences
392///
393/// This function handles text containing ANSI escape sequences (like color codes)
394/// by calculating display width correctly, ignoring the escape sequences.
395///
396/// # Examples
397///
398/// ```
399/// use ansi_align::{ansi_align, ansi_align_with_options, Alignment, AlignOptions};
400///
401/// // Basic center alignment
402/// let result = ansi_align("hello\nworld");
403///
404/// // Right alignment with custom padding
405/// let opts = AlignOptions::new(Alignment::Right).with_pad('.');
406/// let result = ansi_align_with_options("hi\nhello", &opts);
407///
408/// // Works with ANSI escape sequences
409/// let colored = "\x1b[31mred\x1b[0m\n\x1b[32mgreen\x1b[0m";
410/// let result = ansi_align_with_options(colored, &AlignOptions::new(Alignment::Center));
411/// ```
412///
413/// # Performance
414///
415/// This function makes a single pass through the input text for optimal performance.
416/// For left alignment, it returns the input unchanged as an optimization.
417#[must_use]
418pub fn ansi_align_with_options(text: &str, opts: &AlignOptions) -> String {
419 if text.is_empty() || opts.align == Alignment::Left {
420 return text.to_string();
421 }
422
423 let line_data = calculate_line_widths(text, &opts.split);
424 let max_width = find_max_width(&line_data);
425
426 let aligned_lines: Vec<String> = line_data
427 .into_iter()
428 .map(|(line, width)| align_line(line, width, max_width, opts.align, opts.pad))
429 .collect();
430
431 aligned_lines.join(&opts.split)
432}
433
434/// Align text to the left (no-op, returns original text)
435#[must_use]
436pub fn left(text: &str) -> String {
437 ansi_align_with_options(text, &AlignOptions::new(Alignment::Left))
438}
439
440/// Align text to the center
441#[must_use]
442pub fn center(text: &str) -> String {
443 ansi_align_with_options(text, &AlignOptions::new(Alignment::Center))
444}
445
446/// Align text to the right
447#[must_use]
448pub fn right(text: &str) -> String {
449 ansi_align_with_options(text, &AlignOptions::new(Alignment::Right))
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 // Basic alignment tests
457 #[test]
458 fn test_left_alignment() {
459 let text = "hello\nworld";
460 let result = left(text);
461 assert_eq!(result, text); // Left alignment should be no-op
462 }
463
464 #[test]
465 fn test_center_alignment() {
466 let text = "hi\nhello";
467 let result = center(text);
468 let lines: Vec<&str> = result.split('\n').collect();
469 assert_eq!(lines[0], " hi"); // 1 space padding for "hi"
470 assert_eq!(lines[1], "hello"); // No padding for "hello"
471 }
472
473 #[test]
474 fn test_right_alignment() {
475 let text = "hi\nhello";
476 let result = right(text);
477 let lines: Vec<&str> = result.split('\n').collect();
478 assert_eq!(lines[0], " hi"); // 3 spaces padding for "hi"
479 assert_eq!(lines[1], "hello"); // No padding for "hello"
480 }
481
482 // Unicode and ANSI tests
483 #[test]
484 fn test_unicode_characters() {
485 let text = "古\n古古古";
486 let result = center(text);
487 let lines: Vec<&str> = result.split('\n').collect();
488 assert_eq!(lines[0], " 古"); // 2 spaces padding (CJK char is width 2)
489 assert_eq!(lines[1], "古古古"); // No padding
490 }
491
492 #[test]
493 fn test_ansi_escape_sequences() {
494 let text = "hello\n\u{001B}[1mworld\u{001B}[0m";
495 let result = center(text);
496 let lines: Vec<&str> = result.split('\n').collect();
497 assert_eq!(lines[0], "hello");
498 assert_eq!(lines[1], "\u{001B}[1mworld\u{001B}[0m"); // ANSI codes preserved
499 }
500
501 #[test]
502 fn test_complex_ansi_sequences() {
503 // Test with multiple ANSI codes and colors
504 let text = "\x1b[31m\x1b[1mred\x1b[0m\n\x1b[32mgreen text\x1b[0m";
505 let result = right(text);
506 let lines: Vec<&str> = result.split('\n').collect();
507 // "red" has display width 3, "green text" has width 10
508 assert_eq!(lines[0], " \x1b[31m\x1b[1mred\x1b[0m"); // 7 spaces padding
509 assert_eq!(lines[1], "\x1b[32mgreen text\x1b[0m"); // No padding
510 }
511
512 // Edge cases
513 #[test]
514 fn test_empty_string() {
515 assert_eq!(ansi_align_with_options("", &AlignOptions::default()), "");
516 assert_eq!(left(""), "");
517 assert_eq!(center(""), "");
518 assert_eq!(right(""), "");
519 }
520
521 #[test]
522 fn test_single_line() {
523 let text = "hello";
524 assert_eq!(left(text), "hello");
525 assert_eq!(center(text), "hello");
526 assert_eq!(right(text), "hello");
527 }
528
529 #[test]
530 fn test_single_character() {
531 let text = "a\nb";
532 let result = center(text);
533 assert_eq!(result, "a\nb"); // Both lines same width, no padding needed
534 }
535
536 #[test]
537 fn test_whitespace_only() {
538 let text = " \n ";
539 let result = center(text);
540 let lines: Vec<&str> = result.split('\n').collect();
541 assert_eq!(lines[0], " "); // 3 spaces, no padding needed
542 assert_eq!(lines[1], " "); // 1 space + 1 padding space
543 }
544
545 // Custom options tests
546 #[test]
547 fn test_custom_split_and_pad() {
548 let text = "a|bb";
549 let opts = AlignOptions::new(Alignment::Right)
550 .with_split("|")
551 .with_pad('.');
552 let result = ansi_align_with_options(text, &opts);
553 assert_eq!(result, ".a|bb");
554 }
555
556 #[test]
557 fn test_custom_split_multichar() {
558 let text = "short<->very long line";
559 let opts = AlignOptions::new(Alignment::Center).with_split("<->");
560 let result = ansi_align_with_options(text, &opts);
561 assert_eq!(result, " short<->very long line");
562 }
563
564 #[test]
565 fn test_different_padding_chars() {
566 let text = "hi\nhello";
567
568 // Test dot padding
569 let opts = AlignOptions::new(Alignment::Right).with_pad('.');
570 let result = ansi_align_with_options(text, &opts);
571 assert_eq!(result, "...hi\nhello");
572
573 // Test underscore padding
574 let opts = AlignOptions::new(Alignment::Center).with_pad('_');
575 let result = ansi_align_with_options(text, &opts);
576 assert_eq!(result, "_hi\nhello");
577
578 // Test zero padding
579 let opts = AlignOptions::new(Alignment::Right).with_pad('0');
580 let result = ansi_align_with_options(text, &opts);
581 assert_eq!(result, "000hi\nhello");
582 }
583
584 // Performance and memory optimization tests
585 #[test]
586 fn test_large_padding() {
587 let text = format!("a\n{}", "b".repeat(100));
588 let result = right(&text);
589 let lines: Vec<&str> = result.split('\n').collect();
590 assert_eq!(lines[0].len(), 100); // 99 spaces + "a"
591 assert!(lines[0].starts_with(&" ".repeat(99)));
592 assert!(lines[0].ends_with('a'));
593 assert_eq!(lines[1], "b".repeat(100));
594 }
595
596 #[test]
597 fn test_no_padding_optimization() {
598 // Test that lines requiring no padding are handled efficiently
599 let text = "same\nsame\nsame";
600 let result = center(text);
601 assert_eq!(result, text); // Should be unchanged
602 }
603
604 // Width type tests
605 #[test]
606 fn test_width_type() {
607 let width = Width::new(42);
608 assert_eq!(width.get(), 42);
609
610 let width_from_usize: Width = 24.into();
611 assert_eq!(width_from_usize.get(), 24);
612
613 // Test ordering
614 assert!(Width::new(10) < Width::new(20));
615 assert_eq!(Width::new(15), Width::new(15));
616 }
617
618 #[test]
619 fn test_width_arithmetic() {
620 let w1 = Width::new(10);
621 let w2 = Width::new(5);
622
623 // Test addition
624 assert_eq!((w1 + w2).get(), 15);
625
626 // Test subtraction
627 assert_eq!((w1 - w2).get(), 5);
628
629 // Test multiplication
630 assert_eq!((w1 * w2).get(), 50);
631
632 // Test division
633 assert_eq!((w1 / w2).get(), 2);
634
635 // Test saturating subtraction
636 assert_eq!(w2.saturating_sub(w1).get(), 0); // 5 - 10 = 0 (saturated)
637 assert_eq!(w1.saturating_sub(w2).get(), 5); // 10 - 5 = 5
638
639 // Test is_zero
640 assert!(Width::new(0).is_zero());
641 assert!(!w1.is_zero());
642 }
643
644 #[test]
645 fn test_width_display() {
646 let width = Width::new(42);
647 assert_eq!(format!("{width}"), "42");
648 }
649
650 // Comprehensive alignment scenarios
651 #[test]
652 fn test_mixed_width_lines() {
653 let text = "a\nbb\nccc\ndddd\neeeee";
654
655 // Center alignment
656 let result = center(text);
657 let lines: Vec<&str> = result.split('\n').collect();
658
659 // The longest line is "eeeee" with 5 chars
660 // So padding for center should be: (5-1)/2=2, (5-2)/2=1, (5-3)/2=1, (5-4)/2=0, (5-5)/2=0
661 assert_eq!(lines[0], " a"); // 2 spaces + "a"
662 assert_eq!(lines[1], " bb"); // 1 space + "bb"
663 assert_eq!(lines[2], " ccc"); // 1 space + "ccc" (corrected)
664 assert_eq!(lines[3], "dddd"); // no padding (corrected)
665 assert_eq!(lines[4], "eeeee"); // no padding
666
667 // Right alignment
668 let result = right(text);
669 let lines: Vec<&str> = result.split('\n').collect();
670 assert_eq!(lines[0], " a"); // 4 spaces + "a"
671 assert_eq!(lines[1], " bb"); // 3 spaces + "bb"
672 assert_eq!(lines[2], " ccc"); // 2 spaces + "ccc"
673 assert_eq!(lines[3], " dddd"); // 1 space + "dddd"
674 assert_eq!(lines[4], "eeeee"); // no padding
675 }
676
677 #[test]
678 fn test_center_odd_padding() {
679 // Test center alignment with odd padding amounts
680 let text = "a\nbbbb";
681 let result = center(text);
682 let lines: Vec<&str> = result.split('\n').collect();
683 assert_eq!(lines[0], " a"); // (4-1)/2 = 1 space
684 assert_eq!(lines[1], "bbbb"); // no padding
685 }
686
687 #[test]
688 fn test_multiline_with_empty_lines() {
689 let text = "hello\n\nworld";
690 let result = center(text);
691 let lines: Vec<&str> = result.split('\n').collect();
692 assert_eq!(lines[0], "hello");
693 assert_eq!(lines[1], " "); // 2 spaces for empty line (center of 5-char width)
694 assert_eq!(lines[2], "world");
695 }
696
697 // Regression tests for performance improvements
698 #[test]
699 fn test_no_unnecessary_allocations() {
700 // This test ensures we don't regress on the performance improvements
701 let text = "line1\nline2\nline3";
702 let result = left(text);
703 // Left alignment should return original string (no allocations for processing)
704 assert_eq!(result, text);
705 }
706
707 #[test]
708 fn test_padding_efficiency() {
709 // Test the efficient padding creation for different sizes
710 let text = format!("a\n{}", "b".repeat(20));
711
712 // Small padding (should use repeat)
713 let opts = AlignOptions::new(Alignment::Right);
714 let result = ansi_align_with_options("a\nbb", &opts);
715 assert_eq!(result, " a\nbb");
716
717 // Large padding (should use with_capacity)
718 let result = ansi_align_with_options(&text, &opts);
719 let lines: Vec<&str> = result.split('\n').collect();
720 assert_eq!(lines[0].len(), 20); // 19 spaces + "a"
721 assert!(lines[0].ends_with('a'));
722 }
723
724 #[test]
725 fn test_create_padding_unicode() {
726 // Test padding with Unicode characters
727 let text = "a\nbb";
728 let opts = AlignOptions::new(Alignment::Right).with_pad('•');
729 let result = ansi_align_with_options(text, &opts);
730 assert_eq!(result, "•a\nbb");
731
732 // Test with multi-byte Unicode character
733 let opts = AlignOptions::new(Alignment::Right).with_pad('🎯');
734 let result = ansi_align_with_options(text, &opts);
735 assert_eq!(result, "🎯a\nbb");
736 }
737
738 // Integration tests
739 #[test]
740 fn test_real_world_scenario() {
741 // Simulate aligning a simple table or menu
742 let menu = "Home\nAbout Us\nContact\nServices";
743 let result = center(menu);
744 let lines: Vec<&str> = result.split('\n').collect();
745
746 // "About Us" and "Services" are both 8 chars (longest)
747 assert_eq!(lines[0], " Home"); // 2 spaces (8-4)/2 = 2
748 assert_eq!(lines[1], "About Us"); // no padding (8 chars)
749 assert_eq!(lines[2], "Contact"); // no padding - "Contact" is 7 chars, (8-7)/2 = 0
750 assert_eq!(lines[3], "Services"); // no padding (8 chars)
751 }
752
753 #[test]
754 fn test_code_alignment() {
755 // Test aligning code-like content
756 let code = "if x:\n return y\nelse:\n return z";
757 let result = right(code);
758 let lines: Vec<&str> = result.split('\n').collect();
759
760 // " return y" and " return z" are longest at 12 chars
761 assert_eq!(lines[0], " if x:"); // 7 spaces + "if x:" (12-5=7)
762 assert_eq!(lines[1], " return y"); // no padding (12 chars)
763 assert_eq!(lines[2], " else:"); // 7 spaces + "else:" (12-5=7)
764 assert_eq!(lines[3], " return z"); // no padding (12 chars)
765 }
766}