You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

569 lines
19KB

  1. /*
  2. * meli - ui crate.
  3. *
  4. * Copyright 2017-2018 Manos Pitsidianakis
  5. *
  6. * This file is part of meli.
  7. *
  8. * meli is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * meli is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with meli. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. /*! The application's state.
  22. The UI crate has an Box<Component>-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct.
  23. `State` owns all the Components of the UI. In the application's main event loop, input is handed to the state in the form of `UIEvent` objects which traverse the component graph. Components decide to handle each input or not.
  24. Input is received in the main loop from threads which listen on the stdin for user input, observe folders for file changes etc. The relevant struct is `ThreadEvent`.
  25. */
  26. use super::*;
  27. use melib::backends::{FolderHash, NotifyFn};
  28. use chan::{Receiver, Sender};
  29. use fnv::FnvHashMap;
  30. use std::io::Write;
  31. use std::result;
  32. use std::thread;
  33. use termion::raw::IntoRawMode;
  34. use termion::screen::AlternateScreen;
  35. use termion::{clear, cursor, style};
  36. type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
  37. struct InputHandler {
  38. rx: Receiver<bool>,
  39. tx: Sender<bool>,
  40. }
  41. impl InputHandler {
  42. fn restore(&self, tx: Sender<ThreadEvent>) {
  43. let stdin = std::io::stdin();
  44. let rx = self.rx.clone();
  45. thread::Builder::new()
  46. .name("input-thread".to_string())
  47. .spawn(move || {
  48. get_events(
  49. stdin,
  50. |k| {
  51. tx.send(ThreadEvent::Input(k));
  52. },
  53. || {
  54. tx.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(UIMode::Fork)));
  55. },
  56. &rx,
  57. )
  58. })
  59. .unwrap();
  60. }
  61. fn kill(&self) {
  62. self.tx.send(false);
  63. }
  64. }
  65. /// A context container for loaded settings, accounts, UI changes, etc.
  66. pub struct Context {
  67. pub accounts: Vec<Account>,
  68. pub mailbox_hashes: FnvHashMap<FolderHash, usize>,
  69. pub settings: Settings,
  70. pub runtime_settings: Settings,
  71. /// Areas of the screen that must be redrawn in the next render
  72. pub dirty_areas: VecDeque<Area>,
  73. /// Events queue that components send back to the state
  74. pub replies: VecDeque<UIEvent>,
  75. sender: Sender<ThreadEvent>,
  76. receiver: Receiver<ThreadEvent>,
  77. input: InputHandler,
  78. pub temp_files: Vec<File>,
  79. }
  80. impl Context {
  81. pub fn replies(&mut self) -> Vec<UIEvent> {
  82. self.replies.drain(0..).collect()
  83. }
  84. pub fn input_kill(&self) {
  85. self.input.kill();
  86. }
  87. pub fn restore_input(&self) {
  88. self.input.restore(self.sender.clone());
  89. }
  90. pub fn account_status(
  91. &mut self,
  92. idx_a: usize,
  93. folder_hash: FolderHash,
  94. ) -> result::Result<(), usize> {
  95. match self.accounts[idx_a].status(folder_hash) {
  96. Ok(()) => {
  97. self.replies
  98. .push_back(UIEvent::MailboxUpdate((idx_a, folder_hash)));
  99. Ok(())
  100. }
  101. Err(n) => Err(n),
  102. }
  103. }
  104. }
  105. /// A State object to manage and own components and components of the UI. `State` is responsible for
  106. /// managing the terminal and interfacing with `melib`
  107. pub struct State {
  108. cols: usize,
  109. rows: usize,
  110. grid: CellBuffer,
  111. stdout: Option<StateStdout>,
  112. child: Option<ForkType>,
  113. pub mode: UIMode,
  114. components: Vec<Box<Component>>,
  115. pub context: Context,
  116. threads: FnvHashMap<thread::ThreadId, (chan::Sender<bool>, thread::JoinHandle<()>)>,
  117. work_controller: WorkController,
  118. }
  119. impl Drop for State {
  120. fn drop(&mut self) {
  121. // When done, restore the defaults to avoid messing with the terminal.
  122. write!(
  123. self.stdout(),
  124. "{}{}{}{}{}",
  125. clear::All,
  126. style::Reset,
  127. cursor::Goto(1, 1),
  128. cursor::Show,
  129. BracketModeEnd,
  130. )
  131. .unwrap();
  132. self.flush();
  133. }
  134. }
  135. impl Default for State {
  136. fn default() -> Self {
  137. Self::new()
  138. }
  139. }
  140. impl State {
  141. pub fn new() -> Self {
  142. /* Create a channel to communicate with other threads. The main process is the sole receiver.
  143. * */
  144. let (sender, receiver) = chan::sync(32 * ::std::mem::size_of::<ThreadEvent>());
  145. /*
  146. * Create async channel to block the input-thread if we need to fork and stop it from reading
  147. * stdin, see get_events() for details
  148. * */
  149. let input_thread = chan::r#async();
  150. let _stdout = std::io::stdout();
  151. _stdout.lock();
  152. let backends = Backends::new();
  153. let settings = Settings::new();
  154. let stdout = AlternateScreen::from(_stdout.into_raw_mode().unwrap());
  155. let termsize = termion::terminal_size().ok();
  156. let termcols = termsize.map(|(w, _)| w);
  157. let termrows = termsize.map(|(_, h)| h);
  158. let cols = termcols.unwrap_or(0) as usize;
  159. let rows = termrows.unwrap_or(0) as usize;
  160. let mut accounts: Vec<Account> = settings
  161. .accounts
  162. .iter()
  163. .map(|(n, a_s)| {
  164. let sender = sender.clone();
  165. Account::new(
  166. n.to_string(),
  167. a_s.clone(),
  168. &backends,
  169. NotifyFn::new(Box::new(move |f: FolderHash| {
  170. sender.send(ThreadEvent::UIEvent(UIEvent::StartupCheck(f)))
  171. })),
  172. )
  173. })
  174. .collect();
  175. accounts.sort_by(|a, b| a.name().cmp(&b.name()));
  176. let mut s = State {
  177. cols,
  178. rows,
  179. grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
  180. stdout: Some(stdout),
  181. child: None,
  182. mode: UIMode::Normal,
  183. components: Vec::with_capacity(1),
  184. context: Context {
  185. accounts,
  186. mailbox_hashes: FnvHashMap::with_capacity_and_hasher(1, Default::default()),
  187. settings: settings.clone(),
  188. runtime_settings: settings,
  189. dirty_areas: VecDeque::with_capacity(5),
  190. replies: VecDeque::with_capacity(5),
  191. temp_files: Vec::new(),
  192. sender,
  193. receiver,
  194. input: InputHandler {
  195. rx: input_thread.1,
  196. tx: input_thread.0,
  197. },
  198. },
  199. threads: FnvHashMap::with_capacity_and_hasher(1, Default::default()),
  200. work_controller: WorkController::new(),
  201. };
  202. for a in s.context.accounts.iter_mut() {
  203. for worker in a.workers.values_mut() {
  204. if let Some(worker) = worker.as_mut() {
  205. if let Some(w) = worker.work() {
  206. s.work_controller.queue.add_work(w);
  207. }
  208. }
  209. }
  210. }
  211. write!(
  212. s.stdout(),
  213. "{}{}{}{}",
  214. BracketModeStart,
  215. cursor::Hide,
  216. clear::All,
  217. cursor::Goto(1, 1)
  218. )
  219. .unwrap();
  220. s.flush();
  221. debug!("inserting mailbox hashes:");
  222. for (x, account) in s.context.accounts.iter_mut().enumerate() {
  223. for folder in account.backend.folders().values() {
  224. debug!("hash & folder: {:?} {}", folder.hash(), folder.name());
  225. s.context.mailbox_hashes.insert(folder.hash(), x);
  226. }
  227. let sender = s.context.sender.clone();
  228. account.watch(RefreshEventConsumer::new(Box::new(move |r| {
  229. sender.send(ThreadEvent::from(r));
  230. })));
  231. }
  232. s.restore_input();
  233. s
  234. }
  235. pub fn worker_receiver(&mut self) -> chan::Receiver<bool> {
  236. self.work_controller.results_rx()
  237. }
  238. /*
  239. * When we receive a folder hash from a watcher thread,
  240. * we match the hash to the index of the mailbox, request a reload
  241. * and startup a thread to remind us to poll it every now and then till it's finished.
  242. */
  243. pub fn refresh_event(&mut self, event: RefreshEvent) {
  244. let hash = event.hash();
  245. if let Some(&idxa) = self.context.mailbox_hashes.get(&hash) {
  246. if self.context.accounts[idxa].status(hash).is_err() {
  247. self.context.replies.push_back(UIEvent::from(event));
  248. return;
  249. }
  250. if let Some(notification) = self.context.accounts[idxa].reload(event, hash) {
  251. self.context
  252. .sender
  253. .send(ThreadEvent::UIEvent(UIEvent::StartupCheck(hash)));
  254. self.context
  255. .sender
  256. .send(ThreadEvent::UIEvent(UIEvent::MailboxUpdate((idxa, hash))));
  257. self.context.replies.push_back(notification);
  258. }
  259. self.context
  260. .replies
  261. .push_back(UIEvent::MailboxUpdate((idxa, hash)));
  262. } else {
  263. debug!(
  264. "BUG: mailbox with hash {} not found in mailbox_hashes.",
  265. hash
  266. );
  267. }
  268. }
  269. /// If an owned thread returns a `ThreadEvent::ThreadJoin` event to `State` then it must remove
  270. /// the thread from its list and `join` it.
  271. pub fn join(&mut self, id: thread::ThreadId) {
  272. let (tx, handle) = self.threads.remove(&id).unwrap();
  273. tx.send(true);
  274. handle.join().unwrap();
  275. }
  276. /// Switch back to the terminal's main screen (The command line the user sees before opening
  277. /// the application)
  278. pub fn switch_to_main_screen(&mut self) {
  279. write!(
  280. self.stdout(),
  281. "{}{}",
  282. termion::screen::ToMainScreen,
  283. cursor::Show
  284. )
  285. .unwrap();
  286. self.flush();
  287. self.stdout = None;
  288. self.context.input.kill();
  289. }
  290. pub fn switch_to_alternate_screen(&mut self) {
  291. let s = std::io::stdout();
  292. s.lock();
  293. self.stdout = Some(AlternateScreen::from(s.into_raw_mode().unwrap()));
  294. write!(
  295. self.stdout(),
  296. "{}{}{}{}",
  297. termion::screen::ToAlternateScreen,
  298. cursor::Hide,
  299. clear::All,
  300. cursor::Goto(1, 1)
  301. )
  302. .unwrap();
  303. self.flush();
  304. }
  305. pub fn receiver(&self) -> Receiver<ThreadEvent> {
  306. self.context.receiver.clone()
  307. }
  308. pub fn restore_input(&mut self) {
  309. self.context.restore_input();
  310. }
  311. /// On `SIGWNICH` the `State` redraws itself according to the new terminal size.
  312. pub fn update_size(&mut self) {
  313. let termsize = termion::terminal_size().ok();
  314. let termcols = termsize.map(|(w, _)| w);
  315. let termrows = termsize.map(|(_, h)| h);
  316. if termcols.unwrap_or(72) as usize != self.cols
  317. || termrows.unwrap_or(120) as usize != self.rows
  318. {
  319. debug!(
  320. "Size updated, from ({}, {}) -> ({:?}, {:?})",
  321. self.cols, self.rows, termcols, termrows
  322. );
  323. }
  324. self.cols = termcols.unwrap_or(72) as usize;
  325. self.rows = termrows.unwrap_or(120) as usize;
  326. self.grid.resize(self.cols, self.rows, Cell::with_char(' '));
  327. self.rcv_event(UIEvent::Resize);
  328. // Invalidate dirty areas.
  329. self.context.dirty_areas.clear();
  330. }
  331. /// Force a redraw for all dirty components.
  332. pub fn redraw(&mut self) {
  333. for i in 0..self.components.len() {
  334. self.draw_component(i);
  335. }
  336. let mut areas: Vec<Area> = self.context.dirty_areas.drain(0..).collect();
  337. /* Sort by x_start, ie upper_left corner's x coordinate */
  338. areas.sort_by(|a, b| (a.0).0.partial_cmp(&(b.0).0).unwrap());
  339. /* draw each dirty area */
  340. let rows = self.rows;
  341. for y in 0..rows {
  342. let mut segment = None;
  343. for ((x_start, y_start), (x_end, y_end)) in &areas {
  344. if y < *y_start || y > *y_end {
  345. continue;
  346. }
  347. if let Some((x_start, x_end)) = segment.take() {
  348. self.draw_horizontal_segment(x_start, x_end, y);
  349. }
  350. match segment {
  351. ref mut s @ None => {
  352. *s = Some((*x_start, *x_end));
  353. }
  354. ref mut s @ Some(_) if s.unwrap().1 < *x_start => {
  355. self.draw_horizontal_segment(s.unwrap().0, s.unwrap().1, y);
  356. *s = Some((*x_start, *x_end));
  357. }
  358. ref mut s @ Some(_) if s.unwrap().1 < *x_end => {
  359. self.draw_horizontal_segment(s.unwrap().0, s.unwrap().1, y);
  360. *s = Some((s.unwrap().1, *x_end));
  361. }
  362. Some((_, ref mut x)) => {
  363. *x = *x_end;
  364. }
  365. }
  366. }
  367. if let Some((x_start, x_end)) = segment {
  368. self.draw_horizontal_segment(x_start, x_end, y);
  369. }
  370. }
  371. self.flush();
  372. }
  373. /// Draw only a specific `area` on the screen.
  374. fn draw_horizontal_segment(&mut self, x_start: usize, x_end: usize, y: usize) {
  375. write!(
  376. self.stdout(),
  377. "{}",
  378. cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
  379. )
  380. .unwrap();
  381. for x in x_start..=x_end {
  382. let c = self.grid[(x, y)];
  383. if c.bg() != Color::Default {
  384. write!(self.stdout(), "{}", termion::color::Bg(c.bg().as_termion())).unwrap();
  385. }
  386. if c.fg() != Color::Default {
  387. write!(self.stdout(), "{}", termion::color::Fg(c.fg().as_termion())).unwrap();
  388. }
  389. write!(self.stdout(), "{}", c.ch()).unwrap();
  390. if c.bg() != Color::Default {
  391. write!(
  392. self.stdout(),
  393. "{}",
  394. termion::color::Bg(termion::color::Reset)
  395. )
  396. .unwrap();
  397. }
  398. if c.fg() != Color::Default {
  399. write!(
  400. self.stdout(),
  401. "{}",
  402. termion::color::Fg(termion::color::Reset)
  403. )
  404. .unwrap();
  405. }
  406. }
  407. }
  408. /// Draw the entire screen from scratch.
  409. pub fn render(&mut self) {
  410. self.update_size();
  411. let cols = self.cols;
  412. let rows = self.rows;
  413. self.context
  414. .dirty_areas
  415. .push_back(((0, 0), (cols - 1, rows - 1)));
  416. self.redraw();
  417. }
  418. pub fn draw_component(&mut self, idx: usize) {
  419. let component = &mut self.components[idx];
  420. let upper_left = (0, 0);
  421. let bottom_right = (self.cols - 1, self.rows - 1);
  422. if component.is_dirty() {
  423. component.draw(
  424. &mut self.grid,
  425. (upper_left, bottom_right),
  426. &mut self.context,
  427. );
  428. }
  429. }
  430. pub fn register_component(&mut self, component: Box<Component>) {
  431. self.components.push(component);
  432. }
  433. /// Convert user commands to actions/method calls.
  434. fn parse_command(&mut self, cmd: &str) {
  435. let result = parse_command(&cmd.as_bytes()).to_full_result();
  436. if let Ok(v) = result {
  437. self.rcv_event(UIEvent::Action(v));
  438. }
  439. }
  440. /// The application's main loop sends `UIEvents` to state via this method.
  441. pub fn rcv_event(&mut self, mut event: UIEvent) {
  442. match event {
  443. // Command type is handled only by State.
  444. UIEvent::Command(cmd) => {
  445. self.parse_command(&cmd);
  446. return;
  447. }
  448. UIEvent::Fork(child) => {
  449. self.mode = UIMode::Fork;
  450. self.child = Some(child);
  451. if let Some(ForkType::Finished) = self.child {
  452. /*
  453. * Fork has finished in the past.
  454. * We're back in the AlternateScreen, but the cursor is reset to Shown, so fix
  455. * it.
  456. */
  457. write!(self.stdout(), "{}", cursor::Hide,).unwrap();
  458. self.flush();
  459. }
  460. return;
  461. }
  462. UIEvent::ChangeMode(m) => {
  463. self.context
  464. .sender
  465. .send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m)));
  466. }
  467. _ => {}
  468. }
  469. /* inform each component */
  470. for i in 0..self.components.len() {
  471. self.components[i].process_event(&mut event, &mut self.context);
  472. }
  473. if !self.context.replies.is_empty() {
  474. let replies: Vec<UIEvent> = self.context.replies.drain(0..).collect();
  475. // Pass replies to self and call count on the map iterator to force evaluation
  476. replies.into_iter().map(|r| self.rcv_event(r)).count();
  477. }
  478. }
  479. pub fn try_wait_on_child(&mut self) -> Option<bool> {
  480. let should_return_flag = match self.child {
  481. Some(ForkType::NewDraft(_, ref mut c)) => {
  482. let w = c.try_wait();
  483. match w {
  484. Ok(Some(_)) => true,
  485. Ok(None) => false,
  486. Err(_) => {
  487. return None;
  488. }
  489. }
  490. }
  491. Some(ForkType::Generic(ref mut c)) => {
  492. let w = c.try_wait();
  493. match w {
  494. Ok(Some(_)) => true,
  495. Ok(None) => false,
  496. Err(_) => {
  497. return None;
  498. }
  499. }
  500. }
  501. Some(ForkType::Finished) => {
  502. /* Fork has already finished */
  503. std::mem::replace(&mut self.child, None);
  504. return None;
  505. }
  506. _ => {
  507. return None;
  508. }
  509. };
  510. if should_return_flag {
  511. return Some(true);
  512. }
  513. Some(false)
  514. }
  515. fn flush(&mut self) {
  516. if let Some(s) = self.stdout.as_mut() {
  517. s.flush().unwrap();
  518. }
  519. }
  520. fn stdout(&mut self) -> &mut StateStdout {
  521. self.stdout.as_mut().unwrap()
  522. }
  523. }