1#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub enum InputRequest {
24 SetCursor(usize),
25 InsertChar(char),
26 GoToPrevChar,
27 GoToNextChar,
28 GoToPrevWord,
29 GoToNextWord,
30 GoToStart,
31 GoToEnd,
32 DeletePrevChar,
33 DeleteNextChar,
34 DeletePrevWord,
35 DeleteNextWord,
36 DeleteLine,
37 DeleteTillEnd,
38}
39
40#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct StateChanged {
43 pub value: bool,
44 pub cursor: bool,
45}
46
47pub type InputResponse = Option<StateChanged>;
48
49#[derive(Default, Debug, Clone)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
63pub struct Input {
64 value: String,
65 cursor: usize,
66}
67
68impl Input {
69 pub fn new(value: String) -> Self {
72 let len = value.chars().count();
73 Self { value, cursor: len }
74 }
75
76 pub fn with_value(mut self, value: String) -> Self {
79 self.cursor = value.chars().count();
80 self.value = value;
81 self
82 }
83
84 pub fn with_cursor(mut self, cursor: usize) -> Self {
87 self.cursor = cursor.min(self.value.chars().count());
88 self
89 }
90
91 pub fn reset(&mut self) {
93 self.cursor = Default::default();
94 self.value = Default::default();
95 }
96
97 pub fn value_and_reset(&mut self) -> String {
99 let val = self.value.clone();
100 self.reset();
101 val
102 }
103
104 pub fn handle(&mut self, req: InputRequest) -> InputResponse {
106 use InputRequest::*;
107 match req {
108 SetCursor(pos) => {
109 let pos = pos.min(self.value.chars().count());
110 if self.cursor == pos {
111 None
112 } else {
113 self.cursor = pos;
114 Some(StateChanged {
115 value: false,
116 cursor: true,
117 })
118 }
119 }
120 InsertChar(c) => {
121 if self.cursor == self.value.chars().count() {
122 self.value.push(c);
123 } else {
124 self.value = self
125 .value
126 .chars()
127 .take(self.cursor)
128 .chain(
129 std::iter::once(c)
130 .chain(self.value.chars().skip(self.cursor)),
131 )
132 .collect();
133 }
134 self.cursor += 1;
135 Some(StateChanged {
136 value: true,
137 cursor: true,
138 })
139 }
140
141 DeletePrevChar => {
142 if self.cursor == 0 {
143 None
144 } else {
145 self.cursor -= 1;
146 self.value = self
147 .value
148 .chars()
149 .enumerate()
150 .filter(|(i, _)| i != &self.cursor)
151 .map(|(_, c)| c)
152 .collect();
153
154 Some(StateChanged {
155 value: true,
156 cursor: true,
157 })
158 }
159 }
160
161 DeleteNextChar => {
162 if self.cursor == self.value.chars().count() {
163 None
164 } else {
165 self.value = self
166 .value
167 .chars()
168 .enumerate()
169 .filter(|(i, _)| i != &self.cursor)
170 .map(|(_, c)| c)
171 .collect();
172 Some(StateChanged {
173 value: true,
174 cursor: false,
175 })
176 }
177 }
178
179 GoToPrevChar => {
180 if self.cursor == 0 {
181 None
182 } else {
183 self.cursor -= 1;
184 Some(StateChanged {
185 value: false,
186 cursor: true,
187 })
188 }
189 }
190
191 GoToPrevWord => {
192 if self.cursor == 0 {
193 None
194 } else {
195 self.cursor = self
196 .value
197 .chars()
198 .rev()
199 .skip(self.value.chars().count().max(self.cursor) - self.cursor)
200 .skip_while(|c| !c.is_alphanumeric())
201 .skip_while(|c| c.is_alphanumeric())
202 .count();
203 Some(StateChanged {
204 value: false,
205 cursor: true,
206 })
207 }
208 }
209
210 GoToNextChar => {
211 if self.cursor == self.value.chars().count() {
212 None
213 } else {
214 self.cursor += 1;
215 Some(StateChanged {
216 value: false,
217 cursor: true,
218 })
219 }
220 }
221
222 GoToNextWord => {
223 if self.cursor == self.value.chars().count() {
224 None
225 } else {
226 self.cursor = self
227 .value
228 .chars()
229 .enumerate()
230 .skip(self.cursor)
231 .skip_while(|(_, c)| c.is_alphanumeric())
232 .find(|(_, c)| c.is_alphanumeric())
233 .map(|(i, _)| i)
234 .unwrap_or_else(|| self.value.chars().count());
235
236 Some(StateChanged {
237 value: false,
238 cursor: true,
239 })
240 }
241 }
242
243 DeleteLine => {
244 if self.value.is_empty() {
245 None
246 } else {
247 let cursor = self.cursor;
248 self.value = "".into();
249 self.cursor = 0;
250 Some(StateChanged {
251 value: true,
252 cursor: self.cursor == cursor,
253 })
254 }
255 }
256
257 DeletePrevWord => {
258 if self.cursor == 0 {
259 None
260 } else {
261 let remaining = self.value.chars().skip(self.cursor);
262 let rev = self
263 .value
264 .chars()
265 .rev()
266 .skip(self.value.chars().count().max(self.cursor) - self.cursor)
267 .skip_while(|c| !c.is_alphanumeric())
268 .skip_while(|c| c.is_alphanumeric())
269 .collect::<Vec<char>>();
270 let rev_len = rev.len();
271 self.value = rev.into_iter().rev().chain(remaining).collect();
272 self.cursor = rev_len;
273 Some(StateChanged {
274 value: true,
275 cursor: true,
276 })
277 }
278 }
279
280 DeleteNextWord => {
281 if self.cursor == self.value.chars().count() {
282 None
283 } else {
284 self.value = self
285 .value
286 .chars()
287 .take(self.cursor)
288 .chain(
289 self.value
290 .chars()
291 .skip(self.cursor)
292 .skip_while(|c| c.is_alphanumeric())
293 .skip_while(|c| !c.is_alphanumeric()),
294 )
295 .collect();
296
297 Some(StateChanged {
298 value: true,
299 cursor: false,
300 })
301 }
302 }
303
304 GoToStart => {
305 if self.cursor == 0 {
306 None
307 } else {
308 self.cursor = 0;
309 Some(StateChanged {
310 value: false,
311 cursor: true,
312 })
313 }
314 }
315
316 GoToEnd => {
317 let count = self.value.chars().count();
318 if self.cursor == count {
319 None
320 } else {
321 self.cursor = count;
322 Some(StateChanged {
323 value: false,
324 cursor: true,
325 })
326 }
327 }
328
329 DeleteTillEnd => {
330 self.value = self.value.chars().take(self.cursor).collect();
331 Some(StateChanged {
332 value: true,
333 cursor: false,
334 })
335 }
336 }
337 }
338
339 pub fn value(&self) -> &str {
341 self.value.as_str()
342 }
343
344 pub fn cursor(&self) -> usize {
346 self.cursor
347 }
348
349 pub fn visual_cursor(&self) -> usize {
351 if self.cursor == 0 {
352 return 0;
353 }
354
355 unicode_width::UnicodeWidthStr::width(unsafe {
357 self.value.get_unchecked(
358 0..self
359 .value
360 .char_indices()
361 .nth(self.cursor)
362 .map_or_else(|| self.value.len(), |(index, _)| index),
363 )
364 })
365 }
366
367 pub fn visual_scroll(&self, width: usize) -> usize {
369 let scroll = (self.visual_cursor()).max(width) - width;
370 let mut uscroll = 0;
371 let mut chars = self.value().chars();
372
373 while uscroll < scroll {
374 match chars.next() {
375 Some(c) => {
376 uscroll += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
377 }
378 None => break,
379 }
380 }
381 uscroll
382 }
383}
384
385impl From<Input> for String {
386 fn from(input: Input) -> Self {
387 input.value
388 }
389}
390
391impl From<String> for Input {
392 fn from(value: String) -> Self {
393 Self::new(value)
394 }
395}
396
397impl From<&str> for Input {
398 fn from(value: &str) -> Self {
399 Self::new(value.into())
400 }
401}
402
403impl std::fmt::Display for Input {
404 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405 self.value.fmt(f)
406 }
407}
408
409#[cfg(test)]
410mod tests {
411
412 const TEXT: &str = "first second, third.";
413
414 use super::*;
415
416 #[test]
417 fn format() {
418 let input: Input = TEXT.into();
419 println!("{}", input);
420 println!("{}", input);
421 }
422
423 #[test]
424 fn set_cursor() {
425 let mut input: Input = TEXT.into();
426
427 let req = InputRequest::SetCursor(3);
428 let resp = input.handle(req);
429
430 assert_eq!(
431 resp,
432 Some(StateChanged {
433 value: false,
434 cursor: true,
435 })
436 );
437
438 assert_eq!(input.value(), "first second, third.");
439 assert_eq!(input.cursor(), 3);
440
441 let req = InputRequest::SetCursor(30);
442 let resp = input.handle(req);
443
444 assert_eq!(input.cursor(), TEXT.chars().count());
445 assert_eq!(
446 resp,
447 Some(StateChanged {
448 value: false,
449 cursor: true,
450 })
451 );
452
453 let req = InputRequest::SetCursor(TEXT.chars().count());
454 let resp = input.handle(req);
455
456 assert_eq!(input.cursor(), TEXT.chars().count());
457 assert_eq!(resp, None);
458 }
459
460 #[test]
461 fn insert_char() {
462 let mut input: Input = TEXT.into();
463
464 let req = InputRequest::InsertChar('x');
465 let resp = input.handle(req);
466
467 assert_eq!(
468 resp,
469 Some(StateChanged {
470 value: true,
471 cursor: true,
472 })
473 );
474
475 assert_eq!(input.value(), "first second, third.x");
476 assert_eq!(input.cursor(), TEXT.chars().count() + 1);
477 input.handle(req);
478 assert_eq!(input.value(), "first second, third.xx");
479 assert_eq!(input.cursor(), TEXT.chars().count() + 2);
480
481 let mut input = input.with_cursor(3);
482 input.handle(req);
483 assert_eq!(input.value(), "firxst second, third.xx");
484 assert_eq!(input.cursor(), 4);
485
486 input.handle(req);
487 assert_eq!(input.value(), "firxxst second, third.xx");
488 assert_eq!(input.cursor(), 5);
489 }
490
491 #[test]
492 fn go_to_prev_char() {
493 let mut input: Input = TEXT.into();
494
495 let req = InputRequest::GoToPrevChar;
496 let resp = input.handle(req);
497
498 assert_eq!(
499 resp,
500 Some(StateChanged {
501 value: false,
502 cursor: true,
503 })
504 );
505
506 assert_eq!(input.value(), "first second, third.");
507 assert_eq!(input.cursor(), TEXT.chars().count() - 1);
508
509 let mut input = input.with_cursor(3);
510 input.handle(req);
511 assert_eq!(input.value(), "first second, third.");
512 assert_eq!(input.cursor(), 2);
513
514 input.handle(req);
515 assert_eq!(input.value(), "first second, third.");
516 assert_eq!(input.cursor(), 1);
517 }
518
519 #[test]
520 fn remove_unicode_chars() {
521 let mut input: Input = "¡test¡".into();
522
523 let req = InputRequest::DeletePrevChar;
524 let resp = input.handle(req);
525
526 assert_eq!(
527 resp,
528 Some(StateChanged {
529 value: true,
530 cursor: true,
531 })
532 );
533
534 assert_eq!(input.value(), "¡test");
535 assert_eq!(input.cursor(), 5);
536
537 input.handle(InputRequest::GoToStart);
538
539 let req = InputRequest::DeleteNextChar;
540 let resp = input.handle(req);
541
542 assert_eq!(
543 resp,
544 Some(StateChanged {
545 value: true,
546 cursor: false,
547 })
548 );
549
550 assert_eq!(input.value(), "test");
551 assert_eq!(input.cursor(), 0);
552 }
553
554 #[test]
555 fn insert_unicode_chars() {
556 let mut input = Input::from("¡test¡").with_cursor(5);
557
558 let req = InputRequest::InsertChar('☆');
559 let resp = input.handle(req);
560
561 assert_eq!(
562 resp,
563 Some(StateChanged {
564 value: true,
565 cursor: true,
566 })
567 );
568
569 assert_eq!(input.value(), "¡test☆¡");
570 assert_eq!(input.cursor(), 6);
571
572 input.handle(InputRequest::GoToStart);
573 input.handle(InputRequest::GoToNextChar);
574
575 let req = InputRequest::InsertChar('☆');
576 let resp = input.handle(req);
577
578 assert_eq!(
579 resp,
580 Some(StateChanged {
581 value: true,
582 cursor: true,
583 })
584 );
585
586 assert_eq!(input.value(), "¡☆test☆¡");
587 assert_eq!(input.cursor(), 2);
588 }
589
590 #[test]
591 fn multispace_characters() {
592 let input: Input = "Hello, world!".into();
593 assert_eq!(input.cursor(), 13);
594 assert_eq!(input.visual_cursor(), 23);
595 assert_eq!(input.visual_scroll(6), 18);
596 }
597}