Skip to content

Commit c4d094d

Browse files
committed
Implement delete_char, take Unicode into account
1 parent 367d7aa commit c4d094d

1 file changed

Lines changed: 157 additions & 23 deletions

File tree

src/components/textinput.rs

Lines changed: 157 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -198,14 +198,19 @@ impl<'a> TextArea<'a> {
198198
CursorMove::Forward => {
199199
self.cursor = (
200200
current_row,
201-
(current_column + 1)
202-
.min(self.lines[current_row].len()),
201+
(current_column + 1).min(
202+
self.lines[current_row]
203+
.char_indices()
204+
.count(),
205+
),
203206
);
204207
}
205208
CursorMove::Home => self.cursor = (current_row, 0),
206209
CursorMove::End => {
207-
self.cursor =
208-
(current_row, self.lines[current_row].len());
210+
self.cursor = (
211+
current_row,
212+
self.lines[current_row].char_indices().count(),
213+
);
209214
}
210215
}
211216
}
@@ -227,22 +232,43 @@ impl<'a> TextArea<'a> {
227232
}
228233

229234
fn delete_next_char(&mut self) {
230-
todo!();
235+
let (current_row, current_column) = self.cursor;
236+
let current_line = &mut self.lines[current_row];
237+
238+
if current_column < current_line.len() {
239+
if let Some((offset, _)) =
240+
current_line.char_indices().nth(current_column)
241+
{
242+
current_line.remove(offset);
243+
}
244+
} else if current_row < self.lines.len().saturating_sub(1) {
245+
let next_line = self.lines[current_row + 1].clone();
246+
self.lines[current_row].push_str(&next_line);
247+
self.lines.remove(current_row + 1);
248+
} else {
249+
// We're at the end of the input. Do nothing.
250+
}
231251
}
232252

