mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
Fixing bash completion bug when prefix started with '>', '<', or ':' (#4826)
* Fixing bash completion bug when prefix started with '>', '<', or ':' * Fixing code formatting * More code formatting fixes * Fixing test * Refactor to fix bug by improving completion parser * Supporting '<' and '>>' in completion parser * Support for all IO redirect tokens
This commit is contained in:
parent
aa03bc9013
commit
f2ca59a291
5 changed files with 109 additions and 2 deletions
24
news/fix-bash-completion.rst
Normal file
24
news/fix-bash-completion.rst
Normal file
|
@ -0,0 +1,24 @@
|
|||
**Added:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Changed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Removed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Fixed:**
|
||||
|
||||
* Fixed a bash completion bug when prefixing a file path with '<' or '>' (for redirecting stdin/stdout/stderr)
|
||||
* Fixed a bash completion bug when completing a git branch name when deleting a remote branch (e.g. `git push origin :dev-branch`)
|
||||
|
||||
**Security:**
|
||||
|
||||
* <news item>
|
|
@ -232,3 +232,38 @@ def test_equal_sign_arg(command_context, completions, lprefix, exp_append_space)
|
|||
isinstance(comp, RichCompletion) and comp.append_space == exp_append_space
|
||||
for comp in bash_completions
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bash_completer(fake_process):
|
||||
fake_process.register_subprocess(
|
||||
command=["bash", fake_process.any()],
|
||||
# completion for "git push origin :dev-b"
|
||||
stdout=b"""\
|
||||
complete -o bashdefault -o default -o nospace -F __git_wrap__git_main git
|
||||
dev-branch
|
||||
""",
|
||||
)
|
||||
|
||||
return fake_process
|
||||
|
||||
|
||||
# git push origin :dev-b<TAB> -> git push origin :dev-branch
|
||||
def test_git_delete_remote_branch(bash_completer):
|
||||
command_context = CommandContext(
|
||||
args=(
|
||||
CommandArg("git"),
|
||||
CommandArg("push"),
|
||||
CommandArg("origin"),
|
||||
),
|
||||
arg_index=3,
|
||||
prefix=":dev-b",
|
||||
)
|
||||
bash_completions, bash_lprefix = complete_from_bash(
|
||||
CompletionContext(command_context)
|
||||
)
|
||||
assert bash_completions == {"dev-branch"} and bash_lprefix == 5
|
||||
assert all(
|
||||
isinstance(comp, RichCompletion) and comp.append_space is False
|
||||
for comp in bash_completions
|
||||
)
|
||||
|
|
|
@ -127,6 +127,38 @@ COMMAND_EXAMPLES = (
|
|||
suffix="b",
|
||||
),
|
||||
),
|
||||
(
|
||||
f"command >/dev/nul{X}",
|
||||
CommandContext(
|
||||
args=(CommandArg("command"), CommandArg(">", is_io_redir=True)),
|
||||
arg_index=2,
|
||||
prefix="/dev/nul",
|
||||
),
|
||||
),
|
||||
(
|
||||
f"command 2>/dev/nul{X}",
|
||||
CommandContext(
|
||||
args=(CommandArg("command"), CommandArg("2>", is_io_redir=True)),
|
||||
arg_index=2,
|
||||
prefix="/dev/nul",
|
||||
),
|
||||
),
|
||||
(
|
||||
f"command >>/dev/nul{X}",
|
||||
CommandContext(
|
||||
args=(CommandArg("command"), CommandArg(">>", is_io_redir=True)),
|
||||
arg_index=2,
|
||||
prefix="/dev/nul",
|
||||
),
|
||||
),
|
||||
(
|
||||
f"command </dev/nul{X}",
|
||||
CommandContext(
|
||||
args=(CommandArg("command"), CommandArg("<", is_io_redir=True)),
|
||||
arg_index=2,
|
||||
prefix="/dev/nul",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
EMPTY_COMMAND_EXAMPLES = (
|
||||
|
|
|
@ -432,6 +432,10 @@ def bash_completions(
|
|||
# to be incorrectly calculated, so it needs to be fixed here
|
||||
if "=" in prefix and "=" not in commprefix:
|
||||
strip_len = prefix.index("=") + 1
|
||||
# Fix case where remote git branch is being deleted
|
||||
# (e.g. 'git push origin :dev-branch')
|
||||
elif ":" in prefix and ":" not in commprefix:
|
||||
strip_len = prefix.index(":") + 1
|
||||
|
||||
return out, max(len(prefix) - strip_len, 0)
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ class CommandArg(NamedTuple):
|
|||
"""The arg's opening quote (if it exists)"""
|
||||
closing_quote: str = ""
|
||||
"""The arg's closing quote (if it exists)"""
|
||||
is_io_redir: bool = False
|
||||
"""Whether the arg is IO redirection"""
|
||||
|
||||
@property
|
||||
def raw_value(self):
|
||||
|
@ -328,6 +330,13 @@ class CompletionContextParser:
|
|||
"OR",
|
||||
}
|
||||
used_tokens |= multi_tokens
|
||||
io_redir_tokens = {
|
||||
"LT",
|
||||
"GT",
|
||||
"RSHIFT",
|
||||
"IOREDIRECT",
|
||||
}
|
||||
used_tokens |= io_redir_tokens
|
||||
artificial_tokens = {"ANY"}
|
||||
ignored_tokens = {"INDENT", "DEDENT", "WS"}
|
||||
|
||||
|
@ -701,7 +710,8 @@ class CompletionContextParser:
|
|||
|
||||
arg = CompletionContextParser.try_parse_string_literal(raw_arg)
|
||||
if arg is None:
|
||||
arg = CommandArg(raw_arg)
|
||||
is_io_redir = p.slice[1].type in self.io_redir_tokens
|
||||
arg = CommandArg(raw_arg, is_io_redir=is_io_redir)
|
||||
|
||||
p[0] = Spanned(arg, span, cursor_context=relative_cursor)
|
||||
|
||||
|
@ -727,7 +737,9 @@ class CompletionContextParser:
|
|||
joined_raw = f"{last_arg.value.raw_value}{in_between}{new_arg.value.raw_value}"
|
||||
string_literal = self.try_parse_string_literal(joined_raw)
|
||||
|
||||
if string_literal is not None or not in_between:
|
||||
is_redir = new_arg.value.is_io_redir or last_arg.value.is_io_redir
|
||||
|
||||
if string_literal is not None or (not in_between and not is_redir):
|
||||
if string_literal is not None:
|
||||
# we're appending to a partial string, e.g. `"a b`
|
||||
arg = string_literal
|
||||
|
|
Loading…
Add table
Reference in a new issue