Add more documentation.
parent
e95cc4c1e9
commit
ab099b524a
|
@ -0,0 +1,2 @@
|
||||||
|
break rust_panic
|
||||||
|
break core::option::expect_failed::h4927e1fef06c4878
|
|
@ -18,6 +18,12 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*! This crate contains the frontend stuff of the application. The application entry way on `src/bin.rs` creates an event loop and passes input to the `ui` module.
|
||||||
|
|
||||||
|
The mail handling stuff is done in the `melib` crate which includes all backend needs. The split is done to theoretically be able to create different frontends with the same innards.
|
||||||
|
*/
|
||||||
|
|
||||||
extern crate melib;
|
extern crate melib;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate nom;
|
extern crate nom;
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
|
/*!
|
||||||
|
Define a (x, y) point in the terminal display as a holder of a character, foreground/background
|
||||||
|
colors and attributes.
|
||||||
|
*/
|
||||||
use std::ops::{Index, IndexMut, Deref, DerefMut};
|
use std::ops::{Index, IndexMut, Deref, DerefMut};
|
||||||
use super::position::*;
|
use super::position::*;
|
||||||
use termion::color::AnsiValue;
|
use termion::color::AnsiValue;
|
||||||
|
|
||||||
|
|
||||||
|
/// Types and implementations taken from rustty for convenience.
|
||||||
|
|
||||||
pub trait CellAccessor: HasSize {
|
pub trait CellAccessor: HasSize {
|
||||||
fn cellvec(&self) -> &Vec<Cell>;
|
fn cellvec(&self) -> &Vec<Cell>;
|
||||||
fn cellvec_mut(&mut self) -> &mut Vec<Cell>;
|
fn cellvec_mut(&mut self) -> &mut Vec<Cell>;
|
||||||
|
@ -409,10 +416,10 @@ impl Color {
|
||||||
pub fn as_termion(&self) -> AnsiValue {
|
pub fn as_termion(&self) -> AnsiValue {
|
||||||
match *self {
|
match *self {
|
||||||
b @ Color::Black | b @ Color::Red | b @ Color::Green | b @ Color::Yellow | b @ Color::Blue | b @ Color::Magenta | b @ Color::Cyan | b @ Color::White | b @ Color::Default =>
|
b @ Color::Black | b @ Color::Red | b @ Color::Green | b @ Color::Yellow | b @ Color::Blue | b @ Color::Magenta | b @ Color::Cyan | b @ Color::White | b @ Color::Default =>
|
||||||
{
|
{
|
||||||
AnsiValue(b.as_byte())
|
AnsiValue(b.as_byte())
|
||||||
},
|
},
|
||||||
Color::Byte(b) => {
|
Color::Byte(b) => {
|
||||||
AnsiValue(b as u8)
|
AnsiValue(b as u8)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
/*! Entities that handle Mail specific functions.
|
||||||
|
*/
|
||||||
use ui::components::*;
|
use ui::components::*;
|
||||||
use ui::cells::*;
|
use ui::cells::*;
|
||||||
|
|
||||||
|
@ -90,10 +92,10 @@ impl MailListing {
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&(_, x))
|
Some(&(_, x))
|
||||||
if mailbox.get_thread(*x).get_indentation() == indentation =>
|
if mailbox.get_thread(*x).get_indentation() == indentation =>
|
||||||
{
|
{
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
indentations.push(true);
|
indentations.push(true);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
indentations.push(false);
|
indentations.push(false);
|
||||||
|
@ -115,8 +117,8 @@ impl MailListing {
|
||||||
Color::Byte(236)
|
Color::Byte(236)
|
||||||
} else {
|
} else {
|
||||||
Color::Default
|
Color::Default
|
||||||
}
|
};
|
||||||
let x = write_string_to_grid(&MailListing::make_thread_entry(envelope, idx, indentation, container, idx == self.cursor_pos.2, &indentations),
|
let x = write_string_to_grid(&MailListing::make_thread_entry(envelope, idx, indentation, container, &indentations),
|
||||||
&mut content,
|
&mut content,
|
||||||
fg_color,
|
fg_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
|
@ -129,23 +131,23 @@ impl MailListing {
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&(_, x))
|
Some(&(_, x))
|
||||||
if mailbox.get_thread(*x).get_indentation() > indentation =>
|
if mailbox.get_thread(*x).get_indentation() > indentation =>
|
||||||
{
|
{
|
||||||
indentations.push(false);
|
indentations.push(false);
|
||||||
}
|
}
|
||||||
Some(&(_, x))
|
Some(&(_, x))
|
||||||
if mailbox.get_thread(*x).get_indentation() < indentation =>
|
if mailbox.get_thread(*x).get_indentation() < indentation =>
|
||||||
{
|
{
|
||||||
for _ in 0..(indentation - mailbox.get_thread(*x).get_indentation()) {
|
for _ in 0..(indentation - mailbox.get_thread(*x).get_indentation()) {
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Populate `CellBuffer` with every entry.
|
// Populate `CellBuffer` with every entry.
|
||||||
// TODO: Lazy load?
|
// TODO: Lazy load?
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
for y in 0..=self.length {
|
for y in 0..=self.length {
|
||||||
if idx >= self.length {
|
if idx >= self.length {
|
||||||
|
@ -254,6 +256,40 @@ impl MailListing {
|
||||||
context.dirty_areas.push_back(area);
|
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.
|
/// 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) {
|
||||||
{
|
{
|
||||||
|
@ -272,28 +308,12 @@ impl MailListing {
|
||||||
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,
|
fn make_thread_entry(envelope: &Envelope, idx: usize, indent: usize,
|
||||||
container: &Container, highlight: bool, indentations: &Vec<bool>) -> String {
|
container: &Container, indentations: &Vec<bool>) -> String {
|
||||||
let has_sibling = container.has_sibling();
|
let has_sibling = container.has_sibling();
|
||||||
let has_parent = container.has_parent();
|
let has_parent = container.has_parent();
|
||||||
let show_subject = container.get_show_subject();
|
let show_subject = container.get_show_subject();
|
||||||
|
|
||||||
let fg_color = if !envelope.is_seen() {
|
let mut s = format!("{} {} ", idx, &envelope.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string());
|
||||||
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 {
|
for i in 0..indent {
|
||||||
if indentations.len() > i && indentations[i]
|
if indentations.len() > i && indentations[i]
|
||||||
{
|
{
|
||||||
|
@ -398,50 +418,50 @@ impl Component for MailListing {
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
||||||
Color::Byte(33),
|
Color::Byte(33),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
(set_y(upper_left, mid+1), set_y(bottom_right, mid+1)));
|
(set_y(upper_left, mid+1), set_y(bottom_right, mid+1)));
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x, mid+1)].set_ch(' ');
|
grid[(x, mid+1)].set_ch(' ');
|
||||||
grid[(x, mid+1)].set_bg(Color::Default);
|
grid[(x, mid+1)].set_bg(Color::Default);
|
||||||
grid[(x, mid+1)].set_fg(Color::Default);
|
grid[(x, mid+1)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let x = write_string_to_grid(&format!("From: {}", envelope.get_from()),
|
let x = write_string_to_grid(&format!("From: {}", envelope.get_from()),
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(33),
|
Color::Byte(33),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
(set_y(upper_left, mid+2), set_y(bottom_right, mid+2)));
|
(set_y(upper_left, mid+2), set_y(bottom_right, mid+2)));
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x, mid+2)].set_ch(' ');
|
grid[(x, mid+2)].set_ch(' ');
|
||||||
grid[(x, mid+2)].set_bg(Color::Default);
|
grid[(x, mid+2)].set_bg(Color::Default);
|
||||||
grid[(x, mid+2)].set_fg(Color::Default);
|
grid[(x, mid+2)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let x = write_string_to_grid(&format!("To: {}", envelope.get_to()),
|
let x = write_string_to_grid(&format!("To: {}", envelope.get_to()),
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(33),
|
Color::Byte(33),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
(set_y(upper_left, mid+3), set_y(bottom_right, mid+3)));
|
(set_y(upper_left, mid+3), set_y(bottom_right, mid+3)));
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x, mid+3)].set_ch(' ');
|
grid[(x, mid+3)].set_ch(' ');
|
||||||
grid[(x, mid+3)].set_bg(Color::Default);
|
grid[(x, mid+3)].set_bg(Color::Default);
|
||||||
grid[(x, mid+3)].set_fg(Color::Default);
|
grid[(x, mid+3)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let x = write_string_to_grid(&format!("Subject: {}", envelope.get_subject()),
|
let x = write_string_to_grid(&format!("Subject: {}", envelope.get_subject()),
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(33),
|
Color::Byte(33),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
(set_y(upper_left, mid+4), set_y(bottom_right, mid+4)));
|
(set_y(upper_left, mid+4), set_y(bottom_right, mid+4)));
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x, mid+4)].set_ch(' ');
|
grid[(x, mid+4)].set_ch(' ');
|
||||||
grid[(x, mid+4)].set_bg(Color::Default);
|
grid[(x, mid+4)].set_bg(Color::Default);
|
||||||
grid[(x, mid+4)].set_fg(Color::Default);
|
grid[(x, mid+4)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let x = write_string_to_grid(&format!("Message-ID: {}", envelope.get_message_id_raw()),
|
let x = write_string_to_grid(&format!("Message-ID: {}", envelope.get_message_id_raw()),
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(33),
|
Color::Byte(33),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
(set_y(upper_left, mid+5), set_y(bottom_right, mid+5)));
|
(set_y(upper_left, mid+5), set_y(bottom_right, mid+5)));
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x, mid+5)].set_ch(' ');
|
grid[(x, mid+5)].set_ch(' ');
|
||||||
grid[(x, mid+5)].set_bg(Color::Default);
|
grid[(x, mid+5)].set_bg(Color::Default);
|
||||||
|
@ -588,7 +608,7 @@ impl AccountMenu {
|
||||||
|
|
||||||
|
|
||||||
let highlight = self.cursor.map(|(x,_)| x == a.index).unwrap_or(false);
|
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() {
|
||||||
|
@ -648,9 +668,9 @@ impl AccountMenu {
|
||||||
};
|
};
|
||||||
let color_fg = if highlight {
|
let color_fg = if highlight {
|
||||||
if idx > 1 && self.cursor.unwrap().1 == idx - 2 {
|
if idx > 1 && self.cursor.unwrap().1 == idx - 2 {
|
||||||
Color::Byte(233)
|
Color::Byte(233)
|
||||||
} else {
|
} else {
|
||||||
Color::Byte(15)
|
Color::Byte(15)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Color::Default
|
Color::Default
|
||||||
|
@ -658,19 +678,19 @@ impl AccountMenu {
|
||||||
|
|
||||||
let color_bg = if highlight {
|
let color_bg = if highlight {
|
||||||
if idx > 1 && self.cursor.unwrap().1 == idx - 2 {
|
if idx > 1 && self.cursor.unwrap().1 == idx - 2 {
|
||||||
Color::Byte(15)
|
Color::Byte(15)
|
||||||
} else {
|
} else {
|
||||||
Color::Byte(233)
|
Color::Byte(233)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Color::Default
|
Color::Default
|
||||||
};
|
};
|
||||||
|
|
||||||
let x = write_string_to_grid(&s,
|
let x = write_string_to_grid(&s,
|
||||||
grid,
|
grid,
|
||||||
color_fg,
|
color_fg,
|
||||||
color_bg,
|
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 {
|
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);
|
change_colors(grid, ((x, y),(get_x(bottom_right)+1, y)), color_fg , color_bg);
|
||||||
|
|
|
@ -19,6 +19,12 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Components are ways to handle application data. They can draw on the terminal and receive events, but also do other stuff as well. (For example, see the `notifications` module.)
|
||||||
|
|
||||||
|
See the `Component` Trait for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
pub mod mail;
|
pub mod mail;
|
||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
|
@ -78,7 +84,7 @@ pub trait Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy Area src to dest
|
/// Copy a source `Area` to a destination.
|
||||||
pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, src: Area) {
|
pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, src: Area) {
|
||||||
if !is_valid_area!(dest) || !is_valid_area!(src) {
|
if !is_valid_area!(dest) || !is_valid_area!(src) {
|
||||||
eprintln!("BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}", src, dest);
|
eprintln!("BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}", src, dest);
|
||||||
|
@ -104,6 +110,7 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change foreground and background colors in an `Area`
|
||||||
pub fn change_colors(grid: &mut CellBuffer, area: Area, fg_color: Color, bg_color: Color) {
|
pub fn change_colors(grid: &mut CellBuffer, area: Area, fg_color: Color, bg_color: Color) {
|
||||||
if !is_valid_area!(area) {
|
if !is_valid_area!(area) {
|
||||||
eprintln!("BUG: Invalid area in change_colors:\n area: {:?}", area);
|
eprintln!("BUG: Invalid area in change_colors:\n area: {:?}", area);
|
||||||
|
@ -118,6 +125,7 @@ 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) -> usize {
|
||||||
let bounds = grid.size();
|
let bounds = grid.size();
|
||||||
let upper_left = upper_left!(area);
|
let upper_left = upper_left!(area);
|
||||||
|
@ -144,6 +152,7 @@ fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_colo
|
||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Completely clear an `Area` with an empty char and the terminal's default colors.
|
||||||
fn clear_area(grid: &mut CellBuffer, area: Area) {
|
fn clear_area(grid: &mut CellBuffer, area: Area) {
|
||||||
let upper_left = upper_left!(area);
|
let upper_left = upper_left!(area);
|
||||||
let bottom_right = bottom_right!(area);
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
/*!
|
||||||
|
Notification handling components.
|
||||||
|
*/
|
||||||
use notify_rust::Notification as notify_Notification;
|
use notify_rust::Notification as notify_Notification;
|
||||||
|
|
||||||
use ui::*;
|
use ui::*;
|
||||||
use ui::components::*;
|
use ui::components::*;
|
||||||
|
|
||||||
|
/// Passes notifications to the OS using the XDG specifications.
|
||||||
pub struct XDGNotifications {}
|
pub struct XDGNotifications {}
|
||||||
|
|
||||||
impl Component for XDGNotifications {
|
impl Component for XDGNotifications {
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
/*! Various useful components that can be used in a generic fashion.
|
||||||
|
*/
|
||||||
|
|
||||||
use ui::components::*;
|
use ui::components::*;
|
||||||
use ui::cells::*;
|
use ui::cells::*;
|
||||||
|
|
||||||
|
@ -125,6 +128,7 @@ pub struct Pager {
|
||||||
content: CellBuffer,
|
content: CellBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make the `new` method content agnostic.
|
||||||
impl Pager {
|
impl Pager {
|
||||||
pub fn new(mail: &Envelope, pager_filter: Option<String>) -> Self {
|
pub fn new(mail: &Envelope, pager_filter: Option<String>) -> Self {
|
||||||
let mut text = mail.get_body().get_text();
|
let mut text = mail.get_body().get_text();
|
||||||
|
@ -159,6 +163,20 @@ impl Pager {
|
||||||
content: content,
|
content: content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn new_from_str(s: &str) -> Self {
|
||||||
|
let lines: Vec<&str> = s.trim().split('\n').collect();
|
||||||
|
let height = lines.len();
|
||||||
|
let width = lines.iter().map(|l| l.len()).max().unwrap_or(0);
|
||||||
|
let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
|
||||||
|
Pager::print_string(&mut content, s);
|
||||||
|
Pager {
|
||||||
|
cursor_pos: 0,
|
||||||
|
height: height,
|
||||||
|
width: width,
|
||||||
|
dirty: true,
|
||||||
|
content: content,
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn print_string(content: &mut CellBuffer, s: &str) {
|
pub fn print_string(content: &mut CellBuffer, s: &str) {
|
||||||
let lines: Vec<&str> = s.trim().split('\n').collect();
|
let lines: Vec<&str> = s.trim().split('\n').collect();
|
||||||
let width = lines.iter().map(|l| l.len()).max().unwrap_or(0);
|
let width = lines.iter().map(|l| l.len()).max().unwrap_or(0);
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
/*! A parser module for user commands passed through the Ex mode.
|
||||||
|
*/
|
||||||
use std;
|
use std;
|
||||||
use nom::digit;
|
use nom::digit;
|
||||||
|
|
||||||
|
|
||||||
named!(usize_c<usize>,
|
named!(usize_c<usize>,
|
||||||
map_res!(map_res!(ws!(digit), std::str::from_utf8), std::str::FromStr::from_str));
|
map_res!(map_res!(ws!(digit), std::str::from_utf8), std::str::FromStr::from_str));
|
||||||
|
|
||||||
named!(pub goto<usize>,
|
named!(pub goto<usize>,
|
||||||
preceded!(tag!("b "),
|
preceded!(tag!("b "),
|
||||||
call!(usize_c))
|
call!(usize_c))
|
||||||
);
|
);
|
||||||
|
|
138
src/ui/mod.rs
138
src/ui/mod.rs
|
@ -19,57 +19,41 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
The UI module has an Entity-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct.
|
||||||
|
|
||||||
|
`State` owns all the Entities of the UI, which are currently plain Containers for `Component`s. In the application's main event loop, input is handed to the state in the form of `UIEvent` objects which traverse the entity graph. Components decide to handle each input or not.
|
||||||
|
|
||||||
|
Input is received in the main loop from threads which listen on the stdin for user input, observe folders for file changes etc. The relevant struct is `ThreadEvent`.
|
||||||
|
*/
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod position;
|
mod position;
|
||||||
pub mod components;
|
pub mod components;
|
||||||
pub mod cells;
|
mod cells;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod execute;
|
mod execute;
|
||||||
use self::execute::goto;
|
use self::execute::goto;
|
||||||
|
|
||||||
extern crate termion;
|
|
||||||
extern crate melib;
|
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
pub use self::position::*;
|
pub use self::position::*;
|
||||||
|
|
||||||
use melib::*;
|
|
||||||
|
|
||||||
|
|
||||||
use std;
|
|
||||||
use termion::{clear, style, cursor};
|
|
||||||
use termion::raw::IntoRawMode;
|
|
||||||
use termion::event::{Key as TermionKey, };
|
|
||||||
|
|
||||||
use chan::Sender;
|
|
||||||
|
|
||||||
|
|
||||||
use std::io::{Write, };
|
|
||||||
use termion::input::TermRead;
|
|
||||||
|
|
||||||
use self::cells::*;
|
use self::cells::*;
|
||||||
pub use self::components::*;
|
pub use self::components::*;
|
||||||
|
|
||||||
/* Color pairs; foreground && background. */
|
extern crate melib;
|
||||||
/// Default color.
|
use melib::*;
|
||||||
pub static COLOR_PAIR_DEFAULT: i16 = 1;
|
|
||||||
/// Highlighted cursor line in index view.
|
use std;
|
||||||
pub static COLOR_PAIR_CURSOR: i16 = 2;
|
use std::io::{Write, };
|
||||||
/// Header colour in pager view.
|
use std::collections::VecDeque;
|
||||||
pub static COLOR_PAIR_HEADERS: i16 = 3;
|
use std::fmt;
|
||||||
/// Indentation symbol color in index view.
|
extern crate termion;
|
||||||
pub static COLOR_PAIR_THREAD_INDENT: i16 = 4;
|
use termion::{clear, style, cursor};
|
||||||
/// Line color for odd entries in index view.
|
use termion::raw::IntoRawMode;
|
||||||
pub static COLOR_PAIR_THREAD_ODD: i16 = 5;
|
use termion::event::{Key as TermionKey, };
|
||||||
/// Line color for even entries in index view.
|
use termion::input::TermRead;
|
||||||
pub static COLOR_PAIR_THREAD_EVEN: i16 = 6;
|
|
||||||
/// Line color for unread odd entries in index view.
|
use chan::Sender;
|
||||||
pub static COLOR_PAIR_UNREAD_ODD: i16 = 7;
|
|
||||||
/// Line color for unread even entries in index view.
|
|
||||||
pub static COLOR_PAIR_UNREAD_EVEN: i16 = 8;
|
|
||||||
|
|
||||||
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
|
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
|
||||||
/// to the main process.
|
/// to the main process.
|
||||||
|
@ -103,6 +87,7 @@ pub enum UIEventType {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// An event passed from `State` to its Entities.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UIEvent {
|
pub struct UIEvent {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
|
@ -124,6 +109,7 @@ impl fmt::Display for UIMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An event notification that is passed to Entities for handling.
|
||||||
pub struct Notification {
|
pub struct Notification {
|
||||||
title: String,
|
title: String,
|
||||||
content: String,
|
content: String,
|
||||||
|
@ -131,6 +117,7 @@ pub struct Notification {
|
||||||
timestamp: std::time::Instant,
|
timestamp: std::time::Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A context container for loaded settings, accounts, UI changes, etc.
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
pub accounts: Vec<Account>,
|
pub accounts: Vec<Account>,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
|
@ -149,6 +136,8 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// A State object to manage and own components and entities of the UI. `State` is responsible for
|
||||||
|
/// managing the terminal and interfacing with `melib`
|
||||||
pub struct State<W: Write> {
|
pub struct State<W: Write> {
|
||||||
cols: usize,
|
cols: usize,
|
||||||
rows: usize,
|
rows: usize,
|
||||||
|
@ -285,24 +274,22 @@ impl<W: Write> State<W> {
|
||||||
pub fn register_entity(&mut self, entity: Entity) {
|
pub fn register_entity(&mut self, entity: Entity) {
|
||||||
self.entities.push(entity);
|
self.entities.push(entity);
|
||||||
}
|
}
|
||||||
|
/// Convert user commands to actions/method calls.
|
||||||
fn parse_command(&mut self, cmd: String) {
|
fn parse_command(&mut self, cmd: String) {
|
||||||
|
//TODO: Make ex mode useful
|
||||||
eprintln!("received command: {}", cmd);
|
eprintln!("received command: {}", cmd);
|
||||||
|
|
||||||
let result = goto(&cmd.as_bytes()).to_full_result();
|
let result = goto(&cmd.as_bytes()).to_full_result();
|
||||||
eprintln!("result is {:?}", result);
|
eprintln!("result is {:?}", result);
|
||||||
|
|
||||||
if let Ok(v) = result {
|
if let Ok(v) = result {
|
||||||
|
|
||||||
self.refresh_mailbox(0, v);
|
self.refresh_mailbox(0, v);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rcv_event(&mut self, event: UIEvent) {
|
pub fn rcv_event(&mut self, event: UIEvent) {
|
||||||
match event.event_type {
|
match event.event_type {
|
||||||
// Command type is only for the State itself.
|
// Command type is handled only by State.
|
||||||
UIEventType::Command(cmd) => {
|
UIEventType::Command(cmd) => {
|
||||||
self.parse_command(cmd);
|
self.parse_command(cmd);
|
||||||
return;
|
return;
|
||||||
|
@ -331,29 +318,8 @@ impl<W: Write> State<W> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_key(k: TermionKey ) -> Key {
|
|
||||||
match k {
|
|
||||||
TermionKey::Backspace => Key::Backspace,
|
|
||||||
TermionKey::Left => Key::Left,
|
|
||||||
TermionKey::Right => Key::Right,
|
|
||||||
TermionKey::Up => Key::Up,
|
|
||||||
TermionKey::Down => Key::Down,
|
|
||||||
TermionKey::Home => Key::Home,
|
|
||||||
TermionKey::End => Key::End,
|
|
||||||
TermionKey::PageUp => Key::PageUp,
|
|
||||||
TermionKey::PageDown => Key::PageDown,
|
|
||||||
TermionKey::Delete => Key::Delete,
|
|
||||||
TermionKey::Insert => Key::Insert,
|
|
||||||
TermionKey::F(u) => Key::F(u),
|
|
||||||
TermionKey::Char(c) => Key::Char(c),
|
|
||||||
TermionKey::Alt(c) => Key::Alt(c),
|
|
||||||
TermionKey::Ctrl(c) => Key::Ctrl(c),
|
|
||||||
TermionKey::Null => Key::Null,
|
|
||||||
TermionKey::Esc => Key::Esc,
|
|
||||||
_ => Key::Char(' '),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// TODO: Pass Ctrl C etc to the terminal.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Key {
|
pub enum Key {
|
||||||
/// Backspace.
|
/// Backspace.
|
||||||
|
@ -396,12 +362,36 @@ pub enum Key {
|
||||||
Esc,
|
Esc,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_events<F>(stdin: std::io::Stdin, mut closure: F) where F: FnMut(Key) -> (){
|
impl From<TermionKey> for Key {
|
||||||
let stdin = stdin.lock();
|
fn from(k: TermionKey ) -> Self {
|
||||||
for c in stdin.keys() {
|
match k {
|
||||||
if let Ok(k) = c {
|
TermionKey::Backspace => Key::Backspace,
|
||||||
let k = convert_key(k);
|
TermionKey::Left => Key::Left,
|
||||||
closure(k);
|
TermionKey::Right => Key::Right,
|
||||||
|
TermionKey::Up => Key::Up,
|
||||||
|
TermionKey::Down => Key::Down,
|
||||||
|
TermionKey::Home => Key::Home,
|
||||||
|
TermionKey::End => Key::End,
|
||||||
|
TermionKey::PageUp => Key::PageUp,
|
||||||
|
TermionKey::PageDown => Key::PageDown,
|
||||||
|
TermionKey::Delete => Key::Delete,
|
||||||
|
TermionKey::Insert => Key::Insert,
|
||||||
|
TermionKey::F(u) => Key::F(u),
|
||||||
|
TermionKey::Char(c) => Key::Char(c),
|
||||||
|
TermionKey::Alt(c) => Key::Alt(c),
|
||||||
|
TermionKey::Ctrl(c) => Key::Ctrl(c),
|
||||||
|
TermionKey::Null => Key::Null,
|
||||||
|
TermionKey::Esc => Key::Esc,
|
||||||
|
_ => Key::Char(' '),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_events(stdin: std::io::Stdin, mut closure: impl FnMut(Key)) -> (){
|
||||||
|
let stdin = stdin.lock();
|
||||||
|
for c in stdin.keys() {
|
||||||
|
if let Ok(k) = c {
|
||||||
|
closure(Key::from(k));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
/*!
|
||||||
|
Simple type definitions and macro helper for a (x,y) position on the terminal and the areas they define.
|
||||||
|
|
||||||
|
An `Area` consists of two points: the upper left and bottom right corners.
|
||||||
|
*/
|
||||||
|
|
||||||
/// A `(x, y)` position on screen.
|
/// A `(x, y)` position on screen.
|
||||||
pub type Pos = (usize, usize);
|
pub type Pos = (usize, usize);
|
||||||
|
|
||||||
|
@ -18,6 +24,7 @@ pub fn set_y(p: Pos, new_y: usize) -> Pos {
|
||||||
(p.0, new_y)
|
(p.0, new_y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An `Area` consists of two points: the upper left and bottom right corners.
|
||||||
pub type Area = (Pos, Pos);
|
pub type Area = (Pos, Pos);
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -25,15 +32,19 @@ macro_rules! upper_left { ($a:expr) => ( $a.0 ) }
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! bottom_right { ($a:expr) => ( $a.1 ) }
|
macro_rules! bottom_right { ($a:expr) => ( $a.1 ) }
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! is_valid_area { ($a:expr) => { {
|
macro_rules! is_valid_area { ($a:expr) =>
|
||||||
let upper_left = upper_left!($a);
|
{
|
||||||
let bottom_right = bottom_right!($a);
|
{
|
||||||
if get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right) {
|
let upper_left = upper_left!($a);
|
||||||
false
|
let bottom_right = bottom_right!($a);
|
||||||
} else {
|
if get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right) {
|
||||||
true
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} } }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A `(cols, rows)` size.
|
/// A `(cols, rows)` size.
|
||||||
pub type Size = (usize, usize);
|
pub type Size = (usize, usize);
|
||||||
|
@ -46,45 +57,3 @@ pub trait HasPosition {
|
||||||
fn origin(&self) -> Pos;
|
fn origin(&self) -> Pos;
|
||||||
fn set_origin(&mut self, new_origin: Pos);
|
fn set_origin(&mut self, new_origin: Pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A cursor position.
|
|
||||||
pub struct Cursor {
|
|
||||||
pos: Option<Pos>,
|
|
||||||
last_pos: Option<Pos>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cursor {
|
|
||||||
pub fn new() -> Cursor {
|
|
||||||
Cursor {
|
|
||||||
pos: None,
|
|
||||||
last_pos: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether the current and last coordinates are sequential and returns `true` if they
|
|
||||||
/// are and `false` otherwise.
|
|
||||||
pub fn is_seq(&self) -> bool {
|
|
||||||
if let Some((cx, cy)) = self.pos {
|
|
||||||
if let Some((lx, ly)) = self.last_pos {
|
|
||||||
(lx + 1, ly) == (cx, cy)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pos(&self) -> Option<Pos> {
|
|
||||||
self.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_pos(&mut self, newpos: Option<Pos>) {
|
|
||||||
self.last_pos = self.pos;
|
|
||||||
self.pos = newpos;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn invalidate_last_pos(&mut self) {
|
|
||||||
self.last_pos = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue