diff options
author | Canop <cano.petrole@gmail.com> | 2021-04-21 11:43:59 +0200 |
---|---|---|
committer | Canop <cano.petrole@gmail.com> | 2021-04-21 11:43:59 +0200 |
commit | 69371b95f19e186ec3c6c8396f88697590ee8705 (patch) | |
tree | ba5c51b00b53bf948a934b6f73e67eabdb59f108 /src/stage | |
parent | 3ff275098ad85ad64040ef15297281f69484ba5e (diff) |
staging area: selection, unstaging
It's now possible to select a line and move the selection, and to
unstage the selected line
Diffstat (limited to 'src/stage')
-rw-r--r-- | src/stage/filtered_stage.rs | 89 | ||||
-rw-r--r-- | src/stage/stage.rs | 6 | ||||
-rw-r--r-- | src/stage/stage_state.rs | 73 |
3 files changed, 156 insertions, 12 deletions
diff --git a/src/stage/filtered_stage.rs b/src/stage/filtered_stage.rs index 5c121fd..873ba1c 100644 --- a/src/stage/filtered_stage.rs +++ b/src/stage/filtered_stage.rs @@ -4,7 +4,8 @@ use { pattern::*, }, std::{ - path::PathBuf, + convert::TryFrom, + path::Path, }, }; @@ -13,6 +14,7 @@ pub struct FilteredStage { stage_version: usize, paths_idx: Vec<usize>, // indexes of the matching paths in the stage pattern: InputPattern, // an optional filtering pattern + selection: Option<usize>, // index in paths_idx, always in [0, paths_idx.len()[ } impl FilteredStage { @@ -50,25 +52,108 @@ impl FilteredStage { stage_version: stage.version(), paths_idx: Self::compute(stage, &pattern.pattern), pattern, + selection: None, } } + /// chech whether the stage has changed, and update the + /// filtered list if necessary pub fn update(&mut self, stage: &Stage) -> bool { if stage.version() == self.stage_version { false } else { + debug!("filtering stage"); + let selected_path_before = self.selection + .map(|idx| &stage.paths()[self.paths_idx[idx]]); self.paths_idx = Self::compute(stage, &self.pattern.pattern); + self.selection = selected_path_before + .and_then(|p| { + self.paths_idx.iter() + .position(|&pi| p==&stage.paths()[self.paths_idx[pi]]) + }); true } } + /// change the pattern, keeping the selection if possible + /// Assumes the stage didn't change (if it changed, we lose the + /// selection) + pub fn set_pattern(&mut self, stage: &Stage, pattern: InputPattern) { + let selected_idx_before = self.selection + .filter(|_| self.stage_version == stage.version()) + .map(|idx| self.paths_idx[idx]); + self.stage_version = stage.version(); // in case it changed + self.pattern = pattern; + self.paths_idx = Self::compute(stage, &self.pattern.pattern); + self.selection = selected_idx_before + .and_then(|pi| self.paths_idx.iter().position(|&v| v==pi)); + } pub fn len(&self) -> usize { self.paths_idx.len() } - pub fn path<'s>(&self, stage: &'s Stage, idx: usize) -> Option<&'s PathBuf> { + pub fn path<'s>(&self, stage: &'s Stage, idx: usize) -> Option<&'s Path> { self.paths_idx .get(idx) .and_then(|&idx| stage.paths().get(idx)) + .map(|p| p.as_path()) + } + pub fn path_sel<'s>(&self, stage: &'s Stage, idx: usize) -> Option<(&'s Path, bool)> { + self.path(stage, idx) + .map(|p| (p, self.selection.map_or(false, |si| idx==si))) } pub fn pattern(&self) -> &InputPattern { &self.pattern } + pub fn has_selection(&self) -> bool { + self.selection.is_some() + } + pub fn selected_path<'s>(&self, stage: &'s Stage) -> Option<&'s Path> { + self.selection + .and_then(|pi| self.paths_idx.get(pi)) + .and_then(|&idx| stage.paths().get(idx)) + .map(|p| p.as_path()) + } + pub fn unselect(&mut self) { + self.selection = None + } + /// unstage the selection, if any, or return false. + /// If possible we select the item below so that the user + /// may easily remove a few items + pub fn unstage_selection(&mut self, stage: &mut Stage) -> bool { + if let Some(spi) = self.selection { + stage.remove_idx(self.paths_idx[spi]); + self.stage_version = stage.version(); + self.paths_idx = Self::compute(stage, &self.pattern.pattern); + if spi >= self.paths_idx.len() { + self.selection = None; + } + true + } else { + false + } + } + pub fn move_selection(&mut self, dy: i32, cycle: bool) { + self.selection = if self.paths_idx.is_empty() { + None + } else if let Some(sel_idx) = self.selection.and_then(|i| i32::try_from(i).ok()) { + let new_sel_idx = sel_idx + dy; + Some( + if new_sel_idx < 0 { + if cycle && sel_idx == 0 { + self.paths_idx.len() - 1 + } else { + 0 + } + } else if new_sel_idx as usize >= self.paths_idx.len() { + if cycle && sel_idx == self.paths_idx.len() as i32 - 1 { + 0 + } else { + self.paths_idx.len() - 1 + } + } else { + new_sel_idx as usize + } + ) + } else { + Some(0) + }; + } } diff --git a/src/stage/stage.rs b/src/stage/stage.rs index 8b2fff6..be04397 100644 --- a/src/stage/stage.rs +++ b/src/stage/stage.rs @@ -46,6 +46,12 @@ impl Stage { false } } + pub fn remove_idx(&mut self, idx: usize) { + if idx < self.paths.len() { + self.version += 1; + self.paths.remove(idx); + } + } pub fn clear(&mut self) { self.version += 1; self.paths.clear() diff --git a/src/stage/stage_state.rs b/src/stage/stage_state.rs index 8c1ddfe..b244777 100644 --- a/src/stage/stage_state.rs +++ b/src/stage/stage_state.rs @@ -26,7 +26,7 @@ pub struct StageState { filtered_stage: FilteredStage, - /// those options are mainly kept for transmission to child state + /// those options are only kept for transmission to child state /// (if they become possible) tree_options: TreeOptions, @@ -160,7 +160,7 @@ impl PanelState for StageState { app_state: &AppState, _con: &AppContext, ) -> Result<CmdResult, ProgramError> { - self.filtered_stage = FilteredStage::filtered(&app_state.stage, pat); + self.filtered_stage.set_pattern(&app_state.stage, pat); Ok(CmdResult::Keep) } @@ -187,12 +187,20 @@ impl PanelState for StageState { w.queue(cursor::MoveTo(area.left, y))?; let mut cw = CropWriter::new(w, width); let cw = &mut cw; - if let Some(path) = self.filtered_stage.path(stage, stage_idx) { - let style = if path.is_dir() { + if let Some((path, selected)) = self.filtered_stage.path_sel(stage, stage_idx) { + let mut style = if path.is_dir() { &styles.directory } else { &styles.file }; + let mut bg_style; + if selected { + bg_style = style.clone(); + if let Some(c) = styles.selected_line.get_bg() { + bg_style.set_bg(c); + } + style = &bg_style; + } if pattern_object.subpath { let label = path.to_string_lossy(); // we must display the matching on the whole path @@ -201,7 +209,7 @@ impl PanelState for StageState { let matched_string = MatchedString::new( name_match, &label, - &style, + style, &styles.char_match, ); matched_string.queue_on(cw)?; @@ -210,12 +218,21 @@ impl PanelState for StageState { let label_cols = label.width(); if label_cols + 2 < cw.allowed { if let Some(parent_path) = path.parent() { + let mut parent_style = &styles.parent; + let mut bg_style; + if selected { + bg_style = parent_style.clone(); + if let Some(c) = styles.selected_line.get_bg() { + bg_style.set_bg(c); + } + parent_style = &bg_style; + } let cols_max = cw.allowed - label_cols - 3; let parent_path = parent_path.to_string_lossy(); let parent_cols = parent_path.width(); if parent_cols <= cols_max { cw.queue_str( - &styles.parent, + parent_style, &parent_path, )?; } else { @@ -234,16 +251,16 @@ impl PanelState for StageState { bytes_count += c.len_utf8(); } cw.queue_char( - &styles.parent, + parent_style, ELLIPSIS, )?; cw.queue_str( - &styles.parent, + parent_style, &parent_path[parent_path.len()-bytes_count..], )?; } cw.queue_char( - &styles.parent, + parent_style, '/', )?; } @@ -252,7 +269,7 @@ impl PanelState for StageState { let matched_string = MatchedString::new( name_match, &label, - &style, + style, &styles.char_match, ); matched_string.queue_on(cw)?; @@ -260,6 +277,7 @@ impl PanelState for StageState { // this should not happen warn!("how did we fall on a path without filename?"); } + cw.fill(style, &SPACE_FILLING)?; } cw.fill(&styles.default, &SPACE_FILLING)?; } @@ -292,6 +310,41 @@ impl PanelState for StageState { self.filtered_stage = FilteredStage::unfiltered(&app_state.stage); CmdResult::Keep } + Internal::back if self.filtered_stage.pattern().is_some() => { + self.filtered_stage = FilteredStage::unfiltered(&app_state.stage); + CmdResult::Keep + } + Internal::line_down => { + let count = get_arg(input_invocation, internal_exec, 1); + self.filtered_stage.move_selection(count, true); + CmdResult::Keep + } + Internal::line_up => { + let count = get_arg(input_invocation, internal_exec, 1); + self.filtered_stage.move_selection(-count, true); + CmdResult::Keep + } + Internal::line_down_no_cycle => { + let count = get_arg(input_invocation, internal_exec, 1); + self.filtered_stage.move_selection(count, false); + CmdResult::Keep + } + Internal::line_up_no_cycle => { + let count = get_arg(input_invocation, internal_exec, 1); + self.filtered_stage.move_selection(-count, false); + CmdResult::Keep + } + Internal::stage => { + // shall we restage what we just unstaged ? + CmdResult::error("nothing to stage here") + } + Internal::unstage | Internal::toggle_stage => { + if self.filtered_stage.unstage_selection(&mut app_state.stage) { + CmdResult::Keep + } else { + CmdResult::error("you must select a path to unstage") + } + } _ => self.on_internal_generic( w, internal_exec, |