@@ -64,6 +64,12 @@ pub struct App {
6464
6565 /// Skip scroll adjustment on next render (set by mouse scroll)
6666 skip_scroll_adjustment : bool ,
67+
68+ /// Filter history (up to 50 entries)
69+ filter_history : Vec < String > ,
70+
71+ /// Current position in filter history (None = not navigating)
72+ history_index : Option < usize > ,
6773}
6874
6975impl App {
@@ -85,6 +91,8 @@ impl App {
8591 last_filtered_line : 0 ,
8692 show_help : false ,
8793 skip_scroll_adjustment : false ,
94+ filter_history : Vec :: new ( ) ,
95+ history_index : None ,
8896 }
8997 }
9098
@@ -254,6 +262,86 @@ impl App {
254262 pub fn cancel_filter_input ( & mut self ) {
255263 self . input_mode = InputMode :: Normal ;
256264 self . input_buffer . clear ( ) ;
265+ self . history_index = None ;
266+ }
267+
268+ /// Add filter pattern to history (called on filter submit)
269+ pub fn add_to_history ( & mut self , pattern : String ) {
270+ if pattern. is_empty ( ) {
271+ return ;
272+ }
273+
274+ // Don't add if it's the same as the last entry
275+ if let Some ( last) = self . filter_history . last ( ) {
276+ if last == & pattern {
277+ return ;
278+ }
279+ }
280+
281+ // Add to history
282+ self . filter_history . push ( pattern) ;
283+
284+ // Limit history to 50 entries
285+ if self . filter_history . len ( ) > 50 {
286+ self . filter_history . remove ( 0 ) ;
287+ }
288+
289+ // Reset history navigation
290+ self . history_index = None ;
291+ }
292+
293+ /// Navigate up in filter history (older entries)
294+ pub fn history_up ( & mut self ) {
295+ if self . filter_history . is_empty ( ) {
296+ return ;
297+ }
298+
299+ let new_index = match self . history_index {
300+ None => {
301+ // First time navigating - save current input and go to most recent
302+ Some ( self . filter_history . len ( ) - 1 )
303+ }
304+ Some ( idx) => {
305+ // Already navigating - go to older entry
306+ if idx > 0 {
307+ Some ( idx - 1 )
308+ } else {
309+ Some ( idx) // At oldest, stay there
310+ }
311+ }
312+ } ;
313+
314+ self . history_index = new_index;
315+ if let Some ( idx) = new_index {
316+ self . input_buffer = self . filter_history [ idx] . clone ( ) ;
317+ }
318+ }
319+
320+ /// Navigate down in filter history (newer entries)
321+ pub fn history_down ( & mut self ) {
322+ if self . filter_history . is_empty ( ) {
323+ return ;
324+ }
325+
326+ let new_index = match self . history_index {
327+ None => None , // Not navigating, do nothing
328+ Some ( idx) => {
329+ if idx < self . filter_history . len ( ) - 1 {
330+ Some ( idx + 1 )
331+ } else {
332+ // At newest entry, go back to empty input
333+ None
334+ }
335+ }
336+ } ;
337+
338+ self . history_index = new_index;
339+ if let Some ( idx) = new_index {
340+ self . input_buffer = self . filter_history [ idx] . clone ( ) ;
341+ } else {
342+ // Back to empty
343+ self . input_buffer . clear ( ) ;
344+ }
257345 }
258346
259347 /// Add a character to the input buffer
@@ -366,7 +454,12 @@ impl App {
366454 AppEvent :: StartFilterInput => self . start_filter_input ( ) ,
367455 AppEvent :: FilterInputChar ( c) => self . input_char ( c) ,
368456 AppEvent :: FilterInputBackspace => self . input_backspace ( ) ,
369- AppEvent :: FilterInputSubmit => self . cancel_filter_input ( ) ,
457+ AppEvent :: FilterInputSubmit => {
458+ // Save current filter to history before closing
459+ let pattern = self . input_buffer . clone ( ) ;
460+ self . add_to_history ( pattern) ;
461+ self . cancel_filter_input ( ) ;
462+ }
370463 AppEvent :: FilterInputCancel => self . cancel_filter_input ( ) ,
371464 AppEvent :: ClearFilter => self . clear_filter ( ) ,
372465
@@ -466,13 +559,14 @@ impl App {
466559 }
467560 AppEvent :: LineJumpInputCancel => self . cancel_line_jump_input ( ) ,
468561
562+ // Filter history navigation
563+ AppEvent :: HistoryUp => self . history_up ( ) ,
564+ AppEvent :: HistoryDown => self . history_down ( ) ,
565+
469566 // Future events - not yet implemented
470567 AppEvent :: StartFilter { .. } => {
471568 // Will be handled in main loop to trigger background filter
472569 }
473- AppEvent :: HistoryUp | AppEvent :: HistoryDown => {
474- // Not yet implemented - placeholders for future features
475- }
476570 }
477571 }
478572}
@@ -1110,4 +1204,195 @@ mod tests {
11101204 // Should apply padding adjustment (selection is at top, should add padding)
11111205 assert_eq ! ( app. scroll_position, 2 ) ; // 5 - 3 (padding)
11121206 }
1207+
1208+ #[ test]
1209+ fn test_add_to_history ( ) {
1210+ let mut app = App :: new ( 10 ) ;
1211+
1212+ // Add patterns to history
1213+ app. add_to_history ( "ERROR" . to_string ( ) ) ;
1214+ app. add_to_history ( "WARN" . to_string ( ) ) ;
1215+ app. add_to_history ( "INFO" . to_string ( ) ) ;
1216+
1217+ assert_eq ! ( app. filter_history. len( ) , 3 ) ;
1218+ assert_eq ! ( app. filter_history[ 0 ] , "ERROR" ) ;
1219+ assert_eq ! ( app. filter_history[ 1 ] , "WARN" ) ;
1220+ assert_eq ! ( app. filter_history[ 2 ] , "INFO" ) ;
1221+ }
1222+
1223+ #[ test]
1224+ fn test_add_to_history_skips_duplicates ( ) {
1225+ let mut app = App :: new ( 10 ) ;
1226+
1227+ app. add_to_history ( "ERROR" . to_string ( ) ) ;
1228+ app. add_to_history ( "ERROR" . to_string ( ) ) ; // Duplicate - should not add
1229+
1230+ assert_eq ! ( app. filter_history. len( ) , 1 ) ;
1231+ }
1232+
1233+ #[ test]
1234+ fn test_add_to_history_skips_empty ( ) {
1235+ let mut app = App :: new ( 10 ) ;
1236+
1237+ app. add_to_history ( "" . to_string ( ) ) ;
1238+
1239+ assert_eq ! ( app. filter_history. len( ) , 0 ) ;
1240+ }
1241+
1242+ #[ test]
1243+ fn test_history_limit ( ) {
1244+ let mut app = App :: new ( 10 ) ;
1245+
1246+ // Add 52 entries to exceed limit of 50
1247+ for i in 0 ..52 {
1248+ app. add_to_history ( format ! ( "pattern{}" , i) ) ;
1249+ }
1250+
1251+ // Should only keep 50 most recent
1252+ assert_eq ! ( app. filter_history. len( ) , 50 ) ;
1253+ // Oldest should be removed
1254+ assert_eq ! ( app. filter_history[ 0 ] , "pattern2" ) ;
1255+ assert_eq ! ( app. filter_history[ 49 ] , "pattern51" ) ;
1256+ }
1257+
1258+ #[ test]
1259+ fn test_history_up_navigation ( ) {
1260+ use crate :: event:: AppEvent ;
1261+
1262+ let mut app = App :: new ( 10 ) ;
1263+
1264+ app. add_to_history ( "ERROR" . to_string ( ) ) ;
1265+ app. add_to_history ( "WARN" . to_string ( ) ) ;
1266+ app. add_to_history ( "INFO" . to_string ( ) ) ;
1267+
1268+ // Start filter input
1269+ app. start_filter_input ( ) ;
1270+
1271+ // Navigate up (most recent)
1272+ app. apply_event ( AppEvent :: HistoryUp ) ;
1273+ assert_eq ! ( app. input_buffer, "INFO" ) ;
1274+ assert_eq ! ( app. history_index, Some ( 2 ) ) ;
1275+
1276+ // Navigate up again (older)
1277+ app. apply_event ( AppEvent :: HistoryUp ) ;
1278+ assert_eq ! ( app. input_buffer, "WARN" ) ;
1279+ assert_eq ! ( app. history_index, Some ( 1 ) ) ;
1280+
1281+ // Navigate up again
1282+ app. apply_event ( AppEvent :: HistoryUp ) ;
1283+ assert_eq ! ( app. input_buffer, "ERROR" ) ;
1284+ assert_eq ! ( app. history_index, Some ( 0 ) ) ;
1285+
1286+ // Try to go up past oldest (should stay)
1287+ app. apply_event ( AppEvent :: HistoryUp ) ;
1288+ assert_eq ! ( app. input_buffer, "ERROR" ) ;
1289+ assert_eq ! ( app. history_index, Some ( 0 ) ) ;
1290+ }
1291+
1292+ #[ test]
1293+ fn test_history_down_navigation ( ) {
1294+ use crate :: event:: AppEvent ;
1295+
1296+ let mut app = App :: new ( 10 ) ;
1297+
1298+ app. add_to_history ( "ERROR" . to_string ( ) ) ;
1299+ app. add_to_history ( "WARN" . to_string ( ) ) ;
1300+ app. add_to_history ( "INFO" . to_string ( ) ) ;
1301+
1302+ app. start_filter_input ( ) ;
1303+
1304+ // Navigate up to oldest
1305+ app. apply_event ( AppEvent :: HistoryUp ) ;
1306+ app. apply_event ( AppEvent :: HistoryUp ) ;
1307+ app. apply_event ( AppEvent :: HistoryUp ) ;
1308+ assert_eq ! ( app. input_buffer, "ERROR" ) ;
1309+
1310+ // Navigate down (newer)
1311+ app. apply_event ( AppEvent :: HistoryDown ) ;
1312+ assert_eq ! ( app. input_buffer, "WARN" ) ;
1313+ assert_eq ! ( app. history_index, Some ( 1 ) ) ;
1314+
1315+ // Navigate down again
1316+ app. apply_event ( AppEvent :: HistoryDown ) ;
1317+ assert_eq ! ( app. input_buffer, "INFO" ) ;
1318+ assert_eq ! ( app. history_index, Some ( 2 ) ) ;
1319+
1320+ // Navigate down past newest (should clear)
1321+ app. apply_event ( AppEvent :: HistoryDown ) ;
1322+ assert_eq ! ( app. input_buffer, "" ) ;
1323+ assert_eq ! ( app. history_index, None ) ;
1324+ }
1325+
1326+ #[ test]
1327+ fn test_history_down_when_not_navigating ( ) {
1328+ use crate :: event:: AppEvent ;
1329+
1330+ let mut app = App :: new ( 10 ) ;
1331+
1332+ app. add_to_history ( "ERROR" . to_string ( ) ) ;
1333+ app. start_filter_input ( ) ;
1334+
1335+ // Down arrow when not navigating should do nothing
1336+ app. apply_event ( AppEvent :: HistoryDown ) ;
1337+ assert_eq ! ( app. input_buffer, "" ) ;
1338+ assert_eq ! ( app. history_index, None ) ;
1339+ }
1340+
1341+ #[ test]
1342+ fn test_filter_submit_saves_to_history ( ) {
1343+ use crate :: event:: AppEvent ;
1344+
1345+ let mut app = App :: new ( 10 ) ;
1346+
1347+ // Start filter and type
1348+ app. start_filter_input ( ) ;
1349+ app. input_char ( 'E' ) ;
1350+ app. input_char ( 'R' ) ;
1351+ app. input_char ( 'R' ) ;
1352+
1353+ // Submit filter
1354+ app. apply_event ( AppEvent :: FilterInputSubmit ) ;
1355+
1356+ // Should be saved to history
1357+ assert_eq ! ( app. filter_history. len( ) , 1 ) ;
1358+ assert_eq ! ( app. filter_history[ 0 ] , "ERR" ) ;
1359+ }
1360+
1361+ #[ test]
1362+ fn test_cancel_filter_resets_history_index ( ) {
1363+ use crate :: event:: AppEvent ;
1364+
1365+ let mut app = App :: new ( 10 ) ;
1366+
1367+ app. add_to_history ( "ERROR" . to_string ( ) ) ;
1368+ app. start_filter_input ( ) ;
1369+
1370+ // Navigate history
1371+ app. apply_event ( AppEvent :: HistoryUp ) ;
1372+ assert_eq ! ( app. history_index, Some ( 0 ) ) ;
1373+
1374+ // Cancel filter
1375+ app. cancel_filter_input ( ) ;
1376+
1377+ // History index should be reset
1378+ assert_eq ! ( app. history_index, None ) ;
1379+ }
1380+
1381+ #[ test]
1382+ fn test_history_empty ( ) {
1383+ use crate :: event:: AppEvent ;
1384+
1385+ let mut app = App :: new ( 10 ) ;
1386+
1387+ app. start_filter_input ( ) ;
1388+
1389+ // Try to navigate empty history
1390+ app. apply_event ( AppEvent :: HistoryUp ) ;
1391+ assert_eq ! ( app. input_buffer, "" ) ;
1392+ assert_eq ! ( app. history_index, None ) ;
1393+
1394+ app. apply_event ( AppEvent :: HistoryDown ) ;
1395+ assert_eq ! ( app. input_buffer, "" ) ;
1396+ assert_eq ! ( app. history_index, None ) ;
1397+ }
11131398}
0 commit comments