1
1
use std:: {
2
2
cell:: RefCell ,
3
3
env, fs,
4
- io:: { self , BufRead , BufReader , Read , Write } ,
4
+ io:: { self , Write } ,
5
5
mem,
6
6
path:: PathBuf ,
7
7
rc:: Rc ,
@@ -46,14 +46,14 @@ struct Config {
46
46
close_on_click : bool ,
47
47
#[ serde( default ) ]
48
48
show_results_immediately : bool ,
49
- #[ serde( default ) ]
50
- max_entries : Option < usize > ,
51
49
#[ serde( default = "Config::default_layer" ) ]
52
50
layer : Layer ,
53
51
#[ serde( default ) ]
54
52
persist_state : bool ,
55
53
#[ serde( default ) ]
56
54
state_ttl_secs : Option < u64 > ,
55
+ #[ serde( default ) ]
56
+ max_entries : Option < usize > ,
57
57
}
58
58
59
59
impl Config {
@@ -186,6 +186,12 @@ struct RuntimeData {
186
186
state_dir : Option < String > ,
187
187
}
188
188
189
+ #[ derive( Deserialize , Serialize ) ]
190
+ struct PersistentState {
191
+ timestamp : u64 ,
192
+ text : String ,
193
+ }
194
+
189
195
impl RuntimeData {
190
196
fn new ( config_dir_path : Option < String > , cli_config : ConfigArgs ) -> Self {
191
197
// Setup config directory
@@ -252,53 +258,50 @@ impl RuntimeData {
252
258
253
259
fn state_file ( & self ) -> String {
254
260
let state_dir = self . state_dir . as_ref ( ) . expect ( "state operations called when persistence is disabled" ) ;
255
- PathBuf :: from ( state_dir) . join ( "state.txt " ) . to_str ( ) . unwrap ( ) . to_string ( )
261
+ PathBuf :: from ( state_dir) . join ( "state.ron " ) . to_str ( ) . unwrap ( ) . to_string ( )
256
262
}
257
263
258
264
fn save_state ( & self , text : & str ) -> io:: Result < ( ) > {
259
265
if !self . config . persist_state {
260
266
return Ok ( ( ) ) ;
261
267
}
262
- let timestamp = SystemTime :: now ( )
263
- . duration_since ( UNIX_EPOCH )
264
- . unwrap ( )
265
- . as_millis ( ) ;
266
-
267
- let mut file = fs:: File :: create ( self . state_file ( ) ) ?;
268
- writeln ! ( file, "{}" , timestamp) ?;
269
- write ! ( file, "{}" , text)
268
+
269
+ let state = PersistentState {
270
+ timestamp : SystemTime :: now ( )
271
+ . duration_since ( UNIX_EPOCH )
272
+ . unwrap ( )
273
+ . as_secs ( ) ,
274
+ text : text. to_string ( ) ,
275
+ } ;
276
+
277
+ fs:: write ( self . state_file ( ) , ron:: ser:: to_string_pretty ( & state, ron:: ser:: PrettyConfig :: default ( ) )
278
+ . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: Other , e) ) ?)
270
279
}
271
280
272
- fn load_state ( & self ) -> io:: Result < String > {
281
+ fn load_state ( & self ) -> io:: Result < Option < String > > {
273
282
if !self . config . persist_state {
274
- return Ok ( String :: new ( ) ) ;
283
+ return Ok ( None ) ;
275
284
}
276
- match fs:: File :: open ( self . state_file ( ) ) {
277
- Ok ( file) => {
278
- let mut reader = BufReader :: new ( file) ;
279
-
280
- // Read timestamp from first line
281
- let mut timestamp_str = String :: new ( ) ;
282
- reader. read_line ( & mut timestamp_str) ?;
283
- let timestamp = timestamp_str. trim ( ) . parse :: < u128 > ( ) . unwrap_or ( 0 ) ;
284
-
285
+
286
+ match fs:: read_to_string ( self . state_file ( ) ) {
287
+ Ok ( content) => {
288
+ let state: PersistentState = ron:: from_str ( & content)
289
+ . map_err ( |e| io:: Error :: new ( io:: ErrorKind :: Other , e) ) ?;
290
+
285
291
// Check if state has expired
286
292
if let Some ( expiry_secs) = self . config . state_ttl_secs {
287
293
let now = SystemTime :: now ( )
288
294
. duration_since ( UNIX_EPOCH )
289
295
. unwrap ( )
290
- . as_millis ( ) ;
291
- if now - timestamp > u128 :: from ( expiry_secs) * 1000 {
292
- return Ok ( String :: new ( ) ) ;
296
+ . as_secs ( ) ;
297
+ if now - state . timestamp > u64 :: from ( expiry_secs) {
298
+ return Ok ( None ) ;
293
299
}
294
300
}
295
-
296
- // Read text from second line to end
297
- let mut text = String :: new ( ) ;
298
- reader. read_to_string ( & mut text) ?;
299
- Ok ( text)
301
+
302
+ Ok ( Some ( state. text ) )
300
303
}
301
- Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => Ok ( String :: new ( ) ) ,
304
+ Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => Ok ( None ) ,
302
305
Err ( e) => Err ( e) ,
303
306
}
304
307
}
@@ -307,7 +310,12 @@ impl RuntimeData {
307
310
if !self . config . persist_state {
308
311
return Ok ( ( ) ) ;
309
312
}
310
- fs:: write ( self . state_file ( ) , "0\n " )
313
+
314
+ match fs:: remove_file ( self . state_file ( ) ) {
315
+ Ok ( ( ) ) => Ok ( ( ) ) ,
316
+ Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => Ok ( ( ) ) , // File doesn't exist = already cleared
317
+ Err ( e) => Err ( e) ,
318
+ }
311
319
}
312
320
}
313
321
@@ -557,13 +565,6 @@ fn activate(app: >k::Application, runtime_data: Rc<RefCell<RuntimeData>>) {
557
565
. name ( style_names:: ENTRY )
558
566
. build ( ) ;
559
567
560
- // Set initial text from loaded state
561
- if let Ok ( initial_text) = runtime_data. borrow ( ) . load_state ( ) {
562
- entry. set_text ( & initial_text) ;
563
- } else {
564
- eprintln ! ( "Failed to load state" ) ;
565
- }
566
-
567
568
// Update last_input, save state and refresh matches when text changes
568
569
let runtime_data_clone = runtime_data. clone ( ) ;
569
570
entry. connect_changed ( move |entry| {
@@ -743,11 +744,19 @@ fn activate(app: >k::Application, runtime_data: Rc<RefCell<RuntimeData>>) {
743
744
// Only create the widgets once to avoid issues
744
745
let configure_once = Once :: new ( ) ;
745
746
746
- // Create widgets here for proper positioning
747
+ // Load initial state before configuring
748
+ let initial_text = if runtime_data. borrow ( ) . config . persist_state {
749
+ runtime_data. borrow ( ) . load_state ( ) . ok ( ) . flatten ( )
750
+ } else {
751
+ None
752
+ } ;
753
+ let initial_text_clone = initial_text. clone ( ) ;
754
+
747
755
window. connect_configure_event ( move |window, event| {
748
756
let runtime_data = runtime_data. clone ( ) ;
749
757
let entry = entry. clone ( ) ;
750
758
let main_list = main_list. clone ( ) ;
759
+ let initial_text_inner = initial_text_clone. clone ( ) ;
751
760
752
761
configure_once. call_once ( move || {
753
762
{
@@ -791,13 +800,18 @@ fn activate(app: >k::Application, runtime_data: Rc<RefCell<RuntimeData>>) {
791
800
main_vbox. add ( & main_list) ;
792
801
main_list. show ( ) ;
793
802
entry. grab_focus ( ) ; // Grab the focus so typing is immediately accepted by the entry box
794
- entry. set_position ( -1 ) ; // -1 moves cursor to end of text in case some text was restored
803
+
804
+ // Set initial text if we loaded state
805
+ if let Some ( text) = & initial_text_inner {
806
+ entry. set_text ( text) ;
807
+ entry. set_position ( -1 ) ; // -1 moves cursor to end of text
808
+ }
795
809
}
796
810
797
- // Show initial results if state restoration is enabled or immediate results are configured
811
+ // Show initial results if state was loaded or immediate results are configured
798
812
let should_show_results = {
799
813
let data = runtime_data. borrow ( ) ;
800
- data . config . persist_state || data. config . show_results_immediately
814
+ initial_text_inner . is_some ( ) || data. config . show_results_immediately
801
815
} ;
802
816
803
817
if should_show_results {
0 commit comments