Skip to main content

forge_fmt/pp/
convenience.rs

1use super::{BeginToken, BreakToken, Breaks, IndentStyle, Printer, SIZE_INFINITY, Token};
2use std::borrow::Cow;
3
4impl Printer {
5    /// "raw box"
6    pub fn rbox(&mut self, indent: isize, breaks: Breaks) {
7        self.scan_begin(BeginToken { indent: IndentStyle::Block { offset: indent }, breaks });
8    }
9
10    /// Inconsistent breaking box
11    pub fn ibox(&mut self, indent: isize) {
12        self.rbox(indent, Breaks::Inconsistent);
13    }
14
15    /// Consistent breaking box
16    pub fn cbox(&mut self, indent: isize) {
17        self.rbox(indent, Breaks::Consistent);
18    }
19
20    pub fn visual_align(&mut self) {
21        self.scan_begin(BeginToken { indent: IndentStyle::Visual, breaks: Breaks::Consistent });
22    }
23
24    pub fn break_offset(&mut self, n: usize, off: isize) {
25        self.scan_break(BreakToken { offset: off, blank_space: n, ..BreakToken::default() });
26    }
27
28    pub fn end(&mut self) {
29        self.scan_end();
30    }
31
32    pub fn eof(mut self) -> String {
33        self.scan_eof();
34        self.out
35    }
36
37    pub fn word(&mut self, w: impl Into<Cow<'static, str>>) {
38        self.scan_string(w.into());
39    }
40
41    fn spaces(&mut self, n: usize) {
42        self.break_offset(n, 0);
43    }
44
45    pub fn zerobreak(&mut self) {
46        self.spaces(0);
47    }
48
49    pub fn space(&mut self) {
50        self.spaces(1);
51    }
52
53    pub fn hardbreak(&mut self) {
54        self.spaces(SIZE_INFINITY as usize);
55    }
56
57    pub fn last_token_is_neverbreak(&self) -> bool {
58        if let Some(token) = self.last_token() {
59            return token.is_neverbreak();
60        }
61
62        false
63    }
64
65    pub fn last_token_is_break(&self) -> bool {
66        if let Some(token) = self.last_token() {
67            return matches!(token, Token::Break(_));
68        }
69        false
70    }
71
72    pub fn last_token_is_space(&self) -> bool {
73        if let Some(token) = self.last_token()
74            && token.is_space()
75        {
76            return true;
77        }
78
79        self.out.ends_with(' ')
80    }
81
82    pub fn is_beginning_of_line(&self) -> bool {
83        match self.last_token() {
84            // Verbatim-printed source can end with a line break. Regular tokens never contain one.
85            Some(Token::String(s)) => s.ends_with('\n'),
86            Some(last_token) => last_token.is_hardbreak(),
87            None => self.out.is_empty() || self.out.ends_with('\n'),
88        }
89    }
90
91    /// Attempts to identify whether the current position is:
92    ///   1. the beginning of a line (empty)
93    ///   2. a line with only indentation (just whitespaces)
94    ///
95    /// NOTE: this is still an educated guess, based on a heuristic.
96    pub fn is_bol_or_only_ind(&self) -> bool {
97        for i in self.buf.index_range().rev() {
98            let token = &self.buf[i].token;
99            if token.is_hardbreak() {
100                return true;
101            }
102            // Verbatim-printed source can contain line breaks; judge by its last line.
103            if let Token::String(s) = token
104                && let Some(pos) = s.rfind('\n')
105            {
106                return s[pos + 1..].trim().is_empty();
107            }
108            if Self::token_has_non_whitespace_content(token) {
109                return false;
110            }
111        }
112
113        let last_line =
114            if let Some(pos) = self.out.rfind('\n') { &self.out[pos + 1..] } else { &self.out[..] };
115
116        last_line.trim().is_empty()
117    }
118
119    fn token_has_non_whitespace_content(token: &Token) -> bool {
120        match token {
121            Token::String(s) => !s.trim().is_empty(),
122            Token::Break(BreakToken { pre_break: Some(s), .. }) => !s.trim().is_empty(),
123            _ => false,
124        }
125    }
126
127    pub(crate) fn hardbreak_tok_offset(offset: isize) -> Token {
128        Token::Break(BreakToken {
129            offset,
130            blank_space: SIZE_INFINITY as usize,
131            ..BreakToken::default()
132        })
133    }
134
135    pub fn hardbreak_if_nonempty(&mut self) {
136        self.scan_break(BreakToken {
137            blank_space: SIZE_INFINITY as usize,
138            if_nonempty: true,
139            ..BreakToken::default()
140        });
141    }
142
143    pub fn neverbreak(&mut self) {
144        self.scan_break(BreakToken { never_break: true, ..BreakToken::default() });
145    }
146}
147
148impl Token {
149    pub(crate) const fn is_neverbreak(&self) -> bool {
150        if let Self::Break(BreakToken { never_break, .. }) = *self {
151            return never_break;
152        }
153        false
154    }
155
156    pub(crate) const fn is_hardbreak(&self) -> bool {
157        if let Self::Break(BreakToken { blank_space, never_break, .. }) = *self {
158            return blank_space == SIZE_INFINITY as usize && !never_break;
159        }
160        false
161    }
162
163    pub(crate) fn is_space(&self) -> bool {
164        match self {
165            Self::Break(BreakToken { offset, blank_space, .. }) => {
166                *offset == 0 && *blank_space == 1
167            }
168            Self::String(s) => s.ends_with(' '),
169            _ => false,
170        }
171    }
172}