leetcode_tui_rs/
app.rs

1use crate::utils::update_database_questions;
2use crate::{ctx::Ctx, executor::Executor, signals::Signals, widgets::root::Root};
3use color_eyre::Result;
4use leetcode_tui_config::{constants::EDITOR, key::Key};
5use leetcode_tui_core::{emit, Event, UBStrSender};
6use leetcode_tui_db::{DbQuestion, DbTopic};
7use leetcode_tui_shared::tui::Term;
8
9pub struct App {
10    cx: super::ctx::Ctx,
11    term: Option<Term>,
12    signals: Signals,
13}
14
15impl App {
16    pub async fn run() -> Result<()> {
17        let term = Term::start()?;
18        let signals = Signals::start()?;
19        let mut app = Self {
20            cx: Ctx::new().await,
21            term: Some(term),
22            signals,
23        };
24        emit!(Render);
25        while let Some(event) = app.signals.recv().await {
26            match event {
27                Event::Quit => {
28                    // app.dispatch_quit();
29                    break;
30                }
31                Event::Input(sender, default_input) => app.dispatch_input(sender, default_input),
32                Event::Key(key) => app.dispatch_key(key),
33                Event::Render(_) => app.dispatch_render(),
34                Event::Topic(topic) => app.dispatch_topic_update(topic),
35                Event::AddQuestions(qs) => app.dispatch_add_question(qs),
36                Event::AdhocQuestion(qs) => app.dispatch_adhoc_question(qs),
37                Event::Questions(qs) => app.dispatch_question_update(qs),
38                Event::Popup(title, lines) => app.dispatch_popup(title, lines),
39                Event::SelectPopup(maybe_title, lines, result_sender) => {
40                    app.dispatch_select_popup(maybe_title, lines, result_sender)
41                }
42                Event::Error(e) => app.dispatch_popup(Some("Error".into()), vec![e]),
43                Event::Open(file_path) => app.dispatch_opener(file_path),
44                Event::SyncDb => app.dispatch_db_update().await,
45                Event::ProgressUpdate(title, progress, total) => {
46                    app.dispatch_progress_update(title, progress, total)
47                }
48                e => app.dispatch_module_event(e),
49                // Event::Paste(str) => app.dispatch_paste(str),
50                // Event::Resize(..) => app.dispatch_resize(),
51                // Event::Stop(state, tx) => app.dispatch_stop(state, tx),
52                // Event::Call(exec, layer) => app.dispatch_call(exec, layer),
53                // event => app.dispatch_module(event),
54            }
55        }
56        Ok(())
57    }
58
59    fn dispatch_key(&mut self, key: impl Into<Key>) {
60        if Executor::handle(&mut self.cx, key.into()) {
61            emit!(Render);
62        }
63    }
64
65    fn dispatch_topic_update(&mut self, topic: DbTopic) {
66        let questions = topic.fetch_questions().unwrap();
67        self.cx.content.get_topic_mut().set_topic(&topic);
68        self.dispatch_question_update(questions);
69    }
70
71    fn dispatch_question_update(&mut self, questions: Vec<DbQuestion>) {
72        self.cx.content.get_questions_mut().set_questions(questions);
73        emit!(Render);
74    }
75
76    fn dispatch_add_question(&mut self, questions: Vec<DbQuestion>) {
77        for ques in questions {
78            self.cx.content.get_questions_mut().add_question(ques);
79        }
80    }
81
82    fn dispatch_render(&mut self) {
83        if let Some(term) = &mut self.term {
84            let _ = term.draw(|f| {
85                f.render_widget(Root::new(&mut self.cx), f.area());
86            });
87        }
88    }
89
90    fn dispatch_popup(&mut self, title: Option<String>, lines: Vec<String>) {
91        self.cx.popup.reset(title, lines);
92        self.cx.popup.toggle();
93        emit!(Render);
94    }
95
96    fn dispatch_select_popup(
97        &mut self,
98        maybe_title: Option<String>,
99        lines: Vec<String>,
100        sender: tokio::sync::oneshot::Sender<Option<usize>>,
101    ) {
102        self.cx.select_popup.with_items(maybe_title, lines, sender);
103        self.cx.select_popup.toggle();
104        emit!(Render);
105    }
106
107    fn dispatch_opener(&mut self, file_path: std::path::PathBuf) {
108        // TODO: unwraps handling
109        self.signals.stop_looking_for_io_events();
110        if let Some(term) = &mut self.term {
111            term.suspend().unwrap();
112            let editor = EDITOR.get().expect("editor not set");
113            std::process::Command::new("sh")
114                .arg("-c")
115                .arg(&format!(
116                    r#"{} "{}""#,
117                    editor,
118                    file_path.as_os_str().to_str().unwrap()
119                ))
120                .spawn()
121                .unwrap()
122                .wait()
123                .unwrap();
124            term.resume().unwrap();
125            emit!(Render);
126        }
127        self.signals.start_looking_for_io_events();
128    }
129
130    fn dispatch_module_event(&mut self, e: Event) {
131        match e {
132            Event::QuestionFilter(needle) => self.cx.content.get_questions_mut().filter_by(needle),
133            Event::QuestionUpdate => self.cx.content.get_topic().notify_change(),
134            _ => (),
135        }
136        emit!(Render);
137    }
138
139    fn dispatch_input(&mut self, sender: UBStrSender, default_input: Option<String>) {
140        self.cx.input.toggle();
141        self.cx.input.reset_with(sender, default_input);
142        emit!(Render);
143    }
144
145    fn dispatch_adhoc_question(&mut self, qs: DbQuestion) {
146        self.cx.content.get_questions_mut().set_adhoc(qs);
147        emit!(Render);
148    }
149
150    async fn dispatch_db_update(&mut self) {
151        tokio::spawn(async move {
152            update_database_questions(true).await.unwrap();
153            emit!(Topic(DbTopic {
154                slug: "all".to_string()
155            }));
156            emit!(Render);
157        });
158    }
159
160    fn dispatch_progress_update(&mut self, title: String, progress: u32, total: u32) {
161        self.cx.progress.set_progress(title, progress, total);
162        emit!(Render);
163    }
164}