summaryrefslogtreecommitdiffstats
path: root/src/stage
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2021-04-08 17:22:30 +0200
committerCanop <cano.petrole@gmail.com>2021-04-15 13:00:23 +0200
commit831c10d6c5ccf74d0109b6bc3ffd708a9da61cab (patch)
tree1435f23e6f19549e49f01e52f4cbfc702ec3a06b /src/stage
parentacade5051d04d14509b5526f48b1bb9de2ee4f70 (diff)
staging area [WIP]
The staging area will be the way to operate on multiple files. See issue #266 (it's far from ready)
Diffstat (limited to 'src/stage')
-rw-r--r--src/stage/mod.rs7
-rw-r--r--src/stage/stage.rs42
-rw-r--r--src/stage/stage_state.rs280
3 files changed, 329 insertions, 0 deletions
diff --git a/src/stage/mod.rs b/src/stage/mod.rs
new file mode 100644
index 0000000..fbd5388
--- /dev/null
+++ b/src/stage/mod.rs
@@ -0,0 +1,7 @@
+mod stage;
+mod stage_state;
+
+pub use {
+ stage::*,
+ stage_state::*,
+};
diff --git a/src/stage/stage.rs b/src/stage/stage.rs
new file mode 100644
index 0000000..2b20646
--- /dev/null
+++ b/src/stage/stage.rs
@@ -0,0 +1,42 @@
+use {
+ std::{
+ path::{Path, PathBuf},
+ },
+};
+
+/// a staging area: selection of several paths
+/// for later user
+#[derive(Default, Debug)]
+pub struct Stage {
+ pub paths: Vec<PathBuf>,
+}
+
+impl Stage {
+ pub fn contains(&self, path: &Path) -> bool {
+ self.paths
+ .iter()
+ .any(|p| p==path)
+ }
+ pub fn is_empty(&self) -> bool {
+ self.paths.is_empty()
+ }
+ /// return true when there's a change
+ pub fn add(&mut self, path: PathBuf) -> bool {
+ if self.contains(&path) {
+ false
+ } else {
+ self.paths.push(path);
+ true
+ }
+ }
+ /// return true when there's a change
+ pub fn remove(&mut self, path: &Path) -> bool {
+ if let Some(pos) = self.paths.iter().position(|p| p == path) {
+ self.paths.remove(pos);
+ true
+ } else {
+ false
+ }
+ }
+
+}
diff --git a/src/stage/stage_state.rs b/src/stage/stage_state.rs
new file mode 100644
index 0000000..a3b1c45
--- /dev/null
+++ b/src/stage/stage_state.rs
@@ -0,0 +1,280 @@
+use {
+ crate::{
+ app::*,
+ command::{Command, TriggerType},
+ display::{CropWriter, Screen, SPACE_FILLING, W},
+ errors::ProgramError,
+ pattern::*,
+ path::{self, PathAnchor},
+ skin::PanelSkin,
+ tree::*,
+ verb::*,
+ },
+ crossterm::{
+ cursor,
+ style::{Color, Print, SetBackgroundColor, SetForegroundColor},
+ QueueableCommand,
+ },
+ std::path::{Path, PathBuf},
+ termimad::Area,
+};
+
+pub struct StageState {
+ tree_options: TreeOptions,
+ mode: Mode,
+}
+
+impl StageState {
+
+ pub fn new(
+ tree_options: TreeOptions,
+ con: &AppContext,
+ ) -> StageState {
+ Self {
+ tree_options,
+ mode: initial_mode(con),
+ }
+ }
+}
+
+impl PanelState for StageState {
+
+ fn get_pending_task(&self) -> Option<&'static str> {
+ None
+ }
+
+ fn selected_path(&self) -> Option<&Path> {
+ None
+ }
+
+ fn selection(&self) -> Option<Selection<'_>> {
+ None
+ }
+
+ fn sel_info<'c>(&'c self, app_state: &'c AppState) -> SelInfo<'c> {
+ match app_state.stage.paths.len() {
+ 0 => SelInfo::None,
+ 1 => SelInfo::One(Selection {
+ path: &app_state.stage.paths[0],
+ stype: SelectionType::File,
+ is_exe: false,
+ line: 0,
+ }),
+ _ => SelInfo::More(&app_state.stage),
+ }
+ }
+
+ fn has_at_least_one_selection(&self, cc: &CmdContext) -> bool {
+ !cc.app.app_state.stage.is_empty()
+ }
+
+ fn tree_options(&self) -> TreeOptions {
+ self.tree_options.clone()
+ }
+
+ fn with_new_options(
+ &mut self,
+ screen: Screen,
+ change_options: &dyn Fn(&mut TreeOptions),
+ in_new_panel: bool,
+ con: &AppContext,
+ ) -> CmdResult {
+ // TODO implement: sorting, etc.
+ CmdResult::Keep
+ }
+
+ fn on_pattern(
+ &mut self,
+ pat: InputPattern,
+ _con: &AppContext,
+ ) -> Result<CmdResult, ProgramError> {
+ // TODO implement
+ Ok(CmdResult::Keep)
+ }
+
+ fn display(
+ &mut self,
+ w: &mut W,
+ disc: &DisplayContext,
+ ) -> Result<(), ProgramError> {
+ let stage = &disc.app_state.stage;
+ let area = &disc.state_area;
+ let styles = &disc.panel_skin.styles;
+ let line_count = area.height as usize;
+ let width = area.width as usize;
+ for y in 0..line_count {
+ w.queue(cursor::MoveTo(area.left, y as u16 + area.top))?;
+ let mut cw = CropWriter::new(w, width);
+ if let Some(path) = stage.paths.get(y) {
+ cw.queue_g_string(
+ &styles.default,
+ path.to_string_lossy().to_string(),
+ )?;
+ }
+ cw.fill(
+ &styles.default,
+ &SPACE_FILLING,
+ )?;
+ }
+ Ok(())
+ }
+
+ fn refresh(&mut self, _screen: Screen, _con: &AppContext) -> Command {
+ Command::empty()
+ }
+
+ fn set_mode(&mut self, mode: Mode) {
+ self.mode = mode;
+ }
+
+ fn get_mode(&self) -> Mode {
+ self.mode
+ }
+
+ fn on_internal(
+ &mut self,
+ w: &mut W,
+ internal_exec: &InternalExecution,
+ input_invocation: Option<&VerbInvocation>,
+ trigger_type: TriggerType,
+ cc: &CmdContext,
+ ) -> Result<CmdResult, ProgramError> {
+ use Internal::*;
+ Ok(match internal_exec.internal {
+ _ => self.on_internal_generic(
+ w,
+ internal_exec,
+ input_invocation,
+ trigger_type,
+ cc,
+ )?,
+ })
+ }
+
+ /// the stage state deals with a multiple selection, which means
+ /// the tests on what can be executed are different, and the verb
+ /// display is different too
+ fn get_verb_status(
+ &self,
+ verb: &Verb,
+ invocation: &VerbInvocation,
+ cc: &CmdContext,
+ ) -> Status {
+ if cc.app.app_state.stage.paths.len() > 1 {
+ if let VerbExecution::External(external) = &verb.execution {
+ if external.exec_mode != ExternalExecutionMode::StayInBroot {
+ return Status::new(
+ "only verbs returning to broot on end can be executed on a multi-selection".to_owned(),
+ true,
+ );
+ }
+ }
+ // right now there's no check for sequences but they're inherently dangereous
+ }
+ if cc.app.app_state.stage.is_empty() {
+ if let Some(err) = verb.check_args(&None, invocation, &cc.app.other_path) {
+ return Status::new(err, true);
+ }
+ } else {
+ // we check that the verb applies to all selections
+ // TODO make it faster when the verb doesn't need the selection ?
+ for path in &cc.app.app_state.stage.paths {
+ let selection = Selection {
+ path,
+ line: 0,
+ stype: SelectionType::from(path),
+ is_exe: false,
+ };
+ if let Some(err) = verb.check_args(&Some(selection), invocation, &cc.app.other_path) {
+ return Status::new(err, true);
+ }
+ }
+ }
+ Status::new(
+ verb.get_status_markdown(
+ None,
+ &cc.app.other_path,
+ invocation,
+ ),
+ false,
+ )
+ }
+
+ fn execute_external(
+ &mut self,
+ w: &mut W,
+ verb: &Verb,
+ external_execution: &ExternalExecution,
+ invocation: Option<&VerbInvocation>,
+ cc: &CmdContext,
+ ) -> Result<CmdResult, ProgramError> {
+ if cc.app.app_state.stage.paths.len() > 1 {
+ if external_execution.exec_mode != ExternalExecutionMode::StayInBroot {
+ return Ok(CmdResult::error(
+ "only verbs returning to broot on end can be executed on a multi-selection".to_owned()
+ ));
+ }
+ }
+ if cc.app.app_state.stage.is_empty() {
+ // execution on no selection
+ let exec_builder = ExecutionStringBuilder::from_invocation(
+ &verb.invocation_parser,
+ None,
+ &cc.app.other_path,
+ if let Some(inv) = invocation {
+ &inv.args
+ } else {
+ &None
+ },
+ );
+ external_execution.to_cmd_result(w, exec_builder, &cc.app.con)
+ } else {
+ let mut refresh = false;
+ // we apply the verb to all selections
+ for path in &cc.app.app_state.stage.paths {
+ let selection = Selection {
+ path,
+ line: 0,
+ stype: SelectionType::from(path),
+ is_exe: false,
+ };
+ let exec_builder = ExecutionStringBuilder::from_invocation(
+ &verb.invocation_parser,
+ Some(selection),
+ &cc.app.other_path,
+ if let Some(inv) = invocation {
+ &inv.args
+ } else {
+ &None
+ },
+ );
+ match external_execution.to_cmd_result(w, exec_builder, &cc.app.con)? {
+ CmdResult::Keep => {}
+ CmdResult::RefreshState { .. } => {
+ refresh = true;
+ }
+ cr => {
+ return Ok(CmdResult::error(format!("unexpected execution result: {:?}", cr)));
+ }
+ }
+ }
+ Ok(if refresh {
+ CmdResult::RefreshState { clear_cache: true }
+ } else {
+ CmdResult::Keep
+ })
+ }
+ }
+
+ fn execute_sequence(
+ &mut self,
+ w: &mut W,
+ verb: &Verb,
+ seq_ex: &SequenceExecution,
+ invocation: Option<&VerbInvocation>,
+ cc: &CmdContext,
+ ) -> Result<CmdResult, ProgramError> {
+ Ok(CmdResult::error("sequence execution not yet implemented on staging area"))
+ }
+}
+