1use std::cmp::Ordering;
5use std::hash::Hash;
6use std::ops::{Add, Range};
7
8use crate::intern::Token;
9
10pub trait SliderHeuristic {
16 fn best_slider_end(&mut self, tokens: &[Token], hunk: Range<u32>, earliest_end: u32) -> u32;
28}
29
30impl<F> SliderHeuristic for F
31where
32 F: FnMut(&[Token], Range<u32>, u32) -> u32,
33{
34 fn best_slider_end(&mut self, tokens: &[Token], hunk: Range<u32>, earliest_end: u32) -> u32 {
35 self(tokens, hunk, earliest_end)
36 }
37}
38
39pub struct NoSliderHeuristic;
44
45impl SliderHeuristic for NoSliderHeuristic {
46 fn best_slider_end(&mut self, _tokens: &[Token], hunk: Range<u32>, _earliest_end: u32) -> u32 {
47 hunk.end
48 }
49}
50
51pub struct IndentHeuristic<IndentOfToken> {
57 indent_of_token: IndentOfToken,
59}
60
61impl<IndentOfToken> IndentHeuristic<IndentOfToken> {
62 pub fn new(indent_of_token: IndentOfToken) -> Self {
68 Self { indent_of_token }
69 }
70}
71
72impl<IndentOfToken: Fn(Token) -> IndentLevel> SliderHeuristic for IndentHeuristic<IndentOfToken> {
73 fn best_slider_end(&mut self, tokens: &[Token], hunk: Range<u32>, earliest_end: u32) -> u32 {
74 const MAX_SLIDING: u32 = 100;
75 let mut top_slider_end = earliest_end;
78 if top_slider_end < hunk.start - 1 {
80 top_slider_end = hunk.start - 1;
81 }
82 if hunk.end > top_slider_end + MAX_SLIDING {
83 top_slider_end = hunk.end - MAX_SLIDING;
84 }
85 let group_size = hunk.end - hunk.start;
86 let mut best_score = Score::for_range(
87 top_slider_end - group_size..top_slider_end,
88 tokens,
89 &self.indent_of_token,
90 );
91 let mut best_slider_end = top_slider_end;
92 for slider_end in (top_slider_end + 1)..=hunk.end {
93 let score = Score::for_range(slider_end - group_size..slider_end, tokens, &self.indent_of_token);
94 if score.is_improvement_over(best_score) {
95 best_score = score;
96 best_slider_end = slider_end;
97 }
98 }
99 best_slider_end
100 }
101}
102
103#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd)]
108pub struct IndentLevel(u8);
109
110impl IndentLevel {
111 const BLANK: IndentLevel = IndentLevel(u8::MAX);
113 const MAX: IndentLevel = IndentLevel(200);
115
116 pub fn for_ascii_line(src: impl IntoIterator<Item = u8>, tab_width: u8) -> IndentLevel {
127 let mut indent_level = IndentLevel(0);
128 let tab_width = tab_width.max(1);
129 for c in src {
130 match c {
131 b' ' => indent_level = indent_level.increased_by(1),
132 b'\t' => indent_level = indent_level.increased_by(tab_width - indent_level.0 % tab_width),
133 b'\r' | b'\n' | b'\x0C' => (),
134 _ => return indent_level,
135 }
136 if indent_level >= Self::MAX {
137 return Self::MAX;
138 }
139 }
140 IndentLevel::BLANK
141 }
142
143 pub fn for_line(src: impl IntoIterator<Item = char>, tab_width: u8) -> IndentLevel {
154 let mut indent_level = IndentLevel(0);
155 let tab_width = tab_width.max(1);
156 for c in src {
157 match c {
158 ' ' => indent_level = indent_level.increased_by(1),
159 '\t' => indent_level = indent_level.increased_by(tab_width - indent_level.0 % tab_width),
160 '\r' | '\n' | '\x0C' => (),
161 _ => return indent_level,
162 }
163 if indent_level >= Self::MAX {
164 return Self::MAX;
165 }
166 }
167 IndentLevel::BLANK
168 }
169
170 fn increased_by(self, amount: u8) -> Self {
171 IndentLevel(self.0.saturating_add(amount).min(Self::MAX.0))
172 }
173
174 fn map_or<T>(self, default: T, f: impl FnOnce(u8) -> T) -> T {
175 if self == Self::BLANK {
176 default
177 } else {
178 f(self.0)
179 }
180 }
181
182 fn or(self, default: Self) -> Self {
183 if self == Self::BLANK {
184 default
185 } else {
186 self
187 }
188 }
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub struct Indents {
196 indent: IndentLevel,
198 prev_indent: IndentLevel,
200 next_indent: IndentLevel,
202 leading_blanks: u8,
204 trailing_blanks: u8,
206}
207
208const MAX_BLANKS: usize = 20;
210
211impl Indents {
212 fn at_token(tokens: &[Token], token_idx: usize, indent_of_token: impl Fn(Token) -> IndentLevel) -> Indents {
213 let (leading_blank_lines, indent_previous_line) = tokens[..token_idx]
214 .iter()
215 .rev()
216 .enumerate()
217 .find_map(|(i, &token)| {
218 if i == MAX_BLANKS {
219 Some((i, IndentLevel(0)))
220 } else {
221 let level = indent_of_token(token);
222 if level == IndentLevel::BLANK {
223 None
224 } else {
225 Some((i, level))
226 }
227 }
228 })
229 .unwrap_or((token_idx, IndentLevel::BLANK));
230 let at_eof = token_idx == tokens.len();
231 let (trailing_blank_lines, indent_next_line) = if at_eof {
232 (0, IndentLevel::BLANK)
233 } else {
234 tokens[token_idx + 1..]
235 .iter()
236 .enumerate()
237 .find_map(|(i, &token)| {
238 if i == MAX_BLANKS {
239 Some((i, IndentLevel(0)))
240 } else {
241 let level = indent_of_token(token);
242 if level == IndentLevel::BLANK {
243 None
244 } else {
245 Some((i, level))
246 }
247 }
248 })
249 .unwrap_or((token_idx, IndentLevel::BLANK))
250 };
251 let indent = tokens
252 .get(token_idx)
253 .map_or(IndentLevel::BLANK, |&token| indent_of_token(token));
254 Indents {
255 indent,
256 prev_indent: indent_previous_line,
257 next_indent: indent_next_line,
258 leading_blanks: leading_blank_lines as u8,
259 trailing_blanks: trailing_blank_lines as u8,
260 }
261 }
262
263 fn score(&self) -> Score {
264 let mut penalty = 0;
265 if self.prev_indent == IndentLevel::BLANK && self.leading_blanks == 0 {
266 penalty += START_OF_FILE_PENALTY;
267 }
268 if self.next_indent == IndentLevel::BLANK && self.trailing_blanks == 0 {
269 penalty += END_OF_FILE_PENALTY;
270 }
271
272 let trailing_blank_lines = if self.indent == IndentLevel::BLANK {
273 self.trailing_blanks as i32 + 1
274 } else {
275 0
276 };
277 let total_blank_lines = trailing_blank_lines + self.leading_blanks as i32;
278 penalty += TOTAL_BLANK_LINE_WEIGHT * total_blank_lines + trailing_blank_lines * TRAILING_BLANK_LINES_WEIGHT;
279 let indent = self.indent.or(self.next_indent);
280 if indent != IndentLevel::BLANK && self.prev_indent != IndentLevel::BLANK {
281 match indent.0.cmp(&self.prev_indent.0) {
282 Ordering::Equal => {}
283 Ordering::Less if self.next_indent.0 <= indent.0 => {
286 penalty += if total_blank_lines != 0 {
287 RELATIVE_DEDENT_WITH_BLANK_PENALTY
288 } else {
289 RELATIVE_DEDENT_PENALTY
290 }
291 }
292 Ordering::Less => {
293 penalty += if total_blank_lines != 0 {
294 RELATIVE_OUTDENT_WITH_BLANK_PENALTY
295 } else {
296 RELATIVE_OUTDENT_PENALTY
297 }
298 }
299 Ordering::Greater => {
300 penalty += if total_blank_lines != 0 {
301 RELATIVE_INDENT_WITH_BLANK_PENALTY
302 } else {
303 RELATIVE_INDENT_PENALTY
304 }
305 }
306 }
307 }
308 Score {
309 indent: indent.map_or(-1, i32::from),
310 penalty,
311 }
312 }
313}
314
315const START_OF_FILE_PENALTY: i32 = 1;
317const END_OF_FILE_PENALTY: i32 = 21;
319const TOTAL_BLANK_LINE_WEIGHT: i32 = -30;
321const TRAILING_BLANK_LINES_WEIGHT: i32 = 6;
323
324const RELATIVE_INDENT_PENALTY: i32 = -4;
326const RELATIVE_INDENT_WITH_BLANK_PENALTY: i32 = 10;
328
329const RELATIVE_OUTDENT_PENALTY: i32 = 24;
331const RELATIVE_OUTDENT_WITH_BLANK_PENALTY: i32 = 17;
333
334const RELATIVE_DEDENT_PENALTY: i32 = 23;
336const RELATIVE_DEDENT_WITH_BLANK_PENALTY: i32 = 17;
338
339const INDENT_WEIGHT: i32 = 60;
341
342#[derive(PartialEq, Eq, Clone, Copy)]
347struct Score {
348 indent: i32,
350 penalty: i32,
352}
353
354impl Score {
355 fn for_range(range: Range<u32>, tokens: &[Token], indent_of_token: impl Fn(Token) -> IndentLevel) -> Score {
356 Indents::at_token(tokens, range.start as usize, &indent_of_token).score()
357 + Indents::at_token(tokens, range.end as usize, &indent_of_token).score()
358 }
359}
360
361impl Add for Score {
362 type Output = Score;
363
364 fn add(self, rhs: Self) -> Self::Output {
365 Score {
366 indent: self.indent + rhs.indent,
367 penalty: self.penalty + rhs.penalty,
368 }
369 }
370}
371
372impl Score {
373 fn is_improvement_over(self, prev_score: Self) -> bool {
374 let indent_score = match prev_score.indent.cmp(&self.indent) {
376 Ordering::Less => INDENT_WEIGHT,
377 Ordering::Greater => -INDENT_WEIGHT,
378 Ordering::Equal => 0,
379 };
380 (indent_score + self.penalty - prev_score.penalty) <= 0
381 }
382}
383
384#[cfg(test)]
385mod tests {
386 use super::IndentLevel;
387
388 #[test]
389 fn ascii_indent_clamps_before_overflow() {
390 assert_eq!(
391 IndentLevel::for_ascii_line(std::iter::repeat(b' ').take(255), 1),
392 IndentLevel::MAX
393 );
394 assert_eq!(
395 IndentLevel::for_ascii_line(std::iter::repeat(b'\t').take(8), u8::MAX),
396 IndentLevel::MAX
397 );
398 }
399
400 #[test]
401 fn unicode_indent_treats_zero_tab_width_as_one() {
402 assert_eq!(IndentLevel::for_line(['\t', 'x'], 0), IndentLevel(1));
403 }
404}