summaryrefslogtreecommitdiffstats
path: root/src/stage
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2021-04-21 11:43:59 +0200
committerCanop <cano.petrole@gmail.com>2021-04-21 11:43:59 +0200
commit69371b95f19e186ec3c6c8396f88697590ee8705 (patch)
treeba5c51b00b53bf948a934b6f73e67eabdb59f108 /src/stage
parent3ff275098ad85ad64040ef15297281f69484ba5e (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.rs89
-rw-r--r--src/stage/stage.rs6
-rw-r--r--src/stage/stage_state.rs73
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,