Merge a98dd4abcf into 85919cd1ff
This commit is contained in:
commit
212ae1ba6b
|
|
@ -1,5 +1,8 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
- Fix `region_highlight` entries leaking across accept/edit cycles, causing stale colors to bleed onto accepted text (#698, #789). Plugin-owned entries now carry a `memo=zsh-autosuggestions` tag on zsh 5.9+ and are tracked individually for older versions.
|
||||
|
||||
## v0.7.1
|
||||
- Clear POSTDISPLAY instead of unsetting (#634)
|
||||
- Always reset async file descriptor after consuming it (#630)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,121 @@
|
|||
require 'tempfile'
|
||||
|
||||
describe 'suggestion highlighting' do
|
||||
# We communicate ZLE state back to the test process via a file, because
|
||||
# `region_highlight` only exists inside a ZLE widget. Bind a dump widget
|
||||
# so we can inspect the array at any point in the interactive flow.
|
||||
let(:dump_file) { Tempfile.create(['zsh-autosuggest-highlight', '.log']).path }
|
||||
|
||||
let(:options) do
|
||||
[
|
||||
%(ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=red'),
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
session.
|
||||
run_command(%(_dump_region_highlight() { print -l -- "${region_highlight[@]}" > #{dump_file} })).
|
||||
run_command('zle -N dump-region-highlight _dump_region_highlight').
|
||||
run_command('bindkey "^X^D" dump-region-highlight').
|
||||
clear_screen
|
||||
end
|
||||
|
||||
after do
|
||||
File.delete(dump_file) if File.exist?(dump_file)
|
||||
end
|
||||
|
||||
def region_highlight_entries
|
||||
# Dump + give zle a moment to write the file.
|
||||
session.send_keys('C-x C-d')
|
||||
deadline = Time.now + 2
|
||||
until Time.now > deadline
|
||||
content = File.read(dump_file) rescue ''
|
||||
return content.lines.map(&:chomp).reject(&:empty?) if File.exist?(dump_file)
|
||||
sleep 0.05
|
||||
end
|
||||
[]
|
||||
end
|
||||
|
||||
def plugin_owned_entries(entries)
|
||||
entries.select { |e| e.include?('fg=red') || e.include?('memo=zsh-autosuggestions') }
|
||||
end
|
||||
|
||||
context 'when the suggestion is accepted and edited' do
|
||||
it 'does not leak stale region_highlight entries' do
|
||||
with_history('echo hello world') do
|
||||
session.send_string('echo h')
|
||||
wait_for { session.content }.to eq('echo hello world')
|
||||
|
||||
# Accept full suggestion via forward-char at end of buffer
|
||||
session.send_keys('End')
|
||||
wait_for { session.content }.to eq('echo hello world')
|
||||
|
||||
# Delete a few chars from the accepted text
|
||||
3.times { session.send_keys('BSpace') }
|
||||
wait_for { session.content }.to eq('echo hello w')
|
||||
|
||||
owned = plugin_owned_entries(region_highlight_entries)
|
||||
expect(owned.size).to be <= 1,
|
||||
"expected at most one plugin-owned region_highlight entry, got #{owned.size}: #{owned.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a hex color in ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE' do
|
||||
let(:options) do
|
||||
[
|
||||
%(ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=#5a548b'),
|
||||
]
|
||||
end
|
||||
|
||||
def plugin_owned_entries(entries)
|
||||
# Hex colors contain `#`, so match by memo tag (5.9+) or the raw style.
|
||||
entries.select { |e| e.include?('fg=#5a548b') || e.include?('memo=zsh-autosuggestions') }
|
||||
end
|
||||
|
||||
it 'still removes owned entries on reset (regression: #789)' do
|
||||
with_history('echo hello world') do
|
||||
session.send_string('echo h')
|
||||
wait_for { session.content }.to eq('echo hello world')
|
||||
|
||||
session.send_keys('End')
|
||||
3.times { session.send_keys('BSpace') }
|
||||
wait_for { session.content }.to eq('echo hello w')
|
||||
|
||||
owned = plugin_owned_entries(region_highlight_entries)
|
||||
expect(owned.size).to be <= 1,
|
||||
"expected at most one plugin-owned region_highlight entry, got #{owned.size}: #{owned.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when another plugin appends to region_highlight after ours' do
|
||||
let(:after_sourcing) do
|
||||
-> do
|
||||
# Simulate zsh-syntax-highlighting by wrapping line-pre-redraw to
|
||||
# append an entry AFTER autosuggestions has already applied its own.
|
||||
session.
|
||||
run_command('_fake_other_plugin() { region_highlight+=("0 1 fg=green memo=fake-syntax-highlighter") }').
|
||||
run_command('zle -N _fake_other_plugin').
|
||||
run_command('autoload -Uz add-zle-hook-widget').
|
||||
run_command('add-zle-hook-widget line-pre-redraw _fake_other_plugin')
|
||||
end
|
||||
end
|
||||
|
||||
it 'still removes only its own entries on reset' do
|
||||
with_history('echo hello world') do
|
||||
session.send_string('echo h')
|
||||
wait_for { session.content }.to eq('echo hello world')
|
||||
|
||||
session.send_keys('End')
|
||||
3.times { session.send_keys('BSpace') }
|
||||
wait_for { session.content }.to eq('echo hello w')
|
||||
|
||||
entries = region_highlight_entries
|
||||
owned = plugin_owned_entries(entries)
|
||||
expect(owned.size).to be <= 1,
|
||||
"expected at most one plugin-owned entry, got: #{owned.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3,14 +3,77 @@
|
|||
# Highlighting #
|
||||
#--------------------------------------------------------------------#
|
||||
|
||||
# `is-at-least` is autoloaded in src/start.zsh, but we may be called
|
||||
# before start.zsh runs (e.g. another plugin triggering us). Safe to
|
||||
# autoload here too — it's idempotent.
|
||||
autoload -Uz is-at-least
|
||||
|
||||
# Array of every region_highlight entry this plugin has added. Using an
|
||||
# array (instead of a single scalar) ensures every entry we own can be
|
||||
# removed on reset even when:
|
||||
# * another plugin appends to region_highlight between our apply and
|
||||
# reset calls (e.g. zsh-syntax-highlighting),
|
||||
# * multiple apply calls occur without an intervening successful reset
|
||||
# (which can happen under rapid edits / widget wrapping edge cases).
|
||||
typeset -ga _ZSH_AUTOSUGGEST_OWNED_HIGHLIGHTS
|
||||
|
||||
# Returns 0 if this zsh supports the `memo=` attribute on region_highlight
|
||||
# entries (zsh 5.9+). The result is cached on first call.
|
||||
_zsh_autosuggest_highlight_memo_support() {
|
||||
typeset -g _ZSH_AUTOSUGGEST_MEMO_SUPPORT
|
||||
if [[ -z "$_ZSH_AUTOSUGGEST_MEMO_SUPPORT" ]]; then
|
||||
if is-at-least 5.9; then
|
||||
_ZSH_AUTOSUGGEST_MEMO_SUPPORT=1
|
||||
else
|
||||
_ZSH_AUTOSUGGEST_MEMO_SUPPORT=0
|
||||
fi
|
||||
fi
|
||||
(( _ZSH_AUTOSUGGEST_MEMO_SUPPORT ))
|
||||
}
|
||||
|
||||
# If there was a highlight, remove it
|
||||
_zsh_autosuggest_highlight_reset() {
|
||||
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||
|
||||
if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then
|
||||
region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}")
|
||||
if (( $#_ZSH_AUTOSUGGEST_OWNED_HIGHLIGHTS == 0 )); then
|
||||
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||
return
|
||||
fi
|
||||
|
||||
if _zsh_autosuggest_highlight_memo_support; then
|
||||
# Single pass: drop every region_highlight entry carrying our
|
||||
# memo tag. Order-independent and robust against interleaving
|
||||
# with other plugins' entries.
|
||||
local entry
|
||||
local -a kept=()
|
||||
for entry in $region_highlight; do
|
||||
[[ "$entry" != *memo=zsh-autosuggestions* ]] && kept+=("$entry")
|
||||
done
|
||||
region_highlight=("${kept[@]}")
|
||||
else
|
||||
# Fallback for zsh < 5.9: remove by literal string comparison.
|
||||
# We intentionally do NOT use `${(@)array:#$needle}` because
|
||||
# that treats $needle as a glob pattern — `#` in hex colors
|
||||
# (e.g. `fg=#RRGGBB`) would be interpreted as pattern-matching
|
||||
# syntax and the entry would never be removed.
|
||||
# See https://github.com/zsh-users/zsh-autosuggestions/issues/789
|
||||
local owned entry
|
||||
for owned in $_ZSH_AUTOSUGGEST_OWNED_HIGHLIGHTS; do
|
||||
local -a kept=()
|
||||
local removed=0
|
||||
for entry in $region_highlight; do
|
||||
if (( ! removed )) && [[ "$entry" == "$owned" ]]; then
|
||||
removed=1
|
||||
else
|
||||
kept+=("$entry")
|
||||
fi
|
||||
done
|
||||
region_highlight=("${kept[@]}")
|
||||
done
|
||||
fi
|
||||
|
||||
_ZSH_AUTOSUGGEST_OWNED_HIGHLIGHTS=()
|
||||
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||
}
|
||||
|
||||
# If there's a suggestion, highlight it
|
||||
|
|
@ -18,8 +81,16 @@ _zsh_autosuggest_highlight_apply() {
|
|||
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||
|
||||
if (( $#POSTDISPLAY )); then
|
||||
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
|
||||
region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT")
|
||||
local entry="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
|
||||
if _zsh_autosuggest_highlight_memo_support; then
|
||||
entry+=" memo=zsh-autosuggestions"
|
||||
fi
|
||||
region_highlight+=("$entry")
|
||||
_ZSH_AUTOSUGGEST_OWNED_HIGHLIGHTS+=("$entry")
|
||||
|
||||
# Retained for backwards compatibility with anything reading
|
||||
# this variable externally.
|
||||
_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$entry"
|
||||
else
|
||||
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -241,14 +241,77 @@ _zsh_autosuggest_invoke_original_widget() {
|
|||
# Highlighting #
|
||||
#--------------------------------------------------------------------#
|
||||
|
||||
# `is-at-least` is autoloaded in src/start.zsh, but we may be called
|
||||
# before start.zsh runs (e.g. another plugin triggering us). Safe to
|
||||
# autoload here too — it's idempotent.
|
||||
autoload -Uz is-at-least
|
||||
|
||||
# Array of every region_highlight entry this plugin has added. Using an
|
||||
# array (instead of a single scalar) ensures every entry we own can be
|
||||
# removed on reset even when:
|
||||
# * another plugin appends to region_highlight between our apply and
|
||||
# reset calls (e.g. zsh-syntax-highlighting),
|
||||
# * multiple apply calls occur without an intervening successful reset
|
||||
# (which can happen under rapid edits / widget wrapping edge cases).
|
||||
typeset -ga _ZSH_AUTOSUGGEST_OWNED_HIGHLIGHTS
|
||||
|
||||
# Returns 0 if this zsh supports the `memo=` attribute on region_highlight
|
||||
# entries (zsh 5.9+). The result is cached on first call.
|
||||
_zsh_autosuggest_highlight_memo_support() {
|
||||
typeset -g _ZSH_AUTOSUGGEST_MEMO_SUPPORT
|
||||
if [[ -z "$_ZSH_AUTOSUGGEST_MEMO_SUPPORT" ]]; then
|
||||
if is-at-least 5.9; then
|
||||
_ZSH_AUTOSUGGEST_MEMO_SUPPORT=1
|
||||
else
|
||||
_ZSH_AUTOSUGGEST_MEMO_SUPPORT=0
|
||||
fi
|
||||
fi
|
||||
(( _ZSH_AUTOSUGGEST_MEMO_SUPPORT ))
|
||||
}
|
||||
|
||||
# If there was a highlight, remove it
|
||||
_zsh_autosuggest_highlight_reset() {
|
||||
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||
|
||||
if [[ -n "$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT" ]]; then
|
||||
region_highlight=("${(@)region_highlight:#$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT}")
|
||||
if (( $#_ZSH_AUTOSUGGEST_OWNED_HIGHLIGHTS == 0 )); then
|
||||
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||
return
|
||||
fi
|
||||
|
||||
if _zsh_autosuggest_highlight_memo_support; then
|
||||
# Single pass: drop every region_highlight entry carrying our
|
||||
# memo tag. Order-independent and robust against interleaving
|
||||
# with other plugins' entries.
|
||||
local entry
|
||||
local -a kept=()
|
||||
for entry in $region_highlight; do
|
||||
[[ "$entry" != *memo=zsh-autosuggestions* ]] && kept+=("$entry")
|
||||
done
|
||||
region_highlight=("${kept[@]}")
|
||||
else
|
||||
# Fallback for zsh < 5.9: remove by literal string comparison.
|
||||
# We intentionally do NOT use `${(@)array:#$needle}` because
|
||||
# that treats $needle as a glob pattern — `#` in hex colors
|
||||
# (e.g. `fg=#RRGGBB`) would be interpreted as pattern-matching
|
||||
# syntax and the entry would never be removed.
|
||||
# See https://github.com/zsh-users/zsh-autosuggestions/issues/789
|
||||
local owned entry
|
||||
for owned in $_ZSH_AUTOSUGGEST_OWNED_HIGHLIGHTS; do
|
||||
local -a kept=()
|
||||
local removed=0
|
||||
for entry in $region_highlight; do
|
||||
if (( ! removed )) && [[ "$entry" == "$owned" ]]; then
|
||||
removed=1
|
||||
else
|
||||
kept+=("$entry")
|
||||
fi
|
||||
done
|
||||
region_highlight=("${kept[@]}")
|
||||
done
|
||||
fi
|
||||
|
||||
_ZSH_AUTOSUGGEST_OWNED_HIGHLIGHTS=()
|
||||
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||
}
|
||||
|
||||
# If there's a suggestion, highlight it
|
||||
|
|
@ -256,8 +319,16 @@ _zsh_autosuggest_highlight_apply() {
|
|||
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||
|
||||
if (( $#POSTDISPLAY )); then
|
||||
typeset -g _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
|
||||
region_highlight+=("$_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT")
|
||||
local entry="$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE"
|
||||
if _zsh_autosuggest_highlight_memo_support; then
|
||||
entry+=" memo=zsh-autosuggestions"
|
||||
fi
|
||||
region_highlight+=("$entry")
|
||||
_ZSH_AUTOSUGGEST_OWNED_HIGHLIGHTS+=("$entry")
|
||||
|
||||
# Retained for backwards compatibility with anything reading
|
||||
# this variable externally.
|
||||
_ZSH_AUTOSUGGEST_LAST_HIGHLIGHT="$entry"
|
||||
else
|
||||
unset _ZSH_AUTOSUGGEST_LAST_HIGHLIGHT
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in New Issue