233253
fn delete_char(&mut self) {
234254
let (current_row, current_column) = self.cursor;
255+
let current_line = &mut self.lines[current_row];
235256

236257
if current_column > 0 {
237-
self.lines[current_row]
238-
.remove(current_column.saturating_sub(1));
239-
self.cursor = (current_row, current_column - 1);
258+
if let Some((offset, _)) =
259+
current_line.char_indices().nth(current_column - 1)
260+
{
261+
current_line.remove(offset);
262+
self.cursor = (current_row, current_column - 1);
263+
}
240264
} else if current_row > 0 {
241265
let current_line = self.lines[current_row].clone();
242266
self.lines[current_row - 1].push_str(&current_line);
243267
self.lines.remove(current_row);
244-
self.cursor =
245-
(current_row - 1, self.lines[current_row - 1].len());
268+
self.cursor = (
269+
current_row - 1,
270+
self.lines[current_row - 1].char_indices().count(),
271+
);
246272
} else {
247273
// We're at (0, 0), there's no characters to be deleted. Do nothing.
248274
}
@@ -254,8 +280,14 @@ impl<'a> TextArea<'a> {
254280

255281
fn insert_char(&mut self, char: char) {
256282
let (current_row, current_column) = self.cursor;
283+
let current_line = &mut self.lines[current_row];
284+
285+
let offset = current_line
286+
.char_indices()
287+
.nth(current_column)
288+
.map_or_else(|| current_line.len(), |(i, _)| i);
257289

258-
self.lines[current_row].insert(current_column, char);
290+
current_line.insert(offset, char);
259291
self.cursor = (current_row, current_column + 1);
260292
}
261293

@@ -321,26 +353,43 @@ impl<'a> TextAreaComponent {
321353
.skip(self.scroll.get_top())
322354
.map(|(row, line)| {
323355
if row == current_row {
324-
if current_column == line.len() {
325-
Line::from(vec![
356+
if current_column == line.char_indices().count() {
357+
return Line::from(vec![
326358
Span::from(line.clone()),
327359
Span::styled(" ", self.cursor_style),
328-
])
329-
} else {
360+
]);
361+
}
362+
363+
if let Some((offset, _)) =
364+
line.char_indices().nth(current_column)
365+
{
330366
let (before_cursor, cursor) =
331-
line.split_at(current_column);
332-
let (cursor, after_cursor) =
333-
cursor.split_at(1);
367+
line.split_at(offset);
368+
369+
if let Some((next_offset, _)) =
370+
cursor.char_indices().nth(1)
371+
{
372+
let (cursor, after_cursor) =
373+
cursor.split_at(next_offset);
374+
375+
return Line::from(vec![
376+
Span::from(before_cursor),
377+
Span::styled(
378+
cursor,
379+
self.cursor_style,
380+
),
381+
Span::from(after_cursor),
382+
]);
383+
}
334384

335-
Line::from(vec![
385+
return Line::from(vec![
336386
Span::from(before_cursor),
337387
Span::styled(cursor, self.cursor_style),
338-
Span::from(after_cursor),
339-
])
388+
]);
340389
}
341-
} else {
342-
Line::from(line.clone())
343390
}
391+
392+
Line::from(line.clone())
344393
})
345394
.collect();
346395
let paragraph = Paragraph::new(Text::from(lines));
@@ -1134,4 +1183,89 @@ mod tests {
11341183
assert_eq!(ta.cursor(), (1, 5));
11351184
}
11361185
}
1186+
1187+
#[test]
1188+
fn test_delete_char_unicode() {
1189+
let env = Environment::test_env();
1190+
let mut comp = TextInputComponent::new(&env, "", "", false);
1191+
comp.show_inner_textarea();
1192+
comp.set_text(String::from("äÜö"));
1193+
assert!(comp.is_visible());
1194+
1195+
if let Some(ta) = &mut comp.textarea {
1196+
ta.move_cursor(CursorMove::End);
1197+
assert_eq!(ta.cursor(), (0, 3));
1198+
1199+
ta.delete_char();
1200+
assert_eq!(ta.lines(), &["äÜ"]);
1201+
assert_eq!(ta.cursor(), (0, 2));
1202+
}
1203+
}
1204+
1205+
#[test]
1206+
fn test_delete_next_char() {
1207+
let env = Environment::test_env();
1208+
let mut comp = TextInputComponent::new(&env, "", "", false);
1209+
comp.show_inner_textarea();
1210+
comp.set_text(String::from("aa\ndef sa\ngitui"));
1211+
assert!(comp.is_visible());
1212+
1213+
if let Some(ta) = &mut comp.textarea {
1214+
assert_eq!(ta.cursor(), (0, 0));
1215+
1216+
ta.delete_next_char();
1217+
assert_eq!(ta.lines(), &["a", "def sa", "gitui"]);
1218+
assert_eq!(ta.cursor(), (0, 0));
1219+
1220+
ta.delete_next_char();
1221+
assert_eq!(ta.lines(), &["", "def sa", "gitui"]);
1222+
assert_eq!(ta.cursor(), (0, 0));
1223+
1224+
ta.delete_next_char();
1225+
assert_eq!(ta.lines(), &["def sa", "gitui"]);
1226+
assert_eq!(ta.cursor(), (0, 0));
1227+
1228+
ta.move_cursor(CursorMove::Down);
1229+
assert_eq!(ta.cursor(), (1, 0));
1230+
1231+
ta.delete_next_char();
1232+
assert_eq!(ta.lines(), &["def sa", "itui"]);
1233+
assert_eq!(ta.cursor(), (1, 0));
1234+
}
1235+
}
1236+
1237+
#[test]
1238+
fn test_delete_next_char_empty() {
1239+
let env = Environment::test_env();
1240+
let mut comp = TextInputComponent::new(&env, "", "", false);
1241+
comp.show_inner_textarea();
1242+
comp.set_text("".into());
1243+
assert!(comp.is_visible());
1244+
1245+
if let Some(ta) = &mut comp.textarea {
1246+
assert_eq!(ta.cursor(), (0, 0));
1247+
1248+
ta.delete_next_char();
1249+
assert_eq!(ta.lines(), &[""]);
1250+
assert_eq!(ta.cursor(), (0, 0));
1251+
}
1252+
}
1253+
1254+
#[test]
1255+
fn test_delete_next_char_unicode() {
1256+
let env = Environment::test_env();
1257+
let mut comp = TextInputComponent::new(&env, "", "", false);
1258+
comp.show_inner_textarea();
1259+
comp.set_text("üäu".into());
1260+
assert!(comp.is_visible());
1261+
1262+
if let Some(ta) = &mut comp.textarea {
1263+
ta.move_cursor(CursorMove::Forward);
1264+
assert_eq!(ta.cursor(), (0, 1));
1265+
1266+
ta.delete_next_char();
1267+
assert_eq!(ta.lines(), &["üu"]);
1268+
assert_eq!(ta.cursor(), (0, 1));
1269+
}
1270+
}
11371271
}

0 commit comments

Comments
 (0)