escape parentheses and other special characters in tab completion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Florian WaltherandClaude Opus 5.5 committed 2026-10-06 11:16:17 +02:00
1 parent 82f9c90a39
commit eb8653c9f5
2 files changed
+42 -3

No files matched your search

+5 -3
View File
@@ -963,11 +963,13 @@ func currentWord(s string) (word string, start int, quote byte) {
return b.String(), start, quote return b.String(), start, quote
} }
// escapeWord escapes characters that would otherwise be interpreted by shlex // escapeWord escapes characters that would otherwise be interpreted by the
// shell. # and ~ are only special at the start of a word, so they are only
// escaped at the start of s.
func escapeWord(s string) string { func escapeWord(s string) string {
var b strings.Builder var b strings.Builder
for _, r := range s { for i, r := range s {
if strings.ContainsRune(" \t\\'\";|&<>*?[${", r) { if strings.ContainsRune(" \t\\'\"`;|&<>()*?[]{}$!", r) || (i == 0 && (r == '#' || r == '~')) {
b.WriteByte('\\') b.WriteByte('\\')
} }
b.WriteRune(r) b.WriteRune(r)
+37
View File
@@ -879,6 +879,43 @@ func TestShellCompleter_Do(t *testing.T) {
} }
} }
func TestEscapeWord(t *testing.T) {
tests := []struct{ in, want string }{
{"plain.txt", "plain.txt"},
{"my file (1).txt", `my\ file\ \(1\).txt`},
{"a`b`$c", "a\\`b\\`\\$c"},
{"a;b|c&d<e>f", `a\;b\|c\&d\<e\>f`},
{"*?[x]{y}!", `\*\?\[x\]\{y\}\!`},
{`it's "q"\`, `it\'s\ \"q\"\\`},
{"#a#b", `\#a#b`},
{"~a~b", `\~a~b`},
}
for _, tt := range tests {
if got := escapeWord(tt.in); got != tt.want {
t.Errorf("escapeWord(%q) = %q; want %q", tt.in, got, tt.want)
}
}
}
func TestShellCompleter_SpecialCharacters(t *testing.T) {
origDir, _ := os.Getwd()
defer os.Chdir(origDir)
os.Chdir(t.TempDir())
os.Create("song (live) & more.mp3")
got, length := completeLine("ls so")
want := []string{`ng\ \(live\)\ \&\ more.mp3`}
if !reflect.DeepEqual(got, want) || length != 2 {
t.Errorf("Do(%q) = %q, %d; want %q, 2", "ls so", got, length, want)
}
// Inside quotes nothing is escaped
got, _ = completeLine(`ls "so`)
want = []string{"ng (live) & more.mp3"}
if !reflect.DeepEqual(got, want) {
t.Errorf("Do(%q) = %q; want %q", `ls "so`, got, want)
}
}
// ============================================================================= // =============================================================================
// Security fixes: history file, quoting/expansion, escape sequences, SIGINT // Security fixes: history file, quoting/expansion, escape sequences, SIGINT
// ============================================================================= // =============================================================================