joplin-snippy/snippy
2019-10-28 15:08:28 +04:00

230 lines
7.8 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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}" )"
# replace tmpfile for snippets with ##tmpfile header
elif grep -qE "^##tmpfile" "${snippets_directory}/${snippet}"; then
content="$( bashdown_simple <<< "$(tail -n +2 "${snippets_directory}/${snippet}" | sed "s%\$tmpfile%$tmpfile%g" )" )"
# 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
if [ -n "$content" ]; then
printf "%s" "$content" > "$tmpfile"
fi
else [[ ${snippet} =~ ^$ ]]
${snippet##*$} 2>/dev/null > "$tmpfile" # execute as bashcommand
fi
# if tmpfile is empty at this step, nothing to do.
if [ ! -s "$tmpfile" ]; then
return
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 --clipboard --input
}
run