Logging, testing and other improvements

pull/3/head
Arijit Basu 3 years ago
parent 71a23e1f64
commit af8a637030
No known key found for this signature in database
GPG Key ID: 7D7BF809E7378863

44
Cargo.lock generated

@ -132,6 +132,20 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"serde",
"time",
"winapi",
]
[[package]]
name = "clap"
version = "2.33.3"
@ -341,7 +355,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
@ -519,6 +533,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@ -940,6 +964,17 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
@ -1026,6 +1061,12 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.73"
@ -1126,6 +1167,7 @@ name = "xplr"
version = "0.2.14"
dependencies = [
"anyhow",
"chrono",
"criterion",
"crossterm",
"dirs",

@ -22,6 +22,7 @@ serde_yaml = "0.8"
handlebars = "3.5"
mime_guess = "2.0.3"
anyhow = "1.0"
chrono = { version = "0.4", features = ["serde"] }
[dev-dependencies]
criterion = "0.3"

@ -2,6 +2,7 @@ use crate::config::Config;
use crate::config::Mode;
use crate::input::Key;
use anyhow::{bail, Result};
use chrono::{DateTime, Utc};
use mime_guess;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
@ -162,7 +163,7 @@ pub enum InternalMsg {
HandleKey(Key),
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum NodeFilter {
RelativePathIs,
RelativePathIsNot,
@ -376,7 +377,7 @@ impl NodeFilterApplicable {
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct NodeFilterFromInputString {
pub struct NodeFilterFromInput {
filter: NodeFilter,
#[serde(default)]
case_sensitive: bool,
@ -407,14 +408,16 @@ pub enum ExternalMsg {
FocusFirst,
FocusLast,
FocusPath(String),
FocusPathFromInput,
FocusByIndex(usize),
FocusByIndexFromInput,
FocusByFileName(String),
ChangeDirectory(String),
Enter,
Back,
BufferString(String),
BufferStringFromKey,
BufferInput(String),
BufferInputFromKey,
SetInputBuffer(String),
ResetInputBuffer,
SwitchMode(String),
Call(Command),
@ -425,8 +428,11 @@ pub enum ExternalMsg {
AddNodeFilter(NodeFilterApplicable),
RemoveNodeFilter(NodeFilterApplicable),
ToggleNodeFilter(NodeFilterApplicable),
AddNodeFilterFromInputString(NodeFilterFromInputString),
AddNodeFilterFromInput(NodeFilterFromInput),
ResetNodeFilters,
LogInfo(String),
LogSuccess(String),
LogError(String),
PrintResultAndQuit,
PrintAppStateAndQuit,
Debug(String),
@ -463,11 +469,17 @@ pub struct Task {
priority: usize,
msg: MsgIn,
key: Option<Key>,
created_at: DateTime<Utc>,
}
impl Task {
pub fn new(priority: usize, msg: MsgIn, key: Option<Key>) -> Self {
Self { priority, msg, key }
Self {
priority,
msg,
key,
created_at: Utc::now(),
}
}
}
@ -476,7 +488,10 @@ impl Ord for Task {
// Notice that the we flip the ordering on costs.
// In case of a tie we compare positions - this step is necessary
// to make implementations of `PartialEq` and `Ord` consistent.
other.priority.cmp(&self.priority)
other
.priority
.cmp(&self.priority)
.then_with(|| other.created_at.cmp(&self.created_at))
}
}
impl PartialOrd for Task {
@ -485,6 +500,48 @@ impl PartialOrd for Task {
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum LogLevel {
Info,
Success,
Error,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Log {
pub level: LogLevel,
pub message: String,
pub created_at: DateTime<Utc>,
}
impl Log {
pub fn new(level: LogLevel, message: String) -> Self {
Self {
level,
message,
created_at: Utc::now(),
}
}
}
impl std::fmt::Display for Log {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let level_str = match self.level {
LogLevel::Info => "INFO ",
LogLevel::Success => "SUCCESS",
LogLevel::Error => "ERROR ",
};
write!(f, "[{}] {} {}", &self.created_at, level_str, &self.message)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum HelpMenuLine {
KeyMap(String, String),
Paragraph(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct App {
config: Config,
@ -499,6 +556,7 @@ pub struct App {
session_path: String,
pipe: Pipe,
explorer_config: ExplorerConfig,
logs: Vec<Log>,
}
impl App {
@ -569,6 +627,7 @@ impl App {
session_path: session_path.clone(),
pipe: Pipe::from_session_path(&session_path),
explorer_config,
logs: Default::default(),
})
}
}
@ -621,14 +680,16 @@ impl App {
self.focus_next_by_relative_index_from_input()
}
ExternalMsg::FocusPath(p) => self.focus_path(&p),
ExternalMsg::FocusPathFromInput => self.focus_path_from_input(),
ExternalMsg::FocusByIndex(i) => self.focus_by_index(i),
ExternalMsg::FocusByIndexFromInput => self.focus_by_index_from_input(),
ExternalMsg::FocusByFileName(n) => self.focus_by_file_name(&n),
ExternalMsg::ChangeDirectory(dir) => self.change_directory(&dir),
ExternalMsg::Enter => self.enter(),
ExternalMsg::Back => self.back(),
ExternalMsg::BufferString(input) => self.buffer_string(&input),
ExternalMsg::BufferStringFromKey => self.buffer_string_from_key(key),
ExternalMsg::BufferInput(input) => self.buffer_input(&input),
ExternalMsg::BufferInputFromKey => self.buffer_input_from_key(key),
ExternalMsg::SetInputBuffer(input) => self.set_input_buffer(input),
ExternalMsg::ResetInputBuffer => self.reset_input_buffer(),
ExternalMsg::SwitchMode(mode) => self.switch_mode(&mode),
ExternalMsg::Call(cmd) => self.call(cmd),
@ -637,12 +698,13 @@ impl App {
ExternalMsg::ToggleSelection => self.toggle_selection(),
ExternalMsg::ClearSelection => self.clear_selection(),
ExternalMsg::AddNodeFilter(f) => self.add_node_filter(f),
ExternalMsg::AddNodeFilterFromInputString(f) => {
self.add_node_filter_from_input_string(f)
}
ExternalMsg::AddNodeFilterFromInput(f) => self.add_node_filter_from_input(f),
ExternalMsg::RemoveNodeFilter(f) => self.remove_node_filter(f),
ExternalMsg::ToggleNodeFilter(f) => self.toggle_node_filter(f),
ExternalMsg::ResetNodeFilters => self.reset_node_filters(),
ExternalMsg::LogInfo(l) => self.log_info(l),
ExternalMsg::LogSuccess(l) => self.log_success(l),
ExternalMsg::LogError(l) => self.log_error(l),
ExternalMsg::PrintResultAndQuit => self.print_result_and_quit(),
ExternalMsg::PrintAppStateAndQuit => self.print_app_state_and_quit(),
ExternalMsg::Debug(path) => self.debug(&path),
@ -781,7 +843,7 @@ impl App {
.unwrap_or(Ok(self))
}
fn buffer_string(mut self, input: &String) -> Result<Self> {
fn buffer_input(mut self, input: &String) -> Result<Self> {
if let Some(buf) = self.input_buffer.as_mut() {
buf.extend(input.chars());
} else {
@ -791,14 +853,20 @@ impl App {
Ok(self)
}
fn buffer_string_from_key(self, key: Option<Key>) -> Result<Self> {
fn buffer_input_from_key(self, key: Option<Key>) -> Result<Self> {
if let Some(c) = key.and_then(|k| k.to_char()) {
self.buffer_string(&c.to_string())
self.buffer_input(&c.to_string())
} else {
Ok(self)
}
}
fn set_input_buffer(mut self, string: String) -> Result<Self> {
self.input_buffer = Some(string);
self.msg_out.push_back(MsgOut::Refresh);
Ok(self)
}
fn reset_input_buffer(mut self) -> Result<Self> {
self.input_buffer = None;
self.msg_out.push_back(MsgOut::Refresh);
@ -852,6 +920,14 @@ impl App {
}
}
fn focus_path_from_input(self) -> Result<Self> {
if let Some(p) = self.input_buffer() {
self.focus_path(&p)
} else {
Ok(self)
}
}
fn switch_mode(mut self, mode: &String) -> Result<Self> {
if let Some(mode) = self.config.modes.get(mode) {
self.input_buffer = None;
@ -912,21 +988,18 @@ impl App {
fn add_node_filter(mut self, filter: NodeFilterApplicable) -> Result<Self> {
self.explorer_config.filters.push(filter);
self.msg_out.push_back(MsgOut::Explore);
self.msg_out.push_back(MsgOut::Refresh);
Ok(self)
}
fn add_node_filter_from_input_string(
mut self,
filter: NodeFilterFromInputString,
) -> Result<Self> {
fn add_node_filter_from_input(mut self, filter: NodeFilterFromInput) -> Result<Self> {
if let Some(input) = self.input_buffer() {
self.explorer_config.filters.push(NodeFilterApplicable::new(
filter.filter,
input,
filter.case_sensitive,
));
self.msg_out.push_back(MsgOut::Explore);
self.msg_out.push_back(MsgOut::Refresh);
};
Ok(self)
}
@ -938,7 +1011,7 @@ impl App {
.into_iter()
.filter(|f| f != &filter)
.collect();
self.msg_out.push_back(MsgOut::Explore);
self.msg_out.push_back(MsgOut::Refresh);
Ok(self)
}
@ -960,8 +1033,23 @@ impl App {
Default::default(),
));
};
self.msg_out.push_back(MsgOut::Explore);
self.msg_out.push_back(MsgOut::Refresh);
Ok(self)
}
fn log_info(mut self, message: String) -> Result<Self> {
self.logs.push(Log::new(LogLevel::Info, message));
Ok(self)
}
fn log_success(mut self, message: String) -> Result<Self> {
self.logs.push(Log::new(LogLevel::Success, message));
Ok(self)
}
fn log_error(mut self, message: String) -> Result<Self> {
self.logs.push(Log::new(LogLevel::Error, message));
Ok(self)
}
@ -1068,4 +1156,9 @@ impl App {
pub fn explorer_config(&self) -> &ExplorerConfig {
&self.explorer_config
}
/// Get a reference to the app's logs.
pub fn logs(&self) -> &Vec<Log> {
&self.logs
}
}

@ -1,4 +1,5 @@
use crate::app::ExternalMsg;
use crate::app::HelpMenuLine;
use crate::app::VERSION;
use serde::{Deserialize, Serialize};
use serde_yaml;
@ -293,11 +294,17 @@ impl Default for KeyBindings {
ctrl-f:
help: search [/]
messages:
- ResetNodeFilters
- SwitchMode: search
- SetInputBuffer: ""
- Explore
/:
messages:
- ResetNodeFilters
- SwitchMode: search
- SetInputBuffer: ""
- Explore
d:
help: delete
@ -326,6 +333,7 @@ impl Default for KeyBindings {
- ToggleNodeFilter:
filter: RelativePathDoesNotStartWith
input: .
- Explore
enter:
help: quit with result
@ -336,6 +344,18 @@ impl Default for KeyBindings {
messages:
- PrintAppStateAndQuit
"?":
help: global help menu
messages:
- Call:
command: bash
args:
- -c
- |
echo -e "${XPLR_GLOBAL_HELP_MENU}"
echo
read -p "[enter to continue]"
ctrl-c:
help: cancel & quit [q|esc]
messages:
@ -360,8 +380,9 @@ impl Default for KeyBindings {
let on_number = Some(Action {
help: Some("input".to_string()),
messages: vec![
ExternalMsg::BufferStringFromKey,
ExternalMsg::ResetInputBuffer,
ExternalMsg::SwitchMode("number".into()),
ExternalMsg::BufferInputFromKey,
],
});
@ -389,6 +410,71 @@ pub struct Mode {
pub key_bindings: KeyBindings,
}
impl Mode {
pub fn help_menu(&self) -> Vec<HelpMenuLine> {
let extra_help_lines = self.extra_help.clone().map(|e| {
e.lines()
.map(|l| HelpMenuLine::Paragraph(l.into()))
.collect::<Vec<HelpMenuLine>>()
});
self.help
.clone()
.map(|h| {
h.lines()
.map(|l| HelpMenuLine::Paragraph(l.into()))
.collect()
})
.unwrap_or_else(|| {
extra_help_lines
.unwrap_or_default()
.into_iter()
.chain(self.key_bindings.on_key.iter().filter_map(|(k, a)| {
a.help
.clone()
.map(|h| HelpMenuLine::KeyMap(k.into(), h.into()))
}))
.chain(
self.key_bindings
.on_alphabet
.iter()
.map(|a| ("[a-Z]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), h.into()))
}),
)
.chain(
self.key_bindings
.on_number
.iter()
.map(|a| ("[0-9]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), h.into()))
}),
)
.chain(
self.key_bindings
.on_special_character
.iter()
.map(|a| ("[spcl chars]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), h.into()))
}),
)
.chain(
self.key_bindings
.default
.iter()
.map(|a| ("[default]", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| HelpMenuLine::KeyMap(k.into(), h.into()))
}),
)
.collect()
})
}
}
impl Default for Mode {
fn default() -> Self {
Self {
@ -426,6 +512,7 @@ impl Default for Config {
messages:
- ResetNodeFilters
- SwitchMode: default
- Explore
up:
help: up
@ -440,29 +527,32 @@ impl Default for Config {
right:
help: enter
messages:
- Enter
- ResetInputBuffer
- ResetNodeFilters
- Enter
- SwitchMode: default
- Explore
left:
help: back
messages:
- ResetNodeFilters
- Back
- ResetInputBuffer
- SwitchMode: default
- Explore
esc:
help: cancel
messages:
- ResetNodeFilters
- SwitchMode: default
- Explore
backspace:
help: clear
messages:
- ResetInputBuffer
- SetInputBuffer: ""
- ResetNodeFilters
- Explore
ctrl-c:
help: cancel & quit
@ -471,10 +561,12 @@ impl Default for Config {
default:
messages:
- BufferStringFromKey
- AddNodeFilterFromInputString:
- BufferInputFromKey
- ResetNodeFilters
- AddNodeFilterFromInput:
filter: RelativePathDoesContain
case_sensitive: false
- Explore
"###,
)
.unwrap();
@ -526,8 +618,8 @@ impl Default for Config {
- Explore
- SwitchMode: default
n:
help: create new
c:
help: create
messages:
- SwitchMode: create
@ -536,6 +628,18 @@ impl Default for Config {
messages:
- SwitchMode: selection ops
l:
help: logs
messages:
- Call:
command: bash
args:
- -c
- |
echo -e "$XPLR_LOGS"
read -p "[enter to continue]"
- SwitchMode: default
ctrl-c:
help: cancel & quit [q]
messages:
@ -566,11 +670,15 @@ impl Default for Config {
- -c
- |
(while IFS= read -r line; do
cp -v "${line:?}" ./
if cp -v "${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_SELECTION:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
echo ClearSelection >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- ClearSelection
- Explore
- SwitchMode: default
m:
@ -582,10 +690,14 @@ impl Default for Config {
- -c
- |
(while IFS= read -r line; do
mv -v "${line:?}" ./
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_SELECTION:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- Explore
- SwitchMode: default
ctrl-c:
@ -646,7 +758,7 @@ impl Default for Config {
on_number:
help: input
messages:
- BufferStringFromKey
- BufferInputFromKey
default:
messages:
@ -658,6 +770,40 @@ impl Default for Config {
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:
@ -668,11 +814,44 @@ impl Default for Config {
args:
- -c
- |
touch "${XPLR_INPUT_BUFFER:?}"
PTH="${XPLR_INPUT_BUFFER:?}"
if touch "${PTH:?}"; then
echo "LogSuccess: $PTH created" >> "${XPLR_PIPE_MSG_IN:?}"
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to create $PTH" >> "${XPLR_PIPE_MSG_IN:?}"
echo Refresh >> "${XPLR_PIPE_MSG_IN:?}"
fi
- SwitchMode: default
- Explore
ctrl-d:
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:
- Call:
@ -680,14 +859,19 @@ impl Default for Config {
args:
- -c
- |
mkdir -p "${XPLR_INPUT_BUFFER:?}"
PTH="${XPLR_INPUT_BUFFER:?}"
if mkdir -p "$PTH"; then
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
echo "LogSuccess: $PTH created" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to create $PTH" >> "${XPLR_PIPE_MSG_IN:?}"
fi
- SwitchMode: default
- Explore
backspace:
help: clear
messages:
- ResetInputBuffer
- SetInputBuffer: ""
esc:
help: cancel
@ -701,7 +885,7 @@ impl Default for Config {
default:
messages:
- BufferStringFromKey
- BufferInputFromKey
"###,
)
.unwrap();
@ -721,14 +905,22 @@ impl Default for Config {
- |
(while IFS= read -r line; do
if [ -d "$line" ]; then
rmdir -v "${line:?}"
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
rm -v "${line:?}"
if rm -v "${line:?}"; then
echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
fi
done <<< "${XPLR_RESULT:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- SwitchMode: default
- Explore
D:
help: force delete
@ -738,7 +930,14 @@ impl Default for Config {
args:
- -c
- |
(echo -e "${XPLR_RESULT:?}" | xargs -l rm -rfv)
(while IFS= read -r line; do
if rm -rfv "${line:?}"; then
echo "LogSuccess: $line deleted" >> "${XPLR_PIPE_MSG_IN:?}"
else
echo "LogError: failed to delete $line" >> "${XPLR_PIPE_MSG_IN:?}"
fi
done <<< "${XPLR_RESULT:?}")
echo Explore >> "${XPLR_PIPE_MSG_IN:?}"
read -p "[enter to continue]"
- SwitchMode: default
- Explore
@ -760,6 +959,8 @@ impl Default for Config {
modes.insert("go to".into(), goto_mode);
modes.insert("number".into(), number_mode);
modes.insert("create".into(), create_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);

@ -15,18 +15,27 @@ pub fn keep_reading(tx_msg_in: Sender<Task>, rx_event_reader: Receiver<bool>) {
if !is_paused {
if event::poll(std::time::Duration::from_millis(1)).unwrap() {
match event::read().unwrap() {
Event::Key(key) => {
match event::read() {
Ok(Event::Key(key)) => {
let key = Key::from_event(key);
let msg = MsgIn::Internal(InternalMsg::HandleKey(key));
tx_msg_in.send(Task::new(0, msg, Some(key))).unwrap();
}
Event::Resize(_, _) => {
Ok(Event::Resize(_, _)) => {
let msg = MsgIn::External(ExternalMsg::Refresh);
tx_msg_in.send(Task::new(0, msg, None)).unwrap();
}
_ => {}
Ok(_) => {}
Err(e) => {
tx_msg_in
.send(Task::new(
0,
MsgIn::External(ExternalMsg::LogError(e.to_string())),
None,
))
.unwrap();
}
}
}
}

@ -170,6 +170,35 @@ fn main() -> Result<()> {
})
.unwrap_or_default();
let logs = app
.logs()
.iter()
.map(|l| l.to_string())
.collect::<Vec<String>>()
.join("\n");
let help_menu = app
.config()
.modes
.iter()
.map(|(name, mode)| {
let help = mode
.help_menu()
.iter()
.map(|l| match l {
app::HelpMenuLine::Paragraph(p) => format!("\t{}", p),
app::HelpMenuLine::KeyMap(k, h) => {
format!(" {:15} | {}", k, h)
}
})
.collect::<Vec<String>>()
.join("\n");
format!("### {}\n\n key | action\n --------------- | ------\n{}", name, help)
})
.collect::<Vec<String>>()
.join("\n\n\n");
let pipe_msg_in = app.pipe().msg_in.clone();
let pipe_focus_out = app.pipe().focus_out.clone();
let pipe_selection_out = app.pipe().selection_out.clone();
@ -191,7 +220,9 @@ fn main() -> Result<()> {
.env("XPLR_PIPE_FOCUS_OUT", pipe_focus_out)
.env("XPLR_APP_YAML", app_yaml)
.env("XPLR_RESULT", result)
.env("XPLR_GLOBAL_HELP_MENU", help_menu)
.env("XPLR_DIRECTORY_NODES", directory_nodes)
.env("XPLR_LOGS", logs)
.args(cmd.args.clone())
.status();

@ -12,10 +12,20 @@ pub fn keep_reading(pipe: String, tx: Sender<Task>) {
if !in_str.is_empty() {
let msgs = in_str
.lines()
.filter_map(|s| serde_yaml::from_str::<ExternalMsg>(s.trim()).ok());
.map(|s| serde_yaml::from_str::<ExternalMsg>(s.trim()));
msgs.for_each(|msg| {
tx.send(Task::new(2, MsgIn::External(msg), None)).unwrap();
msgs.for_each(|msg| match msg {
Ok(m) => {
tx.send(Task::new(2, MsgIn::External(m), None)).unwrap();
}
Err(e) => {
tx.send(Task::new(
0,
MsgIn::External(ExternalMsg::LogError(e.to_string())),
None,
))
.unwrap();
}
});
fs::write(&pipe, "").unwrap();
};

@ -1,10 +1,12 @@
use crate::app;
use crate::app::HelpMenuLine;
use crate::app::Node;
use handlebars::Handlebars;
use serde::{Deserialize, Serialize};
use tui::backend::Backend;
use tui::layout::Rect;
use tui::layout::{Constraint as TUIConstraint, Direction, Layout};
use tui::style::{Color, Style};
use tui::widgets::{
Block, Borders, Cell, List, ListItem, ListState, Paragraph, Row, Table, TableState,
};
@ -275,83 +277,21 @@ fn draw_selection<B: Backend>(f: &mut Frame<B>, rect: Rect, app: &app::App, _: &
}
fn draw_help_menu<B: Backend>(f: &mut Frame<B>, rect: Rect, app: &app::App, _: &Handlebars) {
// Help menu
let mode = app.mode();
let extra_help_lines = mode
.extra_help
.clone()
.map(|e| e.lines().map(|l| l.to_string()).collect::<Vec<String>>());
let help_menu_rows: Vec<Row> = mode
.help
.clone()
.map(|h| {
h.lines()
.map(|l| Row::new(vec![Cell::from(l.to_string())]))
.collect()
let help_menu_rows = app
.mode()
.help_menu()
.into_iter()
.map(|l| match l {
HelpMenuLine::Paragraph(p) => Row::new([Cell::from(p)].to_vec()),
HelpMenuLine::KeyMap(k, h) => Row::new([Cell::from(k), Cell::from(h)].to_vec()),
})
.unwrap_or_else(|| {
extra_help_lines
.unwrap_or_default()
.into_iter()
.map(|l| Row::new(vec![Cell::from(l)]))
.chain(mode.key_bindings.on_key.iter().filter_map(|(k, a)| {
a.help.clone().map(|h| {
Row::new(vec![Cell::from(k.to_string()), Cell::from(h.to_string())])
})
}))
.chain(
mode.key_bindings
.on_alphabet
.iter()
.map(|a| ("a-Z", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| {
Row::new(vec![Cell::from(k.to_string()), Cell::from(h.to_string())])
})
}),
)
.chain(
mode.key_bindings
.on_number
.iter()
.map(|a| ("0-9", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| {
Row::new(vec![Cell::from(k.to_string()), Cell::from(h.to_string())])
})
}),
)
.chain(
mode.key_bindings
.on_special_character
.iter()
.map(|a| ("spcl chars", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| {
Row::new(vec![Cell::from(k.to_string()), Cell::from(h.to_string())])
})
}),
)
.chain(
mode.key_bindings
.default
.iter()
.map(|a| ("default", a.help.clone()))
.filter_map(|(k, mh)| {
mh.map(|h| {
Row::new(vec![Cell::from(k.to_string()), Cell::from(h.to_string())])
})
}),
)
.collect::<Vec<Row>>()
});
.collect::<Vec<Row>>();
let help_menu = Table::new(help_menu_rows)
.block(
Block::default()
.borders(Borders::ALL)
.title(format!(" Help [{}] ", &mode.name)),
.title(format!(" Help [{}] ", &app.mode().name)),
)
.widths(&[TUIConstraint::Percentage(30), TUIConstraint::Percentage(70)]);
f.render_widget(help_menu, rect);
@ -363,6 +303,31 @@ fn draw_input_buffer<B: Backend>(f: &mut Frame<B>, rect: Rect, app: &app::App, _
f.render_widget(input_buf, rect);
}
fn draw_logs<B: Backend>(f: &mut Frame<B>, rect: Rect, app: &app::App, _: &Handlebars) {
let logs = app
.logs()
.iter()
.rev()
.take(1)
.rev()
.map(|l| match &l.level {
app::LogLevel::Info => {
ListItem::new(l.to_string()).style(Style::default().fg(Color::Gray))
}
app::LogLevel::Success => {
ListItem::new(l.to_string()).style(Style::default().fg(Color::Green))
}
app::LogLevel::Error => {
ListItem::new(l.to_string()).style(Style::default().fg(Color::Red))
}
})
.collect::<Vec<ListItem>>();
let logs_list = List::new(logs).block(Block::default().borders(Borders::ALL).title(" Logs "));
f.render_widget(logs_list, rect);
}
pub fn draw<B: Backend>(f: &mut Frame<B>, app: &app::App, hb: &Handlebars) {
let rect = f.size();
@ -382,13 +347,19 @@ pub fn draw<B: Backend>(f: &mut Frame<B>, app: &app::App, hb: &Handlebars) {
)
.split(chunks[0]);
draw_table(f, left_chunks[0], app, hb);
if app.input_buffer().is_some() {
draw_input_buffer(f, left_chunks[1], app, hb);
} else {
draw_logs(f, left_chunks[1], app, hb);
};
let right_chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([TUIConstraint::Percentage(50), TUIConstraint::Percentage(50)].as_ref())
.split(chunks[1]);
draw_table(f, left_chunks[0], app, hb);
draw_input_buffer(f, left_chunks[1], app, hb);
draw_selection(f, right_chunks[0], app, hb);
draw_help_menu(f, right_chunks[1], app, hb);
}

@ -0,0 +1,29 @@
use std::collections::BinaryHeap;
use xplr::*;
#[test]
fn test_task_priority() {
let task1 = app::Task::new(2, app::MsgIn::External(app::ExternalMsg::Refresh), None);
let task2 = app::Task::new(2, app::MsgIn::External(app::ExternalMsg::Refresh), None);
let task3 = app::Task::new(1, app::MsgIn::External(app::ExternalMsg::Refresh), None);
let task4 = app::Task::new(1, app::MsgIn::External(app::ExternalMsg::Refresh), None);
let task5 = app::Task::new(3, app::MsgIn::External(app::ExternalMsg::Refresh), None);
let task6 = app::Task::new(3, app::MsgIn::External(app::ExternalMsg::Refresh), None);
let mut heap = BinaryHeap::new();
heap.push(task1.clone());
heap.push(task2.clone());
heap.push(task3.clone());
heap.push(task4.clone());
heap.push(task5.clone());
heap.push(task6.clone());
assert_eq!(heap.pop(), Some(task3));
assert_eq!(heap.pop(), Some(task4));
assert_eq!(heap.pop(), Some(task1));
assert_eq!(heap.pop(), Some(task2));
assert_eq!(heap.pop(), Some(task5));
assert_eq!(heap.pop(), Some(task6));
assert_eq!(heap.pop(), None);
}
Loading…
Cancel
Save