@@ -8,12 +8,11 @@ use crate::{
8
8
} ;
9
9
use anyhow:: Result ;
10
10
use crossterm:: event:: { Event , KeyCode , KeyModifiers } ;
11
- use std:: borrow:: Cow ;
12
11
use strings:: commands;
13
12
use tui:: {
14
13
backend:: Backend ,
15
14
layout:: Rect ,
16
- style:: Style ,
15
+ style:: { Modifier , Style } ,
17
16
widgets:: { Clear , Text } ,
18
17
Frame ,
19
18
} ;
@@ -25,6 +24,7 @@ pub struct TextInputComponent {
25
24
msg : String ,
26
25
visible : bool ,
27
26
theme : Theme ,
27
+ cursor_position : usize ,
28
28
}
29
29
30
30
impl TextInputComponent {
@@ -40,19 +40,56 @@ impl TextInputComponent {
40
40
theme : * theme,
41
41
title : title. to_string ( ) ,
42
42
default_msg : default_msg. to_string ( ) ,
43
+ cursor_position : 0 ,
43
44
}
44
45
}
45
46
46
- ///
47
+ /// Clear the `msg`.
47
48
pub fn clear ( & mut self ) {
48
49
self . msg . clear ( ) ;
49
50
}
50
51
51
- ///
52
+ /// Get the `msg`.
52
53
pub const fn get_text ( & self ) -> & String {
53
54
& self . msg
54
55
}
55
56
57
+ /// Move the cursor right one char.
58
+ fn incr_cursor ( & mut self ) {
59
+ if let Some ( pos) = self . next_char_position ( ) {
60
+ self . cursor_position = pos;
61
+ }
62
+ }
63
+
64
+ /// Move the cursor left one char.
65
+ fn decr_cursor ( & mut self ) {
66
+ let mut new_pos: usize = 0 ;
67
+ for ( bytes, _) in self . msg . char_indices ( ) {
68
+ if bytes >= self . cursor_position {
69
+ break ;
70
+ }
71
+ new_pos = bytes;
72
+ }
73
+ self . cursor_position = new_pos;
74
+ }
75
+
76
+ /// Get the position of the next char, or, if the cursor points
77
+ /// to the last char, the `msg.len()`.
78
+ /// Returns None when the cursor is already at `msg.len()`.
79
+ fn next_char_position ( & self ) -> Option < usize > {
80
+ let mut char_indices =
81
+ self . msg [ self . cursor_position ..] . char_indices ( ) ;
82
+ if char_indices. next ( ) . is_some ( ) {
83
+ if let Some ( ( bytes, _) ) = char_indices. next ( ) {
84
+ Some ( self . cursor_position + bytes)
85
+ } else {
86
+ Some ( self . msg . len ( ) )
87
+ }
88
+ } else {
89
+ None
90
+ }
91
+ }
92
+
56
93
///
57
94
pub fn set_text ( & mut self , msg : String ) {
58
95
self . msg = msg;
@@ -71,16 +108,43 @@ impl DrawableComponent for TextInputComponent {
71
108
_rect : Rect ,
72
109
) -> Result < ( ) > {
73
110
if self . visible {
74
- let txt = if self . msg . is_empty ( ) {
75
- [ Text :: Styled (
76
- Cow :: from ( self . default_msg . as_str ( ) ) ,
111
+ let mut txt: Vec < tui:: widgets:: Text > = Vec :: new ( ) ;
112
+ if self . msg . is_empty ( ) {
113
+ txt. push ( Text :: styled (
114
+ self . default_msg . as_str ( ) ,
77
115
self . theme . text ( false , false ) ,
78
- ) ]
116
+ ) ) ;
79
117
} else {
80
- [ Text :: Styled (
81
- Cow :: from ( self . msg . clone ( ) ) ,
82
- Style :: default ( ) ,
83
- ) ]
118
+ // the portion of the text before the cursor is added
119
+ // if the cursor is not at the first character
120
+ if self . cursor_position > 0 {
121
+ txt. push ( Text :: styled (
122
+ & self . msg [ ..self . cursor_position ] ,
123
+ Style :: default ( ) ,
124
+ ) ) ;
125
+ }
126
+
127
+ txt. push ( Text :: styled (
128
+ if let Some ( pos) = self . next_char_position ( ) {
129
+ & self . msg [ self . cursor_position ..pos]
130
+ } else {
131
+ // if the cursor is at the end of the msg
132
+ // a whitespace is used to underline
133
+ " "
134
+ } ,
135
+ Style :: default ( ) . modifier ( Modifier :: UNDERLINED ) ,
136
+ ) ) ;
137
+
138
+ // the final portion of the text is added if there is
139
+ // still remaining characters
140
+ if let Some ( pos) = self . next_char_position ( ) {
141
+ if pos < self . msg . len ( ) {
142
+ txt. push ( Text :: styled (
143
+ & self . msg [ pos..] ,
144
+ Style :: default ( ) ,
145
+ ) ) ;
146
+ }
147
+ }
84
148
} ;
85
149
86
150
let area = ui:: centered_rect ( 60 , 20 , f. size ( ) ) ;
@@ -128,11 +192,39 @@ impl Component for TextInputComponent {
128
192
return Ok ( true ) ;
129
193
}
130
194
KeyCode :: Char ( c) if !is_ctrl => {
131
- self . msg . push ( c) ;
195
+ self . msg . insert ( self . cursor_position , c) ;
196
+ self . incr_cursor ( ) ;
197
+ return Ok ( true ) ;
198
+ }
199
+ KeyCode :: Delete => {
200
+ if self . cursor_position < self . msg . len ( ) {
201
+ self . msg . remove ( self . cursor_position ) ;
202
+ }
132
203
return Ok ( true ) ;
133
204
}
134
205
KeyCode :: Backspace => {
135
- self . msg . pop ( ) ;
206
+ if self . cursor_position > 0 {
207
+ self . decr_cursor ( ) ;
208
+ if self . cursor_position < self . msg . len ( ) {
209
+ }
210
+ self . msg . remove ( self . cursor_position ) ;
211
+ }
212
+ return Ok ( true ) ;
213
+ }
214
+ KeyCode :: Left => {
215
+ self . decr_cursor ( ) ;
216
+ return Ok ( true ) ;
217
+ }
218
+ KeyCode :: Right => {
219
+ self . incr_cursor ( ) ;
220
+ return Ok ( true ) ;
221
+ }
222
+ KeyCode :: Home => {
223
+ self . cursor_position = 0 ;
224
+ return Ok ( true ) ;
225
+ }
226
+ KeyCode :: End => {
227
+ self . cursor_position = self . msg . len ( ) ;
136
228
return Ok ( true ) ;
137
229
}
138
230
_ => ( ) ,
0 commit comments