mirror of
https://git.meli.delivery/meli/meli
synced 2024-11-10 19:10:57 +00:00
ui: add autocomplete for commands in execute bar
This commit is contained in:
parent
a028aa9a44
commit
70e5949590
@ -37,6 +37,10 @@ pub trait Graphemes: UnicodeSegmentation + CodePointsIter {
|
|||||||
|
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn grapheme_len(&self) -> usize {
|
||||||
|
self.split_graphemes().len()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Graphemes for str {}
|
impl Graphemes for str {}
|
||||||
|
@ -205,6 +205,9 @@ impl Composer {
|
|||||||
let book: &AddressBook = &c.accounts[account_cursor].address_book;
|
let book: &AddressBook = &c.accounts[account_cursor].address_book;
|
||||||
let results: Vec<String> = book.search(term);
|
let results: Vec<String> = book.search(term);
|
||||||
results
|
results
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| AutoCompleteEntry::from(r))
|
||||||
|
.collect::<Vec<AutoCompleteEntry>>()
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
|
@ -699,17 +699,24 @@ impl Component for StatusBar {
|
|||||||
if self.ex_buffer.as_str().split_graphemes().len() <= 2 {
|
if self.ex_buffer.as_str().split_graphemes().len() <= 2 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let suggestions: Vec<String> = self
|
let mut suggestions: Vec<AutoCompleteEntry> = self
|
||||||
.cmd_history
|
.cmd_history
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|h| {
|
.filter_map(|h| {
|
||||||
if h.starts_with(self.ex_buffer.as_str()) {
|
if h.starts_with(self.ex_buffer.as_str()) {
|
||||||
Some(h.clone())
|
Some(h.clone().into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.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() {
|
if suggestions.is_empty() && !self.auto_complete.suggestions().is_empty() {
|
||||||
self.auto_complete.set_suggestions(suggestions);
|
self.auto_complete.set_suggestions(suggestions);
|
||||||
/* redraw self.container because we have got ridden of an autocomplete
|
/* redraw self.container because we have got ridden of an autocomplete
|
||||||
@ -803,7 +810,7 @@ impl Component for StatusBar {
|
|||||||
.take(hist_height)
|
.take(hist_height)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
s.as_str(),
|
s.as_str(),
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(88), // DarkRed,
|
Color::Byte(88), // DarkRed,
|
||||||
@ -817,6 +824,14 @@ impl Component for StatusBar {
|
|||||||
),
|
),
|
||||||
true,
|
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() {
|
if y_offset + offset == self.auto_complete.cursor() {
|
||||||
change_colors(
|
change_colors(
|
||||||
grid,
|
grid,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
|
|
||||||
type AutoCompleteFn = Box<Fn(&Context, &str) -> Vec<String> + Send>;
|
type AutoCompleteFn = Box<Fn(&Context, &str) -> Vec<AutoCompleteEntry> + Send>;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum FormFocus {
|
enum FormFocus {
|
||||||
@ -575,9 +575,50 @@ 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)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct AutoComplete {
|
pub struct AutoComplete {
|
||||||
entries: Vec<String>,
|
entries: Vec<AutoCompleteEntry>,
|
||||||
content: CellBuffer,
|
content: CellBuffer,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
|
|
||||||
@ -637,7 +678,7 @@ impl Component for AutoComplete {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AutoComplete {
|
impl AutoComplete {
|
||||||
pub fn new(entries: Vec<String>) -> Self {
|
pub fn new(entries: Vec<AutoCompleteEntry>) -> Self {
|
||||||
let mut ret = AutoComplete {
|
let mut ret = AutoComplete {
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
content: CellBuffer::default(),
|
content: CellBuffer::default(),
|
||||||
@ -649,26 +690,39 @@ impl AutoComplete {
|
|||||||
ret
|
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 {
|
if entries.len() == self.entries.len() && entries == self.entries {
|
||||||
return false;;
|
return false;;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut content = CellBuffer::new(
|
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(),
|
entries.len(),
|
||||||
Cell::with_style(Color::Byte(23), Color::Byte(7), Attr::Default),
|
Cell::with_style(Color::Byte(23), Color::Byte(7), Attr::Default),
|
||||||
);
|
);
|
||||||
let width = content.cols();
|
let width = content.cols();
|
||||||
for (i, e) in entries.iter().enumerate() {
|
for (i, e) in entries.iter().enumerate() {
|
||||||
write_string_to_grid(
|
let (x, _) = write_string_to_grid(
|
||||||
e,
|
&e.entry,
|
||||||
&mut content,
|
&mut content,
|
||||||
Color::Byte(23),
|
Color::Byte(23),
|
||||||
Color::Byte(7),
|
Color::Byte(7),
|
||||||
((0, i), (width - 1, i)),
|
((0, i), (width - 1, i)),
|
||||||
false,
|
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(
|
write_string_to_grid(
|
||||||
"▒",
|
"▒",
|
||||||
&mut content,
|
&mut content,
|
||||||
@ -712,10 +766,10 @@ impl AutoComplete {
|
|||||||
self.entries.clear();
|
self.entries.clear();
|
||||||
self.cursor = 0;
|
self.cursor = 0;
|
||||||
self.content.empty();
|
self.content.empty();
|
||||||
Some(ret)
|
Some(ret.entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn suggestions(&self) -> &Vec<String> {
|
pub fn suggestions(&self) -> &Vec<AutoCompleteEntry> {
|
||||||
&self.entries
|
&self.entries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,140 @@ pub use crate::actions::ListingAction::{self, *};
|
|||||||
pub use crate::actions::MailingListAction::{self, *};
|
pub use crate::actions::MailingListAction::{self, *};
|
||||||
pub use crate::actions::TabAction::{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!(
|
named!(
|
||||||
usize_c<usize>,
|
usize_c<usize>,
|
||||||
map_res!(
|
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!(
|
named!(
|
||||||
threaded<Action>,
|
threaded<Action>,
|
||||||
map!(ws!(tag!("threaded")), |_| Listing(SetThreaded))
|
map!(ws!(tag!("threaded")), |_| Listing(SetThreaded))
|
||||||
@ -85,76 +202,10 @@ named!(
|
|||||||
compact<Action>,
|
compact<Action>,
|
||||||
map!(ws!(tag!("compact")), |_| Listing(SetCompact))
|
map!(ws!(tag!("compact")), |_| Listing(SetCompact))
|
||||||
);
|
);
|
||||||
|
|
||||||
named!(
|
|
||||||
toggle<Action>,
|
|
||||||
preceded!(tag!("set "), alt_complete!(threaded | plain | compact))
|
|
||||||
);
|
|
||||||
named!(
|
named!(
|
||||||
listing_action<Action>,
|
listing_action<Action>,
|
||||||
alt_complete!(toggle | envelope_action | filter | toggle_thread_snooze)
|
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>,
|
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)
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user