219 lines
7.4 KiB
Bash
Executable File
219 lines
7.4 KiB
Bash
Executable File
#!/usr/bin/bash
|
||
# video demo at: http://www.youtube.com/watch?v=90xoathBYfk
|
||
|
||
# augmented by barbuk: https://github.com/BarbUk/dotfiles/blob/master/bin/snippy
|
||
# . restore current clipboard
|
||
# . {clipboard} placeholder to use current clipboard in snippet
|
||
# . {cursor} placeholder to place the cursor
|
||
# . go left to the correct position for cli and gui paste
|
||
# . go up for block snippet for gui paste
|
||
# . ##noparse header in snippet to not parse
|
||
# . execute command begining by $
|
||
# . execute bash script in $snippets_directory/scripts
|
||
#
|
||
# augmented by "opennomad": https://gist.github.com/opennomad/15c4d624dce99066a82d
|
||
# originally written by "mhwombat": https://bbs.archlinux.org/viewtopic.php?id=71938&p=2
|
||
# Based on "snippy" by "sessy"
|
||
# (https://bbs.archlinux.org/viewtopic.php?id=71938)
|
||
#
|
||
# You will also need "dmenu", "zenity", "xsel", "sponge" and "xdotool". Get them from your linux
|
||
# distro in the usual way.
|
||
#
|
||
# To use:
|
||
# 1. Create the directory ~/.snippy
|
||
#
|
||
# 2. Create a file in that directory for each snippet that you want.
|
||
# The filename will be used as a menu item, so you might want to
|
||
# omit the file extension when you name the file.
|
||
#
|
||
# TIP: If you have a lot of snippets, you can organise them into
|
||
# subdirectories under ~/.snippy.
|
||
#
|
||
# TIP: The contents of the file will be pasted asis, so if you
|
||
# don't want a newline at the end when the text is pasted, don't
|
||
# put one in the file.
|
||
#
|
||
# 3. Bind a convenient key combination to this script.
|
||
#
|
||
# TIP: If you're using XMonad, add something like this to xmonad.hs
|
||
# ((mod4Mask, xK_s), spawn "/path/to/snippy")
|
||
#
|
||
|
||
readonly snippets_directory=${HOME}/.snippy
|
||
readonly MENU_ENGINE="rofi"
|
||
readonly DMENU_ARGS='-dmenu -i -sort'
|
||
|
||
# if nothing happens, try "xdotool click 2", "xdotool key ctrl+v" or "xdotool key ctrl+shift+v"
|
||
readonly GUIPASTE="xdotool key ctrl+v"
|
||
readonly CLIPASTE="xdotool key ctrl+shift+v"
|
||
|
||
readonly MENU_ARGS=${DMENU_ARGS}
|
||
readonly cursor_placeholder="{cursor}"
|
||
readonly clipboard_placeholder="{clipboard}"
|
||
|
||
readonly tmpfile=$(mktemp)
|
||
trap 'rm -f $tmpfile' EXIT HUP INT TRAP TERM
|
||
|
||
# smarty like template engine which executes inline bash in (bashdown) strings (replaces variables with values e.g.)
|
||
# @link http://github.com/coderofsalvation/bashdown
|
||
# @dependancies: sed cat
|
||
# @example: echo 'hi $NAME it is $(date)' | bashdown
|
||
# fetches a document and interprets bashsyntax in string (bashdown) templates
|
||
# @param string - string with bash(down) syntax (usually surrounded by ' quotes instead of ")
|
||
bashdown() {
|
||
while IFS= read -r line; do
|
||
line="$(eval "printf -- \"$( printf "%s" "$line" | sed 's/"/\\"/g' )\"")";
|
||
echo -e "$line"
|
||
done
|
||
}
|
||
|
||
# Simplified version of bashdown, use echo and bash var search and replace
|
||
# Better handling of symbol char
|
||
bashdown_simple() {
|
||
while IFS= read -r line; do
|
||
line="$(eval "echo \"${line//\"/\\\"}\"")"
|
||
echo "$line"
|
||
done
|
||
}
|
||
|
||
# Detect if focused app is a terminal or a gui
|
||
is_gui() {
|
||
name="$(xprop -id "$(xdotool getwindowfocus)" WM_CLASS | cut -d'"' -f2 | tr '[:upper:]' '[:lower:]')"
|
||
[[ "$name" =~ term|tilda|kitty|alacritty ]] && return 1
|
||
return 0
|
||
}
|
||
|
||
# Find the index of a string in a string
|
||
strindex() {
|
||
x="${1%%$2*}"
|
||
[[ "$x" = "$1" ]] && echo -1 || echo "${#x}"
|
||
}
|
||
|
||
is_rich_snippet() {
|
||
file="$1"
|
||
grep -q "##richsnippet" "$file"
|
||
}
|
||
|
||
# Move the cursor up or left
|
||
move_cursor() {
|
||
local key=$1
|
||
local count=$2
|
||
local keys="End "
|
||
if [[ $count -gt 0 ]]; then
|
||
until [ "$count" -eq 0 ]; do
|
||
keys+="${key} "
|
||
((count-=1))
|
||
done
|
||
# shellcheck disable=SC2086
|
||
xdotool key --delay 0.1 $keys
|
||
fi
|
||
}
|
||
|
||
run() {
|
||
cd "${snippets_directory}" || exit
|
||
|
||
local current_clipboard cursor_line cursor_line_position cursor_line cursor_position cursor_line_lenght
|
||
|
||
# Use the filenames in the snippy directory as menu entries.
|
||
# Get the menu selection from the user.
|
||
# shellcheck disable=SC2086
|
||
snippet=$( find -L . -type f \
|
||
| grep -v '^\.$' \
|
||
| sed 's!\.\/!!' \
|
||
| grep -vE '\.git/|\.gitconfig|\.gitkeep' \
|
||
| ${MENU_ENGINE} ${MENU_ARGS} -p '❯ ')
|
||
|
||
# just return if nothing was selected
|
||
[[ -z "${snippet}" ]] && return 1
|
||
|
||
if [ -f "${snippets_directory}/${snippet}" ]; then
|
||
|
||
# Put the contents of the selected file into the paste buffer.
|
||
# If file is empty, the content is the basename of the file
|
||
if [ ! -s "${snippets_directory}/${snippet}" ]; then
|
||
content="$(basename "${snippet}")"
|
||
|
||
# don't parse file with the ##noparse header
|
||
elif grep -qE "^##noparse" "${snippets_directory}/${snippet}"; then
|
||
content="$( tail -n +2 "${snippets_directory}/${snippet}" )"
|
||
|
||
# execute bash script in scripts dir
|
||
elif [[ $(dirname "${snippet}") == 'scripts' ]] && grep -qE "^#!/bin/bash" "${snippets_directory}/${snippet}"; then
|
||
content="$( bash "${snippets_directory}/${snippet}" )"
|
||
|
||
# default action
|
||
else
|
||
content="$( bashdown_simple < "${snippets_directory}/${snippet}" )"
|
||
fi
|
||
|
||
printf "%s" "$content" > "$tmpfile"
|
||
|
||
else [[ ${snippet} =~ ^$ ]]
|
||
${snippet##*$} 2>/dev/null > "$tmpfile" # execute as bashcommand
|
||
fi
|
||
|
||
# save current clipboard
|
||
current_clipboard=$(xsel -b)
|
||
# replace {clipboard} by the clipboard content
|
||
# use awk to handle correctly multiline clipboard
|
||
if grep -q "$clipboard_placeholder" "$tmpfile"; then
|
||
awk \
|
||
-v clipboard="$current_clipboard" \
|
||
-v placeholder="$clipboard_placeholder" \
|
||
'{ gsub(placeholder, clipboard); print }' "$tmpfile" | sponge "$tmpfile"
|
||
|
||
# remove last EOL
|
||
perl -pi -e 'chomp if eof' "$tmpfile"
|
||
fi
|
||
|
||
|
||
# define cursor position and line at 0, we don't need to go up or left if there is no {cursor} placeholder
|
||
cursor_line_position=0
|
||
cursor_position=0
|
||
|
||
# Check if there is a {cursor} placeholder
|
||
if grep -qF $cursor_placeholder "$tmpfile"; then
|
||
# retrieve the line number of the cursor placeholder
|
||
cursor_line_position=$(grep -n "$cursor_placeholder" "$tmpfile" | cut -d: -f1)
|
||
# retrieve the line
|
||
cursor_line=$(grep $cursor_placeholder "$tmpfile")
|
||
# calculate snippet total lines
|
||
file_lines=$(wc -l < "$tmpfile")
|
||
# determine the number of line to go up
|
||
cursor_line_position=$(( file_lines - cursor_line_position + 1 ))
|
||
# Extract cursor position
|
||
cursor_position=$(strindex "$cursor_line" $cursor_placeholder)
|
||
# total cursor line lenght
|
||
cursor_line_lenght=${#cursor_line}
|
||
# Compute the final cursor position ( 8 is the lenght of the placeholder {cursor} )
|
||
cursor_position=$(( cursor_line_lenght - cursor_position - 8 ))
|
||
# remove the placeholder
|
||
sed -i -e "s/$cursor_placeholder//g" "$tmpfile"
|
||
fi
|
||
|
||
# Copy snippet in clipboard
|
||
if is_rich_snippet "${snippets_directory}/${snippet}"; then
|
||
xclip -t text/html -selection clipboard < "$tmpfile"
|
||
else
|
||
xclip -selection clipboard < "$tmpfile"
|
||
fi
|
||
|
||
# Paste into the current application.
|
||
if is_gui; then
|
||
# We need a little pause to handle the time to focus on the window
|
||
sleep 0.17
|
||
|
||
${GUIPASTE}
|
||
move_cursor "Up" $cursor_line_position
|
||
else
|
||
${CLIPASTE}
|
||
fi
|
||
|
||
move_cursor "Left" $cursor_position
|
||
|
||
# Restore current clipboard
|
||
echo -ne "$current_clipboard" | xsel -b --input
|
||
}
|
||
|
||
run
|