Repository: xonsh/xonsh

Issue:
xontribs hang on piping output
<!--- Provide a general summary of the issue in the Title above -->
<!--- 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: http://xon.sh/bash_to_xsh.html
If you don't find an answer there, please do open an issue! -->
## xonfig
<!--- Please post the output of the `xonfig` command (run from inside xonsh) so we know more about your current setup -->
```
+------------------+----------------------+
| xonsh            | 0.7.2                |
| Git SHA          | d9651fe8             |
| Commit Date      | Jul 30 10:58:07 2018 |
| Python           | 3.6.6                |
| PLY              | 3.11                 |
| have readline    | True                 |
| prompt toolkit   | 1.0.15               |
| shell type       | prompt_toolkit1      |
| pygments         | 2.2.0                |
| on posix         | True                 |
| on linux         | True                 |
| distro           | Ubuntu               |
| 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      |
+------------------+----------------------+
```
## Expected Behavior
<!--- Tell us what should happen -->
`$ xontrib list | grep loaded` should produce an output of loaded xontribs.
## Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
<!--- 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` -->
xonsh hungs
## Steps to Reproduce
<!--- Please try to write out a minimal reproducible snippet to trigger the bug, it will help us fix it! -->
```
$ xonsh
$ xontrib list | grep loaded
```

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:
Thanks for opening this.  I think the issue is that `xontrib` is unthreadable (for loading), and its output is being written before grep starts up.  This is probably a general problem for all unthreadable callable aliases.
@scopatz Hey, and could you please point me to the code where I can make it threadable, just to check that assumption? Thanks
Sorry for the super slow reply @alex-bender - but I believe that it is this line: https://github.com/xonsh/xonsh/blob/master/xonsh/xontribs.py#L167
It's ok! Glad that you found one.
I can reproduce this by running `xontrib list | grep loaded`.
Closed with the reference to https://github.com/xonsh/xonsh/issues/5317#issuecomment-2032017790

Resolving patch:
diff --git a/xonsh/history/main.py b/xonsh/history/main.py
--- a/xonsh/history/main.py
+++ b/xonsh/history/main.py
@@ -507,4 +507,4 @@ def __call__(self, args, *rest, **kwargs):
         return super().__call__(args, *rest, **kwargs)
 
 
-history_main = HistoryAlias()
+history_main = HistoryAlias(threadable=True)
diff --git a/xonsh/xontribs.py b/xonsh/xontribs.py
--- a/xonsh/xontribs.py
+++ b/xonsh/xontribs.py
@@ -330,7 +330,7 @@ def xontribs_loaded():
     return [k for k, xontrib in get_xontribs().items() if xontrib.is_loaded]
 
 
-def xontribs_list(to_json=False):
+def xontribs_list(to_json=False, _stdout=None):
     """List installed xontribs and show whether they are loaded or not
 
     Parameters
@@ -341,7 +341,7 @@ def xontribs_list(to_json=False):
     data = xontrib_data()
     if to_json:
         s = json.dumps(data)
-        print(s)
+        return s
     else:
         nname = max([6] + [len(x) for x in data])
         s = ""
@@ -356,7 +356,7 @@ def xontribs_list(to_json=False):
             else:
                 s += "{RED}not-loaded{RESET}"
             s += "\n"
-        print_color(s[:-1])
+        print_color(s[:-1], file=_stdout)
 
 
 def _get_xontrib_entrypoints() -> "tp.Iterable[EntryPoint]":


Test patch: