Add more documentation.

master
Manos Pitsidianakis 2018-07-18 10:42:52 +03:00
parent e95cc4c1e9
commit ab099b524a
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
10 changed files with 221 additions and 193 deletions

2
.gdbinit 100644
View File

@ -0,0 +1,2 @@
break rust_panic
break core::option::expect_failed::h4927e1fef06c4878

View File

@ -18,6 +18,12 @@
* You should have received a copy of the GNU General Public License
* 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;
#[macro_use]
extern crate nom;

View File

@ -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 super::position::*;
use termion::color::AnsiValue;
/// Types and implementations taken from rustty for convenience.
pub trait CellAccessor: HasSize {
fn cellvec(&self) -> &Vec<Cell>;
fn cellvec_mut(&mut self) -> &mut Vec<Cell>;
@ -409,10 +416,10 @@ impl Color {
pub fn as_termion(&self) -> AnsiValue {
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 =>
{
{
AnsiValue(b.as_byte())
},
Color::Byte(b) => {
Color::Byte(b) => {
AnsiValue(b as u8)
},
}

View File

@ -1,3 +1,5 @@
/*! Entities that handle Mail specific functions.
*/
use ui::components::*;
use ui::cells::*;
@ -90,10 +92,10 @@ impl MailListing {
match iter.peek() {
Some(&(_, x))
if mailbox.get_thread(*x).get_indentation() == indentation =>
{
indentations.pop();
indentations.push(true);
}
{
indentations.pop();
indentations.push(true);
}
_ => {
indentations.pop();
indentations.push(false);
@ -115,8 +117,8 @@ impl MailListing {
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),
};
let x = write_string_to_grid(&MailListing::make_thread_entry(envelope, idx, indentation, container, &indentations),
&mut content,
fg_color,
bg_color,
@ -129,23 +131,23 @@ impl MailListing {
match iter.peek() {
Some(&(_, x))
if mailbox.get_thread(*x).get_indentation() > indentation =>
{
indentations.push(false);
}
{
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();
{
for _ in 0..(indentation - mailbox.get_thread(*x).get_indentation()) {
indentations.pop();
}
}
}
_ => {}
}
}
} else {
// Populate `CellBuffer` with every entry.
// TODO: Lazy load?
// Populate `CellBuffer` with every entry.
// TODO: Lazy load?
let mut idx = 0;
for y in 0..=self.length {
if idx >= self.length {
@ -254,6 +256,40 @@ 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) {
{
@ -272,28 +308,12 @@ impl MailListing {
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 {
container: &Container, 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())
let mut s = format!("{} {} ", idx, &envelope.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string());
for i in 0..indent {
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()),
grid,
Color::Byte(33),
Color::Default,
(set_y(upper_left, mid+1), set_y(bottom_right, mid+1)));
grid,
Color::Byte(33),
Color::Default,
(set_y(upper_left, mid+1), set_y(bottom_right, mid+1)));
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);
}
let x = 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)));
grid,
Color::Byte(33),
Color::Default,
(set_y(upper_left, mid+2), set_y(bottom_right, mid+2)));
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);
}
let x = 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)));
grid,
Color::Byte(33),
Color::Default,
(set_y(upper_left, mid+3), set_y(bottom_right, mid+3)));
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);
}
let x = 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)));
grid,
Color::Byte(33),
Color::Default,
(set_y(upper_left, mid+4), set_y(bottom_right, mid+4)));
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);
}
let x = 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)));
grid,
Color::Byte(33),
Color::Default,
(set_y(upper_left, mid+5), set_y(bottom_right, mid+5)));
for x in x..=get_x(bottom_right) {
grid[(x, mid+5)].set_ch(' ');
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 mut parents: Vec<Option<usize>> = vec!(None; a.entries.len());
for (idx, e) in a.entries.iter().enumerate() {
@ -648,9 +668,9 @@ impl AccountMenu {
};
let color_fg = if highlight {
if idx > 1 && self.cursor.unwrap().1 == idx - 2 {
Color::Byte(233)
Color::Byte(233)
} else {
Color::Byte(15)
Color::Byte(15)
}
} else {
Color::Default
@ -658,19 +678,19 @@ impl AccountMenu {
let color_bg = if highlight {
if idx > 1 && self.cursor.unwrap().1 == idx - 2 {
Color::Byte(15)
Color::Byte(15)
} else {
Color::Byte(233)
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));
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);

View File

@ -19,6 +19,12 @@
* 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 mail;
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) {
if !is_valid_area!(dest) || !is_valid_area!(src) {
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) {
if !is_valid_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 {
let bounds = grid.size();
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
}
/// Completely clear an `Area` with an empty char and the terminal's default colors.
fn clear_area(grid: &mut CellBuffer, area: Area) {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);

View File

@ -1,7 +1,12 @@
/*!
Notification handling components.
*/
use notify_rust::Notification as notify_Notification;
use ui::*;
use ui::components::*;
/// Passes notifications to the OS using the XDG specifications.
pub struct XDGNotifications {}
impl Component for XDGNotifications {

View File

@ -1,3 +1,6 @@
/*! Various useful components that can be used in a generic fashion.
*/
use ui::components::*;
use ui::cells::*;
@ -125,6 +128,7 @@ pub struct Pager {
content: CellBuffer,
}
// TODO: Make the `new` method content agnostic.
impl Pager {
pub fn new(mail: &Envelope, pager_filter: Option<String>) -> Self {
let mut text = mail.get_body().get_text();
@ -159,6 +163,20 @@ impl Pager {
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) {
let lines: Vec<&str> = s.trim().split('\n').collect();
let width = lines.iter().map(|l| l.len()).max().unwrap_or(0);

View File

@ -1,11 +1,13 @@
/*! A parser module for user commands passed through the Ex mode.
*/
use std;
use nom::digit;
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>,
preceded!(tag!("b "),
call!(usize_c))
);
preceded!(tag!("b "),
call!(usize_c))
);

View File

@ -19,57 +19,41 @@
* 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]
pub mod position;
mod position;
pub mod components;
pub mod cells;
mod cells;
#[macro_use]
mod execute;
use self::execute::goto;
extern crate termion;
extern crate melib;
use std::collections::VecDeque;
use std::fmt;
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::*;
pub use self::components::*;
/* Color pairs; foreground && background. */
/// Default color.
pub static COLOR_PAIR_DEFAULT: i16 = 1;
/// Highlighted cursor line in index view.
pub static COLOR_PAIR_CURSOR: i16 = 2;
/// Header colour in pager view.
pub static COLOR_PAIR_HEADERS: i16 = 3;
/// Indentation symbol color in index view.
pub static COLOR_PAIR_THREAD_INDENT: i16 = 4;
/// Line color for odd entries in index view.
pub static COLOR_PAIR_THREAD_ODD: i16 = 5;
/// Line color for even entries in index view.
pub static COLOR_PAIR_THREAD_EVEN: i16 = 6;
/// Line color for unread odd entries in index view.
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;
extern crate melib;
use melib::*;
use std;
use std::io::{Write, };
use std::collections::VecDeque;
use std::fmt;
extern crate termion;
use termion::{clear, style, cursor};
use termion::raw::IntoRawMode;
use termion::event::{Key as TermionKey, };
use termion::input::TermRead;
use chan::Sender;
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
/// to the main process.
@ -103,6 +87,7 @@ pub enum UIEventType {
}
/// An event passed from `State` to its Entities.
#[derive(Debug)]
pub struct UIEvent {
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 {
title: String,
content: String,
@ -131,6 +117,7 @@ pub struct Notification {
timestamp: std::time::Instant,
}
/// A context container for loaded settings, accounts, UI changes, etc.
pub struct Context {
pub accounts: Vec<Account>,
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> {
cols: usize,
rows: usize,
@ -285,24 +274,22 @@ impl<W: Write> State<W> {
pub fn register_entity(&mut self, entity: Entity) {
self.entities.push(entity);
}
/// Convert user commands to actions/method calls.
fn parse_command(&mut self, cmd: String) {
//TODO: Make ex mode useful
eprintln!("received command: {}", cmd);
let result = goto(&cmd.as_bytes()).to_full_result();
eprintln!("result is {:?}", result);
if let Ok(v) = result {
self.refresh_mailbox(0, v);
}
}
pub fn rcv_event(&mut self, event: UIEvent) {
match event.event_type {
// Command type is only for the State itself.
// Command type is handled only by State.
UIEventType::Command(cmd) => {
self.parse_command(cmd);
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)]
pub enum Key {
/// Backspace.
@ -396,12 +362,36 @@ pub enum Key {
Esc,
}
pub fn get_events<F>(stdin: std::io::Stdin, mut closure: F) where F: FnMut(Key) -> (){
let stdin = stdin.lock();
for c in stdin.keys() {
if let Ok(k) = c {
let k = convert_key(k);
closure(k);
impl From<TermionKey> for Key {
fn from(k: TermionKey ) -> Self {
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(' '),
}
}
}
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));
}
}
}

View File

@ -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.
pub type Pos = (usize, usize);
@ -18,6 +24,7 @@ pub fn set_y(p: Pos, new_y: usize) -> Pos {
(p.0, new_y)
}
/// An `Area` consists of two points: the upper left and bottom right corners.
pub type Area = (Pos, Pos);
#[macro_export]
@ -25,15 +32,19 @@ macro_rules! upper_left { ($a:expr) => ( $a.0 ) }
#[macro_export]
macro_rules! bottom_right { ($a:expr) => ( $a.1 ) }
#[macro_export]
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) {
false
} else {
true
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) {
false
} else {
true
}
}
} } }
}
}
/// A `(cols, rows)` size.
pub type Size = (usize, usize);
@ -46,45 +57,3 @@ pub trait HasPosition {
fn origin(&self) -> 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;
}
}