@@ -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 ,
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,25 +40,58 @@ 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
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 index = self . cursor_position . saturating_sub ( 1 ) ;
67
+ while index > 0 && !self . msg . is_char_boundary ( index) {
68
+ index -= 1 ;
69
+ }
70
+ self . cursor_position = index;
71
+ }
72
+
73
+ /// Get the position of the next char, or, if the cursor points
74
+ /// to the last char, the `msg.len()`.
75
+ /// Returns None when the cursor is already at `msg.len()`.
76
+ fn next_char_position ( & self ) -> Option < usize > {
77
+ if self . cursor_position >= self . msg . len ( ) {
78
+ return None ;
79
+ }
80
+ let mut index = self . cursor_position . saturating_add ( 1 ) ;
81
+ while index < self . msg . len ( )
82
+ && !self . msg . is_char_boundary ( index)
83
+ {
84
+ index += 1 ;
85
+ }
86
+ Some ( index)
87
+ }
88
+
89
+ /// Set the `msg`.
57
90
pub fn set_text ( & mut self , msg : String ) {
58
91
self . msg = msg;
59
92
}
60
93
61
- ///
94
+ /// Set the `title`.
62
95
pub fn set_title ( & mut self , t : String ) {
63
96
self . title = t;
64
97
}
@@ -71,16 +104,46 @@ impl DrawableComponent for TextInputComponent {
71
104
_rect : Rect ,
72
105
) -> Result < ( ) > {
73
106
if self . visible {
74
- let txt = if self . msg . is_empty ( ) {
75
- [ Text :: Styled (
76
- Cow :: from ( self . default_msg . as_str ( ) ) ,
107
+ let mut txt: Vec < tui:: widgets:: Text > = Vec :: new ( ) ;
108
+
109
+ if self . msg . is_empty ( ) {
110
+ txt. push ( Text :: styled (
111
+ self . default_msg . as_str ( ) ,
77
112
self . theme . text ( false , false ) ,
78
- ) ]
113
+ ) ) ;
79
114
} else {
80
- [ Text :: Styled (
81
- Cow :: from ( self . msg . clone ( ) ) ,
82
- Style :: default ( ) ,
83
- ) ]
115
+ let style = self . theme . text ( true , false ) ;
116
+
117
+ // the portion of the text before the cursor is added
118
+ // if the cursor is not at the first character
119
+ if self . cursor_position > 0 {
120
+ txt. push ( Text :: styled (
121
+ & self . msg [ ..self . cursor_position ] ,
122
+ style,
123
+ ) ) ;
124
+ }
125
+
126
+ txt. push ( Text :: styled (
127
+ if let Some ( pos) = self . next_char_position ( ) {
128
+ & self . msg [ self . cursor_position ..pos]
129
+ } else {
130
+ // if the cursor is at the end of the msg
131
+ // a whitespace is used to underline
132
+ " "
133
+ } ,
134
+ style. modifier ( Modifier :: UNDERLINED ) ,
135
+ ) ) ;
136
+
137
+ // the final portion of the text is added if there is
138
+ // still remaining characters
139
+ if let Some ( pos) = self . next_char_position ( ) {
140
+ if pos < self . msg . len ( ) {
141
+ txt. push ( Text :: styled (
142
+ & self . msg [ pos..] ,
143
+ style,
144
+ ) ) ;
145
+ }
146
+ }
84
147
} ;
85
148
86
149
let area = ui:: centered_rect ( 60 , 20 , f. size ( ) ) ;
@@ -128,11 +191,39 @@ impl Component for TextInputComponent {
128
191
return Ok ( true ) ;
129
192
}
130
193
KeyCode :: Char ( c) if !is_ctrl => {
131
- self . msg . push ( c) ;
194
+ self . msg . insert ( self . cursor_position , c) ;
195
+ self . incr_cursor ( ) ;
196
+ return Ok ( true ) ;
197
+ }
198
+ KeyCode :: Delete => {
199
+ if self . cursor_position < self . msg . len ( ) {
200
+ self . msg . remove ( self . cursor_position ) ;
201
+ }
132
202
return Ok ( true ) ;
133
203
}
134
204
KeyCode :: Backspace => {
135
- self . msg . pop ( ) ;
205
+ if self . cursor_position > 0 {
206
+ self . decr_cursor ( ) ;
207
+ if self . cursor_position < self . msg . len ( ) {
208
+ }
209
+ self . msg . remove ( self . cursor_position ) ;
210
+ }
211
+ return Ok ( true ) ;
212
+ }
213
+ KeyCode :: Left => {
214
+ self . decr_cursor ( ) ;
215
+ return Ok ( true ) ;
216
+ }
217
+ KeyCode :: Right => {
218
+ self . incr_cursor ( ) ;
219
+ return Ok ( true ) ;
220
+ }
221
+ KeyCode :: Home => {
222
+ self . cursor_position = 0 ;
223
+ return Ok ( true ) ;
224
+ }
225
+ KeyCode :: End => {
226
+ self . cursor_position = self . msg . len ( ) ;
136
227
return Ok ( true ) ;
137
228
}
138
229
_ => ( ) ,
0 commit comments