Improve bash and zsh widgets (#486)

This commit is contained in:
Denis Isidoro 2021-04-09 11:55:44 -03:00 committed by GitHub
parent 1f3560f7f9
commit 307f0c11b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 89 additions and 172 deletions

2
Cargo.lock generated
View file

@ -308,7 +308,7 @@ dependencies = [
[[package]]
name = "navi"
version = "2.14.0"
version = "2.15.0"
dependencies = [
"anyhow",
"clap",

View file

@ -1,6 +1,6 @@
[package]
name = "navi"
version = "2.14.0"
version = "2.15.0"
authors = ["Denis Isidoro <denis_isidoro@live.com>"]
edition = "2018"
description = "An interactive cheatsheet tool for the command-line"

View file

@ -75,8 +75,6 @@ Feel free to be the maintainer of **navi** for any package manager you'd like!
### Installing the shell widget
The shell widget maintains your `.history`-like file consistent and allows you to edit commands before executing them.
If you want to install it, add this line to your `.bashrc`-like file:
```sh
# bash

View file

@ -1,46 +1,35 @@
#!/usr/bin/env bash
_call_navi() {
local selected
if [ -n "${READLINE_LINE}" ]; then
if selected="$(printf "%s" "$(navi --print --fzf-overrides '--no-select-1' --query "${READLINE_LINE}" </dev/tty)")"; then
READLINE_LINE="$selected"
READLINE_POINT=${#READLINE_LINE}
fi
else
if selected="$(printf "%s" "$(navi --print </dev/tty)")"; then
READLINE_LINE="$selected"
READLINE_POINT=${#READLINE_LINE}
fi
_navi_call() {
local result="$(navi "$@" </dev/tty)"
if [ -z "${result}" ]; then
result="$(navi --print </dev/tty)"
fi
printf "%s" "$result"
}
__call_navi_legacy_versions() {
local -r result="$(navi --print)"
local -r linecount="$(echo "$result" | wc -l)"
_navi_widget() {
local -r input="${READLINE_LINE}"
local -r last_command="$(echo "${input}" | navi fn widget::last_command)"
if [[ "$linecount" -lt 2 ]]; then
printf "%s" "$result"
return 0
fi
IFS=$'\n'
local i=1;
for line in $result; do
if echo "$line" | grep -q '\\$'; then
printf "${line::-1} "
elif [[ "$i" -eq "$linecount" ]]; then
printf "$line "
if [ -z "${last_command}" ]; then
local -r output="$(_navi_call --print --fzf-overrides '--no-select-1')"
else
printf "${line}; "
local -r find="$last_command"
local -r replacement="$(_navi_call --print --query "${last_command}")"
local -r output="${input//$find/$replacement}"
fi
i=$((i+1))
done
READLINE_LINE="$output"
READLINE_POINT=${#READLINE_LINE}
}
_navi_widget_legacy() {
_navi_call --print
}
if [ ${BASH_VERSION:0:1} -lt 4 ]; then
bind '"\C-g": " \C-b\C-k \C-u`__call_navi_legacy_versions`\e\C-e\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
bind '"\C-g": " \C-b\C-k \C-u`_navi_widget_legacy`\e\C-e\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
else
bind -x '"\C-g": _call_navi'
bind -x '"\C-g": _navi_widget'
fi

View file

@ -1,147 +1,36 @@
#!/usr/bin/env zsh
# Copy-pasted from https://gist.github.com/enisozgen/2109cc80ea9f405f80c6c383f2375e77
# Change last part of the command
# NOTE Creates sometime problem if there is same word in the input
ChangeLastCommand()
{
[[ -z "${2// }" ]] && printf "%s" "$(echo "${1}${3}")" || printf "%s" "$(echo "${1/$2/ $3}")"
_navi_call() {
local result="$(navi "$@" </dev/tty)"
if [ -z "${result}" ]; then
result="$(navi --print </dev/tty)"
fi
printf "%s" "$result"
}
SendLastCommandAfterPIPE()
{
# Send last command after pipe
INPUT_STRING="${1}"
a=("${(@s/|/)${INPUT_STRING}}") # | modifier
printf "%s" "$(echo "${a[-1]}")"
}
_navi_widget() {
local -r input="${LBUFFER}"
local -r last_command="$(echo "${input}" | navi fn widget::last_command)"
local find="$last_command"
local replacement="$last_command"
# Is there pipe in the input string
IsPipeExist()
{
INPUT_STRING=$1
if [[ $INPUT_STRING == *\|* ]]; then
NAVI_PIPE="true"
if [ -z "${last_command}" ]; then
replacement="$(_navi_call --print --fzf-overrides '--no-select-1')"
elif [ "${LASTWIDGET}" = "_navi_widget" ] && [ "$input" = "$previous_output" ]; then
find="$input"
replacement="$(_navi_call --print --query "${previous_last_command:-$last_command}")"
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)")
replacement="$(_navi_call --print --best-match --query "${last_command}")"
fi
previous_last_command="$last_command"
previous_output="${input//$find/$replacement}"
# 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
zle kill-whole-line
LBUFFER="${previous_output}"
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
zle -N _navi_widget
bindkey '^g' _navi_widget

View file

@ -2,11 +2,13 @@ use crate::handler;
use crate::structures::config;
use crate::url;
use anyhow::Error;
use std::io::{self, Read};
#[derive(Debug)]
pub enum Func {
UrlOpen,
Welcome,
WidgetLastCommand,
}
pub fn main(func: &Func, args: Vec<String>) -> Result<(), Error> {
@ -15,5 +17,43 @@ pub fn main(func: &Func, args: Vec<String>) -> Result<(), Error> {
Func::Welcome => handler::handle_config(config::config_from_iter(
"navi --path /tmp/navi/irrelevant".split(' ').collect(),
)),
Func::WidgetLastCommand => widget_last_command(),
}
}
fn widget_last_command() -> Result<(), Error> {
let mut text = String::new();
io::stdin().read_to_string(&mut text)?;
let replacements = vec![("|", ""), ("||", ""), ("&&", "")];
let parts = shellwords::split(&text).unwrap_or_else(|_| text.split('|').map(|s| s.to_string()).collect());
for p in parts {
for (pattern, escaped) in replacements.clone() {
if p.contains(pattern) && p != pattern {
let replacement = p.replace(pattern, escaped);
text = text.replace(&p, &replacement);
}
}
}
let mut extracted = text.clone();
for (pattern, _) in replacements.clone() {
let mut new_parts = text.rsplit(pattern);
if let Some(extracted_attempt) = new_parts.next() {
if extracted_attempt.len() <= extracted.len() {
extracted = extracted_attempt.to_string();
}
}
}
for (pattern, escaped) in replacements.clone() {
text = text.replace(&escaped, &pattern);
extracted = extracted.replace(&escaped, &pattern);
}
println!("{}", extracted.trim_start());
Ok(())
}

View file

@ -38,6 +38,7 @@ impl FromStr for Func {
match s {
"url::open" => Ok(Func::UrlOpen),
"welcome" => Ok(Func::Welcome),
"widget::last_command" => Ok(Func::WidgetLastCommand),
_ => Err("no match"),
}
}
@ -147,7 +148,7 @@ pub enum Command {
/// Performs ad-hoc functions provided by navi
Fn {
/// Function name (example: "url::open")
#[clap(possible_values = &["url::welcome", "open"], case_insensitive = true)]
#[clap(possible_values = &["url::welcome", "open", "widget::last_command"], case_insensitive = true)]
func: Func,
/// List of arguments (example: "https://google.com")
args: Vec<String>,