parent
d43d8d282c
commit
b2c7430907
|
@ -107,6 +107,8 @@ pub struct Container {
|
|||
struct ContainerTree {
|
||||
id: usize,
|
||||
children: Option<Vec<ContainerTree>>,
|
||||
len: usize,
|
||||
has_unseen: bool,
|
||||
}
|
||||
|
||||
impl ContainerTree {
|
||||
|
@ -114,6 +116,8 @@ impl ContainerTree {
|
|||
ContainerTree {
|
||||
id,
|
||||
children: None,
|
||||
len: 1,
|
||||
has_unseen: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -173,8 +177,33 @@ impl<'a> IntoIterator for &'a Threads {
|
|||
}
|
||||
|
||||
|
||||
pub struct RootIterator<'a> {
|
||||
pos: usize,
|
||||
tree: Ref<'a ,Vec<ContainerTree>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RootIterator<'a> {
|
||||
type Item = (usize, usize, bool);
|
||||
fn next(&mut self) -> Option<(usize, usize, bool)> {
|
||||
if self.pos == self.tree.len() {
|
||||
return None;
|
||||
}
|
||||
let node = &self.tree[self.pos];
|
||||
self.pos += 1;
|
||||
return Some((node.id, node.len, node.has_unseen));
|
||||
}
|
||||
}
|
||||
|
||||
impl Threads {
|
||||
pub fn root_len(&self) -> usize {
|
||||
self.tree.borrow().len()
|
||||
}
|
||||
pub fn root_set(&self) -> &Vec<usize> {
|
||||
&self.root_set
|
||||
}
|
||||
pub fn root_set_iter(&self) -> RootIterator {
|
||||
RootIterator { pos: 0, tree: self.tree.borrow() }
|
||||
}
|
||||
pub fn thread_to_mail(&self, i: usize) -> usize {
|
||||
let thread = self.containers[self.threaded_collection[i]];
|
||||
thread.message().unwrap()
|
||||
|
@ -226,49 +255,49 @@ impl Threads {
|
|||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_sort_by(&self, sort: (SortField, SortOrder), collection: &[Envelope]) {
|
||||
let tree = &mut self.tree.borrow_mut();
|
||||
let containers = &self.containers;
|
||||
tree.sort_by(|a, b| { match sort {
|
||||
(SortField::Date, SortOrder::Desc) => {
|
||||
let a = containers[a.id];
|
||||
let b = containers[b.id];
|
||||
b.date.cmp(&a.date)
|
||||
tree.sort_by(|a, b| { match sort {
|
||||
(SortField::Date, SortOrder::Desc) => {
|
||||
let a = containers[a.id];
|
||||
let b = containers[b.id];
|
||||
b.date.cmp(&a.date)
|
||||
|
||||
}
|
||||
(SortField::Date, SortOrder::Asc) => {
|
||||
let a = containers[a.id];
|
||||
let b = containers[b.id];
|
||||
a.date.cmp(&b.date)
|
||||
}
|
||||
(SortField::Subject, SortOrder::Desc) => {
|
||||
let a = containers[a.id].message();
|
||||
let b = containers[b.id].message();
|
||||
|
||||
if a.is_none() || b.is_none() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
let ma = &collection[a.unwrap()];
|
||||
let mb = &collection[b.unwrap()];
|
||||
ma.subject().cmp(&mb.subject())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Asc) => {
|
||||
let a = containers[a.id].message();
|
||||
let b = containers[b.id].message();
|
||||
|
||||
if a.is_none() || b.is_none() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
let ma = &collection[a.unwrap()];
|
||||
let mb = &collection[b.unwrap()];
|
||||
mb.subject().cmp(&ma.subject())
|
||||
}
|
||||
}
|
||||
});
|
||||
(SortField::Date, SortOrder::Asc) => {
|
||||
let a = containers[a.id];
|
||||
let b = containers[b.id];
|
||||
a.date.cmp(&b.date)
|
||||
}
|
||||
(SortField::Subject, SortOrder::Desc) => {
|
||||
let a = containers[a.id].message();
|
||||
let b = containers[b.id].message();
|
||||
|
||||
if a.is_none() || b.is_none() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
let ma = &collection[a.unwrap()];
|
||||
let mb = &collection[b.unwrap()];
|
||||
ma.subject().cmp(&mb.subject())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Asc) => {
|
||||
let a = containers[a.id].message();
|
||||
let b = containers[b.id].message();
|
||||
|
||||
if a.is_none() || b.is_none() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
let ma = &collection[a.unwrap()];
|
||||
let mb = &collection[b.unwrap()];
|
||||
mb.subject().cmp(&ma.subject())
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
pub fn sort_by(&self, sort: (SortField, SortOrder), subsort: (SortField, SortOrder), collection: &[Envelope]) {
|
||||
if *self.sort.borrow() != sort {
|
||||
|
@ -292,7 +321,6 @@ impl Threads {
|
|||
root_subject_idx: usize,
|
||||
collection: &[Envelope],
|
||||
) {
|
||||
tree.id = i;
|
||||
let thread = containers[i];
|
||||
if let Some(msg_idx) = containers[root_subject_idx].message() {
|
||||
let root_subject = collection[msg_idx].subject();
|
||||
|
@ -301,6 +329,7 @@ impl Threads {
|
|||
* list.) */
|
||||
if indentation > 0 && thread.has_message() {
|
||||
let subject = collection[thread.message().unwrap()].subject();
|
||||
tree.has_unseen = !collection[thread.message().unwrap()].is_seen();
|
||||
if subject == root_subject
|
||||
|| subject.starts_with("Re: ")
|
||||
&& subject.as_ref().ends_with(root_subject.as_ref())
|
||||
|
@ -332,6 +361,7 @@ impl Threads {
|
|||
loop {
|
||||
let mut new_child_tree = ContainerTree::new(fc);
|
||||
build_threaded(&mut new_child_tree, containers, indentation, threaded, fc, i, collection);
|
||||
tree.has_unseen |= new_child_tree.has_unseen;
|
||||
child_vec.push(new_child_tree);
|
||||
let thread_ = containers[fc];
|
||||
if !thread_.has_sibling() {
|
||||
|
@ -339,6 +369,7 @@ impl Threads {
|
|||
}
|
||||
fc = thread_.next_sibling().unwrap();
|
||||
}
|
||||
tree.len = child_vec.iter().map(|c| c.len).sum();
|
||||
tree.children = Some(child_vec);
|
||||
}
|
||||
}
|
||||
|
|
46
src/bin.rs
46
src/bin.rs
|
@ -35,7 +35,6 @@ extern crate ui;
|
|||
pub use melib::*;
|
||||
pub use ui::*;
|
||||
|
||||
use std::thread;
|
||||
|
||||
#[macro_use]
|
||||
extern crate chan;
|
||||
|
@ -45,28 +44,6 @@ use chan_signal::Signal;
|
|||
|
||||
extern crate nix;
|
||||
|
||||
fn make_input_thread(
|
||||
sx: chan::Sender<ThreadEvent>,
|
||||
rx: chan::Receiver<bool>,
|
||||
) -> thread::JoinHandle<()> {
|
||||
let stdin = std::io::stdin();
|
||||
thread::Builder::new()
|
||||
.name("input-thread".to_string())
|
||||
.spawn(move || {
|
||||
get_events(
|
||||
stdin,
|
||||
|k| {
|
||||
sx.send(ThreadEvent::Input(k));
|
||||
},
|
||||
|| {
|
||||
sx.send(ThreadEvent::UIEvent(UIEventType::ChangeMode(UIMode::Fork)));
|
||||
},
|
||||
&rx,
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
/* Lock all stdio outs */
|
||||
//let _stdout = stdout();
|
||||
|
@ -79,31 +56,22 @@ fn main() {
|
|||
/* Catch SIGWINCH to handle terminal resizing */
|
||||
let signal = chan_signal::notify(&[Signal::WINCH]);
|
||||
|
||||
/* Create a channel to communicate with other threads. The main process is the sole receiver.
|
||||
* */
|
||||
let (sender, receiver) = chan::sync(::std::mem::size_of::<ThreadEvent>());
|
||||
|
||||
/*
|
||||
* Create async channel to block the input-thread if we need to fork and stop it from reading
|
||||
* stdin, see get_events() for details
|
||||
* */
|
||||
let (tx, rx) = chan::async();
|
||||
/* Get input thread handle to join it if we need to */
|
||||
let mut _thread_handler = make_input_thread(sender.clone(), rx.clone());
|
||||
|
||||
/* Create the application State. This is the 'System' part of an ECS architecture */
|
||||
let mut state = State::new(sender.clone(), tx);
|
||||
let mut state = State::new();
|
||||
|
||||
let receiver = state.receiver();
|
||||
|
||||
/* Register some reasonably useful interfaces */
|
||||
let menu = Entity {
|
||||
component: Box::new(AccountMenu::new(&state.context.accounts)),
|
||||
};
|
||||
let listing = MailListing::new();
|
||||
let listing = CompactListing::new();
|
||||
let b = Entity {
|
||||
component: Box::new(listing),
|
||||
};
|
||||
let mut tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true))]));
|
||||
tabs.add_component(Box::new(Composer {}));
|
||||
tabs.add_component(Box::new(Composer::default()));
|
||||
let window = Entity { component: tabs };
|
||||
|
||||
let status_bar = Entity {
|
||||
|
@ -137,7 +105,7 @@ fn main() {
|
|||
let self_pid = nix::unistd::Pid::this();
|
||||
nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGSTOP).unwrap();
|
||||
state.switch_to_alternate_screen();
|
||||
_thread_handler = make_input_thread(sender.clone(), rx.clone());
|
||||
state.restore_input();
|
||||
// BUG: thread sends input event after one received key
|
||||
state.update_size();
|
||||
state.render();
|
||||
|
@ -237,7 +205,7 @@ fn main() {
|
|||
'reap: loop {
|
||||
match state.try_wait_on_child() {
|
||||
Some(true) => {
|
||||
make_input_thread(sender.clone(), rx.clone());
|
||||
state.restore_input();
|
||||
state.mode = UIMode::Normal;
|
||||
state.render();
|
||||
}
|
||||
|
|
|
@ -21,7 +21,26 @@
|
|||
|
||||
use super::*;
|
||||
|
||||
pub struct Composer {}
|
||||
pub struct Composer {
|
||||
dirty: bool,
|
||||
mode: ViewMode,
|
||||
pager: Pager,
|
||||
}
|
||||
|
||||
impl Default for Composer {
|
||||
fn default() -> Self {
|
||||
Composer {
|
||||
dirty: true,
|
||||
mode: ViewMode::Overview,
|
||||
pager: Pager::from_str("asdfs\nfdsfds\ndsfdsfs\n\n\n\naaaaaaaaaaaaaa\nfdgfd", None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ViewMode {
|
||||
//Compose,
|
||||
Overview,
|
||||
}
|
||||
|
||||
impl fmt::Display for Composer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
|
@ -32,14 +51,90 @@ impl fmt::Display for Composer {
|
|||
|
||||
impl Component for Composer {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
clear_area(grid, area);
|
||||
context.dirty_areas.push_back(area);
|
||||
if self.dirty {
|
||||
clear_area(grid, area);
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
||||
let header_height = 12;
|
||||
let width = width!(area);
|
||||
let mid = if width > 80 {
|
||||
let width = width - 80;
|
||||
let mid = width / 2;;
|
||||
|
||||
if self.dirty {
|
||||
for i in get_y(upper_left)..=get_y(bottom_right) {
|
||||
grid[(mid, i)].set_ch(VERT_BOUNDARY);
|
||||
grid[(mid, i)].set_fg(Color::Default);
|
||||
grid[(mid, i)].set_bg(Color::Default);
|
||||
grid[(mid + 80, i)].set_ch(VERT_BOUNDARY);
|
||||
grid[(mid + 80, i)].set_fg(Color::Default);
|
||||
grid[(mid + 80, i)].set_bg(Color::Default);
|
||||
}
|
||||
}
|
||||
mid
|
||||
} else { 0 };
|
||||
|
||||
if self.dirty {
|
||||
for i in get_x(upper_left)+ mid + 1..=get_x(upper_left) + mid + 79 {
|
||||
grid[(i, header_height)].set_ch(HORZ_BOUNDARY);
|
||||
grid[(i, header_height)].set_fg(Color::Default);
|
||||
grid[(i, header_height)].set_bg(Color::Default);
|
||||
}
|
||||
}
|
||||
|
||||
let body_area = ((mid + 1, header_height+2), (mid + 78, get_y(bottom_right)));
|
||||
|
||||
if self.dirty {
|
||||
context.dirty_areas.push_back(area);
|
||||
self.dirty = false;
|
||||
}
|
||||
match self.mode {
|
||||
ViewMode::Overview => {
|
||||
self.pager.draw(grid, body_area, context);
|
||||
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {}
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||
match event.event_type {
|
||||
UIEventType::Resize => {
|
||||
self.dirty = true;
|
||||
}
|
||||
UIEventType::Input(Key::Char('\n')) => {
|
||||
use std::process::{Command, Stdio};
|
||||
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
||||
{
|
||||
context.input_kill();
|
||||
}
|
||||
let mut f = create_temp_file(&new_draft(context), None);
|
||||
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
|
||||
|
||||
// TODO: check exit status
|
||||
Command::new("vim")
|
||||
.arg("+/^$")
|
||||
.arg(&f.path())
|
||||
.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
self.pager.update_from_string(f.read_to_string());
|
||||
context.restore_input();
|
||||
self.dirty = true;
|
||||
return;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
self.pager.process_event(event, context);
|
||||
}
|
||||
|
||||
fn is_dirty(&self) -> bool {
|
||||
true
|
||||
self.dirty || self.pager.is_dirty()
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
self.pager.set_dirty();
|
||||
}
|
||||
fn set_dirty(&mut self) {}
|
||||
}
|
||||
|
|
|
@ -20,18 +20,20 @@
|
|||
*/
|
||||
|
||||
use super::*;
|
||||
use melib::mailbox::backends::BackendOp;
|
||||
|
||||
//use melib::mailbox::backends::BackendOp;
|
||||
|
||||
const MAX_COLS: usize = 500;
|
||||
|
||||
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the thread's content in a
|
||||
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
|
||||
/// `ThreadView`.
|
||||
pub struct CompactMailListing {
|
||||
pub struct CompactListing {
|
||||
/// (x, y, z): x is accounts, y is folders, z is index inside a folder.
|
||||
cursor_pos: (usize, usize, usize),
|
||||
new_cursor_pos: (usize, usize, usize),
|
||||
length: usize,
|
||||
sort: (SortField, SortOrder),
|
||||
//subsort: (SortField, SortOrder),
|
||||
subsort: (SortField, SortOrder),
|
||||
/// Cache current view.
|
||||
content: CellBuffer,
|
||||
/// If we must redraw on next redraw event
|
||||
|
@ -41,27 +43,48 @@ pub struct CompactMailListing {
|
|||
view: Option<ThreadView>,
|
||||
}
|
||||
|
||||
impl Default for CompactMailListing {
|
||||
impl Default for CompactListing {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CompactMailListing {
|
||||
impl fmt::Display for CompactListing {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "mail")
|
||||
}
|
||||
}
|
||||
|
||||
impl CompactMailListing {
|
||||
impl CompactListing {
|
||||
/// Helper function to format entry strings for CompactListing */
|
||||
/* TODO: Make this configurable */
|
||||
fn make_entry_string(e: &Envelope, len: usize, idx: usize) -> String {
|
||||
if len > 1 {
|
||||
format!(
|
||||
"{} {} {:.85} ({})",
|
||||
idx,
|
||||
&CompactListing::format_date(e),
|
||||
e.subject(),
|
||||
len
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{} {} {:.85}",
|
||||
idx,
|
||||
&CompactListing::format_date(e),
|
||||
e.subject(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
let content = CellBuffer::new(0, 0, Cell::with_char(' '));
|
||||
CompactMailListing {
|
||||
CompactListing {
|
||||
cursor_pos: (0, 1, 0),
|
||||
new_cursor_pos: (0, 0, 0),
|
||||
length: 0,
|
||||
sort: (SortField::Date, SortOrder::Desc),
|
||||
//subsort: (SortField::Date, SortOrder::Asc),
|
||||
sort: (Default::default(), Default::default()),
|
||||
subsort: (Default::default(), Default::default()),
|
||||
content: content,
|
||||
dirty: true,
|
||||
unfocused: false,
|
||||
|
@ -94,187 +117,86 @@ impl CompactMailListing {
|
|||
.as_ref()
|
||||
.unwrap();
|
||||
|
||||
self.length = mailbox.threads.containers().len();
|
||||
let mut content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||
|
||||
self.length = mailbox.threads.root_len();
|
||||
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||
if self.length == 0 {
|
||||
write_string_to_grid(
|
||||
&format!("Folder `{}` is empty.", mailbox.folder.name()),
|
||||
&mut content,
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
true,
|
||||
);
|
||||
self.content = content;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Fix the threaded hell and refactor stuff into seperate functions and/or modules.
|
||||
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||
let mut thread_idx = 0; // needed for alternate thread colors
|
||||
/* Draw threaded view. */
|
||||
let mut local_collection: Vec<usize> = mailbox.threads.threaded_collection().clone();
|
||||
let threads: &Vec<Container> = &mailbox.threads.containers();
|
||||
local_collection.sort_by(|a, b| match self.sort {
|
||||
(SortField::Date, SortOrder::Desc) => {
|
||||
mailbox.thread(*b).date().cmp(&mailbox.thread(*a).date())
|
||||
}
|
||||
(SortField::Date, SortOrder::Asc) => {
|
||||
mailbox.thread(*a).date().cmp(&mailbox.thread(*b).date())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Desc) => {
|
||||
let a = mailbox.thread(*a);
|
||||
let b = mailbox.thread(*b);
|
||||
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
||||
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
||||
ma.subject().cmp(&mb.subject())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Asc) => {
|
||||
let a = mailbox.thread(*a);
|
||||
let b = mailbox.thread(*b);
|
||||
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
||||
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
||||
mb.subject().cmp(&ma.subject())
|
||||
}
|
||||
});
|
||||
let mut iter = local_collection.iter().enumerate().peekable();
|
||||
let len = mailbox
|
||||
.threads
|
||||
.threaded_collection()
|
||||
.len()
|
||||
.to_string()
|
||||
.chars()
|
||||
.count();
|
||||
/* This is just a desugared for loop so that we can use .peek() */
|
||||
while let Some((idx, i)) = iter.next() {
|
||||
let container = &threads[*i];
|
||||
let indentation = container.indentation();
|
||||
|
||||
if indentation == 0 {
|
||||
thread_idx += 1;
|
||||
}
|
||||
|
||||
assert!(container.has_message());
|
||||
match iter.peek() {
|
||||
Some(&(_, x)) if threads[*x].indentation() == indentation => {
|
||||
indentations.pop();
|
||||
indentations.push(true);
|
||||
}
|
||||
_ => {
|
||||
indentations.pop();
|
||||
indentations.push(false);
|
||||
}
|
||||
}
|
||||
if container.has_sibling() {
|
||||
indentations.pop();
|
||||
indentations.push(true);
|
||||
}
|
||||
let envelope: &Envelope = &mailbox.collection[container.message().unwrap()];
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
let threads = &mailbox.threads;
|
||||
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
|
||||
for (idx, (t, len, has_unseen)) in threads.root_set_iter().enumerate() {
|
||||
let container = &threads.containers()[t];
|
||||
let i = if let Some(i) = container.message() {
|
||||
i
|
||||
} else {
|
||||
threads.containers()[
|
||||
container.first_child().unwrap()
|
||||
].message().unwrap()
|
||||
};
|
||||
let root_envelope: &Envelope = &mailbox.collection[i];
|
||||
let fg_color = if has_unseen {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if !envelope.is_seen() {
|
||||
let bg_color = if has_unseen {
|
||||
Color::Byte(251)
|
||||
} else if thread_idx % 2 == 0 {
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let (x, _) = write_string_to_grid(
|
||||
&CompactMailListing::make_thread_entry(
|
||||
envelope,
|
||||
idx,
|
||||
indentation,
|
||||
container,
|
||||
&indentations,
|
||||
len,
|
||||
context.accounts[self.cursor_pos.0].backend.operation(envelope.hash()),
|
||||
),
|
||||
&mut content,
|
||||
&CompactListing::make_entry_string(root_envelope, len, idx),
|
||||
&mut self.content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, idx), (MAX_COLS - 1, idx)),
|
||||
false,
|
||||
);
|
||||
);
|
||||
|
||||
for x in x..MAX_COLS {
|
||||
content[(x, idx)].set_ch(' ');
|
||||
content[(x, idx)].set_bg(bg_color);
|
||||
self.content[(x, idx)].set_ch(' ');
|
||||
self.content[(x, idx)].set_bg(bg_color);
|
||||
}
|
||||
|
||||
match iter.peek() {
|
||||
Some(&(_, x)) if threads[*x].indentation() > indentation => {
|
||||
indentations.push(false);
|
||||
}
|
||||
Some(&(_, x)) if threads[*x].indentation() < indentation => {
|
||||
for _ in 0..(indentation - threads[*x].indentation()) {
|
||||
indentations.pop();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self.content = content;
|
||||
}
|
||||
|
||||
fn highlight_line_self(&mut self, idx: usize, context: &Context) {
|
||||
let threaded = context.accounts[self.cursor_pos.0]
|
||||
.runtime_settings
|
||||
.threaded;
|
||||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let envelope: &Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mailbox.collection[i]
|
||||
} else {
|
||||
&mailbox.collection[idx]
|
||||
};
|
||||
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if !envelope.is_seen() {
|
||||
Color::Byte(251)
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
change_colors(
|
||||
&mut self.content,
|
||||
((0, idx), (MAX_COLS - 1, idx)),
|
||||
fg_color,
|
||||
bg_color,
|
||||
);
|
||||
}
|
||||
|
||||
fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
|
||||
let threaded = context.accounts[self.cursor_pos.0]
|
||||
.runtime_settings
|
||||
.threaded;
|
||||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let envelope: &Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mailbox.collection[i]
|
||||
let threads = &mailbox.threads;
|
||||
let container = threads.root_set()[idx];
|
||||
let container = &threads.containers()[container];
|
||||
let i = if let Some(i) = container.message() {
|
||||
i
|
||||
} else {
|
||||
&mailbox.collection[idx]
|
||||
threads.containers()[
|
||||
container.first_child().unwrap()
|
||||
].message().unwrap()
|
||||
};
|
||||
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
let root_envelope: &Envelope = &mailbox.collection[i];
|
||||
let fg_color = if !root_envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if self.cursor_pos.2 == idx {
|
||||
Color::Byte(246)
|
||||
} else if !envelope.is_seen() {
|
||||
} else if !root_envelope.is_seen() {
|
||||
Color::Byte(251)
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
|
@ -343,56 +265,6 @@ impl CompactMailListing {
|
|||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
||||
fn make_thread_entry(
|
||||
envelope: &Envelope,
|
||||
idx: usize,
|
||||
indent: usize,
|
||||
container: &Container,
|
||||
indentations: &[bool],
|
||||
idx_width: usize,
|
||||
op: Box<BackendOp>,
|
||||
) -> String {
|
||||
let has_sibling = container.has_sibling();
|
||||
let has_parent = container.has_parent();
|
||||
let show_subject = container.show_subject();
|
||||
|
||||
let mut s = format!(
|
||||
"{}{}{} ",
|
||||
idx,
|
||||
" ".repeat(idx_width + 2 - (idx.to_string().chars().count())),
|
||||
CompactMailListing::format_date(&envelope)
|
||||
);
|
||||
for i in 0..indent {
|
||||
if indentations.len() > i && indentations[i] {
|
||||
s.push('│');
|
||||
} else {
|
||||
s.push(' ');
|
||||
}
|
||||
if i > 0 {
|
||||
s.push(' ');
|
||||
}
|
||||
}
|
||||
if indent > 0 {
|
||||
if has_sibling && has_parent {
|
||||
s.push('├');
|
||||
} else if has_sibling {
|
||||
s.push('┬');
|
||||
} else {
|
||||
s.push('└');
|
||||
}
|
||||
s.push('─');
|
||||
s.push('>');
|
||||
}
|
||||
|
||||
if show_subject {
|
||||
s.push_str(&format!("{:.85}", envelope.subject()));
|
||||
}
|
||||
let attach_count = envelope.body(op).count_attachments();
|
||||
if attach_count > 1 {
|
||||
s.push_str(&format!(" {}∞ ", attach_count - 1));
|
||||
}
|
||||
s
|
||||
}
|
||||
fn format_date(envelope: &Envelope) -> String {
|
||||
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date());
|
||||
let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap();
|
||||
|
@ -407,7 +279,7 @@ impl CompactMailListing {
|
|||
}
|
||||
}
|
||||
|
||||
impl Component for CompactMailListing {
|
||||
impl Component for CompactListing {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !self.unfocused {
|
||||
if !self.is_dirty() {
|
||||
|
@ -434,88 +306,14 @@ impl Component for CompactMailListing {
|
|||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
/* Mark message as read */
|
||||
let idx = self.cursor_pos.2;
|
||||
let must_highlight = {
|
||||
if self.length == 0 {
|
||||
false
|
||||
} else {
|
||||
let threaded = context.accounts[self.cursor_pos.0]
|
||||
.runtime_settings
|
||||
.threaded;
|
||||
let account = &mut context.accounts[self.cursor_pos.0];
|
||||
let (hash, is_seen) = {
|
||||
let mailbox = &mut account[self.cursor_pos.1]
|
||||
.as_mut()
|
||||
.unwrap();
|
||||
let envelope: &mut Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mut mailbox.collection[i]
|
||||
} else {
|
||||
&mut mailbox.collection[idx]
|
||||
};
|
||||
(envelope.hash(), envelope.is_seen())
|
||||
};
|
||||
if is_seen {
|
||||
let op = {
|
||||
let backend = &account.backend;
|
||||
backend.operation(hash)
|
||||
};
|
||||
let mailbox = &mut account[self.cursor_pos.1]
|
||||
.as_mut()
|
||||
.unwrap();
|
||||
let envelope: &mut Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mut mailbox.collection[i]
|
||||
} else {
|
||||
&mut mailbox.collection[idx]
|
||||
};
|
||||
envelope.set_seen(op).unwrap();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
if must_highlight {
|
||||
self.highlight_line_self(idx, context);
|
||||
}
|
||||
let mid = get_y(upper_left) + total_rows - bottom_entity_rows;
|
||||
self.draw_list(
|
||||
grid,
|
||||
(
|
||||
upper_left,
|
||||
(get_x(bottom_right), get_y(upper_left) + mid - 1),
|
||||
),
|
||||
context,
|
||||
);
|
||||
if self.length == 0 {
|
||||
self.dirty = false;
|
||||
return;
|
||||
}
|
||||
{
|
||||
/* TODO: Move the box drawing business in separate functions */
|
||||
if get_x(upper_left) > 0 && grid[(get_x(upper_left) - 1, mid)].ch() == VERT_BOUNDARY
|
||||
{
|
||||
grid[(get_x(upper_left) - 1, mid)].set_ch(LIGHT_VERTICAL_AND_RIGHT);
|
||||
}
|
||||
|
||||
for i in get_x(upper_left)..=get_x(bottom_right) {
|
||||
grid[(i, mid)].set_ch('─');
|
||||
}
|
||||
context
|
||||
.dirty_areas
|
||||
.push_back((set_y(upper_left, mid), set_y(bottom_right, mid)));
|
||||
}
|
||||
// TODO: Make headers view configurable
|
||||
|
||||
if !self.dirty {
|
||||
if let Some(v) = self.view.as_mut() {
|
||||
v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.view = Some(ThreadView::new(Vec::new()));
|
||||
self.view = Some(ThreadView::new(self.cursor_pos));
|
||||
self.view.as_mut().unwrap().draw(
|
||||
grid,
|
||||
(set_y(upper_left, mid + 1), bottom_right),
|
||||
|
@ -542,53 +340,6 @@ impl Component for CompactMailListing {
|
|||
self.unfocused = true;
|
||||
self.dirty = true;
|
||||
}
|
||||
UIEventType::Input(Key::Char('m')) if !self.unfocused => {
|
||||
use std::process::{Command, Stdio};
|
||||
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
||||
{
|
||||
/* I tried thread::park() here but for some reason it never blocked and always
|
||||
* returned. Spinlocks are also useless because you have to keep the mutex
|
||||
* guard alive til the child process exits, which requires some effort.
|
||||
*
|
||||
* The only problem with this approach is tht the user has to send some input
|
||||
* in order for the input-thread to wake up and realise it should kill itself.
|
||||
*
|
||||
* I tried writing to stdin/tty manually but for some reason rustty didn't
|
||||
* acknowledge it.
|
||||
*/
|
||||
|
||||
/*
|
||||
* tx sends to input-thread and it kills itself.
|
||||
*/
|
||||
let tx = context.input_thread();
|
||||
tx.send(true);
|
||||
}
|
||||
let mut f = create_temp_file(&new_draft(context), None);
|
||||
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
|
||||
|
||||
// TODO: check exit status
|
||||
let mut output = Command::new("vim")
|
||||
.arg("+/^$")
|
||||
.arg(&f.path())
|
||||
.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.spawn()
|
||||
.expect("failed to execute process");
|
||||
|
||||
/*
|
||||
* Main loop will wait on children and when they reap them the loop spawns a new
|
||||
* input-thread
|
||||
*/
|
||||
context.replies.push_back(UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::Fork(ForkType::NewDraft(f, output)),
|
||||
});
|
||||
context.replies.push_back(UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::ChangeMode(UIMode::Fork),
|
||||
});
|
||||
return;
|
||||
}
|
||||
UIEventType::Input(Key::Char('i')) if self.unfocused => {
|
||||
self.unfocused = false;
|
||||
self.dirty = true;
|
||||
|
@ -649,9 +400,10 @@ impl Component for CompactMailListing {
|
|||
self.view = None;
|
||||
}
|
||||
UIEventType::MailboxUpdate((ref idxa, ref idxf)) => {
|
||||
if *idxa == self.new_cursor_pos.1 && *idxf == self.new_cursor_pos.0 {
|
||||
self.refresh_mailbox(context);
|
||||
if *idxa == self.new_cursor_pos.0 && *idxf == self.new_cursor_pos.1 {
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
UIEventType::ChangeMode(UIMode::Normal) => {
|
||||
|
@ -677,24 +429,28 @@ impl Component for CompactMailListing {
|
|||
self.refresh_mailbox(context);
|
||||
return;
|
||||
}
|
||||
Action::Sort(field, order) => {
|
||||
self.sort = (field.clone(), order.clone());
|
||||
Action::SubSort(field, order) => {
|
||||
eprintln!("SubSort {:?} , {:?}", field, order);
|
||||
self.subsort = (*field, *order);
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
Action::Sort(field, order) => {
|
||||
eprintln!("Sort {:?} , {:?}", field, order);
|
||||
self.sort = (*field, *order);
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
return;
|
||||
}
|
||||
// _ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
if let Some(ref mut v) = self.view {
|
||||
v.process_event(event, context);
|
||||
}
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||
true
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,17 +119,16 @@ impl MailListing {
|
|||
} else {
|
||||
mailbox.len()
|
||||
};
|
||||
let mut content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||
if self.length == 0 {
|
||||
write_string_to_grid(
|
||||
&format!("Folder `{}` is empty.", mailbox.folder.name()),
|
||||
&mut content,
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
true,
|
||||
);
|
||||
self.content = content;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -200,15 +199,15 @@ impl MailListing {
|
|||
len,
|
||||
// context.accounts[self.cursor_pos.0].backend.operation(envelope.hash())
|
||||
),
|
||||
&mut content,
|
||||
&mut self.content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, idx), (MAX_COLS - 1, idx)),
|
||||
false,
|
||||
);
|
||||
for x in x..MAX_COLS {
|
||||
content[(x, idx)].set_ch(' ');
|
||||
content[(x, idx)].set_bg(bg_color);
|
||||
self.content[(x, idx)].set_ch(' ');
|
||||
self.content[(x, idx)].set_bg(bg_color);
|
||||
}
|
||||
|
||||
match iter.peek() {
|
||||
|
@ -230,7 +229,7 @@ impl MailListing {
|
|||
for y in 0..=self.length {
|
||||
if idx >= self.length {
|
||||
/* No more entries left, so fill the rest of the area with empty space */
|
||||
clear_area(&mut content, ((0, y), (MAX_COLS - 1, self.length)));
|
||||
clear_area(&mut self.content, ((0, y), (MAX_COLS - 1, self.length)));
|
||||
break;
|
||||
}
|
||||
/* Write an entire line for each envelope entry. */
|
||||
|
@ -274,7 +273,7 @@ impl MailListing {
|
|||
};
|
||||
let (x, y) = write_string_to_grid(
|
||||
&MailListing::make_entry_string(envelope, idx),
|
||||
&mut content,
|
||||
&mut self.content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, y), (MAX_COLS - 1, y)),
|
||||
|
@ -282,15 +281,13 @@ impl MailListing {
|
|||
);
|
||||
|
||||
for x in x..MAX_COLS {
|
||||
content[(x, y)].set_ch(' ');
|
||||
content[(x, y)].set_bg(bg_color);
|
||||
self.content[(x, y)].set_ch(' ');
|
||||
self.content[(x, y)].set_bg(bg_color);
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.content = content;
|
||||
}
|
||||
|
||||
fn highlight_line_self(&mut self, idx: usize, context: &Context) {
|
||||
|
@ -636,8 +633,7 @@ impl Component for MailListing {
|
|||
/*
|
||||
* tx sends to input-thread and it kills itself.
|
||||
*/
|
||||
let tx = context.input_thread();
|
||||
tx.send(true);
|
||||
context.input_kill();
|
||||
}
|
||||
let mut f = create_temp_file(&new_draft(context), None);
|
||||
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
|
||||
|
|
|
@ -20,37 +20,20 @@
|
|||
*/
|
||||
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
pub struct ThreadView {
|
||||
pager: Pager,
|
||||
bytes: Vec<u8>,
|
||||
dirty: bool,
|
||||
coordinates: (usize, usize, usize),
|
||||
|
||||
}
|
||||
|
||||
impl ThreadView {
|
||||
pub fn new(bytes: Vec<u8>) -> Self {
|
||||
let mut html_filter = Command::new("w3m")
|
||||
.args(&["-I", "utf-8", "-T", "text/html"])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("Failed to start html filter process");
|
||||
html_filter
|
||||
.stdin
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.write_all(&bytes)
|
||||
.expect("Failed to write to w3m stdin");
|
||||
let mut display_text =
|
||||
String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
|
||||
display_text.push_str(&String::from_utf8_lossy(
|
||||
&html_filter.wait_with_output().unwrap().stdout,
|
||||
));
|
||||
|
||||
let buf = MailView::plain_text_to_buf(&display_text, true);
|
||||
let pager = Pager::from_buf(&buf, None);
|
||||
ThreadView { pager, bytes }
|
||||
pub fn new(coordinates: (usize, usize, usize),
|
||||
) -> Self {
|
||||
ThreadView {
|
||||
dirty: true,
|
||||
coordinates,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,39 +46,95 @@ impl fmt::Display for ThreadView {
|
|||
|
||||
impl Component for ThreadView {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
self.pager.draw(grid, area, context);
|
||||
}
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||
match event.event_type {
|
||||
UIEventType::Input(Key::Char('v')) => {
|
||||
// TODO: Optional filter that removes outgoing resource requests (images and
|
||||
// scripts)
|
||||
let binary = query_default_app("text/html");
|
||||
if let Ok(binary) = binary {
|
||||
let mut p = create_temp_file(&self.bytes, None);
|
||||
Command::new(&binary)
|
||||
.arg(p.path())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap_or_else(|_| panic!("Failed to start {}", binary.display()));
|
||||
context.temp_files.push(p);
|
||||
} else {
|
||||
context.replies.push_back(UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::StatusNotification(format!(
|
||||
"Couldn't find a default application for html files."
|
||||
)),
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap();
|
||||
let threads = &mailbox.threads;
|
||||
let container = &threads.containers()[threads.root_set()[self.coordinates.2]];
|
||||
let i = if let Some(i) = container.message() {
|
||||
i
|
||||
} else {
|
||||
threads.containers()[
|
||||
container.first_child().unwrap()
|
||||
].message().unwrap()
|
||||
};
|
||||
let envelope: &Envelope = &mailbox.collection[i];
|
||||
let (x, y) = write_string_to_grid(
|
||||
&format!("Date: {}", envelope.date_as_str()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
area,
|
||||
true,
|
||||
);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
self.pager.process_event(event, context);
|
||||
let (x, y) = write_string_to_grid(
|
||||
&format!("From: {}", envelope.from_to_string()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y + 1), bottom_right),
|
||||
true,
|
||||
);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let (x, y) = write_string_to_grid(
|
||||
&format!("To: {}", envelope.to_to_string()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y + 1), bottom_right),
|
||||
true,
|
||||
);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let (x, y) = write_string_to_grid(
|
||||
&format!("Subject: {}", envelope.subject()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y + 1), bottom_right),
|
||||
true,
|
||||
);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let (x, y) = write_string_to_grid(
|
||||
&format!("Message-ID: <{}>", envelope.message_id_raw()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, y + 1), bottom_right),
|
||||
true,
|
||||
);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
clear_area(grid, (set_y(upper_left, y + 1), set_y(bottom_right, y + 2)));
|
||||
context
|
||||
.dirty_areas
|
||||
.push_back((upper_left, set_y(bottom_right, y + 1)));
|
||||
}
|
||||
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.pager.is_dirty()
|
||||
self.dirty
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
}
|
||||
fn set_dirty(&mut self) {}
|
||||
}
|
||||
|
|
|
@ -193,6 +193,12 @@ pub struct Pager {
|
|||
content: CellBuffer,
|
||||
}
|
||||
|
||||
impl Default for Pager {
|
||||
fn default() -> Self {
|
||||
Pager::from_str("", None)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Pager {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display info
|
||||
|
@ -201,6 +207,19 @@ impl fmt::Display for Pager {
|
|||
}
|
||||
|
||||
impl Pager {
|
||||
pub fn update_from_string(&mut self, text: String) -> () {
|
||||
let lines: Vec<&str> = text.trim().split('\n').collect();
|
||||
let height = lines.len() + 1;
|
||||
let width = lines.iter().map(|l| l.len()).max().unwrap_or(0);
|
||||
let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
|
||||
//interpret_format_flowed(&text);
|
||||
Pager::print_string(&mut content, &text);
|
||||
self.content = content;
|
||||
self.height = height;
|
||||
self.width = width;
|
||||
self.dirty = true;
|
||||
self.cursor_pos = 0;
|
||||
}
|
||||
pub fn from_string(mut text: String, context: &mut Context, cursor_pos: Option<usize>) -> Self {
|
||||
let pager_filter: Option<&String> = context.settings.pager.filter.as_ref();
|
||||
//let format_flowed: bool = context.settings.pager.format_flowed;
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
*/
|
||||
|
||||
use super::*;
|
||||
use chan::Sender;
|
||||
use chan::{Receiver, Sender};
|
||||
use fnv::FnvHashMap;
|
||||
use std::io::Write;
|
||||
use std::thread;
|
||||
|
@ -38,6 +38,36 @@ use termion::raw::IntoRawMode;
|
|||
use termion::screen::AlternateScreen;
|
||||
use termion::{clear, cursor, style};
|
||||
|
||||
struct InputHandler {
|
||||
rx: Receiver<bool>,
|
||||
tx: Sender<bool>,
|
||||
}
|
||||
|
||||
impl InputHandler {
|
||||
fn restore(&self, tx: Sender<ThreadEvent>) {
|
||||
let stdin = std::io::stdin();
|
||||
let rx = self.rx.clone();
|
||||
thread::Builder::new()
|
||||
.name("input-thread".to_string())
|
||||
.spawn(move || {
|
||||
get_events(
|
||||
stdin,
|
||||
|k| {
|
||||
tx.send(ThreadEvent::Input(k));
|
||||
},
|
||||
|| {
|
||||
tx.send(ThreadEvent::UIEvent(UIEventType::ChangeMode(UIMode::Fork)));
|
||||
},
|
||||
&rx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
fn kill(&self) {
|
||||
self.tx.send(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// A context container for loaded settings, accounts, UI changes, etc.
|
||||
pub struct Context {
|
||||
pub accounts: Vec<Account>,
|
||||
|
@ -50,8 +80,10 @@ pub struct Context {
|
|||
|
||||
/// Events queue that components send back to the state
|
||||
pub replies: VecDeque<UIEvent>,
|
||||
sender: Sender<ThreadEvent>,
|
||||
receiver: Receiver<ThreadEvent>,
|
||||
input: InputHandler,
|
||||
|
||||
input_thread: chan::Sender<bool>,
|
||||
pub temp_files: Vec<File>,
|
||||
}
|
||||
|
||||
|
@ -59,8 +91,11 @@ impl Context {
|
|||
pub fn replies(&mut self) -> Vec<UIEvent> {
|
||||
self.replies.drain(0..).collect()
|
||||
}
|
||||
pub fn input_thread(&mut self) -> &mut chan::Sender<bool> {
|
||||
&mut self.input_thread
|
||||
pub fn input_kill(&self) {
|
||||
self.input.kill();
|
||||
}
|
||||
pub fn restore_input(&self) {
|
||||
self.input.restore(self.sender.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +109,6 @@ pub struct State<W: Write> {
|
|||
stdout: Option<termion::screen::AlternateScreen<termion::raw::RawTerminal<W>>>,
|
||||
child: Option<ForkType>,
|
||||
pub mode: UIMode,
|
||||
sender: Sender<ThreadEvent>,
|
||||
entities: Vec<Entity>,
|
||||
pub context: Context,
|
||||
|
||||
|
@ -99,7 +133,16 @@ impl<W: Write> Drop for State<W> {
|
|||
}
|
||||
|
||||
impl State<std::io::Stdout> {
|
||||
pub fn new(sender: Sender<ThreadEvent>, input_thread: chan::Sender<bool>) -> Self {
|
||||
pub fn new() -> Self {
|
||||
/* Create a channel to communicate with other threads. The main process is the sole receiver.
|
||||
* */
|
||||
let (sender, receiver) = chan::sync(::std::mem::size_of::<ThreadEvent>());
|
||||
|
||||
/*
|
||||
* Create async channel to block the input-thread if we need to fork and stop it from reading
|
||||
* stdin, see get_events() for details
|
||||
* */
|
||||
let input_thread = chan::async();
|
||||
let _stdout = std::io::stdout();
|
||||
_stdout.lock();
|
||||
let backends = Backends::new();
|
||||
|
@ -149,7 +192,6 @@ impl State<std::io::Stdout> {
|
|||
stdout: Some(stdout),
|
||||
child: None,
|
||||
mode: UIMode::Normal,
|
||||
sender,
|
||||
entities: Vec::with_capacity(1),
|
||||
|
||||
context: Context {
|
||||
|
@ -162,7 +204,12 @@ impl State<std::io::Stdout> {
|
|||
replies: VecDeque::with_capacity(5),
|
||||
temp_files: Vec::new(),
|
||||
|
||||
input_thread,
|
||||
sender,
|
||||
receiver,
|
||||
input: InputHandler {
|
||||
rx: input_thread.1,
|
||||
tx: input_thread.0,
|
||||
},
|
||||
},
|
||||
startup_thread: Some(startup_tx.clone()),
|
||||
threads: FnvHashMap::with_capacity_and_hasher(1, Default::default()),
|
||||
|
@ -181,11 +228,12 @@ impl State<std::io::Stdout> {
|
|||
for (y, folder) in account.backend.folders().iter().enumerate() {
|
||||
s.context.mailbox_hashes.insert(folder.hash(), (x, y));
|
||||
}
|
||||
let sender = s.sender.clone();
|
||||
let sender = s.context.sender.clone();
|
||||
account.watch(RefreshEventConsumer::new(Box::new(move |r| {
|
||||
sender.send(ThreadEvent::from(r));
|
||||
})));
|
||||
}
|
||||
s.restore_input();
|
||||
s
|
||||
}
|
||||
/*
|
||||
|
@ -198,7 +246,7 @@ impl State<std::io::Stdout> {
|
|||
self.context.accounts[idxa].reload(idxm);
|
||||
let (startup_tx, startup_rx) = chan::async();
|
||||
let startup_thread = {
|
||||
let sender = self.sender.clone();
|
||||
let sender = self.context.sender.clone();
|
||||
let startup_rx = startup_rx.clone();
|
||||
|
||||
thread::Builder::new()
|
||||
|
@ -257,7 +305,7 @@ impl State<std::io::Stdout> {
|
|||
).unwrap();
|
||||
self.flush();
|
||||
self.stdout = None;
|
||||
self.context.input_thread.send(false);
|
||||
self.context.input.kill();
|
||||
}
|
||||
pub fn switch_to_alternate_screen(&mut self) {
|
||||
let s = std::io::stdout();
|
||||
|
@ -274,6 +322,13 @@ impl State<std::io::Stdout> {
|
|||
}
|
||||
}
|
||||
impl<W: Write> State<W> {
|
||||
pub fn receiver(&self) -> Receiver<ThreadEvent> {
|
||||
self.context.receiver.clone()
|
||||
}
|
||||
pub fn restore_input(&mut self) {
|
||||
self.context.restore_input();
|
||||
}
|
||||
|
||||
/// On `SIGWNICH` the `State` redraws itself according to the new terminal size.
|
||||
pub fn update_size(&mut self) {
|
||||
let termsize = termion::terminal_size().ok();
|
||||
|
|
|
@ -20,8 +20,9 @@
|
|||
*/
|
||||
|
||||
use std;
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::io::{Write, Read};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
@ -50,6 +51,13 @@ impl File {
|
|||
pub fn path(&self) -> &PathBuf {
|
||||
&self.path
|
||||
}
|
||||
pub fn read_to_string(&self) -> String {
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let mut f = fs::File::open(&self.path).expect(&format!("Can't open {}", &self.path.display()));
|
||||
f.read_to_end(&mut buf).expect(&format!("Can't read {}", &self.path.display()));
|
||||
String::from_utf8(buf).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned `File` will be deleted when dropped, so make sure to add it on `context.temp_files`
|
||||
|
|
|
@ -75,6 +75,24 @@ macro_rules! height {
|
|||
};
|
||||
}
|
||||
|
||||
/// Get an area's width
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate ui; fn main() {
|
||||
/// use ui::*;
|
||||
///
|
||||
/// let new_area = ((0, 0), (1, 1));
|
||||
/// assert_eq!(width!(new_area), 1);
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! width{
|
||||
($a:expr) => {
|
||||
(get_x(bottom_right!($a))).saturating_sub(get_x(upper_left!($a)))
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the upper left Position of an area
|
||||
///
|
||||
/// Example:
|
||||
|
|
Loading…
Reference in New Issue