Repository: xonsh/xonsh

Issue:
Unexpected exception while updating completions
<!--- Provide a general summary of the issue in the Title above -->
When I set $UPDATE_COMPLETIONS_ON_KEYPRESS = True and type for instance /usr/bin/ls -a in terminal, following exception is thrown: "Exception [Errno 13] Permission denied: '/usr/bin/ls.json'"
<!--- If you have a question along the lines of "How do I do this Bash command in xonsh"
please first look over the Bash to Xonsh translation guide: https://xon.sh/bash_to_xsh.html
If you don't find an answer there, please do open an issue! -->

## xonfig

<details>

```
+------------------+----------------------+
| xonsh            | 0.13.4               |
| Python           | 3.8.10               |
| PLY              | 3.11                 |
| have readline    | True                 |
| prompt toolkit   | 3.0.36               |
| shell type       | prompt_toolkit       |
| history backend  | json                 |
| pygments         | 2.14.0               |
| on posix         | True                 |
| on linux         | True                 |
| distro           | ubuntu               |
| on wsl           | False                |
| on darwin        | False                |
| on windows       | False                |
| on cygwin        | False                |
| on msys2         | False                |
| is superuser     | False                |
| default encoding | utf-8                |
| xonsh encoding   | utf-8                |
| encoding errors  | surrogateescape      |
| xontrib          | []                   |
| RC file 1        | /home/ralis/.xonshrc |
+------------------+----------------------+

```

</details>

## Expected Behavior
<!--- Tell us what should happen -->
The warning should be either more subtle or no completion suggestions should be shown.
## Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
Huge multi-line error is printed.
<!--- If part of your bug report is a traceback, please first enter debug mode before triggering the error
To enter debug mode, set the environment variable `XONSH_DEBUG=1` _before_ starting `xonsh`.
On Linux and OSX, an easy way to to do this is to run `env XONSH_DEBUG=1 xonsh` -->

### Traceback (if applicable)

<details>

```

Unhandled exception in event loop:
  File "/home/ralis/.local/lib/python3.8/site-packages/prompt_toolkit/buffer.py", line 1939, in new_coroutine
    await coroutine(*a, **kw)
  File "/home/ralis/.local/lib/python3.8/site-packages/prompt_toolkit/buffer.py", line 1763, in async_completer
    async for completion in async_generator:
  File "/home/ralis/.local/lib/python3.8/site-packages/prompt_toolkit/completion/base.py", line 326, in get_completions_async
    async for completion in completer.get_completions_async(
  File "/home/ralis/.local/lib/python3.8/site-packages/prompt_toolkit/completion/base.py", line 202, in get_completions_async
    for item in self.get_completions(document, complete_event):
  File "/usr/local/lib/python3.8/dist-packages/xonsh/ptk_shell/completer.py", line 58, in get_completions
    completions, plen = self.completer.complete(
  File "/usr/local/lib/python3.8/dist-packages/xonsh/completer.py", line 121, in complete
    return self.complete_from_context(
  File "/usr/local/lib/python3.8/dist-packages/xonsh/completer.py", line 272, in complete_from_context
    for comp in self.generate_completions(
  File "/usr/local/lib/python3.8/dist-packages/xonsh/completer.py", line 233, in generate_completions
    for comp in res:
  File "/usr/local/lib/python3.8/dist-packages/xonsh/completers/man.py", line 137, in completions
    for desc, opts in _parse_man_page_options(cmd).items():
  File "/usr/local/lib/python3.8/dist-packages/xonsh/completers/man.py", line 121, in _parse_man_page_options
    path.write_text(json.dumps(options))
  File "/usr/lib/python3.8/pathlib.py", line 1255, in write_text
    with self.open(mode='w', encoding=encoding, errors=errors) as f:
  File "/usr/lib/python3.8/pathlib.py", line 1222, in open
    return io.open(self, mode, buffering, encoding, errors, newline,
  File "/usr/lib/python3.8/pathlib.py", line 1078, in _opener
    return self._accessor.open(self, flags, mode)

Exception [Errno 13] Permission denied: '/usr/bin/ls.json'

```

</details>

## Steps to Reproduce
<!--- Please try to write out a minimal reproducible snippet to trigger the bug, it will help us fix it! -->
```xsh
$UPDATE_COMPLETIONS_ON_KEYPRESS = True
/usr/bin/ls -  # exception after typing
```
## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:
You have to configure Xonsh to write its completion cache files to a directory where you have write permissions:
`export XONSH_CACHE_DIR=/path/to/your/cache/directory`
Replace /path/to/your/cache/directory with a directory where you have write access.


I have $XONSH_CACHE_DIR set by default to `~/.cache/xonsh` to which I have write access.
This issue appears to be a misapplication of `pathlib`'s join operator `/`. 

```Python
@functools.lru_cache(maxsize=10)
def _parse_man_page_options(cmd: str) -> "dict[str, tuple[str, ...]]":
    path = get_man_completions_path() / f"{cmd}.json"
```
In the example reported, `cmd` has an absolute path value of `/usr/bin/ls`. Per the `pathlib` [docs](https://docs.python.org/3/library/pathlib.html#operators)

> If the argument is an absolute path, the previous path is ignored.

I'd be happy to make a pull request fixing this if someone can confirm the correct behavior for me. I believe the code should perform the join using the command name instead of the full path to the command.

I.e., replace `/usr/bin/ls` with `ls` and `/path/to/command` with `command` before doing the path join.

Resolving patch:
diff --git a/xonsh/completers/man.py b/xonsh/completers/man.py
--- a/xonsh/completers/man.py
+++ b/xonsh/completers/man.py
@@ -114,7 +114,7 @@ def get_options(text):
 
 @functools.lru_cache(maxsize=10)
 def _parse_man_page_options(cmd: str) -> "dict[str, tuple[str, ...]]":
-    path = get_man_completions_path() / f"{cmd}.json"
+    path = get_man_completions_path() / Path(cmd).with_suffix(".json").name
     if path.exists():
         return json.loads(path.read_text())
     options = dict(generate_options_of(cmd))


Test patch: