1mod value;
41
42use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
43use unicode_width::UnicodeWidthStr;
44
45use self::value::Value;
46
47fn prev_grapheme(s: &str, byte: usize) -> Option<usize> {
48 GraphemeCursor::new(byte, s.len(), true)
49 .prev_boundary(s, 0)
50 .ok()
51 .flatten()
52}
53
54fn next_grapheme(s: &str, byte: usize) -> Option<usize> {
55 GraphemeCursor::new(byte, s.len(), true)
56 .next_boundary(s, 0)
57 .ok()
58 .flatten()
59}
60
61fn is_word(s: &str) -> bool {
62 s.chars()
63 .any(|c| !c.is_whitespace() && !c.is_ascii_punctuation())
64}
65
66fn prev_word_byte(s: &str, byte: usize) -> usize {
67 let words = s
68 .split_word_bound_indices()
69 .filter(|(i, _)| *i < byte)
70 .rev();
71 for (i, word) in words {
72 if is_word(word) {
73 return i;
74 }
75 }
76 0
77}
78
79fn next_word_byte(s: &str, byte: usize) -> usize {
80 let words = s.split_word_bound_indices().filter(|(i, _)| *i > byte);
81 for (i, word) in words {
82 if is_word(word) {
83 return i;
84 }
85 }
86 s.len()
87}
88
89pub(crate) fn widths(s: &str) -> impl Iterator<Item = usize> + '_ {
92 s.graphemes(true).map(UnicodeWidthStr::width)
93}
94
95pub(crate) fn codepoint_to_byte(s: &str, n: usize) -> usize {
96 s.char_indices().nth(n).map_or(s.len(), |(i, _)| i)
97}
98
99fn byte_to_codepoint(s: &str, byte: usize) -> usize {
100 s[..byte].chars().count()
101}
102
103enum Side {
104 Left,
105 Right,
106}
107
108#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113pub enum InputRequest {
114 SetCursor(usize),
115 InsertChar(char),
116 GoToPrevChar,
117 GoToNextChar,
118 GoToPrevWord,
119 GoToNextWord,
120 GoToStart,
121 GoToEnd,
122 DeletePrevChar,
123 DeleteNextChar,
124 DeletePrevWord,
125 DeleteNextWord,
126 DeleteLine,
127 DeleteTillEnd,
128 DeleteFromStart,
129 Yank,
130}
131
132#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
134pub struct StateChanged {
135 pub value: bool,
136 pub cursor: bool,
137}
138
139pub type InputResponse = Option<StateChanged>;
140
141#[derive(Default, Debug, Clone)]
154#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
155pub struct Input {
156 value: Value,
157 cursor: usize,
159 yank: Value,
160 last_was_cut: bool,
161}
162
163impl Input {
164 pub fn new(value: String) -> Self {
167 let value = Value::new(value);
168 let cursor = value.chars();
169 Self {
170 value,
171 cursor,
172 yank: Value::default(),
173 last_was_cut: false,
174 }
175 }
176
177 pub fn with_value(mut self, value: String) -> Self {
180 self.value = Value::new(value);
181 self.cursor = self.value.chars();
182 self
183 }
184
185 pub fn with_cursor(mut self, cursor: usize) -> Self {
188 self.cursor = cursor.min(self.value.chars());
189 self
190 }
191
192 pub fn reset(&mut self) {
194 self.cursor = Default::default();
195 self.value = Default::default();
196 }
197
198 pub fn value_and_reset(&mut self) -> String {
200 let val = self.value.as_str().to_owned();
201 self.reset();
202 val
203 }
204
205 fn add_to_yank(&mut self, deleted: String, side: Side) {
206 if self.last_was_cut {
207 match side {
208 Side::Left => self.yank.edit().insert_str(0, &deleted),
209 Side::Right => self.yank.edit().push_str(&deleted),
210 }
211 } else {
212 self.yank = Value::new(deleted);
213 }
214 }
215
216 fn set_last_was_cut(&mut self, req: InputRequest) {
217 use InputRequest::*;
218 self.last_was_cut = matches!(
219 req,
220 DeleteLine | DeletePrevWord | DeleteNextWord | DeleteTillEnd
221 );
222 }
223
224 pub fn handle(&mut self, req: InputRequest) -> InputResponse {
226 use InputRequest::*;
227 let result = match req {
228 SetCursor(pos) => {
229 let pos = pos.min(self.value.chars());
230 if self.cursor == pos {
231 None
232 } else {
233 self.cursor = pos;
234 Some(StateChanged {
235 value: false,
236 cursor: true,
237 })
238 }
239 }
240 InsertChar(c) => {
241 let byte = codepoint_to_byte(self.value.as_str(), self.cursor);
242 self.value.edit().insert(byte, c);
243 self.cursor += 1;
244 Some(StateChanged {
245 value: true,
246 cursor: true,
247 })
248 }
249
250 DeletePrevChar => {
251 let s = self.value.as_str();
252 let byte = codepoint_to_byte(s, self.cursor);
253 let prev = prev_grapheme(s, byte)?;
254 let removed = s[prev..byte].chars().count();
255 self.value.edit().replace_range(prev..byte, "");
256 self.cursor -= removed;
257 Some(StateChanged {
258 value: true,
259 cursor: true,
260 })
261 }
262
263 DeleteNextChar => {
264 let s = self.value.as_str();
265 let byte = codepoint_to_byte(s, self.cursor);
266 let next = next_grapheme(s, byte)?;
267 self.value.edit().replace_range(byte..next, "");
268 Some(StateChanged {
269 value: true,
270 cursor: false,
271 })
272 }
273
274 GoToPrevChar => {
275 let s = self.value.as_str();
276 let byte = codepoint_to_byte(s, self.cursor);
277 let prev = prev_grapheme(s, byte)?;
278 self.cursor -= s[prev..byte].chars().count();
279 Some(StateChanged {
280 value: false,
281 cursor: true,
282 })
283 }
284
285 GoToPrevWord => {
286 let s = self.value.as_str();
287 let byte = codepoint_to_byte(s, self.cursor);
288 let prev = prev_word_byte(s, byte);
289 if self.cursor == 0 {
290 None
291 } else {
292 self.cursor = byte_to_codepoint(s, prev);
293 Some(StateChanged {
294 value: false,
295 cursor: true,
296 })
297 }
298 }
299
300 GoToNextChar => {
301 let s = self.value.as_str();
302 let byte = codepoint_to_byte(s, self.cursor);
303 let next = next_grapheme(s, byte)?;
304 self.cursor += s[byte..next].chars().count();
305 Some(StateChanged {
306 value: false,
307 cursor: true,
308 })
309 }
310
311 GoToNextWord => {
312 let s = self.value.as_str();
313 let byte = codepoint_to_byte(s, self.cursor);
314 let next = next_word_byte(s, byte);
315 if self.cursor == self.value.chars() {
316 None
317 } else {
318 self.cursor = byte_to_codepoint(s, next);
319 Some(StateChanged {
320 value: false,
321 cursor: true,
322 })
323 }
324 }
325
326 DeleteLine => {
327 if self.value.as_str().is_empty() {
328 None
329 } else {
330 let side = if self.cursor == self.value.chars() {
331 Side::Left
332 } else {
333 Side::Right
334 };
335 self.add_to_yank(self.value.as_str().to_owned(), side);
336 self.value.edit().clear();
337 self.cursor = 0;
338 Some(StateChanged {
339 value: true,
340 cursor: true,
341 })
342 }
343 }
344
345 DeletePrevWord => {
346 if self.cursor == 0 {
347 None
348 } else {
349 let s = self.value.as_str();
350 let byte = codepoint_to_byte(s, self.cursor);
351 let prev = prev_word_byte(s, byte);
352 let deleted = s[prev..byte].to_string();
353 self.add_to_yank(deleted, Side::Left);
354 self.value.edit().replace_range(prev..byte, "");
355 self.cursor = byte_to_codepoint(self.value.as_str(), prev);
356 Some(StateChanged {
357 value: true,
358 cursor: true,
359 })
360 }
361 }
362
363 DeleteNextWord => {
364 let s = self.value.as_str();
365 let byte = codepoint_to_byte(s, self.cursor);
366 let next = next_word_byte(s, byte);
367 if self.cursor == self.value.chars() {
368 None
369 } else {
370 let deleted = s[byte..next].to_string();
371 self.add_to_yank(deleted, Side::Right);
372 self.value.edit().replace_range(byte..next, "");
373 Some(StateChanged {
374 value: true,
375 cursor: false,
376 })
377 }
378 }
379
380 GoToStart => {
381 if self.cursor == 0 {
382 None
383 } else {
384 self.cursor = 0;
385 Some(StateChanged {
386 value: false,
387 cursor: true,
388 })
389 }
390 }
391
392 GoToEnd => {
393 let count = self.value.chars();
394 if self.cursor == count {
395 None
396 } else {
397 self.cursor = count;
398 Some(StateChanged {
399 value: false,
400 cursor: true,
401 })
402 }
403 }
404
405 DeleteTillEnd => {
406 let byte = codepoint_to_byte(self.value.as_str(), self.cursor);
407 let deleted = self.value.as_str()[byte..].to_string();
408 self.add_to_yank(deleted, Side::Right);
409 self.value.edit().truncate(byte);
410 Some(StateChanged {
411 value: true,
412 cursor: false,
413 })
414 }
415
416 DeleteFromStart => {
417 if self.cursor == 0 {
418 None
419 } else {
420 let byte = codepoint_to_byte(self.value.as_str(), self.cursor);
421 let deleted = self.value.edit().drain(..byte).collect();
422 self.add_to_yank(deleted, Side::Left);
423 self.cursor = 0;
424 Some(StateChanged {
425 value: true,
426 cursor: true,
427 })
428 }
429 }
430
431 Yank => {
432 if self.yank.as_str().is_empty() {
433 None
434 } else {
435 let byte = codepoint_to_byte(self.value.as_str(), self.cursor);
436 self.value.edit().insert_str(byte, self.yank.as_str());
437 self.cursor += self.yank.chars();
438 Some(StateChanged {
439 value: true,
440 cursor: true,
441 })
442 }
443 }
444 };
445 self.set_last_was_cut(req);
446 result
447 }
448
449 pub fn value(&self) -> &str {
451 self.value.as_str()
452 }
453
454 pub fn cursor(&self) -> usize {
459 self.cursor
460 }
461
462 pub fn visual_cursor(&self) -> usize {
465 let s = self.value.as_str();
466 widths(&s[..codepoint_to_byte(s, self.cursor)]).sum()
467 }
468
469 pub fn visual_scroll(&self, width: usize) -> usize {
471 let scroll = (self.visual_cursor()).max(width) - width;
472 let mut uscroll = 0;
473 let mut widths = widths(self.value());
474
475 while uscroll < scroll {
476 match widths.next() {
477 Some(w) => uscroll += w,
478 None => break,
479 }
480 }
481 uscroll
482 }
483}
484
485impl From<Input> for String {
486 fn from(input: Input) -> Self {
487 input.value.into()
488 }
489}
490
491impl From<String> for Input {
492 fn from(value: String) -> Self {
493 Self::new(value)
494 }
495}
496
497impl From<&str> for Input {
498 fn from(value: &str) -> Self {
499 Self::new(value.into())
500 }
501}
502
503impl std::fmt::Display for Input {
504 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
505 self.value.as_str().fmt(f)
506 }
507}
508
509#[cfg(test)]
510mod tests;