Compare commits
30 Commits
548c9f4ac3
...
fead7a5da4
Author | SHA1 | Date |
---|---|---|
Manos Pitsidianakis | fead7a5da4 | |
Manos Pitsidianakis | 962283f9fe | |
Manos Pitsidianakis | 63cdf1a38f | |
Manos Pitsidianakis | 0aa2659072 | |
Manos Pitsidianakis | c22a141b14 | |
Manos Pitsidianakis | 22fb0c0844 | |
Manos Pitsidianakis | 647cb10b33 | |
Manos Pitsidianakis | 42747ef590 | |
Manos Pitsidianakis | eef007600b | |
Manos Pitsidianakis | 9b7875c023 | |
Manos Pitsidianakis | cadb1e1613 | |
Manos Pitsidianakis | 0b4109dfdb | |
Manos Pitsidianakis | 9616fbb544 | |
Manos Pitsidianakis | b107424258 | |
Manos Pitsidianakis | 50bfed7247 | |
Manos Pitsidianakis | 6b7dea35dc | |
Manos Pitsidianakis | 6afac835e0 | |
Manos Pitsidianakis | eb501b6d50 | |
Manos Pitsidianakis | 3bca6d1d9c | |
Manos Pitsidianakis | 4a4c8e265a | |
Manos Pitsidianakis | 333db9ed37 | |
Manos Pitsidianakis | d6e3c51b07 | |
Manos Pitsidianakis | f131e01bfc | |
Manos Pitsidianakis | 4301fa3b04 | |
Manos Pitsidianakis | af38b1306a | |
Manos Pitsidianakis | 144eb62b76 | |
Manos Pitsidianakis | f5e694cf5a | |
Manos Pitsidianakis | f208948651 | |
Manos Pitsidianakis | d6f04c9ed3 | |
Manos Pitsidianakis | ad76d4d44d |
|
@ -48,6 +48,7 @@ smallvec = { version = "1.1.0", features = ["serde", ] }
|
|||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = "z"
|
||||
debug = false
|
||||
|
||||
[workspace]
|
||||
|
@ -58,6 +59,7 @@ default = ["sqlite3"]
|
|||
notmuch = ["melib/notmuch_backend", ]
|
||||
jmap = ["melib/jmap_backend",]
|
||||
sqlite3 = ["rusqlite"]
|
||||
cli-docs = []
|
||||
|
||||
# Print tracing logs as meli runs in stderr
|
||||
# enable for debug tracing logs: build with --features=debug-tracing
|
||||
|
|
12
Makefile
12
Makefile
|
@ -28,18 +28,18 @@ CARGO_BIN ?= cargo
|
|||
|
||||
# Installation parameters
|
||||
MANPAGES ?= meli.1 meli.conf.5 meli-themes.5
|
||||
FEATURES ?= --features="$(MELI_FEATURES)"
|
||||
FEATURES ?= --features "${MELI_FEATURES}"
|
||||
|
||||
MANPATHS := `manpath 2> /dev/null`
|
||||
VERSION ?= `sed -n "s/^version\s*=\s*\"\(.*\)\"/\1/p" Cargo.toml`
|
||||
|
||||
# Output parameters
|
||||
BOLD ?= `tput bold`
|
||||
UNDERLINE ?= `tput smul`
|
||||
ANSI_RESET ?= `tput sgr0`
|
||||
BOLD ?= `[ -z $${TERM} ] && echo "" || tput bold`
|
||||
UNDERLINE ?= `[ -z $${TERM} ] && echo "" || tput smul`
|
||||
ANSI_RESET ?= `[ -z $${TERM} ] && echo "" || tput sgr0`
|
||||
CARGO_COLOR ?= `[ -z $${NO_COLOR+x} ] && echo "" || echo "--color=never "`
|
||||
RED ?= `[ -z $${NO_COLOR+x} ] && tput setaf 1 || echo ""`
|
||||
GREEN ?= `[ -z $${NO_COLOR+x} ] && tput setaf 2 || echo ""`
|
||||
RED ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 1) || echo ""`
|
||||
GREEN ?= `[ -z $${NO_COLOR+x} ] && ([ -z $${TERM} ] && echo "" || tput setaf 2) || echo ""`
|
||||
|
||||
.POSIX:
|
||||
.SUFFIXES:
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* meli - build.rs
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "cli-docs")]
|
||||
{
|
||||
const MANDOC_OPTS: &[&'static str] = &["-T", "utf8", "-I", "os=Generated by mandoc(1)"];
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let mut out_dir_path = Path::new(&out_dir).to_path_buf();
|
||||
|
||||
// Note that there are a number of downsides to this approach, the comments
|
||||
// below detail how to improve the portability of these commands.
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg("meli.1")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
out_dir_path.push("meli.txt");
|
||||
let mut file = File::create(&out_dir_path).unwrap();
|
||||
file.write_all(&output.stdout).unwrap();
|
||||
out_dir_path.pop();
|
||||
|
||||
out_dir_path.push("meli.conf.txt");
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg("meli.conf.5")
|
||||
.output()
|
||||
.unwrap();
|
||||
let mut file = File::create(&out_dir_path).unwrap();
|
||||
file.write_all(&output.stdout).unwrap();
|
||||
out_dir_path.pop();
|
||||
|
||||
out_dir_path.push("meli-themes.txt");
|
||||
let output = Command::new("mandoc")
|
||||
.args(MANDOC_OPTS)
|
||||
.arg("meli-themes.5")
|
||||
.output()
|
||||
.unwrap();
|
||||
let mut file = File::create(&out_dir_path).unwrap();
|
||||
file.write_all(&output.stdout).unwrap();
|
||||
}
|
||||
}
|
|
@ -2,13 +2,14 @@ Source: meli
|
|||
Section: mail
|
||||
Priority: optional
|
||||
Maintainer: Manos Pitsidianakis <epilys@nessuent.xyz>
|
||||
Build-Depends: debhelper (>=11~)
|
||||
Build-Depends: debhelper (>=11~), mandoc (>=1.14.4-1)
|
||||
Standards-Version: 4.1.4
|
||||
Homepage: https://meli.delivery
|
||||
|
||||
Package: meli
|
||||
Architecture: any
|
||||
Multi-Arch: foreign
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}, xdg-utils (>=1.1.3-1)
|
||||
Recommends: libnotmuch5 (>=0.28.4-1)
|
||||
Description: auto-generated package by debmake
|
||||
terminal mail client
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic
|
||||
#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||
export MELI_FEATURES = cli-docs sqlite3
|
||||
|
||||
%:
|
||||
dh $@ --with quilt
|
||||
|
|
|
@ -64,6 +64,17 @@ use std::sync::{Arc, RwLock};
|
|||
use fnv::FnvHashMap;
|
||||
use std;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_path_hash {
|
||||
($path:expr) => {{
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hasher = DefaultHasher::new();
|
||||
$path.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}};
|
||||
}
|
||||
|
||||
pub type BackendCreator = Box<
|
||||
dyn Fn(
|
||||
&AccountSettings,
|
||||
|
@ -232,18 +243,6 @@ impl NotifyFn {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub enum FolderOperation {
|
||||
Create,
|
||||
Delete,
|
||||
Subscribe,
|
||||
Unsubscribe,
|
||||
Rename(NewFolderName),
|
||||
SetPermissions(FolderPermissions),
|
||||
}
|
||||
|
||||
type NewFolderName = String;
|
||||
|
||||
pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
||||
fn is_online(&self) -> Result<()>;
|
||||
fn connect(&mut self) {}
|
||||
|
@ -264,9 +263,6 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
|||
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp>;
|
||||
|
||||
fn save(&self, bytes: &[u8], folder: &str, flags: Option<Flag>) -> Result<()>;
|
||||
fn create_folder(&mut self, _path: String) -> Result<Folder> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
|
||||
None
|
||||
}
|
||||
|
@ -275,6 +271,30 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
|||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn create_folder(&mut self, _path: String) -> Result<Folder> {
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
}
|
||||
|
||||
fn delete_folder(&mut self, _folder_hash: FolderHash) -> Result<()> {
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
}
|
||||
|
||||
fn set_folder_subscription(&mut self, _folder_hash: FolderHash, _val: bool) -> Result<()> {
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
}
|
||||
|
||||
fn rename_folder(&mut self, _folder_hash: FolderHash, _new_path: String) -> Result<Folder> {
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
}
|
||||
|
||||
fn set_folder_permissions(
|
||||
&mut self,
|
||||
_folder_hash: FolderHash,
|
||||
_val: FolderPermissions,
|
||||
) -> Result<()> {
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
}
|
||||
}
|
||||
|
||||
/// A `BackendOp` manages common operations for the various mail backends. They only live for the
|
||||
|
@ -319,8 +339,6 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
|||
pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
|
||||
fn description(&self) -> String;
|
||||
fn as_bytes(&mut self) -> Result<&[u8]>;
|
||||
//fn delete(&self) -> ();
|
||||
//fn copy(&self
|
||||
fn fetch_flags(&self) -> Flag;
|
||||
fn set_flag(&mut self, envelope: &mut Envelope, flag: Flag, value: bool) -> Result<()>;
|
||||
fn set_tag(&mut self, envelope: &mut Envelope, tag: String, value: bool) -> Result<()>;
|
||||
|
@ -536,3 +554,9 @@ impl Default for FolderPermissions {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FolderPermissions {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(fmt, "{:#?}", self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use crate::get_path_hash;
|
||||
use smallvec::SmallVec;
|
||||
#[macro_use]
|
||||
mod protocol_parser;
|
||||
|
@ -158,12 +159,12 @@ impl MailBackend for ImapType {
|
|||
let uid_store = self.uid_store.clone();
|
||||
let tag_index = self.tag_index.clone();
|
||||
let can_create_flags = self.can_create_flags.clone();
|
||||
let folder_path = folder.path().to_string();
|
||||
let folder_hash = folder.hash();
|
||||
let (permissions, folder_exists, no_select, unseen) = {
|
||||
let (permissions, folder_path, folder_exists, no_select, unseen) = {
|
||||
let f = &self.folders.read().unwrap()[&folder_hash];
|
||||
(
|
||||
f.permissions.clone(),
|
||||
f.imap_path().to_string(),
|
||||
f.exists.clone(),
|
||||
f.no_select,
|
||||
f.unseen.clone(),
|
||||
|
@ -374,7 +375,7 @@ impl MailBackend for ImapType {
|
|||
Box::new(ImapOp::new(
|
||||
uid,
|
||||
self.folders.read().unwrap()[&folder_hash]
|
||||
.path()
|
||||
.imap_path()
|
||||
.to_string(),
|
||||
self.connection.clone(),
|
||||
self.uid_store.clone(),
|
||||
|
@ -400,7 +401,7 @@ impl MailBackend for ImapType {
|
|||
}
|
||||
|
||||
f_result
|
||||
.map(|v| v.path().to_string())
|
||||
.map(|v| v.imap_path().to_string())
|
||||
.ok_or(MeliError::new(format!(
|
||||
"Folder with name {} not found.",
|
||||
folder
|
||||
|
@ -425,70 +426,6 @@ impl MailBackend for ImapType {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
fn folder_operation(&mut self, path: &str, op: FolderOperation) -> Result<()> {
|
||||
use FolderOperation::*;
|
||||
|
||||
match (
|
||||
&op,
|
||||
self.folders
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.any(|f| f.path == path),
|
||||
) {
|
||||
(Create, true) => {
|
||||
return Err(MeliError::new(format!(
|
||||
"Folder named `{}` in account `{}` already exists.",
|
||||
path, self.account_name,
|
||||
)));
|
||||
}
|
||||
(op, false) if *op != Create => {
|
||||
return Err(MeliError::new(format!(
|
||||
"No folder named `{}` in account `{}`",
|
||||
path, self.account_name,
|
||||
)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let mut response = String::with_capacity(8 * 1024);
|
||||
match op {
|
||||
Create => {
|
||||
let mut conn = self.connection.lock()?;
|
||||
conn.send_command(format!("CREATE \"{}\"", path,).as_bytes())?;
|
||||
conn.read_response(&mut response)?;
|
||||
conn.send_command(format!("SUBSCRIBE \"{}\"", path,).as_bytes())?;
|
||||
conn.read_response(&mut response)?;
|
||||
}
|
||||
Rename(dest) => {
|
||||
let mut conn = self.connection.lock()?;
|
||||
conn.send_command(format!("RENAME \"{}\" \"{}\"", path, dest).as_bytes())?;
|
||||
conn.read_response(&mut response)?;
|
||||
}
|
||||
Delete => {
|
||||
let mut conn = self.connection.lock()?;
|
||||
conn.send_command(format!("DELETE \"{}\"", path,).as_bytes())?;
|
||||
conn.read_response(&mut response)?;
|
||||
}
|
||||
Subscribe => {
|
||||
let mut conn = self.connection.lock()?;
|
||||
conn.send_command(format!("SUBSCRIBE \"{}\"", path,).as_bytes())?;
|
||||
conn.read_response(&mut response)?;
|
||||
}
|
||||
Unsubscribe => {
|
||||
let mut conn = self.connection.lock()?;
|
||||
conn.send_command(format!("UNSUBSCRIBE \"{}\"", path,).as_bytes())?;
|
||||
conn.read_response(&mut response)?;
|
||||
}
|
||||
SetPermissions(_new_val) => {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
|
||||
fn as_any(&self) -> &dyn::std::any::Any {
|
||||
self
|
||||
}
|
||||
|
@ -505,36 +442,191 @@ impl MailBackend for ImapType {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_folder(&mut self, path: String) -> Result<Folder> {
|
||||
let mut response = String::with_capacity(8 * 1024);
|
||||
if self
|
||||
.folders
|
||||
.read()
|
||||
.unwrap()
|
||||
.values()
|
||||
.any(|f| f.path == path)
|
||||
{
|
||||
fn create_folder(&mut self, mut path: String) -> Result<Folder> {
|
||||
/* Must transform path to something the IMAP server will accept
|
||||
*
|
||||
* Each root mailbox has a hierarchy delimeter reported by the LIST entry. All paths
|
||||
* must use this delimeter to indicate children of this mailbox.
|
||||
*
|
||||
* A new root mailbox should have the default delimeter, which can be found out by issuing
|
||||
* an empty LIST command as described in RFC3501:
|
||||
* C: A101 LIST "" ""
|
||||
* S: * LIST (\Noselect) "/" ""
|
||||
*
|
||||
* The default delimiter for us is '/' just like UNIX paths. I apologise if this
|
||||
* decision is unpleasant for you.
|
||||
*/
|
||||
|
||||
let mut folders = self.folders.write().unwrap();
|
||||
for root_folder in folders.values().filter(|f| f.parent.is_none()) {
|
||||
if path.starts_with(&root_folder.name) {
|
||||
debug!("path starts with {:?}", &root_folder);
|
||||
path = path.replace(
|
||||
'/',
|
||||
(root_folder.separator as char).encode_utf8(&mut [0; 4]),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if folders.values().any(|f| f.path == path) {
|
||||
return Err(MeliError::new(format!(
|
||||
"Folder named `{}` in account `{}` already exists.",
|
||||
path, self.account_name,
|
||||
)));
|
||||
}
|
||||
let mut conn_lck = self.connection.lock()?;
|
||||
conn_lck.send_command(debug!(format!("CREATE \"{}\"", path,)).as_bytes())?;
|
||||
conn_lck.read_response(&mut response)?;
|
||||
conn_lck.send_command(debug!(format!("SUBSCRIBE \"{}\"", path,)).as_bytes())?;
|
||||
conn_lck.read_response(&mut response)?;
|
||||
drop(conn_lck);
|
||||
self.folders.write().unwrap().clear();
|
||||
self.folders().and_then(|f| {
|
||||
debug!(f)
|
||||
.into_iter()
|
||||
.find(|(_, f)| f.path() == path)
|
||||
.map(|f| f.1)
|
||||
.ok_or(MeliError::new(
|
||||
"Internal error: could not find folder after creating it?",
|
||||
|
||||
let mut response = String::with_capacity(8 * 1024);
|
||||
{
|
||||
let mut conn_lck = self.connection.lock()?;
|
||||
|
||||
conn_lck.send_command(format!("CREATE \"{}\"", path,).as_bytes())?;
|
||||
conn_lck.read_response(&mut response)?;
|
||||
conn_lck.send_command(format!("SUBSCRIBE \"{}\"", path,).as_bytes())?;
|
||||
conn_lck.read_response(&mut response)?;
|
||||
}
|
||||
let ret: Result<()> = ImapResponse::from(&response).into();
|
||||
ret?;
|
||||
folders.clear();
|
||||
drop(folders);
|
||||
self.folders().map_err(|err| format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
|
||||
let new_hash = get_path_hash!(path.as_str());
|
||||
Ok(BackendFolder::clone(
|
||||
&self.folders.read().unwrap()[&new_hash],
|
||||
))
|
||||
}
|
||||
|
||||
fn delete_folder(&mut self, folder_hash: FolderHash) -> Result<()> {
|
||||
let mut folders = self.folders.write().unwrap();
|
||||
let permissions = folders[&folder_hash].permissions();
|
||||
if !permissions.delete_mailbox {
|
||||
return Err(MeliError::new(format!("You do not have permission to delete `{}`. Set permissions for this mailbox are {}", folders[&folder_hash].name(), permissions)));
|
||||
}
|
||||
let mut response = String::with_capacity(8 * 1024);
|
||||
{
|
||||
let mut conn_lck = self.connection.lock()?;
|
||||
if folders[&folder_hash].is_subscribed() {
|
||||
conn_lck.send_command(
|
||||
format!("UNSUBSCRIBE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
|
||||
)?;
|
||||
conn_lck.read_response(&mut response)?;
|
||||
}
|
||||
|
||||
if !folders[&folder_hash].no_select {
|
||||
/* make sure mailbox is not selected before it gets deleted, otherwise
|
||||
* connection gets dropped by server */
|
||||
if conn_lck
|
||||
.capabilities
|
||||
.iter()
|
||||
.any(|cap| cap.eq_ignore_ascii_case(b"UNSELECT"))
|
||||
{
|
||||
conn_lck.send_command(
|
||||
format!("UNSELECT \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
|
||||
)?;
|
||||
conn_lck.read_response(&mut response)?;
|
||||
} else {
|
||||
conn_lck.send_command(
|
||||
format!("SELECT \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
|
||||
)?;
|
||||
conn_lck.read_response(&mut response)?;
|
||||
conn_lck.send_command(
|
||||
format!("EXAMINE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
|
||||
)?;
|
||||
conn_lck.read_response(&mut response)?;
|
||||
}
|
||||
}
|
||||
conn_lck.send_command(
|
||||
debug!(format!("DELETE \"{}\"", folders[&folder_hash].imap_path())).as_bytes(),
|
||||
)?;
|
||||
conn_lck.read_response(&mut response)?;
|
||||
}
|
||||
let ret: Result<()> = ImapResponse::from(&response).into();
|
||||
if ret.is_ok() {
|
||||
folders.clear();
|
||||
drop(folders);
|
||||
self.folders().map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn set_folder_subscription(&mut self, folder_hash: FolderHash, new_val: bool) -> Result<()> {
|
||||
let mut folders = self.folders.write().unwrap();
|
||||
if folders[&folder_hash].is_subscribed() == new_val {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut response = String::with_capacity(8 * 1024);
|
||||
{
|
||||
let mut conn_lck = self.connection.lock()?;
|
||||
if new_val {
|
||||
conn_lck.send_command(
|
||||
format!("SUBSCRIBE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
|
||||
)?;
|
||||
} else {
|
||||
conn_lck.send_command(
|
||||
format!("UNSUBSCRIBE \"{}\"", folders[&folder_hash].imap_path()).as_bytes(),
|
||||
)?;
|
||||
}
|
||||
conn_lck.read_response(&mut response)?;
|
||||
}
|
||||
|
||||
let ret: Result<()> = ImapResponse::from(&response).into();
|
||||
if ret.is_ok() {
|
||||
folders.entry(folder_hash).and_modify(|entry| {
|
||||
let _ = entry.set_is_subscribed(new_val);
|
||||
});
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn rename_folder(&mut self, folder_hash: FolderHash, mut new_path: String) -> Result<Folder> {
|
||||
let mut folders = self.folders.write().unwrap();
|
||||
let permissions = folders[&folder_hash].permissions();
|
||||
if !permissions.delete_mailbox {
|
||||
return Err(MeliError::new(format!("You do not have permission to rename folder `{}` (rename is equivalent to delete + create). Set permissions for this mailbox are {}", folders[&folder_hash].name(), permissions)));
|
||||
}
|
||||
let mut response = String::with_capacity(8 * 1024);
|
||||
if folders[&folder_hash].separator != b'/' {
|
||||
new_path = new_path.replace(
|
||||
'/',
|
||||
(folders[&folder_hash].separator as char).encode_utf8(&mut [0; 4]),
|
||||
);
|
||||
}
|
||||
{
|
||||
let mut conn_lck = self.connection.lock()?;
|
||||
conn_lck.send_command(
|
||||
debug!(format!(
|
||||
"RENAME \"{}\" \"{}\"",
|
||||
folders[&folder_hash].imap_path(),
|
||||
new_path
|
||||
))
|
||||
})
|
||||
.as_bytes(),
|
||||
)?;
|
||||
conn_lck.read_response(&mut response)?;
|
||||
}
|
||||
let new_hash = get_path_hash!(new_path.as_str());
|
||||
let ret: Result<()> = ImapResponse::from(&response).into();
|
||||
ret?;
|
||||
folders.clear();
|
||||
drop(folders);
|
||||
self.folders().map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
|
||||
Ok(BackendFolder::clone(
|
||||
&self.folders.read().unwrap()[&new_hash],
|
||||
))
|
||||
}
|
||||
|
||||
fn set_folder_permissions(
|
||||
&mut self,
|
||||
folder_hash: FolderHash,
|
||||
_val: crate::backends::FolderPermissions,
|
||||
) -> Result<()> {
|
||||
let folders = self.folders.write().unwrap();
|
||||
let permissions = folders[&folder_hash].permissions();
|
||||
if !permissions.change_permissions {
|
||||
return Err(MeliError::new(format!("You do not have permission to change permissions for folder `{}`. Set permissions for this mailbox are {}", folders[&folder_hash].name(), permissions)));
|
||||
}
|
||||
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -547,13 +639,7 @@ impl ImapType {
|
|||
let server_username = get_conf_val!(s["server_username"])?;
|
||||
let server_password = get_conf_val!(s["server_password"])?;
|
||||
let server_port = get_conf_val!(s["server_port"], 143)?;
|
||||
let use_starttls = get_conf_val!(s["use_starttls"], {
|
||||
if server_port == 993 {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})?;
|
||||
let use_starttls = get_conf_val!(s["use_starttls"], !(server_port == 993))?;
|
||||
let danger_accept_invalid_certs: bool =
|
||||
get_conf_val!(s["danger_accept_invalid_certs"], false)?;
|
||||
let server_conf = ImapServerConf {
|
||||
|
@ -707,7 +793,9 @@ impl ImapType {
|
|||
let folders_lck = self.folders.read()?;
|
||||
let mut response = String::with_capacity(8 * 1024);
|
||||
let mut conn = self.connection.lock()?;
|
||||
conn.send_command(format!("EXAMINE \"{}\"", folders_lck[&folder_hash].path()).as_bytes())?;
|
||||
conn.send_command(
|
||||
format!("EXAMINE \"{}\"", folders_lck[&folder_hash].imap_path()).as_bytes(),
|
||||
)?;
|
||||
conn.read_response(&mut response)?;
|
||||
conn.send_command(format!("UID SEARCH CHARSET UTF-8 {}", query).as_bytes())?;
|
||||
conn.read_response(&mut response)?;
|
||||
|
|
|
@ -340,8 +340,14 @@ impl ImapConnection {
|
|||
|
||||
pub fn read_response(&mut self, ret: &mut String) -> Result<()> {
|
||||
self.try_send(|s| s.read_response(ret))?;
|
||||
let r: Result<()> = ImapResponse::from(&ret).into();
|
||||
r
|
||||
let r: ImapResponse = ImapResponse::from(&ret);
|
||||
if let ImapResponse::Bye(ref response_code) = r {
|
||||
self.stream = Err(MeliError::new(format!(
|
||||
"Offline: received BYE: {:?}",
|
||||
response_code
|
||||
)));
|
||||
}
|
||||
r.into()
|
||||
}
|
||||
|
||||
pub fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> {
|
||||
|
|
|
@ -25,10 +25,12 @@ use std::sync::{Arc, Mutex, RwLock};
|
|||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ImapFolder {
|
||||
pub(super) hash: FolderHash,
|
||||
pub(super) imap_path: String,
|
||||
pub(super) path: String,
|
||||
pub(super) name: String,
|
||||
pub(super) parent: Option<FolderHash>,
|
||||
pub(super) children: Vec<FolderHash>,
|
||||
pub separator: u8,
|
||||
pub usage: Arc<RwLock<SpecialUsageMailbox>>,
|
||||
pub no_select: bool,
|
||||
pub is_subscribed: bool,
|
||||
|
@ -38,6 +40,12 @@ pub struct ImapFolder {
|
|||
pub unseen: Arc<Mutex<usize>>,
|
||||
}
|
||||
|
||||
impl ImapFolder {
|
||||
pub fn imap_path(&self) -> &str {
|
||||
&self.imap_path
|
||||
}
|
||||
}
|
||||
|
||||
impl BackendFolder for ImapFolder {
|
||||
fn hash(&self) -> FolderHash {
|
||||
self.hash
|
||||
|
@ -79,7 +87,6 @@ impl BackendFolder for ImapFolder {
|
|||
}
|
||||
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
|
||||
self.is_subscribed = new_val;
|
||||
// FIXME: imap subscribe
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -21,9 +21,8 @@
|
|||
|
||||
use super::*;
|
||||
use crate::email::parser::BytesExt;
|
||||
use crate::get_path_hash;
|
||||
use nom::{digit, is_digit, rest, IResult};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -65,54 +64,55 @@ pub enum ResponseCode {
|
|||
Uidvalidity(UID),
|
||||
/// Followed by a decimal number, indicates the number of the first message without the \Seen flag set.
|
||||
Unseen(usize),
|
||||
|
||||
None,
|
||||
}
|
||||
|
||||
impl ResponseCode {
|
||||
fn from(val: &str) -> Option<ResponseCode> {
|
||||
fn from(val: &str) -> ResponseCode {
|
||||
use ResponseCode::*;
|
||||
if !val.starts_with("[") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let val = &val[1..];
|
||||
use ResponseCode::*;
|
||||
if val.starts_with("BADCHARSET") {
|
||||
Some(Badcharset)
|
||||
Badcharset
|
||||
} else if val.starts_with("READONLY") {
|
||||
Some(ReadOnly)
|
||||
ReadOnly
|
||||
} else if val.starts_with("READWRITE") {
|
||||
Some(ReadWrite)
|
||||
ReadWrite
|
||||
} else if val.starts_with("TRYCREATE") {
|
||||
Some(Trycreate)
|
||||
Trycreate
|
||||
} else if val.starts_with("UIDNEXT") {
|
||||
//FIXME
|
||||
Some(Uidnext(0))
|
||||
Uidnext(0)
|
||||
} else if val.starts_with("UIDVALIDITY") {
|
||||
//FIXME
|
||||
Some(Uidvalidity(0))
|
||||
Uidvalidity(0)
|
||||
} else if val.starts_with("UNSEEN") {
|
||||
//FIXME
|
||||
Some(Unseen(0))
|
||||
Unseen(0)
|
||||
} else {
|
||||
let msg = &val[val.as_bytes().find(b"] ").unwrap() + 1..].trim();
|
||||
Some(Alert(msg.to_string()))
|
||||
Alert(msg.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ImapResponse {
|
||||
Ok(Option<ResponseCode>),
|
||||
No(Option<ResponseCode>),
|
||||
Bad(Option<ResponseCode>),
|
||||
Preauth(Option<ResponseCode>),
|
||||
Bye(Option<ResponseCode>),
|
||||
Ok(ResponseCode),
|
||||
No(ResponseCode),
|
||||
Bad(ResponseCode),
|
||||
Preauth(ResponseCode),
|
||||
Bye(ResponseCode),
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> From<T> for ImapResponse {
|
||||
fn from(val: T) -> ImapResponse {
|
||||
let val: &str = val.as_ref().split_rn().last().unwrap_or(val.as_ref());
|
||||
debug!(&val);
|
||||
assert!(val.starts_with("M"));
|
||||
let mut val = val[val.as_bytes().find(b" ").unwrap() + 1..].trim();
|
||||
// M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n
|
||||
if val.ends_with(" secs).") {
|
||||
|
@ -139,8 +139,9 @@ impl Into<Result<()>> for ImapResponse {
|
|||
fn into(self) -> Result<()> {
|
||||
match self {
|
||||
Self::Ok(_) | Self::Preauth(_) | Self::Bye(_) => Ok(()),
|
||||
Self::No(Some(ResponseCode::Alert(msg)))
|
||||
| Self::Bad(Some(ResponseCode::Alert(msg))) => Err(MeliError::new(msg)),
|
||||
Self::No(ResponseCode::Alert(msg)) | Self::Bad(ResponseCode::Alert(msg)) => {
|
||||
Err(MeliError::new(msg))
|
||||
}
|
||||
Self::No(_) => Err(MeliError::new("IMAP NO Response.")),
|
||||
Self::Bad(_) => Err(MeliError::new("IMAP BAD Response.")),
|
||||
}
|
||||
|
@ -149,7 +150,7 @@ impl Into<Result<()>> for ImapResponse {
|
|||
|
||||
#[test]
|
||||
fn test_imap_response() {
|
||||
assert_eq!(ImapResponse::from("M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n"), ImapResponse::No(Some(ResponseCode::Alert("Invalid mailbox name: Name must not have '/' characters".to_string()))));
|
||||
assert_eq!(ImapResponse::from("M12 NO [CANNOT] Invalid mailbox name: Name must not have \'/\' characters (0.000 + 0.098 + 0.097 secs).\r\n"), ImapResponse::No(ResponseCode::Alert("Invalid mailbox name: Name must not have '/' characters".to_string())));
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ImapLineIterator<'a> {
|
||||
|
@ -206,13 +207,7 @@ macro_rules! dbg_dmp (
|
|||
dbg_dmp!($i, call!($f));
|
||||
);
|
||||
);
|
||||
macro_rules! get_path_hash {
|
||||
($path:expr) => {{
|
||||
let mut hasher = DefaultHasher::new();
|
||||
$path.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}};
|
||||
}
|
||||
|
||||
/*
|
||||
* LIST (\HasNoChildren) "." INBOX.Sent
|
||||
* LIST (\HasChildren) "." INBOX
|
||||
|
@ -243,14 +238,20 @@ named!(
|
|||
let _ = f.set_special_usage(SpecialUsageMailbox::Drafts);
|
||||
}
|
||||
}
|
||||
f.hash = get_path_hash!(path);
|
||||
f.path = String::from_utf8_lossy(path).into();
|
||||
f.name = if let Some(pos) = path.iter().rposition(|&c| c == separator) {
|
||||
f.parent = Some(get_path_hash!(&path[..pos]));
|
||||
String::from_utf8_lossy(&path[pos + 1..]).into()
|
||||
f.imap_path = String::from_utf8_lossy(path).into();
|
||||
f.hash = get_path_hash!(&f.imap_path);
|
||||
f.path = if separator == b'/' {
|
||||
f.imap_path.clone()
|
||||
} else {
|
||||
f.path.clone()
|
||||
f.imap_path.replace(separator as char, "/")
|
||||
};
|
||||
f.name = if let Some(pos) = f.imap_path.as_bytes().iter().rposition(|&c| c == separator) {
|
||||
f.parent = Some(get_path_hash!(&f.imap_path[..pos]));
|
||||
f.imap_path[pos + 1..].to_string()
|
||||
} else {
|
||||
f.imap_path.clone()
|
||||
};
|
||||
f.separator = separator;
|
||||
|
||||
debug!(f)
|
||||
})
|
||||
|
|
|
@ -149,7 +149,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
|
|||
folder_hash,
|
||||
work_context,
|
||||
thread_id,
|
||||
conn.send_command(format!("SELECT \"{}\"", folder.path()).as_bytes())
|
||||
conn.send_command(format!("SELECT \"{}\"", folder.imap_path()).as_bytes())
|
||||
conn.read_response(&mut response)
|
||||
);
|
||||
debug!("select response {}", &response);
|
||||
|
@ -531,7 +531,7 @@ fn examine_updates(
|
|||
folder_hash,
|
||||
work_context,
|
||||
thread_id,
|
||||
conn.send_command(format!("EXAMINE \"{}\"", folder.path()).as_bytes())
|
||||
conn.send_command(format!("EXAMINE \"{}\"", folder.imap_path()).as_bytes())
|
||||
conn.read_response(&mut response)
|
||||
);
|
||||
match protocol_parser::select_response(&response) {
|
||||
|
|
|
@ -63,16 +63,6 @@ pub trait Method<OBJ: Object>: Serialize {
|
|||
const NAME: &'static str;
|
||||
}
|
||||
|
||||
macro_rules! get_path_hash {
|
||||
($path:expr) => {{
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
let mut hasher = DefaultHasher::new();
|
||||
$path.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}};
|
||||
}
|
||||
|
||||
static USING: &'static [&'static str] = &["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"];
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
|
|
@ -192,42 +192,20 @@ impl MaildirFolder {
|
|||
children: Vec<FolderHash>,
|
||||
settings: &AccountSettings,
|
||||
) -> Result<Self> {
|
||||
macro_rules! strip_slash {
|
||||
($v:expr) => {
|
||||
if $v.ends_with("/") {
|
||||
&$v[..$v.len() - 1]
|
||||
} else {
|
||||
$v
|
||||
}
|
||||
};
|
||||
}
|
||||
let pathbuf = PathBuf::from(&path);
|
||||
let mut h = DefaultHasher::new();
|
||||
pathbuf.hash(&mut h);
|
||||
|
||||
/* Check if folder path (Eg `INBOX/Lists/luddites`) is included in the subscribed
|
||||
* mailboxes in user configuration */
|
||||
let fname = if let Ok(fname) = pathbuf.strip_prefix(
|
||||
PathBuf::from(&settings.root_folder)
|
||||
.expand()
|
||||
.parent()
|
||||
.unwrap_or_else(|| &Path::new("/")),
|
||||
) {
|
||||
if fname.components().count() != 0
|
||||
&& !settings
|
||||
.subscribed_folders
|
||||
.iter()
|
||||
.any(|x| x == strip_slash!(fname.to_str().unwrap()))
|
||||
{
|
||||
return Err(MeliError::new(format!(
|
||||
"Folder with name `{}` is not included in configured subscribed mailboxes",
|
||||
fname.display()
|
||||
)));
|
||||
}
|
||||
Some(fname)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let fname = pathbuf
|
||||
.strip_prefix(
|
||||
PathBuf::from(&settings.root_folder)
|
||||
.expand()
|
||||
.parent()
|
||||
.unwrap_or_else(|| &Path::new("/")),
|
||||
)
|
||||
.ok();
|
||||
|
||||
let read_only = if let Ok(metadata) = std::fs::metadata(&pathbuf) {
|
||||
metadata.permissions().readonly()
|
||||
|
@ -243,7 +221,7 @@ impl MaildirFolder {
|
|||
parent,
|
||||
children,
|
||||
usage: Arc::new(RwLock::new(SpecialUsageMailbox::Normal)),
|
||||
is_subscribed: true,
|
||||
is_subscribed: false,
|
||||
permissions: FolderPermissions {
|
||||
create_messages: !read_only,
|
||||
remove_messages: !read_only,
|
||||
|
|
|
@ -34,12 +34,7 @@ extern crate notify;
|
|||
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||
use std::time::Duration;
|
||||
|
||||
use std::sync::mpsc::channel;
|
||||
//use std::sync::mpsc::sync_channel;
|
||||
//use std::sync::mpsc::SyncSender;
|
||||
//use std::time::Duration;
|
||||
use fnv::{FnvHashMap, FnvHashSet, FnvHasher};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
@ -48,6 +43,7 @@ use std::ops::{Deref, DerefMut};
|
|||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::result;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
|
@ -132,7 +128,6 @@ macro_rules! path_is_new {
|
|||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_path_hash {
|
||||
($path:expr) => {{
|
||||
let mut path = $path.clone();
|
||||
|
@ -145,9 +140,7 @@ macro_rules! get_path_hash {
|
|||
path.pop();
|
||||
};
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
path.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
crate::get_path_hash!(path)
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -642,6 +635,64 @@ impl MailBackend for MaildirType {
|
|||
fn as_any(&self) -> &dyn::std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn create_folder(&mut self, new_path: String) -> Result<Folder> {
|
||||
let mut path = self.path.clone();
|
||||
path.push(&new_path);
|
||||
if !path.starts_with(&self.path) {
|
||||
return Err(MeliError::new(format!("Path given (`{}`) is absolute. Please provide a path relative to the account's root folder.", &new_path)));
|
||||
}
|
||||
|
||||
std::fs::create_dir(&path)?;
|
||||
/* create_dir does not create intermediate directories (like `mkdir -p`), so the parent must be a valid
|
||||
* folder at this point. */
|
||||
|
||||
let parent = path.parent().and_then(|p| {
|
||||
self.folders
|
||||
.iter()
|
||||
.find(|(_, f)| f.fs_path == p)
|
||||
.map(|item| *item.0)
|
||||
});
|
||||
|
||||
let folder_hash = get_path_hash!(&path);
|
||||
let new_folder = MaildirFolder {
|
||||
hash: folder_hash,
|
||||
path: PathBuf::from(&new_path),
|
||||
name: new_path,
|
||||
fs_path: path,
|
||||
parent,
|
||||
children: vec![],
|
||||
usage: Default::default(),
|
||||
is_subscribed: true,
|
||||
permissions: Default::default(),
|
||||
unseen: Default::default(),
|
||||
total: Default::default(),
|
||||
};
|
||||
|
||||
let ret = BackendFolder::clone(debug!(&new_folder));
|
||||
self.folders.insert(folder_hash, new_folder);
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn delete_folder(&mut self, _folder_hash: FolderHash) -> Result<()> {
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
}
|
||||
|
||||
fn set_folder_subscription(&mut self, _folder_hash: FolderHash, _val: bool) -> Result<()> {
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
}
|
||||
|
||||
fn rename_folder(&mut self, _folder_hash: FolderHash, _new_path: String) -> Result<Folder> {
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
}
|
||||
|
||||
fn set_folder_permissions(
|
||||
&mut self,
|
||||
_folder_hash: FolderHash,
|
||||
_val: crate::backends::FolderPermissions,
|
||||
) -> Result<()> {
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
}
|
||||
}
|
||||
|
||||
impl MaildirType {
|
||||
|
@ -667,7 +718,7 @@ impl MaildirType {
|
|||
)));
|
||||
}
|
||||
let mut children = Vec::new();
|
||||
for mut f in fs::read_dir(p).unwrap() {
|
||||
for mut f in fs::read_dir(&p).unwrap() {
|
||||
'entries: for f in f.iter_mut() {
|
||||
{
|
||||
let path = f.path();
|
||||
|
@ -736,10 +787,10 @@ impl MaildirType {
|
|||
.count();
|
||||
folders.get_mut(&root_hash).map(|f| f.children = children);
|
||||
}
|
||||
folders.retain(|_, f| is_subscribed(f.path()));
|
||||
let keys = folders.keys().cloned().collect::<FnvHashSet<FolderHash>>();
|
||||
for f in folders.values_mut() {
|
||||
f.children.retain(|c| keys.contains(c));
|
||||
if is_subscribed(f.path()) {
|
||||
f.is_subscribed = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mut hash_indexes =
|
||||
|
|
|
@ -34,6 +34,7 @@ use crate::conf::AccountSettings;
|
|||
use crate::email::parser::BytesExt;
|
||||
use crate::email::*;
|
||||
use crate::error::{MeliError, Result};
|
||||
use crate::get_path_hash;
|
||||
use crate::shellexpand::ShellExpandTrait;
|
||||
use fnv::FnvHashMap;
|
||||
use libc;
|
||||
|
@ -41,9 +42,7 @@ use memmap::{Mmap, Protection};
|
|||
use nom::{IResult, Needed};
|
||||
extern crate notify;
|
||||
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fs::File;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
@ -74,14 +73,6 @@ fn get_rw_lock_blocking(f: &File) {
|
|||
assert!(-1 != ret_val);
|
||||
}
|
||||
|
||||
macro_rules! get_path_hash {
|
||||
($path:expr) => {{
|
||||
let mut hasher = DefaultHasher::new();
|
||||
$path.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MboxFolder {
|
||||
hash: FolderHash,
|
||||
|
|
|
@ -74,7 +74,7 @@ pub fn timestamp_to_string(timestamp: UnixTimestamp, fmt: Option<&str>) -> Strin
|
|||
s.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
fn tm_to_secs(tm: ::libc::tm) -> UnixTimestamp {
|
||||
fn tm_to_secs(tm: ::libc::tm) -> std::result::Result<i64, ()> {
|
||||
let mut is_leap = false;
|
||||
let mut year = tm.tm_year;
|
||||
let mut month = tm.tm_mon;
|
||||
|
@ -87,19 +87,19 @@ fn tm_to_secs(tm: ::libc::tm) -> UnixTimestamp {
|
|||
}
|
||||
year += adj;
|
||||
}
|
||||
let mut t = year_to_secs(year as u64, &mut is_leap);
|
||||
t += month_to_secs(month as usize, is_leap);
|
||||
t += 86400 * (tm.tm_mday as u64 - 1);
|
||||
t += 3600 * (tm.tm_hour as u64);
|
||||
t += 60 * (tm.tm_min as u64);
|
||||
t += tm.tm_sec as u64;
|
||||
t
|
||||
let mut t = year_to_secs(year.into(), &mut is_leap)?;
|
||||
t += month_to_secs(month.try_into().unwrap_or(0), is_leap);
|
||||
t += 86400 * (tm.tm_mday - 1) as i64;
|
||||
t += 3600 * (tm.tm_hour) as i64;
|
||||
t += 60 * (tm.tm_min) as i64;
|
||||
t += tm.tm_sec as i64;
|
||||
Ok(t)
|
||||
}
|
||||
|
||||
fn year_to_secs(year: u64, is_leap: &mut bool) -> u64 {
|
||||
if year < 100 {
|
||||
fn year_to_secs(year: i64, is_leap: &mut bool) -> std::result::Result<i64, ()> {
|
||||
if year < -100 {
|
||||
/* Sorry time travelers. */
|
||||
return 0;
|
||||
return Err(());
|
||||
}
|
||||
|
||||
if year - 2 <= 136 {
|
||||
|
@ -111,7 +111,9 @@ fn year_to_secs(year: u64, is_leap: &mut bool) -> u64 {
|
|||
} else {
|
||||
*is_leap = false;
|
||||
}
|
||||
return 31536000 * (y - 70) + 86400 * leaps;
|
||||
return Ok((31536000 * (y - 70) + 86400 * leaps)
|
||||
.try_into()
|
||||
.unwrap_or(0));
|
||||
}
|
||||
|
||||
let cycles = (year - 100) / 400;
|
||||
|
@ -154,11 +156,14 @@ fn year_to_secs(year: u64, is_leap: &mut bool) -> u64 {
|
|||
|
||||
leaps += 97 * cycles + 24 * centuries - if *is_leap { 1 } else { 0 };
|
||||
|
||||
return (year - 100) * 31536000 + leaps * 86400 + 946684800 + 86400;
|
||||
return Ok(match (year - 100).overflowing_mul(31536000) {
|
||||
(_, true) => return Err(()),
|
||||
(res, false) => res + leaps * 86400 + 946684800 + 86400,
|
||||
});
|
||||
}
|
||||
|
||||
fn month_to_secs(month: usize, is_leap: bool) -> u64 {
|
||||
const SECS_THROUGH_MONTH: [u64; 12] = [
|
||||
fn month_to_secs(month: usize, is_leap: bool) -> i64 {
|
||||
const SECS_THROUGH_MONTH: [i64; 12] = [
|
||||
0,
|
||||
31 * 86400,
|
||||
59 * 86400,
|
||||
|
@ -172,7 +177,7 @@ fn month_to_secs(month: usize, is_leap: bool) -> u64 {
|
|||
304 * 86400,
|
||||
334 * 86400,
|
||||
];
|
||||
let mut t = SECS_THROUGH_MONTH[month as usize];
|
||||
let mut t = SECS_THROUGH_MONTH[month];
|
||||
if is_leap && month >= 2 {
|
||||
t += 86400;
|
||||
}
|
||||
|
@ -226,7 +231,9 @@ where
|
|||
0
|
||||
}
|
||||
};
|
||||
return (tm_to_secs(new_tm) as i64 - tm_gmtoff) as u64;
|
||||
return tm_to_secs(new_tm)
|
||||
.map(|res| (res - tm_gmtoff) as u64)
|
||||
.unwrap_or(0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -929,7 +929,7 @@ pub fn phrase(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
|||
/* We have the start of an encoded word but not the end, so parse it as ascii */
|
||||
ascii_e = input[ascii_s..]
|
||||
.find(b" ")
|
||||
.unwrap_or_else(|| input[ascii_s..].len());
|
||||
.unwrap_or_else(|| ascii_s + input[ascii_s..].len());
|
||||
ptr = ascii_e;
|
||||
}
|
||||
|
||||
|
@ -1052,7 +1052,7 @@ mod tests {
|
|||
use crate::make_address;
|
||||
|
||||
#[test]
|
||||
fn test_subject() {
|
||||
fn test_phrase() {
|
||||
let words = b"=?iso-8859-7?B?W215Y291cnNlcy5udHVhLmdyIC0gyvXs4fTp6t4g6uHpIMri4e306ere?=
|
||||
=?iso-8859-7?B?INb18+nq3l0gzd3hIMHt4erv3+358+c6IMzF0c/TIMHQz9TFy8XTzMHU?=
|
||||
=?iso-8859-7?B?2c0gwiDUzC4gysHNLiDFzsXUwdPH0yAyMDE3LTE4OiDTx8zFydnTxw==?=";
|
||||
|
@ -1145,7 +1145,10 @@ mod tests {
|
|||
debug!("{:?}", date(__s));
|
||||
assert_eq!(date(s).unwrap(), date(_s).unwrap());
|
||||
assert_eq!(date(_s).unwrap(), date(__s).unwrap());
|
||||
let val = b"Fri, 23 Dec 0001 21:20:36 -0800 (PST)";
|
||||
assert_eq!(date(val).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attachments() {
|
||||
//FIXME: add file
|
||||
|
|
|
@ -301,8 +301,6 @@ pub mod shellexpand {
|
|||
|
||||
#[test]
|
||||
fn test_shellexpandtrait() {
|
||||
let path = &Path::new("~/.v");
|
||||
//println!("ret = {:?}", path.expand().complete(false));
|
||||
assert!(Path::new("~").expand().complete(false).is_empty());
|
||||
assert!(!Path::new("~").expand().complete(true).is_empty());
|
||||
}
|
||||
|
|
|
@ -71,10 +71,16 @@ pub trait TextProcessing: UnicodeSegmentation + CodePointsIter {
|
|||
|
||||
impl TextProcessing for str {
|
||||
fn split_lines(&self, width: usize) -> Vec<String> {
|
||||
if width == 0 {
|
||||
return vec![];
|
||||
}
|
||||
super::line_break::linear(self, width)
|
||||
}
|
||||
|
||||
fn split_lines_reflow(&self, reflow: Reflow, width: Option<usize>) -> Vec<String> {
|
||||
if width == Some(0) {
|
||||
return vec![];
|
||||
}
|
||||
super::line_break::split_lines_reflow(self, reflow, width)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1023,6 +1023,9 @@ pub fn split_lines_reflow(text: &str, reflow: Reflow, width: Option<usize>) -> V
|
|||
ret.push(format!("⤷{}", &line[prev_line_offset..end_offset]));
|
||||
}
|
||||
}
|
||||
if prev_line_offset == end_offset && prev == new_off {
|
||||
break;
|
||||
}
|
||||
prev_line_offset = end_offset;
|
||||
prev = new_off;
|
||||
}
|
||||
|
|
|
@ -56,36 +56,60 @@ pub trait GlobMatch {
|
|||
}
|
||||
|
||||
impl GlobMatch for str {
|
||||
fn matches_glob(&self, s: &str) -> bool {
|
||||
let parts = s.split("*");
|
||||
fn matches_glob(&self, _pattern: &str) -> bool {
|
||||
macro_rules! strip_slash {
|
||||
($v:expr) => {
|
||||
if $v.ends_with("/") {
|
||||
&$v[..$v.len() - 1]
|
||||
} else {
|
||||
$v
|
||||
}
|
||||
};
|
||||
}
|
||||
let pattern: Vec<&str> = strip_slash!(_pattern).split_graphemes();
|
||||
let s: Vec<&str> = strip_slash!(self).split_graphemes();
|
||||
|
||||
let mut ptr = 0;
|
||||
let mut part_no = 0;
|
||||
// Taken from https://research.swtch.com/glob
|
||||
|
||||
for p in parts {
|
||||
if p.is_empty() {
|
||||
/* asterisk is at beginning and/or end of glob */
|
||||
/* eg "*".split("*") gives ["", ""] */
|
||||
part_no += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ptr >= self.len() {
|
||||
return false;
|
||||
}
|
||||
if part_no > 0 {
|
||||
while !&self[ptr..].starts_with(p) {
|
||||
ptr += 1;
|
||||
if ptr >= self.len() {
|
||||
return false;
|
||||
let mut px = 0;
|
||||
let mut sx = 0;
|
||||
let mut next_px = 0;
|
||||
let mut next_sx = 0;
|
||||
while px < pattern.len() || sx < s.len() {
|
||||
if px < pattern.len() {
|
||||
match pattern[px] {
|
||||
"?" => {
|
||||
if sx < s.len() {
|
||||
px += 1;
|
||||
sx += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
"*" => {
|
||||
// Try to match at sx.
|
||||
// If that doesn't work out,
|
||||
// restart at sx+1 next.
|
||||
next_px = px;
|
||||
next_sx = sx + 1;
|
||||
px += 1;
|
||||
continue;
|
||||
}
|
||||
p => {
|
||||
if sx < s.len() && s[sx] == p {
|
||||
px += 1;
|
||||
sx += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !&self[ptr..].starts_with(p) {
|
||||
return false;
|
||||
// Mismatch. Maybe restart.
|
||||
if 0 < next_sx && next_sx <= s.len() {
|
||||
px = next_px;
|
||||
sx = next_sx;
|
||||
continue;
|
||||
}
|
||||
ptr += p.len();
|
||||
part_no += 1;
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -98,8 +122,18 @@ impl GlobMatch for str {
|
|||
#[test]
|
||||
fn test_globmatch() {
|
||||
assert!("INBOX".matches_glob("INBOX"));
|
||||
assert!("INBOX/".matches_glob("INBOX"));
|
||||
assert!("INBOX".matches_glob("INBO?"));
|
||||
|
||||
assert!("INBOX/Sent".matches_glob("INBOX/*"));
|
||||
assert!(!"INBOX/Sent".matches_glob("INBOX"));
|
||||
assert!(!"INBOX/Sent".matches_glob("*/Drafts"));
|
||||
assert!("INBOX/Sent".matches_glob("*/Sent"));
|
||||
|
||||
assert!("INBOX/Archives/2047".matches_glob("*"));
|
||||
assert!("INBOX/Archives/2047".matches_glob("INBOX/*/2047"));
|
||||
assert!("INBOX/Archives/2047".matches_glob("INBOX/Archives/2*047"));
|
||||
assert!("INBOX/Archives/2047".matches_glob("INBOX/Archives/204?"));
|
||||
|
||||
assert!(!"INBOX/Lists/".matches_glob("INBOX/Lists/*"));
|
||||
}
|
||||
|
|
84
src/bin.rs
84
src/bin.rs
|
@ -134,8 +134,14 @@ macro_rules! error_and_exit {
|
|||
}}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ManPages {
|
||||
Main = 0,
|
||||
Conf = 1,
|
||||
Themes = 2,
|
||||
}
|
||||
|
||||
struct CommandLineArguments {
|
||||
print_manpage: Option<ManPages>,
|
||||
create_config: Option<String>,
|
||||
test_config: Option<String>,
|
||||
config: Option<String>,
|
||||
|
@ -155,6 +161,7 @@ fn main() {
|
|||
|
||||
fn run_app() -> Result<()> {
|
||||
enum CommandLineFlags {
|
||||
PrintManPage,
|
||||
CreateConfig,
|
||||
TestConfig,
|
||||
Config,
|
||||
|
@ -162,6 +169,7 @@ fn run_app() -> Result<()> {
|
|||
use CommandLineFlags::*;
|
||||
let mut prev: Option<CommandLineFlags> = None;
|
||||
let mut args = CommandLineArguments {
|
||||
print_manpage: None,
|
||||
create_config: None,
|
||||
test_config: None,
|
||||
config: None,
|
||||
|
@ -176,12 +184,18 @@ fn run_app() -> Result<()> {
|
|||
Some(CreateConfig) => error_and_exit!("invalid value for flag `--create-config`"),
|
||||
Some(Config) => error_and_exit!("invalid value for flag `--config`"),
|
||||
Some(TestConfig) => error_and_exit!("invalid value for flag `--test-config`"),
|
||||
Some(PrintManPage) => {
|
||||
error_and_exit!("invalid value for flag `--print-documentation`")
|
||||
}
|
||||
},
|
||||
"--create-config" => match prev {
|
||||
None => prev = Some(CreateConfig),
|
||||
Some(CreateConfig) => error_and_exit!("invalid value for flag `--create-config`"),
|
||||
Some(TestConfig) => error_and_exit!("invalid value for flag `--test-config`"),
|
||||
Some(Config) => error_and_exit!("invalid value for flag `--config`"),
|
||||
Some(PrintManPage) => {
|
||||
error_and_exit!("invalid value for flag `--print-documentation`")
|
||||
}
|
||||
},
|
||||
"--config" | "-c" => match prev {
|
||||
None => prev = Some(Config),
|
||||
|
@ -192,6 +206,9 @@ fn run_app() -> Result<()> {
|
|||
Some(CreateConfig) => error_and_exit!("invalid value for flag `--create-config`"),
|
||||
Some(Config) => error_and_exit!("invalid value for flag `--config`"),
|
||||
Some(TestConfig) => error_and_exit!("invalid value for flag `--test-config`"),
|
||||
Some(PrintManPage) => {
|
||||
error_and_exit!("invalid value for flag `--print-documentation`")
|
||||
}
|
||||
},
|
||||
"--help" | "-h" => {
|
||||
args.help = true;
|
||||
|
@ -208,6 +225,13 @@ fn run_app() -> Result<()> {
|
|||
print!("{}", conf::Theme::default().key_to_string("dark", false));
|
||||
return Ok(());
|
||||
}
|
||||
"--print-documentation" => {
|
||||
if args.print_manpage.is_some() {
|
||||
error_and_exit!("Multiple invocations of --print-documentation");
|
||||
}
|
||||
prev = Some(PrintManPage);
|
||||
args.print_manpage = Some(ManPages::Main);
|
||||
}
|
||||
e => match prev {
|
||||
None => error_and_exit!("error: value without command {}", e),
|
||||
Some(CreateConfig) if args.create_config.is_none() => {
|
||||
|
@ -222,6 +246,18 @@ fn run_app() -> Result<()> {
|
|||
args.test_config = Some(i);
|
||||
prev = None;
|
||||
}
|
||||
Some(PrintManPage) => {
|
||||
match e {
|
||||
"meli" | "main" => { /* This is the default */ }
|
||||
"meli.conf" | "conf" | "config" | "configuration" => {
|
||||
args.print_manpage = Some(ManPages::Conf);
|
||||
}
|
||||
"meli-themes" | "themes" | "theming" | "theme" => {
|
||||
args.print_manpage = Some(ManPages::Themes);
|
||||
}
|
||||
_ => error_and_exit!("Invalid documentation page: {}", e),
|
||||
}
|
||||
}
|
||||
Some(TestConfig) => error_and_exit!("Duplicate value for flag `--test-config`"),
|
||||
Some(CreateConfig) => error_and_exit!("Duplicate value for flag `--create-config`"),
|
||||
Some(Config) => error_and_exit!("Duplicate value for flag `--config`"),
|
||||
|
@ -230,10 +266,11 @@ fn run_app() -> Result<()> {
|
|||
}
|
||||
|
||||
if args.help {
|
||||
println!("usage:\tmeli [--create-config[ PATH]] [--config[ PATH]|-c[ PATH]]");
|
||||
println!("usage:\tmeli [--config PATH|-c PATH]");
|
||||
println!("\tmeli --help");
|
||||
println!("\tmeli --version");
|
||||
println!("");
|
||||
println!("Other options:");
|
||||
println!("\t--help, -h\t\tshow this message and exit");
|
||||
println!("\t--version, -v\t\tprint version and exit");
|
||||
println!("\t--create-config[ PATH]\tcreate a sample configuration file with available configuration options. If PATH is not specified, meli will try to create it in $XDG_CONFIG_HOME/meli/config.toml");
|
||||
|
@ -243,6 +280,10 @@ fn run_app() -> Result<()> {
|
|||
println!("\t--config PATH, -c PATH\tuse specified configuration file");
|
||||
println!("\t--print-loaded-themes\tprint loaded themes in full to stdout and exit.");
|
||||
println!("\t--print-default-theme\tprint default theme in full to stdout and exit.");
|
||||
#[cfg(feature = "cli-docs")]
|
||||
{
|
||||
println!("\t--print-documentation [meli conf themes]\n\t\t\t\tprint documentation page and exit (Piping to a pager is recommended.).");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -257,14 +298,25 @@ fn run_app() -> Result<()> {
|
|||
Some(CreateConfig) => error_and_exit!("Duplicate value for flag `--create-config`"),
|
||||
Some(Config) => error_and_exit!("error: flag without value: `--config`"),
|
||||
Some(TestConfig) => error_and_exit!("error: flag without value: `--test-config`"),
|
||||
Some(PrintManPage) => {}
|
||||
};
|
||||
|
||||
if (args.print_manpage.is_some()
|
||||
^ args.test_config.is_some()
|
||||
^ args.create_config.is_some()
|
||||
^ args.config.is_some())
|
||||
&& !(args.print_manpage.is_some()
|
||||
|| args.test_config.is_some()
|
||||
|| args.create_config.is_some()
|
||||
|| args.config.is_some())
|
||||
{
|
||||
error_and_exit!("error: illegal command-line flag combination");
|
||||
}
|
||||
|
||||
if let Some(config_path) = args.test_config.as_ref() {
|
||||
conf::FileSettings::validate(config_path)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(config_path) = args.create_config.as_mut() {
|
||||
} else if let Some(config_path) = args.create_config.as_mut() {
|
||||
let config_path: PathBuf = if config_path.is_empty() {
|
||||
let xdg_dirs = xdg::BaseDirectories::with_prefix("meli").unwrap();
|
||||
xdg_dirs.place_config_file("config.toml").map_err(|e| {
|
||||
|
@ -282,6 +334,21 @@ fn run_app() -> Result<()> {
|
|||
}
|
||||
conf::create_config_file(&config_path)?;
|
||||
return Ok(());
|
||||
} else if let Some(_page) = args.print_manpage {
|
||||
#[cfg(feature = "cli-docs")]
|
||||
{
|
||||
const MANPAGES: [&'static str; 3] = [
|
||||
include_str!(concat!(env!("OUT_DIR"), "/meli.txt")),
|
||||
include_str!(concat!(env!("OUT_DIR"), "/meli.conf.txt")),
|
||||
include_str!(concat!(env!("OUT_DIR"), "/meli-themes.txt")),
|
||||
];
|
||||
println!("{}", MANPAGES[_page as usize]);
|
||||
return Ok(());
|
||||
}
|
||||
#[cfg(not(feature = "cli-docs"))]
|
||||
{
|
||||
error_and_exit!("error: this version of meli was not build with embedded documentation. You might have it installed as manpages (eg `man meli`), otherwise check https://meli.delivery");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(config_location) = args.config.as_ref() {
|
||||
|
@ -305,9 +372,12 @@ fn run_app() -> Result<()> {
|
|||
let mut state = State::new(sender, receiver.clone())?;
|
||||
|
||||
let window = Box::new(Tabbed::new(vec![
|
||||
Box::new(listing::Listing::new(&state.context.accounts)),
|
||||
Box::new(listing::Listing::new(&mut state.context)),
|
||||
Box::new(ContactList::new(&state.context)),
|
||||
Box::new(StatusPanel::new()),
|
||||
Box::new(StatusPanel::new(crate::conf::value(
|
||||
&state.context,
|
||||
"theme_default",
|
||||
))),
|
||||
]));
|
||||
|
||||
let status_bar = Box::new(StatusBar::new(window));
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
use super::*;
|
||||
use crate::melib::text_processing::{TextProcessing, Truncate};
|
||||
use crate::terminal::boundaries::*;
|
||||
|
||||
pub mod mail;
|
||||
pub use crate::mail::*;
|
||||
|
@ -46,32 +47,6 @@ use fnv::FnvHashMap;
|
|||
use uuid::Uuid;
|
||||
|
||||
use super::{Key, StatusEvent, UIEvent};
|
||||
/// The upper and lower boundary char.
|
||||
const HORZ_BOUNDARY: char = '─';
|
||||
/// The left and right boundary char.
|
||||
const VERT_BOUNDARY: char = '│';
|
||||
|
||||
/// The top-left corner
|
||||
const _TOP_LEFT_CORNER: char = '┌';
|
||||
/// The top-right corner
|
||||
const _TOP_RIGHT_CORNER: char = '┐';
|
||||
/// The bottom-left corner
|
||||
const _BOTTOM_LEFT_CORNER: char = '└';
|
||||
/// The bottom-right corner
|
||||
const _BOTTOM_RIGHT_CORNER: char = '┘';
|
||||
|
||||
const LIGHT_VERTICAL_AND_RIGHT: char = '├';
|
||||
|
||||
const _LIGHT_VERTICAL_AND_LEFT: char = '┤';
|
||||
|
||||
const LIGHT_DOWN_AND_HORIZONTAL: char = '┬';
|
||||
|
||||
const LIGHT_UP_AND_HORIZONTAL: char = '┴';
|
||||
|
||||
const _DOUBLE_DOWN_AND_RIGHT: char = '╔';
|
||||
const _DOUBLE_DOWN_AND_LEFT: char = '╗';
|
||||
const _DOUBLE_UP_AND_LEFT: char = '╝';
|
||||
const _DOUBLE_UP_AND_RIGHT: char = '╚';
|
||||
|
||||
type ComponentId = Uuid;
|
||||
|
||||
|
@ -100,316 +75,7 @@ pub trait Component: Display + Debug + Send {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
fn get_status(&self, _context: &Context) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn bin_to_ch(b: u32) -> char {
|
||||
match b {
|
||||
0b0001 => '╶',
|
||||
0b0010 => '╵',
|
||||
0b0011 => '└',
|
||||
0b0100 => '╴',
|
||||
0b0101 => '─',
|
||||
0b0110 => '┘',
|
||||
0b0111 => '┴',
|
||||
0b1000 => '╷',
|
||||
0b1001 => '┌',
|
||||
0b1010 => '│',
|
||||
0b1011 => '├',
|
||||
0b1100 => '┐',
|
||||
0b1101 => '┬',
|
||||
0b1110 => '┤',
|
||||
0b1111 => '┼',
|
||||
_ => unsafe { std::hint::unreachable_unchecked() },
|
||||
}
|
||||
}
|
||||
|
||||
fn ch_to_bin(ch: char) -> Option<u32> {
|
||||
match ch {
|
||||
'└' => Some(0b0011),
|
||||
'─' => Some(0b0101),
|
||||
'┘' => Some(0b0110),
|
||||
'┴' => Some(0b0111),
|
||||
'┌' => Some(0b1001),
|
||||
|
||||
'│' => Some(0b1010),
|
||||
|
||||
'├' => Some(0b1011),
|
||||
'┐' => Some(0b1100),
|
||||
'┬' => Some(0b1101),
|
||||
|
||||
'┤' => Some(0b1110),
|
||||
|
||||
'┼' => Some(0b1111),
|
||||
'╷' => Some(0b1000),
|
||||
|
||||
'╵' => Some(0b0010),
|
||||
'╴' => Some(0b0100),
|
||||
'╶' => Some(0b0001),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::never_loop)]
|
||||
fn set_and_join_vert(grid: &mut CellBuffer, idx: Pos) -> u32 {
|
||||
let (x, y) = idx;
|
||||
let mut bin_set = 0b1010;
|
||||
/* Check left side
|
||||
*
|
||||
* 1
|
||||
* -> 2 │ 0
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if x > 0 {
|
||||
if let Some(cell) = grid.get_mut(x - 1, y) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
if (adj & 0b0001) > 0 {
|
||||
bin_set |= 0b0100;
|
||||
break;
|
||||
} else if adj == 0b0100 {
|
||||
cell.set_ch(bin_to_ch(0b0101));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
bin_set |= 0b0100;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bin_set &= 0b1011;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check right side
|
||||
*
|
||||
* 1
|
||||
* 2 │ 0 <-
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if let Some(cell) = grid.get_mut(x + 1, y) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
if (adj & 0b0100) > 0 {
|
||||
bin_set |= 0b0001;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
bin_set &= 0b1110;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set upper side
|
||||
*
|
||||
* 1 <-
|
||||
* 2 │ 0
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if y > 0 {
|
||||
if let Some(cell) = grid.get_mut(x, y - 1) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
cell.set_ch(bin_to_ch(adj | 0b1000));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
} else {
|
||||
bin_set &= 0b1101;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set bottom side
|
||||
*
|
||||
* 1
|
||||
* 2 │ 0
|
||||
* 3 <-
|
||||
*/
|
||||
loop {
|
||||
if let Some(cell) = grid.get_mut(x, y + 1) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
cell.set_ch(bin_to_ch(adj | 0b0010));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
} else {
|
||||
bin_set &= 0b0111;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if bin_set == 0 {
|
||||
bin_set = 0b1010;
|
||||
}
|
||||
|
||||
bin_set
|
||||
}
|
||||
|
||||
#[allow(clippy::never_loop)]
|
||||
fn set_and_join_horz(grid: &mut CellBuffer, idx: Pos) -> u32 {
|
||||
let (x, y) = idx;
|
||||
let mut bin_set = 0b0101;
|
||||
/* Check upper side
|
||||
*
|
||||
* 1 <-
|
||||
* 2 ─ 0
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if y > 0 {
|
||||
if let Some(cell) = grid.get_mut(x, y - 1) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
if (adj & 0b1000) > 0 {
|
||||
bin_set |= 0b0010;
|
||||
break;
|
||||
} else if adj == 0b0010 {
|
||||
bin_set |= 0b0010;
|
||||
cell.set_ch(bin_to_ch(0b1010));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bin_set &= 0b1101;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check bottom side
|
||||
*
|
||||
* 1
|
||||
* 2 ─ 0
|
||||
* 3 <-
|
||||
*/
|
||||
loop {
|
||||
if let Some(cell) = grid.get_mut(x, y + 1) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
if (adj & 0b0010) > 0 {
|
||||
bin_set |= 0b1000;
|
||||
break;
|
||||
} else if adj == 0b1000 {
|
||||
bin_set |= 0b1000;
|
||||
cell.set_ch(bin_to_ch(0b1010));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
bin_set &= 0b0111;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set left side
|
||||
*
|
||||
* 1
|
||||
* -> 2 ─ 0
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if x > 0 {
|
||||
if let Some(cell) = grid.get_mut(x - 1, y) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
cell.set_ch(bin_to_ch(adj | 0b0001));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
} else {
|
||||
bin_set &= 0b1011;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set right side
|
||||
*
|
||||
* 1
|
||||
* 2 ─ 0 <-
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if let Some(cell) = grid.get_mut(x + 1, y) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
cell.set_ch(bin_to_ch(adj | 0b0100));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
} else {
|
||||
bin_set &= 0b1110;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if bin_set == 0 {
|
||||
bin_set = 0b0101;
|
||||
}
|
||||
|
||||
bin_set
|
||||
}
|
||||
|
||||
pub enum BoxBoundary {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
pub(crate) fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: BoxBoundary) {
|
||||
/* Connected sides:
|
||||
*
|
||||
* 1
|
||||
* 2 c 0
|
||||
* 3
|
||||
*
|
||||
* #3210
|
||||
* 0b____
|
||||
*/
|
||||
|
||||
if grid.ascii_drawing {
|
||||
grid[idx].set_ch(match ch {
|
||||
BoxBoundary::Vertical => '|',
|
||||
BoxBoundary::Horizontal => '-',
|
||||
});
|
||||
|
||||
grid[idx].set_fg(Color::Byte(240));
|
||||
return;
|
||||
}
|
||||
|
||||
let bin_set = match ch {
|
||||
BoxBoundary::Vertical => set_and_join_vert(grid, idx),
|
||||
BoxBoundary::Horizontal => set_and_join_horz(grid, idx),
|
||||
};
|
||||
|
||||
grid[idx].set_ch(bin_to_ch(bin_set));
|
||||
grid[idx].set_fg(Color::Byte(240));
|
||||
}
|
||||
|
||||
pub fn create_box(grid: &mut CellBuffer, area: Area) {
|
||||
if !is_valid_area!(area) {
|
||||
return;
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
||||
if !grid.ascii_drawing {
|
||||
for x in get_x(upper_left)..get_x(bottom_right) {
|
||||
grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY);
|
||||
grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY);
|
||||
grid[(x, get_y(bottom_right))].set_fg(Color::Byte(240));
|
||||
}
|
||||
|
||||
for y in get_y(upper_left)..get_y(bottom_right) {
|
||||
grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY);
|
||||
grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY);
|
||||
grid[(get_x(bottom_right), y)].set_fg(Color::Byte(240));
|
||||
}
|
||||
set_and_join_box(grid, upper_left, BoxBoundary::Horizontal);
|
||||
set_and_join_box(
|
||||
grid,
|
||||
set_x(upper_left, get_x(bottom_right)),
|
||||
BoxBoundary::Horizontal,
|
||||
);
|
||||
set_and_join_box(
|
||||
grid,
|
||||
set_y(upper_left, get_y(bottom_right)),
|
||||
BoxBoundary::Vertical,
|
||||
);
|
||||
set_and_join_box(grid, bottom_right, BoxBoundary::Vertical);
|
||||
fn get_status(&self, _context: &Context) -> String {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,29 +43,13 @@ pub struct ContactManager {
|
|||
form: FormWidget,
|
||||
account_pos: usize,
|
||||
content: CellBuffer,
|
||||
theme_default: ThemeAttribute,
|
||||
dirty: bool,
|
||||
has_changes: bool,
|
||||
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
impl Default for ContactManager {
|
||||
fn default() -> Self {
|
||||
ContactManager {
|
||||
id: Uuid::nil(),
|
||||
parent_id: Uuid::nil(),
|
||||
card: Card::new(),
|
||||
mode: ViewMode::Edit,
|
||||
form: FormWidget::default(),
|
||||
account_pos: 0,
|
||||
content: CellBuffer::new(100, 1, Cell::with_char(' ')),
|
||||
dirty: true,
|
||||
has_changes: false,
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ContactManager {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "contacts")
|
||||
|
@ -73,6 +57,30 @@ impl fmt::Display for ContactManager {
|
|||
}
|
||||
|
||||
impl ContactManager {
|
||||
fn new(context: &Context) -> Self {
|
||||
let theme_default: ThemeAttribute = crate::conf::value(context, "theme_default");
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(theme_default.fg)
|
||||
.set_bg(theme_default.bg)
|
||||
.set_attrs(theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
ContactManager {
|
||||
id: Uuid::nil(),
|
||||
parent_id: Uuid::nil(),
|
||||
card: Card::new(),
|
||||
mode: ViewMode::Edit,
|
||||
form: FormWidget::default(),
|
||||
account_pos: 0,
|
||||
content: CellBuffer::new(100, 1, default_cell),
|
||||
theme_default,
|
||||
dirty: true,
|
||||
has_changes: false,
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(&mut self) {
|
||||
let (width, _) = self.content.size();
|
||||
|
||||
|
@ -80,8 +88,8 @@ impl ContactManager {
|
|||
"Last edited: ",
|
||||
&mut self.content,
|
||||
Color::Byte(250),
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((0, 0), (width - 1, 0)),
|
||||
None,
|
||||
);
|
||||
|
@ -89,8 +97,8 @@ impl ContactManager {
|
|||
&self.card.last_edited(),
|
||||
&mut self.content,
|
||||
Color::Byte(250),
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((x, 0), (width - 1, 0)),
|
||||
None,
|
||||
);
|
||||
|
@ -103,8 +111,8 @@ impl ContactManager {
|
|||
"This contact's origin is external and cannot be edited within meli.",
|
||||
&mut self.content,
|
||||
Color::Byte(250),
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((x, y), (width - 1, y)),
|
||||
None,
|
||||
);
|
||||
|
@ -151,6 +159,7 @@ impl Component for ContactManager {
|
|||
clear_area(
|
||||
grid,
|
||||
(upper_left, set_y(bottom_right, get_y(upper_left) + 1)),
|
||||
self.theme_default,
|
||||
);
|
||||
copy_area_with_break(grid, &self.content, area, ((0, 0), (width - 1, 0)));
|
||||
self.dirty = false;
|
||||
|
|
|
@ -48,6 +48,7 @@ pub struct ContactList {
|
|||
length: usize,
|
||||
data_columns: DataColumns,
|
||||
initialized: bool,
|
||||
theme_default: ThemeAttribute,
|
||||
|
||||
id_positions: Vec<CardId>,
|
||||
|
||||
|
@ -89,6 +90,7 @@ impl ContactList {
|
|||
id_positions: Vec::new(),
|
||||
mode: ViewMode::List,
|
||||
data_columns: DataColumns::default(),
|
||||
theme_default: crate::conf::value(context, "theme_default"),
|
||||
initialized: false,
|
||||
dirty: true,
|
||||
movement: None,
|
||||
|
@ -129,22 +131,25 @@ impl ContactList {
|
|||
min_width.2 = cmp::max(min_width.2, c.url().split_graphemes().len());
|
||||
}
|
||||
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.theme_default.fg)
|
||||
.set_bg(self.theme_default.bg)
|
||||
.set_attrs(self.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
/* name column */
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(min_width.0, self.length, Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.0, self.length, default_cell, context);
|
||||
/* email column */
|
||||
self.data_columns.columns[1] =
|
||||
CellBuffer::new_with_context(min_width.1, self.length, Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.1, self.length, default_cell, context);
|
||||
/* url column */
|
||||
self.data_columns.columns[2] =
|
||||
CellBuffer::new_with_context(min_width.2, self.length, Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.2, self.length, default_cell, context);
|
||||
/* source column */
|
||||
self.data_columns.columns[3] = CellBuffer::new_with_context(
|
||||
"external".len(),
|
||||
self.length,
|
||||
Cell::with_char(' '),
|
||||
context,
|
||||
);
|
||||
self.data_columns.columns[3] =
|
||||
CellBuffer::new_with_context("external".len(), self.length, default_cell, context);
|
||||
|
||||
let account = &context.accounts[self.account_pos];
|
||||
let book = &account.address_book;
|
||||
|
@ -156,9 +161,9 @@ impl ContactList {
|
|||
write_string_to_grid(
|
||||
c.name(),
|
||||
&mut self.data_columns.columns[0],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((0, idx), (min_width.0, idx)),
|
||||
None,
|
||||
);
|
||||
|
@ -166,9 +171,9 @@ impl ContactList {
|
|||
write_string_to_grid(
|
||||
c.email(),
|
||||
&mut self.data_columns.columns[1],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((0, idx), (min_width.1, idx)),
|
||||
None,
|
||||
);
|
||||
|
@ -176,9 +181,9 @@ impl ContactList {
|
|||
write_string_to_grid(
|
||||
c.url(),
|
||||
&mut self.data_columns.columns[2],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((0, idx), (min_width.2, idx)),
|
||||
None,
|
||||
);
|
||||
|
@ -190,28 +195,31 @@ impl ContactList {
|
|||
"local"
|
||||
},
|
||||
&mut self.data_columns.columns[3],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((0, idx), (min_width.3, idx)),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
if self.length == 0 {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.theme_default.fg)
|
||||
.set_bg(self.theme_default.bg)
|
||||
.set_attrs(self.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
let message = "Address book is empty.".to_string();
|
||||
self.data_columns.columns[0] = CellBuffer::new_with_context(
|
||||
message.len(),
|
||||
self.length,
|
||||
Cell::with_char(' '),
|
||||
context,
|
||||
);
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(message.len(), self.length, default_cell, context);
|
||||
write_string_to_grid(
|
||||
&message,
|
||||
&mut self.data_columns.columns[0],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
None,
|
||||
);
|
||||
|
@ -221,11 +229,11 @@ impl ContactList {
|
|||
|
||||
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize) {
|
||||
/* Reset previously highlighted line */
|
||||
let fg_color = Color::Default;
|
||||
let fg_color = self.theme_default.fg;
|
||||
let bg_color = if idx == self.new_cursor_pos {
|
||||
Color::Byte(246)
|
||||
} else {
|
||||
Color::Default
|
||||
self.theme_default.bg
|
||||
};
|
||||
change_colors(grid, area, fg_color, bg_color);
|
||||
}
|
||||
|
@ -234,7 +242,7 @@ impl ContactList {
|
|||
if !self.is_dirty() {
|
||||
return;
|
||||
}
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.theme_default);
|
||||
/* visually divide menu and listing */
|
||||
area = (area.0, pos_dec(area.1, (1, 0)));
|
||||
let upper_left = upper_left!(area);
|
||||
|
@ -271,7 +279,7 @@ impl ContactList {
|
|||
(Color::Byte(15), Color::Byte(233))
|
||||
}
|
||||
} else {
|
||||
(Color::Default, Color::Default)
|
||||
(self.theme_default.fg, self.theme_default.bg)
|
||||
};
|
||||
|
||||
let s = format!(" [{}]", context.accounts[a.index].address_book.len());
|
||||
|
@ -348,7 +356,7 @@ impl ContactList {
|
|||
let bottom_right = bottom_right!(area);
|
||||
|
||||
if self.length == 0 {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.theme_default);
|
||||
copy_area(
|
||||
grid,
|
||||
&self.data_columns.columns[0],
|
||||
|
@ -441,8 +449,10 @@ impl ContactList {
|
|||
width.saturating_sub(self.data_columns.widths[0] + self.data_columns.widths[1] + 4);
|
||||
self.data_columns.widths[2] = remainder / 6;
|
||||
}
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.theme_default);
|
||||
/* Page_no has changed, so draw new page */
|
||||
|
||||
let header_attrs = crate::conf::value(context, "widgets.list.header");
|
||||
let mut x = get_x(upper_left);
|
||||
for i in 0..self.data_columns.columns.len() {
|
||||
if self.data_columns.widths[i] == 0 {
|
||||
|
@ -458,9 +468,9 @@ impl ContactList {
|
|||
_ => "",
|
||||
},
|
||||
grid,
|
||||
Color::Black,
|
||||
Color::White,
|
||||
Attr::Bold,
|
||||
header_attrs.fg,
|
||||
header_attrs.bg,
|
||||
header_attrs.attrs,
|
||||
(
|
||||
set_x(upper_left!(area), x),
|
||||
(
|
||||
|
@ -500,8 +510,8 @@ impl ContactList {
|
|||
upper_left!(area),
|
||||
set_y(bottom_right, get_y(upper_left!(area))),
|
||||
),
|
||||
Color::Black,
|
||||
Color::White,
|
||||
header_attrs.fg,
|
||||
header_attrs.bg,
|
||||
);
|
||||
|
||||
if top_idx + rows > self.length {
|
||||
|
@ -511,6 +521,7 @@ impl ContactList {
|
|||
pos_inc(upper_left, (0, self.length - top_idx + 2)),
|
||||
bottom_right,
|
||||
),
|
||||
self.theme_default,
|
||||
);
|
||||
}
|
||||
self.highlight_line(
|
||||
|
@ -552,14 +563,16 @@ impl Component for ContactList {
|
|||
if self.dirty && mid != get_x(upper_left) {
|
||||
if self.show_divider {
|
||||
for i in get_y(upper_left)..=get_y(bottom_right) {
|
||||
grid[(mid, i)].set_ch(VERT_BOUNDARY);
|
||||
grid[(mid, i)].set_fg(Color::Default);
|
||||
grid[(mid, i)].set_bg(Color::Default);
|
||||
grid[(mid, i)]
|
||||
.set_ch(VERT_BOUNDARY)
|
||||
.set_fg(self.theme_default.fg)
|
||||
.set_bg(self.theme_default.bg);
|
||||
}
|
||||
} else {
|
||||
for i in get_y(upper_left)..=get_y(bottom_right) {
|
||||
grid[(mid, i)].set_fg(Color::Default);
|
||||
grid[(mid, i)].set_bg(Color::Default);
|
||||
grid[(mid, i)]
|
||||
.set_fg(self.theme_default.fg)
|
||||
.set_bg(self.theme_default.bg);
|
||||
}
|
||||
}
|
||||
context
|
||||
|
@ -590,7 +603,7 @@ impl Component for ContactList {
|
|||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[Self::DESCRIPTION]["create_contact"]) =>
|
||||
{
|
||||
let mut manager = ContactManager::default();
|
||||
let mut manager = ContactManager::new(context);
|
||||
manager.set_parent_id(self.id);
|
||||
manager.account_pos = self.account_pos;
|
||||
|
||||
|
@ -607,7 +620,7 @@ impl Component for ContactList {
|
|||
let account = &mut context.accounts[self.account_pos];
|
||||
let book = &mut account.address_book;
|
||||
let card = book[&self.id_positions[self.cursor_pos]].clone();
|
||||
let mut manager = ContactManager::default();
|
||||
let mut manager = ContactManager::new(context);
|
||||
manager.set_parent_id(self.id);
|
||||
manager.card = card;
|
||||
manager.account_pos = self.account_pos;
|
||||
|
@ -665,7 +678,7 @@ impl Component for ContactList {
|
|||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
|
||||
self.get_status(context).unwrap(),
|
||||
self.get_status(context),
|
||||
)));
|
||||
}
|
||||
|
||||
|
@ -702,7 +715,7 @@ impl Component for ContactList {
|
|||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
|
||||
self.get_status(context).unwrap(),
|
||||
self.get_status(context),
|
||||
)));
|
||||
}
|
||||
return true;
|
||||
|
@ -889,10 +902,10 @@ impl Component for ContactList {
|
|||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
fn get_status(&self, context: &Context) -> Option<String> {
|
||||
Some(format!(
|
||||
fn get_status(&self, context: &Context) -> String {
|
||||
format!(
|
||||
"{} entries",
|
||||
context.accounts[self.account_pos].address_book.len()
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,12 +141,15 @@ impl fmt::Display for Composer {
|
|||
|
||||
impl Composer {
|
||||
const DESCRIPTION: &'static str = "composing";
|
||||
pub fn new(account_cursor: usize) -> Self {
|
||||
Composer {
|
||||
pub fn new(account_cursor: usize, context: &Context) -> Self {
|
||||
let mut ret = Composer {
|
||||
account_cursor,
|
||||
id: ComponentId::new_v4(),
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
ret.pager
|
||||
.set_colors(crate::conf::value(context, "theme_default"));
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn edit(account_pos: usize, h: EnvelopeHash, context: &Context) -> Result<Self> {
|
||||
|
@ -167,6 +170,8 @@ impl Composer {
|
|||
) -> Self {
|
||||
let account = &context.accounts[coordinates.0];
|
||||
let mut ret = Composer::default();
|
||||
ret.pager
|
||||
.set_colors(crate::conf::value(context, "theme_default"));
|
||||
let parent_message = account.collection.get_env(msg);
|
||||
/* If message is from a mailing list and we detect a List-Post header, ask user if they
|
||||
* want to reply to the mailing list or the submitter of the message */
|
||||
|
@ -258,7 +263,8 @@ impl Composer {
|
|||
|
||||
fn draw_attachments(&self, grid: &mut CellBuffer, area: Area, context: &Context) {
|
||||
let attachments_no = self.draft.attachments().len();
|
||||
clear_area(grid, area);
|
||||
let theme_default = crate::conf::value(context, "theme_default");
|
||||
clear_area(grid, area, theme_default);
|
||||
if self.sign_mail.is_true() {
|
||||
write_string_to_grid(
|
||||
&format!(
|
||||
|
@ -272,9 +278,9 @@ impl Composer {
|
|||
.unwrap_or("default key")
|
||||
),
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
theme_default.attrs,
|
||||
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
|
||||
None,
|
||||
);
|
||||
|
@ -282,9 +288,9 @@ impl Composer {
|
|||
write_string_to_grid(
|
||||
"☐ don't sign",
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
theme_default.attrs,
|
||||
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
|
||||
None,
|
||||
);
|
||||
|
@ -293,9 +299,9 @@ impl Composer {
|
|||
write_string_to_grid(
|
||||
"no attachments",
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
theme_default.attrs,
|
||||
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
|
||||
None,
|
||||
);
|
||||
|
@ -303,9 +309,9 @@ impl Composer {
|
|||
write_string_to_grid(
|
||||
&format!("{} attachments ", attachments_no),
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
theme_default.attrs,
|
||||
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
|
||||
None,
|
||||
);
|
||||
|
@ -320,9 +326,9 @@ impl Composer {
|
|||
a.raw.len()
|
||||
),
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
theme_default.attrs,
|
||||
(pos_inc(upper_left!(area), (0, 3 + i)), bottom_right!(area)),
|
||||
None,
|
||||
);
|
||||
|
@ -330,9 +336,9 @@ impl Composer {
|
|||
write_string_to_grid(
|
||||
&format!("[{}] {} {} bytes", i, a.content_type(), a.raw.len()),
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
theme_default.attrs,
|
||||
(pos_inc(upper_left!(area), (0, 3 + i)), bottom_right!(area)),
|
||||
None,
|
||||
);
|
||||
|
@ -371,6 +377,7 @@ impl Component for Composer {
|
|||
self.initialized = true;
|
||||
}
|
||||
let header_height = self.form.len();
|
||||
let theme_default = crate::conf::value(context, "theme_default");
|
||||
|
||||
let mid = if width > 80 {
|
||||
let width = width - 80;
|
||||
|
@ -379,11 +386,13 @@ impl Component for Composer {
|
|||
if self.dirty {
|
||||
for i in get_y(upper_left)..=get_y(bottom_right) {
|
||||
//set_and_join_box(grid, (mid, i), VERT_BOUNDARY);
|
||||
grid[(mid, i)].set_fg(Color::Default);
|
||||
grid[(mid, i)].set_bg(Color::Default);
|
||||
grid[(mid, i)]
|
||||
.set_fg(theme_default.fg)
|
||||
.set_bg(theme_default.bg);
|
||||
//set_and_join_box(grid, (mid + 80, i), VERT_BOUNDARY);
|
||||
grid[(mid + 80, i)].set_fg(Color::Default);
|
||||
grid[(mid + 80, i)].set_bg(Color::Default);
|
||||
grid[(mid + 80, i)]
|
||||
.set_fg(theme_default.fg)
|
||||
.set_bg(theme_default.bg);
|
||||
}
|
||||
}
|
||||
mid
|
||||
|
@ -425,7 +434,7 @@ impl Component for Composer {
|
|||
),
|
||||
None,
|
||||
);
|
||||
clear_area(grid, ((x, y), (set_y(bottom_right, y))));
|
||||
clear_area(grid, ((x, y), (set_y(bottom_right, y))), theme_default);
|
||||
change_colors(
|
||||
grid,
|
||||
(
|
||||
|
@ -442,6 +451,7 @@ impl Component for Composer {
|
|||
pos_dec(upper_left, (0, 1)),
|
||||
set_x(bottom_right, get_x(upper_left) + mid),
|
||||
),
|
||||
theme_default,
|
||||
);
|
||||
clear_area(
|
||||
grid,
|
||||
|
@ -452,6 +462,7 @@ impl Component for Composer {
|
|||
),
|
||||
bottom_right,
|
||||
),
|
||||
theme_default,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -462,7 +473,7 @@ impl Component for Composer {
|
|||
match embed_pty {
|
||||
EmbedStatus::Running(_, _) => {
|
||||
let mut guard = embed_pty.lock().unwrap();
|
||||
clear_area(grid, embed_area);
|
||||
clear_area(grid, embed_area, theme_default);
|
||||
copy_area(
|
||||
grid,
|
||||
&guard.grid,
|
||||
|
@ -475,13 +486,13 @@ impl Component for Composer {
|
|||
return;
|
||||
}
|
||||
EmbedStatus::Stopped(_, _) => {
|
||||
clear_area(grid, body_area);
|
||||
clear_area(grid, body_area, theme_default);
|
||||
write_string_to_grid(
|
||||
"process has stopped, press 'e' to re-activate",
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
theme_default.attrs,
|
||||
body_area,
|
||||
None,
|
||||
);
|
||||
|
@ -501,7 +512,7 @@ impl Component for Composer {
|
|||
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))),
|
||||
bottom_right!(body_area),
|
||||
),
|
||||
Color::Default,
|
||||
theme_default.fg,
|
||||
Color::Byte(237),
|
||||
);
|
||||
} else {
|
||||
|
@ -511,8 +522,8 @@ impl Component for Composer {
|
|||
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))),
|
||||
bottom_right!(body_area),
|
||||
),
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
theme_default.fg,
|
||||
theme_default.bg,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,9 @@ pub use self::thread::*;
|
|||
mod plain;
|
||||
pub use self::plain::*;
|
||||
|
||||
mod offline;
|
||||
pub use self::offline::*;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DataColumns {
|
||||
pub columns: [CellBuffer; 12],
|
||||
|
@ -122,6 +125,7 @@ struct AccountMenuEntry {
|
|||
name: String,
|
||||
// Index in the config account vector.
|
||||
index: usize,
|
||||
entries: SmallVec<[(usize, FolderHash); 16]>,
|
||||
}
|
||||
|
||||
pub trait MailListingTrait: ListingTrait {
|
||||
|
@ -200,11 +204,14 @@ pub trait MailListingTrait: ListingTrait {
|
|||
fn redraw_list(&mut self, _context: &Context, _items: Box<dyn Iterator<Item = ThreadHash>>) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Use `force` when there have been changes in the mailbox or account lists in `context`
|
||||
fn refresh_mailbox(&mut self, context: &mut Context, force: bool);
|
||||
}
|
||||
|
||||
pub trait ListingTrait: Component {
|
||||
fn coordinates(&self) -> (usize, usize);
|
||||
fn set_coordinates(&mut self, _: (usize, usize));
|
||||
fn coordinates(&self) -> (usize, FolderHash);
|
||||
fn set_coordinates(&mut self, _: (usize, FolderHash));
|
||||
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
|
||||
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context);
|
||||
fn filter(&mut self, _filter_term: &str, _context: &Context) {}
|
||||
|
@ -217,6 +224,7 @@ pub enum ListingComponent {
|
|||
Threaded(ThreadListing),
|
||||
Compact(CompactListing),
|
||||
Conversations(ConversationsListing),
|
||||
Offline(OfflineListing),
|
||||
}
|
||||
use crate::ListingComponent::*;
|
||||
|
||||
|
@ -229,6 +237,7 @@ impl core::ops::Deref for ListingComponent {
|
|||
Plain(ref l) => l,
|
||||
Threaded(ref l) => l,
|
||||
Conversations(ref l) => l,
|
||||
Offline(ref l) => l,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -240,6 +249,7 @@ impl core::ops::DerefMut for ListingComponent {
|
|||
Plain(l) => l,
|
||||
Threaded(l) => l,
|
||||
Conversations(l) => l,
|
||||
Offline(l) => l,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -251,37 +261,25 @@ impl ListingComponent {
|
|||
if let Plain(_) = self {
|
||||
return;
|
||||
}
|
||||
let mut new_l = PlainListing::default();
|
||||
let coors = self.coordinates();
|
||||
new_l.set_coordinates((coors.0, coors.1));
|
||||
*self = Plain(new_l);
|
||||
*self = Plain(PlainListing::new(self.coordinates()));
|
||||
}
|
||||
IndexStyle::Threaded => {
|
||||
if let Threaded(_) = self {
|
||||
return;
|
||||
}
|
||||
let mut new_l = ThreadListing::default();
|
||||
let coors = self.coordinates();
|
||||
new_l.set_coordinates((coors.0, coors.1));
|
||||
*self = Threaded(new_l);
|
||||
*self = Threaded(ThreadListing::new(self.coordinates()));
|
||||
}
|
||||
IndexStyle::Compact => {
|
||||
if let Compact(_) = self {
|
||||
return;
|
||||
}
|
||||
let mut new_l = CompactListing::default();
|
||||
let coors = self.coordinates();
|
||||
new_l.set_coordinates((coors.0, coors.1));
|
||||
*self = Compact(new_l);
|
||||
*self = Compact(CompactListing::new(self.coordinates()));
|
||||
}
|
||||
IndexStyle::Conversations => {
|
||||
if let Conversations(_) = self {
|
||||
return;
|
||||
}
|
||||
let mut new_l = ConversationsListing::default();
|
||||
let coors = self.coordinates();
|
||||
new_l.set_coordinates((coors.0, coors.1));
|
||||
*self = Conversations(new_l);
|
||||
*self = Conversations(ConversationsListing::new(self.coordinates()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -312,6 +310,7 @@ impl fmt::Display for Listing {
|
|||
Plain(ref l) => write!(f, "{}", l),
|
||||
Threaded(ref l) => write!(f, "{}", l),
|
||||
Conversations(ref l) => write!(f, "{}", l),
|
||||
Offline(ref l) => write!(f, "{}", l),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -328,7 +327,6 @@ impl Component for Listing {
|
|||
if !is_valid_area!(area) {
|
||||
return;
|
||||
}
|
||||
self.theme_default = crate::conf::value(context, "theme_default");
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
let total_cols = get_x(bottom_right) - get_x(upper_left);
|
||||
|
@ -363,13 +361,13 @@ impl Component for Listing {
|
|||
|
||||
if right_component_width == total_cols {
|
||||
if let Err(err) = context.is_online(self.cursor_pos.0) {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.theme_default);
|
||||
let (x, _) = write_string_to_grid(
|
||||
"offline: ",
|
||||
grid,
|
||||
Color::Byte(243),
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
(set_x(upper_left, mid + 1), bottom_right),
|
||||
None,
|
||||
);
|
||||
|
@ -377,8 +375,8 @@ impl Component for Listing {
|
|||
&err.to_string(),
|
||||
grid,
|
||||
Color::Red,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
(set_x(upper_left, x + 1), bottom_right),
|
||||
None,
|
||||
);
|
||||
|
@ -392,13 +390,17 @@ impl Component for Listing {
|
|||
} else {
|
||||
self.draw_menu(grid, (upper_left, (mid, get_y(bottom_right))), context);
|
||||
if let Err(err) = context.is_online(self.cursor_pos.0) {
|
||||
clear_area(grid, (set_x(upper_left, mid + 1), bottom_right));
|
||||
clear_area(
|
||||
grid,
|
||||
(set_x(upper_left, mid + 1), bottom_right),
|
||||
self.theme_default,
|
||||
);
|
||||
let (x, _) = write_string_to_grid(
|
||||
"offline: ",
|
||||
grid,
|
||||
Color::Byte(243),
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
(set_x(upper_left, mid + 1), bottom_right),
|
||||
None,
|
||||
);
|
||||
|
@ -406,8 +408,8 @@ impl Component for Listing {
|
|||
&err.to_string(),
|
||||
grid,
|
||||
Color::Red,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
(set_x(upper_left, x + 1), bottom_right),
|
||||
None,
|
||||
);
|
||||
|
@ -420,15 +422,11 @@ impl Component for Listing {
|
|||
}
|
||||
self.dirty = false;
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||
match event {
|
||||
UIEvent::StartupCheck(ref f) => {
|
||||
if context.accounts[self.component.coordinates().0]
|
||||
.folders_order
|
||||
.get(self.component.coordinates().1)
|
||||
.map(|&folder_hash| *f == folder_hash)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if self.component.coordinates().1 == *f {
|
||||
if !self.startup_checks_rate.tick() {
|
||||
return false;
|
||||
}
|
||||
|
@ -437,15 +435,54 @@ impl Component for Listing {
|
|||
UIEvent::Timer(n) if *n == self.startup_checks_rate.id() => {
|
||||
if self.startup_checks_rate.active {
|
||||
self.startup_checks_rate.reset();
|
||||
if let Some(folder_hash) = context.accounts[self.component.coordinates().0]
|
||||
.folders_order
|
||||
.get(self.component.coordinates().1)
|
||||
{
|
||||
return self
|
||||
.process_event(&mut UIEvent::StartupCheck(*folder_hash), context);
|
||||
}
|
||||
return self.process_event(
|
||||
&mut UIEvent::StartupCheck(self.component.coordinates().1),
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
UIEvent::AccountStatusChange(account_index) => {
|
||||
if self.cursor_pos.0 == *account_index {
|
||||
self.change_account(context);
|
||||
} else {
|
||||
self.accounts[*account_index].entries = context.accounts[*account_index]
|
||||
.list_folders()
|
||||
.into_iter()
|
||||
.filter(|folder_node| {
|
||||
context.accounts[*account_index].ref_folders()[&folder_node.hash]
|
||||
.is_subscribed()
|
||||
})
|
||||
.map(|f| (f.depth, f.hash))
|
||||
.collect::<_>();
|
||||
self.set_dirty(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
UIEvent::MailboxDelete((account_index, _folder_hash))
|
||||
| UIEvent::MailboxCreate((account_index, _folder_hash)) => {
|
||||
self.accounts[*account_index].entries = context.accounts[*account_index]
|
||||
.list_folders()
|
||||
.into_iter()
|
||||
.filter(|folder_node| {
|
||||
context.accounts[*account_index].ref_folders()[&folder_node.hash]
|
||||
.is_subscribed()
|
||||
})
|
||||
.map(|f| (f.depth, f.hash))
|
||||
.collect::<_>();
|
||||
if self.cursor_pos.0 == *account_index {
|
||||
self.cursor_pos.1 = std::cmp::min(
|
||||
self.accounts[self.cursor_pos.0].entries.len() - 1,
|
||||
self.cursor_pos.1,
|
||||
);
|
||||
self.component.set_coordinates((
|
||||
self.cursor_pos.0,
|
||||
self.accounts[self.cursor_pos.0].entries[self.cursor_pos.1].1,
|
||||
));
|
||||
self.component.refresh_mailbox(context, true);
|
||||
}
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -474,15 +511,15 @@ impl Component for Listing {
|
|||
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
|
||||
return true;
|
||||
};
|
||||
let folder_length = context.accounts[self.cursor_pos.0].len();
|
||||
match k {
|
||||
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_folder"])
|
||||
&& folder_length > 0 =>
|
||||
{
|
||||
if self.cursor_pos.1 + amount < folder_length {
|
||||
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_folder"]) => {
|
||||
if let Some((_, folder_hash)) = self.accounts[self.cursor_pos.0]
|
||||
.entries
|
||||
.get(self.cursor_pos.1 + amount)
|
||||
{
|
||||
self.cursor_pos.1 += amount;
|
||||
self.component
|
||||
.set_coordinates((self.cursor_pos.0, self.cursor_pos.1));
|
||||
.set_coordinates((self.cursor_pos.0, *folder_hash));
|
||||
self.set_dirty(true);
|
||||
} else {
|
||||
return true;
|
||||
|
@ -490,25 +527,32 @@ impl Component for Listing {
|
|||
}
|
||||
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_folder"]) => {
|
||||
if self.cursor_pos.1 >= amount {
|
||||
self.cursor_pos.1 -= amount;
|
||||
self.component
|
||||
.set_coordinates((self.cursor_pos.0, self.cursor_pos.1));
|
||||
self.set_dirty(true);
|
||||
if let Some((_, folder_hash)) = self.accounts[self.cursor_pos.0]
|
||||
.entries
|
||||
.get(self.cursor_pos.1 - amount)
|
||||
{
|
||||
self.cursor_pos.1 -= amount;
|
||||
self.component
|
||||
.set_coordinates((self.cursor_pos.0, *folder_hash));
|
||||
self.set_dirty(true);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_ => return false,
|
||||
_ => {}
|
||||
}
|
||||
/* Account might have no folders yet if it's offline */
|
||||
if let Some(&folder_hash) = context.accounts[self.cursor_pos.0]
|
||||
.folders_order
|
||||
if let Some((_, folder_hash)) = self.accounts[self.cursor_pos.0]
|
||||
.entries
|
||||
.get(self.cursor_pos.1)
|
||||
{
|
||||
/* Account might have no folders yet if it's offline */
|
||||
/* Check if per-folder configuration overrides general configuration */
|
||||
if let Some(index_style) =
|
||||
context.accounts.get(self.cursor_pos.0).and_then(|account| {
|
||||
account.folder_confs(folder_hash).conf_override.index_style
|
||||
account.folder_confs(*folder_hash).conf_override.index_style
|
||||
})
|
||||
{
|
||||
self.component.set_style(index_style);
|
||||
|
@ -523,7 +567,7 @@ impl Component for Listing {
|
|||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
|
||||
self.get_status(context).unwrap(),
|
||||
self.get_status(context),
|
||||
)));
|
||||
return true;
|
||||
}
|
||||
|
@ -550,8 +594,6 @@ impl Component for Listing {
|
|||
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_account"]) => {
|
||||
if self.cursor_pos.0 + amount < self.accounts.len() {
|
||||
self.cursor_pos = (self.cursor_pos.0 + amount, 0);
|
||||
self.component.set_coordinates((self.cursor_pos.0, 0));
|
||||
self.set_dirty(true);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
@ -559,40 +601,14 @@ impl Component for Listing {
|
|||
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_account"]) => {
|
||||
if self.cursor_pos.0 >= amount {
|
||||
self.cursor_pos = (self.cursor_pos.0 - amount, 0);
|
||||
self.component.set_coordinates((self.cursor_pos.0, 0));
|
||||
self.set_dirty(true);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
self.change_account(context);
|
||||
|
||||
/* Account might have no folders yet if it's offline */
|
||||
if let Some(&folder_hash) = context.accounts[self.cursor_pos.0]
|
||||
.folders_order
|
||||
.get(self.cursor_pos.1)
|
||||
{
|
||||
/* Check if per-folder configuration overrides general configuration */
|
||||
if let Some(index_style) =
|
||||
context.accounts.get(self.cursor_pos.0).and_then(|account| {
|
||||
account.folder_confs(folder_hash).conf_override.index_style
|
||||
})
|
||||
{
|
||||
self.component.set_style(index_style);
|
||||
} else if let Some(index_style) = context
|
||||
.accounts
|
||||
.get(self.cursor_pos.0)
|
||||
.and_then(|account| Some(account.settings.conf.index_style()))
|
||||
{
|
||||
self.component.set_style(index_style);
|
||||
}
|
||||
}
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
|
||||
self.get_status(context).unwrap(),
|
||||
)));
|
||||
return true;
|
||||
}
|
||||
UIEvent::Action(ref action) => match action {
|
||||
|
@ -623,6 +639,19 @@ impl Component for Listing {
|
|||
self.component.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
Action::ViewMailbox(idx) => {
|
||||
if let Some((_, folder_hash)) =
|
||||
self.accounts[self.cursor_pos.0].entries.get(*idx)
|
||||
{
|
||||
self.cursor_pos.1 = *idx;
|
||||
self.component
|
||||
.set_coordinates((self.cursor_pos.0, *folder_hash));
|
||||
self.set_dirty(true);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
UIEvent::ChangeMode(UIMode::Normal) => {
|
||||
|
@ -776,7 +805,7 @@ impl Component for Listing {
|
|||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
|
||||
self.get_status(context).unwrap(),
|
||||
self.get_status(context),
|
||||
)));
|
||||
}
|
||||
UIEvent::MailboxUpdate(_) => {
|
||||
|
@ -784,7 +813,7 @@ impl Component for Listing {
|
|||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
|
||||
self.get_status(context).unwrap(),
|
||||
self.get_status(context),
|
||||
)));
|
||||
}
|
||||
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt('')) => {
|
||||
|
@ -830,86 +859,79 @@ impl Component for Listing {
|
|||
self.component.set_id(id);
|
||||
}
|
||||
|
||||
fn get_status(&self, context: &Context) -> Option<String> {
|
||||
Some({
|
||||
let folder_hash = if let Some(h) = context.accounts[self.cursor_pos.0]
|
||||
.folders_order
|
||||
.get(self.cursor_pos.1)
|
||||
{
|
||||
*h
|
||||
} else {
|
||||
return Some(String::new());
|
||||
};
|
||||
if !context.accounts[self.cursor_pos.0].folders[&folder_hash].is_available() {
|
||||
return Some(String::new());
|
||||
}
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let m = if account[self.cursor_pos.1].is_available() {
|
||||
account[self.cursor_pos.1].unwrap()
|
||||
} else {
|
||||
return Some(String::new());
|
||||
};
|
||||
fn get_status(&self, context: &Context) -> String {
|
||||
let folder_hash = if let Some((_, folder_hash)) = self.accounts[self.cursor_pos.0]
|
||||
.entries
|
||||
.get(self.cursor_pos.1)
|
||||
{
|
||||
*folder_hash
|
||||
} else {
|
||||
return String::new();
|
||||
};
|
||||
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
if let Ok(m) = account[folder_hash].as_result() {
|
||||
format!(
|
||||
"Mailbox: {}, Messages: {}, New: {}",
|
||||
m.folder.name(),
|
||||
m.envelopes.len(),
|
||||
m.folder.count().ok().map(|(v, _)| v).unwrap_or(0),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
account[folder_hash].to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IndexStyle> for ListingComponent {
|
||||
fn from(index_style: IndexStyle) -> Self {
|
||||
impl From<(IndexStyle, (usize, FolderHash))> for ListingComponent {
|
||||
fn from((index_style, coordinates): (IndexStyle, (usize, FolderHash))) -> Self {
|
||||
match index_style {
|
||||
IndexStyle::Plain => Plain(Default::default()),
|
||||
IndexStyle::Threaded => Threaded(Default::default()),
|
||||
IndexStyle::Compact => Compact(Default::default()),
|
||||
IndexStyle::Conversations => Conversations(Default::default()),
|
||||
IndexStyle::Plain => Plain(PlainListing::new(coordinates)),
|
||||
IndexStyle::Threaded => Threaded(ThreadListing::new(coordinates)),
|
||||
IndexStyle::Compact => Compact(CompactListing::new(coordinates)),
|
||||
IndexStyle::Conversations => Conversations(ConversationsListing::new(coordinates)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Listing {
|
||||
const DESCRIPTION: &'static str = "listing";
|
||||
pub fn new(accounts: &[Account]) -> Self {
|
||||
let account_entries = accounts
|
||||
pub fn new(context: &mut Context) -> Self {
|
||||
let account_entries: Vec<AccountMenuEntry> = context
|
||||
.accounts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, a)| AccountMenuEntry {
|
||||
name: a.name().to_string(),
|
||||
index: i,
|
||||
.map(|(i, a)| {
|
||||
let entries: SmallVec<[(usize, FolderHash); 16]> = a
|
||||
.list_folders()
|
||||
.into_iter()
|
||||
.filter(|folder_node| a.ref_folders()[&folder_node.hash].is_subscribed())
|
||||
.map(|f| (f.depth, f.hash))
|
||||
.collect::<_>();
|
||||
|
||||
AccountMenuEntry {
|
||||
name: a.name().to_string(),
|
||||
index: i,
|
||||
entries,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
/* Check if per-folder configuration overrides general configuration */
|
||||
let component = if let Some(index_style) = accounts.get(0).and_then(|account| {
|
||||
account.folders_order.get(0).and_then(|folder_hash| {
|
||||
account.folder_confs(*folder_hash).conf_override.index_style
|
||||
})
|
||||
}) {
|
||||
ListingComponent::from(index_style)
|
||||
} else if let Some(index_style) = accounts
|
||||
.get(0)
|
||||
.and_then(|account| Some(account.settings.conf.index_style()))
|
||||
{
|
||||
ListingComponent::from(index_style)
|
||||
} else {
|
||||
Conversations(Default::default())
|
||||
};
|
||||
Listing {
|
||||
component,
|
||||
let mut ret = Listing {
|
||||
component: Offline(OfflineListing::new((0, 0))),
|
||||
accounts: account_entries,
|
||||
visible: true,
|
||||
dirty: true,
|
||||
cursor_pos: (0, 0),
|
||||
startup_checks_rate: RateLimit::new(2, 1000),
|
||||
theme_default: ThemeAttribute::default(),
|
||||
theme_default: conf::value(context, "theme_default"),
|
||||
id: ComponentId::new_v4(),
|
||||
show_divider: false,
|
||||
menu_visibility: true,
|
||||
ratio: 90,
|
||||
cmd_buf: String::with_capacity(4),
|
||||
}
|
||||
};
|
||||
ret.change_account(context);
|
||||
ret
|
||||
}
|
||||
|
||||
fn draw_menu(&mut self, grid: &mut CellBuffer, mut area: Area, context: &mut Context) {
|
||||
|
@ -952,86 +974,31 @@ impl Listing {
|
|||
debug!("BUG: invalid area in print_account");
|
||||
}
|
||||
// Each entry and its index in the account
|
||||
let entries: FnvHashMap<FolderHash, Folder> = context.accounts[a.index]
|
||||
.list_folders()
|
||||
.into_iter()
|
||||
.map(|f| (f.hash(), f))
|
||||
.collect();
|
||||
let folders_order: FnvHashMap<FolderHash, usize> = context.accounts[a.index]
|
||||
.folders_order()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &fh)| (fh, i))
|
||||
.collect();
|
||||
let folders: FnvHashMap<FolderHash, Folder> =
|
||||
context.accounts[a.index].ref_folders().clone();
|
||||
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
||||
let must_highlight_account: bool = self.cursor_pos.0 == a.index;
|
||||
|
||||
let mut inc = 0;
|
||||
let mut depth = 0;
|
||||
let mut lines: Vec<(usize, usize, FolderHash, Option<usize>)> = Vec::new();
|
||||
|
||||
/* Gather the folder tree structure in `lines` recursively */
|
||||
fn print(
|
||||
folder_idx: FolderHash,
|
||||
depth: &mut usize,
|
||||
inc: &mut usize,
|
||||
entries: &FnvHashMap<FolderHash, Folder>,
|
||||
folders_order: &FnvHashMap<FolderHash, usize>,
|
||||
lines: &mut Vec<(usize, usize, FolderHash, Option<usize>)>,
|
||||
index: usize, //account index
|
||||
context: &mut Context,
|
||||
) {
|
||||
match context.accounts[index].status(entries[&folder_idx].hash()) {
|
||||
Ok(_) => {
|
||||
lines.push((
|
||||
*depth,
|
||||
*inc,
|
||||
folder_idx,
|
||||
entries[&folder_idx].count().ok().map(|(v, _)| v),
|
||||
));
|
||||
for (i, &(depth, folder_hash)) in a.entries.iter().enumerate() {
|
||||
if folders[&folder_hash].is_subscribed() {
|
||||
match context.accounts[a.index].status(folder_hash) {
|
||||
Ok(_) => {
|
||||
lines.push((
|
||||
depth,
|
||||
i,
|
||||
folder_hash,
|
||||
folders[&folder_hash].count().ok().map(|(v, _)| v),
|
||||
));
|
||||
}
|
||||
Err(_) => {
|
||||
lines.push((depth, i, folder_hash, None));
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
lines.push((*depth, *inc, folder_idx, None));
|
||||
}
|
||||
}
|
||||
*inc += 1;
|
||||
let mut children: Vec<FolderHash> = entries[&folder_idx].children().to_vec();
|
||||
children
|
||||
.sort_unstable_by(|a, b| folders_order[a].partial_cmp(&folders_order[b]).unwrap());
|
||||
*depth += 1;
|
||||
for child in children {
|
||||
print(
|
||||
child,
|
||||
depth,
|
||||
inc,
|
||||
entries,
|
||||
folders_order,
|
||||
lines,
|
||||
index,
|
||||
context,
|
||||
);
|
||||
}
|
||||
*depth -= 1;
|
||||
}
|
||||
let mut keys = entries.keys().cloned().collect::<Vec<FolderHash>>();
|
||||
keys.sort_unstable_by(|a, b| folders_order[a].partial_cmp(&folders_order[b]).unwrap());
|
||||
|
||||
/* Start with roots */
|
||||
for f in keys {
|
||||
if entries[&f].parent().is_none() {
|
||||
print(
|
||||
f,
|
||||
&mut depth,
|
||||
&mut inc,
|
||||
&entries,
|
||||
&folders_order,
|
||||
&mut lines,
|
||||
a.index,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1051,8 +1018,8 @@ impl Listing {
|
|||
"offline",
|
||||
grid,
|
||||
Color::Byte(243),
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
(pos_inc(upper_left, (0, 1)), bottom_right),
|
||||
None,
|
||||
);
|
||||
|
@ -1138,7 +1105,7 @@ impl Listing {
|
|||
None,
|
||||
);
|
||||
let (x, _) = write_string_to_grid(
|
||||
entries[&folder_idx].name(),
|
||||
folders[&folder_idx].name(),
|
||||
grid,
|
||||
att.fg,
|
||||
att.bg,
|
||||
|
@ -1190,4 +1157,46 @@ impl Listing {
|
|||
idx - 1
|
||||
}
|
||||
}
|
||||
|
||||
fn change_account(&mut self, context: &mut Context) {
|
||||
self.accounts[self.cursor_pos.0].entries = context.accounts[self.cursor_pos.0]
|
||||
.list_folders()
|
||||
.into_iter()
|
||||
.filter(|folder_node| {
|
||||
context.accounts[self.cursor_pos.0].ref_folders()[&folder_node.hash].is_subscribed()
|
||||
})
|
||||
.map(|f| (f.depth, f.hash))
|
||||
.collect::<_>();
|
||||
/* Account might have no folders yet if it's offline */
|
||||
if let Some((_, folder_hash)) = self.accounts[self.cursor_pos.0]
|
||||
.entries
|
||||
.get(self.cursor_pos.1)
|
||||
{
|
||||
self.component
|
||||
.set_coordinates((self.cursor_pos.0, *folder_hash));
|
||||
/* Check if per-folder configuration overrides general configuration */
|
||||
if let Some(index_style) = context
|
||||
.accounts
|
||||
.get(self.cursor_pos.0)
|
||||
.and_then(|account| account.folder_confs(*folder_hash).conf_override.index_style)
|
||||
{
|
||||
self.component.set_style(index_style);
|
||||
} else if let Some(index_style) = context
|
||||
.accounts
|
||||
.get(self.cursor_pos.0)
|
||||
.and_then(|account| Some(account.settings.conf.index_style()))
|
||||
{
|
||||
self.component.set_style(index_style);
|
||||
}
|
||||
} else {
|
||||
/* Set to dummy */
|
||||
self.component = Offline(OfflineListing::new((self.cursor_pos.0, 0)));
|
||||
}
|
||||
self.set_dirty(true);
|
||||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(debug!(
|
||||
self.get_status(context)
|
||||
))));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,9 +49,8 @@ macro_rules! address_list {
|
|||
#[derive(Debug)]
|
||||
pub struct CompactListing {
|
||||
/// (x, y, z): x is accounts, y is folders, z is index inside a folder.
|
||||
cursor_pos: (usize, usize, usize),
|
||||
new_cursor_pos: (usize, usize, usize),
|
||||
folder_hash: FolderHash,
|
||||
cursor_pos: (usize, FolderHash, usize),
|
||||
new_cursor_pos: (usize, FolderHash, usize),
|
||||
length: usize,
|
||||
sort: (SortField, SortOrder),
|
||||
subsort: (SortField, SortOrder),
|
||||
|
@ -100,16 +99,102 @@ impl MailListingTrait for CompactListing {
|
|||
.cloned();
|
||||
SmallVec::from_iter(iter.into_iter())
|
||||
}
|
||||
|
||||
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account folder the user has
|
||||
/// chosen.
|
||||
fn refresh_mailbox(&mut self, context: &mut Context, force: bool) {
|
||||
self.dirty = true;
|
||||
let old_cursor_pos = self.cursor_pos;
|
||||
if !(self.cursor_pos.0 == self.new_cursor_pos.0
|
||||
&& self.cursor_pos.1 == self.new_cursor_pos.1)
|
||||
{
|
||||
self.cursor_pos.2 = 0;
|
||||
self.new_cursor_pos.2 = 0;
|
||||
}
|
||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||
|
||||
self.color_cache = ColorCache {
|
||||
unseen: crate::conf::value(context, "mail.listing.compact.unseen"),
|
||||
highlighted: crate::conf::value(context, "mail.listing.compact.highlighted"),
|
||||
even: crate::conf::value(context, "mail.listing.compact.even"),
|
||||
odd: crate::conf::value(context, "mail.listing.compact.odd"),
|
||||
selected: crate::conf::value(context, "mail.listing.compact.selected"),
|
||||
attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
|
||||
thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
|
||||
theme_default: crate::conf::value(context, "theme_default"),
|
||||
..self.color_cache
|
||||
};
|
||||
if std::env::var("NO_COLOR").is_ok()
|
||||
&& (context.settings.terminal.use_color.is_false()
|
||||
|| context.settings.terminal.use_color.is_internal())
|
||||
{
|
||||
self.color_cache.highlighted.attrs |= Attr::Reverse;
|
||||
}
|
||||
|
||||
// Get mailbox as a reference.
|
||||
//
|
||||
match context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) {
|
||||
Ok(()) => {}
|
||||
Err(_) => {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
let message: String =
|
||||
context.accounts[self.cursor_pos.0][self.cursor_pos.1].to_string();
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
|
||||
self.length = 0;
|
||||
write_string_to_grid(
|
||||
message.as_str(),
|
||||
&mut self.data_columns.columns[0],
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let threads = &context.accounts[self.cursor_pos.0].collection.threads[&self.cursor_pos.1];
|
||||
self.all_threads.clear();
|
||||
let mut roots = threads.roots();
|
||||
threads.group_inner_sort_by(
|
||||
&mut roots,
|
||||
self.sort,
|
||||
&context.accounts[self.cursor_pos.0].collection.envelopes,
|
||||
);
|
||||
|
||||
self.redraw_list(
|
||||
context,
|
||||
Box::new(roots.into_iter()) as Box<dyn Iterator<Item = ThreadHash>>,
|
||||
);
|
||||
|
||||
if !force && old_cursor_pos == self.new_cursor_pos {
|
||||
self.view.update(context);
|
||||
} else if self.unfocused {
|
||||
let thread = self.get_thread_under_cursor(self.cursor_pos.2);
|
||||
|
||||
self.view = ThreadView::new(self.new_cursor_pos, thread, None, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ListingTrait for CompactListing {
|
||||
fn coordinates(&self) -> (usize, usize) {
|
||||
fn coordinates(&self) -> (usize, FolderHash) {
|
||||
(self.new_cursor_pos.0, self.new_cursor_pos.1)
|
||||
}
|
||||
|
||||
fn set_coordinates(&mut self, coordinates: (usize, usize)) {
|
||||
fn set_coordinates(&mut self, coordinates: (usize, FolderHash)) {
|
||||
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
|
||||
self.unfocused = false;
|
||||
self.view = ThreadView::default();
|
||||
self.filtered_selection.clear();
|
||||
self.filtered_order.clear();
|
||||
self.filter_term.clear();
|
||||
|
@ -123,7 +208,7 @@ impl ListingTrait for CompactListing {
|
|||
let thread_hash = self.get_thread_under_cursor(idx);
|
||||
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let threads = &account.collection.threads[&self.folder_hash];
|
||||
let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||
let thread = threads.thread_ref(thread_hash);
|
||||
|
||||
let fg_color = if thread.unseen() > 0 {
|
||||
|
@ -190,12 +275,12 @@ impl ListingTrait for CompactListing {
|
|||
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0
|
||||
{
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
if self.length == 0 {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
copy_area(
|
||||
grid,
|
||||
&self.data_columns.columns[0],
|
||||
|
@ -206,6 +291,9 @@ impl ListingTrait for CompactListing {
|
|||
return;
|
||||
}
|
||||
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
|
||||
if rows == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(mvm) = self.movement.take() {
|
||||
match mvm {
|
||||
|
@ -326,7 +414,7 @@ impl ListingTrait for CompactListing {
|
|||
);
|
||||
}
|
||||
}
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
/* Page_no has changed, so draw new page */
|
||||
let mut x = get_x(upper_left);
|
||||
let mut flag_x = 0;
|
||||
|
@ -419,6 +507,7 @@ impl ListingTrait for CompactListing {
|
|||
pos_inc(upper_left, (0, self.length - top_idx)),
|
||||
bottom_right,
|
||||
),
|
||||
self.color_cache.theme_default,
|
||||
);
|
||||
}
|
||||
context.dirty_areas.push_back(area);
|
||||
|
@ -441,9 +530,9 @@ impl ListingTrait for CompactListing {
|
|||
}
|
||||
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
match account.search(&self.filter_term, self.sort, self.folder_hash) {
|
||||
match account.search(&self.filter_term, self.sort, self.cursor_pos.1) {
|
||||
Ok(results) => {
|
||||
let threads = &account.collection.threads[&self.folder_hash];
|
||||
let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||
for env_hash in results {
|
||||
if !account.collection.contains_key(&env_hash) {
|
||||
continue;
|
||||
|
@ -472,8 +561,15 @@ impl ListingTrait for CompactListing {
|
|||
self.new_cursor_pos.2 =
|
||||
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
|
||||
} else {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(0, 0, Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(0, 0, default_cell, context);
|
||||
}
|
||||
self.redraw_list(
|
||||
context,
|
||||
|
@ -492,14 +588,21 @@ impl ListingTrait for CompactListing {
|
|||
format!("Failed to search for term {}: {}", &self.filter_term, e),
|
||||
ERROR,
|
||||
);
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
|
||||
write_string_to_grid(
|
||||
&message,
|
||||
&mut self.data_columns.columns[0],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (message.len() - 1, 0)),
|
||||
None,
|
||||
);
|
||||
|
@ -519,19 +622,12 @@ impl fmt::Display for CompactListing {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for CompactListing {
|
||||
fn default() -> Self {
|
||||
CompactListing::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CompactListing {
|
||||
const DESCRIPTION: &'static str = "compact listing";
|
||||
fn new() -> Self {
|
||||
pub fn new(coordinates: (usize, FolderHash)) -> Self {
|
||||
CompactListing {
|
||||
cursor_pos: (0, 1, 0),
|
||||
new_cursor_pos: (0, 0, 0),
|
||||
folder_hash: 0,
|
||||
new_cursor_pos: (coordinates.0, coordinates.1, 0),
|
||||
length: 0,
|
||||
sort: (Default::default(), Default::default()),
|
||||
subsort: (SortField::Date, SortOrder::Desc),
|
||||
|
@ -560,7 +656,7 @@ impl CompactListing {
|
|||
hash: ThreadHash,
|
||||
) -> EntryStrings {
|
||||
let thread = threads.thread_ref(hash);
|
||||
let folder = &context.accounts[self.cursor_pos.0].folder_confs[&self.folder_hash];
|
||||
let folder = &context.accounts[self.cursor_pos.0].folder_confs[&self.cursor_pos.1];
|
||||
let mut tags = String::new();
|
||||
let mut colors: SmallVec<[_; 8]> = SmallVec::new();
|
||||
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
|
||||
|
@ -624,93 +720,6 @@ impl CompactListing {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account folder the user has
|
||||
/// chosen.
|
||||
fn refresh_mailbox(&mut self, context: &mut Context) {
|
||||
self.dirty = true;
|
||||
let old_cursor_pos = self.cursor_pos;
|
||||
if !(self.cursor_pos.0 == self.new_cursor_pos.0
|
||||
&& self.cursor_pos.1 == self.new_cursor_pos.1)
|
||||
{
|
||||
self.cursor_pos.2 = 0;
|
||||
self.new_cursor_pos.2 = 0;
|
||||
}
|
||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||
self.folder_hash = if let Some(h) = context.accounts[self.cursor_pos.0]
|
||||
.folders_order
|
||||
.get(self.cursor_pos.1)
|
||||
{
|
||||
*h
|
||||
} else {
|
||||
self.cursor_pos.1 = old_cursor_pos.1;
|
||||
self.dirty = false;
|
||||
return;
|
||||
};
|
||||
|
||||
self.color_cache = ColorCache {
|
||||
unseen: crate::conf::value(context, "mail.listing.compact.unseen"),
|
||||
highlighted: crate::conf::value(context, "mail.listing.compact.highlighted"),
|
||||
even: crate::conf::value(context, "mail.listing.compact.even"),
|
||||
odd: crate::conf::value(context, "mail.listing.compact.odd"),
|
||||
selected: crate::conf::value(context, "mail.listing.compact.selected"),
|
||||
attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
|
||||
thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
|
||||
..self.color_cache
|
||||
};
|
||||
if std::env::var("NO_COLOR").is_ok()
|
||||
&& (context.settings.terminal.use_color.is_false()
|
||||
|| context.settings.terminal.use_color.is_internal())
|
||||
{
|
||||
self.color_cache.highlighted.attrs |= Attr::Reverse;
|
||||
}
|
||||
|
||||
// Get mailbox as a reference.
|
||||
//
|
||||
match context.accounts[self.cursor_pos.0].status(self.folder_hash) {
|
||||
Ok(()) => {}
|
||||
Err(_) => {
|
||||
let message: String =
|
||||
context.accounts[self.cursor_pos.0][self.folder_hash].to_string();
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
|
||||
self.length = 0;
|
||||
write_string_to_grid(
|
||||
message.as_str(),
|
||||
&mut self.data_columns.columns[0],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let threads = &context.accounts[self.cursor_pos.0].collection.threads[&self.folder_hash];
|
||||
self.all_threads.clear();
|
||||
let mut roots = threads.roots();
|
||||
threads.group_inner_sort_by(
|
||||
&mut roots,
|
||||
self.sort,
|
||||
&context.accounts[self.cursor_pos.0].collection.envelopes,
|
||||
);
|
||||
|
||||
self.redraw_list(
|
||||
context,
|
||||
Box::new(roots.into_iter()) as Box<dyn Iterator<Item = ThreadHash>>,
|
||||
);
|
||||
|
||||
if old_cursor_pos == self.new_cursor_pos {
|
||||
self.view.update(context);
|
||||
} else if self.unfocused {
|
||||
let thread = self.get_thread_under_cursor(self.cursor_pos.2);
|
||||
|
||||
self.view = ThreadView::new(self.new_cursor_pos, thread, None, context);
|
||||
}
|
||||
}
|
||||
|
||||
fn redraw_list(&mut self, context: &Context, items: Box<dyn Iterator<Item = ThreadHash>>) {
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let mailbox = &account[self.cursor_pos.1].unwrap();
|
||||
|
@ -736,7 +745,6 @@ impl CompactListing {
|
|||
);
|
||||
|
||||
for (idx, thread) in items.enumerate() {
|
||||
debug!(thread);
|
||||
self.length += 1;
|
||||
let thread_node = &threads.thread_nodes()[&threads.thread_ref(thread).root()];
|
||||
let root_env_hash = thread_node.message().unwrap_or_else(|| {
|
||||
|
@ -804,23 +812,30 @@ impl CompactListing {
|
|||
|
||||
min_width.0 = self.length.saturating_sub(1).to_string().len();
|
||||
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
/* index column */
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(min_width.0, rows.len(), Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.0, rows.len(), default_cell, context);
|
||||
|
||||
/* date column */
|
||||
self.data_columns.columns[1] =
|
||||
CellBuffer::new_with_context(min_width.1, rows.len(), Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.1, rows.len(), default_cell, context);
|
||||
/* from column */
|
||||
self.data_columns.columns[2] =
|
||||
CellBuffer::new_with_context(min_width.2, rows.len(), Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.2, rows.len(), default_cell, context);
|
||||
self.data_columns.segment_tree[2] = row_widths.2.into();
|
||||
/* flags column */
|
||||
self.data_columns.columns[3] =
|
||||
CellBuffer::new_with_context(min_width.3, rows.len(), Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.3, rows.len(), default_cell, context);
|
||||
/* subject column */
|
||||
self.data_columns.columns[4] =
|
||||
CellBuffer::new_with_context(min_width.4, rows.len(), Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.4, rows.len(), default_cell, context);
|
||||
self.data_columns.segment_tree[4] = row_widths.4.into();
|
||||
|
||||
for ((idx, (thread, root_env_hash)), strings) in rows {
|
||||
|
@ -836,67 +851,75 @@ impl CompactListing {
|
|||
panic!();
|
||||
}
|
||||
let thread = threads.thread_ref(thread);
|
||||
let (fg_color, bg_color) = if thread.unseen() > 0 {
|
||||
(self.color_cache.unseen.fg, self.color_cache.unseen.bg)
|
||||
let row_attr = if thread.unseen() > 0 {
|
||||
self.color_cache.unseen
|
||||
} else if idx % 2 == 0 {
|
||||
(self.color_cache.even.fg, self.color_cache.even.bg)
|
||||
self.color_cache.even
|
||||
} else {
|
||||
(self.color_cache.odd.fg, self.color_cache.odd.bg)
|
||||
self.color_cache.odd
|
||||
};
|
||||
let (x, _) = write_string_to_grid(
|
||||
&idx.to_string(),
|
||||
&mut self.data_columns.columns[0],
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
row_attr.fg,
|
||||
row_attr.bg,
|
||||
row_attr.attrs,
|
||||
((0, idx), (min_width.0, idx)),
|
||||
None,
|
||||
);
|
||||
for x in x..min_width.0 {
|
||||
self.data_columns.columns[0][(x, idx)].set_bg(bg_color);
|
||||
self.data_columns.columns[0][(x, idx)]
|
||||
.set_bg(row_attr.bg)
|
||||
.set_attrs(row_attr.attrs);
|
||||
}
|
||||
let (x, _) = write_string_to_grid(
|
||||
&strings.date,
|
||||
&mut self.data_columns.columns[1],
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
row_attr.fg,
|
||||
row_attr.bg,
|
||||
row_attr.attrs,
|
||||
((0, idx), (min_width.1, idx)),
|
||||
None,
|
||||
);
|
||||
for x in x..min_width.1 {
|
||||
self.data_columns.columns[1][(x, idx)].set_bg(bg_color);
|
||||
self.data_columns.columns[1][(x, idx)]
|
||||
.set_bg(row_attr.bg)
|
||||
.set_attrs(row_attr.attrs);
|
||||
}
|
||||
let (x, _) = write_string_to_grid(
|
||||
&strings.from,
|
||||
&mut self.data_columns.columns[2],
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
row_attr.fg,
|
||||
row_attr.bg,
|
||||
row_attr.attrs,
|
||||
((0, idx), (min_width.2, idx)),
|
||||
None,
|
||||
);
|
||||
for x in x..min_width.2 {
|
||||
self.data_columns.columns[2][(x, idx)].set_bg(bg_color);
|
||||
self.data_columns.columns[2][(x, idx)]
|
||||
.set_bg(row_attr.bg)
|
||||
.set_attrs(row_attr.attrs);
|
||||
}
|
||||
let (x, _) = write_string_to_grid(
|
||||
&strings.flag,
|
||||
&mut self.data_columns.columns[3],
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
row_attr.fg,
|
||||
row_attr.bg,
|
||||
row_attr.attrs,
|
||||
((0, idx), (min_width.3, idx)),
|
||||
None,
|
||||
);
|
||||
for x in x..min_width.3 {
|
||||
self.data_columns.columns[3][(x, idx)].set_bg(bg_color);
|
||||
self.data_columns.columns[3][(x, idx)]
|
||||
.set_bg(row_attr.bg)
|
||||
.set_attrs(row_attr.attrs);
|
||||
}
|
||||
let (x, _) = write_string_to_grid(
|
||||
&strings.subject,
|
||||
&mut self.data_columns.columns[4],
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
row_attr.fg,
|
||||
row_attr.bg,
|
||||
row_attr.attrs,
|
||||
((0, idx), (min_width.4, idx)),
|
||||
None,
|
||||
);
|
||||
|
@ -927,8 +950,10 @@ impl CompactListing {
|
|||
x
|
||||
};
|
||||
for x in x..min_width.4 {
|
||||
self.data_columns.columns[4][(x, idx)].set_ch(' ');
|
||||
self.data_columns.columns[4][(x, idx)].set_bg(bg_color);
|
||||
self.data_columns.columns[4][(x, idx)]
|
||||
.set_ch(' ')
|
||||
.set_bg(row_attr.bg)
|
||||
.set_attrs(row_attr.attrs);
|
||||
}
|
||||
match (thread.snoozed(), thread.has_attachments()) {
|
||||
(true, true) => {
|
||||
|
@ -951,18 +976,14 @@ impl CompactListing {
|
|||
if self.length == 0 && self.filter_term.is_empty() {
|
||||
let mailbox = &account[self.cursor_pos.1];
|
||||
let message = mailbox.to_string();
|
||||
self.data_columns.columns[0] = CellBuffer::new_with_context(
|
||||
message.len(),
|
||||
self.length + 1,
|
||||
Cell::with_char(' '),
|
||||
context,
|
||||
);
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(message.len(), self.length + 1, default_cell, context);
|
||||
write_string_to_grid(
|
||||
&message,
|
||||
&mut self.data_columns.columns[0],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
None,
|
||||
);
|
||||
|
@ -987,7 +1008,7 @@ impl CompactListing {
|
|||
|
||||
fn update_line(&mut self, context: &Context, thread_hash: ThreadHash) {
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let threads = &account.collection.threads[&self.folder_hash];
|
||||
let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||
let thread = threads.thread_ref(thread_hash);
|
||||
let thread_node_hash = threads.thread_group_iter(thread_hash).next().unwrap().1;
|
||||
if let Some(env_hash) = threads.thread_nodes()[&thread_node_hash].message() {
|
||||
|
@ -1139,13 +1160,24 @@ impl Component for CompactListing {
|
|||
self.filter_term
|
||||
),
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
area,
|
||||
Some(get_x(upper_left)),
|
||||
);
|
||||
clear_area(grid, ((x, y), set_y(bottom_right, y)));
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
for row in grid.bounds_iter(((x, y), set_y(bottom_right, y))) {
|
||||
for c in row {
|
||||
grid[c] = default_cell;
|
||||
}
|
||||
}
|
||||
context
|
||||
.dirty_areas
|
||||
.push_back((upper_left, set_y(bottom_right, y + 1)));
|
||||
|
@ -1183,7 +1215,7 @@ impl Component for CompactListing {
|
|||
}
|
||||
} else {
|
||||
if self.length == 0 && self.dirty {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
|
@ -1244,7 +1276,7 @@ impl Component for CompactListing {
|
|||
// FIXME: perform sort
|
||||
self.dirty = true;
|
||||
} else {
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1260,13 +1292,13 @@ impl Component for CompactListing {
|
|||
account
|
||||
.collection
|
||||
.threads
|
||||
.entry(self.folder_hash)
|
||||
.entry(self.cursor_pos.1)
|
||||
.and_modify(|threads| {
|
||||
let is_snoozed = threads.thread_ref(thread).snoozed();
|
||||
threads.thread_ref_mut(thread).set_snoozed(!is_snoozed);
|
||||
});
|
||||
self.row_updates.push(thread);
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1278,18 +1310,18 @@ impl Component for CompactListing {
|
|||
}
|
||||
match *event {
|
||||
UIEvent::MailboxUpdate((ref idxa, ref idxf))
|
||||
if (*idxa, *idxf) == (self.new_cursor_pos.0, self.folder_hash) =>
|
||||
if (*idxa, *idxf) == (self.new_cursor_pos.0, self.cursor_pos.1) =>
|
||||
{
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::StartupCheck(ref f) if *f == self.folder_hash => {
|
||||
self.refresh_mailbox(context);
|
||||
UIEvent::StartupCheck(ref f) if *f == self.cursor_pos.1 => {
|
||||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => {
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let threads = &account.collection.threads[&self.folder_hash];
|
||||
let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||
if !account.collection.contains_key(&new_hash) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1316,24 +1348,11 @@ impl Component for CompactListing {
|
|||
}
|
||||
UIEvent::Input(Key::Esc) if !self.unfocused && !self.filter_term.is_empty() => {
|
||||
self.set_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1));
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Action(ref action) => match action {
|
||||
Action::ViewMailbox(idx) => {
|
||||
if context.accounts[self.cursor_pos.0]
|
||||
.folders_order
|
||||
.get(*idx)
|
||||
.is_none()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
self.filtered_selection.clear();
|
||||
self.new_cursor_pos.1 = *idx;
|
||||
self.refresh_mailbox(context);
|
||||
return true;
|
||||
}
|
||||
Action::Listing(Filter(ref filter_term)) if !self.unfocused => {
|
||||
self.filter(filter_term, context);
|
||||
self.dirty = true;
|
||||
|
|
|
@ -28,9 +28,8 @@ use std::iter::FromIterator;
|
|||
#[derive(Debug)]
|
||||
pub struct ConversationsListing {
|
||||
/// (x, y, z): x is accounts, y is folders, z is index inside a folder.
|
||||
cursor_pos: (usize, usize, usize),
|
||||
new_cursor_pos: (usize, usize, usize),
|
||||
folder_hash: FolderHash,
|
||||
cursor_pos: (usize, FolderHash, usize),
|
||||
new_cursor_pos: (usize, FolderHash, usize),
|
||||
length: usize,
|
||||
sort: (SortField, SortOrder),
|
||||
subsort: (SortField, SortOrder),
|
||||
|
@ -79,16 +78,109 @@ impl MailListingTrait for ConversationsListing {
|
|||
.cloned();
|
||||
SmallVec::from_iter(iter.into_iter())
|
||||
}
|
||||
|
||||
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account folder the user has
|
||||
/// chosen.
|
||||
fn refresh_mailbox(&mut self, context: &mut Context, force: bool) {
|
||||
self.dirty = true;
|
||||
let old_folder_hash = self.cursor_pos.1;
|
||||
let old_cursor_pos = self.cursor_pos;
|
||||
if !(self.cursor_pos.0 == self.new_cursor_pos.0
|
||||
&& self.cursor_pos.1 == self.new_cursor_pos.1)
|
||||
{
|
||||
self.cursor_pos.2 = 0;
|
||||
self.new_cursor_pos.2 = 0;
|
||||
}
|
||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||
|
||||
self.color_cache = ColorCache {
|
||||
theme_default: crate::conf::value(context, "mail.listing.conversations"),
|
||||
subject: crate::conf::value(context, "mail.listing.conversations.subject"),
|
||||
from: crate::conf::value(context, "mail.listing.conversations.from"),
|
||||
date: crate::conf::value(context, "mail.listing.conversations.date"),
|
||||
padding: crate::conf::value(context, "mail.listing.conversations.padding"),
|
||||
unseen: crate::conf::value(context, "mail.listing.conversations.unseen"),
|
||||
unseen_padding: crate::conf::value(
|
||||
context,
|
||||
"mail.listing.conversations.unseen_padding",
|
||||
),
|
||||
highlighted: crate::conf::value(context, "mail.listing.conversations.highlighted"),
|
||||
attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
|
||||
thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
|
||||
..self.color_cache
|
||||
};
|
||||
|
||||
if std::env::var("NO_COLOR").is_ok()
|
||||
&& (context.settings.terminal.use_color.is_false()
|
||||
|| context.settings.terminal.use_color.is_internal())
|
||||
{
|
||||
self.color_cache.highlighted.attrs |= Attr::Reverse;
|
||||
}
|
||||
// Get mailbox as a reference.
|
||||
//
|
||||
match context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) {
|
||||
Ok(()) => {}
|
||||
Err(_) => {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
let message: String =
|
||||
context.accounts[self.cursor_pos.0][self.cursor_pos.1].to_string();
|
||||
self.content =
|
||||
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
|
||||
self.length = 0;
|
||||
write_string_to_grid(
|
||||
message.as_str(),
|
||||
&mut self.content,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (message.len() - 1, 0)),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let threads = &context.accounts[self.cursor_pos.0].collection.threads[&self.cursor_pos.1];
|
||||
self.all_threads.clear();
|
||||
let mut roots = threads.roots();
|
||||
threads.group_inner_sort_by(
|
||||
&mut roots,
|
||||
self.sort,
|
||||
&context.accounts[self.cursor_pos.0].collection.envelopes,
|
||||
);
|
||||
|
||||
self.redraw_list(
|
||||
context,
|
||||
Box::new(roots.into_iter()) as Box<dyn Iterator<Item = ThreadHash>>,
|
||||
);
|
||||
|
||||
if !force && old_cursor_pos == self.new_cursor_pos && old_folder_hash == self.cursor_pos.1 {
|
||||
self.view.update(context);
|
||||
} else if self.unfocused {
|
||||
let thread_group = self.get_thread_under_cursor(self.cursor_pos.2);
|
||||
|
||||
self.view = ThreadView::new(self.new_cursor_pos, thread_group, None, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ListingTrait for ConversationsListing {
|
||||
fn coordinates(&self) -> (usize, usize) {
|
||||
fn coordinates(&self) -> (usize, FolderHash) {
|
||||
(self.new_cursor_pos.0, self.new_cursor_pos.1)
|
||||
}
|
||||
|
||||
fn set_coordinates(&mut self, coordinates: (usize, usize)) {
|
||||
fn set_coordinates(&mut self, coordinates: (usize, FolderHash)) {
|
||||
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
|
||||
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
|
||||
self.unfocused = false;
|
||||
self.view = ThreadView::default();
|
||||
self.filtered_selection.clear();
|
||||
self.filtered_order.clear();
|
||||
self.filter_term.clear();
|
||||
|
@ -102,7 +194,7 @@ impl ListingTrait for ConversationsListing {
|
|||
let thread_hash = self.get_thread_under_cursor(idx);
|
||||
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let threads = &account.collection.threads[&self.folder_hash];
|
||||
let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||
let thread = threads.thread_ref(thread_hash);
|
||||
|
||||
let fg_color = if thread.unseen() > 0 {
|
||||
|
@ -183,12 +275,12 @@ impl ListingTrait for ConversationsListing {
|
|||
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0
|
||||
{
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
if self.length == 0 {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
copy_area(
|
||||
grid,
|
||||
&self.content,
|
||||
|
@ -199,6 +291,9 @@ impl ListingTrait for ConversationsListing {
|
|||
return;
|
||||
}
|
||||
let rows = (get_y(bottom_right) - get_y(upper_left) + 1) / 3;
|
||||
if rows == 0 {
|
||||
return;
|
||||
}
|
||||
let pad = (get_y(bottom_right) - get_y(upper_left) + 1) % 3;
|
||||
|
||||
if let Some(mvm) = self.movement.take() {
|
||||
|
@ -265,7 +360,7 @@ impl ListingTrait for ConversationsListing {
|
|||
self.cursor_pos.2 = self.new_cursor_pos.2;
|
||||
}
|
||||
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
/* Page_no has changed, so draw new page */
|
||||
copy_area(
|
||||
grid,
|
||||
|
@ -305,6 +400,7 @@ impl ListingTrait for ConversationsListing {
|
|||
pos_inc(upper_left, (0, 3 * (self.length - top_idx))),
|
||||
bottom_right,
|
||||
),
|
||||
self.color_cache.theme_default,
|
||||
);
|
||||
(0, self.length - top_idx)
|
||||
} else {
|
||||
|
@ -321,12 +417,14 @@ impl ListingTrait for ConversationsListing {
|
|||
let bg_color = grid[(get_x(upper_left) + width - 1, y_offset + 3 * y)].bg();
|
||||
for x in (get_x(upper_left) + width)..=get_x(bottom_right) {
|
||||
grid[(x, y_offset + 3 * y)].set_bg(bg_color);
|
||||
grid[(x, y_offset + 3 * y + 1)].set_ch('▁');
|
||||
grid[(x, y_offset + 3 * y + 2)].set_fg(Color::Default);
|
||||
grid[(x, y_offset + 3 * y + 1)].set_bg(bg_color);
|
||||
grid[(x, y_offset + 3 * y + 2)].set_ch('▓');
|
||||
grid[(x, y_offset + 3 * y + 2)].set_fg(padding_fg);
|
||||
grid[(x, y_offset + 3 * y + 2)].set_bg(bg_color);
|
||||
grid[(x, y_offset + 3 * y + 1)]
|
||||
.set_ch('▁')
|
||||
.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(bg_color);
|
||||
grid[(x, y_offset + 3 * y + 2)]
|
||||
.set_ch('▓')
|
||||
.set_fg(padding_fg)
|
||||
.set_bg(bg_color);
|
||||
}
|
||||
}
|
||||
if pad > 0 {
|
||||
|
@ -337,10 +435,10 @@ impl ListingTrait for ConversationsListing {
|
|||
grid[(x, y_offset + y + 1)].set_ch('▁');
|
||||
grid[(x, y_offset + y + 1)].set_bg(bg_color);
|
||||
if pad == 2 {
|
||||
grid[(x, y_offset + y + 2)].set_fg(Color::Default);
|
||||
grid[(x, y_offset + y + 2)].set_ch('▓');
|
||||
grid[(x, y_offset + y + 2)].set_fg(padding_fg);
|
||||
grid[(x, y_offset + y + 2)].set_bg(bg_color);
|
||||
grid[(x, y_offset + y + 2)]
|
||||
.set_ch('▓')
|
||||
.set_fg(padding_fg)
|
||||
.set_bg(bg_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -366,9 +464,9 @@ impl ListingTrait for ConversationsListing {
|
|||
}
|
||||
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
match account.search(&self.filter_term, self.sort, self.folder_hash) {
|
||||
match account.search(&self.filter_term, self.sort, self.cursor_pos.1) {
|
||||
Ok(results) => {
|
||||
let threads = &account.collection.threads[&self.folder_hash];
|
||||
let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||
for env_hash in results {
|
||||
if !account.collection.contains_key(&env_hash) {
|
||||
continue;
|
||||
|
@ -397,8 +495,14 @@ impl ListingTrait for ConversationsListing {
|
|||
self.new_cursor_pos.2 =
|
||||
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
|
||||
} else {
|
||||
self.content =
|
||||
CellBuffer::new_with_context(0, 0, Cell::with_char(' '), context);
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
self.content = CellBuffer::new_with_context(0, 0, default_cell, context);
|
||||
}
|
||||
self.redraw_list(
|
||||
context,
|
||||
|
@ -417,14 +521,21 @@ impl ListingTrait for ConversationsListing {
|
|||
format!("Failed to search for term {}: {}", self.filter_term, e),
|
||||
ERROR,
|
||||
);
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
self.content =
|
||||
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
|
||||
write_string_to_grid(
|
||||
&message,
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (message.len() - 1, 0)),
|
||||
None,
|
||||
);
|
||||
|
@ -444,19 +555,12 @@ impl fmt::Display for ConversationsListing {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for ConversationsListing {
|
||||
fn default() -> Self {
|
||||
ConversationsListing::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConversationsListing {
|
||||
const DESCRIPTION: &'static str = "compact listing";
|
||||
fn new() -> Self {
|
||||
pub fn new(coordinates: (usize, FolderHash)) -> Self {
|
||||
ConversationsListing {
|
||||
cursor_pos: (0, 1, 0),
|
||||
new_cursor_pos: (0, 0, 0),
|
||||
folder_hash: 0,
|
||||
new_cursor_pos: (coordinates.0, coordinates.1, 0),
|
||||
length: 0,
|
||||
sort: (Default::default(), Default::default()),
|
||||
subsort: (SortField::Date, SortOrder::Desc),
|
||||
|
@ -486,7 +590,7 @@ impl ConversationsListing {
|
|||
hash: ThreadHash,
|
||||
) -> EntryStrings {
|
||||
let thread = threads.thread_ref(hash);
|
||||
let folder = &context.accounts[self.cursor_pos.0].folder_confs[&self.folder_hash];
|
||||
let folder = &context.accounts[self.cursor_pos.0].folder_confs[&self.cursor_pos.1];
|
||||
let mut tags = String::new();
|
||||
let mut colors = SmallVec::new();
|
||||
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
|
||||
|
@ -550,99 +654,6 @@ impl ConversationsListing {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account folder the user has
|
||||
/// chosen.
|
||||
fn refresh_mailbox(&mut self, context: &mut Context) {
|
||||
self.dirty = true;
|
||||
let old_cursor_pos = self.cursor_pos;
|
||||
if !(self.cursor_pos.0 == self.new_cursor_pos.0
|
||||
&& self.cursor_pos.1 == self.new_cursor_pos.1)
|
||||
{
|
||||
self.cursor_pos.2 = 0;
|
||||
self.new_cursor_pos.2 = 0;
|
||||
}
|
||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||
self.folder_hash = if let Some(h) = context.accounts[self.cursor_pos.0]
|
||||
.folders_order
|
||||
.get(self.cursor_pos.1)
|
||||
{
|
||||
*h
|
||||
} else {
|
||||
self.cursor_pos.1 = old_cursor_pos.1;
|
||||
self.dirty = false;
|
||||
return;
|
||||
};
|
||||
|
||||
self.color_cache = ColorCache {
|
||||
theme_default: crate::conf::value(context, "mail.listing.conversations"),
|
||||
subject: crate::conf::value(context, "mail.listing.conversations.subject"),
|
||||
from: crate::conf::value(context, "mail.listing.conversations.from"),
|
||||
date: crate::conf::value(context, "mail.listing.conversations.date"),
|
||||
padding: crate::conf::value(context, "mail.listing.conversations.padding"),
|
||||
unseen: crate::conf::value(context, "mail.listing.conversations.unseen"),
|
||||
unseen_padding: crate::conf::value(
|
||||
context,
|
||||
"mail.listing.conversations.unseen_padding",
|
||||
),
|
||||
highlighted: crate::conf::value(context, "mail.listing.conversations.highlighted"),
|
||||
attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
|
||||
thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
|
||||
..self.color_cache
|
||||
};
|
||||
|
||||
if std::env::var("NO_COLOR").is_ok()
|
||||
&& (context.settings.terminal.use_color.is_false()
|
||||
|| context.settings.terminal.use_color.is_internal())
|
||||
{
|
||||
self.color_cache.highlighted.attrs |= Attr::Reverse;
|
||||
}
|
||||
// Get mailbox as a reference.
|
||||
//
|
||||
match context.accounts[self.cursor_pos.0].status(self.folder_hash) {
|
||||
Ok(()) => {}
|
||||
Err(_) => {
|
||||
let message: String =
|
||||
context.accounts[self.cursor_pos.0][self.folder_hash].to_string();
|
||||
self.content =
|
||||
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
|
||||
self.length = 0;
|
||||
write_string_to_grid(
|
||||
message.as_str(),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
((0, 0), (message.len() - 1, 0)),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let threads = &context.accounts[self.cursor_pos.0].collection.threads[&self.folder_hash];
|
||||
self.all_threads.clear();
|
||||
let mut roots = threads.roots();
|
||||
threads.group_inner_sort_by(
|
||||
&mut roots,
|
||||
self.sort,
|
||||
&context.accounts[self.cursor_pos.0].collection.envelopes,
|
||||
);
|
||||
|
||||
self.redraw_list(
|
||||
context,
|
||||
Box::new(roots.into_iter()) as Box<dyn Iterator<Item = ThreadHash>>,
|
||||
);
|
||||
|
||||
if old_cursor_pos == self.new_cursor_pos {
|
||||
self.view.update(context);
|
||||
} else if self.unfocused {
|
||||
let thread_group = self.get_thread_under_cursor(self.cursor_pos.2);
|
||||
|
||||
self.view = ThreadView::new(self.new_cursor_pos, thread_group, None, context);
|
||||
}
|
||||
}
|
||||
|
||||
fn redraw_list(&mut self, context: &Context, items: Box<dyn Iterator<Item = ThreadHash>>) {
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let mailbox = &account[self.cursor_pos.1].unwrap();
|
||||
|
@ -832,16 +843,22 @@ impl ConversationsListing {
|
|||
}
|
||||
}
|
||||
if self.length == 0 && self.filter_term.is_empty() {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
let mailbox = &account[self.cursor_pos.1];
|
||||
let message = mailbox.to_string();
|
||||
self.content =
|
||||
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
|
||||
self.content = CellBuffer::new_with_context(message.len(), 1, default_cell, context);
|
||||
write_string_to_grid(
|
||||
&message,
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (message.len() - 1, 0)),
|
||||
None,
|
||||
);
|
||||
|
@ -900,7 +917,7 @@ impl ConversationsListing {
|
|||
|
||||
fn update_line(&mut self, context: &Context, thread_hash: ThreadHash) {
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let threads = &account.collection.threads[&self.folder_hash];
|
||||
let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||
let thread = threads.thread_ref(thread_hash);
|
||||
let thread_node_hash = threads.thread_group_iter(thread_hash).next().unwrap().1;
|
||||
let idx: usize = self.order[&thread_hash];
|
||||
|
@ -1048,6 +1065,7 @@ impl Component for ConversationsListing {
|
|||
pos_inc(upper_left, (width!(area) / 3, 0)),
|
||||
set_x(bottom_right, get_x(upper_left) + width!(area) / 3 + 1),
|
||||
),
|
||||
self.color_cache.theme_default,
|
||||
);
|
||||
context.dirty_areas.push_back((
|
||||
pos_inc(upper_left, (width!(area) / 3, 0)),
|
||||
|
@ -1069,16 +1087,20 @@ impl Component for ConversationsListing {
|
|||
self.filter_term
|
||||
),
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
area,
|
||||
Some(get_x(upper_left)),
|
||||
);
|
||||
for c in grid.row_iter(x..(get_x(bottom_right) + 1), y) {
|
||||
grid[c] = Cell::default();
|
||||
}
|
||||
clear_area(grid, ((x, y), set_y(bottom_right, y)));
|
||||
clear_area(
|
||||
grid,
|
||||
((x, y), set_y(bottom_right, y)),
|
||||
self.color_cache.theme_default,
|
||||
);
|
||||
context
|
||||
.dirty_areas
|
||||
.push_back((upper_left, set_y(bottom_right, y + 1)));
|
||||
|
@ -1119,7 +1141,7 @@ impl Component for ConversationsListing {
|
|||
}
|
||||
if self.unfocused {
|
||||
if self.length == 0 && self.dirty {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
|
@ -1178,7 +1200,7 @@ impl Component for ConversationsListing {
|
|||
}
|
||||
UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => {
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let threads = &account.collection.threads[&self.folder_hash];
|
||||
let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||
if !account.collection.contains_key(&new_hash) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1203,10 +1225,10 @@ impl Component for ConversationsListing {
|
|||
self.subsort = (*field, *order);
|
||||
// FIXME subsort
|
||||
//if !self.filtered_selection.is_empty() {
|
||||
// let threads = &account.collection.threads[&self.folder_hash];
|
||||
// let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||
// threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort, &account.collection);
|
||||
//} else {
|
||||
// self.refresh_mailbox(context);
|
||||
// self.refresh_mailbox(context, false);
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
|
@ -1217,7 +1239,7 @@ impl Component for ConversationsListing {
|
|||
self.sort = (*field, *order);
|
||||
if !self.filtered_selection.is_empty() {
|
||||
let threads = &context.accounts[self.cursor_pos.0].collection.threads
|
||||
[&self.folder_hash];
|
||||
[&self.cursor_pos.1];
|
||||
threads.vec_inner_sort_by(
|
||||
&mut self.filtered_selection,
|
||||
self.sort,
|
||||
|
@ -1225,7 +1247,7 @@ impl Component for ConversationsListing {
|
|||
);
|
||||
self.dirty = true;
|
||||
} else {
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
|
@ -1236,13 +1258,13 @@ impl Component for ConversationsListing {
|
|||
account
|
||||
.collection
|
||||
.threads
|
||||
.entry(self.folder_hash)
|
||||
.entry(self.cursor_pos.1)
|
||||
.and_modify(|threads| {
|
||||
let is_snoozed = threads.thread_ref(thread).snoozed();
|
||||
threads.thread_ref_mut(thread).set_snoozed(!is_snoozed);
|
||||
});
|
||||
self.row_updates.push(thread);
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
|
@ -1252,13 +1274,13 @@ impl Component for ConversationsListing {
|
|||
}
|
||||
match *event {
|
||||
UIEvent::MailboxUpdate((ref idxa, ref idxf))
|
||||
if (*idxa, *idxf) == (self.new_cursor_pos.0, self.folder_hash) =>
|
||||
if (*idxa, *idxf) == (self.new_cursor_pos.0, self.cursor_pos.1) =>
|
||||
{
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::StartupCheck(ref f) if *f == self.folder_hash => {
|
||||
self.refresh_mailbox(context);
|
||||
UIEvent::StartupCheck(ref f) if *f == self.cursor_pos.1 => {
|
||||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::ChangeMode(UIMode::Normal) => {
|
||||
|
@ -1268,19 +1290,6 @@ impl Component for ConversationsListing {
|
|||
self.dirty = true;
|
||||
}
|
||||
UIEvent::Action(ref action) => match action {
|
||||
Action::ViewMailbox(idx) => {
|
||||
if context.accounts[self.cursor_pos.0]
|
||||
.folders_order
|
||||
.get(*idx)
|
||||
.is_none()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
self.set_coordinates((self.new_cursor_pos.0, *idx));
|
||||
self.refresh_mailbox(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
Action::Listing(Filter(ref filter_term)) if !self.unfocused => {
|
||||
self.filter(filter_term, context);
|
||||
self.dirty = true;
|
||||
|
@ -1292,7 +1301,7 @@ impl Component for ConversationsListing {
|
|||
if !self.unfocused && !&self.filter_term.is_empty() =>
|
||||
{
|
||||
self.set_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1));
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
self.force_draw = false;
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2020 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
use crate::components::utilities::PageMovement;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OfflineListing {
|
||||
cursor_pos: (usize, FolderHash),
|
||||
_row_updates: SmallVec<[ThreadHash; 8]>,
|
||||
|
||||
id: ComponentId,
|
||||
}
|
||||
|
||||
impl MailListingTrait for OfflineListing {
|
||||
fn row_updates(&mut self) -> &mut SmallVec<[ThreadHash; 8]> {
|
||||
&mut self._row_updates
|
||||
}
|
||||
|
||||
fn get_focused_items(&self, _context: &Context) -> SmallVec<[ThreadHash; 8]> {
|
||||
return SmallVec::new();
|
||||
}
|
||||
|
||||
/// chosen.
|
||||
fn refresh_mailbox(&mut self, _context: &mut Context, _force: bool) {}
|
||||
}
|
||||
|
||||
impl ListingTrait for OfflineListing {
|
||||
fn coordinates(&self) -> (usize, FolderHash) {
|
||||
self.cursor_pos
|
||||
}
|
||||
|
||||
fn set_coordinates(&mut self, coordinates: (usize, FolderHash)) {
|
||||
self.cursor_pos = coordinates;
|
||||
}
|
||||
|
||||
fn highlight_line(
|
||||
&mut self,
|
||||
_grid: &mut CellBuffer,
|
||||
_area: Area,
|
||||
_idx: usize,
|
||||
_context: &Context,
|
||||
) {
|
||||
}
|
||||
|
||||
fn draw_list(&mut self, _: &mut CellBuffer, _: Area, _: &mut Context) {}
|
||||
|
||||
fn filter(&mut self, _: &str, _: &Context) {}
|
||||
|
||||
fn set_movement(&mut self, _: PageMovement) {}
|
||||
}
|
||||
|
||||
impl fmt::Display for OfflineListing {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "mail")
|
||||
}
|
||||
}
|
||||
|
||||
impl OfflineListing {
|
||||
pub fn new(cursor_pos: (usize, FolderHash)) -> Self {
|
||||
OfflineListing {
|
||||
cursor_pos,
|
||||
_row_updates: SmallVec::new(),
|
||||
id: ComponentId::new_v4(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for OfflineListing {
|
||||
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
|
||||
fn process_event(&mut self, _event: &mut UIEvent, _context: &mut Context) -> bool {
|
||||
false
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn set_dirty(&mut self, _value: bool) {}
|
||||
|
||||
fn id(&self) -> ComponentId {
|
||||
self.id
|
||||
}
|
||||
fn set_id(&mut self, id: ComponentId) {
|
||||
self.id = id;
|
||||
}
|
||||
}
|
|
@ -48,9 +48,8 @@ macro_rules! address_list {
|
|||
#[derive(Debug)]
|
||||
pub struct PlainListing {
|
||||
/// (x, y, z): x is accounts, y is folders, z is index inside a folder.
|
||||
cursor_pos: (usize, usize, usize),
|
||||
new_cursor_pos: (usize, usize, usize),
|
||||
folder_hash: FolderHash,
|
||||
cursor_pos: (usize, FolderHash, usize),
|
||||
new_cursor_pos: (usize, FolderHash, usize),
|
||||
length: usize,
|
||||
sort: (SortField, SortOrder),
|
||||
subsort: (SortField, SortOrder),
|
||||
|
@ -101,16 +100,108 @@ impl MailListingTrait for PlainListing {
|
|||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account folder the user has
|
||||
/// chosen.
|
||||
fn refresh_mailbox(&mut self, context: &mut Context, force: bool) {
|
||||
self.dirty = true;
|
||||
let old_cursor_pos = self.cursor_pos;
|
||||
if !(self.cursor_pos.0 == self.new_cursor_pos.0
|
||||
&& self.cursor_pos.1 == self.new_cursor_pos.1)
|
||||
{
|
||||
self.cursor_pos.2 = 0;
|
||||
self.new_cursor_pos.2 = 0;
|
||||
}
|
||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||
|
||||
self.color_cache = ColorCache {
|
||||
unseen: crate::conf::value(context, "mail.listing.plain.unseen"),
|
||||
highlighted: crate::conf::value(context, "mail.listing.plain.highlighted"),
|
||||
even: crate::conf::value(context, "mail.listing.plain.even"),
|
||||
odd: crate::conf::value(context, "mail.listing.plain.odd"),
|
||||
selected: crate::conf::value(context, "mail.listing.plain.selected"),
|
||||
attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
|
||||
thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
|
||||
..self.color_cache
|
||||
};
|
||||
if std::env::var("NO_COLOR").is_ok()
|
||||
&& (context.settings.terminal.use_color.is_false()
|
||||
|| context.settings.terminal.use_color.is_internal())
|
||||
{
|
||||
self.color_cache.highlighted.attrs |= Attr::Reverse;
|
||||
}
|
||||
|
||||
// Get mailbox as a reference.
|
||||
//
|
||||
match context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) {
|
||||
Ok(()) => {}
|
||||
Err(_) => {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
let message: String =
|
||||
context.accounts[self.cursor_pos.0][self.cursor_pos.1].to_string();
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
|
||||
self.length = 0;
|
||||
write_string_to_grid(
|
||||
message.as_str(),
|
||||
&mut self.data_columns.columns[0],
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.local_collection = context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||
.unwrap()
|
||||
.envelopes
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let env_lck = context.accounts[self.cursor_pos.0]
|
||||
.collection
|
||||
.envelopes
|
||||
.read()
|
||||
.unwrap();
|
||||
self.thread_node_hashes = context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||
.unwrap()
|
||||
.envelopes
|
||||
.iter()
|
||||
.map(|h| (*h, env_lck[h].thread()))
|
||||
.collect();
|
||||
drop(env_lck);
|
||||
self.redraw_list(context);
|
||||
|
||||
if self.length > 0 {
|
||||
let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
|
||||
let temp = (self.new_cursor_pos.0, self.new_cursor_pos.1, env_hash);
|
||||
if !force && old_cursor_pos == self.new_cursor_pos {
|
||||
self.view.update(temp);
|
||||
} else if self.unfocused {
|
||||
self.view = MailView::new(temp, None, None, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ListingTrait for PlainListing {
|
||||
fn coordinates(&self) -> (usize, usize) {
|
||||
fn coordinates(&self) -> (usize, FolderHash) {
|
||||
(self.new_cursor_pos.0, self.new_cursor_pos.1)
|
||||
}
|
||||
|
||||
fn set_coordinates(&mut self, coordinates: (usize, usize)) {
|
||||
fn set_coordinates(&mut self, coordinates: (usize, FolderHash)) {
|
||||
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
|
||||
self.unfocused = false;
|
||||
self.view = MailView::default();
|
||||
self.filtered_selection.clear();
|
||||
self.filtered_order.clear();
|
||||
self.filter_term.clear();
|
||||
|
@ -189,12 +280,12 @@ impl ListingTrait for PlainListing {
|
|||
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0
|
||||
{
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
if self.length == 0 {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
copy_area(
|
||||
grid,
|
||||
&self.data_columns.columns[0],
|
||||
|
@ -205,6 +296,9 @@ impl ListingTrait for PlainListing {
|
|||
return;
|
||||
}
|
||||
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
|
||||
if rows == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(mvm) = self.movement.take() {
|
||||
match mvm {
|
||||
|
@ -307,7 +401,7 @@ impl ListingTrait for PlainListing {
|
|||
self.data_columns.widths[2] = min_col_width;
|
||||
}
|
||||
}
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
/* Page_no has changed, so draw new page */
|
||||
let mut x = get_x(upper_left);
|
||||
let mut flag_x = 0;
|
||||
|
@ -395,6 +489,7 @@ impl ListingTrait for PlainListing {
|
|||
pos_inc(upper_left, (0, self.length - top_idx)),
|
||||
bottom_right,
|
||||
),
|
||||
self.color_cache.theme_default,
|
||||
);
|
||||
}
|
||||
context.dirty_areas.push_back(area);
|
||||
|
@ -417,7 +512,7 @@ impl ListingTrait for PlainListing {
|
|||
}
|
||||
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
match account.search(&self.filter_term, self.sort, self.folder_hash) {
|
||||
match account.search(&self.filter_term, self.sort, self.cursor_pos.1) {
|
||||
Ok(results) => {
|
||||
for env_hash in results {
|
||||
if !account.collection.contains_key(&env_hash) {
|
||||
|
@ -436,8 +531,15 @@ impl ListingTrait for PlainListing {
|
|||
self.new_cursor_pos.2 =
|
||||
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
|
||||
} else {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(0, 0, Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(0, 0, default_cell, context);
|
||||
}
|
||||
self.redraw_list(context);
|
||||
}
|
||||
|
@ -452,14 +554,21 @@ impl ListingTrait for PlainListing {
|
|||
format!("Failed to search for term {}: {}", &self.filter_term, e),
|
||||
ERROR,
|
||||
);
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
|
||||
write_string_to_grid(
|
||||
&message,
|
||||
&mut self.data_columns.columns[0],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (message.len() - 1, 0)),
|
||||
None,
|
||||
);
|
||||
|
@ -479,19 +588,12 @@ impl fmt::Display for PlainListing {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for PlainListing {
|
||||
fn default() -> Self {
|
||||
PlainListing::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PlainListing {
|
||||
const DESCRIPTION: &'static str = "plain listing";
|
||||
fn new() -> Self {
|
||||
pub fn new(coordinates: (usize, FolderHash)) -> Self {
|
||||
PlainListing {
|
||||
cursor_pos: (0, 1, 0),
|
||||
new_cursor_pos: (0, 0, 0),
|
||||
folder_hash: 0,
|
||||
new_cursor_pos: (coordinates.0, coordinates.1, 0),
|
||||
length: 0,
|
||||
sort: (Default::default(), Default::default()),
|
||||
subsort: (SortField::Date, SortOrder::Desc),
|
||||
|
@ -517,7 +619,7 @@ impl PlainListing {
|
|||
}
|
||||
}
|
||||
fn make_entry_string(&self, e: EnvelopeRef, context: &Context) -> EntryStrings {
|
||||
let folder = &context.accounts[self.cursor_pos.0].folder_confs[&self.folder_hash];
|
||||
let folder = &context.accounts[self.cursor_pos.0].folder_confs[&self.cursor_pos.1];
|
||||
let mut tags = String::new();
|
||||
let mut colors = SmallVec::new();
|
||||
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
|
||||
|
@ -563,100 +665,6 @@ impl PlainListing {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fill the `self.data_columns` `CellBuffers` with the contents of the account folder the user has
|
||||
/// chosen.
|
||||
fn refresh_mailbox(&mut self, context: &mut Context) {
|
||||
self.dirty = true;
|
||||
let old_cursor_pos = self.cursor_pos;
|
||||
if !(self.cursor_pos.0 == self.new_cursor_pos.0
|
||||
&& self.cursor_pos.1 == self.new_cursor_pos.1)
|
||||
{
|
||||
self.cursor_pos.2 = 0;
|
||||
self.new_cursor_pos.2 = 0;
|
||||
}
|
||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||
self.folder_hash = if let Some(h) = context.accounts[self.cursor_pos.0]
|
||||
.folders_order
|
||||
.get(self.cursor_pos.1)
|
||||
{
|
||||
*h
|
||||
} else {
|
||||
self.cursor_pos.1 = old_cursor_pos.1;
|
||||
self.dirty = false;
|
||||
return;
|
||||
};
|
||||
|
||||
self.color_cache = ColorCache {
|
||||
unseen: crate::conf::value(context, "mail.listing.plain.unseen"),
|
||||
highlighted: crate::conf::value(context, "mail.listing.plain.highlighted"),
|
||||
even: crate::conf::value(context, "mail.listing.plain.even"),
|
||||
odd: crate::conf::value(context, "mail.listing.plain.odd"),
|
||||
selected: crate::conf::value(context, "mail.listing.plain.selected"),
|
||||
attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
|
||||
thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
|
||||
..self.color_cache
|
||||
};
|
||||
if std::env::var("NO_COLOR").is_ok()
|
||||
&& (context.settings.terminal.use_color.is_false()
|
||||
|| context.settings.terminal.use_color.is_internal())
|
||||
{
|
||||
self.color_cache.highlighted.attrs |= Attr::Reverse;
|
||||
}
|
||||
|
||||
// Get mailbox as a reference.
|
||||
//
|
||||
match context.accounts[self.cursor_pos.0].status(self.folder_hash) {
|
||||
Ok(()) => {}
|
||||
Err(_) => {
|
||||
let message: String =
|
||||
context.accounts[self.cursor_pos.0][self.folder_hash].to_string();
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context);
|
||||
self.length = 0;
|
||||
write_string_to_grid(
|
||||
message.as_str(),
|
||||
&mut self.data_columns.columns[0],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.local_collection = context.accounts[self.cursor_pos.0][self.folder_hash]
|
||||
.unwrap()
|
||||
.envelopes
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let env_lck = context.accounts[self.cursor_pos.0]
|
||||
.collection
|
||||
.envelopes
|
||||
.read()
|
||||
.unwrap();
|
||||
self.thread_node_hashes = context.accounts[self.cursor_pos.0][self.folder_hash]
|
||||
.unwrap()
|
||||
.envelopes
|
||||
.iter()
|
||||
.map(|h| (*h, env_lck[h].thread()))
|
||||
.collect();
|
||||
drop(env_lck);
|
||||
self.redraw_list(context);
|
||||
|
||||
if self.length > 0 {
|
||||
let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
|
||||
let temp = (self.new_cursor_pos.0, self.new_cursor_pos.1, env_hash);
|
||||
if old_cursor_pos == self.new_cursor_pos {
|
||||
self.view.update(temp);
|
||||
} else if self.unfocused {
|
||||
self.view = MailView::new(temp, None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn redraw_list(&mut self, context: &Context) {
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let mailbox = &account[self.cursor_pos.1].unwrap();
|
||||
|
@ -735,21 +743,28 @@ impl PlainListing {
|
|||
|
||||
min_width.0 = self.length.saturating_sub(1).to_string().len();
|
||||
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
/* index column */
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(min_width.0, rows.len(), Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.0, rows.len(), default_cell, context);
|
||||
/* date column */
|
||||
self.data_columns.columns[1] =
|
||||
CellBuffer::new_with_context(min_width.1, rows.len(), Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.1, rows.len(), default_cell, context);
|
||||
/* from column */
|
||||
self.data_columns.columns[2] =
|
||||
CellBuffer::new_with_context(min_width.2, rows.len(), Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.2, rows.len(), default_cell, context);
|
||||
/* flags column */
|
||||
self.data_columns.columns[3] =
|
||||
CellBuffer::new_with_context(min_width.3, rows.len(), Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.3, rows.len(), default_cell, context);
|
||||
/* subject column */
|
||||
self.data_columns.columns[4] =
|
||||
CellBuffer::new_with_context(min_width.4, rows.len(), Cell::with_char(' '), context);
|
||||
CellBuffer::new_with_context(min_width.4, rows.len(), default_cell, context);
|
||||
|
||||
let iter = if self.filter_term.is_empty() {
|
||||
Box::new(self.local_collection.iter().cloned())
|
||||
|
@ -774,82 +789,67 @@ impl PlainListing {
|
|||
}
|
||||
|
||||
let envelope: EnvelopeRef = context.accounts[self.cursor_pos.0].collection.get_env(i);
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
let row_attr = if !envelope.is_seen() {
|
||||
self.color_cache.unseen
|
||||
} else if idx % 2 == 0 {
|
||||
self.color_cache.even
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if context.settings.terminal.theme == "light" {
|
||||
if !envelope.is_seen() {
|
||||
Color::Byte(251)
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(252)
|
||||
} else {
|
||||
Color::Default
|
||||
}
|
||||
} else {
|
||||
if !envelope.is_seen() {
|
||||
Color::Byte(253)
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
}
|
||||
self.color_cache.odd
|
||||
};
|
||||
let (x, _) = write_string_to_grid(
|
||||
&idx.to_string(),
|
||||
&mut columns[0],
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
row_attr.fg,
|
||||
row_attr.bg,
|
||||
row_attr.attrs,
|
||||
((0, idx), (min_width.0, idx)),
|
||||
None,
|
||||
);
|
||||
for c in columns[0].row_iter(x..min_width.0, idx) {
|
||||
columns[0][c].set_bg(bg_color);
|
||||
columns[0][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
|
||||
}
|
||||
let (x, _) = write_string_to_grid(
|
||||
&strings.date,
|
||||
&mut columns[1],
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
row_attr.fg,
|
||||
row_attr.bg,
|
||||
row_attr.attrs,
|
||||
((0, idx), (min_width.1, idx)),
|
||||
None,
|
||||
);
|
||||
for c in columns[1].row_iter(x..min_width.1, idx) {
|
||||
columns[1][c].set_bg(bg_color);
|
||||
columns[1][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
|
||||
}
|
||||
let (x, _) = write_string_to_grid(
|
||||
&strings.from,
|
||||
&mut columns[2],
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
row_attr.fg,
|
||||
row_attr.bg,
|
||||
row_attr.attrs,
|
||||
((0, idx), (min_width.2, idx)),
|
||||
None,
|
||||
);
|
||||
for c in columns[2].row_iter(x..min_width.2, idx) {
|
||||
columns[2][c].set_bg(bg_color);
|
||||
columns[2][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
|
||||
}
|
||||
let (x, _) = write_string_to_grid(
|
||||
&strings.flag,
|
||||
&mut columns[3],
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
row_attr.fg,
|
||||
row_attr.bg,
|
||||
row_attr.attrs,
|
||||
((0, idx), (min_width.3, idx)),
|
||||
None,
|
||||
);
|
||||
for c in columns[3].row_iter(x..min_width.3, idx) {
|
||||
columns[3][c].set_bg(bg_color);
|
||||
columns[3][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
|
||||
}
|
||||
let (x, _) = write_string_to_grid(
|
||||
&strings.subject,
|
||||
&mut columns[4],
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
row_attr.fg,
|
||||
row_attr.bg,
|
||||
row_attr.attrs,
|
||||
((0, idx), (min_width.4, idx)),
|
||||
None,
|
||||
);
|
||||
|
@ -869,12 +869,10 @@ impl PlainListing {
|
|||
columns[4][c].set_bg(color);
|
||||
}
|
||||
for c in columns[4].row_iter(_x..(_x + 1), idx) {
|
||||
columns[4][c].set_bg(color);
|
||||
columns[4][c].set_keep_bg(true);
|
||||
columns[4][c].set_bg(color).set_keep_bg(true);
|
||||
}
|
||||
for c in columns[4].row_iter((x + 1)..(_x + 1), idx) {
|
||||
columns[4][c].set_keep_fg(true);
|
||||
columns[4][c].set_keep_bg(true);
|
||||
columns[4][c].set_keep_fg(true).set_keep_bg(true);
|
||||
}
|
||||
for c in columns[4].row_iter(x..(x + 1), idx) {
|
||||
columns[4][c].set_keep_bg(true);
|
||||
|
@ -884,7 +882,7 @@ impl PlainListing {
|
|||
x
|
||||
};
|
||||
for c in columns[4].row_iter(x..min_width.4, idx) {
|
||||
columns[4][c].set_bg(bg_color);
|
||||
columns[4][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
|
||||
}
|
||||
if context.accounts[self.cursor_pos.0]
|
||||
.collection
|
||||
|
@ -897,18 +895,14 @@ impl PlainListing {
|
|||
if self.length == 0 && self.filter_term.is_empty() {
|
||||
let mailbox = &account[self.cursor_pos.1];
|
||||
let message = mailbox.to_string();
|
||||
self.data_columns.columns[0] = CellBuffer::new_with_context(
|
||||
message.len(),
|
||||
self.length + 1,
|
||||
Cell::with_char(' '),
|
||||
context,
|
||||
);
|
||||
self.data_columns.columns[0] =
|
||||
CellBuffer::new_with_context(message.len(), self.length + 1, default_cell, context);
|
||||
write_string_to_grid(
|
||||
&message,
|
||||
&mut self.data_columns.columns[0],
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
None,
|
||||
);
|
||||
|
@ -991,7 +985,11 @@ impl Component for PlainListing {
|
|||
area,
|
||||
Some(get_x(upper_left)),
|
||||
);
|
||||
clear_area(grid, ((x, y), set_y(bottom_right, y)));
|
||||
clear_area(
|
||||
grid,
|
||||
((x, y), set_y(bottom_right, y)),
|
||||
self.color_cache.theme_default,
|
||||
);
|
||||
context
|
||||
.dirty_areas
|
||||
.push_back((upper_left, set_y(bottom_right, y + 1)));
|
||||
|
@ -1027,7 +1025,7 @@ impl Component for PlainListing {
|
|||
}
|
||||
} else {
|
||||
if self.length == 0 && self.dirty {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
|
@ -1050,7 +1048,7 @@ impl Component for PlainListing {
|
|||
{
|
||||
let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
|
||||
let temp = (self.cursor_pos.0, self.cursor_pos.1, env_hash);
|
||||
self.view = MailView::new(temp, None, None);
|
||||
self.view = MailView::new(temp, None, None, context);
|
||||
self.unfocused = true;
|
||||
self.dirty = true;
|
||||
return true;
|
||||
|
@ -1081,10 +1079,10 @@ impl Component for PlainListing {
|
|||
debug!("SubSort {:?} , {:?}", field, order);
|
||||
self.subsort = (*field, *order);
|
||||
//if !self.filtered_selection.is_empty() {
|
||||
// let threads = &account.collection.threads[&self.folder_hash];
|
||||
// let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||
// threads.vec_inner_sort_by(&mut self.filtered_selection, self.sort, &account.collection);
|
||||
//} else {
|
||||
// self.refresh_mailbox(context);
|
||||
// self.refresh_mailbox(contex, falset);
|
||||
//}
|
||||
return true;
|
||||
}
|
||||
|
@ -1132,19 +1130,19 @@ impl Component for PlainListing {
|
|||
}
|
||||
match *event {
|
||||
UIEvent::MailboxUpdate((ref idxa, ref idxf))
|
||||
if (*idxa, *idxf) == (self.new_cursor_pos.0, self.folder_hash) =>
|
||||
if (*idxa, *idxf) == (self.new_cursor_pos.0, self.cursor_pos.1) =>
|
||||
{
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::StartupCheck(ref f) if *f == self.folder_hash => {
|
||||
self.refresh_mailbox(context);
|
||||
UIEvent::StartupCheck(ref f) if *f == self.cursor_pos.1 => {
|
||||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => {
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
if !account.collection.contains_key(new_hash)
|
||||
|| !account[self.folder_hash]
|
||||
|| !account[self.cursor_pos.1]
|
||||
.unwrap()
|
||||
.envelopes
|
||||
.contains(new_hash)
|
||||
|
@ -1179,29 +1177,13 @@ impl Component for PlainListing {
|
|||
UIEvent::Input(Key::Esc) if !self.unfocused && !self.filter_term.is_empty() => {
|
||||
self.set_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1));
|
||||
self.set_dirty(true);
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
return true;
|
||||
}
|
||||
UIEvent::Action(ref action) => match action {
|
||||
Action::ViewMailbox(idx) => {
|
||||
if context.accounts[self.cursor_pos.0]
|
||||
.folders_order
|
||||
.get(*idx)
|
||||
.is_none()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
self.filtered_selection.clear();
|
||||
self.new_cursor_pos.1 = *idx;
|
||||
self.refresh_mailbox(context);
|
||||
return true;
|
||||
}
|
||||
Action::Listing(Filter(ref filter_term)) if !self.unfocused => {
|
||||
self.filter(filter_term, context);
|
||||
self.dirty = true;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
UIEvent::Action(Action::Listing(Filter(ref filter_term))) if !self.unfocused => {
|
||||
self.filter(filter_term, context);
|
||||
self.dirty = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
false
|
||||
|
|
|
@ -29,14 +29,14 @@ const MAX_COLS: usize = 500;
|
|||
#[derive(Debug)]
|
||||
pub struct ThreadListing {
|
||||
/// (x, y, z): x is accounts, y is folders, z is index inside a folder.
|
||||
cursor_pos: (usize, usize, usize),
|
||||
new_cursor_pos: (usize, usize, usize),
|
||||
folder_hash: FolderHash,
|
||||
cursor_pos: (usize, FolderHash, usize),
|
||||
new_cursor_pos: (usize, FolderHash, usize),
|
||||
length: usize,
|
||||
sort: (SortField, SortOrder),
|
||||
subsort: (SortField, SortOrder),
|
||||
/// Cache current view.
|
||||
content: CellBuffer,
|
||||
color_cache: ColorCache,
|
||||
|
||||
row_updates: SmallVec<[ThreadHash; 8]>,
|
||||
locations: Vec<EnvelopeHash>,
|
||||
|
@ -58,15 +58,180 @@ impl MailListingTrait for ThreadListing {
|
|||
fn get_focused_items(&self, _context: &Context) -> SmallVec<[ThreadHash; 8]> {
|
||||
SmallVec::new()
|
||||
}
|
||||
|
||||
/// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
|
||||
/// chosen.
|
||||
fn refresh_mailbox(&mut self, context: &mut Context, _force: bool) {
|
||||
self.dirty = true;
|
||||
if !(self.cursor_pos.0 == self.new_cursor_pos.0
|
||||
&& self.cursor_pos.1 == self.new_cursor_pos.1)
|
||||
{
|
||||
self.cursor_pos.2 = 0;
|
||||
self.new_cursor_pos.2 = 0;
|
||||
}
|
||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||
|
||||
self.color_cache = ColorCache {
|
||||
unseen: crate::conf::value(context, "mail.listing.plain.unseen"),
|
||||
highlighted: crate::conf::value(context, "mail.listing.plain.highlighted"),
|
||||
even: crate::conf::value(context, "mail.listing.plain.even"),
|
||||
odd: crate::conf::value(context, "mail.listing.plain.odd"),
|
||||
selected: crate::conf::value(context, "mail.listing.plain.selected"),
|
||||
attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
|
||||
thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
|
||||
..self.color_cache
|
||||
};
|
||||
if std::env::var("NO_COLOR").is_ok()
|
||||
&& (context.settings.terminal.use_color.is_false()
|
||||
|| context.settings.terminal.use_color.is_internal())
|
||||
{
|
||||
self.color_cache.highlighted.attrs |= Attr::Reverse;
|
||||
}
|
||||
|
||||
// Get mailbox as a reference.
|
||||
//
|
||||
match context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
let message: String =
|
||||
context.accounts[self.cursor_pos.0][self.cursor_pos.1].to_string();
|
||||
self.content =
|
||||
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
|
||||
self.length = 0;
|
||||
write_string_to_grid(
|
||||
message.as_str(),
|
||||
&mut self.content,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let mailbox = account[self.cursor_pos.1].unwrap();
|
||||
|
||||
let threads = &account.collection.threads[&mailbox.folder.hash()];
|
||||
self.length = threads.len();
|
||||
self.locations.clear();
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.color_cache.theme_default.fg)
|
||||
.set_bg(self.color_cache.theme_default.bg)
|
||||
.set_attrs(self.color_cache.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
if self.length == 0 {
|
||||
let message = format!("Folder `{}` is empty.", mailbox.folder.name());
|
||||
self.content = CellBuffer::new_with_context(message.len(), 1, default_cell, context);
|
||||
write_string_to_grid(
|
||||
&message,
|
||||
&mut self.content,
|
||||
self.color_cache.theme_default.fg,
|
||||
self.color_cache.theme_default.bg,
|
||||
self.color_cache.theme_default.attrs,
|
||||
((0, 0), (message.len() - 1, 0)),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.content =
|
||||
CellBuffer::new_with_context(MAX_COLS, self.length + 1, default_cell, context);
|
||||
|
||||
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||
let mut thread_idx = 0; // needed for alternate thread colors
|
||||
/* Draw threaded view. */
|
||||
threads.sort_by(self.sort, self.subsort, &account.collection.envelopes);
|
||||
let thread_nodes: &FnvHashMap<ThreadNodeHash, ThreadNode> = &threads.thread_nodes();
|
||||
let mut iter = threads.threads_iter().peekable();
|
||||
/* This is just a desugared for loop so that we can use .peek() */
|
||||
let mut idx = 0;
|
||||
while let Some((indentation, thread_node_hash, has_sibling)) = iter.next() {
|
||||
let thread_node = &thread_nodes[&thread_node_hash];
|
||||
|
||||
if indentation == 0 {
|
||||
thread_idx += 1;
|
||||
}
|
||||
if thread_node.has_message() {
|
||||
let envelope: EnvelopeRef =
|
||||
account.collection.get_env(thread_node.message().unwrap());
|
||||
self.locations.push(envelope.hash());
|
||||
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 thread_idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let (x, _) = write_string_to_grid(
|
||||
&ThreadListing::make_thread_entry(
|
||||
&envelope,
|
||||
idx,
|
||||
indentation,
|
||||
thread_node_hash,
|
||||
threads,
|
||||
&indentations,
|
||||
has_sibling,
|
||||
),
|
||||
&mut self.content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
((0, idx), (MAX_COLS - 1, idx)),
|
||||
None,
|
||||
);
|
||||
|
||||
for x in x..MAX_COLS {
|
||||
self.content[(x, idx)].set_ch(' ');
|
||||
self.content[(x, idx)].set_bg(bg_color);
|
||||
}
|
||||
idx += 1;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
match iter.peek() {
|
||||
Some((x, _, _)) if *x > indentation => {
|
||||
if has_sibling {
|
||||
indentations.push(true);
|
||||
} else {
|
||||
indentations.push(false);
|
||||
}
|
||||
}
|
||||
Some((x, _, _)) if *x < indentation => {
|
||||
for _ in 0..(indentation - *x) {
|
||||
indentations.pop();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ListingTrait for ThreadListing {
|
||||
fn coordinates(&self) -> (usize, usize) {
|
||||
fn coordinates(&self) -> (usize, FolderHash) {
|
||||
(self.new_cursor_pos.0, self.new_cursor_pos.1)
|
||||
}
|
||||
fn set_coordinates(&mut self, coordinates: (usize, usize)) {
|
||||
fn set_coordinates(&mut self, coordinates: (usize, FolderHash)) {
|
||||
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
|
||||
self.unfocused = false;
|
||||
self.view = None;
|
||||
self.locations.clear();
|
||||
self.row_updates.clear();
|
||||
self.initialised = false;
|
||||
|
@ -74,17 +239,20 @@ impl ListingTrait for ThreadListing {
|
|||
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0
|
||||
{
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
if self.length == 0 {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0)));
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
|
||||
if rows == 0 {
|
||||
return;
|
||||
}
|
||||
if let Some(mvm) = self.movement.take() {
|
||||
match mvm {
|
||||
PageMovement::Up(amount) => {
|
||||
|
@ -221,12 +389,6 @@ impl ListingTrait for ThreadListing {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for ThreadListing {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ThreadListing {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "mail")
|
||||
|
@ -234,16 +396,15 @@ impl fmt::Display for ThreadListing {
|
|||
}
|
||||
|
||||
impl ThreadListing {
|
||||
pub fn new() -> Self {
|
||||
let content = CellBuffer::new(0, 0, Cell::with_char(' '));
|
||||
pub fn new(coordinates: (usize, FolderHash)) -> Self {
|
||||
ThreadListing {
|
||||
cursor_pos: (0, 1, 0),
|
||||
new_cursor_pos: (0, 0, 0),
|
||||
folder_hash: 0,
|
||||
new_cursor_pos: (coordinates.0, coordinates.1, 0),
|
||||
length: 0,
|
||||
sort: (Default::default(), Default::default()),
|
||||
subsort: (Default::default(), Default::default()),
|
||||
content,
|
||||
content: CellBuffer::new(0, 0, Cell::with_char(' ')),
|
||||
color_cache: ColorCache::default(),
|
||||
row_updates: SmallVec::new(),
|
||||
locations: Vec::new(),
|
||||
dirty: true,
|
||||
|
@ -254,144 +415,6 @@ impl ThreadListing {
|
|||
id: ComponentId::new_v4(),
|
||||
}
|
||||
}
|
||||
/// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
|
||||
/// chosen.
|
||||
fn refresh_mailbox(&mut self, context: &mut Context) {
|
||||
self.dirty = true;
|
||||
if !(self.cursor_pos.0 == self.new_cursor_pos.0
|
||||
&& self.cursor_pos.1 == self.new_cursor_pos.1)
|
||||
{
|
||||
self.cursor_pos.2 = 0;
|
||||
self.new_cursor_pos.2 = 0;
|
||||
}
|
||||
self.cursor_pos.1 = self.new_cursor_pos.1;
|
||||
self.cursor_pos.0 = self.new_cursor_pos.0;
|
||||
self.folder_hash = if let Some(h) = context.accounts[self.cursor_pos.0]
|
||||
.folders_order
|
||||
.get(self.cursor_pos.1)
|
||||
{
|
||||
*h
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Get mailbox as a reference.
|
||||
//
|
||||
match context.accounts[self.cursor_pos.0].status(self.folder_hash) {
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
let message: String =
|
||||
context.accounts[self.cursor_pos.0][self.folder_hash].to_string();
|
||||
self.content = CellBuffer::new(message.len(), 1, Cell::with_char(' '));
|
||||
self.length = 0;
|
||||
write_string_to_grid(
|
||||
message.as_str(),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
((0, 0), (MAX_COLS - 1, 0)),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let mailbox = account[self.cursor_pos.1].unwrap();
|
||||
|
||||
let threads = &account.collection.threads[&mailbox.folder.hash()];
|
||||
self.length = threads.len();
|
||||
self.locations.clear();
|
||||
if self.length == 0 {
|
||||
let message = format!("Folder `{}` is empty.", mailbox.folder.name());
|
||||
self.content = CellBuffer::new(message.len(), 1, Cell::with_char(' '));
|
||||
write_string_to_grid(
|
||||
&message,
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
((0, 0), (message.len() - 1, 0)),
|
||||
None,
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||
|
||||
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||
let mut thread_idx = 0; // needed for alternate thread colors
|
||||
/* Draw threaded view. */
|
||||
threads.sort_by(self.sort, self.subsort, &account.collection.envelopes);
|
||||
let thread_nodes: &FnvHashMap<ThreadNodeHash, ThreadNode> = &threads.thread_nodes();
|
||||
let mut iter = threads.threads_iter().peekable();
|
||||
/* This is just a desugared for loop so that we can use .peek() */
|
||||
let mut idx = 0;
|
||||
while let Some((indentation, thread_node_hash, has_sibling)) = iter.next() {
|
||||
let thread_node = &thread_nodes[&thread_node_hash];
|
||||
|
||||
if indentation == 0 {
|
||||
thread_idx += 1;
|
||||
}
|
||||
if thread_node.has_message() {
|
||||
let envelope: EnvelopeRef =
|
||||
account.collection.get_env(thread_node.message().unwrap());
|
||||
self.locations.push(envelope.hash());
|
||||
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 thread_idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let (x, _) = write_string_to_grid(
|
||||
&ThreadListing::make_thread_entry(
|
||||
&envelope,
|
||||
idx,
|
||||
indentation,
|
||||
thread_node_hash,
|
||||
threads,
|
||||
&indentations,
|
||||
has_sibling,
|
||||
),
|
||||
&mut self.content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
Attr::Default,
|
||||
((0, idx), (MAX_COLS - 1, idx)),
|
||||
None,
|
||||
);
|
||||
|
||||
for x in x..MAX_COLS {
|
||||
self.content[(x, idx)].set_ch(' ');
|
||||
self.content[(x, idx)].set_bg(bg_color);
|
||||
}
|
||||
idx += 1;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
match iter.peek() {
|
||||
Some((x, _, _)) if *x > indentation => {
|
||||
if has_sibling {
|
||||
indentations.push(true);
|
||||
} else {
|
||||
indentations.push(false);
|
||||
}
|
||||
}
|
||||
Some((x, _, _)) if *x < indentation => {
|
||||
for _ in 0..(indentation - *x) {
|
||||
indentations.pop();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn highlight_line_self(&mut self, idx: usize, context: &Context) {
|
||||
let mailbox = if context.accounts[self.cursor_pos.0][self.cursor_pos.1].is_available() {
|
||||
|
@ -505,7 +528,7 @@ impl Component for ThreadListing {
|
|||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
if self.length == 0 && self.dirty {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
||||
|
@ -515,7 +538,7 @@ impl Component for ThreadListing {
|
|||
let bottom_entity_rows = (pager_ratio * total_rows) / 100;
|
||||
|
||||
if bottom_entity_rows > total_rows {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.color_cache.theme_default);
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
|
@ -591,7 +614,7 @@ impl Component for ThreadListing {
|
|||
if let Some(ref mut v) = self.view {
|
||||
v.update(coordinates);
|
||||
} else {
|
||||
self.view = Some(MailView::new(coordinates, None, None));
|
||||
self.view = Some(MailView::new(coordinates, None, None, context));
|
||||
}
|
||||
|
||||
self.view.as_mut().unwrap().draw(
|
||||
|
@ -622,13 +645,13 @@ impl Component for ThreadListing {
|
|||
return true;
|
||||
}
|
||||
UIEvent::MailboxUpdate((ref idxa, ref idxf))
|
||||
if (*idxa, *idxf) == (self.new_cursor_pos.0, self.folder_hash) =>
|
||||
if (*idxa, *idxf) == (self.new_cursor_pos.0, self.cursor_pos.1) =>
|
||||
{
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::StartupCheck(ref f) if *f == self.folder_hash => {
|
||||
self.refresh_mailbox(context);
|
||||
UIEvent::StartupCheck(ref f) if *f == self.cursor_pos.1 => {
|
||||
self.refresh_mailbox(context, false);
|
||||
self.set_dirty(true);
|
||||
}
|
||||
UIEvent::ChangeMode(UIMode::Normal) => {
|
||||
|
@ -638,24 +661,18 @@ impl Component for ThreadListing {
|
|||
self.dirty = true;
|
||||
}
|
||||
UIEvent::Action(ref action) => match action {
|
||||
Action::ViewMailbox(idx_m) => {
|
||||
self.new_cursor_pos.1 = *idx_m;
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
return true;
|
||||
}
|
||||
Action::SubSort(field, order) => {
|
||||
debug!("SubSort {:?} , {:?}", field, order);
|
||||
self.subsort = (*field, *order);
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
return true;
|
||||
}
|
||||
Action::Sort(field, order) => {
|
||||
debug!("Sort {:?} , {:?}", field, order);
|
||||
self.sort = (*field, *order);
|
||||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
self.refresh_mailbox(context, false);
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
@ -29,6 +29,7 @@ pub struct StatusPanel {
|
|||
status: Option<AccountStatus>,
|
||||
content: CellBuffer,
|
||||
dirty: bool,
|
||||
theme_default: ThemeAttribute,
|
||||
id: ComponentId,
|
||||
}
|
||||
|
||||
|
@ -50,8 +51,8 @@ impl Component for StatusPanel {
|
|||
let (_, y) = write_string_to_grid(
|
||||
"Worker threads",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
Attr::Bold,
|
||||
((1, 1), (width - 1, height - 1)),
|
||||
Some(1),
|
||||
|
@ -73,9 +74,9 @@ impl Component for StatusPanel {
|
|||
max_name = max_name
|
||||
),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((1, y), (width - 1, height - 1)),
|
||||
Some(1),
|
||||
);
|
||||
|
@ -88,8 +89,8 @@ impl Component for StatusPanel {
|
|||
write_string_to_grid(
|
||||
"Static threads",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
Attr::Bold,
|
||||
((1, y + 1), (width - 1, height - 1)),
|
||||
Some(1),
|
||||
|
@ -113,9 +114,9 @@ impl Component for StatusPanel {
|
|||
max_name = max_name
|
||||
),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((1, y), (width - 1, height - 1)),
|
||||
Some(1),
|
||||
);
|
||||
|
@ -131,7 +132,7 @@ impl Component for StatusPanel {
|
|||
std::cmp::min(width.saturating_sub(cols), self.cursor.0),
|
||||
std::cmp::min(height.saturating_sub(rows), self.cursor.1),
|
||||
);
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.theme_default);
|
||||
copy_area(
|
||||
grid,
|
||||
&self.content,
|
||||
|
@ -170,7 +171,7 @@ impl Component for StatusPanel {
|
|||
return true;
|
||||
}
|
||||
UIEvent::Input(Key::Char('\n')) if self.status.is_none() => {
|
||||
self.status = Some(AccountStatus::new(self.account_cursor));
|
||||
self.status = Some(AccountStatus::new(self.account_cursor, self.theme_default));
|
||||
return true;
|
||||
}
|
||||
UIEvent::Input(Key::Esc) if self.status.is_some() => {
|
||||
|
@ -224,8 +225,15 @@ impl Component for StatusPanel {
|
|||
}
|
||||
|
||||
impl StatusPanel {
|
||||
pub fn new() -> StatusPanel {
|
||||
let mut content = CellBuffer::new(120, 40, Cell::default());
|
||||
pub fn new(theme_default: ThemeAttribute) -> StatusPanel {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(theme_default.fg)
|
||||
.set_bg(theme_default.bg)
|
||||
.set_attrs(theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
let mut content = CellBuffer::new(120, 40, default_cell);
|
||||
content.set_growable(true);
|
||||
|
||||
StatusPanel {
|
||||
|
@ -234,18 +242,26 @@ impl StatusPanel {
|
|||
content,
|
||||
status: None,
|
||||
dirty: true,
|
||||
theme_default,
|
||||
id: ComponentId::new_v4(),
|
||||
}
|
||||
}
|
||||
fn draw_accounts(&mut self, context: &Context) {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(self.theme_default.fg)
|
||||
.set_bg(self.theme_default.bg)
|
||||
.set_attrs(self.theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
self.content
|
||||
.resize(120, 40 + context.accounts.len() * 20, Cell::default());
|
||||
.resize(120, 40 + context.accounts.len() * 20, default_cell);
|
||||
write_string_to_grid(
|
||||
"Accounts",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((2, 10), (120 - 1, 10)),
|
||||
Some(2),
|
||||
);
|
||||
|
@ -258,8 +274,8 @@ impl StatusPanel {
|
|||
let (x, y) = write_string_to_grid(
|
||||
a.name(),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
Attr::Bold,
|
||||
((3, 12 + i * 10), (120 - 2, 12 + i * 10)),
|
||||
Some(3),
|
||||
|
@ -268,17 +284,17 @@ impl StatusPanel {
|
|||
" ▒██▒ ",
|
||||
&mut self.content,
|
||||
Color::Byte(32),
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((x, y), (120 - 2, 12 + i * 10)),
|
||||
None,
|
||||
);
|
||||
write_string_to_grid(
|
||||
&a.runtime_settings.account().identity,
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((4, y + 2), (120 - 2, y + 2)),
|
||||
None,
|
||||
);
|
||||
|
@ -298,9 +314,9 @@ impl StatusPanel {
|
|||
let (mut column_width, _) = write_string_to_grid(
|
||||
&format!("Messages total {}, unseen {}", count.1, count.0),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((5, y + 3), (120 - 2, y + 3)),
|
||||
None,
|
||||
);
|
||||
|
@ -309,9 +325,9 @@ impl StatusPanel {
|
|||
write_string_to_grid(
|
||||
&format!("Contacts total {}", a.address_book.len()),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((5, y + 4), (120 - 2, y + 4)),
|
||||
None,
|
||||
)
|
||||
|
@ -322,9 +338,9 @@ impl StatusPanel {
|
|||
write_string_to_grid(
|
||||
&format!("Backend {}", a.settings.account().format()),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((5, y + 5), (120 - 2, y + 5)),
|
||||
None,
|
||||
)
|
||||
|
@ -334,9 +350,9 @@ impl StatusPanel {
|
|||
write_string_to_grid(
|
||||
"Special Mailboxes:",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
Attr::Bold,
|
||||
((5 + column_width, y + 2), (120 - 2, y + 2)),
|
||||
None,
|
||||
);
|
||||
|
@ -349,9 +365,9 @@ impl StatusPanel {
|
|||
write_string_to_grid(
|
||||
&format!("{}: {}", f.path(), f.special_usage()),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((5 + column_width, y + 3 + i), (120 - 2, y + 2)),
|
||||
None,
|
||||
);
|
||||
|
@ -372,9 +388,9 @@ impl Component for AccountStatus {
|
|||
let (_x, _y) = write_string_to_grid(
|
||||
"(Press Esc to return)",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
Attr::Bold,
|
||||
((1, 0), (width - 1, height - 1)),
|
||||
None,
|
||||
);
|
||||
|
@ -383,8 +399,8 @@ impl Component for AccountStatus {
|
|||
let (_x, _y) = write_string_to_grid(
|
||||
"Tag support: ",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
Attr::Bold,
|
||||
((1, line), (width - 1, height - 1)),
|
||||
None,
|
||||
|
@ -396,9 +412,9 @@ impl Component for AccountStatus {
|
|||
"no"
|
||||
},
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((_x, _y), (width - 1, height - 1)),
|
||||
None,
|
||||
);
|
||||
|
@ -406,8 +422,8 @@ impl Component for AccountStatus {
|
|||
let (_x, _y) = write_string_to_grid(
|
||||
"Cache backend: ",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
Attr::Bold,
|
||||
((1, line), (width - 1, height - 1)),
|
||||
None,
|
||||
|
@ -440,9 +456,9 @@ impl Component for AccountStatus {
|
|||
}
|
||||
},
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((_x, _y), (width - 1, height - 1)),
|
||||
None,
|
||||
);
|
||||
|
@ -451,8 +467,8 @@ impl Component for AccountStatus {
|
|||
write_string_to_grid(
|
||||
"Special Mailboxes:",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
Attr::Bold,
|
||||
((1, line), (width - 1, height - 1)),
|
||||
None,
|
||||
|
@ -466,9 +482,9 @@ impl Component for AccountStatus {
|
|||
write_string_to_grid(
|
||||
&format!("{}: {}", f.path(), f.special_usage()),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((1, line), (width - 1, height - 1)),
|
||||
None,
|
||||
);
|
||||
|
@ -477,21 +493,22 @@ impl Component for AccountStatus {
|
|||
write_string_to_grid(
|
||||
"Subscribed folders:",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
Attr::Bold,
|
||||
((1, line), (width - 1, height - 1)),
|
||||
None,
|
||||
);
|
||||
line += 2;
|
||||
for f in a.list_folders() {
|
||||
for folder_node in a.list_folders() {
|
||||
let f: &Folder = &a.ref_folders()[&folder_node.hash];
|
||||
if f.is_subscribed() {
|
||||
write_string_to_grid(
|
||||
f.path(),
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((1, line), (width - 1, height - 1)),
|
||||
None,
|
||||
);
|
||||
|
@ -506,8 +523,8 @@ impl Component for AccountStatus {
|
|||
write_string_to_grid(
|
||||
"Server Capabilities:",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
Attr::Bold,
|
||||
((1, line), (width - 1, height - 1)),
|
||||
None,
|
||||
|
@ -520,9 +537,9 @@ impl Component for AccountStatus {
|
|||
write_string_to_grid(
|
||||
"meli support:",
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((max_name_width + 6, line), (width - 1, height - 1)),
|
||||
None,
|
||||
);
|
||||
|
@ -533,9 +550,9 @@ impl Component for AccountStatus {
|
|||
write_string_to_grid(
|
||||
&cap,
|
||||
&mut self.content,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.fg,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((1, line + i), (width - 1, height - 1)),
|
||||
None,
|
||||
);
|
||||
|
@ -549,8 +566,8 @@ impl Component for AccountStatus {
|
|||
"supported",
|
||||
&mut self.content,
|
||||
Color::Green,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((max_name_width + 6, line + i), (width - 1, height - 1)),
|
||||
None,
|
||||
);
|
||||
|
@ -559,8 +576,8 @@ impl Component for AccountStatus {
|
|||
"not supported",
|
||||
&mut self.content,
|
||||
Color::Red,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
self.theme_default.bg,
|
||||
self.theme_default.attrs,
|
||||
((max_name_width + 6, line + i), (width - 1, height - 1)),
|
||||
None,
|
||||
);
|
||||
|
@ -577,7 +594,7 @@ impl Component for AccountStatus {
|
|||
std::cmp::min(width.saturating_sub(cols), self.cursor.0),
|
||||
std::cmp::min(height.saturating_sub(rows), self.cursor.1),
|
||||
);
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.theme_default);
|
||||
copy_area(
|
||||
grid,
|
||||
&self.content,
|
||||
|
@ -640,8 +657,15 @@ impl Component for AccountStatus {
|
|||
}
|
||||
|
||||
impl AccountStatus {
|
||||
pub fn new(account_pos: usize) -> AccountStatus {
|
||||
let mut content = CellBuffer::new(120, 5, Cell::default());
|
||||
pub fn new(account_pos: usize, theme_default: ThemeAttribute) -> AccountStatus {
|
||||
let default_cell = {
|
||||
let mut ret = Cell::with_char(' ');
|
||||
ret.set_fg(theme_default.fg)
|
||||
.set_bg(theme_default.bg)
|
||||
.set_attrs(theme_default.attrs);
|
||||
ret
|
||||
};
|
||||
let mut content = CellBuffer::new(120, 5, default_cell);
|
||||
content.set_growable(true);
|
||||
|
||||
AccountStatus {
|
||||
|
@ -649,6 +673,7 @@ impl AccountStatus {
|
|||
account_pos,
|
||||
content,
|
||||
dirty: true,
|
||||
theme_default,
|
||||
id: ComponentId::new_v4(),
|
||||
}
|
||||
}
|
||||
|
@ -660,6 +685,7 @@ struct AccountStatus {
|
|||
account_pos: usize,
|
||||
content: CellBuffer,
|
||||
dirty: bool,
|
||||
theme_default: ThemeAttribute,
|
||||
id: ComponentId,
|
||||
}
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ impl ViewMode {
|
|||
/// menus
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MailView {
|
||||
coordinates: (usize, usize, EnvelopeHash),
|
||||
coordinates: (usize, FolderHash, EnvelopeHash),
|
||||
pager: Pager,
|
||||
subview: Option<Box<dyn Component>>,
|
||||
dirty: bool,
|
||||
|
@ -89,6 +89,7 @@ pub struct MailView {
|
|||
headers_no: usize,
|
||||
headers_cursor: usize,
|
||||
force_draw_headers: bool,
|
||||
theme_default: ThemeAttribute,
|
||||
|
||||
cmd_buf: String,
|
||||
id: ComponentId,
|
||||
|
@ -116,9 +117,10 @@ impl fmt::Display for MailView {
|
|||
impl MailView {
|
||||
const DESCRIPTION: &'static str = "view mail";
|
||||
pub fn new(
|
||||
coordinates: (usize, usize, EnvelopeHash),
|
||||
coordinates: (usize, FolderHash, EnvelopeHash),
|
||||
pager: Option<Pager>,
|
||||
subview: Option<Box<dyn Component>>,
|
||||
context: &Context,
|
||||
) -> Self {
|
||||
MailView {
|
||||
coordinates,
|
||||
|
@ -132,6 +134,8 @@ impl MailView {
|
|||
headers_cursor: 0,
|
||||
force_draw_headers: false,
|
||||
|
||||
theme_default: crate::conf::value(context, "mail.view.body"),
|
||||
|
||||
cmd_buf: String::with_capacity(4),
|
||||
id: ComponentId::new_v4(),
|
||||
}
|
||||
|
@ -249,8 +253,7 @@ impl MailView {
|
|||
} else {
|
||||
s.push(' ');
|
||||
}
|
||||
s.push('\\');
|
||||
s.push(' ');
|
||||
s.push_str("\\_ ");
|
||||
} else {
|
||||
if has_sibling {
|
||||
s.push('|');
|
||||
|
@ -328,7 +331,7 @@ impl MailView {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, new_coordinates: (usize, usize, EnvelopeHash)) {
|
||||
pub fn update(&mut self, new_coordinates: (usize, FolderHash, EnvelopeHash)) {
|
||||
self.coordinates = new_coordinates;
|
||||
self.mode = ViewMode::Normal;
|
||||
self.set_dirty(true);
|
||||
|
@ -374,7 +377,7 @@ impl Component for MailView {
|
|||
let headers = crate::conf::value(context, "mail.view.headers");
|
||||
|
||||
if self.mode == ViewMode::Raw {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, self.theme_default);
|
||||
context.dirty_areas.push_back(area);
|
||||
get_y(upper_left)
|
||||
} else {
|
||||
|
@ -399,7 +402,7 @@ impl Component for MailView {
|
|||
(set_y(upper_left, y), bottom_right),
|
||||
Some(get_x(upper_left)),
|
||||
);
|
||||
clear_area(grid, ((_x, _y), (get_x(bottom_right), _y)));
|
||||
clear_area(grid, ((_x, _y), (get_x(bottom_right), _y)), headers);
|
||||
y = _y + 1;
|
||||
} else {
|
||||
skip_header_ctr -= 1;
|
||||
|
@ -441,7 +444,11 @@ impl Component for MailView {
|
|||
let mut x = get_x(upper_left);
|
||||
if let Some(id) = id {
|
||||
if sticky || skip_header_ctr == 0 {
|
||||
clear_area(grid, (set_y(upper_left, y), set_y(bottom_right, y)));
|
||||
clear_area(
|
||||
grid,
|
||||
(set_y(upper_left, y), set_y(bottom_right, y)),
|
||||
headers,
|
||||
);
|
||||
let (_x, _) = write_string_to_grid(
|
||||
"List-ID: ",
|
||||
grid,
|
||||
|
@ -542,14 +549,18 @@ impl Component for MailView {
|
|||
}
|
||||
|
||||
self.force_draw_headers = false;
|
||||
clear_area(grid, (set_y(upper_left, y), set_y(bottom_right, y)));
|
||||
clear_area(
|
||||
grid,
|
||||
(set_y(upper_left, y), set_y(bottom_right, y)),
|
||||
headers,
|
||||
);
|
||||
context
|
||||
.dirty_areas
|
||||
.push_back((upper_left, set_y(bottom_right, y + 3)));
|
||||
if !context.settings.pager.headers_sticky {
|
||||
let height_p = self.pager.size().1;
|
||||
|
||||
let height = height!(area) - y - 1;
|
||||
let height = height!(area).saturating_sub(y).saturating_sub(1);
|
||||
if self.pager.cursor_pos() >= self.headers_no {
|
||||
get_y(upper_left)
|
||||
} else if height_p > height && self.headers_cursor < self.headers_no + 1 {
|
||||
|
@ -576,7 +587,11 @@ impl Component for MailView {
|
|||
Ok(body) => body,
|
||||
Err(e) => {
|
||||
self.dirty = false;
|
||||
clear_area(grid, (set_y(upper_left, y), bottom_right));
|
||||
clear_area(
|
||||
grid,
|
||||
(set_y(upper_left, y), bottom_right),
|
||||
self.theme_default,
|
||||
);
|
||||
context
|
||||
.dirty_areas
|
||||
.push_back((set_y(upper_left, y), bottom_right));
|
||||
|
@ -776,10 +791,8 @@ impl Component for MailView {
|
|||
UIEvent::Input(ref key)
|
||||
if shortcut!(key == shortcuts[MailView::DESCRIPTION]["reply"]) =>
|
||||
{
|
||||
let account = &context.accounts[self.coordinates.0];
|
||||
let folder_hash = account[self.coordinates.1].unwrap().folder.hash();
|
||||
context.replies.push_back(UIEvent::Action(Tab(Reply(
|
||||
(self.coordinates.0, folder_hash),
|
||||
(self.coordinates.0, self.coordinates.1),
|
||||
self.coordinates.2,
|
||||
))));
|
||||
return true;
|
||||
|
|
|
@ -232,7 +232,7 @@ impl Component for EnvelopeView {
|
|||
let envelope: &Envelope = &self.wrapper;
|
||||
|
||||
if self.mode == ViewMode::Raw {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
context.dirty_areas.push_back(area);
|
||||
get_y(upper_left) - 1
|
||||
} else {
|
||||
|
@ -306,7 +306,11 @@ impl Component for EnvelopeView {
|
|||
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)));
|
||||
clear_area(
|
||||
grid,
|
||||
(set_y(upper_left, y + 1), set_y(bottom_right, y + 2)),
|
||||
crate::conf::value(context, "theme_default"),
|
||||
);
|
||||
context
|
||||
.dirty_areas
|
||||
.push_back((upper_left, set_y(bottom_right, y + 1)));
|
||||
|
|
|
@ -51,7 +51,7 @@ pub struct ThreadView {
|
|||
expanded_pos: usize,
|
||||
new_expanded_pos: usize,
|
||||
reversed: bool,
|
||||
coordinates: (usize, usize, usize),
|
||||
coordinates: (usize, FolderHash, usize),
|
||||
thread_group: ThreadHash,
|
||||
mailview: MailView,
|
||||
show_mailview: bool,
|
||||
|
@ -75,7 +75,7 @@ impl ThreadView {
|
|||
* context: current context
|
||||
*/
|
||||
pub fn new(
|
||||
coordinates: (usize, usize, usize),
|
||||
coordinates: (usize, FolderHash, usize),
|
||||
thread_group: ThreadHash,
|
||||
expanded_hash: Option<ThreadNodeHash>,
|
||||
context: &Context,
|
||||
|
@ -160,12 +160,16 @@ impl ThreadView {
|
|||
}
|
||||
self.set_dirty(true);
|
||||
}
|
||||
|
||||
fn initiate(&mut self, expanded_hash: Option<ThreadNodeHash>, context: &Context) {
|
||||
/* stack to push thread messages in order in order to pop and print them later */
|
||||
let account = &context.accounts[self.coordinates.0];
|
||||
let mailbox = &account[self.coordinates.1].unwrap();
|
||||
let threads = &account.collection.threads[&mailbox.folder.hash()];
|
||||
|
||||
if !threads.groups.contains_key(&self.thread_group) {
|
||||
return;
|
||||
}
|
||||
|
||||
let thread_iter = threads.thread_group_iter(self.thread_group);
|
||||
self.entries.clear();
|
||||
for (line, (ind, thread_node_hash)) in thread_iter.enumerate() {
|
||||
|
@ -496,7 +500,7 @@ impl ThreadView {
|
|||
|
||||
if self.dirty || (page_no != prev_page_no) {
|
||||
if page_no != prev_page_no {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
}
|
||||
let visibles: Vec<&usize> = self
|
||||
.visible_entries
|
||||
|
@ -563,6 +567,7 @@ impl ThreadView {
|
|||
upper_left!(area),
|
||||
set_x(bottom_right, get_x(upper_left!(area)) + 1),
|
||||
),
|
||||
context,
|
||||
self.cursor_pos,
|
||||
rows,
|
||||
visibles.len(),
|
||||
|
@ -616,6 +621,7 @@ impl ThreadView {
|
|||
upper_left!(area),
|
||||
set_x(bottom_right, get_x(upper_left!(area)) + 1),
|
||||
),
|
||||
context,
|
||||
self.cursor_pos,
|
||||
rows,
|
||||
visibles.len(),
|
||||
|
@ -680,7 +686,11 @@ impl ThreadView {
|
|||
context
|
||||
.dirty_areas
|
||||
.push_back(((mid, y + 1), set_x(bottom_right, mid)));
|
||||
clear_area(grid, ((mid, y + 1), set_x(bottom_right, mid)));
|
||||
clear_area(
|
||||
grid,
|
||||
((mid, y + 1), set_x(bottom_right, mid)),
|
||||
crate::conf::value(context, "theme_default"),
|
||||
);
|
||||
y + 2
|
||||
} else {
|
||||
get_y(upper_left) + 2
|
||||
|
@ -707,7 +717,11 @@ impl ThreadView {
|
|||
.draw(grid, (upper_left, bottom_right), context);
|
||||
}
|
||||
(false, true) => {
|
||||
clear_area(grid, ((mid + 1, get_y(upper_left) + y - 1), bottom_right));
|
||||
clear_area(
|
||||
grid,
|
||||
((mid + 1, get_y(upper_left) + y - 1), bottom_right),
|
||||
crate::conf::value(context, "theme_default"),
|
||||
);
|
||||
self.draw_list(grid, (set_y(upper_left, y), bottom_right), context);
|
||||
}
|
||||
(_, false) => {
|
||||
|
@ -724,7 +738,7 @@ impl ThreadView {
|
|||
let bottom_entity_rows = (pager_ratio * total_rows) / 100;
|
||||
|
||||
if bottom_entity_rows > total_rows {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
|
@ -789,7 +803,11 @@ impl ThreadView {
|
|||
/* if this is the first ever draw, there is nothing on the grid to update so populate it
|
||||
* first */
|
||||
if !self.initiated {
|
||||
clear_area(grid, (set_y(upper_left, y), bottom_right));
|
||||
clear_area(
|
||||
grid,
|
||||
(set_y(upper_left, y), bottom_right),
|
||||
crate::conf::value(context, "theme_default"),
|
||||
);
|
||||
let (width, height) = self.content.size();
|
||||
|
||||
match (self.show_mailview, self.show_thread) {
|
||||
|
|
|
@ -179,7 +179,7 @@ impl Component for VSplit {
|
|||
(false, true) => total_cols,
|
||||
(true, false) => 0,
|
||||
(false, false) => {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
@ -305,8 +305,14 @@ impl fmt::Display for Pager {
|
|||
|
||||
impl Pager {
|
||||
pub const DESCRIPTION: &'static str = "pager";
|
||||
pub fn set_reflow(&mut self, new_val: Reflow) {
|
||||
pub fn set_colors(&mut self, new_val: ThemeAttribute) -> &mut Self {
|
||||
self.colors = new_val;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_reflow(&mut self, new_val: Reflow) -> &mut Self {
|
||||
self.reflow = new_val;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reflow(&self) -> Reflow {
|
||||
|
@ -575,7 +581,7 @@ impl Component for Pager {
|
|||
return;
|
||||
}
|
||||
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
let (width, height) = self.content.size();
|
||||
let (cols, rows) = (width!(area), height!(area));
|
||||
self.cursor = (
|
||||
|
@ -699,6 +705,7 @@ impl Component for Pager {
|
|||
fn is_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self, value: bool) {
|
||||
self.dirty = value;
|
||||
}
|
||||
|
@ -713,6 +720,7 @@ impl Component for Pager {
|
|||
fn id(&self) -> ComponentId {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn set_id(&mut self, id: ComponentId) {
|
||||
self.id = id;
|
||||
}
|
||||
|
@ -723,8 +731,6 @@ impl Component for Pager {
|
|||
pub struct StatusBar {
|
||||
container: Box<dyn Component>,
|
||||
status: String,
|
||||
notifications: VecDeque<String>,
|
||||
cur_notification: Option<(std::time::Instant, String)>,
|
||||
ex_buffer: Field,
|
||||
ex_buffer_cmd_history_pos: Option<usize>,
|
||||
display_buffer: String,
|
||||
|
@ -749,8 +755,6 @@ impl StatusBar {
|
|||
StatusBar {
|
||||
container,
|
||||
status: String::with_capacity(256),
|
||||
notifications: VecDeque::new(),
|
||||
cur_notification: None,
|
||||
ex_buffer: Field::Text(UText::new(String::with_capacity(256)), None),
|
||||
ex_buffer_cmd_history_pos: None,
|
||||
display_buffer: String::with_capacity(8),
|
||||
|
@ -798,43 +802,6 @@ impl StatusBar {
|
|||
grid[(x, y)].set_attrs(attribute.attrs | Attr::Bold);
|
||||
}
|
||||
}
|
||||
let noto_colors = crate::conf::value(context, "status.notification");
|
||||
if self.cur_notification.is_some() {
|
||||
let (t, n) = self.cur_notification.as_ref().unwrap();
|
||||
if std::time::Instant::now().duration_since(*t) < std::time::Duration::new(5, 0) {
|
||||
write_string_to_grid(
|
||||
n,
|
||||
grid,
|
||||
noto_colors.fg,
|
||||
noto_colors.bg,
|
||||
noto_colors.attrs,
|
||||
(
|
||||
(std::cmp::max(x, width!(area).saturating_sub(n.len())), y),
|
||||
bottom_right!(area),
|
||||
),
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
self.cur_notification = None;
|
||||
}
|
||||
}
|
||||
if self.cur_notification.is_none() {
|
||||
if let Some(n) = self.notifications.pop_front() {
|
||||
write_string_to_grid(
|
||||
&n,
|
||||
grid,
|
||||
noto_colors.fg,
|
||||
noto_colors.bg,
|
||||
noto_colors.attrs,
|
||||
(
|
||||
(std::cmp::max(x, width!(area).saturating_sub(n.len())), y),
|
||||
bottom_right!(area),
|
||||
),
|
||||
None,
|
||||
);
|
||||
self.cur_notification = Some((std::time::Instant::now(), n));
|
||||
}
|
||||
}
|
||||
|
||||
let (x, y) = bottom_right!(area);
|
||||
for (idx, c) in self.display_buffer.chars().rev().enumerate() {
|
||||
|
@ -849,7 +816,7 @@ impl StatusBar {
|
|||
}
|
||||
|
||||
fn draw_execute_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
let (x, y) = write_string_to_grid(
|
||||
self.ex_buffer.as_str(),
|
||||
grid,
|
||||
|
@ -979,6 +946,7 @@ impl Component for StatusBar {
|
|||
),
|
||||
set_y(bottom_right, get_y(bottom_right) - height),
|
||||
),
|
||||
context,
|
||||
self.auto_complete.cursor(),
|
||||
hist_height,
|
||||
self.auto_complete.suggestions().len(),
|
||||
|
@ -1025,7 +993,11 @@ impl Component for StatusBar {
|
|||
} else {
|
||||
self.auto_complete.cursor()
|
||||
};
|
||||
clear_area(grid, hist_area);
|
||||
clear_area(
|
||||
grid,
|
||||
hist_area,
|
||||
crate::conf::value(context, "theme_default"),
|
||||
);
|
||||
if hist_height > 0 {
|
||||
change_colors(
|
||||
grid,
|
||||
|
@ -1229,10 +1201,6 @@ impl Component for StatusBar {
|
|||
UIEvent::Resize => {
|
||||
self.dirty = true;
|
||||
}
|
||||
UIEvent::StatusEvent(StatusEvent::DisplayMessage(s)) => {
|
||||
self.notifications.push_back(s.clone());
|
||||
self.dirty = true;
|
||||
}
|
||||
UIEvent::StatusEvent(StatusEvent::BufClear) => {
|
||||
self.display_buffer.clear();
|
||||
self.dirty = true;
|
||||
|
@ -1370,12 +1338,13 @@ impl Tabbed {
|
|||
}
|
||||
fn draw_tabs(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
||||
let tab_bar_attribute = crate::conf::value(context, "tab.bar");
|
||||
if self.children.is_empty() {
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, tab_bar_attribute);
|
||||
return;
|
||||
}
|
||||
let tab_bar_attribute = crate::conf::value(context, "tab.bar");
|
||||
let tab_unfocused_attribute = crate::conf::value(context, "tab.unfocused");
|
||||
let mut tab_focused_attribute = crate::conf::value(context, "tab.focused");
|
||||
if std::env::var("NO_COLOR").is_ok()
|
||||
|
@ -1409,6 +1378,10 @@ impl Tabbed {
|
|||
if y != _y_ {
|
||||
break;
|
||||
}
|
||||
if x > get_x(bottom_right) {
|
||||
x = get_x(bottom_right);
|
||||
break;
|
||||
}
|
||||
grid[(x_, _y_)]
|
||||
.set_fg(tab_bar_attribute.fg)
|
||||
.set_bg(tab_bar_attribute.bg)
|
||||
|
@ -1463,6 +1436,7 @@ impl Component for Tabbed {
|
|||
upper_left!(area),
|
||||
set_x(upper_left!(area), get_x(bottom_right!(area))),
|
||||
),
|
||||
crate::conf::value(context, "tab.bar"),
|
||||
);
|
||||
context.dirty_areas.push_back((
|
||||
upper_left!(area),
|
||||
|
@ -1504,7 +1478,7 @@ impl Component for Tabbed {
|
|||
),
|
||||
);
|
||||
context.dirty_areas.push_back(area);
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
create_box(grid, area);
|
||||
let area = (
|
||||
pos_inc(upper_left!(area), (3, 2)),
|
||||
|
@ -1653,9 +1627,7 @@ impl Component for Tabbed {
|
|||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
|
||||
self.children[self.cursor_pos]
|
||||
.get_status(context)
|
||||
.unwrap_or_default(),
|
||||
self.children[self.cursor_pos].get_status(context),
|
||||
)));
|
||||
self.set_dirty(true);
|
||||
}
|
||||
|
@ -1666,9 +1638,7 @@ impl Component for Tabbed {
|
|||
context
|
||||
.replies
|
||||
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
|
||||
self.children[self.cursor_pos]
|
||||
.get_status(context)
|
||||
.unwrap_or_default(),
|
||||
self.children[self.cursor_pos].get_status(context),
|
||||
)));
|
||||
self.set_dirty(true);
|
||||
return true;
|
||||
|
@ -1683,7 +1653,7 @@ impl Component for Tabbed {
|
|||
return true;
|
||||
}
|
||||
UIEvent::Action(Tab(NewDraft(account_idx, ref draft))) => {
|
||||
let mut composer = Composer::new(account_idx);
|
||||
let mut composer = Composer::new(account_idx, context);
|
||||
if let Some(draft) = draft {
|
||||
composer.set_draft(draft.clone());
|
||||
}
|
||||
|
@ -2367,14 +2337,13 @@ impl fmt::Display for RawBuffer {
|
|||
impl Component for RawBuffer {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if self.dirty {
|
||||
clear_area(grid, area);
|
||||
let (width, height) = self.buf.size();
|
||||
let (cols, rows) = (width!(area), height!(area));
|
||||
self.cursor = (
|
||||
std::cmp::min(width.saturating_sub(cols), self.cursor.0),
|
||||
std::cmp::min(height.saturating_sub(rows), self.cursor.1),
|
||||
);
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
copy_area(
|
||||
grid,
|
||||
&self.buf,
|
||||
|
|
|
@ -133,13 +133,14 @@ impl Field {
|
|||
}
|
||||
|
||||
impl Component for Field {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
let theme_attr = crate::conf::value(context, "widgets.form.field");
|
||||
write_string_to_grid(
|
||||
self.as_str(),
|
||||
grid,
|
||||
Color::Default,
|
||||
Color::Default,
|
||||
Attr::Default,
|
||||
theme_attr.fg,
|
||||
theme_attr.bg,
|
||||
theme_attr.attrs,
|
||||
area,
|
||||
None,
|
||||
);
|
||||
|
@ -388,15 +389,17 @@ impl Component for FormWidget {
|
|||
let bottom_right = bottom_right!(area);
|
||||
|
||||
if self.dirty {
|
||||
let theme_default = crate::conf::value(context, "theme_default");
|
||||
clear_area(
|
||||
grid,
|
||||
(
|
||||
upper_left,
|
||||
set_y(bottom_right, get_y(upper_left) + self.layout.len()),
|
||||
),
|
||||
theme_default,
|
||||
);
|
||||
|
||||
let label_attrs = crate::conf::value(context, "widgets.form.label");
|
||||
|
||||
for (i, k) in self.layout.iter().enumerate() {
|
||||
let v = self.fields.get_mut(k).unwrap();
|
||||
/* Write field label */
|
||||
|
@ -472,6 +475,7 @@ impl Component for FormWidget {
|
|||
pos_inc(upper_left, (0, length)),
|
||||
set_y(bottom_right, length + 2 + get_y(upper_left)),
|
||||
),
|
||||
theme_default,
|
||||
);
|
||||
if !self.hide_buttons {
|
||||
self.buttons.draw(
|
||||
|
@ -489,6 +493,7 @@ impl Component for FormWidget {
|
|||
set_y(upper_left, length + 4 + get_y(upper_left)),
|
||||
bottom_right,
|
||||
),
|
||||
theme_default,
|
||||
);
|
||||
self.dirty = false;
|
||||
}
|
||||
|
@ -642,9 +647,10 @@ impl<T> Component for ButtonWidget<T>
|
|||
where
|
||||
T: std::fmt::Debug + Default + Send,
|
||||
{
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if self.dirty {
|
||||
clear_area(grid, area);
|
||||
let theme_default = crate::conf::value(context, "theme_default");
|
||||
clear_area(grid, area, theme_default);
|
||||
let upper_left = upper_left!(area);
|
||||
|
||||
let mut len = 0;
|
||||
|
@ -653,11 +659,11 @@ where
|
|||
write_string_to_grid(
|
||||
k.as_str(),
|
||||
grid,
|
||||
Color::Default,
|
||||
theme_default.fg,
|
||||
if i == self.cursor && self.focus {
|
||||
Color::Byte(246)
|
||||
} else {
|
||||
Color::Default
|
||||
theme_default.bg
|
||||
},
|
||||
Attr::Bold,
|
||||
(
|
||||
|
@ -927,6 +933,7 @@ impl ScrollBar {
|
|||
self,
|
||||
grid: &mut CellBuffer,
|
||||
area: Area,
|
||||
context: &Context,
|
||||
pos: usize,
|
||||
visible_rows: usize,
|
||||
length: usize,
|
||||
|
@ -941,7 +948,7 @@ impl ScrollBar {
|
|||
if self.show_arrows {
|
||||
height -= height;
|
||||
}
|
||||
clear_area(grid, area);
|
||||
clear_area(grid, area, crate::conf::value(context, "theme_default"));
|
||||
|
||||
let visible_ratio: f32 = (std::cmp::min(visible_rows, length) as f32) / (length as f32);
|
||||
let scrollbar_height = std::cmp::max((visible_ratio * (height as f32)) as usize, 1);
|
||||
|
|
77
src/conf.rs
77
src/conf.rs
|
@ -51,7 +51,6 @@ use self::notifications::NotificationsSettings;
|
|||
use self::terminal::TerminalSettings;
|
||||
use crate::pager::PagerSettings;
|
||||
use crate::plugins::Plugin;
|
||||
use melib::backends::SpecialUsageMailbox;
|
||||
use melib::conf::{AccountSettings, FolderConf, ToggleFlag};
|
||||
use melib::error::*;
|
||||
|
||||
|
@ -115,6 +114,7 @@ pub struct FileAccount {
|
|||
|
||||
#[serde(default = "false_val")]
|
||||
read_only: bool,
|
||||
#[serde(default)]
|
||||
subscribed_folders: Vec<String>,
|
||||
#[serde(default)]
|
||||
folders: HashMap<String, FileFolderConf>,
|
||||
|
@ -141,7 +141,7 @@ impl From<FileAccount> for AccountConf {
|
|||
.map(|(k, v)| (k.clone(), v.folder_conf.clone()))
|
||||
.collect();
|
||||
|
||||
let mut acc = AccountSettings {
|
||||
let acc = AccountSettings {
|
||||
name: String::new(),
|
||||
root_folder,
|
||||
format,
|
||||
|
@ -154,60 +154,7 @@ impl From<FileAccount> for AccountConf {
|
|||
extra: x.extra.clone(),
|
||||
};
|
||||
|
||||
let root_path = PathBuf::from(acc.root_folder.as_str());
|
||||
let root_tmp = root_path
|
||||
.components()
|
||||
.last()
|
||||
.and_then(|c| c.as_os_str().to_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
if !acc.subscribed_folders.contains(&root_tmp) {
|
||||
acc.subscribed_folders.push(root_tmp);
|
||||
}
|
||||
let mut folder_confs = x.folders.clone();
|
||||
for s in &x.subscribed_folders {
|
||||
if !folder_confs.contains_key(s) {
|
||||
use melib::text_processing::GlobMatch;
|
||||
if s.is_glob() {
|
||||
continue;
|
||||
}
|
||||
folder_confs.insert(
|
||||
s.to_string(),
|
||||
FileFolderConf {
|
||||
folder_conf: FolderConf {
|
||||
subscribe: ToggleFlag::True,
|
||||
..FolderConf::default()
|
||||
},
|
||||
..FileFolderConf::default()
|
||||
},
|
||||
);
|
||||
} else {
|
||||
if !folder_confs[s].folder_conf().subscribe.is_unset() {
|
||||
continue;
|
||||
}
|
||||
folder_confs.get_mut(s).unwrap().folder_conf.subscribe = ToggleFlag::True;
|
||||
}
|
||||
|
||||
if folder_confs[s].folder_conf().usage.is_none() {
|
||||
let name = s
|
||||
.split(if s.contains('/') { '/' } else { '.' })
|
||||
.last()
|
||||
.unwrap_or("");
|
||||
folder_confs.get_mut(s).unwrap().folder_conf.usage =
|
||||
SpecialUsageMailbox::detect_usage(name);
|
||||
}
|
||||
|
||||
if folder_confs[s].folder_conf().ignore.is_unset() {
|
||||
use SpecialUsageMailbox::*;
|
||||
if [Junk, Sent, Trash]
|
||||
.contains(&folder_confs[s].folder_conf().usage.as_ref().unwrap())
|
||||
{
|
||||
folder_confs.get_mut(s).unwrap().folder_conf.ignore =
|
||||
ToggleFlag::InternalVal(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let folder_confs = x.folders.clone();
|
||||
AccountConf {
|
||||
account: acc,
|
||||
conf: x,
|
||||
|
@ -655,6 +602,7 @@ mod pp {
|
|||
use melib::{
|
||||
error::{MeliError, Result},
|
||||
parsec::*,
|
||||
ShellExpandTrait,
|
||||
};
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -735,19 +683,16 @@ mod pp {
|
|||
l
|
||||
))
|
||||
})? {
|
||||
let p = &Path::new(sub_path);
|
||||
debug!(p);
|
||||
let p_buf = if p.is_relative() {
|
||||
let mut p = Path::new(sub_path).expand();
|
||||
if p.is_relative() {
|
||||
/* We checked that path is ok above so we can do unwrap here */
|
||||
debug!(path);
|
||||
let prefix = path.parent().unwrap();
|
||||
debug!(prefix);
|
||||
prefix.join(p)
|
||||
} else {
|
||||
p.to_path_buf()
|
||||
};
|
||||
p = prefix.join(p)
|
||||
}
|
||||
|
||||
ret.extend(pp_helper(&p_buf, level + 1)?.chars());
|
||||
ret.extend(pp_helper(&p, level + 1)?.chars());
|
||||
} else {
|
||||
ret.push_str(l);
|
||||
ret.push('\n');
|
||||
|
@ -761,9 +706,9 @@ mod pp {
|
|||
/// in the filesystem.
|
||||
pub fn pp<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||
let p_buf: PathBuf = if path.as_ref().is_relative() {
|
||||
path.as_ref().canonicalize()?
|
||||
path.as_ref().expand().canonicalize()?
|
||||
} else {
|
||||
path.as_ref().to_path_buf()
|
||||
path.as_ref().expand()
|
||||
};
|
||||
|
||||
let mut ret = pp_helper(&p_buf, 0)?;
|
||||
|
|
|
@ -27,8 +27,8 @@ use super::{AccountConf, FileFolderConf};
|
|||
use fnv::FnvHashMap;
|
||||
use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
|
||||
use melib::backends::{
|
||||
BackendOp, Backends, Folder, FolderHash, FolderOperation, MailBackend, NotifyFn, ReadOnlyOp,
|
||||
RefreshEvent, RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox,
|
||||
BackendOp, Backends, Folder, FolderHash, MailBackend, NotifyFn, ReadOnlyOp, RefreshEvent,
|
||||
RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox,
|
||||
};
|
||||
use melib::error::{MeliError, Result};
|
||||
use melib::mailbox::*;
|
||||
|
@ -62,6 +62,7 @@ pub enum MailboxEntry {
|
|||
Failed(MeliError),
|
||||
/// first argument is done work, and second is total work
|
||||
Parsing(Mailbox, usize, usize),
|
||||
None,
|
||||
}
|
||||
|
||||
impl Default for MailboxEntry {
|
||||
|
@ -78,6 +79,7 @@ impl std::fmt::Display for MailboxEntry {
|
|||
match self {
|
||||
MailboxEntry::Available(ref m) => m.name().to_string(),
|
||||
MailboxEntry::Failed(ref e) => e.to_string(),
|
||||
MailboxEntry::None => "Not subscribed, is this a bug?".to_string(),
|
||||
MailboxEntry::Parsing(_, done, total) => {
|
||||
format!("Parsing messages. [{}/{}]", done, total)
|
||||
}
|
||||
|
@ -109,6 +111,7 @@ impl MailboxEntry {
|
|||
"Mailbox is not available: {}",
|
||||
e.to_string()
|
||||
))),
|
||||
MailboxEntry::None => Err(MeliError::new("Mailbox is not subscribed.")),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,6 +123,7 @@ impl MailboxEntry {
|
|||
"Mailbox is not available: {}",
|
||||
e.to_string()
|
||||
))),
|
||||
MailboxEntry::None => Err(MeliError::new("Mailbox is not subscribed.")),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,10 +238,11 @@ impl<'a> Iterator for MailboxIterator<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Default)]
|
||||
struct FolderNode {
|
||||
hash: FolderHash,
|
||||
kids: Vec<FolderNode>,
|
||||
#[derive(Serialize, Debug, Clone, Default)]
|
||||
pub struct FolderNode {
|
||||
pub hash: FolderHash,
|
||||
pub depth: usize,
|
||||
pub children: Vec<FolderNode>,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
|
@ -254,8 +259,9 @@ impl Account {
|
|||
let backend = map.get(settings.account().format())(
|
||||
settings.account(),
|
||||
Box::new(move |path: &str| {
|
||||
(s.folder_confs.contains_key(path)
|
||||
&& s.folder_confs[path].folder_conf().subscribe.is_true())
|
||||
s.account.subscribed_folders.is_empty()
|
||||
|| (s.folder_confs.contains_key(path)
|
||||
&& s.folder_confs[path].folder_conf().subscribe.is_true())
|
||||
|| s.account
|
||||
.subscribed_folders
|
||||
.iter()
|
||||
|
@ -328,22 +334,6 @@ impl Account {
|
|||
|
||||
let mut sent_folder = None;
|
||||
for f in ref_folders.values_mut() {
|
||||
if !((self.settings.folder_confs.contains_key(f.path())
|
||||
&& self.settings.folder_confs[f.path()]
|
||||
.folder_conf()
|
||||
.subscribe
|
||||
.is_true())
|
||||
|| self
|
||||
.settings
|
||||
.account
|
||||
.subscribed_folders
|
||||
.iter()
|
||||
.any(|m| f.path().matches_glob(m)))
|
||||
{
|
||||
/* Skip unsubscribed folder */
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(conf) = self.settings.folder_confs.get_mut(f.path()) {
|
||||
conf.folder_conf.usage = if f.special_usage() != SpecialUsageMailbox::Normal {
|
||||
Some(f.special_usage())
|
||||
|
@ -368,7 +358,6 @@ impl Account {
|
|||
folder_confs.insert(f.hash(), conf.clone());
|
||||
} else {
|
||||
let mut new = FileFolderConf::default();
|
||||
new.folder_conf.subscribe = super::ToggleFlag::InternalVal(true);
|
||||
new.folder_conf.usage = if f.special_usage() != SpecialUsageMailbox::Normal {
|
||||
Some(f.special_usage())
|
||||
} else {
|
||||
|
@ -387,8 +376,10 @@ impl Account {
|
|||
let mut tree: Vec<FolderNode> = Vec::new();
|
||||
let mut collection: Collection = Collection::new(Default::default());
|
||||
for (h, f) in ref_folders.iter() {
|
||||
if !folder_confs.contains_key(&h) {
|
||||
if !f.is_subscribed() {
|
||||
/* Skip unsubscribed folder */
|
||||
folders.insert(*h, MailboxEntry::None);
|
||||
workers.insert(*h, None);
|
||||
continue;
|
||||
}
|
||||
folders.insert(
|
||||
|
@ -407,7 +398,7 @@ impl Account {
|
|||
collection.threads.insert(*h, Threads::default());
|
||||
}
|
||||
|
||||
build_folders_order(&folder_confs, &mut tree, &ref_folders, &mut folders_order);
|
||||
build_folders_order(&mut tree, &ref_folders, &mut folders_order);
|
||||
self.folders = folders;
|
||||
self.ref_folders = ref_folders;
|
||||
self.folder_confs = folder_confs;
|
||||
|
@ -731,50 +722,25 @@ impl Account {
|
|||
pub fn is_empty(&self) -> bool {
|
||||
self.folders.is_empty()
|
||||
}
|
||||
pub fn list_folders(&self) -> Vec<Folder> {
|
||||
let mut folders = self.ref_folders.clone();
|
||||
let folder_confs = &self.folder_confs;
|
||||
//debug!("folder renames: {:?}", folder_renames);
|
||||
for f in folders.values_mut() {
|
||||
if let Some(r) = folder_confs.get(&f.hash()) {
|
||||
if let Some(rename) = r.folder_conf().alias() {
|
||||
f.change_name(rename);
|
||||
}
|
||||
|
||||
pub fn ref_folders(&self) -> &FnvHashMap<FolderHash, Folder> {
|
||||
&self.ref_folders
|
||||
}
|
||||
|
||||
pub fn list_folders(&self) -> Vec<FolderNode> {
|
||||
let mut ret = Vec::with_capacity(self.folders.len());
|
||||
fn rec(node: &FolderNode, ret: &mut Vec<FolderNode>) {
|
||||
ret.push(node.clone());
|
||||
for c in node.children.iter() {
|
||||
rec(c, ret);
|
||||
}
|
||||
}
|
||||
/*
|
||||
if let Some(pos) = folders
|
||||
.iter()
|
||||
.position(|f| f.name().eq_ignore_ascii_case("INBOX"))
|
||||
{
|
||||
folders.swap(pos, 0);
|
||||
for node in &self.tree {
|
||||
rec(node, &mut ret);
|
||||
}
|
||||
*/
|
||||
let order: FnvHashMap<FolderHash, usize> = self
|
||||
.folders_order
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &fh)| (fh, i))
|
||||
.collect();
|
||||
let mut folders: Vec<Folder> = folders
|
||||
.drain()
|
||||
.map(|(_, f)| f)
|
||||
.filter(|f| {
|
||||
self.folders.contains_key(&f.hash())
|
||||
|| self
|
||||
.settings
|
||||
.account
|
||||
.subscribed_folders
|
||||
.iter()
|
||||
.any(|m| f.path().matches_glob(m))
|
||||
})
|
||||
.collect();
|
||||
if order.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
folders.sort_unstable_by(|a, b| order[&a.hash()].partial_cmp(&order[&b.hash()]).unwrap());
|
||||
folders
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn folders_order(&self) -> &Vec<FolderHash> {
|
||||
&self.folders_order
|
||||
}
|
||||
|
@ -800,7 +766,7 @@ impl Account {
|
|||
.map(|e| (e.hash(), e))
|
||||
.collect::<FnvHashMap<EnvelopeHash, Envelope>>();
|
||||
match self.folders.entry(folder_hash).or_default() {
|
||||
MailboxEntry::Failed(_) => {}
|
||||
MailboxEntry::Failed(_) | MailboxEntry::None => {}
|
||||
MailboxEntry::Parsing(ref mut m, _, _) | MailboxEntry::Available(ref mut m) => {
|
||||
m.merge(&envelopes);
|
||||
if let Some(updated_folders) =
|
||||
|
@ -821,6 +787,9 @@ impl Account {
|
|||
}
|
||||
|
||||
pub fn status(&mut self, folder_hash: FolderHash) -> result::Result<(), usize> {
|
||||
if folder_hash == 0 {
|
||||
return Err(0);
|
||||
}
|
||||
loop {
|
||||
match self.workers.get_mut(&folder_hash).unwrap() {
|
||||
None => {
|
||||
|
@ -964,67 +933,107 @@ impl Account {
|
|||
&self.collection.threads[&f].thread_nodes()[&h]
|
||||
}
|
||||
|
||||
pub fn folder_operation(&mut self, path: &str, op: FolderOperation) -> Result<()> {
|
||||
pub fn folder_operation(
|
||||
&mut self,
|
||||
op: crate::execute::actions::FolderOperation,
|
||||
) -> Result<String> {
|
||||
use crate::execute::actions::FolderOperation;
|
||||
if self.settings.account.read_only() {
|
||||
return Err(MeliError::new("Account is read-only."));
|
||||
}
|
||||
match op {
|
||||
FolderOperation::Create => {
|
||||
if self.settings.account.read_only() {
|
||||
Err(MeliError::new("Account is read-only."))
|
||||
FolderOperation::Create(path) => {
|
||||
let mut folder = self
|
||||
.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.create_folder(path.to_string())?;
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::MailboxCreate((
|
||||
self.index,
|
||||
folder.hash(),
|
||||
))))
|
||||
.unwrap();
|
||||
let mut new = FileFolderConf::default();
|
||||
new.folder_conf.subscribe = super::ToggleFlag::InternalVal(true);
|
||||
new.folder_conf.usage = if folder.special_usage() != SpecialUsageMailbox::Normal {
|
||||
Some(folder.special_usage())
|
||||
} else {
|
||||
let mut folder = self
|
||||
.backend
|
||||
.write()
|
||||
.unwrap()
|
||||
.create_folder(path.to_string())?;
|
||||
let mut new = FileFolderConf::default();
|
||||
new.folder_conf.subscribe = super::ToggleFlag::InternalVal(true);
|
||||
new.folder_conf.usage = if folder.special_usage() != SpecialUsageMailbox::Normal
|
||||
{
|
||||
Some(folder.special_usage())
|
||||
} else {
|
||||
let tmp = SpecialUsageMailbox::detect_usage(folder.name());
|
||||
if tmp != Some(SpecialUsageMailbox::Normal) && tmp != None {
|
||||
let _ = folder.set_special_usage(tmp.unwrap());
|
||||
}
|
||||
tmp
|
||||
};
|
||||
let tmp = SpecialUsageMailbox::detect_usage(folder.name());
|
||||
if tmp != Some(SpecialUsageMailbox::Normal) && tmp != None {
|
||||
let _ = folder.set_special_usage(tmp.unwrap());
|
||||
}
|
||||
tmp
|
||||
};
|
||||
|
||||
self.folder_confs.insert(folder.hash(), new);
|
||||
self.folder_names
|
||||
.insert(folder.hash(), folder.path().to_string());
|
||||
self.folders.insert(
|
||||
folder.hash(),
|
||||
MailboxEntry::Parsing(
|
||||
Mailbox::new(folder.clone(), &FnvHashMap::default()),
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
self.workers.insert(
|
||||
folder.hash(),
|
||||
Account::new_worker(
|
||||
folder.clone(),
|
||||
&mut self.backend,
|
||||
&self.work_context,
|
||||
self.notify_fn.clone(),
|
||||
),
|
||||
);
|
||||
self.collection
|
||||
.threads
|
||||
.insert(folder.hash(), Threads::default());
|
||||
self.ref_folders.insert(folder.hash(), folder);
|
||||
build_folders_order(
|
||||
&self.folder_confs,
|
||||
&mut self.tree,
|
||||
&self.ref_folders,
|
||||
&mut self.folders_order,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
self.folder_confs.insert(folder.hash(), new);
|
||||
self.folder_names
|
||||
.insert(folder.hash(), folder.path().to_string());
|
||||
self.folders.insert(
|
||||
folder.hash(),
|
||||
MailboxEntry::Parsing(
|
||||
Mailbox::new(folder.clone(), &FnvHashMap::default()),
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
self.workers.insert(
|
||||
folder.hash(),
|
||||
Account::new_worker(
|
||||
folder.clone(),
|
||||
&mut self.backend,
|
||||
&self.work_context,
|
||||
self.notify_fn.clone(),
|
||||
),
|
||||
);
|
||||
self.collection
|
||||
.threads
|
||||
.insert(folder.hash(), Threads::default());
|
||||
self.ref_folders = self.backend.read().unwrap().folders()?;
|
||||
build_folders_order(&mut self.tree, &self.ref_folders, &mut self.folders_order);
|
||||
Ok(format!("`{}` successfully created.", &path))
|
||||
}
|
||||
FolderOperation::Delete => Err(MeliError::new("Not implemented.")),
|
||||
FolderOperation::Subscribe => Err(MeliError::new("Not implemented.")),
|
||||
FolderOperation::Unsubscribe => Err(MeliError::new("Not implemented.")),
|
||||
FolderOperation::Rename(_) => Err(MeliError::new("Not implemented.")),
|
||||
FolderOperation::Delete(path) => {
|
||||
if self.ref_folders.len() == 1 {
|
||||
return Err(MeliError::new("Cannot delete only mailbox."));
|
||||
}
|
||||
let folder_hash = if let Some((folder_hash, _)) =
|
||||
self.ref_folders.iter().find(|(_, f)| f.path() == path)
|
||||
{
|
||||
*folder_hash
|
||||
} else {
|
||||
return Err(MeliError::new("Mailbox with that path not found."));
|
||||
};
|
||||
self.backend.write().unwrap().delete_folder(folder_hash)?;
|
||||
self.sender
|
||||
.send(ThreadEvent::UIEvent(UIEvent::MailboxDelete((
|
||||
self.index,
|
||||
folder_hash,
|
||||
))))
|
||||
.unwrap();
|
||||
self.folders.remove(&folder_hash);
|
||||
self.ref_folders = self.backend.read().unwrap().folders()?;
|
||||
self.folder_confs.remove(&folder_hash);
|
||||
if let Some(pos) = self.folders_order.iter().position(|&h| h == folder_hash) {
|
||||
self.folders_order.remove(pos);
|
||||
}
|
||||
self.folder_names.remove(&folder_hash);
|
||||
if let Some(pos) = self.tree.iter().position(|n| n.hash == folder_hash) {
|
||||
self.tree.remove(pos);
|
||||
}
|
||||
if self.sent_folder == Some(folder_hash) {
|
||||
self.sent_folder = None;
|
||||
}
|
||||
self.collection.threads.remove(&folder_hash);
|
||||
self.workers.remove(&folder_hash); // FIXME Kill worker as well
|
||||
|
||||
// FIXME remove from settings as well
|
||||
|
||||
Ok(format!("'`{}` has been deleted.", &path))
|
||||
}
|
||||
FolderOperation::Subscribe(_) => Err(MeliError::new("Not implemented.")),
|
||||
FolderOperation::Unsubscribe(_) => Err(MeliError::new("Not implemented.")),
|
||||
FolderOperation::Rename(_, _) => Err(MeliError::new("Not implemented.")),
|
||||
FolderOperation::SetPermissions(_) => Err(MeliError::new("Not implemented.")),
|
||||
}
|
||||
}
|
||||
|
@ -1159,7 +1168,6 @@ impl IndexMut<usize> for Account {
|
|||
}
|
||||
|
||||
fn build_folders_order(
|
||||
folder_confs: &FnvHashMap<FolderHash, FileFolderConf>,
|
||||
tree: &mut Vec<FolderNode>,
|
||||
ref_folders: &FnvHashMap<FolderHash, Folder>,
|
||||
folders_order: &mut Vec<FolderHash>,
|
||||
|
@ -1168,23 +1176,24 @@ fn build_folders_order(
|
|||
tree.clear();
|
||||
folders_order.clear();
|
||||
for (h, f) in ref_folders.iter() {
|
||||
if !folder_confs.contains_key(&h) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if f.parent().is_none() {
|
||||
fn rec(h: FolderHash, ref_folders: &FnvHashMap<FolderHash, Folder>) -> FolderNode {
|
||||
fn rec(
|
||||
h: FolderHash,
|
||||
ref_folders: &FnvHashMap<FolderHash, Folder>,
|
||||
depth: usize,
|
||||
) -> FolderNode {
|
||||
let mut node = FolderNode {
|
||||
hash: h,
|
||||
kids: Vec::new(),
|
||||
children: Vec::new(),
|
||||
depth,
|
||||
};
|
||||
for &c in ref_folders[&h].children() {
|
||||
node.kids.push(rec(c, ref_folders));
|
||||
node.children.push(rec(c, ref_folders, depth + 1));
|
||||
}
|
||||
node
|
||||
};
|
||||
|
||||
tree.push(rec(*h, &ref_folders));
|
||||
tree.push(rec(*h, &ref_folders, 0));
|
||||
for &c in f.children() {
|
||||
stack.push(c);
|
||||
}
|
||||
|
@ -1208,10 +1217,10 @@ fn build_folders_order(
|
|||
}
|
||||
});
|
||||
|
||||
let mut stack: SmallVec<[Option<&FolderNode>; 8]> = SmallVec::new();
|
||||
let mut stack: SmallVec<[Option<&FolderNode>; 16]> = SmallVec::new();
|
||||
for n in tree.iter_mut() {
|
||||
folders_order.push(n.hash);
|
||||
n.kids.sort_unstable_by(|a, b| {
|
||||
n.children.sort_unstable_by(|a, b| {
|
||||
if ref_folders[&b.hash].path().eq_ignore_ascii_case("INBOX") {
|
||||
std::cmp::Ordering::Greater
|
||||
} else if ref_folders[&a.hash].path().eq_ignore_ascii_case("INBOX") {
|
||||
|
@ -1222,10 +1231,10 @@ fn build_folders_order(
|
|||
.cmp(&ref_folders[&b.hash].path())
|
||||
}
|
||||
});
|
||||
stack.extend(n.kids.iter().rev().map(Some));
|
||||
stack.extend(n.children.iter().rev().map(Some));
|
||||
while let Some(Some(next)) = stack.pop() {
|
||||
folders_order.push(next.hash);
|
||||
stack.extend(next.kids.iter().rev().map(Some));
|
||||
stack.extend(next.children.iter().rev().map(Some));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,6 +158,7 @@ const DEFAULT_KEYS: &'static [&'static str] = &[
|
|||
"tab.focused",
|
||||
"tab.unfocused",
|
||||
"tab.bar",
|
||||
"widgets.list.header",
|
||||
"widgets.form.label",
|
||||
"widgets.form.field",
|
||||
"widgets.form.highlighted",
|
||||
|
@ -205,7 +206,7 @@ pub struct ThemeAttribute {
|
|||
}
|
||||
|
||||
/// Holds {fore,back}ground color and terminal attribute values.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ThemeAttributeInner {
|
||||
#[serde(default)]
|
||||
fg: ThemeValue<Color>,
|
||||
|
@ -215,6 +216,16 @@ pub struct ThemeAttributeInner {
|
|||
attrs: ThemeValue<Attr>,
|
||||
}
|
||||
|
||||
impl Default for ThemeAttributeInner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fg: "theme_default".into(),
|
||||
bg: "theme_default".into(),
|
||||
attrs: "theme_default".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Holds either an actual value or refers to the key name of the attribute that holds the value.
|
||||
pub enum ThemeValue<T> {
|
||||
|
@ -459,13 +470,18 @@ impl Default for Theme {
|
|||
dark.insert($key.into(), ThemeAttributeInner::default());
|
||||
};
|
||||
}
|
||||
add!("theme_default");
|
||||
add!("theme_default", dark = { fg: Color::Default, bg: Color::Default, attrs: Attr::Default }, light = { fg: Color::Default, bg: Color::Default, attrs: Attr::Default });
|
||||
add!("status.bar", dark = { fg: Color::Byte(123), bg: Color::Byte(26) }, light = { fg: Color::Byte(123), bg: Color::Byte(26) });
|
||||
add!("status.notification", dark = { fg: Color::Byte(219), bg: Color::Byte(88) }, light = { fg: Color::Byte(219), bg: Color::Byte(88) });
|
||||
|
||||
add!("tab.focused");
|
||||
add!("tab.unfocused", dark = { fg: Color::Byte(15), bg: Color::Byte(8), }, light = { fg: Color::Byte(15), bg: Color::Byte(8), });
|
||||
add!("tab.bar");
|
||||
add!(
|
||||
"widgets.list.header",
|
||||
dark = { fg: Color::Black, bg: Color::White, attrs: Attr::Bold },
|
||||
light = {fg: Color::White, bg: Color::Black, attrs: Attr::Bold }
|
||||
);
|
||||
add!(
|
||||
"widgets.form.label",
|
||||
dark = { attrs: Attr::Bold },
|
||||
|
@ -837,7 +853,7 @@ fn is_cyclic(
|
|||
.map(|k| (k, false))
|
||||
.collect::<HashMap<&Cow<'static, str>, bool>>();
|
||||
for k in theme.keys() {
|
||||
for course in [Course::Fg, Course::Bg, Course::Attrs].into_iter() {
|
||||
for course in [Course::Fg, Course::Bg, Course::Attrs].iter() {
|
||||
path.push(k);
|
||||
if is_cyclic_util(course, k, &mut visited, &mut stack, &mut path, &theme) {
|
||||
let path = path
|
||||
|
|
|
@ -21,11 +21,11 @@
|
|||
|
||||
/*! A parser module for user commands passed through the Execute mode.
|
||||
*/
|
||||
use melib::backends::FolderOperation;
|
||||
pub use melib::thread::{SortField, SortOrder};
|
||||
use nom::{digit, not_line_ending, IResult};
|
||||
use std;
|
||||
pub mod actions;
|
||||
use actions::FolderOperation;
|
||||
pub mod history;
|
||||
pub use crate::actions::AccountAction::{self, *};
|
||||
pub use crate::actions::Action::{self, *};
|
||||
|
@ -78,7 +78,7 @@ define_commands!([
|
|||
map!(ws!(tag!("seen")), |_| Listing(SetSeen))
|
||||
| map!(ws!(tag!("unseen")), |_| Listing(SetUnseen))
|
||||
)
|
||||
) | map!(ws!(tag!("delete")), |_| Listing(Delete))
|
||||
) | map!(preceded!(tag!("delete"), eof!()), |_| Listing(Delete))
|
||||
)
|
||||
); )
|
||||
},
|
||||
|
@ -197,14 +197,14 @@ define_commands!([
|
|||
alt_complete!(
|
||||
do_parse!(
|
||||
ws!(tag!("pipe"))
|
||||
>> bin: map_res!(is_not!(" "), std::str::from_utf8)
|
||||
>> bin: quoted_argument
|
||||
>> is_a!(" ")
|
||||
>> args: separated_list!(is_a!(" "), quoted_argument)
|
||||
>> ({
|
||||
View(Pipe(bin.to_string(), args.into_iter().map(String::from).collect::<Vec<String>>()))
|
||||
})) | do_parse!(
|
||||
ws!(tag!("pipe"))
|
||||
>> bin: ws!(map_res!(is_not!(" "), std::str::from_utf8))
|
||||
>> bin: ws!(quoted_argument)
|
||||
>> ({
|
||||
View(Pipe(bin.to_string(), Vec::new()))
|
||||
})
|
||||
|
@ -218,7 +218,7 @@ define_commands!([
|
|||
named!( add_attachment<Action>,
|
||||
do_parse!(
|
||||
ws!(tag!("add-attachment"))
|
||||
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
|
||||
>> path: quoted_argument
|
||||
>> (Compose(AddAttachment(path.to_string())))
|
||||
)
|
||||
);
|
||||
|
@ -230,7 +230,7 @@ define_commands!([
|
|||
named!( remove_attachment<Action>,
|
||||
do_parse!(
|
||||
ws!(tag!("remove-attachment"))
|
||||
>> idx: map_res!(map_res!(call!(not_line_ending), std::str::from_utf8), usize::from_str)
|
||||
>> idx: map_res!(quoted_argument, usize::from_str)
|
||||
>> (Compose(RemoveAttachment(idx)))
|
||||
)
|
||||
);
|
||||
|
@ -255,8 +255,8 @@ define_commands!([
|
|||
ws!(tag!("create-folder"))
|
||||
>> account: quoted_argument
|
||||
>> is_a!(" ")
|
||||
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
|
||||
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Create))
|
||||
>> path: quoted_argument
|
||||
>> (Folder(account.to_string(), FolderOperation::Create(path.to_string())))
|
||||
)
|
||||
);
|
||||
)
|
||||
|
@ -269,8 +269,8 @@ define_commands!([
|
|||
ws!(tag!("subscribe-folder"))
|
||||
>> account: quoted_argument
|
||||
>> is_a!(" ")
|
||||
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
|
||||
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Subscribe))
|
||||
>> path: quoted_argument
|
||||
>> (Folder(account.to_string(), FolderOperation::Subscribe(path.to_string())))
|
||||
)
|
||||
);
|
||||
)
|
||||
|
@ -283,8 +283,8 @@ define_commands!([
|
|||
ws!(tag!("unsubscribe-folder"))
|
||||
>> account: quoted_argument
|
||||
>> is_a!(" ")
|
||||
>> path: map_res!(call!(not_line_ending), std::str::from_utf8)
|
||||
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Unsubscribe))
|
||||
>> path: quoted_argument
|
||||
>> (Folder(account.to_string(), FolderOperation::Unsubscribe(path.to_string())))
|
||||
)
|
||||
);
|
||||
)
|
||||
|
@ -299,8 +299,8 @@ define_commands!([
|
|||
>> is_a!(" ")
|
||||
>> src: quoted_argument
|
||||
>> is_a!(" ")
|
||||
>> dest: map_res!(call!(not_line_ending), std::str::from_utf8)
|
||||
>> (Folder(account.to_string(), src.to_string(), FolderOperation::Rename(dest.to_string())))
|
||||
>> dest: quoted_argument
|
||||
>> (Folder(account.to_string(), FolderOperation::Rename(src.to_string(), dest.to_string())))
|
||||
)
|
||||
);
|
||||
)
|
||||
|
@ -314,7 +314,7 @@ define_commands!([
|
|||
>> account: quoted_argument
|
||||
>> is_a!(" ")
|
||||
>> path: quoted_argument
|
||||
>> (Folder(account.to_string(), path.to_string(), FolderOperation::Delete))
|
||||
>> (Folder(account.to_string(), FolderOperation::Delete(path.to_string())))
|
||||
)
|
||||
);
|
||||
)
|
||||
|
@ -348,7 +348,7 @@ define_commands!([
|
|||
named!( save_attachment<Action>,
|
||||
do_parse!(
|
||||
ws!(tag!("save-attachment"))
|
||||
>> idx: map_res!(map_res!(is_not!(" "), std::str::from_utf8), usize::from_str)
|
||||
>> idx: map_res!(quoted_argument, usize::from_str)
|
||||
>> path: ws!(quoted_argument)
|
||||
>> (View(SaveAttachment(idx, path.to_string())))
|
||||
)
|
||||
|
@ -365,11 +365,11 @@ define_commands!([
|
|||
alt_complete!(
|
||||
do_parse!(
|
||||
ws!(tag!("add"))
|
||||
>> tag: ws!(map_res!(call!(not_line_ending), std::str::from_utf8))
|
||||
>> tag: ws!(quoted_argument)
|
||||
>> (Listing(Tag(Add(tag.to_string())))))
|
||||
| do_parse!(
|
||||
ws!(tag!("remove"))
|
||||
>> tag: ws!(map_res!(call!(not_line_ending), std::str::from_utf8))
|
||||
>> tag: ws!(quoted_argument)
|
||||
>> (Listing(Tag(Remove(tag.to_string())))))
|
||||
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
*/
|
||||
|
||||
use crate::components::Component;
|
||||
use melib::backends::{FolderHash, FolderOperation};
|
||||
use melib::backends::FolderHash;
|
||||
pub use melib::thread::{SortField, SortOrder};
|
||||
use melib::{Draft, EnvelopeHash};
|
||||
|
||||
|
@ -86,6 +86,17 @@ pub enum AccountAction {
|
|||
ReIndex,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FolderOperation {
|
||||
Create(NewFolderPath),
|
||||
Delete(FolderPath),
|
||||
Subscribe(FolderPath),
|
||||
Unsubscribe(FolderPath),
|
||||
Rename(FolderPath, NewFolderPath),
|
||||
// Placeholder
|
||||
SetPermissions(FolderPath),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Action {
|
||||
Listing(ListingAction),
|
||||
|
@ -99,9 +110,10 @@ pub enum Action {
|
|||
SetEnv(String, String),
|
||||
PrintEnv(String),
|
||||
Compose(ComposeAction),
|
||||
Folder(AccountName, FolderPath, FolderOperation),
|
||||
Folder(AccountName, FolderOperation),
|
||||
AccountAction(AccountName, AccountAction),
|
||||
}
|
||||
|
||||
type AccountName = String;
|
||||
type FolderPath = String;
|
||||
type NewFolderPath = String;
|
||||
|
|
256
src/state.rs
256
src/state.rs
|
@ -34,6 +34,7 @@ use melib::backends::{FolderHash, NotifyFn};
|
|||
|
||||
use crossbeam::channel::{unbounded, Receiver, Sender};
|
||||
use fnv::FnvHashMap;
|
||||
use smallvec::SmallVec;
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::result;
|
||||
|
@ -141,15 +142,20 @@ impl Context {
|
|||
let Context {
|
||||
ref mut accounts,
|
||||
ref mut mailbox_hashes,
|
||||
ref mut replies,
|
||||
..
|
||||
} = self;
|
||||
let was_online = accounts[account_pos].is_online;
|
||||
let ret = accounts[account_pos].is_online();
|
||||
if ret.is_ok() {
|
||||
if !was_online {
|
||||
for folder in accounts[account_pos].list_folders() {
|
||||
debug!("hash & folder: {:?} {}", folder.hash(), folder.name());
|
||||
mailbox_hashes.insert(folder.hash(), account_pos);
|
||||
for folder_node in accounts[account_pos].list_folders() {
|
||||
debug!(
|
||||
"hash & folder: {:?} {}",
|
||||
folder_node.hash,
|
||||
accounts[account_pos].ref_folders()[&folder_node.hash].name()
|
||||
);
|
||||
mailbox_hashes.insert(folder_node.hash, account_pos);
|
||||
}
|
||||
/* Account::watch() needs
|
||||
* - work_controller to pass `work_context` to the watcher threads and then add them
|
||||
|
@ -159,6 +165,8 @@ impl Context {
|
|||
* - replies to report any failures to the user
|
||||
*/
|
||||
accounts[account_pos].watch();
|
||||
|
||||
replies.push_back(UIEvent::AccountStatusChange(account_pos));
|
||||
}
|
||||
}
|
||||
ret
|
||||
|
@ -176,14 +184,26 @@ pub struct State {
|
|||
rows: usize,
|
||||
|
||||
grid: CellBuffer,
|
||||
overlay_grid: CellBuffer,
|
||||
draw_rate_limit: RateLimit,
|
||||
stdout: Option<StateStdout>,
|
||||
child: Option<ForkType>,
|
||||
draw_horizontal_segment_fn: fn(&mut State, usize, usize, usize) -> (),
|
||||
draw_horizontal_segment_fn: fn(&mut CellBuffer, &mut StateStdout, usize, usize, usize) -> (),
|
||||
pub mode: UIMode,
|
||||
components: Vec<Box<dyn Component>>,
|
||||
pub context: Context,
|
||||
timer: thread::JoinHandle<()>,
|
||||
|
||||
display_messages: SmallVec<[DisplayMessage; 8]>,
|
||||
display_messages_expiration_start: Option<UnixTimestamp>,
|
||||
display_messages_active: bool,
|
||||
display_messages_pos: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DisplayMessage {
|
||||
timestamp: UnixTimestamp,
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl Drop for State {
|
||||
|
@ -269,6 +289,7 @@ impl State {
|
|||
cols,
|
||||
rows,
|
||||
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
|
||||
overlay_grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
|
||||
stdout: None,
|
||||
child: None,
|
||||
mode: UIMode::Normal,
|
||||
|
@ -283,6 +304,10 @@ impl State {
|
|||
} else {
|
||||
State::draw_horizontal_segment
|
||||
},
|
||||
display_messages: SmallVec::new(),
|
||||
display_messages_expiration_start: None,
|
||||
display_messages_pos: 0,
|
||||
display_messages_active: false,
|
||||
|
||||
context: Context {
|
||||
accounts,
|
||||
|
@ -309,6 +334,7 @@ impl State {
|
|||
.set_value(std::time::Duration::from_millis(3));
|
||||
if s.context.settings.terminal.ascii_drawing {
|
||||
s.grid.set_ascii_drawing(true);
|
||||
s.overlay_grid.set_ascii_drawing(true);
|
||||
}
|
||||
|
||||
s.switch_to_alternate_screen();
|
||||
|
@ -434,6 +460,8 @@ impl State {
|
|||
self.cols = termcols.unwrap_or(72) as usize;
|
||||
self.rows = termrows.unwrap_or(120) as usize;
|
||||
self.grid.resize(self.cols, self.rows, Cell::with_char(' '));
|
||||
self.overlay_grid
|
||||
.resize(self.cols, self.rows, Cell::with_char(' '));
|
||||
|
||||
self.rcv_event(UIEvent::Resize);
|
||||
|
||||
|
@ -452,9 +480,22 @@ impl State {
|
|||
}
|
||||
let mut areas: smallvec::SmallVec<[Area; 8]> =
|
||||
self.context.dirty_areas.drain(0..).collect();
|
||||
if areas.is_empty() {
|
||||
return;
|
||||
if self.display_messages_active {
|
||||
let now = melib::datetime::now();
|
||||
if self
|
||||
.display_messages_expiration_start
|
||||
.map(|t| t + 5 < now)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.display_messages_active = false;
|
||||
self.display_messages_expiration_start = None;
|
||||
areas.push((
|
||||
(0, 0),
|
||||
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/* Sort by x_start, ie upper_left corner's x coordinate */
|
||||
areas.sort_by(|a, b| (a.0).0.partial_cmp(&(b.0).0).unwrap());
|
||||
/* draw each dirty area */
|
||||
|
@ -466,18 +507,36 @@ impl State {
|
|||
continue;
|
||||
}
|
||||
if let Some((x_start, x_end)) = segment.take() {
|
||||
(self.draw_horizontal_segment_fn)(self, x_start, x_end, y);
|
||||
(self.draw_horizontal_segment_fn)(
|
||||
&mut self.grid,
|
||||
self.stdout.as_mut().unwrap(),
|
||||
x_start,
|
||||
x_end,
|
||||
y,
|
||||
);
|
||||
}
|
||||
match segment {
|
||||
ref mut s @ None => {
|
||||
*s = Some((*x_start, *x_end));
|
||||
}
|
||||
ref mut s @ Some(_) if s.unwrap().1 < *x_start => {
|
||||
(self.draw_horizontal_segment_fn)(self, s.unwrap().0, s.unwrap().1, y);
|
||||
(self.draw_horizontal_segment_fn)(
|
||||
&mut self.grid,
|
||||
self.stdout.as_mut().unwrap(),
|
||||
s.unwrap().0,
|
||||
s.unwrap().1,
|
||||
y,
|
||||
);
|
||||
*s = Some((*x_start, *x_end));
|
||||
}
|
||||
ref mut s @ Some(_) if s.unwrap().1 < *x_end => {
|
||||
(self.draw_horizontal_segment_fn)(self, s.unwrap().0, s.unwrap().1, y);
|
||||
(self.draw_horizontal_segment_fn)(
|
||||
&mut self.grid,
|
||||
self.stdout.as_mut().unwrap(),
|
||||
s.unwrap().0,
|
||||
s.unwrap().1,
|
||||
y,
|
||||
);
|
||||
*s = Some((s.unwrap().1, *x_end));
|
||||
}
|
||||
Some((_, ref mut x)) => {
|
||||
|
@ -486,16 +545,111 @@ impl State {
|
|||
}
|
||||
}
|
||||
if let Some((x_start, x_end)) = segment {
|
||||
(self.draw_horizontal_segment_fn)(self, x_start, x_end, y);
|
||||
(self.draw_horizontal_segment_fn)(
|
||||
&mut self.grid,
|
||||
self.stdout.as_mut().unwrap(),
|
||||
x_start,
|
||||
x_end,
|
||||
y,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if self.display_messages_active {
|
||||
if let Some(DisplayMessage {
|
||||
ref timestamp,
|
||||
ref msg,
|
||||
..
|
||||
}) = self.display_messages.get(self.display_messages_pos)
|
||||
{
|
||||
let noto_colors = crate::conf::value(&self.context, "status.notification");
|
||||
use crate::melib::text_processing::{Reflow, TextProcessing};
|
||||
|
||||
let msg_lines = msg.split_lines_reflow(Reflow::All, Some(self.cols / 3));
|
||||
let width = msg_lines
|
||||
.iter()
|
||||
.map(|line| line.grapheme_len() + 4)
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
let displ_area = place_in_area(
|
||||
(
|
||||
(0, 0),
|
||||
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
|
||||
),
|
||||
(width, std::cmp::min(self.rows, msg_lines.len() + 4)),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
for row in self.overlay_grid.bounds_iter(displ_area) {
|
||||
for c in row {
|
||||
self.overlay_grid[c]
|
||||
.set_ch(' ')
|
||||
.set_fg(noto_colors.fg)
|
||||
.set_bg(noto_colors.bg)
|
||||
.set_attrs(noto_colors.attrs);
|
||||
}
|
||||
}
|
||||
let ((x, mut y), box_displ_area_bottom_right) =
|
||||
create_box(&mut self.overlay_grid, displ_area);
|
||||
for line in msg_lines
|
||||
.into_iter()
|
||||
.chain(Some(String::new()))
|
||||
.chain(Some(melib::datetime::timestamp_to_string(*timestamp, None)))
|
||||
{
|
||||
write_string_to_grid(
|
||||
&line,
|
||||
&mut self.overlay_grid,
|
||||
noto_colors.fg,
|
||||
noto_colors.bg,
|
||||
noto_colors.attrs,
|
||||
((x, y), box_displ_area_bottom_right),
|
||||
Some(x),
|
||||
);
|
||||
y += 1;
|
||||
}
|
||||
|
||||
if self.display_messages.len() > 1 {
|
||||
write_string_to_grid(
|
||||
if self.display_messages_pos == 0 {
|
||||
"Next: >"
|
||||
} else if self.display_messages_pos + 1 == self.display_messages.len() {
|
||||
"Prev: <"
|
||||
} else {
|
||||
"Prev: <, Next: >"
|
||||
},
|
||||
&mut self.overlay_grid,
|
||||
noto_colors.fg,
|
||||
noto_colors.bg,
|
||||
noto_colors.attrs,
|
||||
((x, y), box_displ_area_bottom_right),
|
||||
Some(x),
|
||||
);
|
||||
}
|
||||
for y in get_y(upper_left!(displ_area))..=get_y(bottom_right!(displ_area)) {
|
||||
(self.draw_horizontal_segment_fn)(
|
||||
&mut self.overlay_grid,
|
||||
self.stdout.as_mut().unwrap(),
|
||||
get_x(upper_left!(displ_area)),
|
||||
get_x(bottom_right!(displ_area)),
|
||||
y,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.flush();
|
||||
}
|
||||
|
||||
/// Draw only a specific `area` on the screen.
|
||||
fn draw_horizontal_segment(&mut self, x_start: usize, x_end: usize, y: usize) {
|
||||
fn draw_horizontal_segment(
|
||||
grid: &mut CellBuffer,
|
||||
stdout: &mut StateStdout,
|
||||
x_start: usize,
|
||||
x_end: usize,
|
||||
y: usize,
|
||||
) {
|
||||
write!(
|
||||
self.stdout(),
|
||||
stdout,
|
||||
"{}",
|
||||
cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
|
||||
)
|
||||
|
@ -503,12 +657,6 @@ impl State {
|
|||
let mut current_fg = Color::Default;
|
||||
let mut current_bg = Color::Default;
|
||||
let mut current_attrs = Attr::Default;
|
||||
let Self {
|
||||
ref grid,
|
||||
ref mut stdout,
|
||||
..
|
||||
} = self;
|
||||
let stdout = stdout.as_mut().unwrap();
|
||||
write!(stdout, "\x1B[m").unwrap();
|
||||
for x in x_start..=x_end {
|
||||
let c = &grid[(x, y)];
|
||||
|
@ -530,20 +678,20 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
fn draw_horizontal_segment_no_color(&mut self, x_start: usize, x_end: usize, y: usize) {
|
||||
fn draw_horizontal_segment_no_color(
|
||||
grid: &mut CellBuffer,
|
||||
stdout: &mut StateStdout,
|
||||
x_start: usize,
|
||||
x_end: usize,
|
||||
y: usize,
|
||||
) {
|
||||
write!(
|
||||
self.stdout(),
|
||||
stdout,
|
||||
"{}",
|
||||
cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
|
||||
)
|
||||
.unwrap();
|
||||
let mut current_attrs = Attr::Default;
|
||||
let Self {
|
||||
ref grid,
|
||||
ref mut stdout,
|
||||
..
|
||||
} = self;
|
||||
let stdout = stdout.as_mut().unwrap();
|
||||
write!(stdout, "\x1B[m").unwrap();
|
||||
for x in x_start..=x_end {
|
||||
let c = &grid[(x, y)];
|
||||
|
@ -612,24 +760,27 @@ impl State {
|
|||
),
|
||||
));
|
||||
}
|
||||
Folder(account_name, path, op) => {
|
||||
Folder(account_name, op) => {
|
||||
if let Some(account) = self
|
||||
.context
|
||||
.accounts
|
||||
.iter_mut()
|
||||
.find(|a| a.name() == account_name)
|
||||
{
|
||||
if let Err(e) = account.folder_operation(&path, op) {
|
||||
self.context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(e.to_string()),
|
||||
));
|
||||
} else {
|
||||
self.context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"{} succesfully created in `{}`",
|
||||
path, account_name
|
||||
)),
|
||||
));
|
||||
match account.folder_operation(op) {
|
||||
Err(err) => {
|
||||
self.context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(err.to_string()),
|
||||
));
|
||||
}
|
||||
Ok(msg) => {
|
||||
self.context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(format!(
|
||||
"`{}`: {}",
|
||||
account_name, msg
|
||||
)),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.context.replies.push_back(UIEvent::StatusEvent(
|
||||
|
@ -683,6 +834,12 @@ impl State {
|
|||
|
||||
/// The application's main loop sends `UIEvents` to state via this method.
|
||||
pub fn rcv_event(&mut self, mut event: UIEvent) {
|
||||
if let UIEvent::Input(_) = event {
|
||||
if self.display_messages_expiration_start.is_none() {
|
||||
self.display_messages_expiration_start = Some(melib::datetime::now());
|
||||
}
|
||||
}
|
||||
|
||||
match event {
|
||||
// Command type is handled only by State.
|
||||
UIEvent::Command(cmd) => {
|
||||
|
@ -730,6 +887,29 @@ impl State {
|
|||
self.redraw();
|
||||
return;
|
||||
}
|
||||
UIEvent::Input(Key::Alt('<')) => {
|
||||
self.display_messages_expiration_start = Some(melib::datetime::now());
|
||||
self.display_messages_active = true;
|
||||
self.display_messages_pos = self.display_messages_pos.saturating_sub(1);
|
||||
return;
|
||||
}
|
||||
UIEvent::Input(Key::Alt('>')) => {
|
||||
self.display_messages_expiration_start = Some(melib::datetime::now());
|
||||
self.display_messages_active = true;
|
||||
self.display_messages_pos = std::cmp::min(
|
||||
self.display_messages.len().saturating_sub(1),
|
||||
self.display_messages_pos + 1,
|
||||
);
|
||||
return;
|
||||
}
|
||||
UIEvent::StatusEvent(StatusEvent::DisplayMessage(ref msg)) => {
|
||||
self.display_messages.push(DisplayMessage {
|
||||
timestamp: melib::datetime::now(),
|
||||
msg: msg.clone(),
|
||||
});
|
||||
self.display_messages_active = true;
|
||||
self.display_messages_pos = self.display_messages.len() - 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
/* inform each component */
|
||||
|
|
|
@ -1595,10 +1595,13 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area,
|
|||
src_x = get_x(upper_left!(src));
|
||||
src_y += 1;
|
||||
if src_y > get_y(bottom_right!(src)) {
|
||||
clear_area(
|
||||
grid_dest,
|
||||
((get_x(upper_left!(dest)), y + 1), bottom_right!(dest)),
|
||||
);
|
||||
for row in
|
||||
grid_dest.bounds_iter(((get_x(upper_left!(dest)), y + 1), bottom_right!(dest)))
|
||||
{
|
||||
for c in row {
|
||||
grid_dest[c].set_ch(' ');
|
||||
}
|
||||
}
|
||||
ret.1 = y;
|
||||
break;
|
||||
}
|
||||
|
@ -1747,35 +1750,21 @@ pub fn write_string_to_grid(
|
|||
}
|
||||
|
||||
/// Completely clear an `Area` with an empty char and the terminal's default colors.
|
||||
pub fn clear_area(grid: &mut CellBuffer, area: Area) {
|
||||
pub fn clear_area(grid: &mut CellBuffer, area: Area, attributes: crate::conf::ThemeAttribute) {
|
||||
if !is_valid_area!(area) {
|
||||
return;
|
||||
}
|
||||
for row in grid.bounds_iter(area) {
|
||||
for c in row {
|
||||
grid[c] = Cell::default();
|
||||
grid[c]
|
||||
.set_fg(attributes.fg)
|
||||
.set_bg(attributes.bg)
|
||||
.set_attrs(attributes.attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn center_area(area: Area, (width, height): (usize, usize)) -> Area {
|
||||
let mid_x = { std::cmp::max(width!(area) / 2, width / 2) - width / 2 };
|
||||
let mid_y = { std::cmp::max(height!(area) / 2, height / 2) - height / 2 };
|
||||
|
||||
let (upper_x, upper_y) = upper_left!(area);
|
||||
let (max_x, max_y) = bottom_right!(area);
|
||||
(
|
||||
(
|
||||
std::cmp::min(max_x, upper_x + mid_x),
|
||||
std::cmp::min(max_y, upper_y + mid_y),
|
||||
),
|
||||
(
|
||||
std::cmp::min(max_x, upper_x + mid_x + width),
|
||||
std::cmp::min(max_y, upper_y + mid_y + height),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub mod ansi {
|
||||
//! Create a `CellBuffer` from a string slice containing ANSI escape codes.
|
||||
use super::{Cell, CellBuffer, Color};
|
||||
|
@ -2065,3 +2054,366 @@ impl RowIterator {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use boundaries::create_box;
|
||||
pub mod boundaries {
|
||||
use super::*;
|
||||
/// The upper and lower boundary char.
|
||||
pub(crate) const HORZ_BOUNDARY: char = '─';
|
||||
/// The left and right boundary char.
|
||||
pub(crate) const VERT_BOUNDARY: char = '│';
|
||||
|
||||
/// The top-left corner
|
||||
pub(crate) const _TOP_LEFT_CORNER: char = '┌';
|
||||
/// The top-right corner
|
||||
pub(crate) const _TOP_RIGHT_CORNER: char = '┐';
|
||||
/// The bottom-left corner
|
||||
pub(crate) const _BOTTOM_LEFT_CORNER: char = '└';
|
||||
/// The bottom-right corner
|
||||
pub(crate) const _BOTTOM_RIGHT_CORNER: char = '┘';
|
||||
|
||||
pub(crate) const LIGHT_VERTICAL_AND_RIGHT: char = '├';
|
||||
|
||||
pub(crate) const _LIGHT_VERTICAL_AND_LEFT: char = '┤';
|
||||
|
||||
pub(crate) const LIGHT_DOWN_AND_HORIZONTAL: char = '┬';
|
||||
|
||||
pub(crate) const LIGHT_UP_AND_HORIZONTAL: char = '┴';
|
||||
|
||||
pub(crate) const _DOUBLE_DOWN_AND_RIGHT: char = '╔';
|
||||
pub(crate) const _DOUBLE_DOWN_AND_LEFT: char = '╗';
|
||||
pub(crate) const _DOUBLE_UP_AND_LEFT: char = '╝';
|
||||
pub(crate) const _DOUBLE_UP_AND_RIGHT: char = '╚';
|
||||
|
||||
fn bin_to_ch(b: u32) -> char {
|
||||
match b {
|
||||
0b0001 => '╶',
|
||||
0b0010 => '╵',
|
||||
0b0011 => '└',
|
||||
0b0100 => '╴',
|
||||
0b0101 => '─',
|
||||
0b0110 => '┘',
|
||||
0b0111 => '┴',
|
||||
0b1000 => '╷',
|
||||
0b1001 => '┌',
|
||||
0b1010 => '│',
|
||||
0b1011 => '├',
|
||||
0b1100 => '┐',
|
||||
0b1101 => '┬',
|
||||
0b1110 => '┤',
|
||||
0b1111 => '┼',
|
||||
_ => unsafe { std::hint::unreachable_unchecked() },
|
||||
}
|
||||
}
|
||||
|
||||
fn ch_to_bin(ch: char) -> Option<u32> {
|
||||
match ch {
|
||||
'└' => Some(0b0011),
|
||||
'─' => Some(0b0101),
|
||||
'┘' => Some(0b0110),
|
||||
'┴' => Some(0b0111),
|
||||
'┌' => Some(0b1001),
|
||||
|
||||
'│' => Some(0b1010),
|
||||
|
||||
'├' => Some(0b1011),
|
||||
'┐' => Some(0b1100),
|
||||
'┬' => Some(0b1101),
|
||||
|
||||
'┤' => Some(0b1110),
|
||||
|
||||
'┼' => Some(0b1111),
|
||||
'╷' => Some(0b1000),
|
||||
|
||||
'╵' => Some(0b0010),
|
||||
'╴' => Some(0b0100),
|
||||
'╶' => Some(0b0001),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::never_loop)]
|
||||
fn set_and_join_vert(grid: &mut CellBuffer, idx: Pos) -> u32 {
|
||||
let (x, y) = idx;
|
||||
let mut bin_set = 0b1010;
|
||||
/* Check left side
|
||||
*
|
||||
* 1
|
||||
* -> 2 │ 0
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if x > 0 {
|
||||
if let Some(cell) = grid.get_mut(x - 1, y) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
if (adj & 0b0001) > 0 {
|
||||
bin_set |= 0b0100;
|
||||
break;
|
||||
} else if adj == 0b0100 {
|
||||
cell.set_ch(bin_to_ch(0b0101));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
bin_set |= 0b0100;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bin_set &= 0b1011;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check right side
|
||||
*
|
||||
* 1
|
||||
* 2 │ 0 <-
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if let Some(cell) = grid.get_mut(x + 1, y) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
if (adj & 0b0100) > 0 {
|
||||
bin_set |= 0b0001;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
bin_set &= 0b1110;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set upper side
|
||||
*
|
||||
* 1 <-
|
||||
* 2 │ 0
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if y > 0 {
|
||||
if let Some(cell) = grid.get_mut(x, y - 1) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
cell.set_ch(bin_to_ch(adj | 0b1000));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
} else {
|
||||
bin_set &= 0b1101;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set bottom side
|
||||
*
|
||||
* 1
|
||||
* 2 │ 0
|
||||
* 3 <-
|
||||
*/
|
||||
loop {
|
||||
if let Some(cell) = grid.get_mut(x, y + 1) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
cell.set_ch(bin_to_ch(adj | 0b0010));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
} else {
|
||||
bin_set &= 0b0111;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if bin_set == 0 {
|
||||
bin_set = 0b1010;
|
||||
}
|
||||
|
||||
bin_set
|
||||
}
|
||||
|
||||
#[allow(clippy::never_loop)]
|
||||
fn set_and_join_horz(grid: &mut CellBuffer, idx: Pos) -> u32 {
|
||||
let (x, y) = idx;
|
||||
let mut bin_set = 0b0101;
|
||||
/* Check upper side
|
||||
*
|
||||
* 1 <-
|
||||
* 2 ─ 0
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if y > 0 {
|
||||
if let Some(cell) = grid.get_mut(x, y - 1) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
if (adj & 0b1000) > 0 {
|
||||
bin_set |= 0b0010;
|
||||
break;
|
||||
} else if adj == 0b0010 {
|
||||
bin_set |= 0b0010;
|
||||
cell.set_ch(bin_to_ch(0b1010));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bin_set &= 0b1101;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check bottom side
|
||||
*
|
||||
* 1
|
||||
* 2 ─ 0
|
||||
* 3 <-
|
||||
*/
|
||||
loop {
|
||||
if let Some(cell) = grid.get_mut(x, y + 1) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
if (adj & 0b0010) > 0 {
|
||||
bin_set |= 0b1000;
|
||||
break;
|
||||
} else if adj == 0b1000 {
|
||||
bin_set |= 0b1000;
|
||||
cell.set_ch(bin_to_ch(0b1010));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
bin_set &= 0b0111;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set left side
|
||||
*
|
||||
* 1
|
||||
* -> 2 ─ 0
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if x > 0 {
|
||||
if let Some(cell) = grid.get_mut(x - 1, y) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
cell.set_ch(bin_to_ch(adj | 0b0001));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
} else {
|
||||
bin_set &= 0b1011;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set right side
|
||||
*
|
||||
* 1
|
||||
* 2 ─ 0 <-
|
||||
* 3
|
||||
*/
|
||||
loop {
|
||||
if let Some(cell) = grid.get_mut(x + 1, y) {
|
||||
if let Some(adj) = ch_to_bin(cell.ch()) {
|
||||
cell.set_ch(bin_to_ch(adj | 0b0100));
|
||||
cell.set_fg(Color::Byte(240));
|
||||
} else {
|
||||
bin_set &= 0b1110;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if bin_set == 0 {
|
||||
bin_set = 0b0101;
|
||||
}
|
||||
|
||||
bin_set
|
||||
}
|
||||
|
||||
pub(crate) enum BoxBoundary {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
pub(crate) fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: BoxBoundary) {
|
||||
/* Connected sides:
|
||||
*
|
||||
* 1
|
||||
* 2 c 0
|
||||
* 3
|
||||
*
|
||||
* #3210
|
||||
* 0b____
|
||||
*/
|
||||
|
||||
if grid.ascii_drawing {
|
||||
grid[idx].set_ch(match ch {
|
||||
BoxBoundary::Vertical => '|',
|
||||
BoxBoundary::Horizontal => '-',
|
||||
});
|
||||
|
||||
grid[idx].set_fg(Color::Byte(240));
|
||||
return;
|
||||
}
|
||||
|
||||
let bin_set = match ch {
|
||||
BoxBoundary::Vertical => set_and_join_vert(grid, idx),
|
||||
BoxBoundary::Horizontal => set_and_join_horz(grid, idx),
|
||||
};
|
||||
|
||||
grid[idx].set_ch(bin_to_ch(bin_set));
|
||||
grid[idx].set_fg(Color::Byte(240));
|
||||
}
|
||||
|
||||
/// Puts boundaries in `area`.
|
||||
/// Returns the inner area of the created box.
|
||||
pub fn create_box(grid: &mut CellBuffer, area: Area) -> Area {
|
||||
if !is_valid_area!(area) {
|
||||
return ((0, 0), (0, 0));
|
||||
}
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
||||
if !grid.ascii_drawing {
|
||||
for x in get_x(upper_left)..get_x(bottom_right) {
|
||||
grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY);
|
||||
grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY);
|
||||
}
|
||||
|
||||
for y in get_y(upper_left)..get_y(bottom_right) {
|
||||
grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY);
|
||||
grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY);
|
||||
}
|
||||
set_and_join_box(grid, upper_left, BoxBoundary::Horizontal);
|
||||
set_and_join_box(
|
||||
grid,
|
||||
set_x(upper_left, get_x(bottom_right)),
|
||||
BoxBoundary::Horizontal,
|
||||
);
|
||||
set_and_join_box(
|
||||
grid,
|
||||
set_y(upper_left, get_y(bottom_right)),
|
||||
BoxBoundary::Vertical,
|
||||
);
|
||||
set_and_join_box(grid, bottom_right, BoxBoundary::Vertical);
|
||||
}
|
||||
|
||||
(
|
||||
(
|
||||
std::cmp::min(
|
||||
get_x(upper_left) + 2,
|
||||
std::cmp::min(get_x(upper_left) + 1, get_x(bottom_right)),
|
||||
),
|
||||
std::cmp::min(
|
||||
get_y(upper_left) + 2,
|
||||
std::cmp::min(get_y(upper_left) + 1, get_y(bottom_right)),
|
||||
),
|
||||
),
|
||||
(
|
||||
std::cmp::max(
|
||||
get_x(bottom_right).saturating_sub(2),
|
||||
std::cmp::max(get_x(bottom_right).saturating_sub(1), get_x(upper_left)),
|
||||
),
|
||||
std::cmp::max(
|
||||
get_y(bottom_right).saturating_sub(2),
|
||||
std::cmp::max(get_y(bottom_right).saturating_sub(1), get_y(upper_left)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -483,6 +483,7 @@ impl EmbedGrid {
|
|||
terminal_size.1.saturating_sub(1),
|
||||
),
|
||||
),
|
||||
Default::default(),
|
||||
);
|
||||
debug!("{}", EscCode::from((&(*state), byte)));
|
||||
*state = State::Normal;
|
||||
|
@ -565,7 +566,11 @@ impl EmbedGrid {
|
|||
}
|
||||
}
|
||||
debug!("{}", EscCode::from((&(*state), byte)));
|
||||
clear_area(grid, ((0, 0), pos_dec(*terminal_size, (1, 1))));
|
||||
clear_area(
|
||||
grid,
|
||||
((0, 0), pos_dec(*terminal_size, (1, 1))),
|
||||
Default::default(),
|
||||
);
|
||||
*state = State::Normal;
|
||||
}
|
||||
(b'J', State::Csi1(ref buf)) if buf == b"0" => {
|
||||
|
@ -586,6 +591,7 @@ impl EmbedGrid {
|
|||
terminal_size.1.saturating_sub(1),
|
||||
),
|
||||
),
|
||||
Default::default(),
|
||||
);
|
||||
debug!("{}", EscCode::from((&(*state), byte)));
|
||||
*state = State::Normal;
|
||||
|
@ -602,6 +608,7 @@ impl EmbedGrid {
|
|||
cursor.1.saturating_sub(1) + scroll_region.top,
|
||||
),
|
||||
),
|
||||
Default::default(),
|
||||
);
|
||||
debug!("{}", EscCode::from((&(*state), byte)));
|
||||
*state = State::Normal;
|
||||
|
@ -609,7 +616,11 @@ impl EmbedGrid {
|
|||
(b'J', State::Csi1(ref buf)) if buf == b"2" => {
|
||||
/* Erase in Display (ED), VT100.*/
|
||||
/* Erase All */
|
||||
clear_area(grid, ((0, 0), pos_dec(*terminal_size, (1, 1))));
|
||||
clear_area(
|
||||
grid,
|
||||
((0, 0), pos_dec(*terminal_size, (1, 1))),
|
||||
Default::default(),
|
||||
);
|
||||
debug!("{}", EscCode::from((&(*state), byte)));
|
||||
*state = State::Normal;
|
||||
}
|
||||
|
|
|
@ -57,12 +57,8 @@ pub fn pos_dec(p: Pos, dec: (usize, usize)) -> Pos {
|
|||
/// An `Area` consists of two points: the upper left and bottom right corners.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate ui; fn main() {
|
||||
/// use ui::*;
|
||||
///
|
||||
/// ```no_run
|
||||
/// let new_area = ((0, 0), (1, 1));
|
||||
/// # }
|
||||
/// ```
|
||||
pub type Area = (Pos, Pos);
|
||||
|
||||
|
@ -70,12 +66,8 @@ pub type Area = (Pos, Pos);
|
|||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate ui; fn main() {
|
||||
/// use ui::*;
|
||||
///
|
||||
/// let new_area = ((0, 0), (1, 1));
|
||||
/// assert_eq!(height!(new_area), 1);
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! height {
|
||||
|
@ -88,12 +80,8 @@ macro_rules! height {
|
|||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate ui; fn main() {
|
||||
/// use ui::*;
|
||||
///
|
||||
/// let new_area = ((0, 0), (1, 1));
|
||||
/// assert_eq!(width!(new_area), 1);
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! width {
|
||||
|
@ -106,12 +94,8 @@ macro_rules! width {
|
|||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate ui; fn main() {
|
||||
/// use ui::*;
|
||||
///
|
||||
/// let new_area = ((0, 0), (1, 1));
|
||||
/// assert_eq!(upper_left!(new_area), (0, 0));
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! upper_left {
|
||||
|
@ -124,12 +108,8 @@ macro_rules! upper_left {
|
|||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate ui; fn main() {
|
||||
/// use ui::*;
|
||||
///
|
||||
/// let new_area = ((0, 0), (1, 1));
|
||||
/// assert_eq!(bottom_right!(new_area), (1, 1));
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! bottom_right {
|
||||
|
@ -142,15 +122,12 @@ macro_rules! bottom_right {
|
|||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # #[macro_use] extern crate ui; fn main() {
|
||||
/// use ui::*;
|
||||
///
|
||||
/// let valid_area = ((0, 0), (1, 1));
|
||||
/// assert!(is_valid_area!(valid_area));
|
||||
///
|
||||
/// let invalid_area = ((2, 2), (1, 1));
|
||||
/// assert!(!is_valid_area!(invalid_area));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
#[macro_export]
|
||||
macro_rules! is_valid_area {
|
||||
|
@ -160,3 +137,47 @@ macro_rules! is_valid_area {
|
|||
!(get_y(upper_left) > get_y(bottom_right) || get_x(upper_left) > get_x(bottom_right))
|
||||
}};
|
||||
}
|
||||
|
||||
/// Place box given by `(width, height)` in center of `area`
|
||||
pub fn center_area(area: Area, (width, height): (usize, usize)) -> Area {
|
||||
let mid_x = { std::cmp::max(width!(area) / 2, width / 2) - width / 2 };
|
||||
let mid_y = { std::cmp::max(height!(area) / 2, height / 2) - height / 2 };
|
||||
|
||||
let (upper_x, upper_y) = upper_left!(area);
|
||||
let (max_x, max_y) = bottom_right!(area);
|
||||
(
|
||||
(
|
||||
std::cmp::min(max_x, upper_x + mid_x),
|
||||
std::cmp::min(max_y, upper_y + mid_y),
|
||||
),
|
||||
(
|
||||
std::cmp::min(max_x, upper_x + mid_x + width),
|
||||
std::cmp::min(max_y, upper_y + mid_y + height),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/// Place box given by `(width, height)` in corner of `area`
|
||||
pub fn place_in_area(area: Area, (width, height): (usize, usize), upper: bool, left: bool) -> Area {
|
||||
let (upper_x, upper_y) = upper_left!(area);
|
||||
let (max_x, max_y) = bottom_right!(area);
|
||||
let x = if upper {
|
||||
upper_x + 2
|
||||
} else {
|
||||
max_x.saturating_sub(2).saturating_sub(width)
|
||||
};
|
||||
|
||||
let y = if left {
|
||||
upper_y + 2
|
||||
} else {
|
||||
max_y.saturating_sub(2).saturating_sub(height)
|
||||
};
|
||||
|
||||
(
|
||||
(std::cmp::min(x, max_x), std::cmp::min(y, max_y)),
|
||||
(
|
||||
std::cmp::min(x + width, max_x),
|
||||
std::cmp::min(y + height, max_y),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -111,6 +111,9 @@ pub enum UIEvent {
|
|||
Action(Action),
|
||||
StatusEvent(StatusEvent),
|
||||
MailboxUpdate((usize, FolderHash)), // (account_idx, mailbox_idx)
|
||||
MailboxDelete((usize, FolderHash)),
|
||||
MailboxCreate((usize, FolderHash)),
|
||||
AccountStatusChange(usize),
|
||||
ComponentKill(Uuid),
|
||||
WorkerProgress(FolderHash),
|
||||
StartupCheck(FolderHash),
|
||||
|
|
Loading…
Reference in New Issue