listing: select multiple messages with a motion

- Press a number (movement multiplier)
- Press "select_entry" shortcut (default: v)
- Press a movement (arrow keys, PageUp/Down, Home/End)
- Resulting selection will be symmetric difference of previous selection
plus all the entries traversed with movement
master
Manos Pitsidianakis 2020-09-11 12:50:06 +03:00
parent 9e20f6556a
commit ed27ed604c
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
3 changed files with 179 additions and 11 deletions

View File

@ -376,6 +376,7 @@ pub trait ListingTrait: Component {
_context: &Context,
) {
}
fn set_command_modifier(&mut self, _is_active: bool) {}
fn set_movement(&mut self, mvm: PageMovement);
}
@ -647,12 +648,14 @@ impl Component for Listing {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
amount
} else {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
@ -698,12 +701,14 @@ impl Component for Listing {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
amount
} else {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
@ -797,12 +802,14 @@ impl Component for Listing {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
amount
} else {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
@ -818,12 +825,14 @@ impl Component for Listing {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
amount
} else {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
@ -839,12 +848,14 @@ impl Component for Listing {
1
} else if let Ok(mult) = self.cmd_buf.parse::<usize>() {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
mult
} else {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
@ -860,12 +871,14 @@ impl Component for Listing {
1
} else if let Ok(mult) = self.cmd_buf.parse::<usize>() {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
mult
} else {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
@ -957,12 +970,14 @@ impl Component for Listing {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
amount
} else {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
@ -1009,12 +1024,14 @@ impl Component for Listing {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
amount
} else {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
@ -1063,12 +1080,14 @@ impl Component for Listing {
1
} else if let Ok(amount) = self.cmd_buf.parse::<usize>() {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
amount
} else {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
@ -1131,6 +1150,7 @@ impl Component for Listing {
}
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt('')) if !self.cmd_buf.is_empty() => {
self.cmd_buf.clear();
self.component.set_command_modifier(false);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
@ -1138,6 +1158,7 @@ impl Component for Listing {
}
UIEvent::Input(Key::Char(c)) if c >= '0' && c <= '9' => {
self.cmd_buf.push(c);
self.component.set_command_modifier(true);
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufSet(

View File

@ -160,6 +160,8 @@ pub struct CompactListing {
color_cache: ColorCache,
movement: Option<PageMovement>,
modifier_active: bool,
modifier_command: Option<char>,
id: ComponentId,
}
@ -858,6 +860,10 @@ impl ListingTrait for CompactListing {
}
}
fn set_command_modifier(&mut self, is_active: bool) {
self.modifier_active = is_active;
}
fn set_movement(&mut self, mvm: PageMovement) {
self.movement = Some(mvm);
self.set_dirty(true);
@ -897,6 +903,8 @@ impl CompactListing {
view: ThreadView::default(),
color_cache: ColorCache::default(),
movement: None,
modifier_active: false,
modifier_command: None,
id: ComponentId::new_v4(),
}
}
@ -1395,18 +1403,79 @@ impl Component for CompactListing {
area = (set_y(upper_left, y + 1), bottom_right);
}
let (upper_left, bottom_right) = area;
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
if let Some('s') = self.modifier_command.take() {
self.set_command_modifier(false);
if let Some(mvm) = self.movement.as_ref() {
match mvm {
PageMovement::Up(amount) => {
for c in self.new_cursor_pos.2.saturating_sub(*amount)
..=self.new_cursor_pos.2
{
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
PageMovement::PageUp(multiplier) => {
for c in self.new_cursor_pos.2.saturating_sub(rows * multiplier)
..=self.new_cursor_pos.2
{
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
PageMovement::Down(amount) => {
for c in self.new_cursor_pos.2
..std::cmp::min(self.length, self.new_cursor_pos.2 + amount + 1)
{
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
PageMovement::PageDown(multiplier) => {
for c in self.new_cursor_pos.2
..std::cmp::min(
self.new_cursor_pos.2 + rows * multiplier + 1,
self.length,
)
{
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
PageMovement::Right(_) | PageMovement::Left(_) => {}
PageMovement::Home => {
for c in 0..=self.new_cursor_pos.2 {
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
PageMovement::End => {
for c in self.new_cursor_pos.2..self.length {
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
}
}
}
if !self.row_updates.is_empty() {
let (upper_left, bottom_right) = area;
while let Some(row) = self.row_updates.pop() {
self.update_line(context, row);
let row: usize = self.order[&row];
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
let top_idx = page_no * rows;
if row >= top_idx && row <= top_idx + rows {
if row >= top_idx && row < top_idx + rows {
let area = (
set_y(upper_left, get_y(upper_left) + (row % rows)),
set_y(bottom_right, get_y(upper_left) + (row % rows)),
@ -1475,9 +1544,14 @@ impl Component for CompactListing {
key == shortcuts[CompactListing::DESCRIPTION]["select_entry"]
) =>
{
if self.modifier_active {
self.modifier_command = Some('s');
} else {
let thread_hash = self.get_thread_under_cursor(self.cursor_pos.2);
self.selection.entry(thread_hash).and_modify(|e| *e = !*e);
}
return true;
}
UIEvent::Action(ref action) => {
match action {
Action::Sort(field, order) if !self.unfocused => {

View File

@ -123,6 +123,8 @@ pub struct ConversationsListing {
color_cache: ColorCache,
movement: Option<PageMovement>,
modifier_active: bool,
modifier_command: Option<char>,
id: ComponentId,
}
@ -848,6 +850,10 @@ impl ListingTrait for ConversationsListing {
}
}
fn set_command_modifier(&mut self, is_active: bool) {
self.modifier_active = is_active;
}
fn set_movement(&mut self, mvm: PageMovement) {
self.movement = Some(mvm);
self.set_dirty(true);
@ -884,6 +890,8 @@ impl ConversationsListing {
view: ThreadView::default(),
color_cache: ColorCache::default(),
movement: None,
modifier_active: false,
modifier_command: None,
id: ComponentId::new_v4(),
}
}
@ -1201,20 +1209,81 @@ impl Component for ConversationsListing {
area = (set_y(upper_left, y + 1), bottom_right);
}
let (upper_left, bottom_right) = area;
let rows = (get_y(bottom_right) - get_y(upper_left) + 1) / 3;
if let Some('s') = self.modifier_command.take() {
self.set_command_modifier(false);
if let Some(mvm) = self.movement.as_ref() {
match mvm {
PageMovement::Up(amount) => {
for c in self.new_cursor_pos.2.saturating_sub(*amount)
..=self.new_cursor_pos.2
{
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
PageMovement::PageUp(multiplier) => {
for c in self.new_cursor_pos.2.saturating_sub(rows * multiplier)
..=self.new_cursor_pos.2
{
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
PageMovement::Down(amount) => {
for c in self.new_cursor_pos.2
..std::cmp::min(self.length, self.new_cursor_pos.2 + amount + 1)
{
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
PageMovement::PageDown(multiplier) => {
for c in self.new_cursor_pos.2
..std::cmp::min(
self.new_cursor_pos.2 + rows * multiplier + 1,
self.length,
)
{
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
PageMovement::Right(_) | PageMovement::Left(_) => {}
PageMovement::Home => {
for c in 0..=self.new_cursor_pos.2 {
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
PageMovement::End => {
for c in self.new_cursor_pos.2..self.length {
let thread = self.get_thread_under_cursor(c);
self.selection.entry(thread).and_modify(|e| *e = !*e);
self.row_updates.push(thread);
}
}
}
}
}
if !self.row_updates.is_empty() {
/* certain rows need to be updated (eg an unseen message was just set seen)
* */
let (upper_left, bottom_right) = area;
while let Some(row) = self.row_updates.pop() {
self.update_line(context, row);
let row: usize = self.order[&row];
let rows = (get_y(bottom_right) - get_y(upper_left) + 1) / 3;
let page_no = (self.cursor_pos.2).wrapping_div(rows);
let top_idx = page_no * rows;
/* Update row only if it's currently visible */
if row >= top_idx && row <= top_idx + rows {
if row >= top_idx && row < top_idx + rows {
let area = (
set_y(upper_left, get_y(upper_left) + (3 * (row % rows))),
set_y(bottom_right, get_y(upper_left) + (3 * (row % rows) + 2)),
@ -1288,8 +1357,12 @@ impl Component for ConversationsListing {
key == shortcuts[ConversationsListing::DESCRIPTION]["select_entry"]
) =>
{
if self.modifier_active {
self.modifier_command = Some('s');
} else {
let thread = self.get_thread_under_cursor(self.cursor_pos.2);
self.selection.entry(thread).and_modify(|e| *e = !*e);
}
return true;
}
UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => {