Auto-escape pastes inside single-quotes

This is to make pasting literals easier.

When a user pastes something, we normally take it as-is.

The exception is when a single-quote is open, e.g. the current token
is

    foo'bar

When something is pasted here, we escape single-quotes (`'`) and
backslashes (`\\`), so typing a `'` after it will turn it into a
literal token.

Fixes #967.
This commit is contained in:
Fabian Homborg 2017-03-09 16:52:38 +01:00
parent 84cf391faa
commit 99e87dded3
3 changed files with 109 additions and 7 deletions

View file

@ -0,0 +1,60 @@
function __fish_commandline_is_singlequoted --description "Return 0 if the current token has an open single-quote"
# Go through the token char-by-char in a state machine.
# The states are:
# - normal - no quoting is active (the starting state)
# - single - open single-quote
# - double - open double
# - escaped - open \\ - the next character is non-special
# - single-escaped - open \\ inside single-quotes
# - double-escaped - open \\ inside double-quotes
set -l state normal
for char in (commandline -ct | string split "")
switch $char
case "'" # single-quote
switch $state
case normal single-escaped
set state single
case single
set state normal
end
case '"' # double-quote
switch $state
case normal double-escaped
set state double
case double
set state normal
end
case \\ # backslash escapes the next character
switch $state
case double
set state double-escaped
case double-escaped
set state double
case single
set state single-escaped
case single-escaped
set state single
case normal
set state escaped
case escaped
set state normal
end
case "*" # Any other character
switch $state
case escaped
set state normal
case single-escaped
set state single
case double-escaped
set state double
end
end
end
# TODO: Should "single-escaped" also be a success?
if contains -- $state single single-escaped
return 0
else
return 1
end
end

View file

@ -130,16 +130,47 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod
# Re-running `bind` multiple times per mode is still faster than trying to make the list unique, # Re-running `bind` multiple times per mode is still faster than trying to make the list unique,
# even without calling `sort -u` or `uniq`, for the vi-bindings. # even without calling `sort -u` or `uniq`, for the vi-bindings.
# TODO: This can be solved better once #3872 is implemented. # TODO: This can be solved better once #3872 is implemented.
# We usually just pass the text through as-is to facilitate pasting code,
# but when the current token contains an unbalanced single-quote (`'`),
# we escape all single-quotes and backslashes, effectively turning the paste
# into one literal token, to facilitate pasting non-code (e.g. markdown or git commitishes)
set -l allmodes default set -l allmodes default
set allmodes $allmodes (bind -a | string match -r -- '-M \w+' | string replace -- '-M ' '') set allmodes $allmodes (bind -a | string match -r -- '-M \w+' | string replace -- '-M ' '')
for mode in $allmodes for mode in $allmodes
bind -M $mode -m paste \e\[200~ 'set -g __fish_last_bind_mode $fish_bind_mode' bind -M $mode -m paste \e\[200~ '__fish_start_bracketed_paste'
end end
# This sequence ends paste-mode and returns to the previous mode we have saved before. # This sequence ends paste-mode and returns to the previous mode we have saved before.
bind -M paste \e\[201~ 'set fish_bind_mode $__fish_last_bind_mode; commandline -f force-repaint' bind -M paste \e\[201~ '__fish_stop_bracketed_paste'
# In paste-mode, everything self-inserts except for the sequence to get out of it # In paste-mode, everything self-inserts except for the sequence to get out of it
bind -M paste "" self-insert bind -M paste "" self-insert
# Without this, a \r will overwrite the other text, rendering it invisible - which makes the exercise kinda pointless. # Without this, a \r will overwrite the other text, rendering it invisible - which makes the exercise kinda pointless.
# TODO: Test this in windows (\r\n line endings) # TODO: Test this in windows (\r\n line endings)
bind -M paste \r "commandline -i \n" bind -M paste \r "commandline -i \n"
bind -M paste "'" "__fish_commandline_insert_escaped ' \$__fish_paste_quoted"
bind -M paste \\ "__fish_commandline_insert_escaped \\ \$__fish_paste_quoted"
end
function __fish_commandline_insert_escaped --description 'Insert the first arg escaped if a second arg is given'
if set -q argv[2]
commandline -i \\$argv[1]
else
commandline -i $argv[1]
end
end
function __fish_start_bracketed_paste
# Save the last bind mode so we can restore it.
set -g __fish_last_bind_mode $fish_bind_mode
# If the token is currently single-quoted,
# we escape single-quotes (and backslashes).
__fish_commandline_is_singlequoted
and set -g __fish_paste_quoted 1
end
function __fish_stop_bracketed_paste
# Restore the last bind mode.
set fish_bind_mode $__fish_last_bind_mode
set -e __fish_paste_quoted
commandline -f force-repaint
end end

View file

@ -1,14 +1,25 @@
function fish_clipboard_paste function fish_clipboard_paste
set -l data
if type -q pbpaste if type -q pbpaste
commandline -i -- (pbpaste) set data (pbpaste)
else if type -q xsel else if type -q xsel
# Only run `commandline` if `xsel` succeeded. # Return if `xsel` failed.
# That way any xsel error is printed (to show e.g. a non-functioning X connection), # That way any xsel error is printed (to show e.g. a non-functioning X connection),
# but we don't print the redundant (and overly verbose for this) commandline error. # but we don't print the redundant (and overly verbose for this) commandline error.
# Also require non-empty contents to not clear the buffer. # Also require non-empty contents to not clear the buffer.
if set -l data (xsel --clipboard) if not set data (xsel --clipboard)
and test -n "$data" return 1
commandline -i -- $data
end end
end end
# If the current token has an unmatched single-quote,
# escape all single-quotes (and backslashes) in the paste,
# in order to turn it into a single literal token.
#
# This eases pasting non-code (e.g. markdown or git commitishes).
if __fish_commandline_is_singlequoted
set data (string replace -ra "(['\\\])" '\\\\\\\$1' -- $data)
end
if test -n "$data"
commandline -i -- "$data"
end
end end