summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-07-06 20:44:51 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-07-06 20:44:51 +0300
commit70e594959054b7955ddef0854e02fe588392e0db (patch)
tree109abd1e747b09c0bda4889a93973434878752ea
parenta028aa9a44ea2a4c30e5f1d2bfb3d56daac9d6aa (diff)
ui: add autocomplete for commands in execute bar
-rw-r--r--melib/src/grapheme_clusters.rs4
-rw-r--r--ui/src/components/mail/compose.rs3
-rw-r--r--ui/src/components/utilities.rs21
-rw-r--r--ui/src/components/utilities/widgets.rs72
-rw-r--r--ui/src/execute.rs221
5 files changed, 224 insertions, 97 deletions
diff --git a/melib/src/grapheme_clusters.rs b/melib/src/grapheme_clusters.rs
index 8553c9e4..db6b5427 100644
--- a/melib/src/grapheme_clusters.rs
+++ b/melib/src/grapheme_clusters.rs
@@ -37,6 +37,10 @@ pub trait Graphemes: UnicodeSegmentation + CodePointsIter {
count
}
+
+ fn grapheme_len(&self) -> usize {
+ self.split_graphemes().len()
+ }
}
impl Graphemes for str {}
diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs
index 1b7eab6c..f81b210f 100644
--- a/ui/src/components/mail/compose.rs
+++ b/ui/src/components/mail/compose.rs
@@ -205,6 +205,9 @@ impl Composer {
let book: &AddressBook = &c.accounts[account_cursor].address_book;
let results: Vec<String> = book.search(term);
results
+ .into_iter()
+ .map(|r| AutoCompleteEntry::from(r))
+ .collect::<Vec<AutoCompleteEntry>>()
}),
));
} else {
diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs
index 6837d5f5..d538a3a3 100644
--- a/ui/src/components/utilities.rs
+++ b/ui/src/components/utilities.rs
@@ -699,17 +699,24 @@ impl Component for StatusBar {
if self.ex_buffer.as_str().split_graphemes().len() <= 2 {
return;
}
- let suggestions: Vec<String> = self
+ let mut suggestions: Vec<AutoCompleteEntry> = self
.cmd_history
.iter()
.filter_map(|h| {
if h.starts_with(self.ex_buffer.as_str()) {
- Some(h.clone())
+ Some(h.clone().into())
} else {
None
}
})
.collect();
+ suggestions.extend(crate::execute::COMMAND_COMPLETION.iter().filter_map(|e| {
+ if e.0.starts_with(self.ex_buffer.as_str()) {
+ Some(e.into())
+ } else {
+ None
+ }
+ }));
if suggestions.is_empty() && !self.auto_complete.suggestions().is_empty() {
self.auto_complete.set_suggestions(suggestions);
/* redraw self.container because we have got ridden of an autocomplete
@@ -803,7 +810,7 @@ impl Component for StatusBar {
.take(hist_height)
.enumerate()
{
- write_string_to_grid(
+ let (x, y) = write_string_to_grid(
s.as_str(),
grid,
Color::Byte(88), // DarkRed,
@@ -817,6 +824,14 @@ impl Component for StatusBar {
),
true,
);
+ write_string_to_grid(
+ &s.description,
+ grid,
+ Color::White,
+ Color::Byte(174),
+ ((x + 2, y), bottom_right!(hist_area)),
+ false,
+ );
if y_offset + offset == self.auto_complete.cursor() {
change_colors(
grid,
diff --git a/ui/src/components/utilities/widgets.rs b/ui/src/components/utilities/widgets.rs
index a3ad1e18..1582668f 100644
--- a/ui/src/components/utilities/widgets.rs
+++ b/ui/src/components/utilities/widgets.rs
@@ -1,7 +1,7 @@
use super::*;
use fnv::FnvHashMap;
-type AutoCompleteFn = Box<Fn(&Context, &str) -> Vec<String> + Send>;
+type AutoCompleteFn = Box<Fn(&Context, &str) -> Vec<AutoCompleteEntry> + Send>;
#[derive(Debug, PartialEq)]
enum FormFocus {
@@ -576,8 +576,49 @@ where
}
#[derive(Debug, PartialEq, Clone)]
+pub struct AutoCompleteEntry {
+ pub entry: String,
+ pub description: String,
+}
+
+impl AutoCompleteEntry {
+ pub fn as_str(&self) -> &str {
+ self.entry.as_str()
+ }
+}
+
+impl From<String> for AutoCompleteEntry {
+ fn from(val: String) -> Self {
+ AutoCompleteEntry {
+ entry: val,
+ description: String::new(),
+ }
+ }
+}
+
+impl From<&(&str, &str)> for AutoCompleteEntry {
+ fn from(val: &(&str, &str)) -> Self {
+ let (a, b) = val;
+ AutoCompleteEntry {
+ entry: a.to_string(),
+ description: b.to_string(),
+ }
+ }
+}
+
+impl From<(String, String)> for AutoCompleteEntry {
+ fn from(val: (String, String)) -> Self {
+ let (a, b) = val;
+ AutoCompleteEntry {
+ entry: a,
+ description: b,
+ }
+ }
+}
+
+#[derive(Debug, PartialEq, Clone)]
pub struct AutoComplete {
- entries: Vec<String>,
+ entries: Vec<AutoCompleteEntry>,
content: CellBuffer,
cursor: usize,
@@ -637,7 +678,7 @@ impl Component for AutoComplete {
}
impl AutoComplete {
- pub fn new(entries: Vec<String>) -> Self {
+ pub fn new(entries: Vec<AutoCompleteEntry>) -> Self {
let mut ret = AutoComplete {
entries: Vec::new(),
content: CellBuffer::default(),
@@ -649,20 +690,25 @@ impl AutoComplete {
ret
}
- pub fn set_suggestions(&mut self, entries: Vec<String>) -> bool {
+ pub fn set_suggestions(&mut self, entries: Vec<AutoCompleteEntry>) -> bool {
if entries.len() == self.entries.len() && entries == self.entries {
return false;;
}
let mut content = CellBuffer::new(
- entries.iter().map(String::len).max().unwrap_or(0) + 1,
+ entries
+ .iter()
+ .map(|a| a.entry.grapheme_len() + a.description.grapheme_len() + 2)
+ .max()
+ .unwrap_or(0)
+ + 1,
entries.len(),
Cell::with_style(Color::Byte(23), Color::Byte(7), Attr::Default),
);
let width = content.cols();
for (i, e) in entries.iter().enumerate() {
- write_string_to_grid(
- e,
+ let (x, _) = write_string_to_grid(
+ &e.entry,
&mut content,
Color::Byte(23),
Color::Byte(7),
@@ -670,6 +716,14 @@ impl AutoComplete {
false,
);
write_string_to_grid(
+ &e.description,
+ &mut content,
+ Color::Byte(23),
+ Color::Byte(7),
+ ((x + 2, i), (width - 1, i)),
+ false,
+ );
+ write_string_to_grid(
"▒",
&mut content,
Color::Byte(23),
@@ -712,10 +766,10 @@ impl AutoComplete {
self.entries.clear();
self.cursor = 0;
self.content.empty();
- Some(ret)
+ Some(ret.entry)
}
- pub fn suggestions(&self) -> &Vec<String> {
+ pub fn suggestions(&self) -> &Vec<AutoCompleteEntry> {
&self.entries
}
}
diff --git a/ui/src/execute.rs b/ui/src/execute.rs
index e19b5051..7008064a 100644
--- a/ui/src/execute.rs
+++ b/ui/src/execute.rs
@@ -30,6 +30,140 @@ pub use crate::actions::ListingAction::{self, *};
pub use crate::actions::MailingListAction::{self, *};
pub use crate::actions::TabAction::{self, *};
+/* Create a const table with every command part that can be auto-completed and its description */
+macro_rules! define_commands {
+ ( [$({ tags: [$( $tags:literal),*], desc: $desc:literal, parser: ($parser:item)}),*]) => {
+ pub const COMMAND_COMPLETION: &[(&str, &str)] = &[$($( ($tags, $desc ) ),*),* ];
+ $( $parser )*
+ };
+}
+
+define_commands!([
+ { tags: ["set"],
+ desc: "set [seen/unseen], toggles message's Seen flag.",
+ parser:
+ ( named!(
+ envelope_action<Action>,
+ alt_complete!(
+ preceded!(
+ ws!(tag!("set")),
+ alt_complete!(
+ map!(ws!(tag!("read")), |_| Listing(SetRead))
+ | map!(ws!(tag!("unread")), |_| Listing(SetUnread))
+ )
+ ) | map!(ws!(tag!("delete")), |_| Listing(Delete))
+ )
+ ); )
+ },
+ { tags: ["close"],
+ desc: "close non-sticky tabs",
+ parser: (
+ named!(close<Action>, map!(ws!(tag!("close")), |_| Tab(Close)));
+ )
+ },
+ { tags: ["goto"],
+ desc: "goto [n], switch to nth mailbox in this account",
+ parser: (
+ named!(
+ goto<Action>,
+ preceded!(tag!("go "), map!(call!(usize_c), Action::ViewMailbox))
+ );
+ )
+ },
+ { tags: ["subsort"],
+ desc: "subsort [date/subject] [asc/desc], sorts first level replies in threads.",
+ parser: (
+ named!(
+ subsort<Action>,
+ do_parse!(tag!("subsort ") >> p: pair!(sortfield, sortorder) >> (SubSort(p.0, p.1)))
+ );
+ )
+ },
+ { tags: ["sort"],
+ desc: "sort [date/subject] [asc/desc], sorts threads.",
+ parser: (
+ named!(
+ sort<Action>,
+ do_parse!(
+ tag!("sort ") >> p: separated_pair!(sortfield, tag!(" "), sortorder) >> (Sort(p.0, p.1))
+ )
+ );
+ )
+ },
+ { tags: ["set", "set plain", "set threaded", "set compact"],
+ desc: "set [plain/threaded/compact], changes the mail listing view",
+ parser: (
+ named!(
+ toggle<Action>,
+ preceded!(tag!("set "), alt_complete!(threaded | plain | compact))
+ );
+ )
+ },
+ { tags: ["toggle_thread_snooze"],
+ desc: "turn off new notifications for this thread",
+ parser: (
+ named!(toggle_thread_snooze<Action>,
+ map!(ws!(tag!("toggle_thread_snooze")), |_| ToggleThreadSnooze)
+ );
+ )
+ },
+ { tags: ["filter"],
+ desc: "filter <TERM>, filters list with given term",
+ parser:(
+ named!(filter<Action>,
+ do_parse!(
+ ws!(tag!("filter"))
+ >> string: map_res!(call!(not_line_ending), std::str::from_utf8)
+ >> (Listing(Filter(String::from(string))))
+ )
+ );
+ )
+ },
+ { tags: ["list-archive", "list-post", "list-unsubscribe", "list-"],
+ desc: "list-[unsubscribe/post/archive]",
+ parser: (
+ named!(
+ mailinglist<Action>,
+ alt_complete!(
+ map!(ws!(tag!("list-post")), |_| MailingListAction(ListPost))
+ | map!(ws!(tag!("list-unsubscribe")), |_| MailingListAction(
+ ListUnsubscribe
+ ))
+ | map!(ws!(tag!("list-archive")), |_| MailingListAction(
+ ListArchive
+ ))
+ )
+ );
+ )
+ },
+ { tags: ["setenv "],
+ desc:"setenv VAR=VALUE",
+ parser: (
+ named!( setenv<Action>,
+ do_parse!(
+ ws!(tag!("setenv"))
+ >> key: map_res!(take_until1!("="), std::str::from_utf8)
+ >> ws!(tag!("="))
+ >> val: map_res!(call!(not_line_ending), std::str::from_utf8)
+ >> (SetEnv(key.to_string(), val.to_string()))
+ )
+ );
+ )
+ },
+ { tags: ["printenv "],
+ desc: "printenv VAR",
+ parser:(
+ named!( printenv<Action>,
+ do_parse!(
+ ws!(tag!("env"))
+ >> key: map_res!(call!(not_line_ending), std::str::from_utf8)
+ >> (PrintEnv(key.to_string()))
+ )
+ );
+ )
+ }
+]);
+
named!(
usize_c<usize>,
map_res!(
@@ -54,23 +188,6 @@ named!(
)
);
-named!(close<Action>, map!(ws!(tag!("close")), |_| Tab(Close)));
-named!(
- goto<Action>,
- preceded!(tag!("go "), map!(call!(usize_c), Action::ViewMailbox))
-);
-
-named!(
- subsort<Action>,
- do_parse!(tag!("subsort ") >> p: pair!(sortfield, sortorder) >> (SubSort(p.0, p.1)))
-);
-named!(
- sort<Action>,
- do_parse!(
- tag!("sort ") >> p: separated_pair!(sortfield, tag!(" "), sortorder) >> (Sort(p.0, p.1))
- )
-);
-
named!(
threaded<Action>,
map!(ws!(tag!("threaded")), |_| Listing(SetThreaded))
@@ -85,76 +202,10 @@ named!(
compact<Action>,
map!(ws!(tag!("compact")), |_| Listing(SetCompact))
);
-
-named!(
- toggle<Action>,
- preceded!(tag!("set "), alt_complete!(threaded | plain | compact))
-);
named!(
listing_action<Action>,
alt_complete!(toggle | envelope_action | filter | toggle_thread_snooze)
);
-
-named!(
- toggle_thread_snooze<Action>,
- map!(ws!(tag!("toggle_thread_snooze")), |_| ToggleThreadSnooze)
-);
-
-named!(
- filter<Action>,
- do_parse!(
- ws!(tag!("filter"))
- >> string: map_res!(call!(not_line_ending), std::str::from_utf8)
- >> (Listing(Filter(String::from(string))))
- )
-);
-
-named!(
- mailinglist<Action>,
- alt_complete!(
- map!(ws!(tag!("list-post")), |_| MailingListAction(ListPost))
- | map!(ws!(tag!("list-unsubscribe")), |_| MailingListAction(
- ListUnsubscribe
- ))
- | map!(ws!(tag!("list-archive")), |_| MailingListAction(
- ListArchive
- ))
- )
-);
-
-named!(
- envelope_action<Action>,
- alt_complete!(
- preceded!(
- ws!(tag!("set")),
- alt_complete!(
- map!(ws!(tag!("read")), |_| Listing(SetRead))
- | map!(ws!(tag!("unread")), |_| Listing(SetUnread))
- )
- ) | map!(ws!(tag!("delete")), |_| Listing(Delete))
- )
-);
-
-named!(
- setenv<Action>,
- do_parse!(
- ws!(tag!("setenv"))
- >> key: map_res!(take_until1!("="), std::str::from_utf8)
- >> ws!(tag!("="))
- >> val: map_res!(call!(not_line_ending), std::str::from_utf8)
- >> (SetEnv(key.to_string(), val.to_string()))
- )
-);
-
-named!(
- printenv<Action>,
- do_parse!(
- ws!(tag!("env"))
- >> key: map_res!(call!(not_line_ending), std::str::from_utf8)
- >> (PrintEnv(key.to_string()))
- )
-);
-
named!(pub parse_command<Action>,
- alt_complete!( goto | listing_action | sort | subsort | close | mailinglist | setenv | printenv)
- );
+ alt_complete!( goto | listing_action | sort | subsort | close | mailinglist | setenv | printenv)
+);