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