loghawk/
app.rs

1use std::error;
2
3use getset::{Getters, Setters};
4use ratatui::{layout::Rect, Frame};
5
6use crate::{
7    cli::{Cli, FileFormat},
8    csv_data::CsvData,
9    log_view::LogView,
10    LogData, LogViewState, TxtData, ViewPort,
11};
12
13/// Application result type.
14pub type AppResult<T> = std::result::Result<T, Box<dyn error::Error>>;
15
16/// Application.
17#[derive(Debug, Getters, Setters)]
18#[getset(get = "pub", set = "pub")]
19pub struct App {
20    /// Is the application running?
21    running: bool,
22
23    cli: Cli,
24
25    data: Box<dyn LogData>,
26
27    viewstate: LogViewState,
28
29    page_size: u16,
30}
31
32impl App {
33    /// Constructs a new instance of [`App`].
34    pub fn new(cli: Cli) -> anyhow::Result<Self> {
35        let data: Box<dyn LogData> = match cli.file_format() {
36            FileFormat::Csv => Box::new(CsvData::try_from(cli.file().path())?),
37            FileFormat::Txt => Box::new(TxtData::load_from(cli.file().path(), *cli.delimiter())?),
38        };
39
40        let viewstate = LogViewState::default();
41        Ok(Self {
42            running: true,
43            cli,
44            data,
45            viewstate,
46            page_size: 1,
47        })
48    }
49
50    /// Handles the tick event of the terminal.
51    pub fn tick(&self) {}
52
53    /// Set running to false to quit the application.
54    pub fn quit(&mut self) {
55        self.running = false;
56    }
57
58    pub fn forward(&mut self, steps: usize) {
59        if !self.data().is_empty() {
60            self.viewstate.set_vscroll_offset(usize::min(
61                *self.viewstate().vscroll_offset() + steps,
62                self.data().len() - 1,
63            ));
64        }
65    }
66
67    pub fn backward(&mut self, steps: usize) {
68        self.viewstate
69            .set_vscroll_offset(usize::max(*self.viewstate.vscroll_offset(), steps) - steps);
70    }
71
72    pub fn begin(&mut self) {
73        self.viewstate.set_vscroll_offset(0);
74    }
75
76    pub fn end(&mut self) {
77        if !self.data().is_empty() {
78            self.viewstate.set_vscroll_offset(self.data().len() - 1);
79        }
80    }
81
82    pub fn right(&mut self, steps: usize) {
83        let viewport = ViewPort::new(self.viewstate.hscroll_offset() + steps, 0, 44, 55);
84        let width: usize = self.data.data_widths(&viewport).sum();
85        if width > 0 {
86            self.viewstate
87                .set_hscroll_offset(self.viewstate.hscroll_offset() + steps);
88        }
89    }
90
91    pub fn left(&mut self, steps: usize) {
92        if *self.viewstate.hscroll_offset() >= steps {
93            self.viewstate
94                .set_hscroll_offset(self.viewstate.hscroll_offset() - steps);
95        } else {
96            self.viewstate.set_hscroll_offset(0);
97        }
98    }
99
100    pub fn render_log_contents(&mut self, frame: &mut Frame, area: Rect) {
101        let mut viewstate = *self.csv_viewstate();
102        frame.render_stateful_widget(
103            LogView::from(self.data.as_ref()).with_mask_unicode(*self.cli.mask_unicode()),
104            area,
105            &mut viewstate,
106        );
107        self.viewstate = viewstate;
108    }
109
110    pub fn csv_viewstate(&self) -> &LogViewState {
111        &self.viewstate
112    }
113}