Rewrite snippy utility
It now handle:
 * multiline {clipboard}
 * cli {cursor} handling
 * gui {cursor} handling for multiline snippets
			
			
This commit is contained in:
		
							
								
								
									
										142
									
								
								snippy
									
									
									
									
									
								
							
							
						
						
									
										142
									
								
								snippy
									
									
									
									
									
								
							@@ -1,10 +1,12 @@
 | 
				
			|||||||
#!/usr/bin/env bash
 | 
					#!/usr/bin/bash
 | 
				
			||||||
# video demo at: http://www.youtube.com/watch?v=90xoathBYfk
 | 
					# video demo at: http://www.youtube.com/watch?v=90xoathBYfk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# augmented by barbuk: https://github.com/BarbUk/dotfiles/blob/master/bin/snippy
 | 
					# augmented by barbuk: https://github.com/BarbUk/dotfiles/blob/master/bin/snippy
 | 
				
			||||||
#  . restore current clipboard
 | 
					#  . restore current clipboard
 | 
				
			||||||
#  . {clipboard} placeholder to use current clipboard in snippet
 | 
					#  . {clipboard} placeholder to use current clipboard in snippet
 | 
				
			||||||
#  . {cursor} placeholder to place the cursor
 | 
					#  . {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
 | 
					#  . ##noparse header in snippet to not parse
 | 
				
			||||||
#  . execute command begining by $
 | 
					#  . execute command begining by $
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
@@ -42,16 +44,20 @@
 | 
				
			|||||||
#    GUIPASTE="xdotool key ctrl+v"
 | 
					#    GUIPASTE="xdotool key ctrl+v"
 | 
				
			||||||
#    CLIPASTE="xdotool key ctrl+shift+v"
 | 
					#    CLIPASTE="xdotool key ctrl+shift+v"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DIR=${HOME}/.snippy
 | 
					readonly snippets_directory=${HOME}/.snippy
 | 
				
			||||||
MENU_ENGINE="rofi"
 | 
					readonly MENU_ENGINE="rofi"
 | 
				
			||||||
DMENU_ARGS='-dmenu -i -sort'
 | 
					readonly DMENU_ARGS='-dmenu -i -sort'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# if nothing happens, try "xdotool click 2", "xdotool key ctrl+v" or "xdotool key ctrl+shift+v"
 | 
					# if nothing happens, try "xdotool click 2", "xdotool key ctrl+v" or "xdotool key ctrl+shift+v"
 | 
				
			||||||
GUIPASTE="xdotool key ctrl+v"
 | 
					readonly GUIPASTE="xdotool key ctrl+v"
 | 
				
			||||||
CLIPASTE="xdotool key ctrl+shift+v"
 | 
					readonly CLIPASTE="xdotool key ctrl+shift+v"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TMPFILE="/tmp/.snippy.tmp"; :>$TMPFILE
 | 
					readonly MENU_ARGS=${DMENU_ARGS}
 | 
				
			||||||
 | 
					readonly cursor_placeholder="{cursor}"
 | 
				
			||||||
 | 
					readonly clipboard_placeholder="{clipboard}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
MENU_ARGS=${DMENU_ARGS}
 | 
					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.)
 | 
					# smarty like template engine which executes inline bash in (bashdown) strings (replaces variables with values e.g.)
 | 
				
			||||||
# @link http://github.com/coderofsalvation/bashdown
 | 
					# @link http://github.com/coderofsalvation/bashdown
 | 
				
			||||||
@@ -59,81 +65,121 @@ MENU_ARGS=${DMENU_ARGS}
 | 
				
			|||||||
# @example: echo 'hi $NAME it is $(date)' | bashdown
 | 
					# @example: echo 'hi $NAME it is $(date)' | bashdown
 | 
				
			||||||
# fetches a document and interprets bashsyntax in string (bashdown) templates
 | 
					# fetches a document and interprets bashsyntax in string (bashdown) templates
 | 
				
			||||||
# @param string - string with bash(down) syntax (usually surrounded by ' quotes instead of ")
 | 
					# @param string - string with bash(down) syntax (usually surrounded by ' quotes instead of ")
 | 
				
			||||||
