From e8c1e59e2948e3db0a0fca1fa42da030fdd9f971 Mon Sep 17 00:00:00 2001 From: Jorge Morante Date: Sun, 8 May 2016 17:06:02 +0200 Subject: [PATCH] Improve performance with single-pass awk implementation --- scripts/config.sh | 32 +++- scripts/debug.sh | 4 + scripts/fingers.sh | 41 ++--- scripts/hinter.awk | 149 ++++++++++++++++++ scripts/hints.sh | 70 ++------ scripts/tmux-fingers.sh | 3 +- scripts/utils.sh | 16 +- test/fixtures/ip-output | 24 ++- test/helpers.exp | 3 +- .../more-than-one-match-per-line_spec.exp | 4 +- 10 files changed, 239 insertions(+), 107 deletions(-) create mode 100644 scripts/hinter.awk diff --git a/scripts/config.sh b/scripts/config.sh index 1931a91..f659387 100755 --- a/scripts/config.sh +++ b/scripts/config.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +source $CURRENT_DIR/utils.sh +# TODO empty patterns are invalid function check_pattern() { echo "beep beep" | grep -e "$1" 2> /dev/null @@ -12,15 +14,31 @@ function check_pattern() { fi } +HAS_GAWK=$(which gawk &> /dev/null && echo $(($? == 0))) + +function supports_intervals_in_awk() { + echo "wtfwtfwtf" | __awk__ "/(wtf){3}/ { print \"wtf\" }" | grep -c wtf +} + source "$CURRENT_DIR/utils.sh" -PATTERNS_LIST=( -"((^|^\.|[[:space:]]|[[:space:]]\.|[[:space:]]\.\.|^\.\.)[[:alnum:]~_-]*/[][[:alnum:]_.#$%&+=/@-]*)" -"([[:digit:]]{4,})" -"([0-9a-f]{7}|[0-9a-f]{40})" -"((https?://|git@|git://|ssh://|ftp://|file:///)[[:alnum:]?=%/_.:,;~@!#$&()*+-]*)" -"([[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3})" -) +if [[ supports_intervals_in_awk == "1" ]]; then + PATTERNS_LIST=( + "((^|^\.|[[:space:]]|[[:space:]]\.|[[:space:]]\.\.|^\.\.)[[:alnum:]~_-]*/[][[:alnum:]_.#$%&+=/@-]*)" + "([[:digit:]]{4,})" + "([0-9a-f]{7}|[0-9a-f]{40})" + "((https?://|git@|git://|ssh://|ftp://|file:///)[[:alnum:]?=%/_.:,;~@!#$&()*+-]*)" + "([[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3})" + ) +else + PATTERNS_LIST=( + "((^|^\.|[[:space:]]|[[:space:]]\.|[[:space:]]\.\.|^\.\.)[[:alnum:]~_-]*/[][[:alnum:]_.#$%&+=/@-]*)" + "([[:digit:]][[:digit:]][[:digit:]][[:digit:]]([[:digit:]])*)" + "([0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]|[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f])" + "((https?://|git@|git://|ssh://|ftp://|file:///)[[:alnum:]?=%/_.:,;~@!#$&()*+-]*)" + "([[:digit:]][[:digit:]]?[[:digit:]]?\.[[:digit:]][[:digit:]]?[[:digit:]]?\.[[:digit:]][[:digit:]]?[[:digit:]]?\.[[:digit:]][[:digit:]]?[[:digit:]]?)" + ) +fi IFS=$'\n' USER_DEFINED_PATTERNS=($(tmux show-options -g | grep ^@fingers-pattern | sed 's/^@fingers-pattern-[0-9] "\(.*\)"$/(\1)/')) diff --git a/scripts/debug.sh b/scripts/debug.sh index 69ccfca..2b7d2a9 100755 --- a/scripts/debug.sh +++ b/scripts/debug.sh @@ -5,6 +5,10 @@ CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +function current_ms() { + echo $(($(date +%s%N)/1000000)) +} + function log() { echo "$1" >> "$CURRENT_DIR/../fingers.log" } diff --git a/scripts/fingers.sh b/scripts/fingers.sh index 9aeeb13..5f3e336 100755 --- a/scripts/fingers.sh +++ b/scripts/fingers.sh @@ -1,11 +1,13 @@ #!/usr/bin/env bash CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + source $CURRENT_DIR/config.sh source $CURRENT_DIR/actions.sh source $CURRENT_DIR/hints.sh source $CURRENT_DIR/utils.sh +LOG_PATH=$CURRENT_DIR/../fingers.log FINGERS_COPY_COMMAND=$(tmux show-option -gqv @fingers-copy-command) current_pane_id=$1 @@ -14,15 +16,6 @@ tmp_path=$3 BACKSPACE=$'\177' -function clear_screen() { - clear - tmux clearhist -t "$fingers_pane_id" -} - -function has_capitals() { - echo "$1" | grep -c "[A-Z]" -} - function is_pane_zoomed() { local pane_id=$1 @@ -37,12 +30,6 @@ function zoom_pane() { tmux resize-pane -Z -t "$pane_id" } -clear_screen -print_hints -pane_was_zoomed=$(is_pane_zoomed "$current_pane_id") -tmux swap-pane -s "$current_pane_id" -t "$fingers_pane_id" -[[ $pane_was_zoomed == "1" ]] && zoom_pane "$fingers_pane_id" - function handle_exit() { tmux swap-pane -s "$current_pane_id" -t "$fingers_pane_id" [[ $pane_was_zoomed == "1" ]] && zoom_pane "$current_pane_id" @@ -68,20 +55,6 @@ function copy_result() { fi } -function sanitize_input() { - local input=$(echo "$(str_to_ascii "$1")" | sed -r "s/ 27 91 [0-9]{2}//") - local sanitized='' - - OLDIFS=$IFS - IFS=' ' - for char_code in $input; do - sanitized="${sanitized}$(chr "$char_code")" - done - IFS=$OLDIFS - - echo "$sanitized" -} - function is_valid_input() { local input=$1 local is_valid=1 @@ -98,10 +71,18 @@ function is_valid_input() { echo $is_valid } +function hide_cursor() { + echo -n $(tput civis) +} + trap "handle_exit" EXIT -input='' +pane_was_zoomed=$(is_pane_zoomed "$current_pane_id") +show_hints_and_swap $current_pane_id $fingers_pane_id +[[ $pane_was_zoomed == "1" ]] && zoom_pane "$fingers_pane_id" +hide_cursor +input='' while read -rsn1 char; do # Escape sequence, flush input if [[ "$char" == $'\x1b' ]]; then diff --git a/scripts/hinter.awk b/scripts/hinter.awk new file mode 100644 index 0000000..0f9e222 --- /dev/null +++ b/scripts/hinter.awk @@ -0,0 +1,149 @@ +BEGIN { + n_matches = 0; + line_pos = 0; + col_pos = 0; + + HINTS[0] = "p" + HINTS[1] = "o" + HINTS[2] = "i" + HINTS[3] = "u" + HINTS[4] = "l" + HINTS[5] = "k" + HINTS[6] = "j" + HINTS[7] = "t" + HINTS[8] = "r" + HINTS[9] = "e" + HINTS[10] = "wj" + HINTS[11] = "wt" + HINTS[12] = "wr" + HINTS[13] = "we" + HINTS[14] = "ww" + HINTS[15] = "wq" + HINTS[16] = "wf" + HINTS[17] = "wd" + HINTS[18] = "ws" + HINTS[19] = "wa" + HINTS[20] = "qp" + HINTS[21] = "qo" + HINTS[22] = "qi" + HINTS[23] = "qu" + HINTS[24] = "ql" + HINTS[25] = "qk" + HINTS[26] = "qj" + HINTS[27] = "qt" + HINTS[28] = "qr" + HINTS[29] = "qe" + HINTS[30] = "qw" + HINTS[31] = "qq" + HINTS[32] = "qf" + HINTS[33] = "qd" + HINTS[34] = "qs" + HINTS[35] = "qa" + HINTS[36] = "fp" + HINTS[37] = "fo" + HINTS[38] = "fi" + HINTS[39] = "fu" + HINTS[40] = "fl" + HINTS[41] = "fk" + HINTS[42] = "fj" + HINTS[43] = "ft" + HINTS[44] = "fr" + HINTS[45] = "fe" + HINTS[46] = "fw" + HINTS[47] = "fq" + HINTS[48] = "ff" + HINTS[49] = "fd" + HINTS[50] = "fs" + HINTS[51] = "fa" + HINTS[52] = "dp" + HINTS[53] = "do" + HINTS[54] = "di" + HINTS[55] = "du" + HINTS[56] = "dl" + HINTS[57] = "dk" + HINTS[58] = "dj" + HINTS[59] = "dt" + HINTS[60] = "dr" + HINTS[61] = "de" + HINTS[62] = "dw" + HINTS[63] = "dq" + HINTS[64] = "df" + HINTS[65] = "dd" + HINTS[66] = "ds" + HINTS[67] = "da" + HINTS[68] = "sp" + HINTS[69] = "so" + HINTS[70] = "si" + HINTS[71] = "su" + HINTS[72] = "sl" + HINTS[73] = "sk" + HINTS[74] = "sj" + HINTS[75] = "st" + HINTS[76] = "sr" + HINTS[77] = "se" + HINTS[78] = "sw" + HINTS[79] = "sq" + HINTS[80] = "sf" + HINTS[81] = "sd" + HINTS[82] = "ss" + HINTS[83] = "sa" + HINTS[84] = "ap" + HINTS[85] = "ao" + HINTS[86] = "ai" + HINTS[87] = "au" + HINTS[88] = "al" + HINTS[89] = "ak" + HINTS[90] = "aj" + HINTS[91] = "at" + HINTS[92] = "ar" + HINTS[93] = "ae" + HINTS[94] = "aw" + HINTS[95] = "aq" + HINTS[96] = "af" + HINTS[97] = "ad" + HINTS[98] = "as" + HINTS[99] = "aa" + + finger_patterns = ENVIRON["FINGER_PATTERNS"]; + + hint_format = "\033[1;33m[%s]\033[0m" + highlight_format = "\033[1;33m%s\033[0m " + printf "%s\n", finger_patterns | "cat 1>&4" +} + +{ + line = $0; + pos = 0; + col_pos = 0; + col_pos_correction = 0; + + output_line = line; + + while (match(line, finger_patterns)) { + n_matches += 1; + + hint = HINTS[n_matches - 1] + pos += RSTART; + + col_pos = pos; + line_match = substr(line, RSTART, RLENGTH); + + col_pos = col_pos + col_pos_correction + + line_pos = NR; + + pre_match = substr(output_line, 0, col_pos - 1); + hint_match = sprintf(highlight_format hint_format, line_match, hint); + post_match = substr(output_line, col_pos + RLENGTH, length(line) - 1); + + output_line = pre_match hint_match post_match; + + line = post_match; + + col_pos_correction += (length(sprintf(highlight_format, line_match)) - 1 + length(sprintf(hint_format, hint)) - 1) + 1; + + printf hint ":" line_match "\n" | "cat 1>&3" + } + + printf "\n%s", output_line +} diff --git a/scripts/hints.sh b/scripts/hints.sh index bf904d2..36d8f8a 100755 --- a/scripts/hints.sh +++ b/scripts/hints.sh @@ -1,68 +1,26 @@ #!/usr/bin/env bash CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +source $CURRENT_DIR/utils.sh -MATCH_PARSER="\([0-9]*\):\(.*\)" +match_lookup_table=$(fingers_tmp) -HINTS=(p o i u l k j t r e wj wt wr we ww wq wf wd ws wa qp qo qi qu ql qk qj qt qr qe qw qq qf qd qs qa fp fo fi fu fl fk fj ft fr fe fw fq ff fd fs fa dp do di du dl dk dj dt dr de dw dq df dd ds da sp so si su sl sk sj st sr se sw sq sf sd ss sa ap ao ai au al ak aj at ar ae aw aq af ad as aa) -match_lookup_table='' - -declare -A match_lookup_table - -function get_hint() { - echo "${HINTS[$1]}" -} - -function highlight() { - printf "\033[1;33m%s\033[0m" "$1" +function clear_screen() { + local fingers_pane_id=$1 + clear + tmux clearhist -t $fingers_pane_id } function lookup_match() { local input=$1 - echo ${match_lookup_table[$input]} + echo "$(cat $match_lookup_table | grep "^$input:" | sed "s/^$input://")" } -lines='' -OLDIFS=$IFS -IFS= -while read -r line -do - lines+="$line\n" -done < /dev/stdin -IFS=$OLDIFS - -# POSIX grep does linenumber on every line with both -o and -n flags set -normalize_grep_output=' -BEGIN { - previous_line_no = 0; -} -{ - if ( $0 ~ /^[0-9]+:/ ) { - split($0, split_at_colon, ":") - previous_line_no = split_at_colon[1] - print $0 - } else { - printf "%d:%s\n", previous_line_no, $0 - } -} -' -matches=$(echo -e $lines | (grep -oniE "$PATTERNS" 2> /dev/null) | awk $normalize_grep_output | sort -u) -output="$lines" -i=0 - -OLDIFS=$IFS -IFS=$(echo -en "\n\b") # wtf bash? -for match in $matches ; do - hint=$(get_hint $i) - linenumber=$(echo $match | sed "s!${MATCH_PARSER//!\\!}!\1!") - text=$(echo $match | sed "s!${MATCH_PARSER//!\\!}!\2!") - - output=$(echo -ne "$output" | sed "${linenumber}s!${text//!/\\!}!$(highlight ${text//!/\\!}) $(highlight "[${hint//!/\\!}]")!g") - match_lookup_table[$hint]=$text - i=$((i + 1)) -done -IFS=$OLDIFS - -function print_hints() { - echo -ne "$output" +function show_hints_and_swap() { + current_pane_id=$1 + fingers_pane_id=$2 + tmux swap-pane -s "$current_pane_id" -t "$fingers_pane_id" + clear_screen "$fingers_pane_id" + cat | FINGER_PATTERNS=$PATTERNS __awk__ -f $CURRENT_DIR/hinter.awk 3> $match_lookup_table 4>> $CURRENT_DIR/../fingers.log + cat $match_lookup_table >> $CURRENT_DIR/../fingers.log } diff --git a/scripts/tmux-fingers.sh b/scripts/tmux-fingers.sh index d8be151..fb003cc 100755 --- a/scripts/tmux-fingers.sh +++ b/scripts/tmux-fingers.sh @@ -33,8 +33,7 @@ function capture_pane() { function prompt_fingers_for_pane() { local current_pane_id=$1 local fingers_pane_id=$(init_fingers_pane) - local tmp_path=$(mktemp "${TMPDIR:-/tmp}/tmux-fingers.XXXXXXXX") - chmod 600 "$tmp_path" + local tmp_path=$(fingers_tmp) wait diff --git a/scripts/utils.sh b/scripts/utils.sh index c522aa3..961eee3 100755 --- a/scripts/utils.sh +++ b/scripts/utils.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +#TODO split all this crap in lib/ folder function array_join() { local IFS="$1"; shift; echo "$*"; @@ -80,3 +80,17 @@ function pane_exec() { tmux send-keys -t $pane_id " $pane_command" tmux send-keys -t $pane_id Enter } + +function fingers_tmp() { + local tmp_path=$(mktemp "${TMPDIR:-/tmp}/tmux-fingers.XXXXXXXX") + chmod 600 "$tmp_path" + echo "$tmp_path" +} + +function __awk__() { + if hash gawk 2>/dev/null; then + gawk "$@" + else + awk "$@" + fi +} diff --git a/test/fixtures/ip-output b/test/fixtures/ip-output index 8e014ff..6c2bb8c 100644 --- a/test/fixtures/ip-output +++ b/test/fixtures/ip-output @@ -1,18 +1,26 @@ 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 - inet 127.0.0.1 scope host lo + inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever - inet6 ::1 scope host + inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: wlp3s0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 78:31:c1:d5:40:ce brd ff:ff:ff:ff:ff:ff - inet 192.168.1.39 brd 192.168.1.255 scope global dynamic wlp3s0 - valid_lft 33919sec preferred_lft 33919sec - inet6 fe80::7a31:c1ff:fed5:40ce scope link tentative dadfailed + inet 192.168.1.33/24 brd 192.168.1.255 scope global dynamic wlp3s0 + valid_lft 40162sec preferred_lft 40162sec + inet6 fe80::7a31:c1ff:fed5:40ce/64 scope link tentative dadfailed valid_lft forever preferred_lft forever -3: vboxnet0: mtu 1500 qdisc fq_codel state DOWN group default qlen 1000 +3: br0: mtu 1500 qdisc noqueue state DOWN group default qlen 1000 + link/ether 46:06:f6:15:ea:fb brd ff:ff:ff:ff:ff:ff + inet 10.0.3.1/24 brd 10.0.3.255 scope global br0 + valid_lft forever preferred_lft forever +4: vboxnet0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 0a:00:27:00:00:00 brd ff:ff:ff:ff:ff:ff - inet 10.0.1.1 brd 10.0.1.255 scope global vboxnet0 + inet 10.0.1.1/24 brd 10.0.1.255 scope global vboxnet0 valid_lft forever preferred_lft forever - inet6 fe80::800:27ff:fe00:0 scope link + inet6 fe80::800:27ff:fe00:0/64 scope link valid_lft forever preferred_lft forever +5: vboxnet1: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 0a:00:27:00:00:01 brd ff:ff:ff:ff:ff:ff +6: vboxnet2: mtu 1500 qdisc noop state DOWN group default qlen 1000 + link/ether 0a:00:27:00:00:02 brd ff:ff:ff:ff:ff:ff diff --git a/test/helpers.exp b/test/helpers.exp index 3819b14..b2ade99 100644 --- a/test/helpers.exp +++ b/test/helpers.exp @@ -31,7 +31,7 @@ proc init_pane {} { proc invoke_fingers {} { tmux_send "F"; - sleep 0.5; + sleep 1.0; } proc echo_yanked {} { @@ -39,6 +39,7 @@ proc echo_yanked {} { send "echo yanked text is "; tmux_send "]"; send "\r"; + sleep 0.5; } proc exit_ok {} { diff --git a/test/specs/more-than-one-match-per-line_spec.exp b/test/specs/more-than-one-match-per-line_spec.exp index 30ac892..74cd124 100755 --- a/test/specs/more-than-one-match-per-line_spec.exp +++ b/test/specs/more-than-one-match-per-line_spec.exp @@ -12,11 +12,11 @@ sleep 0.5; init_pane exec "cat ./test/fixtures/ip-output"; invoke_fingers; -send "t"; +send "r"; echo_yanked; expect { - "yanked text is 10.0.1.255" { exit_ok } + "yanked text is 192.168.1.33" { exit_ok } timeout { exit_fail } }