@@ -65,10 +65,17 @@ pub fn run_search(files: Vec<PathBuf>, query: &str, top: usize, unique: bool) ->
6565// ---------------------
6666// TUI model
6767// ---------------------
68+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
69+ pub enum Tab {
70+ Main ,
71+ History ,
72+ }
73+
6874#[ derive( Clone ) ]
6975pub struct HistoryEntry {
7076 pub cmd : String ,
7177 pub exit_code : Option < i32 > ,
78+ pub output_lines : Vec < String > ,
7279}
7380
7481pub struct App {
@@ -90,8 +97,14 @@ pub struct App {
9097 pub last_run_cmd : Option < String > ,
9198
9299 // view state
100+ pub current_tab : Tab ,
101+ pub selected_history_index : usize ,
93102 pub pinned_output : bool , // middle-left shows output instead of suggestions
94103 pub recent_runs_area : Option < Rect > , // clickable area cache
104+ pub main_tab_area : Option < Rect > ,
105+ pub history_tab_area : Option < Rect > ,
106+ pub output_scroll : u16 , // scroll offset for main tab output
107+ pub history_scroll : u16 , // scroll offset for history tab output
95108
96109 // corpus
97110 pub corpus : Vec < String > ,
@@ -102,18 +115,31 @@ pub struct App {
102115
103116impl App {
104117 pub fn new ( corpus : Vec < String > , top : usize , db : Option < SqlitePool > ) -> Self {
118+ // Load recent history from database
119+ let history = if let Some ( ref pool) = db {
120+ load_recent_history ( pool, 100 ) . unwrap_or_default ( )
121+ } else {
122+ Vec :: new ( )
123+ } ;
124+
105125 Self {
106126 input : String :: new ( ) ,
107127 cursor : 0 ,
108128 suggestions : Vec :: new ( ) ,
109129 selected : 0 ,
110130 max_suggestions : top,
111- history : Vec :: new ( ) ,
131+ history,
112132 output_lines : Vec :: new ( ) ,
113133 is_running : false ,
114134 last_run_cmd : None ,
135+ current_tab : Tab :: Main ,
136+ selected_history_index : 0 ,
115137 pinned_output : false ,
116138 recent_runs_area : None ,
139+ main_tab_area : None ,
140+ history_tab_area : None ,
141+ output_scroll : 0 ,
142+ history_scroll : 0 ,
117143 corpus,
118144 db,
119145 session_id : Self :: generate_session_id ( ) ,
@@ -299,6 +325,38 @@ fn persist_history_entry(
299325 Ok ( ( ) )
300326}
301327
328+ fn load_recent_history ( pool : & SqlitePool , limit : usize ) -> Result < Vec < HistoryEntry > > {
329+ pool. query_collect (
330+ "SELECT command, output FROM history ORDER BY id DESC LIMIT ?1" ,
331+ vec ! [ Value :: Integer ( limit as i64 ) ] ,
332+ |row| {
333+ let command: String = row. get ( 0 ) ?;
334+ let output_str: String = row. get ( 1 ) . unwrap_or_default ( ) ;
335+
336+ // Parse output to extract lines and exit code
337+ let mut output_lines = Vec :: new ( ) ;
338+ let mut exit_code = None ;
339+
340+ for line in output_str. lines ( ) {
341+ if line. starts_with ( "(exit code: " ) && line. ends_with ( ")" ) {
342+ // Extract exit code from the last line
343+ if let Some ( code_str) = line. strip_prefix ( "(exit code: " ) . and_then ( |s| s. strip_suffix ( ")" ) ) {
344+ exit_code = code_str. parse :: < i32 > ( ) . ok ( ) ;
345+ }
346+ } else {
347+ output_lines. push ( line. to_string ( ) ) ;
348+ }
349+ }
350+
351+ Ok ( HistoryEntry {
352+ cmd : command,
353+ exit_code,
354+ output_lines,
355+ } )
356+ } ,
357+ )
358+ }
359+
302360fn hash_command ( command : & str ) -> String {
303361 let mut hasher = Sha256 :: new ( ) ;
304362 hasher. update ( command. as_bytes ( ) ) ;
0 commit comments