diff --git a/src/complete.cpp b/src/complete.cpp index fccf7016c..266df99fe 100644 --- a/src/complete.cpp +++ b/src/complete.cpp @@ -1362,12 +1362,19 @@ void completer_t::escape_opening_brackets(const wcstring &argument) { bool escaped = false; for (wchar_t c : argument) { have_unquoted_unescaped_bracket |= (c == L'[') && !quote && !escaped; - if (quote) { - if (c == quote && !escaped) quote = L'\0'; - } else { - if ((c == L'\'' || c == L'"') && !escaped) quote = c; + if (escaped) { + escaped = false; + } else if (c == L'\\') { + escaped = true; + } else if (c == L'\'' || c == L'"') { + if (quote == c) { + // Closing a quote. + quote = L'\0'; + } else if (quote == L'\0') { + // Opening a quote. + quote = c; + } } - escaped = c == L'\\'; } if (!have_unquoted_unescaped_bracket) return; // Since completion_apply_to_command_line will escape the completion, we need to provide an diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 7c20ff49c..3b3c045f9 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -2619,6 +2619,8 @@ static void test_complete() { if (system("mkdir -p 'test/complete_test'")) err(L"mkdir failed"); if (system("touch 'test/complete_test/has space'")) err(L"touch failed"); + if (system("touch 'test/complete_test/bracket[abc]'")) err(L"touch failed"); + if (system(R"(touch 'test/complete_test/gnarlybracket\[abc]')")) err(L"touch failed"); if (system("touch 'test/complete_test/testfile'")) err(L"touch failed"); if (system("chmod 700 'test/complete_test/testfile'")) err(L"chmod failed"); @@ -2643,6 +2645,33 @@ static void test_complete() { do_test(completions.size() == 1); do_test(completions.at(0).completion == L"space"); + // Brackets - see #5831 + completions.clear(); + complete(L"echo (ls test/complete_test/bracket[", &completions, {}, vars, parser); + do_test(completions.size() == 1); + fprintf(stderr, "sup: %ls\n", completions.front().completion.c_str()); + do_test(completions.at(0).completion == L"test/complete_test/bracket[abc]"); + + wcstring cmdline = L"touch test/complete_test/bracket["; + completions.clear(); + complete(cmdline, &completions, {}, vars, parser); + do_test(completions.size() == 1); + do_test(completions.front().completion == L"test/complete_test/bracket[abc]"); + size_t where = cmdline.size(); + wcstring newcmdline = completion_apply_to_command_line( + completions.front().completion, completions.front().flags, cmdline, &where, false); + do_test(newcmdline == L"touch test/complete_test/bracket\\[abc\\] "); + + completions.clear(); + cmdline = LR"(touch test/complete_test/gnarlybracket\\[)"; + complete(cmdline, &completions, {}, vars, parser); + do_test(completions.size() == 1); + do_test(completions.front().completion == LR"(test/complete_test/gnarlybracket\[abc])"); + where = cmdline.size(); + newcmdline = completion_apply_to_command_line( + completions.front().completion, completions.front().flags, cmdline, &where, false); + do_test(newcmdline == LR"(touch test/complete_test/gnarlybracket\\\[abc\] )"); + // Add a function and test completing it in various ways. // Note we're depending on function_data_t not complaining when given missing parsed_source / // body_node.