mirror of
https://github.com/denisidoro/navi.git
synced 2024-09-05 15:35:27 +03:00
Refactor list visualization (#138)
Fixes #137 ![Demo](https://user-images.githubusercontent.com/3226564/67512211-b2e99a00-f66e-11e9-8ffe-09f30cb54599.png)
This commit is contained in:
parent
70bf907840
commit
a0c5a6293b
@ -88,7 +88,7 @@ as a [shell widget](#shell-widget) with no additional setup.
|
||||
> to be able to run the command interactively, you will need to do one of the following:
|
||||
|
||||
- Install it to /usr/bin/local (via `sudo make install`)
|
||||
- Manually set your `PATH` so that navi can be found.
|
||||
- Manually set `$PATH` so that navi can be found.
|
||||
|
||||
You can manually update your path by adding a line like this in your `.zshrc`:
|
||||
|
||||
@ -250,6 +250,12 @@ List customization
|
||||
|
||||
Lists can be stylized with the [$FZF_DEFAULT_OPTS](https://github.com/junegunn/fzf) environment variable. This way, you can change the [color scheme](https://github.com/junegunn/fzf/wiki/Color-schemes), for example.
|
||||
|
||||
In addition:
|
||||
- the `--fzf-overrides` option allows you to hide columns, for example
|
||||
- the `--col-widths` option allows you to limit column widths
|
||||
|
||||
Please refer to `navi --help` for more details.
|
||||
|
||||
Related projects
|
||||
----------------
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
% tar, zip, gzip, compression
|
||||
% compression
|
||||
|
||||
# Create a tar containing files
|
||||
tar cf <name>.tar <files>
|
||||
|
@ -1,4 +1,4 @@
|
||||
% crontab, scheduling
|
||||
% crontab, schedule
|
||||
|
||||
# List cron jobs
|
||||
crontab -l
|
||||
|
@ -1,4 +1,4 @@
|
||||
% docker, container
|
||||
% docker
|
||||
|
||||
# Remove an image
|
||||
docker image rm <image_id>
|
||||
@ -47,7 +47,7 @@ $ container_id: docker ps --- --headers 1 --column 1
|
||||
|
||||
|
||||
|
||||
% docker-compose, container
|
||||
% docker-compose
|
||||
|
||||
# Builds, (re)creates, starts, and attaches to containers for all services
|
||||
docker-compose up
|
||||
|
52
docstring.txt
Normal file
52
docstring.txt
Normal file
@ -0,0 +1,52 @@
|
||||
An interactive cheatsheet tool for the command-line
|
||||
|
||||
Usage:
|
||||
navi [command] [<args>...] [options]
|
||||
|
||||
Commands:
|
||||
search <cmd> Search for cheatsheets on online repositories
|
||||
query <cmd> Pre-filter results
|
||||
best <cmd> <args>... Considers the best match
|
||||
widget <shell> Prints the path for the widget file to be sourced
|
||||
|
||||
Options:
|
||||
--print Prevent script execution
|
||||
--path List of :-separated paths to look for cheats
|
||||
--no-interpolation Prevent argument interpolation
|
||||
--no-preview Hide command preview window
|
||||
--fzf-overrides Overrides for fzf commands [default: --with-nth 3,1,2 --exact]
|
||||
--col-widths Set column widths [default: 15,50,0]
|
||||
|
||||
Environment variables:
|
||||
NAVI_PATH List of :-separated paths to look for cheats
|
||||
FZF_DEFAULT_OPTS Default fzf options (e.g. '--layout=reverse')
|
||||
|
||||
Examples:
|
||||
navi # default behavior
|
||||
navi --print # doesn't execute the snippet
|
||||
navi --path '/some/dir:/other/dir' # uses custom cheats
|
||||
navi search docker # uses online data
|
||||
navi query git # filter results by "git"
|
||||
navi best 'sql create db' root mydb # uses a snippet as a CLI
|
||||
source "$(navi widget zsh)" # loads the zsh widget
|
||||
navi --col-widths 10,50,0 # limits the first two columns's width
|
||||
navi --fzf-overrides '--with-nth 1,2' # shows only the comment and tag columns
|
||||
navi --fzf-overrides '--nth 1,2' # search will consider only the first two columns
|
||||
navi --fzf-overrides '--no-exact' # looser search algorithm
|
||||
|
||||
More info:
|
||||
|
||||
search
|
||||
http://cheat.sh is used for cheatsheet retrieval
|
||||
please note that these cheatsheets may not work out of the box
|
||||
always check the preview window before executing commands!
|
||||
|
||||
--col-widths
|
||||
the number is the percentage relative to the terminal width
|
||||
0 means that a column will be as wide as necessary
|
||||
|
||||
fzf's --with-nth
|
||||
1 refers to the comment column; 2, snippet; 3, tag
|
||||
|
||||
full docs
|
||||
please refer to the README at https://github.com/denisidoro/navi
|
33
navi
33
navi
@ -2,41 +2,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
export NAVI_HOME="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
source "${NAVI_HOME}/src/main.sh"
|
||||
|
||||
##? An interactive cheatsheet tool for the command-line
|
||||
##?
|
||||
##? Usage:
|
||||
##? navi [command] [<args>...] [options]
|
||||
##?
|
||||
##? Commands:
|
||||
##? search <cmd> Search for cheatsheets on online repositories
|
||||
##? query <cmd> Pre-filter results
|
||||
##? best <cmd> <args>... Considers the best match
|
||||
##?
|
||||
##? Options:
|
||||
##? --print Prevent script execution
|
||||
##? --path List of paths to look for cheats
|
||||
##? --no-interpolation Prevent argument interpolation
|
||||
##? --no-preview Hide command preview window
|
||||
##?
|
||||
##? Examples:
|
||||
##? navi
|
||||
##? navi --path '/some/folder:/another/folder'
|
||||
##? navi search awk
|
||||
##? navi search docker --print
|
||||
##? navi query git
|
||||
##? navi best 'sql create db' root mydb
|
||||
##?
|
||||
##? More info:
|
||||
##? search
|
||||
##? Queries cheatsheets from http://cheat.sh
|
||||
##? Please note that these cheatsheets may not work out of the box
|
||||
##? Always check the preview window before executing commands!
|
||||
##? full docs
|
||||
##? Please refer to the README at https://github.com/denisidoro/navi
|
||||
|
||||
VERSION="0.14.3"
|
||||
NAVI_ENV="${NAVI_ENV:-prod}"
|
||||
|
||||
|
@ -38,7 +38,7 @@ arg::next() {
|
||||
}
|
||||
|
||||
arg::deserialize() {
|
||||
local arg="$1"
|
||||
local arg="${1:-}"
|
||||
|
||||
arg="${arg:1:${#arg}-2}"
|
||||
echo "$arg" \
|
||||
|
76
src/cheat.sh
76
src/cheat.sh
@ -41,15 +41,68 @@ cheat::memoized_read_all() {
|
||||
| cheat::join_lines
|
||||
}
|
||||
|
||||
# TODO: move this elsewhere
|
||||
cheat::get_index() {
|
||||
local -r txt="$1"
|
||||
local -r ref="$2"
|
||||
|
||||
local -r i="$(echo "$txt" | grep "${ref}\$" | awk '{print $1}')"
|
||||
echo $((i - 1))
|
||||
}
|
||||
|
||||
# TODO: move this elsewhere
|
||||
cheat::with_nth() {
|
||||
grep -Eo 'with\-nth +([^ ]+)' | awk '{print $NF}'
|
||||
}
|
||||
|
||||
cheat::prettify() {
|
||||
awk 'function color(c,s) {
|
||||
printf("\033[%dm%s\033[0m",30+c,s)
|
||||
local -r print="$(dict::get "$OPTIONS" print)"
|
||||
local -r widths="$(dict::get "$OPTIONS" col-widths | tr ',' $'\n')"
|
||||
local -r numbered_with_nth="$(dict::get "$OPTIONS" fzf-overrides | cheat::with_nth | tr ',' $'\n' | str::with_line_numbers)"
|
||||
|
||||
if [ -n "$numbered_with_nth" ]; then
|
||||
local -r comment_index="$(cheat::get_index "$numbered_with_nth" 1 2>/dev/null)"
|
||||
local -r snippet_index="$(cheat::get_index "$numbered_with_nth" 2 2>/dev/null)"
|
||||
local -r tag_index="$(cheat::get_index "$numbered_with_nth" 3 2>/dev/null)"
|
||||
local -r comment_width="$(echo "$widths" | coll::get $comment_index 2>/dev/null || echo 0)"
|
||||
local -r snippet_width="$(echo "$widths" | coll::get $snippet_index 2>/dev/null || echo 0)"
|
||||
local -r tag_width="$(echo "$widths" | coll::get $tag_index 2>/dev/null || echo 0)"
|
||||
local -r columns="$(ui::width)"
|
||||
else
|
||||
local -r comment_width=0
|
||||
local -r snippet_width=0
|
||||
local -r tag_width=0
|
||||
local -r columns=0
|
||||
fi
|
||||
|
||||
awk \
|
||||
-v COMMENT_MAX=$((columns * comment_width / 100)) \
|
||||
-v SNIPPET_MAX=$((columns * snippet_width / 100)) \
|
||||
-v TAG_MAX=$((columns * tag_width / 100)) \
|
||||
-v SEP="$ESCAPE_CHAR_3" \
|
||||
'function color(c,s,max) {
|
||||
if (max > 0 && length(s) > max) {
|
||||
s=substr(s, 0, max)
|
||||
s=s"…"
|
||||
}
|
||||
printf("\033[%dm%s", c, s)
|
||||
}
|
||||
|
||||
/^%/ { tags=" ["substr($0, 3)"]"; next }
|
||||
/^#/ { print color(4, $0) color(60, tags); next }
|
||||
/^%/ { tags=substr($0, 3); next }
|
||||
/^#/ { comment=substr($0, 3); next }
|
||||
/^\$/ { next }
|
||||
NF { print color(7, $0) color(60, tags); next }'
|
||||
BEGIN { ORS="" }
|
||||
NF {
|
||||
print color(34, comment, COMMENT_MAX)
|
||||
print color(0, SEP, 0)
|
||||
print color(37, $0, SNIPPET_MAX)
|
||||
print color(0, SEP, 0)
|
||||
print color(90, tags, TAG_MAX);
|
||||
print color(0, SEP, 0)
|
||||
print color(90, "\033", 0);
|
||||
print "\n"
|
||||
next
|
||||
}'
|
||||
}
|
||||
|
||||
cheat::until_percentage() {
|
||||
@ -60,13 +113,20 @@ cheat::until_percentage() {
|
||||
{ print $0 }'
|
||||
}
|
||||
|
||||
cheat::from_tags() {
|
||||
local -r cheats="$1"
|
||||
local -r tags="$2"
|
||||
|
||||
echo "$cheats" \
|
||||
| grep "% ${tags}" -A99999 \
|
||||
| cheat::until_percentage
|
||||
}
|
||||
|
||||
cheat::from_selection() {
|
||||
local -r cheats="$1"
|
||||
local -r selection="$2"
|
||||
|
||||
local -r tags="$(dict::get "$selection" tags)"
|
||||
|
||||
echo "$cheats" \
|
||||
| grep "% ${tags}" -A99999 \
|
||||
| cheat::until_percentage
|
||||
cheat::from_tags "$cheats" "$tags"
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ handler::main() {
|
||||
|
||||
local -r interpolation="$(dict::get "$OPTIONS" interpolation)"
|
||||
|
||||
local cmd="$(selection::cmd "$selection" "$cheat")"
|
||||
local cmd="$(selection::snippet "$selection")"
|
||||
local result arg value
|
||||
|
||||
local i=0
|
||||
@ -50,10 +50,10 @@ handler::main() {
|
||||
|
||||
handler::preview() {
|
||||
local -r query="$1"
|
||||
local -r selection="$(echo "$query" | selection::dict)"
|
||||
local -r cheats="$(cheat::memoized_read_all)"
|
||||
local -r selection="$(echo "$query" | selection::dict "$cheats")"
|
||||
local -r cheat="$(cheat::from_selection "$cheats" "$selection")"
|
||||
[ -n "$cheat" ] && selection::cmd_or_comment "$selection" "$cheat" | cmd::unescape
|
||||
[ -n "$cheat" ] && ui::print_preview "$selection"
|
||||
}
|
||||
|
||||
handler::help() {
|
||||
|
17
src/opts.sh
17
src/opts.sh
@ -2,8 +2,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
opts::extract_help() {
|
||||
local -r file="$1"
|
||||
grep "^##?" "$file" | cut -c 5-
|
||||
local -r file="${NAVI_HOME}/docstring.txt"
|
||||
cat "$file"
|
||||
}
|
||||
|
||||
opts::eval() {
|
||||
@ -17,6 +17,8 @@ opts::eval() {
|
||||
local best=false
|
||||
local query=""
|
||||
local values=""
|
||||
local col_widths="15,50,0"
|
||||
local fzf_overrides="--with-nth 3,1,2 --exact"
|
||||
|
||||
case "${1:-}" in
|
||||
--version|version) entry_point="version"; shift ;;
|
||||
@ -39,14 +41,21 @@ opts::eval() {
|
||||
search) query="$arg"; wait_for=""; path="${path}:$(search::full_path "$query")"; continue ;;
|
||||
query|best) query="$arg"; wait_for=""; continue ;;
|
||||
widget) SH="$arg"; wait_for=""; continue ;;
|
||||
col-widths) col_widths="$(echo "$arg" | xargs | tr ' ' ',')"; wait_for=""; continue ;;
|
||||
fzf-overrides) fzf_overrides="$arg" ; wait_for=""; continue ;;
|
||||
esac
|
||||
|
||||
case $arg in
|
||||
--print) print=true ;;
|
||||
--no-interpolation) interpolation=false ;;
|
||||
--interpolation) interpolation=true ;;
|
||||
--no-preview) preview=false ;;
|
||||
--preview) preview=true ;;
|
||||
--path|--dir) wait_for="path" ;;
|
||||
--no-autoselect) autoselect=false ;;
|
||||
--autoselect) autoselect=true ;;
|
||||
--col-widths) wait_for="col-widths" ;;
|
||||
--fzf-overrides) wait_for="fzf-overrides" ;;
|
||||
*) values="$(echo "$values" | coll::add "$arg")" ;;
|
||||
esac
|
||||
done
|
||||
@ -59,7 +68,9 @@ opts::eval() {
|
||||
autoselect "$autoselect" \
|
||||
query "$query" \
|
||||
best "$best" \
|
||||
values "$values")"
|
||||
values "$values" \
|
||||
fzf-overrides "$fzf_overrides" \
|
||||
col-widths "$col_widths")"
|
||||
|
||||
export NAVI_PATH="$path"
|
||||
}
|
||||
|
@ -1,41 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
selection::dict() {
|
||||
local -r str="$(cat)"
|
||||
SELECTION_ESCAPE_STR=" "
|
||||
|
||||
local -r tags="$(echo "$str" | awk -F'[' '{print $NF}' | tr -d ']')"
|
||||
local -r core="$(echo "$str" | sed -e "s/ \[${tags}\]$//")"
|
||||
|
||||
dict::new core "$core" tags "$tags" | sed "s/'''/'/g"
|
||||
selection_str::cleanup() {
|
||||
sed -E "s/ +/${SELECTION_ESCAPE_STR}/g"
|
||||
}
|
||||
|
||||
selection::core_is_comment() {
|
||||
grep -qE '^#'
|
||||
selection_str::without_ellipsis() {
|
||||
tr -d "…"
|
||||
}
|
||||
|
||||
selection::cmd_or_comment() {
|
||||
local -r selection="$1"
|
||||
local -r cheat="$2"
|
||||
local -r always_cmd="${3:-false}"
|
||||
selection_str::comment() {
|
||||
echo "$*" | awk -F "${SELECTION_ESCAPE_STR}" '{print $1}' | selection_str::without_ellipsis
|
||||
}
|
||||
|
||||
local -r core="$(echo "$selection" | dict::get core)"
|
||||
selection_str::snippet() {
|
||||
echo "$*" | awk -F "${SELECTION_ESCAPE_STR}" '{print $2}' | selection_str::without_ellipsis
|
||||
}
|
||||
|
||||
if echo "$core" | selection::core_is_comment; then
|
||||
echo "$cheat" \
|
||||
| grep "$core" -A999 \
|
||||
| str::last_paragraph_line \
|
||||
| cmd::escape
|
||||
elif $always_cmd; then
|
||||
echo "$core"
|
||||
selection_str::tags() {
|
||||
echo "$*" | awk -F "${SELECTION_ESCAPE_STR}" '{print $3}' | selection_str::without_ellipsis
|
||||
}
|
||||
|
||||
selection::resolve_ellipsis() {
|
||||
local -r str="$(selection_str::cleanup)"
|
||||
local -r cheats="$*"
|
||||
|
||||
if echo "$str" | grep -q "…"; then
|
||||
local -r comment="$(selection_str::comment "$str")"
|
||||
local -r snippet="$(selection_str::snippet "$str")"
|
||||
local -r tags="$(selection_str::tags "$str")"
|
||||
local -r cheat="$(cheat::from_tags "$cheats" "$tags")"
|
||||
|
||||
local -r tags2="$(echo "$cheat" | head -n1 | str::sub 2)"
|
||||
local -r comment2="$(echo "$cheat" | grep "$comment" | str::sub 2)"
|
||||
local -r snippet2="$(echo "$cheat" | grep "$comment2" -A 999| str::last_paragraph_line)"
|
||||
|
||||
echo "${comment2}${SELECTION_ESCAPE_STR}${snippet2}${SELECTION_ESCAPE_STR}${tags2}"
|
||||
else
|
||||
echo "$cheat" \
|
||||
| grep "^${core}$" -B999 \
|
||||
| str::reverse_lines \
|
||||
| str::last_paragraph_line \
|
||||
| cmd::escape
|
||||
echo "$str"
|
||||
fi
|
||||
}
|
||||
|
||||
selection::cmd() {
|
||||
selection::cmd_or_comment "$@" true
|
||||
selection::dict() {
|
||||
local -r cheats="$1"
|
||||
local -r str="$(selection::resolve_ellipsis "$cheats")"
|
||||
|
||||
local -r comment="$(selection_str::comment "$str")"
|
||||
local -r snippet="$(selection_str::snippet "$str")"
|
||||
local -r tags="$(selection_str::tags "$str")"
|
||||
|
||||
dict::new comment "$comment" snippet "$snippet" tags "$tags" | sed "s/'''/'/g"
|
||||
}
|
||||
|
||||
selection::comment() {
|
||||
local -r selection="$1"
|
||||
dict::get "$selection" comment
|
||||
}
|
||||
|
||||
selection::snippet() {
|
||||
local -r selection="$1"
|
||||
dict::get "$selection" snippet
|
||||
}
|
||||
|
||||
selection::tags() {
|
||||
local -r selection="$1"
|
||||
dict::get "$selection" tags
|
||||
}
|
15
src/str.sh
15
src/str.sh
@ -62,4 +62,19 @@ str::not_empty() {
|
||||
|
||||
str::remove_empty_lines() {
|
||||
sed '/^$/d'
|
||||
}
|
||||
|
||||
str::as_column() {
|
||||
local -r txt="$(cat)"
|
||||
local -r separator="$1"
|
||||
|
||||
if command_exists column; then
|
||||
echo "$txt" | column -t -s "$separator"
|
||||
else
|
||||
echo "$txt" | awk -F "$separator" -vOFS='\t' 'NF > 0 { $1 = $1 } 1'
|
||||
fi
|
||||
}
|
||||
|
||||
str::with_line_numbers() {
|
||||
awk '{printf("%d %s\n", NR,$0)}'
|
||||
}
|
36
src/ui.sh
36
src/ui.sh
@ -2,6 +2,9 @@
|
||||
|
||||
ui::fzf() {
|
||||
local -r autoselect="$(dict::get "$OPTIONS" autoselect)"
|
||||
local -r with_nth="$(dict::get "$OPTIONS" with-nth)"
|
||||
local -r nth="$(dict::get "$OPTIONS" nth)"
|
||||
local -r fzf_overrides="$(dict::get "$OPTIONS" fzf-overrides)"
|
||||
|
||||
local args
|
||||
args+=("--height")
|
||||
@ -10,6 +13,9 @@ ui::fzf() {
|
||||
args+=("--select-1")
|
||||
fi
|
||||
|
||||
local fzf_opts="${FZF_DEFAULT_OPTS:---height 70% --reverse --border --inline-info --cycle}"
|
||||
export FZF_DEFAULT_OPTS="${FZF_DEFAULT_OPTS} ${fzf_overrides}"
|
||||
|
||||
local -r fzf_cmd="$([ $NAVI_ENV == "test" ] && echo "fzf_mock" || echo "fzf")"
|
||||
"$fzf_cmd" ${args[@]:-} --inline-info "$@"
|
||||
}
|
||||
@ -18,7 +24,7 @@ ui::select() {
|
||||
local -r cheats="$1"
|
||||
|
||||
local -r script_path="${NAVI_HOME}/navi"
|
||||
local -r preview_cmd="echo \'{}\' | $(arg::serialize_code) | xargs -I% \"${script_path}\" preview %"
|
||||
local -r preview_cmd="\"${script_path}\" preview \$(echo \'{}\' | $(arg::serialize_code))"
|
||||
|
||||
local -r query="$(dict::get "$OPTIONS" query)"
|
||||
local -r entry_point="$(dict::get "$OPTIONS" entry_point)"
|
||||
@ -30,7 +36,7 @@ ui::select() {
|
||||
args+=("--ansi")
|
||||
if $preview; then
|
||||
args+=("--preview"); args+=("$preview_cmd")
|
||||
args+=("--preview-window"); args+=("up:1")
|
||||
args+=("--preview-window"); args+=("up:2")
|
||||
fi
|
||||
if [[ -n "$query" ]] && $best; then
|
||||
args+=("--filter"); args+=("${query} ")
|
||||
@ -40,14 +46,38 @@ ui::select() {
|
||||
if [ "$entry_point" = "search" ]; then
|
||||
args+=("--header"); args+=("Displaying online results. Please refer to 'navi --help' for details")
|
||||
fi
|
||||
args+=("--delimiter"); args+=('\s\s+');
|
||||
|
||||
echo "$cheats" \
|
||||
| cheat::prettify \
|
||||
| str::as_column $(printf "$ESCAPE_CHAR_3") \
|
||||
| ui::fzf "${args[@]}" \
|
||||
| ($best && head -n1 || cat) \
|
||||
| selection::dict
|
||||
| selection::dict "$cheats"
|
||||
}
|
||||
|
||||
ui::clear_previous_line() {
|
||||
tput cuu1 2>/dev/null && tput el || true
|
||||
}
|
||||
|
||||
ui::width() {
|
||||
shopt -s checkwinsize; (:;:) 2> /dev/null || true
|
||||
if command_exists tput; then
|
||||
tput cols
|
||||
else
|
||||
echo 130
|
||||
fi
|
||||
}
|
||||
|
||||
ui::print_preview() {
|
||||
local -r selection="$1"
|
||||
|
||||
local -r comment="$(selection::comment "$selection" | cmd::unescape)"
|
||||
local -r snippet="$(selection::snippet "$selection" | cmd::unescape)"
|
||||
local -r tags="$(selection::tags "$selection" | cmd::unescape)"
|
||||
|
||||
printf '\033[34m# '; echo -n "$comment"
|
||||
printf " \033[90m["; echo -n "$tags"; echo "]"
|
||||
printf '\033[0m'
|
||||
echo "$snippet"
|
||||
}
|
Loading…
Reference in New Issue
Block a user