import functools
import json
import re
import shutil
import subprocess
import textwrap
from pathlib import Path

from xonsh.built_ins import XSH
from xonsh.completers.tools import RichCompletion, contextual_command_completer
from xonsh.parsers.completion_context import CommandContext


@functools.cache
def get_man_completions_path() -> Path:
    env = XSH.env or {}
    datadir = Path(env["XONSH_DATA_DIR"]) / "generated_completions" / "man"
    if datadir.exists() and (not datadir.is_dir()):
        shutil.move(datadir, datadir.with_suffix(".bkp"))
    if not datadir.exists():
        datadir.mkdir(exist_ok=True, parents=True)
    return datadir


def _get_man_page(cmd: str):
    """without control characters"""
    env = XSH.env.detype()
    manpage = subprocess.Popen(
        ["man", cmd], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=env
    )
    # This is a trick to get rid of reverse line feeds
    return subprocess.check_output(["col", "-b"], stdin=manpage.stdout, env=env)


@functools.cache
def _man_option_string_regex():
    return re.compile(
        r"(?:(,\s?)|^|(\sor\s))(?P<option>-[\w]|--[\w-]+)(?=\[?(\s|,|=\w+|$))"
    )


def generate_options_of(cmd: str):
    out = _get_man_page(cmd)
    if not out:
        return

    def get_headers(text: str):
        """split as header-body based on indent"""
        if not text:
            return
        header = ""
        body = []
        for line in textwrap.dedent(text.replace("\n\t", "\n    ")).splitlines():
            if not line.strip():
                continue
            if line.startswith((" ", "\t")):
                body.append(line)
            else:
                if header or body:
                    yield header, body

                # found new section
                header = line.strip()
                body = []
        if header or body:
            yield header, body

    def split_options_string(text: str):
        text = text.strip()
        regex = _man_option_string_regex()

        regex.findall(text)
        options = []
        for match in regex.finditer(text):
            option = match.groupdict().pop("option", None)
            if option:
                options.append(option)
            text = text[match.end() :]
        return options, text.strip()

    def get_option_section():
        option_sect = dict(get_headers(out.decode()))
        small_names = {k.lower(): k for k in option_sect}
        for head in (
            "options",
            "command options",
            "description",
        ):  # prefer sections in this order
            if head in small_names:
                title = small_names[head]
                return "\n".join(option_sect[title])

    def get_options(text):
        """finally get the options"""
        # return old section if
        for opt, lines in get_headers(text):
            # todo: some have [+-] or such vague notations
            if opt.startswith("-"):
                # sometime a single line will have both desc and options
                option_strings, rest = split_options_string(opt)
                descs = []
                if rest:
                    descs.append(rest)
                if lines:
                    descs.append(textwrap.dedent("\n".join(lines)))
                if option_strings:
                    yield ". ".join(descs), tuple(option_strings)
            elif lines:
                # sometimes the options are nested inside subheaders
                yield from get_options("\n".join(lines))

    yield from get_options(get_option_section())


@functools.lru_cache(maxsize=10)
def _parse_man_page_options(cmd: str) -> "dict[str, tuple[str, ...]]":
    path = get_man_completions_path() / f"{cmd}.json"
    if path.exists():
        return json.loads(path.read_text())
    options = dict(generate_options_of(cmd))
    path.write_text(json.dumps(options))
    return options


@contextual_command_completer
def complete_from_man(context: CommandContext):
    """
    Completes an option name, based on the contents of the associated man
    page.
    """

    if context.arg_index == 0 or not context.prefix.startswith("-"):
        return
    cmd = context.args[0].value

    def completions():
        for desc, opts in _parse_man_page_options(cmd).items():
            yield RichCompletion(
                value=opts[-1], display=", ".join(opts), description=desc
            )

    return completions(), False

xonsh/completers/man.py
import functools
import json
import re
import shutil
import subprocess
import textwrap
from pathlib import Path

from xonsh.built_ins import XSH
from xonsh.completers.tools import RichCompletion, contextual_command_completer
from xonsh.parsers.completion_context import CommandContext


