floem_editor_core/
indent.rs1use lapce_xi_rope::Rope;
2
3use crate::{
4 buffer::{rope_text::RopeText, Buffer},
5 chars::{char_is_line_ending, char_is_whitespace},
6 cursor::CursorAffinity,
7 selection::Selection,
8};
9
10#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
14pub enum IndentStyle {
15 Tabs,
16 Spaces(u8),
17}
18
19impl std::fmt::Display for IndentStyle {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 match self {
22 IndentStyle::Tabs => f.write_str("Tabs"),
23 IndentStyle::Spaces(spaces) => f.write_fmt(format_args!("{spaces} spaces")),
24 }
25 }
26}
27
28impl IndentStyle {
29 pub const LONGEST_INDENT: &'static str = " "; pub const DEFAULT_INDENT: IndentStyle = IndentStyle::Spaces(4);
31
32 #[allow(clippy::should_implement_trait)]
36 #[inline]
37 pub fn from_str(indent: &str) -> Self {
38 debug_assert!(!indent.is_empty() && indent.len() <= Self::LONGEST_INDENT.len());
39 if indent.starts_with(' ') {
40 IndentStyle::Spaces(indent.len() as u8)
41 } else {
42 IndentStyle::Tabs
43 }
44 }
45
46 #[inline]
47 pub fn as_str(&self) -> &'static str {
48 match *self {
49 IndentStyle::Tabs => "\t",
50 IndentStyle::Spaces(x) if x <= Self::LONGEST_INDENT.len() as u8 => {
51 Self::LONGEST_INDENT.split_at(x.into()).0
52 }
53 IndentStyle::Spaces(n) => {
56 debug_assert!(n > 0 && n <= Self::LONGEST_INDENT.len() as u8);
57 " "
58 }
59 }
60 }
61}
62
63pub fn create_edit<'s>(buffer: &Buffer, offset: usize, indent: &'s str) -> (Selection, &'s str) {
64 let indent = if indent.starts_with('\t') {
65 indent
66 } else {
67 let (_, col) = buffer.offset_to_line_col(offset);
68 indent.split_at(indent.len() - col % indent.len()).0
69 };
70 (Selection::caret(offset, CursorAffinity::Backward), indent)
71}
72
73pub fn create_outdent<'s>(
74 buffer: &Buffer,
75 offset: usize,
76 indent: &'s str,
77) -> Option<(Selection, &'s str)> {
78 let (_, col) = buffer.offset_to_line_col(offset);
79 if col == 0 {
80 return None;
81 }
82
83 let start = if indent.starts_with('\t') {
84 offset - 1
85 } else {
86 let r = col % indent.len();
87 let r = if r == 0 { indent.len() } else { r };
88 offset - r
89 };
90
91 Some((
92 Selection::region(start, offset, CursorAffinity::Backward),
93 "",
94 ))
95}
96
97pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
102 let histogram: [usize; 9] = {
107 let mut histogram = [0; 9];
108 let mut prev_line_is_tabs = false;
109 let mut prev_line_leading_count = 0usize;
110
111 let offset = document_text
114 .offset_of_line(document_text.line_of_offset(document_text.len()).min(1000));
115 'outer: for line in document_text.lines(..offset) {
116 let mut c_iter = line.chars();
117
118 let is_tabs = match c_iter.next() {
120 Some('\t') => true,
121 Some(' ') => false,
122
123 Some(c) if char_is_line_ending(c) => continue,
125
126 _ => {
127 prev_line_is_tabs = false;
128 prev_line_leading_count = 0;
129 continue;
130 }
131 };
132
133 let mut leading_count = 1;
135 let mut count_is_done = false;
136 for c in c_iter {
137 match c {
138 '\t' if is_tabs && !count_is_done => leading_count += 1,
139 ' ' if !is_tabs && !count_is_done => leading_count += 1,
140
141 c if char_is_whitespace(c) => count_is_done = true,
146
147 c if char_is_line_ending(c) => continue 'outer,
149
150 _ => break,
151 }
152
153 if leading_count > 256 {
155 continue 'outer;
156 }
157 }
158
159 if (prev_line_is_tabs == is_tabs || prev_line_leading_count == 0)
162 && prev_line_leading_count < leading_count
163 {
164 if is_tabs {
165 histogram[0] += 1;
166 } else {
167 let amount = leading_count - prev_line_leading_count;
168 if amount <= 8 {
169 histogram[amount] += 1;
170 }
171 }
172 }
173
174 prev_line_is_tabs = is_tabs;
177 prev_line_leading_count = leading_count;
178 }
179
180 histogram[0] *= 2;
183
184 histogram
185 };
186
187 let indent = histogram
190 .iter()
191 .enumerate()
192 .max_by_key(|kv| kv.1)
193 .unwrap()
194 .0;
195 let indent_freq = histogram[indent];
196 let indent_freq_2 = *histogram
197 .iter()
198 .enumerate()
199 .filter(|kv| kv.0 != indent)
200 .map(|kv| kv.1)
201 .max()
202 .unwrap();
203
204 if indent_freq >= 1 && (indent_freq_2 as f64 / indent_freq as f64) < 0.66 {
207 Some(match indent {
208 0 => IndentStyle::Tabs,
209 _ => IndentStyle::Spaces(indent as u8),
210 })
211 } else {
212 None
213 }
214}