indent.rs 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. #[derive(Clone, Copy, PartialEq, Eq, Debug)]
  2. pub enum IndentType {
  3. Spaces,
  4. Tabs,
  5. }
  6. #[derive(Debug, Clone)]
  7. pub struct IndentOptions {
  8. width: usize,
  9. indent_string: String,
  10. split_line_attributes: bool,
  11. }
  12. impl IndentOptions {
  13. pub fn new(ty: IndentType, width: usize, split_line_attributes: bool) -> Self {
  14. assert_ne!(width, 0, "Cannot have an indent width of 0");
  15. Self {
  16. width,
  17. indent_string: match ty {
  18. IndentType::Tabs => "\t".into(),
  19. IndentType::Spaces => " ".repeat(width),
  20. },
  21. split_line_attributes,
  22. }
  23. }
  24. /// Gets a string containing one indent worth of whitespace
  25. pub fn indent_str(&self) -> &str {
  26. &self.indent_string
  27. }
  28. /// Computes the line length in characters, counting tabs as the indent width.
  29. pub fn line_length(&self, line: &str) -> usize {
  30. line.chars()
  31. .map(|ch| if ch == '\t' { self.width } else { 1 })
  32. .sum()
  33. }
  34. /// Estimates how many times the line has been indented.
  35. pub fn count_indents(&self, mut line: &str) -> usize {
  36. let mut indent = 0;
  37. while !line.is_empty() {
  38. // Try to count tabs
  39. let num_tabs = line.chars().take_while(|ch| *ch == '\t').count();
  40. if num_tabs > 0 {
  41. indent += num_tabs;
  42. line = &line[num_tabs..];
  43. continue;
  44. }
  45. // Try to count spaces
  46. let num_spaces = line.chars().take_while(|ch| *ch == ' ').count();
  47. if num_spaces >= self.width {
  48. // Intentionally floor here to take only the amount of space that matches an indent
  49. let num_space_indents = num_spaces / self.width;
  50. indent += num_space_indents;
  51. line = &line[num_space_indents * self.width..];
  52. continue;
  53. }
  54. // Line starts with either non-indent characters or an unevent amount of spaces,
  55. // so no more indent remains.
  56. break;
  57. }
  58. indent
  59. }
  60. pub fn split_line_attributes(&self) -> bool {
  61. self.split_line_attributes
  62. }
  63. }
  64. impl Default for IndentOptions {
  65. fn default() -> Self {
  66. Self::new(IndentType::Spaces, 4, false)
  67. }
  68. }
  69. #[cfg(test)]
  70. mod tests {
  71. use super::*;
  72. #[test]
  73. fn count_indents() {
  74. assert_eq!(
  75. IndentOptions::new(IndentType::Spaces, 4, false).count_indents("no indentation here!"),
  76. 0
  77. );
  78. assert_eq!(
  79. IndentOptions::new(IndentType::Spaces, 4, false).count_indents(" v += 2"),
  80. 1
  81. );
  82. assert_eq!(
  83. IndentOptions::new(IndentType::Spaces, 4, false).count_indents(" v += 2"),
  84. 2
  85. );
  86. assert_eq!(
  87. IndentOptions::new(IndentType::Spaces, 4, false).count_indents(" v += 2"),
  88. 2
  89. );
  90. assert_eq!(
  91. IndentOptions::new(IndentType::Spaces, 4, false).count_indents("\t\tv += 2"),
  92. 2
  93. );
  94. assert_eq!(
  95. IndentOptions::new(IndentType::Spaces, 4, false).count_indents("\t\t v += 2"),
  96. 2
  97. );
  98. assert_eq!(
  99. IndentOptions::new(IndentType::Spaces, 2, false).count_indents(" v += 2"),
  100. 2
  101. );
  102. }
  103. }