@functools.cache
def get_man_completions_path() -> Path:
    env = XSH.env or {}
    datadir = Path(env["XONSH_DATA_DIR"]) / "generated_completions" / "man"
    if datadir.exists() and (not datadir.is_dir()):
        shutil.move(datadir, datadir.with_suffix(".bkp"))
    if not datadir.exists():
        datadir.mkdir(exist_ok=True, parents=True)
    return datadir


def _get_man_page(cmd: str):
    """without control characters"""
    env = XSH.env.detype()
    manpage = subprocess.Popen(
        ["man", cmd], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=env
    )
    # This is a trick to get rid of reverse line feeds
    return subprocess.check_output(["col", "-b"], stdin=manpage.stdout, env=env)


@functools.cache
def _man_option_string_regex():
    return re.compile(
        r"(?:(,\s?)|^|(\sor\s))(?P<option>-[\w]|--[\w-]+)(?=\[?(\s|,|=\w+|$))"
    )


def generate_options_of(cmd: str):
    out = _get_man_page(cmd)
    if not out:
        return

    def get_headers(text: str):
        """split as header-body based on indent"""
        if not text:
            return
        header = ""
        body = []
        for line in textwrap.dedent(text.replace("\n\t", "\n    ")).splitlines():
            if not line.strip():
                continue
            if line.startswith((" ", "\t")):
                body.append(line)
            else:
                if header or body:
                    yield header, body

                # found new section
                header = line.strip()
                body = []
        if header or body:
            yield header, body

    def split_options_string(text: str):
        text = text.strip()
        regex = _man_option_string_regex()

        regex.findall(text)
        options = []
        for match in regex.finditer(text):
            option = match.groupdict().pop("option", None)
            if option:
                options.append(option)
            text = text[match.end() :]
        return options, text.strip()

    def get_option_section():
        option_sect = dict(get_headers(out.decode()))
        small_names = {k.lower(): k for k in option_sect}
        for head in (
            "options",
            "command options",
            "description",
        ):  # prefer sections in this order
            if head in small_names:
                title = small_names[head]
                return "\n".join(option_sect[title])

    def get_options(text):
        """finally get the options"""
        # return old section if
        for opt, lines in get_headers(text):
            # todo: some have [+-] or such vague notations
            if opt.startswith("-"):
                # sometime a single line will have both desc and options
                option_strings, rest = split_options_string(opt)
                descs = []
                if rest:
                    descs.append(rest)
                if lines:
                    descs.append(textwrap.dedent("\n".join(lines)))
                if option_strings:
                    yield ". ".join(descs), tuple(option_strings)
            elif lines:
                # sometimes the options are nested inside subheaders
                yield from get_options("\n".join(lines))

    yield from get_options(get_option_section())


@functools.lru_cache(maxsize=10)
def _parse_man_page_options(cmd: str) -> "dict[str, tuple[str, ...]]":
    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))
    path.write_text(json.dumps(options))
    return options


@contextual_command_completer
def complete_from_man(context: CommandContext):
    """
    Completes an option name, based on the contents of the associated man
    page.
    """

    if context.arg_index == 0 or not context.prefix.startswith("-"):
        return
    cmd = context.args[0].value

    def completions():
        for desc, opts in _parse_man_page_options(cmd).items():
            yield RichCompletion(
                value=opts[-1], display=", ".join(opts), description=desc
            )

    return completions(), False

xonsh/completers/man.py
diff --git a/news/fix-abs-path-completions-on-keypress.rst b/news/fix-abs-path-completions-on-keypress.rst
new file mode 100644
index 0000000000..8e7e072b3d
--- /dev/null
+++ b/news/fix-abs-path-completions-on-keypress.rst
@@ -0,0 +1,23 @@
+**Added:**
+
+* <news item>
+
+**Changed:**
+
+* <news item>
+
+**Deprecated:**
+
+* <news item>
+
+**Removed:**
+
+* <news item>
+
+**Fixed:**
+
+* Fixed an issue with completions when using absolute paths to commands and having $UPDATE_COMPLETIONS_ON_KEYPRESS set to True. https://github.com/xonsh/xonsh/issues/5127
+
+**Security:**
+
+* <news item>
diff --git a/xonsh/completers/man.py b/xonsh/completers/man.py
index a2e56f13d7..a0e57bfd92 100644
--- 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))

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**

