#!/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 # . ##tmpfile header in snippet to replace $tmpfile by the temp filename used in the script # . ##richsnippet header in snippet to use paste buffer in rich mode (To copy html content in gui) # . 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