Separate config.yml file from rust files

Also be less aggressive for version compatibility.

Use the following logic:

Knowing that we use `{major}.{minor}.{patch}` versioning,

- Major version mismatch are incompatible. Fail with error, suggesting to
  visit the Upgrade Guide.
- Minor version updates and patch fixes are compatible. Suggest user to
  update the config file version manually. Or visit the Upgrade Guide.

- However, if the config file has greater value for minor version
  than the app, also fail with error. Suggesting the user to visit Upgrade
  Guide. Though in this case, the user will be downgrading.

Ref: https://github.com/sayanarijit/xplr/issues/45
pull/47/head
Arijit Basu 3 years ago committed by Arijit Basu
parent 233f6d44a5
commit 6aa3df301e

1
Cargo.lock generated

@ -1370,6 +1370,7 @@ dependencies = [
"crossterm",
"dirs",
"handlebars",
"lazy_static",
"mime_guess",
"notify",
"serde",

@ -1,6 +1,6 @@
[package]
name = "xplr"
version = "0.3.13" # Update app.rs and default.nix
version = "0.3.13" # Update default_config.rs and default.nix
authors = ["Arijit Basu <sayanarijit@gmail.com>"]
edition = "2018"
description = "A hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf"
@ -23,6 +23,7 @@ mime_guess = "2.0.3"
anyhow = "1.0"
chrono = { version = "0.4", features = ["serde"] }
notify = "4.0.12"
lazy_static = "1.4.0"
[dev-dependencies]
criterion = "0.3"

@ -1,5 +1,6 @@
use crate::config::Config;
use crate::config::Mode;
use crate::default_config::DEFAULT_CONFIG;
use crate::input::Key;
use anyhow::{bail, Result};
use chrono::{DateTime, Utc};
@ -12,7 +13,6 @@ use std::fs;
use std::io;
use std::path::PathBuf;
pub const VERSION: &str = "v0.3.13"; // Update Cargo.toml and default.nix
pub const TEMPLATE_TABLE_ROW: &str = "TEMPLATE_TABLE_ROW";
pub const UNSUPPORTED_STR: &str = "???";
pub const UPGRADE_GUIDE_LINK: &str = "https://github.com/sayanarijit/xplr/wiki/Upgrade-Guide";
@ -695,19 +695,35 @@ pub enum HelpMenuLine {
Paragraph(String),
}
pub fn is_compatible(existing: &str, required: &str) -> bool {
let mut existing = existing.split('.');
let mut required = required.split('.');
let mut major_existing = existing.next().unwrap_or_default();
let mut major_required = required.next().unwrap_or_default();
if major_existing == "v0" && major_required == "v0" {
major_existing = existing.next().unwrap_or_default();
major_required = required.next().unwrap_or_default();
/// Major version should be the same.
/// Config minor version should be lower.
/// Patch/fix version can be anything.
pub fn is_compatible(configv: &str, appv: &str) -> bool {
let mut configv = configv
.strip_prefix('v')
.unwrap_or_default()
.split('.')
.map(|c| c.parse::<u64>().unwrap());
let mut appv = appv
.strip_prefix('v')
.unwrap_or_default()
.split('.')
.map(|c| c.parse::<u64>().unwrap());
let mut major_configv = configv.next().unwrap_or_default();
let mut minor_configv = configv.next().unwrap_or_default();
let mut major_appv = appv.next().unwrap_or_default();
let mut minor_appv = appv.next().unwrap_or_default();
if major_configv == 0 && major_appv == 0 {
major_configv = minor_configv;
minor_configv = configv.next().unwrap_or_default();
major_appv = minor_appv;
minor_appv = appv.next().unwrap_or_default();
};
major_existing == major_required
major_configv == major_appv && minor_configv <= minor_appv
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -741,7 +757,9 @@ impl App {
Config::default()
};
if config.version != VERSION && !is_compatible(&config.version, VERSION) {
if config.version != DEFAULT_CONFIG.version
&& !is_compatible(&config.version, &DEFAULT_CONFIG.version)
{
bail!(
"incompatible configuration version in {}
You config version is : {}
@ -749,16 +767,17 @@ impl App {
Visit {}",
config_file.to_string_lossy().to_string(),
config.version,
VERSION,
DEFAULT_CONFIG.version,
UPGRADE_GUIDE_LINK,
)
};
let mode = config
.modes
.get(&"default".to_string())
.map(|k| k.to_owned())
.unwrap_or_default();
let mode = match config.modes.get(&"default".to_string()) {
Some(m) => m.clone(),
None => {
bail!("'default' mode is missing")
}
};
let pid = std::process::id();
let session_path = dirs::runtime_dir()
@ -779,7 +798,7 @@ impl App {
}
Ok(Self {
version: VERSION.to_string(),
version: DEFAULT_CONFIG.version.clone(),
config,
pwd: pwd.to_string_lossy().to_string(),
directory_buffers: Default::default(),

@ -1,12 +1,10 @@
use crate::app::ExternalMsg;
use crate::app::HelpMenuLine;
use crate::app::VERSION;
use crate::default_config::DEFAULT_CONFIG;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::collections::HashMap;
use tui::layout::Constraint as TuiConstraint;
use tui::style::Color;
use tui::style::Modifier;
use tui::style::Style;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -46,33 +44,7 @@ pub struct FileTypesConfig {
impl Default for FileTypesConfig {
fn default() -> Self {
FileTypesConfig {
directory: FileTypeConfig {
icon: "ð".into(),
style: Style::default()
.add_modifier(Modifier::BOLD)
.fg(Color::Cyan),
custom: Default::default(),
},
file: FileTypeConfig {
icon: "ƒ".into(),
style: Default::default(),
custom: Default::default(),
},
symlink: FileTypeConfig {
icon: "§".into(),
style: Style::default()
.add_modifier(Modifier::ITALIC)
.fg(Color::Cyan),
custom: Default::default(),
},
mime_essence: Default::default(),
extension: Default::default(),
special: Default::default(),
}
DEFAULT_CONFIG.filetypes.clone()
}
}
@ -168,62 +140,7 @@ pub struct GeneralConfig {
impl Default for GeneralConfig {
fn default() -> Self {
let yaml = r###"
show_hidden: false
table:
header:
cols:
- format: "│ path"
- format: "type"
- format: " index"
height: 1
style:
add_modifier:
bits: 1
sub_modifier:
bits: 0
row:
cols:
- format: "{{{tree}}}{{{prefix}}}{{{icon}}} {{{relativePath}}}{{#if isDir}}/{{/if}}{{{suffix}}}"
- format: "{{{mimeEssence}}}"
- format: "{{#if isBeforeFocus}}-{{else}} {{/if}}{{{relativeIndex}}}/{{{index}}}/{{{total}}}"
col_spacing: 3
col_widths:
- percentage: 60
- percentage: 20
- percentage: 20
tree:
- format: "├─"
- format: "├─"
- format: "╰─"
normal_ui:
prefix: " "
suffix: ""
focused_ui:
prefix: "▸["
suffix: "]"
style:
fg: Blue
add_modifier:
bits: 1
sub_modifier:
bits: 0
selection_ui:
prefix: " {"
suffix: "}"
style:
fg: LightGreen
add_modifier:
bits: 1
sub_modifier:
bits: 0
"###;
serde_yaml::from_str(yaml).unwrap()
DEFAULT_CONFIG.general.clone()
}
}
@ -245,156 +162,6 @@ pub struct KeyBindings {
pub default: Option<Action>,
}
impl Default for KeyBindings {
fn default() -> Self {
let on_key: BTreeMap<String, Action> = serde_yaml::from_str(
r###"
up:
help: up [k]
messages:
- FocusPrevious
k:
messages:
- FocusPrevious
down:
help: down [j]
messages:
- FocusNext
j:
messages:
- FocusNext
right:
help: enter [l]
messages:
- Enter
l:
messages:
- Enter
left:
help: back [h]
messages:
- Back
h:
messages:
- Back
g:
help: go to
messages:
- SwitchMode: go to
G:
help: go to bottom
messages:
- FocusLast
ctrl-f:
help: search [/]
messages:
- SwitchMode: search
- SetInputBuffer: ""
- Explore
/:
messages:
- SwitchMode: search
- SetInputBuffer: ""
- Explore
d:
help: delete
messages:
- SwitchMode: delete
":":
help: action
messages:
- SwitchMode: action
space:
help: toggle selection [v]
messages:
- ToggleSelection
- FocusNext
v:
messages:
- ToggleSelection
- FocusNext
r:
help: rename
messages:
- SwitchMode: rename
- BashExecSilently: |
echo "SetInputBuffer: $(basename ${XPLR_FOCUS_PATH})" >> "${XPLR_PIPE_MSG_IN:?}"
".":
help: show hidden
messages:
- ToggleNodeFilter:
filter: RelativePathDoesNotStartWith
input: .
- Explore
enter:
help: quit with result
messages:
- PrintResultAndQuit
"#":
messages:
- PrintAppStateAndQuit
"?":
help: global help menu
messages:
- BashExec: |
${PAGER:-less} "${XPLR_PIPE_GLOBAL_HELP_MENU_OUT}"
ctrl-c:
help: cancel & quit [q]
messages:
- Terminate
q:
messages:
- Terminate
"###,
)
.unwrap();
let default = Some(Action {
help: None,
messages: vec![ExternalMsg::SwitchMode("default".into())],
});
let on_number = Some(Action {
help: Some("input".to_string()),
messages: vec![
ExternalMsg::ResetInputBuffer,
ExternalMsg::SwitchMode("number".into()),
ExternalMsg::BufferInputFromKey,
],
});
Self {
on_key,
on_alphabet: Default::default(),
on_number,
on_special_character: Default::default(),
default,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mode {
pub name: String,
@ -405,7 +172,6 @@ pub struct Mode {
#[serde(default)]
pub extra_help: Option<String>,
#[serde(default)]
pub key_bindings: KeyBindings,
}
@ -464,17 +230,6 @@ impl Mode {
}
}
impl Default for Mode {
fn default() -> Self {
Self {
name: "default".into(),
help: Default::default(),
extra_help: Default::default(),
key_bindings: Default::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub version: String,
@ -485,523 +240,16 @@ pub struct Config {
#[serde(default)]
pub filetypes: FileTypesConfig,
#[serde(default)]
pub modes: HashMap<String, Mode>,
}
impl Default for Config {
fn default() -> Self {
let search_mode: Mode = serde_yaml::from_str(
r###"
name: search
key_bindings:
on_key:
enter:
help: focus
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- SwitchMode: default
- Explore
up:
help: up
messages:
- FocusPrevious
down:
help: down
messages:
- FocusNext
right:
help: enter
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- Enter
- SetInputBuffer: ""
- Explore
left:
help: back
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- Back
- SetInputBuffer: ""
- Explore
esc:
help: cancel
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- SwitchMode: default
- Explore
backspace:
help: clear
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- SetInputBuffer: ""
- Explore
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- BufferInputFromKey
- AddNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- Explore
"###,
)
.unwrap();
let goto_mode: Mode = serde_yaml::from_str(
r###"
name: go to
key_bindings:
on_key:
g:
help: top
messages:
- FocusFirst
- SwitchMode: default
x:
help: open in gui
messages:
- BashExecSilently: |
OPENER="$(which xdg-open)"
${OPENER:-open} "${XPLR_FOCUS_PATH:?}" &> /dev/null
- SwitchMode: default
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- SwitchMode: default
"###,
)
.unwrap();
let action_mode: Mode = serde_yaml::from_str(
r###"
name: action to
key_bindings:
on_number:
help: go to index
messages:
- ResetInputBuffer
- SwitchMode: number
- BufferInputFromKey
on_key:
"!":
help: shell
messages:
- Call:
command: bash
- Explore
- SwitchMode: default
c:
help: create
messages:
- SwitchMode: create
e:
help: open in editor
messages:
- BashExec: |
${EDITOR:-vi} "${XPLR_FOCUS_PATH:?}"
- SwitchMode: default
s:
help: selection operations
messages:
- SwitchMode: selection ops
l:
help: logs
messages:
- BashExec: |
cat "${XPLR_PIPE_LOGS_OUT}"
read -p "[enter to continue]"
- SwitchMode: default
ctrl-c:
help: cancel & quit [q]
messages:
- Terminate
q:
messages:
- Terminate
default:
messages:
- SwitchMode: default
"###,
)
.unwrap();
let selection_ops_mode: Mode = serde_yaml::from_str(
r###"
name: selection ops
key_bindings:
on_key:
c:
help: copy here
messages:
- BashExec: |
(while IFS= read -r line; do
if cp -vr "${line:?}" ./; then
echo "LogSuccess: $line copied to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to copy $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
fi
done < "${XPLR_PIPE_SELECTION_OUT:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
echo ClearSelection >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- SwitchMode: default
m:
help: move here
messages:
- BashExec: |
(while IFS= read -r line; do
if mv -v "${line:?}" ./; then
echo "LogSuccess: $line moved to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to move $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
fi
done < "${XPLR_PIPE_SELECTION_OUT:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- SwitchMode: default
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- SwitchMode: default
"###,
)
.unwrap();
let number_mode: Mode = serde_yaml::from_str(
r###"
name: number
key_bindings:
on_key:
up:
help: to up [k]
messages:
- FocusPreviousByRelativeIndexFromInput
- SwitchMode: default
k:
messages:
- FocusPreviousByRelativeIndexFromInput
- SwitchMode: default
down:
help: to down [j]
messages:
- FocusNextByRelativeIndexFromInput
- SwitchMode: default
j:
messages:
- FocusNextByRelativeIndexFromInput
- SwitchMode: default
enter:
help: to index
messages:
- FocusByIndexFromInput
- SwitchMode: default
backspace:
help: clear
messages:
- ResetInputBuffer
ctrl-c:
help: cancel & quit
messages:
- Terminate
on_number:
help: input
messages:
- BufferInputFromKey
default:
messages:
- SwitchMode: default
"###,
)
.unwrap();
let create_mode: Mode = serde_yaml::from_str(
r###"
name: create
key_bindings:
on_key:
f:
help: create file
messages:
- SwitchMode: create file
- SetInputBuffer: ""
d:
help: create directory
messages:
- SwitchMode: create directory
- SetInputBuffer: ""
esc:
help: cancel
messages:
- SwitchMode: default
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- SwitchMode: default
"###,
)
.unwrap();
let create_file_mode: Mode = serde_yaml::from_str(
r###"
name: create file
key_bindings:
on_key:
enter:
help: create file
messages:
- BashExecSilently: |
PTH="${XPLR_INPUT_BUFFER:?}"
if touch "${PTH:?}"; then
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
echo "LogSuccess: $PTH created" >> "${XPLR_PIPE_MSG_IN:?}"
echo "FocusPath: $PTH" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to create $PTH" >> "${XPLR_PIPE_MSG_IN:?}"
echo Refresh >> "${XPLR_PIPE_MSG_IN:?}"
fi
- SwitchMode: default
backspace:
help: clear
messages:
- SetInputBuffer: ""
esc:
help: cancel
messages:
- SwitchMode: default
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- BufferInputFromKey
"###,
)
.unwrap();
let create_dir_mode: Mode = serde_yaml::from_str(
r###"
name: create directory
key_bindings:
on_key:
enter:
help: create directory
messages:
- BashExecSilently: |
PTH="${XPLR_INPUT_BUFFER:?}"
if mkdir -p "$PTH"; then
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
echo "LogSuccess: $PTH created" >> "${XPLR_PIPE_MSG_IN:?}"
echo "FocusPath: $PTH" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to create $PTH" >> "${XPLR_PIPE_MSG_IN:?}"
fi
- SwitchMode: default
backspace:
help: clear
messages:
- SetInputBuffer: ""
esc:
help: cancel
messages:
- SwitchMode: default
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- BufferInputFromKey
"###,
)
.unwrap();
let rename_mode: Mode = serde_yaml::from_str(
r###"
name: rename
key_bindings:
on_key:
enter:
help: rename
messages:
- BashExecSilently: |
SRC="${XPLR_FOCUS_PATH:?}"
TARGET="${XPLR_INPUT_BUFFER:?}"
if mv -v "${SRC:?}" "${TARGET:?}"; then
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
echo "LogSuccess: $SRC renamed to $TARGET" >> "${XPLR_PIPE_MSG_IN:?}"
echo "FocusPath: $TARGET" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to rename $SRC to $TARGET" >> "${XPLR_PIPE_MSG_IN:?}"
fi
- SwitchMode: default
backspace:
help: clear
messages:
- SetInputBuffer: ""
esc:
help: cancel
messages:
- SwitchMode: default
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- BufferInputFromKey
"###,
)
.unwrap();
let delete_mode: Mode = serde_yaml::from_str(
r###"
name: delete
key_bindings:
on_key:
d:
help: delete
messages:
- BashExec: |
(while IFS= read -r line; do
if [ -d "$line" ]; then
if rmdir -v "${line:?}"; then
echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
else
if rm -v "${line:?}"; then
echo "FocusNext" >> "${XPLR_PIPE_MSG_IN:?}"
echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
fi
done < "${XPLR_PIPE_RESULT_OUT:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- SwitchMode: default
D:
help: force delete
messages:
- BashExec: |
(while IFS= read -r line; do
if rm -rfv "${line:?}"; then
echo "FocusNext" >> "${XPLR_PIPE_MSG_IN:?}"
echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
done < "${XPLR_PIPE_RESULT_OUT:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- SwitchMode: default
- Explore
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- SwitchMode: default
"###,
)
.unwrap();
let mut modes: HashMap<String, Mode> = Default::default();
modes.insert("default".into(), Mode::default());
modes.insert("go to".into(), goto_mode);
modes.insert("number".into(), number_mode);
modes.insert("create".into(), create_mode);
modes.insert("rename".into(), rename_mode);
modes.insert("create file".into(), create_file_mode);
modes.insert("create directory".into(), create_dir_mode);
modes.insert("delete".into(), delete_mode);
modes.insert("action".into(), action_mode);
modes.insert("search".into(), search_mode);
modes.insert("selection ops".into(), selection_ops_mode);
Self {
version: VERSION.into(),
version: DEFAULT_CONFIG.version.clone(),
general: Default::default(),
filetypes: Default::default(),
modes,
modes: DEFAULT_CONFIG.modes.clone(),
}
}
}

@ -0,0 +1,723 @@
version: v0.3.13
general:
show_hidden: false
table:
header:
cols:
- format: │ path
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
- format: type
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
- format: ' index'
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
style:
fg: null
bg: null
add_modifier:
bits: 1
sub_modifier:
bits: 0
height: 1
row:
cols:
- format: '{{{tree}}}{{{prefix}}}{{{icon}}} {{{relativePath}}}{{#if isDir}}/{{/if}}{{{suffix}}}'
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
- format: '{{{mimeEssence}}}'
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
- format: '{{#if isBeforeFocus}}-{{else}} {{/if}}{{{relativeIndex}}}/{{{index}}}/{{{total}}}'
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
height: 0
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
tree:
- format: ├─
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
- format: ├─
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
- format: ╰─
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
col_spacing: 3
col_widths:
- percentage: 60
- percentage: 20
- percentage: 20
normal_ui:
prefix: ' '
suffix: ''
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
focused_ui:
prefix: ▸[
suffix: ']'
style:
fg: Blue
bg: null
add_modifier:
bits: 1
sub_modifier:
bits: 0
selection_ui:
prefix: ' {'
suffix: '}'
style:
fg: LightGreen
bg: null
add_modifier:
bits: 1
sub_modifier:
bits: 0
filetypes:
directory:
icon: ð
style:
fg: Cyan
bg: null
add_modifier:
bits: 1
sub_modifier:
bits: 0
custom: {}
file:
icon: ƒ
style:
fg: null
bg: null
add_modifier:
bits: 0
sub_modifier:
bits: 0
custom: {}
symlink:
icon: §
style:
fg: Cyan
bg: null
add_modifier:
bits: 4
sub_modifier:
bits: 0
custom: {}
mime_essence: {}
extension: {}
special: {}
modes:
selection ops:
name: selection ops
help: null
extra_help: null
key_bindings:
on_key:
c:
help: copy here
messages:
- BashExec: |
(while IFS= read -r line; do
if cp -vr "${line:?}" ./; then
echo "LogSuccess: $line copied to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to copy $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
fi
done < "${XPLR_PIPE_SELECTION_OUT:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
echo ClearSelection >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- SwitchMode: default
m:
help: move here
messages:
- BashExec: |
(while IFS= read -r line; do
if mv -v "${line:?}" ./; then
echo "LogSuccess: $line moved to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to move $line to $PWD" >> "${XPLR_PIPE_MSG_IN:?}"
fi
done < "${XPLR_PIPE_SELECTION_OUT:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- SwitchMode: default
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- SwitchMode: default
create file:
name: create file
help: null
extra_help: null
key_bindings:
on_key:
enter:
help: create file
messages:
- BashExecSilently: |
PTH="${XPLR_INPUT_BUFFER:?}"
if touch "${PTH:?}"; then
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
echo "LogSuccess: $PTH created" >> "${XPLR_PIPE_MSG_IN:?}"
echo "FocusPath: $PTH" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to create $PTH" >> "${XPLR_PIPE_MSG_IN:?}"
echo Refresh >> "${XPLR_PIPE_MSG_IN:?}"
fi
- SwitchMode: default
backspace:
help: clear
messages:
- SetInputBuffer: ""
esc:
help: cancel
messages:
- SwitchMode: default
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- BufferInputFromKey
create:
name: create
help: null
extra_help: null
key_bindings:
on_key:
ctrl-c:
help: cancel & quit
messages:
- Terminate
d:
help: create directory
messages:
- SwitchMode: create directory
- SetInputBuffer: ''
esc:
help: cancel
messages:
- SwitchMode: default
f:
help: create file
messages:
- SwitchMode: create file
- SetInputBuffer: ''
on_alphabet: null
on_number: null
on_special_character: null
default:
help: null
messages:
- SwitchMode: default
rename:
name: rename
help: null
extra_help: null
key_bindings:
on_key:
enter:
help: rename
messages:
- BashExecSilently: |
SRC="${XPLR_FOCUS_PATH:?}"
TARGET="${XPLR_INPUT_BUFFER:?}"
if mv -v "${SRC:?}" "${TARGET:?}"; then
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
echo "LogSuccess: $SRC renamed to $TARGET" >> "${XPLR_PIPE_MSG_IN:?}"
echo "FocusPath: $TARGET" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to rename $SRC to $TARGET" >> "${XPLR_PIPE_MSG_IN:?}"
fi
- SwitchMode: default
backspace:
help: clear
messages:
- SetInputBuffer: ""
esc:
help: cancel
messages:
- SwitchMode: default
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- BufferInputFromKey
default:
name: default
help: null
extra_help: null
key_bindings:
on_key:
'#':
help: null
messages:
- PrintAppStateAndQuit
.:
help: show hidden
messages:
- ToggleNodeFilter:
filter: RelativePathDoesNotStartWith
input: .
case_sensitive: false
- Explore
/:
help: null
messages:
- SwitchMode: search
- SetInputBuffer: ''
- Explore
':':
help: action
messages:
- SwitchMode: action
'?':
help: global help menu
messages:
- BashExec: |
${PAGER:-less} "${XPLR_PIPE_GLOBAL_HELP_MENU_OUT}"
G:
help: go to bottom
messages:
- FocusLast
ctrl-c:
help: cancel & quit [q]
messages:
- Terminate
ctrl-f:
help: search [/]
messages:
- SwitchMode: search
- SetInputBuffer: ''
- Explore
d:
help: delete
messages:
- SwitchMode: delete
down:
help: down [j]
messages:
- FocusNext
enter:
help: quit with result
messages:
- PrintResultAndQuit
g:
help: go to
messages:
- SwitchMode: go to
h:
help: null
messages:
- Back
j:
help: null
messages:
- FocusNext
k:
help: null
messages:
- FocusPrevious
l:
help: null
messages:
- Enter
left:
help: back [h]
messages:
- Back
q:
help: null
messages:
- Terminate
r:
help: rename
messages:
- SwitchMode: rename
- BashExecSilently: |
echo "SetInputBuffer: $(basename ${XPLR_FOCUS_PATH})" >> "${XPLR_PIPE_MSG_IN:?}"
right:
help: enter [l]
messages:
- Enter
space:
help: toggle selection [v]
messages:
- ToggleSelection
- FocusNext
up:
help: up [k]
messages:
- FocusPrevious
v:
help: null
messages:
- ToggleSelection
- FocusNext
on_alphabet: null
on_number:
help: input
messages:
- ResetInputBuffer
- SwitchMode: number
- BufferInputFromKey
on_special_character: null
default:
help: null
messages:
- SwitchMode: default
go to:
name: go to
help: null
extra_help: null
key_bindings:
on_key:
ctrl-c:
help: cancel & quit
messages:
- Terminate
g:
help: top
messages:
- FocusFirst
- SwitchMode: default
x:
help: open in gui
messages:
- BashExecSilently: |
OPENER="$(which xdg-open)"
${OPENER:-open} "${XPLR_FOCUS_PATH:?}" &> /dev/null
- SwitchMode: default
on_alphabet: null
on_number: null
on_special_character: null
default:
help: null
messages:
- SwitchMode: default
number:
name: number
help: null
extra_help: null
key_bindings:
on_key:
backspace:
help: clear
messages:
- ResetInputBuffer
ctrl-c:
help: cancel & quit
messages:
- Terminate
down:
help: to down [j]
messages:
- FocusNextByRelativeIndexFromInput
- SwitchMode: default
enter:
help: to index
messages:
- FocusByIndexFromInput
- SwitchMode: default
j:
help: null
messages:
- FocusNextByRelativeIndexFromInput
- SwitchMode: default
k:
help: null
messages:
- FocusPreviousByRelativeIndexFromInput
- SwitchMode: default
up:
help: to up [k]
messages:
- FocusPreviousByRelativeIndexFromInput
- SwitchMode: default
on_alphabet: null
on_number:
help: input
messages:
- BufferInputFromKey
on_special_character: null
default:
help: null
messages:
- SwitchMode: default
delete:
name: delete
help: null
extra_help: null
key_bindings:
on_key:
d:
help: delete
messages:
- BashExec: |
(while IFS= read -r line; do
if [ -d "$line" ]; then
if rmdir -v "${line:?}"; then
echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
else
if rm -v "${line:?}"; then
echo "FocusNext" >> "${XPLR_PIPE_MSG_IN:?}"
echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
fi
done < "${XPLR_PIPE_RESULT_OUT:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- SwitchMode: default
D:
help: force delete
messages:
- BashExec: |
(while IFS= read -r line; do
if rm -rfv "${line:?}"; then
echo "FocusNext" >> "${XPLR_PIPE_MSG_IN:?}"
echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
done < "${XPLR_PIPE_RESULT_OUT:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- SwitchMode: default
- Explore
ctrl-c:
help: cancel & quit
messages:
- Terminate
default:
messages:
- SwitchMode: default
action:
name: action to
help: null
extra_help: null
key_bindings:
on_number:
help: go to index
messages:
- ResetInputBuffer
- SwitchMode: number
- BufferInputFromKey
on_key:
"!":
help: shell
messages:
- Call:
command: bash
- Explore
- SwitchMode: default
c:
help: create
messages:
- SwitchMode: create
e:
help: open in editor
messages:
- BashExec: |
${EDITOR:-vi} "${XPLR_FOCUS_PATH:?}"
- SwitchMode: default
s:
help: selection operations
messages:
- SwitchMode: selection ops
l:
help: logs
messages:
- BashExec: |
cat "${XPLR_PIPE_LOGS_OUT}"
read -p "[enter to continue]"
- SwitchMode: default
ctrl-c:
help: cancel & quit [q]
messages:
- Terminate
q:
messages:
- Terminate
default:
messages:
- SwitchMode: default
search:
name: search
help: null
extra_help: null
key_bindings:
on_key:
backspace:
help: clear
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- SetInputBuffer: ''
- Explore
ctrl-c:
help: cancel & quit
messages:
- Terminate
down:
help: down
messages:
- FocusNext
enter:
help: focus
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- SwitchMode: default
- Explore
esc:
help: cancel
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- SwitchMode: default
- Explore
left:
help: back
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- Back
- SetInputBuffer: ''
- Explore
right:
help: enter
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- Enter
- SetInputBuffer: ''
- Explore
up:
help: up
messages:
- FocusPrevious
on_alphabet: null
on_number: null
on_special_character: null
default:
help: null
messages:
- RemoveNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- BufferInputFromKey
- AddNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- Explore

@ -0,0 +1,8 @@
pub const DEFAULT_CONFIG_YAML: &str = include_str!("config.yml");
use crate::config::Config;
use lazy_static::lazy_static;
lazy_static! {
pub static ref DEFAULT_CONFIG: Config = serde_yaml::from_str(DEFAULT_CONFIG_YAML).unwrap();
}

@ -5,6 +5,7 @@
pub mod app;
pub mod auto_refresher;
pub mod config;
pub mod default_config;
pub mod event_reader;
pub mod explorer;
pub mod input;

@ -95,7 +95,8 @@ fn main() -> Result<()> {
if app.version() != &app.config().version {
let msg = format!(
"version mismatch, to update your config file to {}, visit {}",
"you can update your config file version from {} to {}, visit {} for more info.",
app.config().version,
app.version(),
app::UPGRADE_GUIDE_LINK,
);

@ -1,14 +1,40 @@
use xplr::*;
#[test]
fn test_version_incompatibility() {
assert!(app::is_compatible("v0.1.0", "v0.1.2"));
assert!(app::is_compatible("v0.2.0", "v0.2.2"));
assert!(!app::is_compatible("v0.1.0", "v0.2.0"));
assert!(!app::is_compatible("v0.1.0", "v1.1.0"));
fn test_version_compatibility() {
// Config version == app version
assert!(app::is_compatible("v0.1.0", "v0.1.0"));
assert!(app::is_compatible("v1.1.0", "v1.1.0"));
assert!(app::is_compatible("v1.1.0", "v1.1.1"));
assert!(app::is_compatible("v1.1.0", "v1.2.1"));
assert!(app::is_compatible("v1.1.0", "v1.2.1"));
assert!(!app::is_compatible("v1.1.0", "v2.0.0"));
// Config major version < app major version
assert!(!app::is_compatible("v0.1.0", "v0.2.0"));
assert!(!app::is_compatible("v0.2.0", "v0.12.0"));
assert!(!app::is_compatible("v1.0.0", "v2.0.0"));
assert!(!app::is_compatible("v2.0.0", "v12.0.0"));
// Config minor version < app minor version
assert!(app::is_compatible("v0.0.1", "v0.0.2"));
assert!(app::is_compatible("v0.0.2", "v0.0.12"));
assert!(app::is_compatible("v1.1.0", "v1.2.0"));
assert!(app::is_compatible("v1.2.0", "v1.12.0"));
// Config patch version < app patch version
assert!(app::is_compatible("v1.1.1", "v1.1.2"));
assert!(app::is_compatible("v1.1.2", "v1.1.12"));
// Config major version > app major version
assert!(!app::is_compatible("v0.2.0", "v0.1.0"));
assert!(!app::is_compatible("v0.12.0", "v0.2.0"));
assert!(!app::is_compatible("v2.0.0", "v1.0.0"));
assert!(!app::is_compatible("v12.0.0", "v2.0.0"));
// Config minor version > app minor version
assert!(!app::is_compatible("v0.0.2", "v0.0.1"));
assert!(!app::is_compatible("v0.0.12", "v0.0.2"));
assert!(!app::is_compatible("v1.2.0", "v1.1.0"));
assert!(!app::is_compatible("v1.12.0", "v1.2.0"));
// Config patch version > app patch version
assert!(app::is_compatible("v1.1.2", "v1.1.1"));
assert!(app::is_compatible("v1.1.12", "v1.1.2"));
}

Loading…
Cancel
Save