ruff_python_trivia/
whitespace.rs1use ruff_source_file::LineRanges;
2use ruff_text_size::{TextRange, TextSize};
3
4pub fn indentation_at_offset(offset: TextSize, source: &str) -> Option<&str> {
6 let line_start = source.line_start(offset);
7 let indentation = &source[TextRange::new(line_start, offset)];
8
9 indentation
10 .chars()
11 .all(is_python_whitespace)
12 .then_some(indentation)
13}
14
15pub fn has_leading_content(offset: TextSize, source: &str) -> bool {
17 let line_start = source.line_start(offset);
18 let leading = &source[TextRange::new(line_start, offset)];
19 leading.chars().any(|char| !is_python_whitespace(char))
20}
21
22pub fn has_trailing_content(offset: TextSize, source: &str) -> bool {
24 let line_end = source.line_end(offset);
25 let trailing = &source[TextRange::new(offset, line_end)];
26
27 for char in trailing.chars() {
28 if char == '#' {
29 return false;
30 }
31 if !is_python_whitespace(char) {
32 return true;
33 }
34 }
35 false
36}
37
38pub const fn is_python_whitespace(c: char) -> bool {
41 matches!(
42 c,
43 ' ' | '\t' | '\x0C'
45 )
46}
47
48pub fn leading_indentation(line: &str) -> &str {
50 line.find(|char: char| !is_python_whitespace(char))
51 .map_or(line, |index| &line[..index])
52}
53
54pub trait PythonWhitespace {
55 fn trim_whitespace(&self) -> &Self;
58
59 fn trim_whitespace_start(&self) -> &Self;
62
63 fn trim_whitespace_end(&self) -> &Self;
66}
67
68impl PythonWhitespace for str {
69 fn trim_whitespace(&self) -> &Self {
70 self.trim_matches(is_python_whitespace)
71 }
72
73 fn trim_whitespace_start(&self) -> &Self {
74 self.trim_start_matches(is_python_whitespace)
75 }
76
77 fn trim_whitespace_end(&self) -> &Self {
78 self.trim_end_matches(is_python_whitespace)
79 }
80}