Add threaded view, notifications, pager filter
parent
51813510b1
commit
e95cc4c1e9
|
@ -28,6 +28,7 @@ notify = "4.0.1"
|
||||||
termion = "1.5.1"
|
termion = "1.5.1"
|
||||||
chan = "0.1.21"
|
chan = "0.1.21"
|
||||||
chan-signal = "0.3.1"
|
chan-signal = "0.3.1"
|
||||||
|
notify-rust = "^3"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
#lto = true
|
#lto = true
|
||||||
|
|
10
src/bin.rs
10
src/bin.rs
|
@ -22,6 +22,7 @@ extern crate melib;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate nom;
|
extern crate nom;
|
||||||
extern crate termion;
|
extern crate termion;
|
||||||
|
extern crate notify_rust;
|
||||||
|
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
use ui::*;
|
use ui::*;
|
||||||
|
@ -69,10 +70,14 @@ fn main() {
|
||||||
let menu = Entity {component: Box::new(AccountMenu::new(&state.context.accounts)) };
|
let menu = Entity {component: Box::new(AccountMenu::new(&state.context.accounts)) };
|
||||||
let listing = MailListing::new();
|
let listing = MailListing::new();
|
||||||
let b = Entity { component: Box::new(listing) };
|
let b = Entity { component: Box::new(listing) };
|
||||||
let window = Entity { component: Box::new(VSplit::new(menu,b,90)) };
|
let window = Entity { component: Box::new(VSplit::new(menu, b, 80)) };
|
||||||
let status_bar = Entity { component: Box::new(StatusBar::new(window)) };
|
let status_bar = Entity { component: Box::new(StatusBar::new(window)) };
|
||||||
state.register_entity(status_bar);
|
state.register_entity(status_bar);
|
||||||
|
|
||||||
|
|
||||||
|
let xdg_notifications = Entity { component: Box::new(ui::components::notifications::XDGNotifications {}) };
|
||||||
|
state.register_entity(xdg_notifications);
|
||||||
|
|
||||||
/* Keep track of the input mode. See ui::UIMode for details */
|
/* Keep track of the input mode. See ui::UIMode for details */
|
||||||
let mut mode: UIMode = UIMode::Normal;
|
let mut mode: UIMode = UIMode::Normal;
|
||||||
'main: loop {
|
'main: loop {
|
||||||
|
@ -94,6 +99,7 @@ fn main() {
|
||||||
UIMode::Normal => {
|
UIMode::Normal => {
|
||||||
match k {
|
match k {
|
||||||
Key::Char('q') | Key::Char('Q') => {
|
Key::Char('q') | Key::Char('Q') => {
|
||||||
|
drop(state);
|
||||||
break 'main;
|
break 'main;
|
||||||
},
|
},
|
||||||
Key::Char(';') => {
|
Key::Char(';') => {
|
||||||
|
@ -124,6 +130,8 @@ fn main() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ThreadEvent::RefreshMailbox { name : n } => {
|
ThreadEvent::RefreshMailbox { name : n } => {
|
||||||
|
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Notification(n.clone())});
|
||||||
|
state.redraw();
|
||||||
/* Don't handle this yet. */
|
/* Don't handle this yet. */
|
||||||
eprintln!("Refresh mailbox {}", n);
|
eprintln!("Refresh mailbox {}", n);
|
||||||
},
|
},
|
||||||
|
|
|
@ -87,7 +87,7 @@ pub struct AccountSettings {
|
||||||
pub folders: Vec<Folder>,
|
pub folders: Vec<Folder>,
|
||||||
format: String,
|
format: String,
|
||||||
pub sent_folder: String,
|
pub sent_folder: String,
|
||||||
threaded: bool,
|
pub threaded: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountSettings {
|
impl AccountSettings {
|
||||||
|
@ -157,8 +157,7 @@ impl Settings {
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
folders.push(Folder::new(path.to_str().unwrap().to_string(), path.file_name().unwrap().to_str().unwrap().to_string(), path_children));
|
folders.push(Folder::new(path.to_str().unwrap().to_string(), path.file_name().unwrap().to_str().unwrap().to_string(), path_children));
|
||||||
}
|
}
|
||||||
//recurse_folders(&mut folders, &x.folders);
|
//folders.sort_by(|a, b| b.name.cmp(&a.name));
|
||||||
eprintln!("folders is {:?}", folders);
|
|
||||||
s.insert(
|
s.insert(
|
||||||
id.clone(),
|
id.clone(),
|
||||||
AccountSettings {
|
AccountSettings {
|
||||||
|
@ -170,7 +169,6 @@ impl Settings {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
eprintln!("pager settings are {:?}", fs.pager);
|
|
||||||
|
|
||||||
Settings { accounts: s, pager: fs.pager }
|
Settings { accounts: s, pager: fs.pager }
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,10 @@ fn eighty_percent () -> usize {
|
||||||
80
|
80
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn none() -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Settings for the pager function.
|
/// Settings for the pager function.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct PagerSettings {
|
pub struct PagerSettings {
|
||||||
|
@ -35,4 +39,9 @@ pub struct PagerSettings {
|
||||||
/// Default: 80
|
/// Default: 80
|
||||||
#[serde(default = "eighty_percent")]
|
#[serde(default = "eighty_percent")]
|
||||||
pub pager_ratio: usize,
|
pub pager_ratio: usize,
|
||||||
|
|
||||||
|
/// A command to pipe mail output through for viewing in pager.
|
||||||
|
/// Default: None
|
||||||
|
#[serde(default = "none")]
|
||||||
|
pub filter: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub struct Account {
|
||||||
|
|
||||||
sent_folder: Option<usize>,
|
sent_folder: Option<usize>,
|
||||||
|
|
||||||
settings: AccountSettings,
|
pub settings: AccountSettings,
|
||||||
pub backend: Box<MailBackend>,
|
pub backend: Box<MailBackend>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub struct ImapOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImapOp {
|
impl ImapOp {
|
||||||
pub fn new(path: String) -> Self {
|
pub fn new(_path: String) -> Self {
|
||||||
ImapOp {
|
ImapOp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,16 +63,16 @@ pub struct ImapType {
|
||||||
|
|
||||||
|
|
||||||
impl MailBackend for ImapType {
|
impl MailBackend for ImapType {
|
||||||
fn get(&self, folder: &Folder) -> Result<Vec<Envelope>> {
|
fn get(&self, _folder: &Folder) -> Result<Vec<Envelope>> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> () {
|
fn watch(&self, _sender: RefreshEventConsumer, _folders: &[Folder]) -> () {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImapType {
|
impl ImapType {
|
||||||
pub fn new(path: &str) -> Self {
|
pub fn new(_path: &str) -> Self {
|
||||||
ImapType {
|
ImapType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub struct MboxOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MboxOp {
|
impl MboxOp {
|
||||||
pub fn new(path: String) -> Self {
|
pub fn new(_path: String) -> Self {
|
||||||
MboxOp {
|
MboxOp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,16 +65,16 @@ pub struct MboxType {
|
||||||
|
|
||||||
|
|
||||||
impl MailBackend for MboxType {
|
impl MailBackend for MboxType {
|
||||||
fn get(&self, folder: &Folder) -> Result<Vec<Envelope>> {
|
fn get(&self, _folder: &Folder) -> Result<Vec<Envelope>> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> () {
|
fn watch(&self, _sender: RefreshEventConsumer, _folders: &[Folder]) -> () {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MboxType {
|
impl MboxType {
|
||||||
pub fn new(path: &str) -> Self {
|
pub fn new(_path: &str) -> Self {
|
||||||
MboxType {
|
MboxType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ impl MailListing {
|
||||||
|
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut content = CellBuffer::new(0, 0, Cell::with_char(' '));
|
let content = CellBuffer::new(0, 0, Cell::with_char(' '));
|
||||||
MailListing {
|
MailListing {
|
||||||
cursor_pos: (0, 1, 0),
|
cursor_pos: (0, 1, 0),
|
||||||
new_cursor_pos: (0, 0, 0),
|
new_cursor_pos: (0, 0, 0),
|
||||||
|
@ -50,11 +50,13 @@ impl MailListing {
|
||||||
self.cursor_pos.2 = 0;
|
self.cursor_pos.2 = 0;
|
||||||
self.new_cursor_pos.2 = 0;
|
self.new_cursor_pos.2 = 0;
|
||||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||||
|
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||||
|
|
||||||
|
let threaded = context.accounts[self.cursor_pos.0].settings.threaded;
|
||||||
// Get mailbox as a reference.
|
// Get mailbox as a reference.
|
||||||
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap();
|
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap();
|
||||||
// Inform State that we changed the current folder view.
|
// Inform State that we changed the current folder view.
|
||||||
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox(mailbox.clone()) });
|
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox((self.cursor_pos.0, self.cursor_pos.1)) });
|
||||||
|
|
||||||
self.length = mailbox.len();
|
self.length = mailbox.len();
|
||||||
let mut content = CellBuffer::new(MAX_COLS, self.length+1, Cell::with_char(' '));
|
let mut content = CellBuffer::new(MAX_COLS, self.length+1, Cell::with_char(' '));
|
||||||
|
@ -69,43 +71,118 @@ impl MailListing {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate `CellBuffer` with every entry.
|
|
||||||
// TODO: Lazy load?
|
// TODO: Fix the threaded hell and refactor stuff into seperate functions and/or modules.
|
||||||
let mut idx = 0;
|
if threaded {
|
||||||
for y in 0..=self.length {
|
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||||
if idx >= self.length {
|
/* Draw threaded view. */
|
||||||
/* No more entries left, so fill the rest of the area with empty space */
|
let mut iter = mailbox
|
||||||
clear_area(&mut content,
|
.threaded_collection
|
||||||
((0, y), (MAX_COLS-1, self.length)));
|
.iter()
|
||||||
break;
|
.enumerate()
|
||||||
|
.peekable();
|
||||||
|
/* This is just a desugared for loop so that we can use .peek() */
|
||||||
|
while let Some((idx, i)) = iter.next() {
|
||||||
|
let container = mailbox.get_thread(*i);
|
||||||
|
let indentation = container.get_indentation();
|
||||||
|
|
||||||
|
assert_eq!(container.has_message(), true);
|
||||||
|
match iter.peek() {
|
||||||
|
Some(&(_, x))
|
||||||
|
if mailbox.get_thread(*x).get_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.get_message().unwrap()];
|
||||||
|
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
|
||||||
|
}
|
||||||
|
let x = write_string_to_grid(&MailListing::make_thread_entry(envelope, idx, indentation, container, idx == self.cursor_pos.2, &indentations),
|
||||||
|
&mut content,
|
||||||
|
fg_color,
|
||||||
|
bg_color,
|
||||||
|
((0, idx) , (MAX_COLS-1, idx)));
|
||||||
|
for x in x..MAX_COLS {
|
||||||
|
content[(x,idx)].set_ch(' ');
|
||||||
|
content[(x,idx)].set_bg(bg_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
match iter.peek() {
|
||||||
|
Some(&(_, x))
|
||||||
|
if mailbox.get_thread(*x).get_indentation() > indentation =>
|
||||||
|
{
|
||||||
|
indentations.push(false);
|
||||||
|
}
|
||||||
|
Some(&(_, x))
|
||||||
|
if mailbox.get_thread(*x).get_indentation() < indentation =>
|
||||||
|
{
|
||||||
|
for _ in 0..(indentation - mailbox.get_thread(*x).get_indentation()) {
|
||||||
|
indentations.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* Write an entire line for each envelope entry. */
|
} else {
|
||||||
let envelope: &Envelope = &mailbox.collection[idx];
|
|
||||||
|
|
||||||
let fg_color = if !envelope.is_seen() {
|
// Populate `CellBuffer` with every entry.
|
||||||
Color::Byte(0)
|
// TODO: Lazy load?
|
||||||
} else {
|
let mut idx = 0;
|
||||||
Color::Default
|
for y in 0..=self.length {
|
||||||
};
|
if idx >= self.length {
|
||||||
let bg_color = if !envelope.is_seen() {
|
/* No more entries left, so fill the rest of the area with empty space */
|
||||||
Color::Byte(251)
|
clear_area(&mut content,
|
||||||
} else if idx % 2 == 0 {
|
((0, y), (MAX_COLS-1, self.length)));
|
||||||
Color::Byte(236)
|
break;
|
||||||
} else {
|
}
|
||||||
Color::Default
|
/* Write an entire line for each envelope entry. */
|
||||||
};
|
let envelope: &Envelope = &mailbox.collection[idx];
|
||||||
let x = write_string_to_grid(&MailListing::make_entry_string(envelope, idx),
|
|
||||||
&mut content,
|
|
||||||
fg_color,
|
|
||||||
bg_color,
|
|
||||||
((0, y) , (MAX_COLS-1, y)));
|
|
||||||
|
|
||||||
for x in x..MAX_COLS {
|
let fg_color = if !envelope.is_seen() {
|
||||||
content[(x,y)].set_ch(' ');
|
Color::Byte(0)
|
||||||
content[(x,y)].set_bg(bg_color);
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let bg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(251)
|
||||||
|
} else if idx % 2 == 0 {
|
||||||
|
Color::Byte(236)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let x = write_string_to_grid(&MailListing::make_entry_string(envelope, idx),
|
||||||
|
&mut content,
|
||||||
|
fg_color,
|
||||||
|
bg_color,
|
||||||
|
((0, y) , (MAX_COLS-1, y)));
|
||||||
|
|
||||||
|
for x in x..MAX_COLS {
|
||||||
|
content[(x,y)].set_ch(' ');
|
||||||
|
content[(x,y)].set_bg(bg_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
idx+=1;
|
||||||
}
|
}
|
||||||
|
|
||||||
idx+=1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.content = content;
|
self.content = content;
|
||||||
|
@ -180,13 +257,69 @@ impl MailListing {
|
||||||
/// Create a pager for the `Envelope` currently under the cursor.
|
/// Create a pager for the `Envelope` currently under the cursor.
|
||||||
fn draw_mail_view(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
fn draw_mail_view(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
{
|
{
|
||||||
|
let threaded = context.accounts[self.cursor_pos.0].settings.threaded;
|
||||||
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap();
|
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap();
|
||||||
let envelope: &Envelope = &mailbox.collection[self.cursor_pos.2];
|
let envelope: &Envelope = if threaded {
|
||||||
|
let i = mailbox.get_threaded_mail(self.cursor_pos.2);
|
||||||
|
&mailbox.collection[i]
|
||||||
|
} else {
|
||||||
|
&mailbox.collection[self.cursor_pos.2]
|
||||||
|
};
|
||||||
|
|
||||||
self.pager = Some(Pager::new(envelope));
|
let pager_filter = context.settings.pager.filter.clone();
|
||||||
|
self.pager = Some(Pager::new(&envelope, pager_filter));
|
||||||
}
|
}
|
||||||
self.pager.as_mut().map(|p| p.draw(grid, area, context));
|
self.pager.as_mut().map(|p| p.draw(grid, area, context));
|
||||||
}
|
}
|
||||||
|
fn make_thread_entry(envelope: &Envelope, idx: usize, indent: usize,
|
||||||
|
container: &Container, highlight: bool, indentations: &Vec<bool>) -> String {
|
||||||
|
let has_sibling = container.has_sibling();
|
||||||
|
let has_parent = container.has_parent();
|
||||||
|
let show_subject = container.get_show_subject();
|
||||||
|
|
||||||
|
let fg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(0)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let bg_color = if highlight {
|
||||||
|
if !envelope.is_seen() {
|
||||||
|
Color::Byte(252)
|
||||||
|
} else if idx % 2 == 0 {
|
||||||
|
Color::Byte(236)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color::Byte(246)
|
||||||
|
};
|
||||||
|
let mut s = format!("{} {} ", idx, &envelope.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string()); // {} {:.85}",idx,),e.get_subject())
|
||||||
|
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.get_subject()));
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for MailListing {
|
impl Component for MailListing {
|
||||||
|
@ -255,8 +388,14 @@ impl Component for MailListing {
|
||||||
|
|
||||||
/* Draw header */
|
/* Draw header */
|
||||||
{
|
{
|
||||||
|
let threaded = context.accounts[self.cursor_pos.0].settings.threaded;
|
||||||
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap();
|
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap();
|
||||||
let envelope: &Envelope = &mailbox.collection[self.cursor_pos.2];
|
let envelope: &Envelope = if threaded {
|
||||||
|
let i = mailbox.get_threaded_mail(self.cursor_pos.2);
|
||||||
|
&mailbox.collection[i]
|
||||||
|
} else {
|
||||||
|
&mailbox.collection[self.cursor_pos.2]
|
||||||
|
};
|
||||||
|
|
||||||
let x = write_string_to_grid(&format!("Date: {}", envelope.get_date_as_str()),
|
let x = write_string_to_grid(&format!("Date: {}", envelope.get_date_as_str()),
|
||||||
grid,
|
grid,
|
||||||
|
@ -369,11 +508,13 @@ impl Component for MailListing {
|
||||||
match k {
|
match k {
|
||||||
'h' if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1 => {
|
'h' if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1 => {
|
||||||
self.new_cursor_pos.0 = self.cursor_pos.0 + 1;
|
self.new_cursor_pos.0 = self.cursor_pos.0 + 1;
|
||||||
|
self.new_cursor_pos.1 = 0;
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
self.refresh_mailbox(context);
|
self.refresh_mailbox(context);
|
||||||
},
|
},
|
||||||
'l' if self.cursor_pos.0 > 0 => {
|
'l' if self.cursor_pos.0 > 0 => {
|
||||||
self.new_cursor_pos.0 = self.cursor_pos.0 - 1;
|
self.new_cursor_pos.0 = self.cursor_pos.0 - 1;
|
||||||
|
self.new_cursor_pos.1 = 0;
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
self.refresh_mailbox(context);
|
self.refresh_mailbox(context);
|
||||||
},
|
},
|
||||||
|
@ -438,16 +579,16 @@ impl AccountMenu {
|
||||||
cursor: None,
|
cursor: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn highlight_folder(&mut self, m: &Mailbox) {
|
|
||||||
self.dirty = true;
|
|
||||||
self.cursor = None;
|
|
||||||
}
|
|
||||||
fn print_account(&self, grid: &mut CellBuffer, area: Area, a: &AccountMenuEntry) -> usize {
|
fn print_account(&self, grid: &mut CellBuffer, area: Area, a: &AccountMenuEntry) -> usize {
|
||||||
if !is_valid_area!(area) {
|
if !is_valid_area!(area) {
|
||||||
eprintln!("BUG: invalid area in print_account");
|
eprintln!("BUG: invalid area in print_account");
|
||||||
}
|
}
|
||||||
let upper_left = upper_left!(area);
|
let upper_left = upper_left!(area);
|
||||||
let bottom_right = bottom_right!(area);
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
||||||
|
|
||||||
|
let highlight = self.cursor.map(|(x,_)| x == a.index).unwrap_or(false);
|
||||||
|
|
||||||
let mut parents: Vec<Option<usize>> = vec!(None; a.entries.len());
|
let mut parents: Vec<Option<usize>> = vec!(None; a.entries.len());
|
||||||
|
|
||||||
for (idx, e) in a.entries.iter().enumerate() {
|
for (idx, e) in a.entries.iter().enumerate() {
|
||||||
|
@ -461,10 +602,11 @@ impl AccountMenu {
|
||||||
roots.push(idx);
|
roots.push(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
eprintln!("roots is {:?}", roots);
|
||||||
|
|
||||||
let mut inc = 0;
|
let mut inc = 0;
|
||||||
let mut depth = String::from("");
|
let mut depth = String::from("");
|
||||||
let mut s = String::from(format!("\n\n {}\n", a.name));
|
let mut s = String::from(format!("{}\n", a.name));
|
||||||
fn pop(depth: &mut String) {
|
fn pop(depth: &mut String) {
|
||||||
depth.pop();
|
depth.pop();
|
||||||
depth.pop();
|
depth.pop();
|
||||||
|
@ -489,8 +631,8 @@ impl AccountMenu {
|
||||||
}
|
}
|
||||||
for r in roots {
|
for r in roots {
|
||||||
print(r, &parents, &mut depth, &a.entries, &mut s, &mut inc);
|
print(r, &parents, &mut depth, &a.entries, &mut s, &mut inc);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
eprintln!("s = {}", s);
|
||||||
|
|
||||||
let lines: Vec<&str> = s.lines().collect();
|
let lines: Vec<&str> = s.lines().collect();
|
||||||
let lines_len = lines.len();
|
let lines_len = lines.len();
|
||||||
|
@ -504,14 +646,40 @@ impl AccountMenu {
|
||||||
} else {
|
} else {
|
||||||
format!("{}", lines[idx])
|
format!("{}", lines[idx])
|
||||||
};
|
};
|
||||||
write_string_to_grid(&s,
|
let color_fg = if highlight {
|
||||||
|
if idx > 1 && self.cursor.unwrap().1 == idx - 2 {
|
||||||
|
Color::Byte(233)
|
||||||
|
} else {
|
||||||
|
Color::Byte(15)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
|
||||||
|
let color_bg = if highlight {
|
||||||
|
if idx > 1 && self.cursor.unwrap().1 == idx - 2 {
|
||||||
|
Color::Byte(15)
|
||||||
|
} else {
|
||||||
|
Color::Byte(233)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
|
||||||
|
let x = write_string_to_grid(&s,
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(30),
|
color_fg,
|
||||||
Color::Default,
|
color_bg,
|
||||||
(set_y(upper_left, y), bottom_right));
|
(set_y(upper_left, y), bottom_right));
|
||||||
|
|
||||||
|
if highlight && idx > 1 && self.cursor.unwrap().1 == idx - 2 {
|
||||||
|
change_colors(grid, ((x, y),(get_x(bottom_right)+1, y)), color_fg , color_bg);
|
||||||
|
} else {
|
||||||
|
change_colors(grid, ((x, y),set_y(bottom_right, y)), color_fg , color_bg);
|
||||||
|
}
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
idx
|
idx - 1
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -525,19 +693,22 @@ impl Component for AccountMenu {
|
||||||
let upper_left = upper_left!(area);
|
let upper_left = upper_left!(area);
|
||||||
let bottom_right = bottom_right!(area);
|
let bottom_right = bottom_right!(area);
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
let mut y = get_y(upper_left);
|
let mut y = get_y(upper_left) + 1;
|
||||||
for a in &self.accounts {
|
for a in &self.accounts {
|
||||||
|
eprintln!("\n\naccount: {:?}\n\n", a);
|
||||||
y += self.print_account(grid,
|
y += self.print_account(grid,
|
||||||
(set_y(upper_left, y), bottom_right),
|
(set_y(upper_left, y), bottom_right),
|
||||||
&a);
|
&a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eprintln!("\n\naccountentries: {:?}\n\n", self.accounts);
|
||||||
context.dirty_areas.push_back(area);
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
||||||
match event.event_type {
|
match event.event_type {
|
||||||
UIEventType::RefreshMailbox(ref m) => {
|
UIEventType::RefreshMailbox(c) => {
|
||||||
self.highlight_folder(m);
|
self.cursor = Some(c);
|
||||||
|
self.dirty = true;
|
||||||
},
|
},
|
||||||
UIEventType::ChangeMode(UIMode::Normal) => {
|
UIEventType::ChangeMode(UIMode::Normal) => {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
pub mod mail;
|
pub mod mail;
|
||||||
|
pub mod notifications;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
pub use utilities::*;
|
pub use utilities::*;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
use notify_rust::Notification as notify_Notification;
|
||||||
|
|
||||||
|
use ui::*;
|
||||||
|
use ui::components::*;
|
||||||
|
pub struct XDGNotifications {}
|
||||||
|
|
||||||
|
impl Component for XDGNotifications {
|
||||||
|
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
||||||
|
match event.event_type {
|
||||||
|
UIEventType::Notification(ref t) => {
|
||||||
|
notify_Notification::new()
|
||||||
|
.summary("Refresh Event")
|
||||||
|
.body(t)
|
||||||
|
.icon("dialog-information")
|
||||||
|
.show().unwrap();
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -126,21 +126,31 @@ pub struct Pager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pager {
|
impl Pager {
|
||||||
pub fn new(mail: &Envelope) -> Self {
|
pub fn new(mail: &Envelope, pager_filter: Option<String>) -> Self {
|
||||||
let text = mail.get_body().get_text();
|
let mut text = mail.get_body().get_text();
|
||||||
|
if let Some(bin) = pager_filter {
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
eprintln!("{}", bin);
|
||||||
|
let mut filter_child = Command::new(bin)
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to start pager filter process");
|
||||||
|
{
|
||||||
|
let mut stdin =
|
||||||
|
filter_child.stdin.as_mut().expect("failed to open stdin");
|
||||||
|
stdin.write_all(text.as_bytes()).expect("Failed to write to stdin");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
text = String::from_utf8_lossy(&filter_child.wait_with_output().expect("Failed to wait on filter").stdout).to_string();
|
||||||
|
}
|
||||||
let lines: Vec<&str> = text.trim().split('\n').collect();
|
let lines: Vec<&str> = text.trim().split('\n').collect();
|
||||||
let height = lines.len();
|
let height = lines.len();
|
||||||
let width = lines.iter().map(|l| l.len()).max().unwrap_or(0);
|
let width = lines.iter().map(|l| l.len()).max().unwrap_or(0);
|
||||||
let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
|
let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
|
||||||
if width > 0 {
|
Pager::print_string(&mut content, &text);
|
||||||
for (i, l) in lines.iter().enumerate() {
|
|
||||||
write_string_to_grid(l,
|
|
||||||
&mut content,
|
|
||||||
Color::Default,
|
|
||||||
Color::Default,
|
|
||||||
((0, i), (width -1, i)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Pager {
|
Pager {
|
||||||
cursor_pos: 0,
|
cursor_pos: 0,
|
||||||
height: height,
|
height: height,
|
||||||
|
@ -149,6 +159,19 @@ impl Pager {
|
||||||
content: content,
|
content: content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn print_string(content: &mut CellBuffer, s: &str) {
|
||||||
|
let lines: Vec<&str> = s.trim().split('\n').collect();
|
||||||
|
let width = lines.iter().map(|l| l.len()).max().unwrap_or(0);
|
||||||
|
if width > 0 {
|
||||||
|
for (i, l) in lines.iter().enumerate() {
|
||||||
|
write_string_to_grid(l,
|
||||||
|
content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((0, i), (width -1, i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Pager {
|
impl Component for Pager {
|
||||||
|
@ -163,6 +186,7 @@ impl Component for Pager {
|
||||||
if self.height == 0 || self.height == self.cursor_pos || self.width == 0 {
|
if self.height == 0 || self.height == self.cursor_pos || self.width == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clear_area(grid,
|
clear_area(grid,
|
||||||
(upper_left, bottom_right));
|
(upper_left, bottom_right));
|
||||||
context.dirty_areas.push_back((upper_left, bottom_right));
|
context.dirty_areas.push_back((upper_left, bottom_right));
|
||||||
|
@ -228,18 +252,20 @@ impl StatusBar {
|
||||||
clear_area(grid, area);
|
clear_area(grid, area);
|
||||||
write_string_to_grid(&self.status,
|
write_string_to_grid(&self.status,
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(36),
|
Color::Byte(123),
|
||||||
Color::Default,
|
Color::Byte(26),
|
||||||
area);
|
area);
|
||||||
|
change_colors(grid, area, Color::Byte(123), Color::Byte(26));
|
||||||
context.dirty_areas.push_back(area);
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
fn draw_execute_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
fn draw_execute_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
clear_area(grid, area);
|
clear_area(grid, area);
|
||||||
write_string_to_grid(&self.ex_buffer,
|
write_string_to_grid(&self.ex_buffer,
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(124),
|
Color::Byte(219),
|
||||||
Color::Default,
|
Color::Byte(88),
|
||||||
area);
|
area);
|
||||||
|
change_colors(grid, area, Color::Byte(219), Color::Byte(88));
|
||||||
context.dirty_areas.push_back(area);
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -279,7 +305,8 @@ impl Component for StatusBar {
|
||||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
self.container.rcv_event(event, context);
|
self.container.rcv_event(event, context);
|
||||||
match event.event_type {
|
match event.event_type {
|
||||||
UIEventType::RefreshMailbox(ref m) => {
|
UIEventType::RefreshMailbox((idx_a, idx_f)) => {
|
||||||
|
let m = &context.accounts[idx_a][idx_f].as_ref().unwrap().as_ref().unwrap();
|
||||||
self.status = format!("{} |Mailbox: {}, Messages: {}, New: {}", self.mode, m.folder.get_name(), m.collection.len(), m.collection.iter().filter(|e| !e.is_seen()).count());
|
self.status = format!("{} |Mailbox: {}, Messages: {}, New: {}", self.mode, m.folder.get_name(), m.collection.len(), m.collection.iter().filter(|e| !e.is_seen()).count());
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
use std;
|
||||||
|
use nom::digit;
|
||||||
|
|
||||||
|
|
||||||
|
named!(usize_c<usize>,
|
||||||
|
map_res!(map_res!(ws!(digit), std::str::from_utf8), std::str::FromStr::from_str));
|
||||||
|
|
||||||
|
named!(pub goto<usize>,
|
||||||
|
preceded!(tag!("b "),
|
||||||
|
call!(usize_c))
|
||||||
|
);
|
|
@ -93,12 +93,13 @@ impl From<RefreshEvent> for ThreadEvent {
|
||||||
pub enum UIEventType {
|
pub enum UIEventType {
|
||||||
Input(Key),
|
Input(Key),
|
||||||
ExInput(Key),
|
ExInput(Key),
|
||||||
RefreshMailbox(Mailbox),
|
RefreshMailbox((usize,usize)),
|
||||||
//Quit?
|
//Quit?
|
||||||
Resize,
|
Resize,
|
||||||
ChangeMailbox(usize),
|
ChangeMailbox(usize),
|
||||||
ChangeMode(UIMode),
|
ChangeMode(UIMode),
|
||||||
Command(String),
|
Command(String),
|
||||||
|
Notification(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -123,6 +124,13 @@ impl fmt::Display for UIMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Notification {
|
||||||
|
title: String,
|
||||||
|
content: String,
|
||||||
|
|
||||||
|
timestamp: std::time::Instant,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
pub accounts: Vec<Account>,
|
pub accounts: Vec<Account>,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
|
@ -140,6 +148,7 @@ impl Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct State<W: Write> {
|
pub struct State<W: Write> {
|
||||||
cols: usize,
|
cols: usize,
|
||||||
rows: usize,
|
rows: usize,
|
||||||
|
@ -169,6 +178,8 @@ impl<W: Write> State<W> {
|
||||||
let termrows = termsize.map(|(_,h)| h);
|
let termrows = termsize.map(|(_,h)| h);
|
||||||
let cols = termcols.unwrap_or(0) as usize;
|
let cols = termcols.unwrap_or(0) as usize;
|
||||||
let rows = termrows.unwrap_or(0) as usize;
|
let rows = termrows.unwrap_or(0) as usize;
|
||||||
|
let mut accounts: Vec<Account> = settings.accounts.iter().map(|(n, a_s)| { Account::new(n.to_string(), a_s.clone(), &backends) }).collect();
|
||||||
|
accounts.sort_by(|a,b| a.get_name().cmp(&b.get_name()) );
|
||||||
let mut s = State {
|
let mut s = State {
|
||||||
cols: cols,
|
cols: cols,
|
||||||
rows: rows,
|
rows: rows,
|
||||||
|
@ -178,7 +189,7 @@ impl<W: Write> State<W> {
|
||||||
entities: Vec::with_capacity(1),
|
entities: Vec::with_capacity(1),
|
||||||
|
|
||||||
context: Context {
|
context: Context {
|
||||||
accounts: settings.accounts.iter().map(|(n, a_s)| { Account::new(n.to_string(), a_s.clone(), &backends) }).collect(),
|
accounts: accounts,
|
||||||
backends: backends,
|
backends: backends,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
dirty_areas: VecDeque::with_capacity(5),
|
dirty_areas: VecDeque::with_capacity(5),
|
||||||
|
@ -197,7 +208,6 @@ impl<W: Write> State<W> {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
pub fn update_size(&mut self) {
|
pub fn update_size(&mut self) {
|
||||||
/* update dimensions. TODO: Only do that in size change events. ie SIGWINCH */
|
|
||||||
let termsize = termion::terminal_size().ok();
|
let termsize = termion::terminal_size().ok();
|
||||||
let termcols = termsize.map(|(w,_)| w);
|
let termcols = termsize.map(|(w,_)| w);
|
||||||
let termrows = termsize.map(|(_,h)| h);
|
let termrows = termsize.map(|(_,h)| h);
|
||||||
|
@ -304,17 +314,20 @@ impl<W: Write> State<W> {
|
||||||
self.entities[i].rcv_event(&event, &mut self.context);
|
self.entities[i].rcv_event(&event, &mut self.context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to load a mailbox's content
|
/// Tries to load a mailbox's content
|
||||||
pub fn refresh_mailbox(&mut self, account_idx: usize, folder_idx: usize) {
|
pub fn refresh_mailbox(&mut self, account_idx: usize, folder_idx: usize) {
|
||||||
let mailbox = match &mut self.context.accounts[account_idx][folder_idx] {
|
let flag = match &mut self.context.accounts[account_idx][folder_idx] {
|
||||||
Some(Ok(v)) => { Some(v.clone()) },
|
Some(Ok(_)) => {
|
||||||
Some(Err(e)) => { eprintln!("error {:?}", e); None },
|
true
|
||||||
None => { eprintln!("None"); None },
|
},
|
||||||
|
Some(Err(e)) => { eprintln!("error {:?}", e); false },
|
||||||
|
None => { eprintln!("None"); false },
|
||||||
};
|
};
|
||||||
if let Some(m) = mailbox {
|
if flag {
|
||||||
self.rcv_event(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox(m) });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
self.rcv_event(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox((account_idx, folder_idx)) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue