1use lapce_xi_rope::Rope;
2
3pub enum SnapDirection {
6 Left,
7 Right,
8 Nearest,
9}
10
11pub fn snap_to_soft_tab(
14 text: &Rope,
15 offset: usize,
16 direction: SnapDirection,
17 tab_width: usize,
18) -> usize {
19 let line = text.line_of_offset(offset);
21 let start_line_offset = text.offset_of_line(line);
23 let offset_within_line = offset - start_line_offset;
25
26 start_line_offset
27 + snap_to_soft_tab_logic(
28 text,
29 offset_within_line,
30 start_line_offset,
31 direction,
32 tab_width,
33 )
34}
35
36pub fn snap_to_soft_tab_line_col(
39 text: &Rope,
40 line: usize,
41 col: usize,
42 direction: SnapDirection,
43 tab_width: usize,
44) -> usize {
45 let start_line_offset = text.offset_of_line(line);
47
48 snap_to_soft_tab_logic(text, col, start_line_offset, direction, tab_width)
49}
50
51fn snap_to_soft_tab_logic(
56 text: &Rope,
57 offset_or_col: usize,
58 start_line_offset: usize,
59 direction: SnapDirection,
60 tab_width: usize,
61) -> usize {
62 assert!(tab_width >= 1);
63
64 let space_count = (count_spaces_from(text, start_line_offset) / tab_width) * tab_width;
66
67 if offset_or_col >= space_count {
69 return offset_or_col;
70 }
71
72 let bias = match direction {
73 SnapDirection::Left => 0,
74 SnapDirection::Right => tab_width - 1,
75 SnapDirection::Nearest => tab_width / 2,
76 };
77
78 ((offset_or_col + bias) / tab_width) * tab_width
79}
80
81fn count_spaces_from(text: &Rope, from_offset: usize) -> usize {
83 let mut cursor = lapce_xi_rope::Cursor::new(text, from_offset);
84 let mut space_count = 0usize;
85 while let Some(next) = cursor.next_codepoint() {
86 if next != ' ' {
87 break;
88 }
89 space_count += 1;
90 }
91 space_count
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_count_spaces_from() {
100 let text = Rope::from(" abc\n def\nghi\n");
101 assert_eq!(count_spaces_from(&text, 0), 5);
102 assert_eq!(count_spaces_from(&text, 1), 4);
103 assert_eq!(count_spaces_from(&text, 5), 0);
104 assert_eq!(count_spaces_from(&text, 6), 0);
105
106 assert_eq!(count_spaces_from(&text, 8), 0);
107 assert_eq!(count_spaces_from(&text, 9), 3);
108 assert_eq!(count_spaces_from(&text, 10), 2);
109
110 assert_eq!(count_spaces_from(&text, 16), 0);
111 assert_eq!(count_spaces_from(&text, 17), 0);
112 }
113
114 #[test]
115 fn test_snap_to_soft_tab() {
116 let text = Rope::from(" abc\n def\n ghi\nklm\n opq");
117
118 let tab_width = 4;
119
120 let test_cases = [
122 (0, 0, 0, 0),
123 (1, 0, 0, 4),
124 (2, 0, 4, 4),
125 (3, 0, 4, 4),
126 (4, 4, 4, 4),
127 (5, 4, 4, 8),
128 (6, 4, 8, 8),
129 (7, 4, 8, 8),
130 (8, 8, 8, 8),
131 (9, 9, 9, 9),
132 (10, 10, 10, 10),
133 (11, 11, 11, 11),
134 (12, 12, 12, 12),
135 (13, 13, 13, 13),
136 (14, 14, 14, 14),
137 (15, 14, 14, 18),
138 (16, 14, 18, 18),
139 (17, 14, 18, 18),
140 (18, 18, 18, 18),
141 (19, 19, 19, 19),
142 (20, 20, 20, 20),
143 (21, 21, 21, 21),
144 ];
145
146 for test_case in test_cases {
147 assert_eq!(
148 snap_to_soft_tab(&text, test_case.0, SnapDirection::Left, tab_width),
149 test_case.1
150 );
151 assert_eq!(
152 snap_to_soft_tab(&text, test_case.0, SnapDirection::Nearest, tab_width),
153 test_case.2
154 );
155 assert_eq!(
156 snap_to_soft_tab(&text, test_case.0, SnapDirection::Right, tab_width),
157 test_case.3
158 );
159 }
160 }
161
162 #[test]
163 fn test_snap_to_soft_tab_line_col() {
164 let text = Rope::from(" abc\n def\n ghi\nklm\n opq");
165
166 let tab_width = 4;
167
168 let test_cases = [
170 (0, 0, 0, 0, 0),
171 (0, 1, 0, 0, 4),
172 (0, 2, 0, 4, 4),
173 (0, 3, 0, 4, 4),
174 (0, 4, 4, 4, 4),
175 (0, 5, 4, 4, 8),
176 (0, 6, 4, 8, 8),
177 (0, 7, 4, 8, 8),
178 (0, 8, 8, 8, 8),
179 (0, 9, 9, 9, 9),
180 (0, 10, 10, 10, 10),
181 (0, 11, 11, 11, 11),
182 (0, 12, 12, 12, 12),
183 (0, 13, 13, 13, 13),
184 (1, 0, 0, 0, 0),
185 (1, 1, 0, 0, 4),
186 (1, 2, 0, 4, 4),
187 (1, 3, 0, 4, 4),
188 (1, 4, 4, 4, 4),
189 (1, 5, 5, 5, 5),
190 (1, 6, 6, 6, 6),
191 (1, 7, 7, 7, 7),
192 (4, 0, 0, 0, 0),
193 (4, 1, 0, 0, 4),
194 (4, 2, 0, 4, 4),
195 (4, 3, 0, 4, 4),
196 (4, 4, 4, 4, 4),
197 (4, 5, 4, 4, 8),
198 (4, 6, 4, 8, 8),
199 (4, 7, 4, 8, 8),
200 (4, 8, 8, 8, 8),
201 (4, 9, 9, 9, 9),
202 ];
203
204 for test_case in test_cases {
205 assert_eq!(
206 snap_to_soft_tab_line_col(
207 &text,
208 test_case.0,
209 test_case.1,
210 SnapDirection::Left,
211 tab_width
212 ),
213 test_case.2
214 );
215 assert_eq!(
216 snap_to_soft_tab_line_col(
217 &text,
218 test_case.0,
219 test_case.1,
220 SnapDirection::Nearest,
221 tab_width
222 ),
223 test_case.3
224 );
225 assert_eq!(
226 snap_to_soft_tab_line_col(
227 &text,
228 test_case.0,
229 test_case.1,
230 SnapDirection::Right,
231 tab_width
232 ),
233 test_case.4
234 );
235 }
236 }
237}