bashdown(){
 | 
					bashdown() {
 | 
				
			||||||
    while IFS= read -r line; do
 | 
					    while IFS= read -r line; do
 | 
				
			||||||
        line="$(eval "printf -- \"$( printf "%s" "$line" | sed 's/"/\\"/g')\"")";
 | 
					        line="$(eval "printf -- \"$( printf "%s" "$line" | sed 's/"/\\"/g')\"")";
 | 
				
			||||||
        echo -e "$line"
 | 
					        echo -e "$line"
 | 
				
			||||||
    done
 | 
					    done
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
is_gui(){
 | 
					# 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="$(xprop -id "$(xdotool getwindowfocus)" WM_CLASS | cut -d'"' -f2 | tr '[:upper:]' '[:lower:]')"
 | 
				
			||||||
    [[ "$name" =~ term|tilda|kitty|alacritty ]] && return 1
 | 
					    [[ "$name" =~ term|tilda|kitty|alacritty ]] && return 1
 | 
				
			||||||
    return 0
 | 
					    return 0
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run(){
 | 
					# Find the index of a string in a string
 | 
				
			||||||
    cd "${DIR}" || exit
 | 
					strindex() {
 | 
				
			||||||
 | 
					    x="${1%%$2*}"
 | 
				
			||||||
 | 
					    [[ "$x" = "$1" ]] && echo -1 || echo "${#x}"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 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
 | 
				
			||||||
 | 
					    current_clipboard=$(xsel -b)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Use the filenames in the snippy directory as menu entries.
 | 
					    # Use the filenames in the snippy directory as menu entries.
 | 
				
			||||||
    # Get the menu selection from the user.
 | 
					    # Get the menu selection from the user.
 | 
				
			||||||
    # shellcheck disable=SC2086
 | 
					    # shellcheck disable=SC2086
 | 
				
			||||||
    FILE=$(find -L .  -type f | grep -v '^\.$' | sed 's!\.\/!!' | grep -vE '\.git/|\.gitconfig|\.gitkeep' | ${MENU_ENGINE} ${MENU_ARGS} -p '❯ ')
 | 
					    snippet=$( find -L .  -type f \
 | 
				
			||||||
    # just return if nothing was selected
 | 
					        | grep -v '^\.$' \
 | 
				
			||||||
    [[ -z "${FILE}" ]] && return 1
 | 
					        | sed 's!\.\/!!' \
 | 
				
			||||||
 | 
					        | grep -vE '\.git/|\.gitconfig|\.gitkeep' \
 | 
				
			||||||
 | 
					        | ${MENU_ENGINE} ${MENU_ARGS} -p '❯ ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if [ -f "${DIR}/${FILE}" ]; then
 | 
					    # 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.
 | 
					        # Put the contents of the selected file into the paste buffer.
 | 
				
			||||||
        # don't parse file with the ##noparse header
 | 
					        # don't parse file with the ##noparse header
 | 
				
			||||||
        if grep -qE "^##noparse" "${FILE}"; then
 | 
					        if grep -qE "^##noparse" "${snippets_directory}/${snippet}"; then
 | 
				
			||||||
            content="$(tail -n +2 "${DIR}/${FILE}")"
 | 
					            content="$( tail -n +2 "${snippets_directory}/${snippet}" )"
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
            content="$(bashdown < "${DIR}/${FILE}")"
 | 
					            content="$( bashdown < "${snippets_directory}/${snippet}" )"
 | 
				
			||||||
        fi
 | 
					        fi
 | 
				
			||||||
        printf "%s" "$content" > $TMPFILE
 | 
					
 | 
				
			||||||
    else [[ ${FILE} =~ ^$ ]]
 | 
					        # replace {clipboard} by the clipboard content
 | 
				
			||||||
        ${FILE##*$} 2>/dev/null > $TMPFILE # execute as bashcommand
 | 
					        # use awk to handle correctly multiline clipboard
 | 
				
			||||||
 | 
					        printf "%s" "$content" | awk \
 | 
				
			||||||
 | 
					        -v clipboard="$current_clipboard" \
 | 
				
			||||||
 | 
					        -v placeholder="$clipboard_placeholder" \
 | 
				
			||||||
 | 
					        '{ gsub(placeholder, clipboard); print }' > "$tmpfile"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    else [[ ${snippet} =~ ^$ ]]
 | 
				
			||||||
 | 
					        ${snippet##*$} 2>/dev/null > "$tmpfile" # execute as bashcommand
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    local current_clip cursor
 | 
					    # define cursor position and line at 0, we don't need to go up or left if there is no {cursor} placeholder
 | 
				
			||||||
    current_clip=$(xsel -b)
 | 
					    cursor_line_position=0
 | 
				
			||||||
    # define cursor at 0, we don't need to go up if there is no {cursor}
 | 
					    cursor_position=0
 | 
				
			||||||
    cursor=0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # replace {clipboard} by the cliboard comtent
 | 
					 | 
				
			||||||
    sed -i -e "s~{clipboard}~${current_clip}~g" "$TMPFILE"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Check if there is a {cursor} placeholder
 | 
					    # Check if there is a {cursor} placeholder
 | 
				
			||||||
    if grep -qF '{cursor}' "$TMPFILE";then
 | 
					    if grep -qF $cursor_placeholder "$tmpfile"; then
 | 
				
			||||||
        local file_lines
 | 
					        # retrieve the line number of the cursor placeholder
 | 
				
			||||||
 | 
					        cursor_line_position=$(grep -n "$cursor_placeholder" "$tmpfile" | cut -d: -f1)
 | 
				
			||||||
        # retrieve the line
 | 
					        # retrieve the line
 | 
				
			||||||
        cursor=$(grep -n '{cursor}' "$TMPFILE" | cut -d: -f1)
 | 
					        cursor_line=$(grep $cursor_placeholder "$tmpfile")
 | 
				
			||||||
        # calculate snippet total lines
 | 
					        # calculate snippet total lines
 | 
				
			||||||
        file_lines=$(wc -l < "$TMPFILE")
 | 
					        file_lines=$(wc -l < "$tmpfile")
 | 
				
			||||||
        # determine the number of line to go
 | 
					        # determine the number of line to go up
 | 
				
			||||||
        cursor=$((file_lines - cursor))
 | 
					        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
 | 
					        # remove the placeholder
 | 
				
			||||||
        sed -i -e "s/{cursor}//g" "$TMPFILE"
 | 
					        sed -i -e "s/$cursor_placeholder//g" "$tmpfile"
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    xsel -b --input < "$TMPFILE"
 | 
					    # Copy snippet in clipboard
 | 
				
			||||||
 | 
					    xsel -b --input < "$tmpfile"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Paste into the current application.
 | 
					    # Paste into the current application.
 | 
				
			||||||
    if is_gui; then
 | 
					    if is_gui; then
 | 
				
			||||||
        ${GUIPASTE}
 | 
					        # We need a little pause to handle the time to focus on the window
 | 
				
			||||||
 | 
					        sleep 0.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if [[ $cursor -gt 0 ]]; then
 | 
					        ${GUIPASTE}
 | 
				
			||||||
            local keyup
 | 
					        move_cursor "Up" $cursor_line_position
 | 
				
			||||||
            until [  $cursor -eq 0 ]; do
 | 
					 | 
				
			||||||
                keyup+="Up "
 | 
					 | 
				
			||||||
            ((cursor-=1))
 | 
					 | 
				
			||||||
            # shellcheck disable=SC2086
 | 
					 | 
				
			||||||
            xdotool key --delay -0.1 $keyup
 | 
					 | 
				
			||||||
        done
 | 
					 | 
				
			||||||
        fi
 | 
					 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
        ${CLIPASTE}
 | 
					        ${CLIPASTE}
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    echo -ne "$current_clip" | xsel -b --input
 | 
					    move_cursor "Left" $cursor_position
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Restore current clipboard
 | 
				
			||||||
 | 
					    echo -ne "$current_clipboard" | xsel -b --input
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run
 | 
					run
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user