parent
6dd247b371
commit
8c98d3a5a0
22
Cargo.toml
22
Cargo.toml
|
@ -3,32 +3,16 @@ name = "meli"
|
|||
version = "0.1.0"
|
||||
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
||||
|
||||
[lib]
|
||||
name = "melib"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "meli"
|
||||
path = "src/bin.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
xdg = "2.1.0"
|
||||
config = "0.6"
|
||||
serde_derive = "^1.0.8"
|
||||
serde = "^1.0.8"
|
||||
nom = "3.2.0"
|
||||
memmap = "0.5.2"
|
||||
base64 = "*"
|
||||
crossbeam = "^0.3.0"
|
||||
fnv = "1.0.3"
|
||||
encoding = "0.2.33"
|
||||
bitflags = "1.0"
|
||||
notify = "4.0.1"
|
||||
termion = "1.5.1"
|
||||
chan = "0.1.21"
|
||||
chan-signal = "0.3.1"
|
||||
notify-rust = "^3"
|
||||
melib = { path = "melib", version = "*" }
|
||||
|
||||
ui = { path = "ui", version = "*" }
|
||||
|
||||
[profile.release]
|
||||
#lto = true
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "melib"
|
||||
version = "0.0.1" #:version
|
||||
authors = []
|
||||
workspace = ".."
|
||||
|
||||
[dependencies]
|
||||
notify = "4.0.1"
|
||||
notify-rust = "^3"
|
||||
crossbeam = "^0.3.0"
|
||||
fnv = "1.0.3"
|
||||
config = "0.6"
|
||||
xdg = "2.1.0"
|
||||
chrono = "0.4"
|
||||
serde_derive = "^1.0.8"
|
||||
serde = "^1.0.8"
|
||||
nom = "3.2.0"
|
||||
memmap = "0.5.2"
|
||||
base64 = "*"
|
||||
encoding = "0.2.33"
|
||||
bitflags = "1.0"
|
||||
termion = "1.5.1"
|
|
@ -204,7 +204,7 @@ impl AttachmentBuilder {
|
|||
};
|
||||
AttachmentType::Multipart {
|
||||
of_type: multipart_type,
|
||||
subattachments: Attachment::subattachments(&self.raw, b),
|
||||
subattachments: Self::subattachments(&self.raw, b),
|
||||
}
|
||||
}
|
||||
ContentType::Unsupported { ref tag } => AttachmentType::Data { tag: tag.clone() },
|
||||
|
@ -216,6 +216,48 @@ impl AttachmentBuilder {
|
|||
attachment_type: attachment_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Attachment> {
|
||||
let boundary_length = boundary.len();
|
||||
match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result()
|
||||
{
|
||||
Ok(attachments) => {
|
||||
let mut vec = Vec::with_capacity(attachments.len());
|
||||
for a in attachments {
|
||||
let (headers, body) = match parser::attachment(a).to_full_result() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
eprintln!("error in parsing attachment");
|
||||
eprintln!("\n-------------------------------");
|
||||
eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a));
|
||||
eprintln!("-------------------------------\n");
|
||||
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mut builder = AttachmentBuilder::new(body);
|
||||
for (name, value) in headers {
|
||||
if name.eq_ignore_ascii_case("content-type") {
|
||||
builder.content_type(value);
|
||||
} else if name.eq_ignore_ascii_case("content-transfer-encoding") {
|
||||
builder.content_transfer_encoding(value);
|
||||
}
|
||||
}
|
||||
vec.push(builder.build());
|
||||
}
|
||||
vec
|
||||
}
|
||||
a => {
|
||||
eprintln!(
|
||||
"error {:?}\n\traw: {:?}\n\tboundary: {:?}",
|
||||
a,
|
||||
::std::str::from_utf8(raw).unwrap(),
|
||||
boundary
|
||||
);
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -267,45 +309,28 @@ impl Attachment {
|
|||
pub fn get_tag(&self) -> String {
|
||||
format!("{}/{}", self.content_type.0, self.content_type.1).to_string()
|
||||
}
|
||||
pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Attachment> {
|
||||
let boundary_length = boundary.len();
|
||||
match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result()
|
||||
{
|
||||
Ok(attachments) => {
|
||||
let mut vec = Vec::with_capacity(attachments.len());
|
||||
for a in attachments {
|
||||
let (headers, body) = match parser::attachment(a).to_full_result() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
eprintln!("error in parsing attachment");
|
||||
eprintln!("\n-------------------------------");
|
||||
eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a));
|
||||
eprintln!("-------------------------------\n");
|
||||
pub fn count_attachments(&mut self) -> usize {
|
||||
let mut counter = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let mut builder = AttachmentBuilder::new(body);
|
||||
for (name, value) in headers {
|
||||
if name.eq_ignore_ascii_case("content-type") {
|
||||
builder.content_type(value);
|
||||
} else if name.eq_ignore_ascii_case("content-transfer-encoding") {
|
||||
builder.content_transfer_encoding(value);
|
||||
}
|
||||
}
|
||||
vec.push(builder.build());
|
||||
fn count_recursive(att: &Attachment, counter: &mut usize) {
|
||||
match att.attachment_type {
|
||||
AttachmentType::Data { .. } => {
|
||||
*counter += 1;
|
||||
}
|
||||
vec
|
||||
}
|
||||
a => {
|
||||
eprintln!(
|
||||
"error in 469 {:?}\n\traw: {:?}\n\tboundary: {:?}",
|
||||
a,
|
||||
::std::str::from_utf8(raw).unwrap(),
|
||||
boundary
|
||||
);
|
||||
Vec::new()
|
||||
AttachmentType::Text { .. } => {
|
||||
}
|
||||
AttachmentType::Multipart {
|
||||
of_type: ref multipart_type,
|
||||
subattachments: ref sub_att_vec,
|
||||
} => if *multipart_type != MultipartType::Alternative {
|
||||
for a in sub_att_vec {
|
||||
count_recursive(a, counter);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
count_recursive(&self, &mut counter);
|
||||
counter
|
||||
}
|
||||
}
|
|
@ -25,12 +25,8 @@ The mail handling stuff is done in the `melib` crate which includes all backend
|
|||
*/
|
||||
|
||||
extern crate melib;
|
||||
#[macro_use]
|
||||
extern crate nom;
|
||||
extern crate termion;
|
||||
extern crate notify_rust;
|
||||
extern crate ui;
|
||||
|
||||
pub mod ui;
|
||||
use ui::*;
|
||||
pub use melib::*;
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
#![cfg(feature = "python")]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
|
||||
#[pymodinit(pythonmeli)]
|
||||
fn pythonmeli(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
// pyo3 aware function. All of our python interface could be declared in a separate module.
|
||||
// Note that the `#[pyfn()]` annotation automatically converts the arguments from
|
||||
// Python objects to Rust values; and the Rust return value back into a Python object.
|
||||
#[pyfn(m, "sum_as_string")]
|
||||
fn sum_as_string_py(_py: Python, a:i64, b:i64) -> PyResult<String> {
|
||||
let out = sum_as_string(a, b);
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// logic implemented as a normal rust function
|
||||
fn sum_as_string(a:i64, b:i64) -> String {
|
||||
format!("{}", a + b).to_string()
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "ui"
|
||||
version = "0.0.1" #:version
|
||||
authors = []
|
||||
workspace = ".."
|
||||
|
||||
[dependencies]
|
||||
melib = { path = "../melib", version = "*" }
|
||||
termion = "1.5.1"
|
||||
chan = "0.1.21"
|
||||
notify = "4.0.1"
|
||||
notify-rust = "^3"
|
||||
nom = "3.2.0"
|
|
@ -1,9 +1,4 @@
|
|||
/*! Entities that handle Mail specific functions.
|
||||
*/
|
||||
use ui::components::*;
|
||||
use ui::cells::*;
|
||||
|
||||
|
||||
use super::*;
|
||||
const MAX_COLS: usize = 500;
|
||||
|
||||
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
|
||||
|
@ -30,9 +25,6 @@ impl MailListing {
|
|||
format!("{} {} {:.85}",idx,&e.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string(),e.get_subject())
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
pub fn new() -> Self {
|
||||
let content = CellBuffer::new(0, 0, Cell::with_char(' '));
|
||||
MailListing {
|
||||
|
@ -60,7 +52,12 @@ impl MailListing {
|
|||
// Inform State that we changed the current folder view.
|
||||
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 = if threaded {
|
||||
mailbox.threaded_collection.len()
|
||||
|
||||
} else {
|
||||
mailbox.len()
|
||||
};
|
||||
let mut content = CellBuffer::new(MAX_COLS, self.length+1, Cell::with_char(' '));
|
||||
if self.length == 0 {
|
||||
write_string_to_grid(&format!("Folder `{}` is empty.",
|
||||
|
@ -68,7 +65,8 @@ impl MailListing {
|
|||
&mut content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
((0, 0), (MAX_COLS-1, 0)));
|
||||
((0, 0), (MAX_COLS-1, 0)),
|
||||
true);
|
||||
self.content = content;
|
||||
return;
|
||||
}
|
||||
|
@ -118,11 +116,12 @@ impl MailListing {
|
|||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let x = write_string_to_grid(&MailListing::make_thread_entry(envelope, idx, indentation, container, &indentations),
|
||||
let (x, y) = write_string_to_grid(&MailListing::make_thread_entry(envelope, idx, indentation, container, &indentations),
|
||||
&mut content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, idx) , (MAX_COLS-1, idx)));
|
||||
((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);
|
||||
|
@ -171,11 +170,12 @@ impl MailListing {
|
|||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let x = write_string_to_grid(&MailListing::make_entry_string(envelope, idx),
|
||||
let (x, y)= write_string_to_grid(&MailListing::make_entry_string(envelope, idx),
|
||||
&mut content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, y) , (MAX_COLS-1, y)));
|
||||
((0, y) , (MAX_COLS-1, y)),
|
||||
false);
|
||||
|
||||
for x in x..MAX_COLS {
|
||||
content[(x,y)].set_ch(' ');
|
||||
|
@ -189,9 +189,15 @@ impl MailListing {
|
|||
|
||||
self.content = content;
|
||||
}
|
||||
fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &mut Context) {
|
||||
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap();
|
||||
let envelope: &Envelope = &mailbox.collection[idx];
|
||||
fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
|
||||
let threaded = context.accounts[self.cursor_pos.0].settings.threaded;
|
||||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap();
|
||||
let envelope: &Envelope = if threaded {
|
||||
let i = mailbox.get_threaded_mail(idx);
|
||||
&mailbox.collection[i]
|
||||
} else {
|
||||
&mailbox.collection[idx]
|
||||
};
|
||||
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
|
@ -202,7 +208,7 @@ impl MailListing {
|
|||
Color::Byte(246)
|
||||
} else {
|
||||
if !envelope.is_seen() {
|
||||
Color::Byte(252)
|
||||
Color::Byte(251)
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
|
@ -256,40 +262,6 @@ impl MailListing {
|
|||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
||||
/// Create a pager for the `Envelope` currently under the cursor.
|
||||
fn draw_header_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 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 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));
|
||||
}
|
||||
/// Create a pager for the `Envelope` currently under the cursor.
|
||||
fn draw_attachment_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 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 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));
|
||||
}
|
||||
/// Create a pager for the `Envelope` currently under the cursor.
|
||||
fn draw_mail_view(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
{
|
||||
|
@ -335,6 +307,8 @@ impl MailListing {
|
|||
}
|
||||
s.push('─'); s.push('>');
|
||||
}
|
||||
s.push_str(&format!(" {}∞ ", envelope.get_body().count_attachments()));
|
||||
|
||||
if show_subject {
|
||||
s.push_str(&format!("{:.85}", envelope.get_subject()));
|
||||
}
|
||||
|
@ -360,37 +334,23 @@ impl Component for MailListing {
|
|||
clear_area(grid, area);
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
// TODO: Make this configurable. User should be able to choose what headers to display,
|
||||
// and toggle between full header view and custom header view like in mutt.
|
||||
let headers_rows: usize = 6;
|
||||
|
||||
/* Render the mail body in a pager, basically copy what HSplit does */
|
||||
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
||||
let pager_ratio = context.settings.pager.pager_ratio;
|
||||
let mut bottom_entity_rows = (pager_ratio*total_rows )/100;
|
||||
if bottom_entity_rows < headers_rows + 2 {
|
||||
bottom_entity_rows = headers_rows + 2;
|
||||
}
|
||||
let bottom_entity_rows = (pager_ratio*total_rows )/100;
|
||||
|
||||
if bottom_entity_rows > total_rows {
|
||||
clear_area(grid, area);
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
let mid = get_y(upper_left) + total_rows - bottom_entity_rows;
|
||||
|
||||
if !self.dirty {
|
||||
if let Some(ref mut p) = self.pager {
|
||||
p.draw(grid,
|
||||
((get_x(upper_left), get_y(upper_left) + mid + headers_rows + 1), bottom_right),
|
||||
context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.dirty = false;
|
||||
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;
|
||||
}
|
||||
{
|
||||
|
@ -405,8 +365,10 @@ impl Component for MailListing {
|
|||
grid[(i, mid)].set_ch('─');
|
||||
}
|
||||
}
|
||||
// TODO: Make headers view configurable
|
||||
|
||||
/* Draw header */
|
||||
let y =
|
||||
{
|
||||
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();
|
||||
|
@ -417,66 +379,82 @@ impl Component for MailListing {
|
|||
&mailbox.collection[self.cursor_pos.2]
|
||||
};
|
||||
|
||||
let x = write_string_to_grid(&format!("Date: {}", envelope.get_date_as_str()),
|
||||
let (x,y) = write_string_to_grid(&format!("Date: {}", envelope.get_date_as_str()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, mid+1), set_y(bottom_right, mid+1)));
|
||||
(set_y(upper_left, mid+1), bottom_right),
|
||||
true);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, mid+1)].set_ch(' ');
|
||||
grid[(x, mid+1)].set_bg(Color::Default);
|
||||
grid[(x, mid+1)].set_fg(Color::Default);
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let x = write_string_to_grid(&format!("From: {}", envelope.get_from()),
|
||||
let (x,y) = write_string_to_grid(&format!("From: {}", envelope.get_from()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, mid+2), set_y(bottom_right, mid+2)));
|
||||
(set_y(upper_left, y+1), bottom_right),
|
||||
true);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, mid+2)].set_ch(' ');
|
||||
grid[(x, mid+2)].set_bg(Color::Default);
|
||||
grid[(x, mid+2)].set_fg(Color::Default);
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let x = write_string_to_grid(&format!("To: {}", envelope.get_to()),
|
||||
let (x,y) = write_string_to_grid(&format!("To: {}", envelope.get_to()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, mid+3), set_y(bottom_right, mid+3)));
|
||||
(set_y(upper_left, y+1), bottom_right),
|
||||
true);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, mid+3)].set_ch(' ');
|
||||
grid[(x, mid+3)].set_bg(Color::Default);
|
||||
grid[(x, mid+3)].set_fg(Color::Default);
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let x = write_string_to_grid(&format!("Subject: {}", envelope.get_subject()),
|
||||
let (x,y) = write_string_to_grid(&format!("Subject: {}", envelope.get_subject()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, mid+4), set_y(bottom_right, mid+4)));
|
||||
(set_y(upper_left, y+1), bottom_right),
|
||||
true);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, mid+4)].set_ch(' ');
|
||||
grid[(x, mid+4)].set_bg(Color::Default);
|
||||
grid[(x, mid+4)].set_fg(Color::Default);
|
||||
grid[(x, y)].set_ch(' ');
|
||||
grid[(x, y)].set_bg(Color::Default);
|
||||
grid[(x, y)].set_fg(Color::Default);
|
||||
}
|
||||
let x = write_string_to_grid(&format!("Message-ID: {}", envelope.get_message_id_raw()),
|
||||
let (x, y) = write_string_to_grid(&format!("Message-ID: {}", envelope.get_message_id_raw()),
|
||||
grid,
|
||||
Color::Byte(33),
|
||||
Color::Default,
|
||||
(set_y(upper_left, mid+5), set_y(bottom_right, mid+5)));
|
||||
(set_y(upper_left, y+1), bottom_right),
|
||||
true);
|
||||
for x in x..=get_x(bottom_right) {
|
||||
grid[(x, mid+5)].set_ch(' ');
|
||||
grid[(x, mid+5)].set_bg(Color::Default);
|
||||
grid[(x, mid+5)].set_fg(Color::Default);
|
||||
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((set_y(upper_left, mid), set_y(bottom_right, y+1)));
|
||||
y + 2
|
||||
};
|
||||
|
||||
|
||||
if !self.dirty {
|
||||
if let Some(ref mut p) = self.pager {
|
||||
p.draw(grid,
|
||||
((get_x(upper_left), y), bottom_right),
|
||||
context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
clear_area(grid,
|
||||
(set_y(upper_left, mid+headers_rows), set_y(bottom_right, mid+headers_rows)));
|
||||
|
||||
|
||||
context.dirty_areas.push_back((set_y(upper_left, mid), set_y(bottom_right, mid+headers_rows)));
|
||||
self.dirty = false;
|
||||
|
||||
/* Draw body */
|
||||
self.draw_mail_view(grid,
|
||||
((get_x(upper_left), get_y(upper_left) + mid + headers_rows + 1), bottom_right),
|
||||
((get_x(upper_left), y), bottom_right),
|
||||
context);
|
||||
|
||||
}
|
||||
|
@ -563,180 +541,3 @@ impl Component for MailListing {
|
|||
self.dirty || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AccountMenuEntry {
|
||||
name: String,
|
||||
index: usize,
|
||||
entries: Vec<(usize, Folder)>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AccountMenu {
|
||||
accounts: Vec<AccountMenuEntry>,
|
||||
dirty: bool,
|
||||
cursor: Option<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl AccountMenu {
|
||||
pub fn new(accounts: &Vec<Account>) -> Self {
|
||||
let accounts = accounts.iter().enumerate().map(|(i, a)| {
|
||||
AccountMenuEntry {
|
||||
name: a.get_name().to_string(),
|
||||
index: i,
|
||||
entries: {
|
||||
let mut entries = Vec::with_capacity(a.len());
|
||||
for (idx, acc) in a.list_folders().iter().enumerate() {
|
||||
entries.push((idx, acc.clone()));
|
||||
}
|
||||
entries}
|
||||
}
|
||||
}).collect();
|
||||
AccountMenu {
|
||||
accounts: accounts,
|
||||
dirty: true,
|
||||
cursor: None,
|
||||
}
|
||||
}
|
||||
fn print_account(&self, grid: &mut CellBuffer, area: Area, a: &AccountMenuEntry) -> usize {
|
||||
if !is_valid_area!(area) {
|
||||
eprintln!("BUG: invalid area in print_account");
|
||||
}
|
||||
let upper_left = upper_left!(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());
|
||||
|
||||
for (idx, e) in a.entries.iter().enumerate() {
|
||||
for c in e.1.get_children() {
|
||||
parents[*c] = Some(idx);
|
||||
}
|
||||
}
|
||||
let mut roots = Vec::new();
|
||||
for (idx, c) in parents.iter().enumerate() {
|
||||
if c.is_none() {
|
||||
roots.push(idx);
|
||||
}
|
||||
}
|
||||
|
||||
let mut inc = 0;
|
||||
let mut depth = String::from("");
|
||||
let mut s = String::from(format!("{}\n", a.name));
|
||||
fn pop(depth: &mut String) {
|
||||
depth.pop();
|
||||
depth.pop();
|
||||
}
|
||||
|
||||
fn push(depth: &mut String, c: char) {
|
||||
depth.push(c);
|
||||
}
|
||||
|
||||
fn print(root: usize, parents: &Vec<Option<usize>>, depth: &mut String, entries: &Vec<(usize, Folder)>, s: &mut String, inc: &mut usize) -> () {
|
||||
let len = s.len();
|
||||
s.insert_str(len, &format!("{} {}\n ", *inc, &entries[root].1.get_name()));
|
||||
*inc += 1;
|
||||
let children_no = entries[root].1.get_children().len();
|
||||
for (idx, child) in entries[root].1.get_children().iter().enumerate() {
|
||||
let len = s.len();
|
||||
s.insert_str(len, &format!("{}├─", depth));
|
||||
push(depth, if idx == children_no - 1 {'│'} else { ' ' });
|
||||
print(*child, parents, depth, entries, s, inc);
|
||||
pop(depth);
|
||||
}
|
||||
}
|
||||
for r in roots {
|
||||
print(r, &parents, &mut depth, &a.entries, &mut s, &mut inc);
|
||||
}
|
||||
|
||||
let lines: Vec<&str> = s.lines().collect();
|
||||
let lines_len = lines.len();
|
||||
let mut idx = 0;
|
||||
for y in get_y(upper_left)..get_y(bottom_right) {
|
||||
if idx == lines_len {
|
||||
break;
|
||||
}
|
||||
let s = if idx == lines_len - 2 {
|
||||
format!("{}", lines[idx].replace("├", "└"))
|
||||
} else {
|
||||
format!("{}", lines[idx])
|
||||
};
|
||||
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,
|
||||
color_fg,
|
||||
color_bg,
|
||||
(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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for AccountMenu {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !self.is_dirty() {
|
||||
return;
|
||||
}
|
||||
clear_area(grid, area);
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
self.dirty = false;
|
||||
let mut y = get_y(upper_left) + 1;
|
||||
for a in &self.accounts {
|
||||
y += self.print_account(grid,
|
||||
(set_y(upper_left, y), bottom_right),
|
||||
&a);
|
||||
}
|
||||
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
||||
match event.event_type {
|
||||
UIEventType::RefreshMailbox(c) => {
|
||||
self.cursor = Some(c);
|
||||
self.dirty = true;
|
||||
},
|
||||
UIEventType::ChangeMode(UIMode::Normal) => {
|
||||
self.dirty = true;
|
||||
},
|
||||
UIEventType::Resize => {
|
||||
self.dirty = true;
|
||||
},
|
||||
_ => {
|
||||
},
|
||||
}
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
/*! Entities that handle Mail specific functions.
|
||||
*/
|
||||
use super::*;
|
||||
|
||||
pub mod listing;
|
||||
pub use listing::*;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AccountMenuEntry {
|
||||
name: String,
|
||||
index: usize,
|
||||
entries: Vec<(usize, Folder)>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AccountMenu {
|
||||
accounts: Vec<AccountMenuEntry>,
|
||||
dirty: bool,
|
||||
cursor: Option<(usize, usize)>,
|
||||
}
|
||||
|
||||
impl AccountMenu {
|
||||
pub fn new(accounts: &Vec<Account>) -> Self {
|
||||
let accounts = accounts.iter().enumerate().map(|(i, a)| {
|
||||
AccountMenuEntry {
|
||||
name: a.get_name().to_string(),
|
||||
index: i,
|
||||
entries: {
|
||||
let mut entries = Vec::with_capacity(a.len());
|
||||
for (idx, acc) in a.list_folders().iter().enumerate() {
|
||||
entries.push((idx, acc.clone()));
|
||||
}
|
||||
entries}
|
||||
}
|
||||
}).collect();
|
||||
AccountMenu {
|
||||
accounts: accounts,
|
||||
dirty: true,
|
||||
cursor: None,
|
||||
}
|
||||
}
|
||||
fn print_account(&self, grid: &mut CellBuffer, area: Area, a: &AccountMenuEntry) -> usize {
|
||||
if !is_valid_area!(area) {
|
||||
eprintln!("BUG: invalid area in print_account");
|
||||
}
|
||||
let upper_left = upper_left!(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());
|
||||
|
||||
for (idx, e) in a.entries.iter().enumerate() {
|
||||
for c in e.1.get_children() {
|
||||
parents[*c] = Some(idx);
|
||||
}
|
||||
}
|
||||
let mut roots = Vec::new();
|
||||
for (idx, c) in parents.iter().enumerate() {
|
||||
if c.is_none() {
|
||||
roots.push(idx);
|
||||
}
|
||||
}
|
||||
|
||||
let mut inc = 0;
|
||||
let mut depth = String::from("");
|
||||
let mut s = String::from(format!("{}\n", a.name));
|
||||
fn pop(depth: &mut String) {
|
||||
depth.pop();
|
||||
depth.pop();
|
||||
}
|
||||
|
||||
fn push(depth: &mut String, c: char) {
|
||||
depth.push(c);
|
||||
}
|
||||
|
||||
fn print(root: usize, parents: &Vec<Option<usize>>, depth: &mut String, entries: &Vec<(usize, Folder)>, s: &mut String, inc: &mut usize) -> () {
|
||||
let len = s.len();
|
||||
s.insert_str(len, &format!("{} {}\n ", *inc, &entries[root].1.get_name()));
|
||||
*inc += 1;
|
||||
let children_no = entries[root].1.get_children().len();
|
||||
for (idx, child) in entries[root].1.get_children().iter().enumerate() {
|
||||
let len = s.len();
|
||||
s.insert_str(len, &format!("{}├─", depth));
|
||||
push(depth, if idx == children_no - 1 {'│'} else { ' ' });
|
||||
print(*child, parents, depth, entries, s, inc);
|
||||
pop(depth);
|
||||
}
|
||||
}
|
||||
for r in roots {
|
||||
print(r, &parents, &mut depth, &a.entries, &mut s, &mut inc);
|
||||
}
|
||||
|
||||
let lines: Vec<&str> = s.lines().collect();
|
||||
let lines_len = lines.len();
|
||||
let mut idx = 0;
|
||||
for y in get_y(upper_left)..get_y(bottom_right) {
|
||||
if idx == lines_len {
|
||||
break;
|
||||
}
|
||||
let s = if idx == lines_len - 2 {
|
||||
format!("{}", lines[idx].replace("├", "└"))
|
||||
} else {
|
||||
format!("{}", lines[idx])
|
||||
};
|
||||
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,
|
||||
color_fg,
|
||||
color_bg,
|
||||
(set_y(upper_left, y), bottom_right),
|
||||
false);
|
||||
|
||||
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;
|
||||
}
|
||||
if idx == 0 {
|
||||
0
|
||||
} else {
|
||||
idx - 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for AccountMenu {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !self.is_dirty() {
|
||||
return;
|
||||
}
|
||||
clear_area(grid, area);
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
self.dirty = false;
|
||||
let mut y = get_y(upper_left) + 1;
|
||||
for a in &self.accounts {
|
||||
y += self.print_account(grid,
|
||||
(set_y(upper_left, y), bottom_right),
|
||||
&a);
|
||||
}
|
||||
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
||||
match event.event_type {
|
||||
UIEventType::RefreshMailbox(c) => {
|
||||
self.cursor = Some(c);
|
||||
self.dirty = true;
|
||||
},
|
||||
UIEventType::ChangeMode(UIMode::Normal) => {
|
||||
self.dirty = true;
|
||||
},
|
||||
UIEventType::Resize => {
|
||||
self.dirty = true;
|
||||
},
|
||||
_ => {
|
||||
},
|
||||
}
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
}
|
|
@ -25,11 +25,11 @@
|
|||
See the `Component` Trait for more details.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
pub mod utilities;
|
||||
pub mod mail;
|
||||
pub mod notifications;
|
||||
|
||||
use super::*;
|
||||
pub use utilities::*;
|
||||
pub use mail::*;
|
||||
|
||||
|
@ -126,34 +126,43 @@ pub fn change_colors(grid: &mut CellBuffer, area: Area, fg_color: Color, bg_colo
|
|||
|
||||
|
||||
/// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed colors.
|
||||
fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_color: Color, area: Area) -> usize {
|
||||
fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_color: Color, area: Area, line_break: bool) -> Pos {
|
||||
let bounds = grid.size();
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
let (mut x, mut y) = upper_left;
|
||||
if y > (get_y(bottom_right)) || x > get_x(bottom_right) ||
|
||||
y > get_y(bounds) || x > get_x(bounds) {
|
||||
return 0;
|
||||
eprintln!(" Invalid area with string {} and area {:?}", s, area);
|
||||
return (x, y);
|
||||
}
|
||||
for c in s.chars() {
|
||||
grid[(x,y)].set_ch(c);
|
||||
grid[(x,y)].set_fg(fg_color);
|
||||
grid[(x,y)].set_bg(bg_color);
|
||||
x += 1;
|
||||
for l in s.lines() {
|
||||
'char: for c in l.chars() {
|
||||
grid[(x,y)].set_ch(c);
|
||||
grid[(x,y)].set_fg(fg_color);
|
||||
grid[(x,y)].set_bg(bg_color);
|
||||
x += 1;
|
||||
|
||||
if x == (get_x(bottom_right))+1 || x > get_x(bounds) {
|
||||
x = get_x(upper_left);
|
||||
y += 1;
|
||||
if y == (get_y(bottom_right))+1 || y > get_y(bounds) {
|
||||
return x;
|
||||
if x == (get_x(bottom_right))+1 || x > get_x(bounds) {
|
||||
x = get_x(upper_left);
|
||||
y += 1;
|
||||
if y >= (get_y(bottom_right)) || y > get_y(bounds) {
|
||||
return (x, y - 1);
|
||||
}
|
||||
if !line_break {
|
||||
break 'char;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
x
|
||||
(x, y)
|
||||
}
|
||||
|
||||
/// Completely clear an `Area` with an empty char and the terminal's default colors.
|
||||
fn clear_area(grid: &mut CellBuffer, area: Area) {
|
||||
if !is_valid_area!(area) {
|
||||
return;
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
for y in get_y(upper_left)..=get_y(bottom_right) {
|
|
@ -3,8 +3,8 @@
|
|||
*/
|
||||
use notify_rust::Notification as notify_Notification;
|
||||
|
||||
use ui::*;
|
||||
use ui::components::*;
|
||||
use super::*;
|
||||
use super::components::*;
|
||||
|
||||
/// Passes notifications to the OS using the XDG specifications.
|
||||
pub struct XDGNotifications {}
|
|
@ -1,8 +1,6 @@
|
|||
/*! Various useful components that can be used in a generic fashion.
|
||||
*/
|
||||
|
||||
use ui::components::*;
|
||||
use ui::cells::*;
|
||||
use super::*;
|
||||
|
||||
/// A horizontally split in half container.
|
||||
pub struct HSplit {
|
||||
|
@ -24,6 +22,9 @@ impl HSplit {
|
|||
|
||||
impl Component for HSplit {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !is_valid_area!(area) {
|
||||
return;
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
||||
|
@ -71,6 +72,9 @@ impl VSplit {
|
|||
|
||||
impl Component for VSplit {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !is_valid_area!(area) {
|
||||
return;
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
let total_cols = get_x(bottom_right) - get_x(upper_left);
|
||||
|
@ -186,7 +190,8 @@ impl Pager {
|
|||
content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
((0, i), (width -1, i)));
|
||||
((0, i), (width -1, i)),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -194,6 +199,9 @@ impl Pager {
|
|||
|
||||
impl Component for Pager {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !is_valid_area!(area) {
|
||||
return;
|
||||
}
|
||||
if !self.is_dirty() {
|
||||
return;
|
||||
}
|
||||
|
@ -272,7 +280,7 @@ impl StatusBar {
|
|||
grid,
|
||||
Color::Byte(123),
|
||||
Color::Byte(26),
|
||||
area);
|
||||
area, false);
|
||||
change_colors(grid, area, Color::Byte(123), Color::Byte(26));
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
@ -282,7 +290,7 @@ impl StatusBar {
|
|||
grid,
|
||||
Color::Byte(219),
|
||||
Color::Byte(88),
|
||||
area);
|
||||
area, false);
|
||||
change_colors(grid, area, Color::Byte(219), Color::Byte(88));
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
@ -291,6 +299,9 @@ impl StatusBar {
|
|||
|
||||
impl Component for StatusBar {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if !is_valid_area!(area) {
|
||||
return;
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
||||
|
@ -359,3 +370,26 @@ impl Component for StatusBar {
|
|||
self.dirty || self.container.component.is_dirty()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// A box with a text content.
|
||||
pub struct TextBox {
|
||||
content: String,
|
||||
}
|
||||
|
||||
impl TextBox {
|
||||
pub fn new(s: String) -> Self {
|
||||
TextBox {
|
||||
content: s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for TextBox {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
|
||||
}
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -30,20 +30,20 @@
|
|||
|
||||
#[macro_use]
|
||||
mod position;
|
||||
pub mod components;
|
||||
mod cells;
|
||||
pub mod components;
|
||||
|
||||
#[macro_use]
|
||||
mod execute;
|
||||
use self::execute::goto;
|
||||
use execute::goto;
|
||||
pub use self::position::*;
|
||||
use self::cells::*;
|
||||
pub use self::components::*;
|
||||
|
||||
extern crate melib;
|
||||
extern crate notify_rust;
|
||||
use melib::*;
|
||||
|
||||
use std;
|
||||
use std::io::{Write, };
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
|
@ -53,6 +53,9 @@ use termion::raw::IntoRawMode;
|
|||
use termion::event::{Key as TermionKey, };
|
||||
use termion::input::TermRead;
|
||||
|
||||
extern crate chan;
|
||||
#[macro_use]
|
||||
extern crate nom;
|
||||
use chan::Sender;
|
||||
|
||||
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
|
Loading…
Reference in New Issue