LCOV - code coverage report
Current view: top level - src/tui - app.c (source / functions) Coverage Total Hit
Test: coverage-functional.info Lines: 47.6 % 103 49
Test Date: 2026-04-20 19:54:24 Functions: 85.7 % 7 6

            Line data    Source code
       1              : /* SPDX-License-Identifier: GPL-3.0-or-later */
       2              : /* Copyright 2026 Peter Csaszar */
       3              : 
       4              : /**
       5              :  * @file tui/app.c
       6              :  * @brief TUI state machine implementation (US-11 v2).
       7              :  */
       8              : 
       9              : #include "tui/app.h"
      10              : 
      11              : #include <string.h>
      12              : 
      13            3 : int tui_app_init(TuiApp *app, int rows, int cols) {
      14            3 :     if (!app) return -1;
      15            3 :     memset(app, 0, sizeof(*app));
      16            3 :     if (screen_init(&app->screen, rows, cols) != 0) return -1;
      17            3 :     layout_compute(&app->layout, rows, cols, 28);
      18            3 :     dialog_pane_init(&app->dialogs);
      19            3 :     history_pane_init(&app->history);
      20            3 :     status_row_init(&app->status);
      21            3 :     app->focus = TUI_FOCUS_DIALOGS;
      22            3 :     app->rows = rows;
      23            3 :     app->cols = cols;
      24              :     /* Wire viewport heights so list_views know how much they render. */
      25            3 :     app->dialogs.lv.rows_visible = app->layout.dialogs.rows;
      26            3 :     app->history.lv.rows_visible = app->layout.history.rows;
      27            3 :     return 0;
      28              : }
      29              : 
      30            3 : void tui_app_free(TuiApp *app) {
      31            3 :     if (!app) return;
      32            3 :     screen_free(&app->screen);
      33              : }
      34              : 
      35            0 : int tui_app_resize(TuiApp *app, int rows, int cols) {
      36            0 :     if (!app) return -1;
      37            0 :     screen_free(&app->screen);
      38            0 :     if (screen_init(&app->screen, rows, cols) != 0) return -1;
      39            0 :     layout_compute(&app->layout, rows, cols, 28);
      40            0 :     list_view_set_viewport(&app->dialogs.lv, app->layout.dialogs.rows);
      41            0 :     list_view_set_viewport(&app->history.lv, app->layout.history.rows);
      42            0 :     app->rows = rows;
      43            0 :     app->cols = cols;
      44            0 :     return 0;
      45              : }
      46              : 
      47            1 : static ListView *focused_list_view(TuiApp *app) {
      48            1 :     return (app->focus == TUI_FOCUS_DIALOGS)
      49              :          ? &app->dialogs.lv
      50            1 :          : &app->history.lv;
      51              : }
      52              : 
      53            2 : TuiEvent tui_app_handle_key(TuiApp *app, TermKey key) {
      54            2 :     if (!app) return TUI_EVENT_NONE;
      55              : 
      56            2 :     switch (key) {
      57            0 :     case TERM_KEY_QUIT:
      58              :     case TERM_KEY_ESC:
      59            0 :         return TUI_EVENT_QUIT;
      60              : 
      61            0 :     case TERM_KEY_LEFT:
      62            0 :         if (app->focus != TUI_FOCUS_DIALOGS) {
      63            0 :             app->focus = TUI_FOCUS_DIALOGS;
      64            0 :             app->status.mode = STATUS_MODE_DIALOGS;
      65            0 :             return TUI_EVENT_REDRAW;
      66              :         }
      67            0 :         return TUI_EVENT_NONE;
      68              : 
      69            0 :     case TERM_KEY_RIGHT:
      70            0 :         if (app->focus != TUI_FOCUS_HISTORY) {
      71            0 :             app->focus = TUI_FOCUS_HISTORY;
      72            0 :             app->status.mode = STATUS_MODE_HISTORY;
      73            0 :             return TUI_EVENT_REDRAW;
      74              :         }
      75            0 :         return TUI_EVENT_NONE;
      76              : 
      77            0 :     case TERM_KEY_PREV_LINE:
      78            0 :         list_view_move_up(focused_list_view(app));
      79            0 :         return TUI_EVENT_REDRAW;
      80              : 
      81            1 :     case TERM_KEY_NEXT_LINE:
      82            1 :         list_view_move_down(focused_list_view(app));
      83            1 :         return TUI_EVENT_REDRAW;
      84              : 
      85            0 :     case TERM_KEY_PREV_PAGE:
      86            0 :         list_view_page_up(focused_list_view(app));
      87            0 :         return TUI_EVENT_REDRAW;
      88              : 
      89            0 :     case TERM_KEY_NEXT_PAGE:
      90            0 :         list_view_page_down(focused_list_view(app));
      91            0 :         return TUI_EVENT_REDRAW;
      92              : 
      93            0 :     case TERM_KEY_HOME:
      94            0 :         list_view_home(focused_list_view(app));
      95            0 :         return TUI_EVENT_REDRAW;
      96              : 
      97            0 :     case TERM_KEY_END:
      98            0 :         list_view_end(focused_list_view(app));
      99            0 :         return TUI_EVENT_REDRAW;
     100              : 
     101            1 :     case TERM_KEY_ENTER:
     102            1 :         if (app->focus == TUI_FOCUS_DIALOGS
     103            1 :             && dialog_pane_selected(&app->dialogs) != NULL) {
     104              :             /* Caller loads history for the selection and then flips focus
     105              :              * to the history pane. We switch focus here pre-emptively so
     106              :              * the status-row hint updates even before the network call. */
     107            1 :             app->focus = TUI_FOCUS_HISTORY;
     108            1 :             app->status.mode = STATUS_MODE_HISTORY;
     109            1 :             return TUI_EVENT_OPEN_DIALOG;
     110              :         }
     111            0 :         return TUI_EVENT_NONE;
     112              : 
     113            0 :     default:
     114            0 :         return TUI_EVENT_NONE;
     115              :     }
     116              : }
     117              : 
     118            2 : TuiEvent tui_app_handle_char(TuiApp *app, int ch) {
     119            2 :     if (!app) return TUI_EVENT_NONE;
     120            2 :     switch (ch) {
     121            1 :     case 'q': case 'Q':
     122            1 :         return TUI_EVENT_QUIT;
     123            1 :     case 'j':
     124            1 :         return tui_app_handle_key(app, TERM_KEY_NEXT_LINE);
     125            0 :     case 'k':
     126            0 :         return tui_app_handle_key(app, TERM_KEY_PREV_LINE);
     127            0 :     case 'h':
     128            0 :         return tui_app_handle_key(app, TERM_KEY_LEFT);
     129            0 :     case 'l':
     130            0 :         return tui_app_handle_key(app, TERM_KEY_RIGHT);
     131            0 :     case 'g':
     132            0 :         return tui_app_handle_key(app, TERM_KEY_HOME);
     133            0 :     case 'G':
     134            0 :         return tui_app_handle_key(app, TERM_KEY_END);
     135            0 :     default:
     136            0 :         return TUI_EVENT_NONE;
     137              :     }
     138              : }
     139              : 
     140            3 : void tui_app_paint(TuiApp *app) {
     141            3 :     if (!app) return;
     142            3 :     screen_clear_back(&app->screen);
     143            3 :     if (pane_is_valid(&app->layout.dialogs)) {
     144            3 :         dialog_pane_render(&app->dialogs, &app->layout.dialogs,
     145              :                            &app->screen,
     146            3 :                            app->focus == TUI_FOCUS_DIALOGS);
     147              :     }
     148            3 :     if (pane_is_valid(&app->layout.history)) {
     149            3 :         history_pane_render(&app->history, &app->layout.history,
     150              :                             &app->screen);
     151              :     }
     152            3 :     if (pane_is_valid(&app->layout.status)) {
     153            3 :         status_row_render(&app->status, &app->layout.status,
     154              :                           &app->screen);
     155              :     }
     156              : }
        

Generated by: LCOV version 2.0-1