2018-08-07 15:01:15 +03:00
|
|
|
/*
|
2020-02-04 15:52:12 +02:00
|
|
|
* meli
|
2018-08-07 15:01:15 +03:00
|
|
|
*
|
|
|
|
* Copyright 2017-2018 Manos Pitsidianakis
|
|
|
|
*
|
|
|
|
* This file is part of meli.
|
|
|
|
*
|
|
|
|
* meli is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* meli is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2018-08-06 22:20:34 +03:00
|
|
|
/*! The application's state.
|
|
|
|
|
2019-09-09 11:54:47 +03:00
|
|
|
The UI crate has an Box<dyn Component>-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct.
|
2018-08-06 16:53:23 +03:00
|
|
|
|
2019-04-10 22:01:02 +03:00
|
|
|
`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.
|
2018-08-06 16:53:23 +03:00
|
|
|
|
2019-03-14 12:19:25 +02:00
|
|
|
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`.
|
|
|
|
*/
|
2018-08-06 16:53:23 +03:00
|
|
|
|
|
|
|
use super::*;
|
2019-12-27 15:20:02 +02:00
|
|
|
use crate::plugins::PluginManager;
|
2018-09-07 15:36:42 +03:00
|
|
|
use melib::backends::{FolderHash, NotifyFn};
|
2018-09-05 16:08:11 +03:00
|
|
|
|
2020-01-08 17:07:14 +02:00
|
|
|
use crossbeam::channel::{unbounded, Receiver, Sender};
|
2018-08-06 16:53:23 +03:00
|
|
|
use fnv::FnvHashMap;
|
2020-02-06 21:55:04 +02:00
|
|
|
use smallvec::SmallVec;
|
2019-06-26 18:56:29 +03:00
|
|
|
use std::env;
|
2018-08-06 16:53:23 +03:00
|
|
|
use std::io::Write;
|
2018-08-16 21:20:53 +03:00
|
|
|
use std::result;
|
2018-08-23 15:36:52 +03:00
|
|
|
use std::thread;
|
2018-08-07 15:01:15 +03:00
|
|
|
use termion::raw::IntoRawMode;
|
|
|
|
use termion::screen::AlternateScreen;
|
2019-10-15 22:55:53 +03:00
|
|
|
use termion::{clear, cursor};
|
2018-08-06 16:53:23 +03:00
|
|
|
|
2019-10-06 11:30:35 +03:00
|
|
|
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
|
2018-09-04 01:49:29 +03:00
|
|
|
|
2018-08-16 16:32:47 +03:00
|
|
|
struct InputHandler {
|
2019-11-05 08:32:27 +02:00
|
|
|
rx: Receiver<InputCommand>,
|
|
|
|
tx: Sender<InputCommand>,
|
2018-08-16 16:32:47 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl InputHandler {
|
|
|
|
fn restore(&self, tx: Sender<ThreadEvent>) {
|
|
|
|
let rx = self.rx.clone();
|
|
|
|
thread::Builder::new()
|
|
|
|
.name("input-thread".to_string())
|
|
|
|
.spawn(move || {
|
|
|
|
get_events(
|
|
|
|
|k| {
|
2019-09-09 12:53:39 +03:00
|
|
|
tx.send(ThreadEvent::Input(k)).unwrap();
|
2018-08-16 16:32:47 +03:00
|
|
|
},
|
2019-11-05 08:32:27 +02:00
|
|
|
|i| {
|
|
|
|
tx.send(ThreadEvent::InputRaw(i)).unwrap();
|
2018-08-16 16:32:47 +03:00
|
|
|
},
|
|
|
|
&rx,
|
2018-08-23 15:36:52 +03:00
|
|
|
)
|
2019-03-14 12:19:25 +02:00
|
|
|
})
|
|
|
|
.unwrap();
|
2018-08-16 16:32:47 +03:00
|
|
|
}
|
2019-11-05 08:32:27 +02:00
|
|
|
|
2018-08-16 16:32:47 +03:00
|
|
|
fn kill(&self) {
|
2019-11-05 08:32:27 +02:00
|
|
|
self.tx.send(InputCommand::Kill).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn switch_to_raw(&self) {
|
|
|
|
self.tx.send(InputCommand::Raw).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn switch_from_raw(&self) {
|
|
|
|
self.tx.send(InputCommand::NoRaw).unwrap();
|
2018-08-16 16:32:47 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-06 16:53:23 +03:00
|
|
|
/// A context container for loaded settings, accounts, UI changes, etc.
|
|
|
|
pub struct Context {
|
|
|
|
pub accounts: Vec<Account>,
|
2019-04-14 17:24:01 +03:00
|
|
|
pub mailbox_hashes: FnvHashMap<FolderHash, usize>,
|
2018-08-06 16:53:23 +03:00
|
|
|
pub settings: Settings,
|
|
|
|
|
|
|
|
pub runtime_settings: Settings,
|
|
|
|
/// Areas of the screen that must be redrawn in the next render
|
|
|
|
pub dirty_areas: VecDeque<Area>,
|
|
|
|
|
|
|
|
/// Events queue that components send back to the state
|
|
|
|
pub replies: VecDeque<UIEvent>,
|
2018-08-16 16:32:47 +03:00
|
|
|
sender: Sender<ThreadEvent>,
|
|
|
|
receiver: Receiver<ThreadEvent>,
|
|
|
|
input: InputHandler,
|
2019-09-11 17:57:55 +03:00
|
|
|
work_controller: WorkController,
|
2019-12-27 15:20:02 +02:00
|
|
|
pub plugin_manager: PluginManager,
|
2018-08-06 16:53:23 +03:00
|
|
|
|
2018-08-08 20:58:15 +03:00
|
|
|
pub temp_files: Vec<File>,
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Context {
|
2020-01-08 17:04:44 +02:00
|
|
|
pub fn replies(&mut self) -> smallvec::SmallVec<[UIEvent; 8]> {
|
2018-08-06 16:53:23 +03:00
|
|
|
self.replies.drain(0..).collect()
|
|
|
|
}
|
2019-11-05 08:32:27 +02:00
|
|
|
|
2018-08-16 16:32:47 +03:00
|
|
|
pub fn input_kill(&self) {
|
|
|
|
self.input.kill();
|
|
|
|
}
|
2019-11-05 08:32:27 +02:00
|
|
|
|
|
|
|
pub fn input_from_raw(&self) {
|
|
|
|
self.input.switch_from_raw();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn input_to_raw(&self) {
|
|
|
|
self.input.switch_to_raw();
|
|
|
|
}
|
|
|
|
|
2018-08-16 16:32:47 +03:00
|
|
|
pub fn restore_input(&self) {
|
|
|
|
self.input.restore(self.sender.clone());
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
2019-04-14 17:24:01 +03:00
|
|
|
pub fn account_status(
|
|
|
|
&mut self,
|
|
|
|
idx_a: usize,
|
|
|
|
folder_hash: FolderHash,
|
|
|
|
) -> result::Result<(), usize> {
|
|
|
|
match self.accounts[idx_a].status(folder_hash) {
|
2018-09-17 07:51:21 +03:00
|
|
|
Ok(()) => {
|
2019-04-10 23:37:20 +03:00
|
|
|
self.replies
|
2019-04-14 17:24:01 +03:00
|
|
|
.push_back(UIEvent::MailboxUpdate((idx_a, folder_hash)));
|
2018-10-14 19:49:16 +03:00
|
|
|
Ok(())
|
2018-09-17 07:51:21 +03:00
|
|
|
}
|
2018-09-05 16:08:11 +03:00
|
|
|
Err(n) => Err(n),
|
2018-08-16 21:20:53 +03:00
|
|
|
}
|
|
|
|
}
|
2019-09-11 17:57:55 +03:00
|
|
|
|
2019-12-14 18:46:12 +02:00
|
|
|
pub fn is_online(&mut self, account_pos: usize) -> Result<()> {
|
2019-11-06 15:19:56 +02:00
|
|
|
let Context {
|
|
|
|
ref mut accounts,
|
|
|
|
ref mut mailbox_hashes,
|
2020-02-08 23:42:31 +02:00
|
|
|
ref mut replies,
|
2019-11-06 15:19:56 +02:00
|
|
|
..
|
|
|
|
} = self;
|
2019-11-07 19:12:06 +02:00
|
|
|
let was_online = accounts[account_pos].is_online;
|
2019-12-14 18:46:12 +02:00
|
|
|
let ret = accounts[account_pos].is_online();
|
|
|
|
if ret.is_ok() {
|
2019-11-07 19:12:06 +02:00
|
|
|
if !was_online {
|
2020-02-08 23:42:31 +02:00
|
|
|
for folder_node in accounts[account_pos].list_folders() {
|
|
|
|
debug!(
|
|
|
|
"hash & folder: {:?} {}",
|
|
|
|
folder_node.hash,
|
|
|
|
accounts[account_pos].ref_folders()[&folder_node.hash].name()
|
|
|
|
);
|
|
|
|
mailbox_hashes.insert(folder_node.hash, account_pos);
|
2019-11-07 19:12:06 +02:00
|
|
|
}
|
|
|
|
/* Account::watch() needs
|
|
|
|
* - work_controller to pass `work_context` to the watcher threads and then add them
|
|
|
|
* to the controller's static thread list,
|
|
|
|
* - sender to pass a RefreshEventConsumer closure to watcher threads for them to
|
|
|
|
* inform the main binary that refresh events arrived
|
|
|
|
* - replies to report any failures to the user
|
|
|
|
*/
|
2019-12-12 01:01:11 +02:00
|
|
|
accounts[account_pos].watch();
|
2020-02-08 23:42:31 +02:00
|
|
|
|
|
|
|
replies.push_back(UIEvent::AccountStatusChange(account_pos));
|
2019-11-06 15:19:56 +02:00
|
|
|
}
|
|
|
|
}
|
2019-12-14 18:46:12 +02:00
|
|
|
ret
|
2019-11-06 15:19:56 +02:00
|
|
|
}
|
|
|
|
|
2019-09-11 17:57:55 +03:00
|
|
|
pub fn work_controller(&self) -> &WorkController {
|
|
|
|
&self.work_controller
|
|
|
|
}
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
|
2019-04-10 22:01:02 +03:00
|
|
|
/// A State object to manage and own components and components of the UI. `State` is responsible for
|
2018-08-06 16:53:23 +03:00
|
|
|
/// managing the terminal and interfacing with `melib`
|
2018-09-04 01:49:29 +03:00
|
|
|
pub struct State {
|
2018-08-06 16:53:23 +03:00
|
|
|
cols: usize,
|
|
|
|
rows: usize,
|
|
|
|
|
|
|
|
grid: CellBuffer,
|
2020-02-06 21:54:07 +02:00
|
|
|
overlay_grid: CellBuffer,
|
2020-01-15 12:36:31 +02:00
|
|
|
draw_rate_limit: RateLimit,
|
2018-09-04 01:49:29 +03:00
|
|
|
stdout: Option<StateStdout>,
|
2018-08-06 16:53:23 +03:00
|
|
|
child: Option<ForkType>,
|
2020-02-06 21:54:07 +02:00
|
|
|
draw_horizontal_segment_fn: fn(&mut CellBuffer, &mut StateStdout, usize, usize, usize) -> (),
|
2018-08-06 16:53:23 +03:00
|
|
|
pub mode: UIMode,
|
2019-09-09 11:54:47 +03:00
|
|
|
components: Vec<Box<dyn Component>>,
|
2018-08-06 16:53:23 +03:00
|
|
|
pub context: Context,
|
2019-11-23 18:00:00 +02:00
|
|
|
timer: thread::JoinHandle<()>,
|
2020-02-06 21:55:04 +02:00
|
|
|
|
|
|
|
display_messages: SmallVec<[DisplayMessage; 8]>,
|
|
|
|
display_messages_expiration_start: Option<UnixTimestamp>,
|
|
|
|
display_messages_active: bool,
|
|
|
|
display_messages_pos: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
struct DisplayMessage {
|
|
|
|
timestamp: UnixTimestamp,
|
|
|
|
msg: String,
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
|
2018-09-04 01:49:29 +03:00
|
|
|
impl Drop for State {
|
2018-08-06 16:53:23 +03:00
|
|
|
fn drop(&mut self) {
|
|
|
|
// When done, restore the defaults to avoid messing with the terminal.
|
2019-10-15 22:55:53 +03:00
|
|
|
self.switch_to_main_screen();
|
2019-11-19 22:47:34 +02:00
|
|
|
if let Some(ForkType::Embed(child_pid)) = self.child.take() {
|
|
|
|
use nix::sys::wait::{waitpid, WaitPidFlag};
|
|
|
|
/* Try wait, we don't want to block */
|
|
|
|
if let Err(e) = waitpid(child_pid, Some(WaitPidFlag::WNOHANG)) {
|
|
|
|
eprintln!("Failed to wait on subprocess {}: {}", child_pid, e);
|
|
|
|
}
|
|
|
|
}
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-04 01:49:29 +03:00
|
|
|
impl State {
|
2020-01-08 17:07:14 +02:00
|
|
|
pub fn new(sender: Sender<ThreadEvent>, receiver: Receiver<ThreadEvent>) -> Result<Self> {
|
2018-08-16 16:32:47 +03:00
|
|
|
/*
|
|
|
|
* Create async channel to block the input-thread if we need to fork and stop it from reading
|
|
|
|
* stdin, see get_events() for details
|
|
|
|
* */
|
2019-09-09 12:53:39 +03:00
|
|
|
let input_thread = unbounded();
|
2019-12-27 15:20:02 +02:00
|
|
|
let mut backends = Backends::new();
|
2019-11-16 00:33:22 +02:00
|
|
|
let settings = Settings::new()?;
|
2019-12-23 17:08:57 +02:00
|
|
|
let mut plugin_manager = PluginManager::new();
|
|
|
|
for (_, p) in settings.plugins.clone() {
|
2019-12-27 15:20:02 +02:00
|
|
|
if crate::plugins::PluginKind::Backend == p.kind() {
|
|
|
|
debug!("registering {:?}", &p);
|
|
|
|
crate::plugins::backend::PluginBackend::register(
|
|
|
|
plugin_manager.listener(),
|
|
|
|
p.clone(),
|
|
|
|
&mut backends,
|
|
|
|
);
|
|
|
|
}
|
2019-12-23 17:08:57 +02:00
|
|
|
plugin_manager.register(p)?;
|
|
|
|
}
|
2018-08-06 16:53:23 +03:00
|
|
|
|
2019-11-16 00:33:22 +02:00
|
|
|
let termsize = termion::terminal_size()?;
|
|
|
|
let cols = termsize.0 as usize;
|
|
|
|
let rows = termsize.1 as usize;
|
2019-10-24 20:30:17 +03:00
|
|
|
|
|
|
|
let work_controller = WorkController::new(sender.clone());
|
2018-08-06 16:53:23 +03:00
|
|
|
let mut accounts: Vec<Account> = settings
|
|
|
|
.accounts
|
|
|
|
.iter()
|
2019-09-20 09:10:33 +03:00
|
|
|
.enumerate()
|
|
|
|
.map(|(index, (n, a_s))| {
|
2018-09-07 15:36:42 +03:00
|
|
|
let sender = sender.clone();
|
|
|
|
Account::new(
|
2019-09-20 09:10:33 +03:00
|
|
|
index,
|
2018-09-07 15:36:42 +03:00
|
|
|
n.to_string(),
|
|
|
|
a_s.clone(),
|
|
|
|
&backends,
|
2019-10-24 20:30:17 +03:00
|
|
|
work_controller.get_context(),
|
2019-12-12 01:01:11 +02:00
|
|
|
sender.clone(),
|
2019-03-30 13:41:32 +02:00
|
|
|
NotifyFn::new(Box::new(move |f: FolderHash| {
|
2019-09-09 12:53:39 +03:00
|
|
|
sender
|
2019-12-14 19:56:43 +02:00
|
|
|
.send(ThreadEvent::UIEvent(UIEvent::WorkerProgress(f)))
|
2019-09-09 12:53:39 +03:00
|
|
|
.unwrap();
|
2018-09-07 15:36:42 +03:00
|
|
|
})),
|
|
|
|
)
|
2019-03-14 12:19:25 +02:00
|
|
|
})
|
2019-11-16 00:33:22 +02:00
|
|
|
.collect::<Result<Vec<Account>>>()?;
|
2018-08-06 16:53:23 +03:00
|
|
|
accounts.sort_by(|a, b| a.name().cmp(&b.name()));
|
2019-08-03 23:55:32 +03:00
|
|
|
|
2019-11-23 18:00:00 +02:00
|
|
|
let timer = {
|
|
|
|
let sender = sender.clone();
|
|
|
|
thread::Builder::new().spawn(move || {
|
|
|
|
let sender = sender;
|
|
|
|
loop {
|
|
|
|
thread::park();
|
|
|
|
|
|
|
|
sender.send(ThreadEvent::Pulse).unwrap();
|
|
|
|
thread::sleep(std::time::Duration::from_millis(100));
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}?;
|
|
|
|
|
|
|
|
timer.thread().unpark();
|
|
|
|
|
2018-08-06 16:53:23 +03:00
|
|
|
let mut s = State {
|
2018-08-07 15:01:15 +03:00
|
|
|
cols,
|
|
|
|
rows,
|
2018-08-06 16:53:23 +03:00
|
|
|
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
|
2020-02-06 21:54:07 +02:00
|
|
|
overlay_grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
|
2019-10-15 22:55:53 +03:00
|
|
|
stdout: None,
|
2018-08-06 16:53:23 +03:00
|
|
|
child: None,
|
|
|
|
mode: UIMode::Normal,
|
2019-04-10 22:01:02 +03:00
|
|
|
components: Vec::with_capacity(1),
|
2019-11-23 18:00:00 +02:00
|
|
|
timer,
|
2020-01-15 12:36:31 +02:00
|
|
|
draw_rate_limit: RateLimit::new(1, 3),
|
2020-01-27 20:17:46 +02:00
|
|
|
draw_horizontal_segment_fn: if env::var("NO_COLOR").is_ok()
|
|
|
|
&& (settings.terminal.use_color.is_false()
|
|
|
|
|| settings.terminal.use_color.is_internal())
|
|
|
|
{
|
|
|
|
State::draw_horizontal_segment_no_color
|
|
|
|
} else {
|
|
|
|
State::draw_horizontal_segment
|
|
|
|
},
|
2020-02-06 21:55:04 +02:00
|
|
|
display_messages: SmallVec::new(),
|
|
|
|
display_messages_expiration_start: None,
|
|
|
|
display_messages_pos: 0,
|
|
|
|
display_messages_active: false,
|
2018-08-06 16:53:23 +03:00
|
|
|
|
|
|
|
context: Context {
|
2018-08-07 15:01:15 +03:00
|
|
|
accounts,
|
2018-08-08 10:41:25 +03:00
|
|
|
mailbox_hashes: FnvHashMap::with_capacity_and_hasher(1, Default::default()),
|
|
|
|
|
2018-08-06 16:53:23 +03:00
|
|
|
settings: settings.clone(),
|
|
|
|
runtime_settings: settings,
|
|
|
|
dirty_areas: VecDeque::with_capacity(5),
|
|
|
|
replies: VecDeque::with_capacity(5),
|
2018-08-08 20:58:15 +03:00
|
|
|
temp_files: Vec::new(),
|
2019-10-24 20:30:17 +03:00
|
|
|
work_controller,
|
2019-12-23 17:08:57 +02:00
|
|
|
plugin_manager,
|
2018-08-06 16:53:23 +03:00
|
|
|
|
2018-08-16 16:32:47 +03:00
|
|
|
sender,
|
|
|
|
receiver,
|
|
|
|
input: InputHandler {
|
|
|
|
rx: input_thread.1,
|
|
|
|
tx: input_thread.0,
|
|
|
|
},
|
2018-08-06 16:53:23 +03:00
|
|
|
},
|
|
|
|
};
|
2020-01-15 12:36:31 +02:00
|
|
|
s.draw_rate_limit
|
|
|
|
.timer
|
|
|
|
.set_value(std::time::Duration::from_millis(3));
|
2019-10-06 11:28:12 +03:00
|
|
|
if s.context.settings.terminal.ascii_drawing {
|
|
|
|
s.grid.set_ascii_drawing(true);
|
2020-02-06 21:54:07 +02:00
|
|
|
s.overlay_grid.set_ascii_drawing(true);
|
2019-10-06 11:28:12 +03:00
|
|
|
}
|
|
|
|
|
2019-10-15 22:55:53 +03:00
|
|
|
s.switch_to_alternate_screen();
|
2019-05-01 19:20:33 +03:00
|
|
|
debug!("inserting mailbox hashes:");
|
2019-11-06 15:19:56 +02:00
|
|
|
for i in 0..s.context.accounts.len() {
|
2019-12-14 18:46:12 +02:00
|
|
|
if s.context.is_online(i).is_ok() && s.context.accounts[i].is_empty() {
|
2019-11-16 00:33:22 +02:00
|
|
|
return Err(MeliError::new(format!(
|
|
|
|
"Account {} has no folders configured.",
|
|
|
|
s.context.accounts[i].name()
|
|
|
|
)));
|
|
|
|
}
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
2019-10-15 22:55:53 +03:00
|
|
|
s.context.restore_input();
|
2019-11-16 00:33:22 +02:00
|
|
|
Ok(s)
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
2018-10-14 19:49:16 +03:00
|
|
|
|
2018-08-11 19:19:30 +03:00
|
|
|
/*
|
|
|
|
* When we receive a folder hash from a watcher thread,
|
|
|
|
* we match the hash to the index of the mailbox, request a reload
|
|
|
|
* and startup a thread to remind us to poll it every now and then till it's finished.
|
|
|
|
*/
|
2018-09-05 16:08:11 +03:00
|
|
|
pub fn refresh_event(&mut self, event: RefreshEvent) {
|
|
|
|
let hash = event.hash();
|
2019-04-14 17:24:01 +03:00
|
|
|
if let Some(&idxa) = self.context.mailbox_hashes.get(&hash) {
|
|
|
|
if self.context.accounts[idxa].status(hash).is_err() {
|
2018-09-15 20:09:41 +03:00
|
|
|
self.context.replies.push_back(UIEvent::from(event));
|
|
|
|
return;
|
|
|
|
}
|
2019-09-11 17:57:55 +03:00
|
|
|
let Context {
|
2019-12-12 01:01:11 +02:00
|
|
|
ref mut accounts, ..
|
2019-09-11 17:57:55 +03:00
|
|
|
} = &mut self.context;
|
|
|
|
|
2019-12-12 01:01:11 +02:00
|
|
|
if let Some(notification) = accounts[idxa].reload(event, hash) {
|
2019-09-15 23:35:30 +03:00
|
|
|
if let UIEvent::Notification(_, _, _) = notification {
|
2019-09-27 13:29:42 +03:00
|
|
|
self.rcv_event(UIEvent::MailboxUpdate((idxa, hash)));
|
2019-07-28 18:52:45 +03:00
|
|
|
}
|
2019-07-04 15:31:12 +03:00
|
|
|
self.rcv_event(notification);
|
2018-09-05 16:08:11 +03:00
|
|
|
}
|
2018-08-16 21:20:53 +03:00
|
|
|
} else {
|
2019-12-12 01:01:11 +02:00
|
|
|
if let melib::backends::RefreshEventKind::Failure(err) = event.kind() {
|
|
|
|
debug!(err);
|
2019-11-23 17:50:22 +02:00
|
|
|
}
|
2018-08-16 21:20:53 +03:00
|
|
|
}
|
2018-08-08 10:41:25 +03:00
|
|
|
}
|
2018-08-06 16:53:23 +03:00
|
|
|
|
2019-12-12 01:01:11 +02:00
|
|
|
pub fn new_thread(&mut self, id: thread::ThreadId, name: String) {
|
|
|
|
self.context
|
|
|
|
.work_controller
|
|
|
|
.static_threads
|
|
|
|
.lock()
|
|
|
|
.unwrap()
|
|
|
|
.insert(id, name.into());
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
|
2018-08-06 22:20:34 +03:00
|
|
|
/// Switch back to the terminal's main screen (The command line the user sees before opening
|
|
|
|
/// the application)
|
2018-08-07 15:01:15 +03:00
|
|
|
pub fn switch_to_main_screen(&mut self) {
|
2018-08-06 16:53:23 +03:00
|
|
|
write!(
|
|
|
|
self.stdout(),
|
2019-10-15 22:55:53 +03:00
|
|
|
"{}{}{}{}",
|
2018-08-06 16:53:23 +03:00
|
|
|
termion::screen::ToMainScreen,
|
2019-10-15 22:55:53 +03:00
|
|
|
cursor::Show,
|
|
|
|
RestoreWindowTitleIconFromStack,
|
|
|
|
BracketModeEnd,
|
2019-03-14 12:19:25 +02:00
|
|
|
)
|
|
|
|
.unwrap();
|
2018-08-06 16:53:23 +03:00
|
|
|
self.flush();
|
|
|
|
self.stdout = None;
|
2018-08-16 16:32:47 +03:00
|
|
|
self.context.input.kill();
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
2019-10-15 22:55:53 +03:00
|
|
|
|
2018-08-07 15:01:15 +03:00
|
|
|
pub fn switch_to_alternate_screen(&mut self) {
|
2018-08-06 16:53:23 +03:00
|
|
|
let s = std::io::stdout();
|
2019-10-15 22:55:53 +03:00
|
|
|
|
|
|
|
let mut stdout = AlternateScreen::from(s.into_raw_mode().unwrap());
|
2018-08-06 16:53:23 +03:00
|
|
|
|
|
|
|
write!(
|
2019-10-15 22:55:53 +03:00
|
|
|
&mut stdout,
|
|
|
|
"{save_title_to_stack}{}{}{}{window_title}{}{}",
|
2018-08-06 16:53:23 +03:00
|
|
|
termion::screen::ToAlternateScreen,
|
2018-08-30 14:48:18 +03:00
|
|
|
cursor::Hide,
|
|
|
|
clear::All,
|
2019-10-15 22:55:53 +03:00
|
|
|
cursor::Goto(1, 1),
|
|
|
|
BracketModeStart,
|
|
|
|
save_title_to_stack = SaveWindowTitleIconToStack,
|
|
|
|
window_title = if let Some(ref title) = self.context.settings.terminal.window_title {
|
|
|
|
format!("\x1b]2;{}\x07", title)
|
|
|
|
} else {
|
|
|
|
String::new()
|
|
|
|
},
|
2019-03-14 12:19:25 +02:00
|
|
|
)
|
|
|
|
.unwrap();
|
2019-10-15 22:55:53 +03:00
|
|
|
|
|
|
|
self.stdout = Some(stdout);
|
2018-08-06 16:53:23 +03:00
|
|
|
self.flush();
|
|
|
|
}
|
2018-09-04 01:49:29 +03:00
|
|
|
|
2018-08-16 16:32:47 +03:00
|
|
|
pub fn receiver(&self) -> Receiver<ThreadEvent> {
|
|
|
|
self.context.receiver.clone()
|
|
|
|
}
|
2019-09-22 11:00:05 +03:00
|
|
|
|
|
|
|
pub fn sender(&self) -> Sender<ThreadEvent> {
|
|
|
|
self.context.sender.clone()
|
|
|
|
}
|
|
|
|
|
2018-08-16 16:32:47 +03:00
|
|
|
pub fn restore_input(&mut self) {
|
|
|
|
self.context.restore_input();
|
|
|
|
}
|
|
|
|
|
2018-08-06 22:20:34 +03:00
|
|
|
/// On `SIGWNICH` the `State` redraws itself according to the new terminal size.
|
2018-08-06 16:53:23 +03:00
|
|
|
pub fn update_size(&mut self) {
|
|
|
|
let termsize = termion::terminal_size().ok();
|
|
|
|
let termcols = termsize.map(|(w, _)| w);
|
|
|
|
let termrows = termsize.map(|(_, h)| h);
|
|
|
|
if termcols.unwrap_or(72) as usize != self.cols
|
|
|
|
|| termrows.unwrap_or(120) as usize != self.rows
|
|
|
|
{
|
2019-05-01 19:20:33 +03:00
|
|
|
debug!(
|
2018-08-06 16:53:23 +03:00
|
|
|
"Size updated, from ({}, {}) -> ({:?}, {:?})",
|
|
|
|
self.cols, self.rows, termcols, termrows
|
|
|
|
);
|
|
|
|
}
|
|
|
|
self.cols = termcols.unwrap_or(72) as usize;
|
|
|
|
self.rows = termrows.unwrap_or(120) as usize;
|
|
|
|
self.grid.resize(self.cols, self.rows, Cell::with_char(' '));
|
2020-02-06 21:54:07 +02:00
|
|
|
self.overlay_grid
|
|
|
|
.resize(self.cols, self.rows, Cell::with_char(' '));
|
2018-08-06 16:53:23 +03:00
|
|
|
|
2019-04-10 23:37:20 +03:00
|
|
|
self.rcv_event(UIEvent::Resize);
|
2018-08-10 12:00:27 +03:00
|
|
|
|
|
|
|
// Invalidate dirty areas.
|
|
|
|
self.context.dirty_areas.clear();
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
|
2018-08-06 22:20:34 +03:00
|
|
|
/// Force a redraw for all dirty components.
|
2018-08-06 16:53:23 +03:00
|
|
|
pub fn redraw(&mut self) {
|
2020-01-15 12:36:31 +02:00
|
|
|
if !self.draw_rate_limit.tick() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-04-10 22:01:02 +03:00
|
|
|
for i in 0..self.components.len() {
|
|
|
|
self.draw_component(i);
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
2020-01-08 17:04:44 +02:00
|
|
|
let mut areas: smallvec::SmallVec<[Area; 8]> =
|
|
|
|
self.context.dirty_areas.drain(0..).collect();
|
2020-02-06 21:55:04 +02:00
|
|
|
if self.display_messages_active {
|
|
|
|
let now = melib::datetime::now();
|
|
|
|
if self
|
|
|
|
.display_messages_expiration_start
|
|
|
|
.map(|t| t + 5 < now)
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
|
|
|
self.display_messages_active = false;
|
|
|
|
self.display_messages_expiration_start = None;
|
|
|
|
areas.push((
|
|
|
|
(0, 0),
|
|
|
|
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
|
|
|
|
));
|
|
|
|
}
|
2019-09-25 23:14:24 +03:00
|
|
|
}
|
2020-02-06 21:55:04 +02:00
|
|
|
|
2019-06-10 15:56:53 +03:00
|
|
|
/* Sort by x_start, ie upper_left corner's x coordinate */
|
|
|
|
areas.sort_by(|a, b| (a.0).0.partial_cmp(&(b.0).0).unwrap());
|
2018-08-06 16:53:23 +03:00
|
|
|
/* draw each dirty area */
|
2019-06-10 15:56:53 +03:00
|
|
|
let rows = self.rows;
|
|
|
|
for y in 0..rows {
|
|
|
|
let mut segment = None;
|
|
|
|
for ((x_start, y_start), (x_end, y_end)) in &areas {
|
|
|
|
if y < *y_start || y > *y_end {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if let Some((x_start, x_end)) = segment.take() {
|
2020-02-06 21:54:07 +02:00
|
|
|
(self.draw_horizontal_segment_fn)(
|
|
|
|
&mut self.grid,
|
|
|
|
self.stdout.as_mut().unwrap(),
|
|
|
|
x_start,
|
|
|
|
x_end,
|
|
|
|
y,
|
|
|
|
);
|
2019-06-10 15:56:53 +03:00
|
|
|
}
|
|
|
|
match segment {
|
|
|
|
ref mut s @ None => {
|
|
|
|
*s = Some((*x_start, *x_end));
|
|
|
|
}
|
|
|
|
ref mut s @ Some(_) if s.unwrap().1 < *x_start => {
|
2020-02-06 21:54:07 +02:00
|
|
|
(self.draw_horizontal_segment_fn)(
|
|
|
|
&mut self.grid,
|
|
|
|
self.stdout.as_mut().unwrap(),
|
|
|
|
s.unwrap().0,
|
|
|
|
s.unwrap().1,
|
|
|
|
y,
|
|
|
|
);
|
2019-06-10 15:56:53 +03:00
|
|
|
*s = Some((*x_start, *x_end));
|
|
|
|
}
|
|
|
|
ref mut s @ Some(_) if s.unwrap().1 < *x_end => {
|
2020-02-06 21:54:07 +02:00
|
|
|
(self.draw_horizontal_segment_fn)(
|
|
|
|
&mut self.grid,
|
|
|
|
self.stdout.as_mut().unwrap(),
|
|
|
|
s.unwrap().0,
|
|
|
|
s.unwrap().1,
|
|
|
|
y,
|
|
|
|
);
|
2019-06-10 15:56:53 +03:00
|
|
|
*s = Some((s.unwrap().1, *x_end));
|
|
|
|
}
|
|
|
|
Some((_, ref mut x)) => {
|
|
|
|
*x = *x_end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some((x_start, x_end)) = segment {
|
2020-02-06 21:54:07 +02:00
|
|
|
(self.draw_horizontal_segment_fn)(
|
|
|
|
&mut self.grid,
|
|
|
|
self.stdout.as_mut().unwrap(),
|
|
|
|
x_start,
|
|
|
|
x_end,
|
|
|
|
y,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.display_messages_active {
|
|
|
|
if let Some(DisplayMessage {
|
|
|
|
ref timestamp,
|
|
|
|
ref msg,
|
|
|
|
..
|
|
|
|
}) = self.display_messages.get(self.display_messages_pos)
|
|
|
|
{
|
|
|
|
let noto_colors = crate::conf::value(&self.context, "status.notification");
|
|
|
|
use crate::melib::text_processing::{Reflow, TextProcessing};
|
|
|
|
|
|
|
|
let msg_lines = msg.split_lines_reflow(Reflow::All, Some(self.cols / 3));
|
|
|
|
let width = msg_lines
|
|
|
|
.iter()
|
|
|
|
.map(|line| line.grapheme_len() + 4)
|
|
|
|
.max()
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
|
|
|
let displ_area = place_in_area(
|
|
|
|
(
|
|
|
|
(0, 0),
|
|
|
|
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
|
|
|
|
),
|
|
|
|
(width, std::cmp::min(self.rows, msg_lines.len() + 4)),
|
|
|
|
false,
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
for row in self.overlay_grid.bounds_iter(displ_area) {
|
|
|
|
for c in row {
|
|
|
|
self.overlay_grid[c]
|
|
|
|
.set_ch(' ')
|
|
|
|
.set_fg(noto_colors.fg)
|
|
|
|
.set_bg(noto_colors.bg)
|
|
|
|
.set_attrs(noto_colors.attrs);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let ((x, mut y), box_displ_area_bottom_right) =
|
|
|
|
create_box(&mut self.overlay_grid, displ_area);
|
|
|
|
for line in msg_lines
|
|
|
|
.into_iter()
|
|
|
|
.chain(Some(String::new()))
|
|
|
|
.chain(Some(melib::datetime::timestamp_to_string(*timestamp, None)))
|
|
|
|
{
|
|
|
|
write_string_to_grid(
|
|
|
|
&line,
|
|
|
|
&mut self.overlay_grid,
|
|
|
|
noto_colors.fg,
|
|
|
|
noto_colors.bg,
|
|
|
|
noto_colors.attrs,
|
|
|
|
((x, y), box_displ_area_bottom_right),
|
|
|
|
Some(x),
|
|
|
|
);
|
|
|
|
y += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.display_messages.len() > 1 {
|
|
|
|
write_string_to_grid(
|
|
|
|
if self.display_messages_pos == 0 {
|
|
|
|
"Next: >"
|
|
|
|
} else if self.display_messages_pos + 1 == self.display_messages.len() {
|
|
|
|
"Prev: <"
|
|
|
|
} else {
|
|
|
|
"Prev: <, Next: >"
|
|
|
|
},
|
|
|
|
&mut self.overlay_grid,
|
|
|
|
noto_colors.fg,
|
|
|
|
noto_colors.bg,
|
|
|
|
noto_colors.attrs,
|
|
|
|
((x, y), box_displ_area_bottom_right),
|
|
|
|
Some(x),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
for y in get_y(upper_left!(displ_area))..=get_y(bottom_right!(displ_area)) {
|
|
|
|
(self.draw_horizontal_segment_fn)(
|
|
|
|
&mut self.overlay_grid,
|
|
|
|
self.stdout.as_mut().unwrap(),
|
|
|
|
get_x(upper_left!(displ_area)),
|
|
|
|
get_x(bottom_right!(displ_area)),
|
|
|
|
y,
|
|
|
|
);
|
|
|
|
}
|
2019-06-10 15:56:53 +03:00
|
|
|
}
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
2019-06-10 15:56:53 +03:00
|
|
|
self.flush();
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
2018-08-06 22:20:34 +03:00
|
|
|
|
|
|
|
/// Draw only a specific `area` on the screen.
|
2020-02-06 21:54:07 +02:00
|
|
|
fn draw_horizontal_segment(
|
|
|
|
grid: &mut CellBuffer,
|
|
|
|
stdout: &mut StateStdout,
|
|
|
|
x_start: usize,
|
|
|
|
x_end: usize,
|
|
|
|
y: usize,
|
|
|
|
) {
|
2019-06-10 15:56:53 +03:00
|
|
|
write!(
|
2020-02-06 21:54:07 +02:00
|
|
|
stdout,
|
2019-06-10 15:56:53 +03:00
|
|
|
"{}",
|
|
|
|
cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
|
|
|
|
)
|
|
|
|
.unwrap();
|
2020-01-27 17:07:29 +02:00
|
|
|
let mut current_fg = Color::Default;
|
|
|
|
let mut current_bg = Color::Default;
|
|
|
|
let mut current_attrs = Attr::Default;
|
|
|
|
write!(stdout, "\x1B[m").unwrap();
|
2019-06-10 15:56:53 +03:00
|
|
|
for x in x_start..=x_end {
|
2020-01-27 17:07:29 +02:00
|
|
|
let c = &grid[(x, y)];
|
|
|
|
if c.attrs() != current_attrs {
|
|
|
|
c.attrs().write(current_attrs, stdout).unwrap();
|
|
|
|
current_attrs = c.attrs();
|
2019-06-10 15:56:53 +03:00
|
|
|
}
|
2020-01-27 17:07:29 +02:00
|
|
|
if c.bg() != current_bg {
|
|
|
|
c.bg().write_bg(stdout).unwrap();
|
|
|
|
current_bg = c.bg();
|
2019-06-10 15:56:53 +03:00
|
|
|
}
|
2020-01-27 17:07:29 +02:00
|
|
|
if c.fg() != current_fg {
|
|
|
|
c.fg().write_fg(stdout).unwrap();
|
|
|
|
current_fg = c.fg();
|
2019-08-18 15:44:40 +03:00
|
|
|
}
|
2019-08-17 12:22:54 +03:00
|
|
|
if !c.empty() {
|
2020-01-27 17:07:29 +02:00
|
|
|
write!(stdout, "{}", c.ch()).unwrap();
|
2019-08-18 15:44:40 +03:00
|
|
|
}
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
}
|
2018-08-06 22:20:34 +03:00
|
|
|
|
2020-02-06 21:54:07 +02:00
|
|
|
fn draw_horizontal_segment_no_color(
|
|
|
|
grid: &mut CellBuffer,
|
|
|
|
stdout: &mut StateStdout,
|
|
|
|
x_start: usize,
|
|
|
|
x_end: usize,
|
|
|
|
y: usize,
|
|
|
|
) {
|
2020-01-27 20:17:46 +02:00
|
|
|
write!(
|
2020-02-06 21:54:07 +02:00
|
|
|
stdout,
|
2020-01-27 20:17:46 +02:00
|
|
|
"{}",
|
|
|
|
cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
let mut current_attrs = Attr::Default;
|
|
|
|
write!(stdout, "\x1B[m").unwrap();
|
|
|
|
for x in x_start..=x_end {
|
|
|
|
let c = &grid[(x, y)];
|
|
|
|
if c.attrs() != current_attrs {
|
|
|
|
c.attrs().write(current_attrs, stdout).unwrap();
|
|
|
|
current_attrs = c.attrs();
|
|
|
|
}
|
|
|
|
if !c.empty() {
|
|
|
|
write!(stdout, "{}", c.ch()).unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-06 22:20:34 +03:00
|
|
|
/// Draw the entire screen from scratch.
|
2018-08-06 16:53:23 +03:00
|
|
|
pub fn render(&mut self) {
|
|
|
|
self.update_size();
|
|
|
|
let cols = self.cols;
|
|
|
|
let rows = self.rows;
|
2019-06-10 15:56:53 +03:00
|
|
|
self.context
|
|
|
|
.dirty_areas
|
|
|
|
.push_back(((0, 0), (cols - 1, rows - 1)));
|
2018-08-06 16:53:23 +03:00
|
|
|
|
2019-06-10 15:56:53 +03:00
|
|
|
self.redraw();
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
2019-06-10 15:56:53 +03:00
|
|
|
|
2019-04-10 22:01:02 +03:00
|
|
|
pub fn draw_component(&mut self, idx: usize) {
|
|
|
|
let component = &mut self.components[idx];
|
2018-08-06 16:53:23 +03:00
|
|
|
let upper_left = (0, 0);
|
|
|
|
let bottom_right = (self.cols - 1, self.rows - 1);
|
|
|
|
|
2019-04-10 22:01:02 +03:00
|
|
|
if component.is_dirty() {
|
|
|
|
component.draw(
|
2018-08-06 16:53:23 +03:00
|
|
|
&mut self.grid,
|
|
|
|
(upper_left, bottom_right),
|
|
|
|
&mut self.context,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2019-09-25 23:14:24 +03:00
|
|
|
|
2019-09-27 13:14:16 +03:00
|
|
|
pub fn can_quit_cleanly(&mut self) -> bool {
|
2019-10-06 11:28:12 +03:00
|
|
|
let State {
|
|
|
|
ref mut components,
|
|
|
|
ref context,
|
|
|
|
..
|
|
|
|
} = self;
|
|
|
|
components.iter_mut().all(|c| c.can_quit_cleanly(context))
|
2019-09-27 13:14:16 +03:00
|
|
|
}
|
|
|
|
|
2019-09-09 11:54:47 +03:00
|
|
|
pub fn register_component(&mut self, component: Box<dyn Component>) {
|
2019-04-10 22:01:02 +03:00
|
|
|
self.components.push(component);
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
2019-09-25 23:14:24 +03:00
|
|
|
|
2018-08-06 16:53:23 +03:00
|
|
|
/// Convert user commands to actions/method calls.
|
2018-08-07 15:01:15 +03:00
|
|
|
fn parse_command(&mut self, cmd: &str) {
|
2018-08-06 16:53:23 +03:00
|
|
|
let result = parse_command(&cmd.as_bytes()).to_full_result();
|
|
|
|
|
|
|
|
if let Ok(v) = result {
|
2019-06-26 18:56:29 +03:00
|
|
|
match v {
|
|
|
|
SetEnv(key, val) => {
|
|
|
|
env::set_var(key.as_str(), val.as_str());
|
|
|
|
}
|
|
|
|
PrintEnv(key) => {
|
|
|
|
self.context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
StatusEvent::DisplayMessage(
|
|
|
|
env::var(key.as_str()).unwrap_or_else(|e| e.to_string()),
|
|
|
|
),
|
|
|
|
));
|
|
|
|
}
|
2020-02-06 01:49:18 +02:00
|
|
|
Folder(account_name, op) => {
|
2019-08-26 19:44:05 +03:00
|
|
|
if let Some(account) = self
|
|
|
|
.context
|
|
|
|
.accounts
|
|
|
|
.iter_mut()
|
|
|
|
.find(|a| a.name() == account_name)
|
|
|
|
{
|
2020-02-06 01:49:18 +02:00
|
|
|
match account.folder_operation(op) {
|
|
|
|
Err(err) => {
|
|
|
|
self.context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
StatusEvent::DisplayMessage(err.to_string()),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
Ok(msg) => {
|
|
|
|
self.context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
"`{}`: {}",
|
|
|
|
account_name, msg
|
|
|
|
)),
|
|
|
|
));
|
|
|
|
}
|
2019-08-26 19:44:05 +03:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.context.replies.push_back(UIEvent::StatusEvent(
|
|
|
|
StatusEvent::DisplayMessage(format!(
|
|
|
|
"Account with name `{}` not found.",
|
|
|
|
account_name
|
|
|
|
)),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
2019-12-17 14:14:48 +02:00
|
|
|
AccountAction(ref account_name, ReIndex) => {
|
2020-02-04 15:52:12 +02:00
|
|
|
#[cfg(feature = "sqlite3")]
|
2019-12-17 14:14:48 +02:00
|
|
|
match crate::sqlite3::index(&mut self.context, account_name) {
|
|
|
|
Ok(()) => {
|
|
|
|
self.context.replies.push_back(UIEvent::Notification(
|
|
|
|
None,
|
|
|
|
"Message index rebuild started.".to_string(),
|
|
|
|
Some(NotificationType::INFO),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
self.context.replies.push_back(UIEvent::Notification(
|
|
|
|
None,
|
|
|
|
format!("Message index rebuild failed: {}.", e),
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
));
|
|
|
|
}
|
2019-11-02 12:18:41 +02:00
|
|
|
}
|
2020-02-04 15:52:12 +02:00
|
|
|
#[cfg(not(feature = "sqlite3"))]
|
|
|
|
{
|
|
|
|
self.context.replies.push_back(UIEvent::Notification(
|
|
|
|
None,
|
|
|
|
"Message index rebuild failed: meli is not built with sqlite3 support."
|
|
|
|
.to_string(),
|
|
|
|
Some(NotificationType::ERROR),
|
|
|
|
));
|
|
|
|
}
|
2019-12-17 14:14:48 +02:00
|
|
|
}
|
2019-06-26 18:56:29 +03:00
|
|
|
v => {
|
|
|
|
self.rcv_event(UIEvent::Action(v));
|
|
|
|
}
|
|
|
|
}
|
2019-08-26 19:44:05 +03:00
|
|
|
} else {
|
|
|
|
self.context
|
|
|
|
.replies
|
|
|
|
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(
|
|
|
|
"invalid command".to_string(),
|
|
|
|
)));
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-06 22:20:34 +03:00
|
|
|
/// The application's main loop sends `UIEvents` to state via this method.
|
2019-02-26 17:50:47 +02:00
|
|
|
pub fn rcv_event(&mut self, mut event: UIEvent) {
|
2020-02-06 21:55:04 +02:00
|
|
|
if let UIEvent::Input(_) = event {
|
|
|
|
if self.display_messages_expiration_start.is_none() {
|
|
|
|
self.display_messages_expiration_start = Some(melib::datetime::now());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-10 23:37:20 +03:00
|
|
|
match event {
|
2018-08-06 16:53:23 +03:00
|
|
|
// Command type is handled only by State.
|
2019-04-10 23:37:20 +03:00
|
|
|
UIEvent::Command(cmd) => {
|
2018-08-07 15:01:15 +03:00
|
|
|
self.parse_command(&cmd);
|
2018-08-06 16:53:23 +03:00
|
|
|
return;
|
|
|
|
}
|
2019-11-05 08:32:27 +02:00
|
|
|
UIEvent::Fork(ForkType::Finished) => {
|
|
|
|
/*
|
|
|
|
* Fork has finished in the past.
|
|
|
|
* We're back in the AlternateScreen, but the cursor is reset to Shown, so fix
|
|
|
|
* it.
|
|
|
|
write!(self.stdout(), "{}", cursor::Hide,).unwrap();
|
|
|
|
self.flush();
|
|
|
|
*/
|
|
|
|
self.switch_to_main_screen();
|
|
|
|
self.switch_to_alternate_screen();
|
|
|
|
self.context.restore_input();
|
|
|
|
return;
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
UIEvent::Fork(child) => {
|
2018-08-06 16:53:23 +03:00
|
|
|
self.mode = UIMode::Fork;
|
|
|
|
self.child = Some(child);
|
|
|
|
return;
|
2019-03-14 12:19:25 +02:00
|
|
|
}
|
2019-12-14 19:56:43 +02:00
|
|
|
UIEvent::WorkerProgress(folder_hash) => {
|
|
|
|
if let Some(&account_idx) = self.context.mailbox_hashes.get(&folder_hash) {
|
|
|
|
let _ = self.context.accounts[account_idx].status(folder_hash);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2019-04-10 23:37:20 +03:00
|
|
|
UIEvent::ChangeMode(m) => {
|
2019-11-05 08:32:27 +02:00
|
|
|
if self.mode == UIMode::Embed {
|
|
|
|
self.context.input_from_raw();
|
|
|
|
}
|
2019-02-25 11:11:56 +02:00
|
|
|
self.context
|
|
|
|
.sender
|
2019-09-09 12:53:39 +03:00
|
|
|
.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m)))
|
|
|
|
.unwrap();
|
2019-11-05 08:32:27 +02:00
|
|
|
if m == UIMode::Embed {
|
|
|
|
self.context.input_to_raw();
|
|
|
|
}
|
2019-03-14 12:19:25 +02:00
|
|
|
}
|
2020-01-15 12:36:31 +02:00
|
|
|
UIEvent::Timer(id) if id == self.draw_rate_limit.id() => {
|
|
|
|
self.draw_rate_limit.reset();
|
|
|
|
self.redraw();
|
|
|
|
return;
|
|
|
|
}
|
2020-02-06 21:55:04 +02:00
|
|
|
UIEvent::Input(Key::Alt('<')) => {
|
|
|
|
self.display_messages_expiration_start = Some(melib::datetime::now());
|
|
|
|
self.display_messages_active = true;
|
|
|
|
self.display_messages_pos = self.display_messages_pos.saturating_sub(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
UIEvent::Input(Key::Alt('>')) => {
|
|
|
|
self.display_messages_expiration_start = Some(melib::datetime::now());
|
|
|
|
self.display_messages_active = true;
|
|
|
|
self.display_messages_pos = std::cmp::min(
|
|
|
|
self.display_messages.len().saturating_sub(1),
|
|
|
|
self.display_messages_pos + 1,
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
UIEvent::StatusEvent(StatusEvent::DisplayMessage(ref msg)) => {
|
|
|
|
self.display_messages.push(DisplayMessage {
|
|
|
|
timestamp: melib::datetime::now(),
|
|
|
|
msg: msg.clone(),
|
|
|
|
});
|
|
|
|
self.display_messages_active = true;
|
|
|
|
self.display_messages_pos = self.display_messages.len() - 1;
|
|
|
|
}
|
2018-08-06 16:53:23 +03:00
|
|
|
_ => {}
|
|
|
|
}
|
2019-04-10 22:01:02 +03:00
|
|
|
/* inform each component */
|
|
|
|
for i in 0..self.components.len() {
|
|
|
|
self.components[i].process_event(&mut event, &mut self.context);
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if !self.context.replies.is_empty() {
|
2020-01-08 17:04:44 +02:00
|
|
|
let replies: smallvec::SmallVec<[UIEvent; 8]> =
|
|
|
|
self.context.replies.drain(0..).collect();
|
2018-08-06 16:53:23 +03:00
|
|
|
// Pass replies to self and call count on the map iterator to force evaluation
|
|
|
|
replies.into_iter().map(|r| self.rcv_event(r)).count();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_wait_on_child(&mut self) -> Option<bool> {
|
2018-09-04 01:49:29 +03:00
|
|
|
let should_return_flag = match self.child {
|
2018-08-07 15:01:15 +03:00
|
|
|
Some(ForkType::NewDraft(_, ref mut c)) => {
|
2019-06-18 21:13:58 +03:00
|
|
|
let w = c.try_wait();
|
2018-08-07 15:01:15 +03:00
|
|
|
match w {
|
|
|
|
Ok(Some(_)) => true,
|
|
|
|
Ok(None) => false,
|
2019-09-20 09:23:48 +03:00
|
|
|
Err(e) => {
|
|
|
|
log(
|
|
|
|
format!("Failed to wait on editor process: {}", e.to_string()),
|
|
|
|
ERROR,
|
|
|
|
);
|
2018-08-07 15:01:15 +03:00
|
|
|
return None;
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
}
|
2018-08-07 15:01:15 +03:00
|
|
|
}
|
|
|
|
Some(ForkType::Generic(ref mut c)) => {
|
2019-06-18 21:13:58 +03:00
|
|
|
let w = c.try_wait();
|
2018-08-07 15:01:15 +03:00
|
|
|
match w {
|
|
|
|
Ok(Some(_)) => true,
|
|
|
|
Ok(None) => false,
|
2019-09-20 09:23:48 +03:00
|
|
|
Err(e) => {
|
|
|
|
log(
|
|
|
|
format!("Failed to wait on child process: {}", e.to_string()),
|
|
|
|
ERROR,
|
|
|
|
);
|
2018-08-07 15:01:15 +03:00
|
|
|
return None;
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-09-04 01:49:29 +03:00
|
|
|
Some(ForkType::Finished) => {
|
|
|
|
/* Fork has already finished */
|
|
|
|
std::mem::replace(&mut self.child, None);
|
|
|
|
return None;
|
|
|
|
}
|
2018-08-07 15:01:15 +03:00
|
|
|
_ => {
|
|
|
|
return None;
|
|
|
|
}
|
2018-09-04 01:49:29 +03:00
|
|
|
};
|
|
|
|
if should_return_flag {
|
2018-08-06 16:53:23 +03:00
|
|
|
return Some(true);
|
|
|
|
}
|
|
|
|
Some(false)
|
|
|
|
}
|
|
|
|
fn flush(&mut self) {
|
2018-08-11 18:00:21 +03:00
|
|
|
if let Some(s) = self.stdout.as_mut() {
|
|
|
|
s.flush().unwrap();
|
|
|
|
}
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|
2018-09-04 01:49:29 +03:00
|
|
|
fn stdout(&mut self) -> &mut StateStdout {
|
2018-08-06 16:53:23 +03:00
|
|
|
self.stdout.as_mut().unwrap()
|
|
|
|
}
|
2019-11-23 18:00:00 +02:00
|
|
|
|
|
|
|
pub fn check_accounts(&mut self) {
|
|
|
|
let mut ctr = 0;
|
|
|
|
for i in 0..self.context.accounts.len() {
|
2019-12-14 18:46:12 +02:00
|
|
|
if self.context.is_online(i).is_ok() {
|
2019-11-23 18:00:00 +02:00
|
|
|
ctr += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ctr != self.context.accounts.len() {
|
|
|
|
self.timer.thread().unpark();
|
|
|
|
}
|
|
|
|
}
|
2018-08-06 16:53:23 +03:00
|
|
|
}
|