rat_text/text_core/
core_op.rs1use crate::core::{TextCore, TextStore};
2use crate::{Cursor, TextError, TextPosition, TextRange};
3
4#[allow(clippy::needless_bool)]
6pub fn insert_quotes<Store: TextStore + Default>(
7 core: &mut TextCore<Store>,
8 mut sel: TextRange,
9 c: char,
10) -> Result<bool, TextError> {
11 core.begin_undo_seq();
12
13 if sel.end.x > 0 {
15 let first = TextRange::new(sel.start, (sel.start.x + 1, sel.start.y));
16 let last = TextRange::new((sel.end.x - 1, sel.end.y), sel.end);
17 let c0 = core.str_slice(first).expect("valid_slice");
18 let c1 = core.str_slice(last).expect("valid_slice");
19 let remove_quote = if c == '\'' || c == '`' || c == '"' {
20 if c0 == "'" && c1 == "'" {
21 true
22 } else if c0 == "\"" && c1 == "\"" {
23 true
24 } else if c0 == "`" && c1 == "`" {
25 true
26 } else {
27 false
28 }
29 } else {
30 if c0 == "<" && c1 == ">" {
31 true
32 } else if c0 == "(" && c1 == ")" {
33 true
34 } else if c0 == "[" && c1 == "]" {
35 true
36 } else if c0 == "{" && c1 == "}" {
37 true
38 } else {
39 false
40 }
41 };
42 if remove_quote {
43 core.remove_char_range(last)?;
44 core.remove_char_range(first)?;
45 if sel.start.y == sel.end.y {
46 sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 2, sel.end.y));
47 } else {
48 sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 1, sel.end.y));
49 }
50 }
51 }
52
53 let cc = match c {
54 '\'' => '\'',
55 '`' => '`',
56 '"' => '"',
57 '<' => '>',
58 '(' => ')',
59 '[' => ']',
60 '{' => '}',
61 _ => unreachable!("invalid quotes"),
62 };
63 core.insert_char(sel.end, cc)?;
64 core.insert_char(sel.start, c)?;
65 if sel.start.y == sel.end.y {
66 sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 2, sel.end.y));
67 } else {
68 sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 1, sel.end.y));
69 }
70 core.set_selection(sel.start, sel.end);
71 core.end_undo_seq();
72
73 Ok(true)
74}
75
76pub fn insert_tab<Store: TextStore + Default>(
78 core: &mut TextCore<Store>,
79 mut pos: TextPosition,
80 expand_tabs: bool,
81 tab_width: u32,
82) -> Result<bool, TextError> {
83 if expand_tabs {
84 let n = tab_width - (pos.x % tab_width);
85 for _ in 0..n {
86 core.insert_char(pos, ' ')?;
87 pos.x += 1;
88 }
89 } else {
90 core.insert_char(pos, '\t')?;
91 }
92
93 Ok(true)
94}
95
96pub fn remove_prev_char<Store: TextStore + Default>(
98 core: &mut TextCore<Store>,
99 pos: TextPosition,
100) -> Result<bool, TextError> {
101 let (sx, sy) = if pos.y == 0 && pos.x == 0 {
102 (0, 0)
103 } else if pos.y > 0 && pos.x == 0 {
104 let prev_line_width = core.line_width(pos.y - 1).expect("line_width");
105 (prev_line_width, pos.y - 1)
106 } else {
107 (pos.x - 1, pos.y)
108 };
109 let range = TextRange::new((sx, sy), (pos.x, pos.y));
110
111 core.remove_char_range(range)
112}
113
114pub fn remove_next_char<Store: TextStore + Default>(
116 core: &mut TextCore<Store>,
117 pos: TextPosition,
118) -> Result<bool, TextError> {
119 let c_line_width = core.line_width(pos.y)?;
120 let c_last_line = core.len_lines() - 1;
121
122 let (ex, ey) = if pos.y == c_last_line && pos.x == c_line_width {
123 (pos.x, pos.y)
124 } else if pos.y != c_last_line && pos.x == c_line_width {
125 (0, pos.y + 1)
126 } else {
127 (pos.x + 1, pos.y)
128 };
129 let range = TextRange::new((pos.x, pos.y), (ex, ey));
130
131 core.remove_char_range(range)
132}
133
134pub fn next_word_start<Store: TextStore + Default>(
137 core: &TextCore<Store>,
138 pos: TextPosition,
139) -> Result<TextPosition, TextError> {
140 let mut it = core.text_graphemes(pos)?;
141 let mut last_pos = it.text_offset();
142 loop {
143 let Some(c) = it.next() else {
144 break;
145 };
146 last_pos = c.text_bytes().start;
147 if !c.is_whitespace() {
148 break;
149 }
150 }
151
152 Ok(core.byte_pos(last_pos).expect("valid_pos"))
153}
154
155pub fn next_word_end<Store: TextStore + Default>(
158 core: &TextCore<Store>,
159 pos: TextPosition,
160) -> Result<TextPosition, TextError> {
161 let mut it = core.text_graphemes(pos)?;
162 let mut last_pos = it.text_offset();
163 let mut init = true;
164 loop {
165 let Some(c) = it.next() else {
166 break;
167 };
168 last_pos = c.text_bytes().start;
169 if init {
170 if !c.is_whitespace() {
171 init = false;
172 }
173 } else {
174 if c.is_whitespace() {
175 break;
176 }
177 }
178 last_pos = c.text_bytes().end;
179 }
180
181 Ok(core.byte_pos(last_pos).expect("valid_pos"))
182}
183
184pub fn prev_word_start<Store: TextStore + Default>(
190 core: &TextCore<Store>,
191 pos: TextPosition,
192) -> Result<TextPosition, TextError> {
193 let mut it = core.text_graphemes(pos)?;
194 let mut last_pos = it.text_offset();
195 let mut init = true;
196 loop {
197 let Some(c) = it.prev() else {
198 break;
199 };
200 if init {
201 if !c.is_whitespace() {
202 init = false;
203 }
204 } else {
205 if c.is_whitespace() {
206 break;
207 }
208 }
209 last_pos = c.text_bytes().start;
210 }
211
212 Ok(core.byte_pos(last_pos).expect("valid_pos"))
213}
214
215pub fn prev_word_end<Store: TextStore + Default>(
219 core: &TextCore<Store>,
220 pos: TextPosition,
221) -> Result<TextPosition, TextError> {
222 let mut it = core.text_graphemes(pos)?;
223 let mut last_pos = it.text_offset();
224 loop {
225 let Some(c) = it.prev() else {
226 break;
227 };
228 if !c.is_whitespace() {
229 break;
230 }
231 last_pos = c.text_bytes().start;
232 }
233
234 Ok(core.byte_pos(last_pos).expect("valid_pos"))
235}
236
237pub fn is_word_boundary<Store: TextStore + Default>(
239 core: &TextCore<Store>,
240 pos: TextPosition,
241) -> Result<bool, TextError> {
242 let mut it = core.text_graphemes(pos)?;
243 if let Some(c0) = it.prev() {
244 it.next();
245 if let Some(c1) = it.next() {
246 Ok(c0.is_whitespace() && !c1.is_whitespace()
247 || !c0.is_whitespace() && c1.is_whitespace())
248 } else {
249 Ok(false)
250 }
251 } else {
252 Ok(false)
253 }
254}
255
256pub fn word_start<Store: TextStore + Default>(
259 core: &TextCore<Store>,
260 pos: TextPosition,
261) -> Result<TextPosition, TextError> {
262 let mut it = core.text_graphemes(pos)?;
263 let mut last_pos = it.text_offset();
264 loop {
265 let Some(c) = it.prev() else {
266 break;
267 };
268 if c.is_whitespace() {
269 break;
270 }
271 last_pos = c.text_bytes().start;
272 }
273
274 Ok(core.byte_pos(last_pos).expect("valid_pos"))
275}
276
277pub fn word_end<Store: TextStore + Default>(
280 core: &TextCore<Store>,
281 pos: TextPosition,
282) -> Result<TextPosition, TextError> {
283 let mut it = core.text_graphemes(pos)?;
284 let mut last_pos = it.text_offset();
285 loop {
286 let Some(c) = it.next() else {
287 break;
288 };
289 last_pos = c.text_bytes().start;
290 if c.is_whitespace() {
291 break;
292 }
293 last_pos = c.text_bytes().end;
294 }
295
296 Ok(core.byte_pos(last_pos).expect("valid_pos"))
297}