mirror of
https://github.com/denisidoro/navi
synced 2024-11-10 14:04:17 +00:00
Make zsh widget smarter (#479)
Related: #463, from @enisozgen Before releasing a new version, this will be refactored
This commit is contained in:
parent
30c642eee3
commit
cc579470d0
2 changed files with 142 additions and 178 deletions
|
@ -1,19 +1,147 @@
|
||||||
#!/usr/bin/env zsh
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
_call_navi() {
|
# Copy-pasted from https://gist.github.com/enisozgen/2109cc80ea9f405f80c6c383f2375e77
|
||||||
local selected
|
|
||||||
if [ -n "$LBUFFER" ]; then
|
|
||||||
if selected="$(printf "%s" "$(navi --print --fzf-overrides '--no-select-1' --query "${LBUFFER}" </dev/tty)")"; then
|
# Change last part of the command
|
||||||
LBUFFER="$selected"
|
# NOTE Creates sometime problem if there is same word in the input
|
||||||
fi
|
ChangeLastCommand()
|
||||||
else
|
{
|
||||||
if selected="$(printf "%s" "$(navi --print </dev/tty)")"; then
|
[[ -z "${2// }" ]] && printf "%s" "$(echo "${1}${3}")" || printf "%s" "$(echo "${1/$2/ $3}")"
|
||||||
LBUFFER="$selected"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
zle redisplay
|
|
||||||
}
|
}
|
||||||
|
|
||||||
zle -N _call_navi
|
SendLastCommandAfterPIPE()
|
||||||
|
{
|
||||||
|
# Send last command after pipe
|
||||||
|
INPUT_STRING="${1}"
|
||||||
|
a=("${(@s/|/)${INPUT_STRING}}") # | modifier
|
||||||
|
printf "%s" "$(echo "${a[-1]}")"
|
||||||
|
}
|
||||||
|
|
||||||
bindkey '^g' _call_navi
|
|
||||||
|
# Is there pipe in the input string
|
||||||
|
IsPipeExist()
|
||||||
|
{
|
||||||
|
INPUT_STRING=$1
|
||||||
|
if [[ $INPUT_STRING == *\|* ]]; then
|
||||||
|
NAVI_PIPE="true"
|
||||||
|
else
|
||||||
|
NAVI_PIPE="false"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IsOnlySpace ()
|
||||||
|
{
|
||||||
|
if [[ -z "${1// }" ]] ; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
NaviUISearch()
|
||||||
|
{
|
||||||
|
NAVI_RET=$(printf "%s" "$(navi --print --fzf-overrides '--no-select-1' --query "${1}" </dev/tty)")
|
||||||
|
printf ${NAVI_RET}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NaviOutputControl ()
|
||||||
|
{
|
||||||
|
if ! [[ -z "$1" ]] ; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SmartNavi()
|
||||||
|
{
|
||||||
|
if IsOnlySpace $1 ; then
|
||||||
|
NAVI_RET=$(printf "%s" "$(navi --print --fzf-overrides '--no-select-1' </dev/tty)")
|
||||||
|
else
|
||||||
|
NAVI_RET=$(printf "%s" "$(navi --print --best-match --fzf-overrides '--no-select-1' --query "${1}" </dev/tty)")
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Return warning if there is no output else return best match
|
||||||
|
if NaviOutputControl ${NAVI_RET}; then
|
||||||
|
printf ${NAVI_RET}
|
||||||
|
else
|
||||||
|
printf "Navi Returned Empty"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_call_navi() {
|
||||||
|
local selected
|
||||||
|
if [ -n "$LBUFFER" ]; then
|
||||||
|
if selected="$(printf "%s" "$(navi --print --fzf-overrides '--no-select-1' --query "${LBUFFER}" </dev/tty)")"; then
|
||||||
|
LBUFFER="$selected"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# If there is not any word on list
|
||||||
|
if selected="$(printf "%s" "$(navi --print </dev/tty)")"; then
|
||||||
|
LBUFFER="$selected"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
region_highlight=("P0 100 bold")
|
||||||
|
zle redisplay
|
||||||
|
}
|
||||||
|
|
||||||
|
_call_smart_navi() {
|
||||||
|
|
||||||
|
# set -x
|
||||||
|
INPUT_STRING=$LBUFFER
|
||||||
|
IsPipeExist ${INPUT_STRING}
|
||||||
|
|
||||||
|
|
||||||
|
# Is there some written stuff in LBUFFER ?
|
||||||
|
if ! [ -z "$INPUT_STRING" ] ; then
|
||||||
|
# If last navi output same as current input
|
||||||
|
# Use this part when you don't like navi best match
|
||||||
|
if [ "${LASTWIDGET}" = "_call_smart_navi" ] && [ "${OUTPUT_STRING}" = "$INPUT_STRING" ];then
|
||||||
|
LBUFFER_LAST_COMMAND=$(SendLastCommandAfterPIPE "${INPUT_STRING}")
|
||||||
|
|
||||||
|
# Searching with same input as before but this time we are using navi interactive UI since navi didn't return us what we want
|
||||||
|
OUTPUT_STRING=$(NaviUISearch ${PREVIOUS_LAST})
|
||||||
|
OUTPUT_STRING=$(ChangeLastCommand "$INPUT_STRING" "$LBUFFER_LAST_COMMAND" "$OUTPUT_STRING")
|
||||||
|
|
||||||
|
else
|
||||||
|
# First search always start from here!!!
|
||||||
|
if [ "${NAVI_PIPE}" = "false" ] ; then
|
||||||
|
|
||||||
|
# LBUFFER_LAST_COMMAND=$(SendLastCommandAfterPIPE "${INPUT_STRING}")
|
||||||
|
# PREVIOUS_LAST=$LBUFFER_LAST_COMMAND
|
||||||
|
|
||||||
|
# Remember what was last command after pipe
|
||||||
|
PREVIOUS_LAST=$INPUT_STRING
|
||||||
|
OUTPUT_STRING=$(SmartNavi ${INPUT_STRING})
|
||||||
|
|
||||||
|
else
|
||||||
|
LBUFFER_LAST_COMMAND=$(SendLastCommandAfterPIPE "${INPUT_STRING}")
|
||||||
|
|
||||||
|
# Remember what was last command after pipe
|
||||||
|
PREVIOUS_LAST=$LBUFFER_LAST_COMMAND
|
||||||
|
|
||||||
|
OUTPUT_STRING=$(SmartNavi ${LBUFFER_LAST_COMMAND})
|
||||||
|
OUTPUT_STRING=$(ChangeLastCommand "$INPUT_STRING" "$LBUFFER_LAST_COMMAND" "$OUTPUT_STRING")
|
||||||
|
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
LBUFFER="$OUTPUT_STRING"
|
||||||
|
else
|
||||||
|
# There is nothing use default navi command
|
||||||
|
_call_navi
|
||||||
|
fi
|
||||||
|
|
||||||
|
region_highlight=("P0 100 bold")
|
||||||
|
zle redisplay
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
zle -N _call_smart_navi
|
||||||
|
bindkey '^g' _call_smart_navi
|
|
@ -1,164 +0,0 @@
|
||||||
use crate::display::Writer;
|
|
||||||
use crate::fs::{pathbuf_to_string, read_lines};
|
|
||||||
use crate::parser;
|
|
||||||
use crate::structures::cheat::VariableMap;
|
|
||||||
use anyhow::Error;
|
|
||||||
use directories_next::BaseDirs;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
pub fn all_cheat_files(path_str: &str) -> Vec<String> {
|
|
||||||
let path_str_with_trailing_slash = if path_str.ends_with('/') {
|
|
||||||
path_str.to_string()
|
|
||||||
} else {
|
|
||||||
format!("{}/", &path_str)
|
|
||||||
};
|
|
||||||
|
|
||||||
WalkDir::new(&path_str)
|
|
||||||
.follow_links(true)
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|e| e.ok())
|
|
||||||
.map(|e| e.path().to_str().unwrap_or("").to_string())
|
|
||||||
.filter(|e| e.ends_with(".cheat"))
|
|
||||||
.map(|e| e.replace(&path_str_with_trailing_slash, ""))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paths_from_path_param(env_var: &str) -> impl Iterator<Item = &str> {
|
|
||||||
env_var.split(':').filter(|folder| folder != &"")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move
|
|
||||||
fn read_file(
|
|
||||||
path: &str,
|
|
||||||
file_index: usize,
|
|
||||||
variables: &mut VariableMap,
|
|
||||||
visited_lines: &mut HashSet<u64>,
|
|
||||||
writer: &mut dyn Writer,
|
|
||||||
stdin: &mut std::process::ChildStdin,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let lines = read_lines(path)?;
|
|
||||||
parser::read_lines(lines, path, file_index, variables, visited_lines, writer, stdin)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default_cheat_pathbuf() -> Result<PathBuf, Error> {
|
|
||||||
let base_dirs = BaseDirs::new().ok_or_else(|| anyhow!("Unable to get base dirs"))?;
|
|
||||||
|
|
||||||
let mut pathbuf = PathBuf::from(base_dirs.data_dir());
|
|
||||||
pathbuf.push("navi");
|
|
||||||
pathbuf.push("cheats");
|
|
||||||
Ok(pathbuf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cheat_paths(path: Option<String>) -> Result<String, Error> {
|
|
||||||
if let Some(p) = path {
|
|
||||||
Ok(p)
|
|
||||||
} else {
|
|
||||||
pathbuf_to_string(default_cheat_pathbuf()?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_all(
|
|
||||||
path: Option<String>,
|
|
||||||
files: &mut Vec<String>,
|
|
||||||
stdin: &mut std::process::ChildStdin,
|
|
||||||
writer: &mut dyn Writer,
|
|
||||||
) -> Result<Option<VariableMap>, Error> {
|
|
||||||
let mut variables = VariableMap::new();
|
|
||||||
let mut found_something = false;
|
|
||||||
let mut visited_lines = HashSet::new();
|
|
||||||
let paths = cheat_paths(path);
|
|
||||||
|
|
||||||
if paths.is_err() {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
let paths = paths.expect("Unable to get paths");
|
|
||||||
let folders = paths_from_path_param(&paths);
|
|
||||||
|
|
||||||
for folder in folders {
|
|
||||||
for file in all_cheat_files(folder) {
|
|
||||||
let full_filename = format!("{}/{}", &folder, &file);
|
|
||||||
files.push(full_filename.clone());
|
|
||||||
let index = files.len() - 1;
|
|
||||||
if read_file(
|
|
||||||
&full_filename,
|
|
||||||
index,
|
|
||||||
&mut variables,
|
|
||||||
&mut visited_lines,
|
|
||||||
writer,
|
|
||||||
stdin,
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
&& !found_something
|
|
||||||
{
|
|
||||||
found_something = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found_something {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(variables))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::display;
|
|
||||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_read_file() {
|
|
||||||
let path = "tests/cheats/ssh.cheat";
|
|
||||||
let mut variables = VariableMap::new();
|
|
||||||
let mut child = Command::new("cat")
|
|
||||||
.stdin(Stdio::piped())
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.spawn()
|
|
||||||
.unwrap();
|
|
||||||
let child_stdin = child.stdin.as_mut().unwrap();
|
|
||||||
let mut visited_lines: HashSet<u64> = HashSet::new();
|
|
||||||
let mut writer: Box<dyn Writer> = Box::new(display::terminal::Writer::new());
|
|
||||||
read_file(
|
|
||||||
path,
|
|
||||||
0,
|
|
||||||
&mut variables,
|
|
||||||
&mut visited_lines,
|
|
||||||
&mut *writer,
|
|
||||||
child_stdin,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let expected_suggestion = (
|
|
||||||
r#" echo -e "$(whoami)\nroot" "#.to_string(),
|
|
||||||
Some(FinderOpts {
|
|
||||||
header_lines: 0,
|
|
||||||
column: None,
|
|
||||||
delimiter: None,
|
|
||||||
suggestion_type: SuggestionType::SingleSelection,
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
let actual_suggestion = variables.get_suggestion("ssh", "user");
|
|
||||||
assert_eq!(Some(&expected_suggestion), actual_suggestion);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn splitting_of_dirs_param_may_not_contain_empty_items() {
|
|
||||||
// Trailing colon indicates potential extra path. Split returns an empty item for it. This empty item should be filtered away, which is what this test checks.
|
|
||||||
let given_path_config = "SOME_PATH:ANOTHER_PATH:";
|
|
||||||
|
|
||||||
let found_paths = paths_from_path_param(given_path_config);
|
|
||||||
|
|
||||||
let mut expected_paths = vec!["SOME_PATH", "ANOTHER_PATH"].into_iter();
|
|
||||||
|
|
||||||
for found in found_paths {
|
|
||||||
let expected = expected_paths.next().unwrap();
|
|
||||||
assert_eq!(found, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue