@@ -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+
23172401fn export_ndjson_objects ( detail : & DetailRecord ) -> Vec < JsonValue > {
23182402 if detail. meta . record_type != RecordType :: Logs {
23192403 let mut obj = JsonMap :: new ( ) ;
0 commit comments