Skip to content

Commit cc7f207

Browse files
author
Bo Maryniuk
committed
Fix caret in entry text
1 parent 446386f commit cc7f207

1 file changed

Lines changed: 105 additions & 21 deletions

File tree

ljx/src/commands/view.rs

Lines changed: 105 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,12 @@ struct ViewApp {
222222
selected_detail: Option<DetailRecord>,
223223
modal_text: Option<String>,
224224
save_filename: String,
225+
save_filename_cursor: usize,
225226
save_message: Option<String>,
226227
export_filename: String,
228+
export_filename_cursor: usize,
227229
export_range: String,
230+
export_range_cursor: usize,
228231
export_field: ExportField,
229232
export_message: Option<String>,
230233
current_scan: Option<ActiveScan>,
@@ -272,9 +275,12 @@ impl ViewApp {
272275
selected_detail: None,
273276
modal_text: None,
274277
save_filename: String::new(),
278+
save_filename_cursor: 0,
275279
save_message: None,
276280
export_filename: String::new(),
281+
export_filename_cursor: 0,
277282
export_range: "all".to_string(),
283+
export_range_cursor: 3,
278284
export_field: ExportField::Filename,
279285
export_message: None,
280286
current_scan: None,
@@ -407,13 +413,29 @@ impl ViewApp {
407413
self.save_current_results()?;
408414
}
409415
KeyCode::Backspace => {
410-
self.save_filename.pop();
416+
delete_char_before(&mut self.save_filename, &mut self.save_filename_cursor);
417+
}
418+
KeyCode::Delete => {
419+
delete_char_at(&mut self.save_filename, self.save_filename_cursor);
420+
}
421+
KeyCode::Left => {
422+
self.save_filename_cursor = self.save_filename_cursor.saturating_sub(1);
423+
}
424+
KeyCode::Right => {
425+
self.save_filename_cursor = (self.save_filename_cursor + 1).min(char_count(&self.save_filename));
426+
}
427+
KeyCode::Home => {
428+
self.save_filename_cursor = 0;
429+
}
430+
KeyCode::End => {
431+
self.save_filename_cursor = char_count(&self.save_filename);
411432
}
412433
KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => {
413434
self.save_filename.clear();
435+
self.save_filename_cursor = 0;
414436
}
415437
KeyCode::Char(ch) if !key.modifiers.intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) => {
416-
self.save_filename.push(ch);
438+
insert_char_at(&mut self.save_filename, &mut self.save_filename_cursor, ch);
417439
}
418440
_ => {}
419441
}
@@ -442,20 +464,42 @@ impl ViewApp {
442464
};
443465
}
444466
KeyCode::Backspace => match self.export_field {
467+
ExportField::Filename => delete_char_before(&mut self.export_filename, &mut self.export_filename_cursor),
468+
ExportField::Range => delete_char_before(&mut self.export_range, &mut self.export_range_cursor),
469+
},
470+
KeyCode::Delete => match self.export_field {
471+
ExportField::Filename => delete_char_at(&mut self.export_filename, self.export_filename_cursor),
472+
ExportField::Range => delete_char_at(&mut self.export_range, self.export_range_cursor),
473+
},
474+
KeyCode::Left => match self.export_field {
475+
ExportField::Filename => self.export_filename_cursor = self.export_filename_cursor.saturating_sub(1),
476+
ExportField::Range => self.export_range_cursor = self.export_range_cursor.saturating_sub(1),
477+
},
478+
KeyCode::Right => match self.export_field {
479+
ExportField::Filename => self.export_filename_cursor = (self.export_filename_cursor + 1).min(char_count(&self.export_filename)),
480+
ExportField::Range => self.export_range_cursor = (self.export_range_cursor + 1).min(char_count(&self.export_range)),
481+
},
482+
KeyCode::Home => match self.export_field {
483+
ExportField::Filename => self.export_filename_cursor = 0,
484+
ExportField::Range => self.export_range_cursor = 0,
485+
},
486+
KeyCode::End => match self.export_field {
487+
ExportField::Filename => self.export_filename_cursor = char_count(&self.export_filename),
488+
ExportField::Range => self.export_range_cursor = char_count(&self.export_range),
489+
},
490+
KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => match self.export_field {
445491
ExportField::Filename => {
446-
self.export_filename.pop();
492+
self.export_filename.clear();
493+
self.export_filename_cursor = 0;
447494
}
448495
ExportField::Range => {
449-
self.export_range.pop();
496+
self.export_range.clear();
497+
self.export_range_cursor = 0;
450498
}
451499
},
452-
KeyCode::Char('u') if key.modifiers.contains(KeyModifiers::CONTROL) => match self.export_field {
453-
ExportField::Filename => self.export_filename.clear(),
454-
ExportField::Range => self.export_range.clear(),
455-
},
456500
KeyCode::Char(ch) if !key.modifiers.intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) => match self.export_field {
457-
ExportField::Filename => self.export_filename.push(ch),
458-
ExportField::Range => self.export_range.push(ch),
501+
ExportField::Filename => insert_char_at(&mut self.export_filename, &mut self.export_filename_cursor, ch),
502+
ExportField::Range => insert_char_at(&mut self.export_range, &mut self.export_range_cursor, ch),
459503
},
460504
_ => {}
461505
}
@@ -600,6 +644,7 @@ impl ViewApp {
600644
if self.save_filename.is_empty() {
601645
self.save_filename = "filtered.logjet".to_string();
602646
}
647+
self.save_filename_cursor = char_count(&self.save_filename);
603648
self.focus = Focus::SavePrompt;
604649
Ok(())
605650
}
@@ -621,6 +666,8 @@ impl ViewApp {
621666
if self.export_range.is_empty() {
622667
self.export_range = "all".to_string();
623668
}
669+
self.export_filename_cursor = char_count(&self.export_filename);
670+
self.export_range_cursor = char_count(&self.export_range);
624671
self.export_field = ExportField::Filename;
625672
self.focus = Focus::ExportPrompt;
626673
Ok(())
@@ -689,7 +736,7 @@ impl ViewApp {
689736
return Ok(());
690737
};
691738

692-
let selected = parse_export_selection(&self.export_range, self.entries.len()).map_err(Error::Usage);
739+
let selected = parse_export_selection(&self.export_range, self.entries.len(), self.selected).map_err(Error::Usage);
693740
let (start, end) = match selected {
694741
Ok(range) => range,
695742
Err(err) => {
@@ -1398,8 +1445,7 @@ impl ViewApp {
13981445
let cursor_x = row
13991446
.x
14001447
.saturating_add(label.chars().count() as u16)
1401-
.saturating_add(1)
1402-
.saturating_add(self.save_filename.chars().count() as u16)
1448+
.saturating_add(self.save_filename_cursor as u16)
14031449
.min(row.x.saturating_add(label.chars().count() as u16 + input_width));
14041450
let cursor_y = row.y;
14051451
frame.set_cursor_position((cursor_x, cursor_y));
@@ -1472,7 +1518,7 @@ impl ViewApp {
14721518
frame.render_widget(
14731519
Paragraph::new(Text::from(vec![
14741520
Line::from("Range accepts:"),
1475-
Line::from(" all | N | N-N"),
1521+
Line::from(" a / all | c / current / 0 | N | N-N"),
14761522
Line::from("Uses the current filtered view order."),
14771523
]))
14781524
.style(Style::default().fg(Color::DarkGray).bg(Color::Gray)),
@@ -1484,17 +1530,15 @@ impl ViewApp {
14841530
let x = filename_row
14851531
.x
14861532
.saturating_add(filename_label.chars().count() as u16)
1487-
.saturating_add(1)
1488-
.saturating_add(self.export_filename.chars().count() as u16)
1533+
.saturating_add(self.export_filename_cursor as u16)
14891534
.min(filename_row.x.saturating_add(filename_label.chars().count() as u16 + filename_width));
14901535
(x, filename_row.y)
14911536
}
14921537
ExportField::Range => {
14931538
let x = range_row
14941539
.x
14951540
.saturating_add(range_label.chars().count() as u16)
1496-
.saturating_add(1)
1497-
.saturating_add(self.export_range.chars().count() as u16)
1541+
.saturating_add(self.export_range_cursor as u16)
14981542
.min(range_row.x.saturating_add(range_label.chars().count() as u16 + range_width));
14991543
(x, range_row.y)
15001544
}
@@ -2284,14 +2328,21 @@ fn render_modal_info_entries(detail: &DetailRecord) -> Vec<(String, String)> {
22842328
lines
22852329
}
22862330

2287-
fn parse_export_selection(input: &str, total: usize) -> std::result::Result<(usize, usize), String> {
2331+
fn parse_export_selection(input: &str, total: usize, selected: usize) -> std::result::Result<(usize, usize), String> {
22882332
let trimmed = input.trim();
22892333
if trimmed.is_empty() {
22902334
return Err("Range must not be empty.".to_string());
22912335
}
2292-
if trimmed.eq_ignore_ascii_case("all") {
2336+
if trimmed.eq_ignore_ascii_case("all") || trimmed.eq_ignore_ascii_case("a") {
22932337
return Ok((0, total));
22942338
}
2339+
if trimmed.eq_ignore_ascii_case("current") || trimmed.eq_ignore_ascii_case("c") || trimmed == "0" {
2340+
if total == 0 {
2341+
return Ok((0, 0));
2342+
}
2343+
let current = selected.min(total.saturating_sub(1));
2344+
return Ok((current, current + 1));
2345+
}
22952346
if let Some((start, end)) = trimmed.split_once('-') {
22962347
let start = start.trim().parse::<usize>().map_err(|_| "Invalid range start.".to_string())?;
22972348
let end = end.trim().parse::<usize>().map_err(|_| "Invalid range end.".to_string())?;
@@ -2307,13 +2358,46 @@ fn parse_export_selection(input: &str, total: usize) -> std::result::Result<(usi
23072358
return Ok((start - 1, end.min(total)));
23082359
}
23092360

2310-
let amount = trimmed.parse::<usize>().map_err(|_| "Range must be all, N, or N-N.".to_string())?;
2361+
let amount = trimmed.parse::<usize>().map_err(|_| "Range must be all/a, current/c/0, N, or N-N.".to_string())?;
23112362
if amount == 0 {
23122363
return Err("Amount must be >= 1.".to_string());
23132364
}
23142365
Ok((0, amount.min(total)))
23152366
}
23162367

2368+
fn char_count(text: &str) -> usize {
2369+
text.chars().count()
2370+
}
2371+
2372+
fn char_to_byte_idx(text: &str, char_idx: usize) -> usize {
2373+
text.char_indices().nth(char_idx).map(|(idx, _)| idx).unwrap_or(text.len())
2374+
}
2375+
2376+
fn insert_char_at(text: &mut String, cursor: &mut usize, ch: char) {
2377+
let idx = char_to_byte_idx(text, *cursor);
2378+
text.insert(idx, ch);
2379+
*cursor += 1;
2380+
}
2381+
2382+
fn delete_char_before(text: &mut String, cursor: &mut usize) {
2383+
if *cursor == 0 {
2384+
return;
2385+
}
2386+
let end = char_to_byte_idx(text, *cursor);
2387+
let start = char_to_byte_idx(text, cursor.saturating_sub(1));
2388+
text.replace_range(start..end, "");
2389+
*cursor = cursor.saturating_sub(1);
2390+
}
2391+
2392+
fn delete_char_at(text: &mut String, cursor: usize) {
2393+
if cursor >= char_count(text) {
2394+
return;
2395+
}
2396+
let start = char_to_byte_idx(text, cursor);
2397+
let end = char_to_byte_idx(text, cursor + 1);
2398+
text.replace_range(start..end, "");
2399+
}
2400+
23172401
fn export_ndjson_objects(detail: &DetailRecord) -> Vec<JsonValue> {
23182402
if detail.meta.record_type != RecordType::Logs {
23192403
let mut obj = JsonMap::new();

0 commit comments

Comments
 (0)