[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Emacs' zap-to-char as a bindable shell function
From: |
Tim Landscheidt |
Subject: |
Emacs' zap-to-char as a bindable shell function |
Date: |
Tue, 24 Oct 2023 10:00:17 +0000 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/28.3 (gnu/linux) |
Hi,
I use zap-to-char (M-z) in Emacs quite frequently and thus
miss it dearly in Bash/readline (though probably not as much
as transient-mark-mode :-)).
Recently, I stumbled upon the section for "bind -x" in the
man page for the first time and thought that it should be
possible to approximate Emacs' behaviour that way, and,
voilĂ , I present to you the results of my labour.
One additional take-away from that exercise for me was the
realization that "bind -x" functions have full control over
the input line, i. e. with Bash/readline one can not only
bind a key to a "macro" where one has to hope that the bound
keys "work" at that specific location, but that a bound
shell function can take context into consideration and then
Do The Right Thing™.
If someone wants to clean up the code and use it for docu-
mentation, you are welcome to (CC0).
| # zap_to_char ARG CHAR.
| function zap_to_char {
| # Default to deleting to the first occurence of CHAR.
| local arg="${READLINE_ARGUMENT:-1}"
| # Read character to zap to.
| local ch
| read -d '' -rsn 1 ch
| local line_before_point="${READLINE_LINE:0:${READLINE_POINT}}"
| local line_after_point="${READLINE_LINE:${READLINE_POINT}}"
| while [ "$arg" -gt 0 ]; do
| local new_line_after_point="${line_after_point#*${ch}}"
| # If CHAR cannot be found ARG times in the current
| # line after point, fail.
| if [ "$line_after_point" = "$new_line_after_point" ]; then
| return
| fi
| line_after_point="$new_line_after_point"
| arg=$(("$arg" - 1))
| done
| # Reposition mark.
| # If the mark was not after the point, do nothing.
| if [ "$READLINE_MARK" -gt "$READLINE_POINT" ]; then
| # Otherwise, the mark gets moved left as many
| # characters as the line after the point shrunk by,
| # but no further left than the point.
| READLINE_MARK=$((READLINE_MARK - (${#READLINE_LINE} - READLINE_POINT
- ${#line_after_point})))
| if [ "$READLINE_MARK" -lt "$READLINE_POINT" ]; then
| READLINE_MARK="${READLINE_POINT}"
| fi
| fi
| # Set new line.
| READLINE_LINE="${line_before_point}${line_after_point}"
| }
| bind -x '"\ez":zap_to_char'
| # Test one zap_to_char call.
| # test_zap_to_char-1 INPUT ARG CHAR EXPOUT.
| # The characters "#" and "!" are used to denote the position
| # of the mark and the point, respectively. If both the mark
| # and the point are at the same position, the order is "#!".
| function test_zap_to_char-1 {
| local READLINE_LINE="$(printf %s "$1" | sed -e 's/[#!]//g;')"
| local READLINE_POINT="$(printf %s "$1" | sed -e
's/^\([^#!]*\)\(#\([^#!]*\)\)\?!.*$/\1\3/;' | wc -c)"
| local READLINE_MARK="$(printf %s "$1" | sed -e
's/^\([^#!]*\)\(!\([^#!]*\)\)\?#.*$/\1\3/;' | wc -c)"
| local READLINE_ARGUMENT="$2"
| if [ "$READLINE_ARGUMENT" = 'x' ]; then
| unset READLINE_ARGUMENT
| fi
| zap_to_char < <(echo -nE "$3")
| local
actual_output="${READLINE_LINE:0:${READLINE_POINT}}!${READLINE_LINE:${READLINE_POINT}}"
| if [ $READLINE_MARK -le $READLINE_POINT ]; then
|
actual_output="${actual_output:0:$READLINE_MARK}#${actual_output:$READLINE_MARK}"
| else
| actual_output="${actual_output:0:$READLINE_MARK +
1}#${actual_output:$READLINE_MARK + 1}"
| fi
| if [ "$actual_output" = "$4" ]; then
| echo ok
| else
| echo "not ok: Expected \"$4\", got \"$actual_output\"."
| fi
| }
| # Test suite for zap_to_char.
| function test_zap_to_char {
| # Basic functionality.
| test_zap_to_char-1 'ABC#!DEFGHIJKLMNOPQRSTUVWXYZ' x 'J'
'ABC#!KLMNOPQRSTUVWXYZ'
| test_zap_to_char-1
'ABC#!DEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ' 2 'J'
'ABC#!KLMNOPQRSTUVWXYZ'
| # Character cannot be found after point.
| test_zap_to_char-1 'ABC#!DEFGHIJKLMNOPQRSTUVWXYZ' x 'j'
'ABC#!DEFGHIJKLMNOPQRSTUVWXYZ'
| # Character cannot be found enough times after point.
| test_zap_to_char-1 'ABC#!DEFGHIJKLMNOPQRSTUVWXYZ' 2 'J'
'ABC#!DEFGHIJKLMNOPQRSTUVWXYZ'
| # Preserve mark.
| test_zap_to_char-1 'ABC#DE!FGHIJKLMNOPQRSTUVWXYZ' x 'J'
'ABC#DE!KLMNOPQRSTUVWXYZ'
| test_zap_to_char-1 'ABCDE!FG#HIJKLMNOPQRSTUVWXYZ' x 'J'
'ABCDE#!KLMNOPQRSTUVWXYZ'
| test_zap_to_char-1 'ABCDE!FGHIJKLM#NOPQRSTUVWXYZ' x 'J'
'ABCDE!KLM#NOPQRSTUVWXYZ'
| }
| test_zap_to_char
Tim
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- Emacs' zap-to-char as a bindable shell function,
Tim Landscheidt <=