From 65588cd2e00172f62f8054f3d25b2e3ce981368c Mon Sep 17 00:00:00 2001 From: Nick Patavalis Date: Thu, 8 Feb 2018 14:36:32 +0200 Subject: [PATCH] Added custom bash completion script for picocom --- bash_completion/picocom | 401 +++++++++++++++++++++++++++++++++++++ bash_completion/readme.txt | 70 +++++++ 2 files changed, 471 insertions(+) create mode 100644 bash_completion/picocom create mode 100644 bash_completion/readme.txt diff --git a/bash_completion/picocom b/bash_completion/picocom new file mode 100644 index 0000000..94d7039 --- /dev/null +++ b/bash_completion/picocom @@ -0,0 +1,401 @@ +# Simple custom bash completion for picocom. +# +# Source this file like this: +# . /bash-completion/picocom +# Or arrange for it to be sourced by your ".bashrc", +# Or copy it in /etc/bash_completion.d (it will be sourced automatically) +# +# The idea is to provide simple custom completions for options names +# and options values, while keeping the standard ones (variable, +# pathname, etc) if the custom ones don't produce matches. It does not +# depend on the "bash-completion" package (just plain bash) and it +# does not use any of its helper functions. +# +# The code is not bullet-proof; you *can* confuse it with strange +# input if you try. There is also a known issue with giving option +# values like this: +# +# --option="value" or --option='value' +# +# That is, with an equal *and* within quotes. The solution is also +# known. For simplicty, though, I decided not to include it. +# +# See also: +# Bash mapage (man bash) +# Bash reference manual, sections 8.6, 8.7, 8.8 +# The bash-completion project / package +# https://github.com/scop/bash-completion +# https://debian-administration.org/article/ +# 316/An_introduction_to_bash_completion_part_1 +# https://debian-administration.org/article/ +# 316/An_introduction_to_bash_completion_part_2 +# +# Tested with: +# GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu) +# GNU bash, version 4.4.18(1)-release (x86_64-unknown-linux-gnu) +# +# by Nick Patavalis (npat@efault.net) +# +# This program is free software; you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA +# + +# _npat_split_line +# +# Splits line into words. Line is split in almost exatcly the same way +# as readline does it. Using this function, you can specify your own +# delimiter and separator characters without having to mess with +# COMP_WORDBREAKS (which may affect other completion functions / +# scripts). In addtion, this function takes into account COMP_POINT +# and splits up to it, which may help when completing to the middle of +# an argument. +# +# Line is split taking into account quoting (backslash, single and +# double), as well as ${...} and $(...). Double-quotes, ${..} and +# $(..} can nest inside each other at any depth. Words are broken up +# either by delimiter characters (which do not appear in the resulting +# word-straem) or by separator characters, which do. +# +# +function _npat_split_line() +{ + local delimiters=$' \t\n' + local separators=$'=' + local line wbreaks state word c c1 i + local -a stack + + while getopts "d:s:" flag "$@"; do + case $flag in + d) delimiters=$OPTARG ;; + s) separators=$OPTARG ;; + esac + done + + # state names: ' (single quote), + # " (double quote), + # { (string-brace), + # ( (string-paren) + # ` (backquote) + # D (delimiter) + # W (naked word) + # + # ", ${, and $( can nest inside each-other any number of times + + wbreaks=$delimiters$separators + line=${COMP_LINE:0:$COMP_POINT} + state=D + for (( i=0; i<${#line}; i++)); do + c=${line:i:1} + c1=${line:i+1:1} + #echo " [$i\t$c\t$c1\t$state\t$word\t\t${stack[*]} ]" > $DEBUG + if [[ $state == D || $state == W ]]; then + if [[ $c == [$wbreaks] ]]; then + if [[ $state == W ]]; then + words+=( "$word" ) + word= + state=D + fi + # handling of separators vs delimiters. Separators are + # treated like delimiters, but they do appear in the + # word stream. Consequitive separators are treated as + # a single word. Ex: + # + # ab=@c=d =@= e=f + # + # will be split like this: + # + # ab | =@ | c | = | d | =@= | e | = | f + # + # assuming that = and @ are separators + if [[ $c == [$separators] ]]; then + while [[ $c == [$separators] ]]; do + word+=$c + let i++ + c=${line:i:1} + done + words+=( "$word" ) + word= + let i-- + fi + continue + elif [[ $c == [\'\"\`] ]]; then + stack+=( W ) + state=$c + elif [[ $c == '\' ]]; then + word+=$c + let i++ + c=$c1 + state=W + elif [[ $c == '$' && ( $c1 == '(' || $c1 == '{' ) ]]; then + word+=$c + let i++ + c=$c1 + stack+=( W ) + state=$c1 + else + state=W + fi + word+=$c + elif [[ $state == "'" ]]; then + if [[ $c == "'" ]]; then + state=${stack[-1]} + unset stack[-1] + fi + word+=$c + elif [[ $state == '"' ]]; then + if [[ $c == '\' ]]; then + word+=$c + let i++ + c=$c1 + elif [[ $c == '`' ]]; then + stack+=( W ) + state=$c + elif [[ $c == '$' && ( $c1 == '(' || $c1 == '{' ) ]]; then + let i++ + word+=$c + c=$c1 + stack+=( $state ) + state=$c1 + elif [[ $c == '"' ]]; then + state=${stack[-1]} + unset stack[-1] + fi + word+=$c + elif [[ $state == '(' || $state == '{' || state == '`' ]]; then + if [[ $c == [\'\"] ]]; then + stack+=( $state ) + state=$c + elif [[ $c == '\' ]]; then + word+=$c + let i++ + c=$c1 + elif [[ $c == '$' && ( $c1 == '(' || $c1 == '{' ) ]]; then + let i++ + word+=$c + c=$c1 + stack+=( $state ) + state=$c + elif [[ $state$c == '{}' || $state$c == "()" || $state$c == '``' ]]; then + state=${stack[-1]} + unset stack[-1] + fi + word+=$c + fi + done + words+=( "$word" ) +} + +_picocom_dequote() +{ + local quoted="$1" + local word i inside + + for (( i=0; i<${#quoted}; i++ )); do + c=${quoted:i:1} + c1=${quoted:i+1:1} + #echo " [$c] [$c1] [$inside]" + if [[ -z $inside ]]; then + if [[ $c == '\' ]]; then + let i++ + c=$c1 + elif [[ $c == [\'\"] ]]; then + inside=$c + continue + fi + elif [[ $inside == "'" ]]; then + if [[ $c == "'" ]]; then + inside= + continue + fi + elif [[ $inside == '"' ]]; then + if [[ $c == '\' ]]; then + let i++ + c=$c1 + elif [[ $c == '"' ]]; then + inside= + continue + fi + fi + word+=$c + done + echo "$word" +} + +_picocom_filter_mappings() +{ + local IFS cur1 m c found + local -a cura + + cur1=$(_picocom_dequote "$cur") + IFS=$', \t' + cura=( $cur1 ) + IFS=$' \t\n' + for m in "${mappings[@]}"; do + found= + for c in "${cura[@]}"; do + [[ $c == "$m" ]] && { found=yes; break; } + done + [[ -z $found ]] && mapfilt+=( "$m" ) + done +} + +# Check if $1 is valid picocom option name +_picocom_is_opt() +{ + local e match="$1" + for e in "${opts[@]}"; do + [[ $e == "$match" ]] && return 0 + done + return 1 +} + +# Custom completion function for picocom +_picocom() +{ + local cur cur0 cur1 prev + local -a opts baudrates mappings mapfilt + local DEBUG=/dev/pts/8 + + opts=( --baud --flow --databits --stopbits --parity \ + --lower-rts --lower-dtr --raise-rts --raise-dtr \ + --imap --omap --emap + --echo --initstring \ + --noinit --noreset --hangup \ + --receive-cmd --send-cmd \ + --escape --no-escape \ + --logfile \ + --exit-after --exit \ + --nolock \ + --quiet --help \ + ) + + baudrates=( 50 75 110 134 150 200 300 600 1200 1800 2400 4800 9600 \ + 19200 38400 57600 115200 \ + 230400 460800 500000 576000 921600 1000000 1152000 1500000 \ + 2000000 2500000 3000000 3500000 4000000 ) + + mappings=( crlf crcrlf igncr lfcr lfcrlf ignlf delbs bsdel \ + spchex tabhex crhex lfhex 8bithex nrmhex ) + + _npat_split_line + cur="${words[-1]}" + prev="${words[-2]}" + #cur="${COMP_WORDS[COMP_CWORD]}" + #prev="${COMP_WORDS[COMP_CWORD-1]}" + + echo > $DEBUG + echo "------------" > $DEBUG + echo COMP_LINE "$COMP_LINE" > $DEBUG + echo COMP_POINT $COMP_POINT > $DEBUG + echo COMP_CWORD $COMP_CWORD > $DEBUG + local wrd + for wrd in "${COMP_WORDS[@]}"; do + echo -n "$wrd | " > $DEBUG + done + echo > $DEBUG + echo "$prev | $cur | " > $DEBUG + + # Try to handle option values given with "=" + if [[ $cur == "=" ]]; then + _picocom_is_opt "$prev" && cur= + fi + if [[ $prev == "=" && $COMP_CWORD -gt 1 ]]; then + prev1="${COMP_WORDS[COMP_CWORD-2]}" + _picocom_is_opt "$prev1" && prev="$prev1" + fi + + case "$prev" in + -v | --receive-cmd | -s | --send-cmd) + # nothing special, just default completion + return 0 + ;; + -I | --imap | -O | --omap | -E | --emap ) + [[ "$cur" =~ ^[\'\"]?[A-Za-z0-9,\ \\]*[\'\"]?$ ]] || return 0 + _picocom_filter_mappings + cur1="${cur##*[, ]}" + cur0="${cur%$cur1}" + echo "$cur0 | $cur1 |" > $DEBUG + local IFS=$'\n' + COMPREPLY=( $(compgen -P "$cur0" -S "," -W "${mapfilt[*]}" -- "$cur1") ) + echo "${COMPREPLY[*]}" > $DEBUG + if [[ ${#COMPREPLY[@]} -ne 0 ]]; then + compopt -o nospace + # This only works for bash-4.4 and newer + compopt -o nosort > /dev/null 2>&1 + fi + return 0 + ;; + -e | --escape) + # nothing special, just default completion + return 0 + ;; + -f | --flow) + COMPREPLY=( $(compgen -W "hard soft none" -- "$cur") ) + return 0 + ;; + -b | --baud) + COMPREPLY=( $(compgen -W "${baudrates[*]}" -- "$cur") ) + if [[ ${#COMPREPLY[@]} -ne 0 ]]; then + # This only works for bash 4.4 and newer + compopt -o nosort > /dev/null 2>&1 + fi + return 0 + ;; + -y | --parity) + COMPREPLY=( $(compgen -W "even odd none" -- "$cur") ) + return 0 + ;; + -d | --databits) + COMPREPLY=( $(compgen -W "5 6 7 8" -- "$cur") ) + return 0 + ;; + -p | --stopbits) + COMPREPLY=( $(compgen -W "1 2" -- "$cur") ) + return 0 + ;; + -g | --logfile) + # nothing special, just default completion + return 0 + ;; + -t | --initstring) + # nothing special, just default completion + return 0 + ;; + -x | --exit-after) + # nothing special, just default completion + return 0 + ;; + *) + ;; + esac + + if [[ ${cur} = -* ]] ; then + COMPREPLY=( $(compgen -W "${opts[*]}" -- "$cur") ) + # This only works for bash 4.4 and newer + compopt -o nosort > /dev/null 2>&1 + return 0 + fi + + if [[ -z $cur ]]; then + COMPREPLY=( $(compgen -G "/dev/tty*") ) + return 0 + fi +} + +# Bind custom completion function to command +complete -o default -o bashdefault -F _picocom picocom + +# Local variables: +# mode: sh +# End: diff --git a/bash_completion/readme.txt b/bash_completion/readme.txt new file mode 100644 index 0000000..552b33c --- /dev/null +++ b/bash_completion/readme.txt @@ -0,0 +1,70 @@ + +Starting with release 3.2, picocom includes support for custom +bash-shell completion. With this you can press the [TAB] key and have +the bash shell complete command-line option names and values and +propose valid selections for both. This makes the experience of using +picocom much more pleasant. + +Custom bash-shell completion works only with recent versions of the +bash shell (>= 4.3). + +To manually enable custom completion support you need to source the +file (custom completion script): + + /bash_completion/picocom + +Assuming you are inside the picocom source directory, you can do it +like this: + + . ./bash_completion/picocom + +This will enable custom completion support for the current shell +session only. Give in a ride and see if you like it. + +To enable support automatically for all bash-shell sessions, you have +the following options: + +1. If you are running a relatively modern Debian or Ubuntu or other + Debian-based distribution, you can simply copy the custom + completion script to the directory: + + /etc/bash_completion.d/ + + Obviously, you need to be root to do this. Assuming you are inside + the picocom source directory, something like this will do it: + + sudo cp ./bash_completion/picocom /etc/bash_completion.d/ + + This will enable custom completion support for picocom, globaly + (for all bash-shell users). + + For other distributions and operating systems you have to check + their documentation to see if they provide a similar mechanism for + automatically sourcing custom completion scripts. + +2. If you want to automatically enable support *only for the current + user*, you must arange for your user's `.bashrc` to source the + custom completion script. There are, obviously, many ways to do + this, so the following *is only a suggestion*: + + Create a directory to keep the custom completion scripts + + mkdir ~/.bash_completion.d + + Copy the picocom completion script to the directory you + created. Assuming you are inside the picocom source directory: + + cp ./bash_completion/picocom ~/.bash_completion.d + + Add the following to the end of your `.bashrc` + + # Source custom bash completions + if [ -d "$HOME"/.bash_completion.d ]; then + for c in "$HOME"/.bash_completion.d/*; do + [ -r "$c" ] && . "$c" + done + fi + + From now on every new shell session you start will load (source) + all the custom completion scripts you have put in + `~/.bash_completion.d`