@@ -245,23 +245,26 @@ fn handle_register_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
245245 match key. code {
246246 // Navigation
247247 KeyCode :: Char ( 'j' ) | KeyCode :: Down => {
248+ app. pending_g = false ;
248249 app. move_down ( txn_count) ;
249250 // Update selected transaction from sorted list
250251 if let Some ( txn) = txns. get ( app. selected_transaction_index ) {
251252 app. selected_transaction = Some ( txn. id ) ;
252253 }
253254 }
254255 KeyCode :: Char ( 'k' ) | KeyCode :: Up => {
256+ app. pending_g = false ;
255257 app. move_up ( ) ;
256258 // Update selected transaction from sorted list
257259 if let Some ( txn) = txns. get ( app. selected_transaction_index ) {
258260 app. selected_transaction = Some ( txn. id ) ;
259261 }
260262 }
261263
262- // Page navigation
264+ // Page navigation (Vim-style)
263265 KeyCode :: Char ( 'G' ) => {
264- // Go to bottom
266+ // Shift-G: Go to bottom
267+ app. pending_g = false ;
265268 if txn_count > 0 {
266269 app. selected_transaction_index = txn_count - 1 ;
267270 if let Some ( txn) = txns. get ( app. selected_transaction_index ) {
@@ -270,20 +273,29 @@ fn handle_register_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
270273 }
271274 }
272275 KeyCode :: Char ( 'g' ) => {
273- // Go to top (gg in vim, but we'll use single g)
274- app. selected_transaction_index = 0 ;
275- if let Some ( txn) = txns. first ( ) {
276- app. selected_transaction = Some ( txn. id ) ;
276+ // gg: Go to top (requires double-g press)
277+ if app. pending_g {
278+ // Second 'g' pressed - go to top
279+ app. pending_g = false ;
280+ app. selected_transaction_index = 0 ;
281+ if let Some ( txn) = txns. first ( ) {
282+ app. selected_transaction = Some ( txn. id ) ;
283+ }
284+ } else {
285+ // First 'g' pressed - wait for second
286+ app. pending_g = true ;
277287 }
278288 }
279289
280290 // Add transaction
281291 KeyCode :: Char ( 'a' ) | KeyCode :: Char ( 'n' ) => {
292+ app. pending_g = false ;
282293 app. open_dialog ( ActiveDialog :: AddTransaction ) ;
283294 }
284295
285296 // Edit transaction
286297 KeyCode :: Char ( 'e' ) => {
298+ app. pending_g = false ;
287299 // DEBUG: Force initialize selection and try edit
288300 if app. selected_transaction . is_none ( ) {
289301 let txns = get_sorted_transactions ( app) ;
@@ -296,6 +308,7 @@ fn handle_register_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
296308 }
297309 }
298310 KeyCode :: Enter => {
311+ app. pending_g = false ;
299312 if app. selected_transaction . is_none ( ) {
300313 let txns = get_sorted_transactions ( app) ;
301314 if let Some ( txn) = txns. get ( app. selected_transaction_index ) {
@@ -309,6 +322,7 @@ fn handle_register_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
309322
310323 // Clear transaction (toggle)
311324 KeyCode :: Char ( 'c' ) => {
325+ app. pending_g = false ;
312326 if let Some ( txn_id) = app. selected_transaction {
313327 // Toggle cleared status
314328 if let Ok ( Some ( txn) ) = app. storage . transactions . get ( txn_id) {
@@ -331,6 +345,7 @@ fn handle_register_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
331345
332346 // Delete transaction
333347 KeyCode :: Char ( 'd' ) if key. modifiers . contains ( KeyModifiers :: CONTROL ) => {
348+ app. pending_g = false ;
334349 if app. selected_transaction . is_some ( ) {
335350 app. open_dialog ( ActiveDialog :: Confirm (
336351 "Delete this transaction?" . to_string ( ) ,
@@ -340,6 +355,7 @@ fn handle_register_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
340355
341356 // Multi-select mode
342357 KeyCode :: Char ( 'v' ) => {
358+ app. pending_g = false ;
343359 app. toggle_multi_select ( ) ;
344360 if app. multi_select_mode {
345361 app. set_status ( "Multi-select mode ON" ) ;
@@ -350,16 +366,19 @@ fn handle_register_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
350366
351367 // Toggle selection in multi-select mode
352368 KeyCode :: Char ( ' ' ) if app. multi_select_mode => {
369+ app. pending_g = false ;
353370 app. toggle_transaction_selection ( ) ;
354371 }
355372
356373 // Bulk categorize
357374 KeyCode :: Char ( 'C' ) if app. multi_select_mode && !app. selected_transactions . is_empty ( ) => {
375+ app. pending_g = false ;
358376 app. open_dialog ( ActiveDialog :: BulkCategorize ) ;
359377 }
360378
361379 // Bulk delete
362380 KeyCode :: Char ( 'D' ) if app. multi_select_mode && !app. selected_transactions . is_empty ( ) => {
381+ app. pending_g = false ;
363382 let count = app. selected_transactions . len ( ) ;
364383 app. open_dialog ( ActiveDialog :: Confirm ( format ! (
365384 "Delete {} transaction{}?" ,
@@ -368,7 +387,9 @@ fn handle_register_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
368387 ) ) ) ;
369388 }
370389
371- _ => { }
390+ _ => {
391+ app. pending_g = false ;
392+ }
372393 }
373394
374395 Ok ( ( ) )
@@ -404,58 +425,95 @@ fn handle_budget_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
404425 match key. code {
405426 // Navigation
406427 KeyCode :: Char ( 'j' ) | KeyCode :: Down => {
428+ app. pending_g = false ;
407429 app. move_down ( category_count) ;
408430 if let Some ( cat) = categories. get ( app. selected_category_index ) {
409431 app. selected_category = Some ( cat. id ) ;
410432 }
411433 }
412434 KeyCode :: Char ( 'k' ) | KeyCode :: Up => {
435+ app. pending_g = false ;
413436 app. move_up ( ) ;
414437 if let Some ( cat) = categories. get ( app. selected_category_index ) {
415438 app. selected_category = Some ( cat. id ) ;
416439 }
417440 }
418441
442+ // Page navigation (Vim-style)
443+ KeyCode :: Char ( 'G' ) => {
444+ // Shift-G: Go to bottom
445+ app. pending_g = false ;
446+ if category_count > 0 {
447+ app. selected_category_index = category_count - 1 ;
448+ if let Some ( cat) = categories. get ( app. selected_category_index ) {
449+ app. selected_category = Some ( cat. id ) ;
450+ }
451+ }
452+ }
453+ KeyCode :: Char ( 'g' ) => {
454+ // gg: Go to top (requires double-g press)
455+ if app. pending_g {
456+ // Second 'g' pressed - go to top
457+ app. pending_g = false ;
458+ app. selected_category_index = 0 ;
459+ if let Some ( cat) = categories. first ( ) {
460+ app. selected_category = Some ( cat. id ) ;
461+ }
462+ } else {
463+ // First 'g' pressed - wait for second
464+ app. pending_g = true ;
465+ }
466+ }
467+
419468 // Period navigation
420469 KeyCode :: Char ( '[' ) | KeyCode :: Char ( 'H' ) => {
470+ app. pending_g = false ;
421471 app. prev_period ( ) ;
422472 }
423473 KeyCode :: Char ( ']' ) | KeyCode :: Char ( 'L' ) => {
474+ app. pending_g = false ;
424475 app. next_period ( ) ;
425476 }
426477
427478 // Header display toggle (cycle through account types)
428479 KeyCode :: Char ( '<' ) | KeyCode :: Char ( ',' ) => {
480+ app. pending_g = false ;
429481 app. budget_header_display = app. budget_header_display . prev ( ) ;
430482 }
431483 KeyCode :: Char ( '>' ) | KeyCode :: Char ( '.' ) => {
484+ app. pending_g = false ;
432485 app. budget_header_display = app. budget_header_display . next ( ) ;
433486 }
434487
435488 // Move funds
436489 KeyCode :: Char ( 'm' ) => {
490+ app. pending_g = false ;
437491 app. open_dialog ( ActiveDialog :: MoveFunds ) ;
438492 }
439493
440494 // Add new category
441495 KeyCode :: Char ( 'a' ) => {
496+ app. pending_g = false ;
442497 app. open_dialog ( ActiveDialog :: AddCategory ) ;
443498 }
444499
445500 // Add new category group
446501 KeyCode :: Char ( 'A' ) => {
502+ app. pending_g = false ;
447503 app. open_dialog ( ActiveDialog :: AddGroup ) ;
448504 }
449505
450506 // Edit category group (Shift+E)
451507 KeyCode :: Char ( 'E' ) => {
508+ app. pending_g = false ;
452509 if let Some ( cat) = categories. get ( app. selected_category_index ) {
453510 app. open_dialog ( ActiveDialog :: EditGroup ( cat. group_id ) ) ;
454511 }
455512 }
456513
457514 // Delete category group (Shift+D)
458515 KeyCode :: Char ( 'D' ) => {
516+ app. pending_g = false ;
459517 if let Some ( cat) = categories. get ( app. selected_category_index ) {
460518 if let Ok ( Some ( group) ) = app. storage . categories . get_group ( cat. group_id ) {
461519 let group_categories = app
@@ -479,6 +537,7 @@ fn handle_budget_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
479537
480538 // Edit category
481539 KeyCode :: Char ( 'e' ) => {
540+ app. pending_g = false ;
482541 if let Some ( cat) = categories. get ( app. selected_category_index ) {
483542 app. selected_category = Some ( cat. id ) ;
484543 app. open_dialog ( ActiveDialog :: EditCategory ( cat. id ) ) ;
@@ -487,6 +546,7 @@ fn handle_budget_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
487546
488547 // Delete category
489548 KeyCode :: Char ( 'd' ) => {
549+ app. pending_g = false ;
490550 if let Some ( cat) = categories. get ( app. selected_category_index ) {
491551 app. selected_category = Some ( cat. id ) ;
492552 if let Ok ( Some ( category) ) = app. storage . categories . get_category ( cat. id ) {
@@ -500,13 +560,16 @@ fn handle_budget_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
500560
501561 // Open unified budget dialog (period budget + target)
502562 KeyCode :: Enter | KeyCode :: Char ( 'b' ) | KeyCode :: Char ( 't' ) => {
563+ app. pending_g = false ;
503564 if let Some ( cat) = categories. get ( app. selected_category_index ) {
504565 app. selected_category = Some ( cat. id ) ;
505566 app. open_dialog ( ActiveDialog :: Budget ) ;
506567 }
507568 }
508569
509- _ => { }
570+ _ => {
571+ app. pending_g = false ;
572+ }
510573 }
511574
512575 Ok ( ( ) )
0 commit comments