Repository: xonsh/xonsh

Issue:
Feature request: Autosuggesion accept word
I'm a happy new xonsh user and beside being able to run python commands I do like the autosuggest a lot. However, very often I want to accept only part of the suggestion, so it would be very nice if it is possible to be able to accept only the first word/part. In fish that's possible with `Alt+→`.

Settings:
- keybinding: I'd like `→` for finishing the word and `Alt+→` or `End` to finish the whole line.
- word separators: whitespace and '/' would be a good start I'd say.

I had a look at the code to see if I could quickly implement it, but couldn't really figure out what can be done in xonsh, and what needs to be changed in prompt_toolkit.


Discussion:
Hi @pierstitus, thanks for the request.
What you are asking for is already implemented in PTK. It's called "Fill partial suggestion" with the default binding of <kbd>Meta</kbd>+<kbd>F</kbd>. It is defined at
http://github.com/prompt-toolkit/python-prompt-toolkit/blob/5629e7d6e3112be3e7c4b9fbedf0e3ead1dfaa3c/prompt_toolkit/key_binding/bindings/auto_suggest.py#L50-L60
as:
```python
    @handle("escape", "f", filter=suggestion_available & emacs_mode)
    def _fill(event: E) -> None:
        """
        Fill partial suggestion.
        """
        b = event.current_buffer
        suggestion = b.suggestion


        if suggestion:
            t = re.split(r"(\S+\s+)", suggestion.text)
            b.insert_text(next(x for x in t if x))
```
You need to open a feature request or a PR at PTK's issue tracker in order to get customized word delimiters.
Alternatively, you can put the following to your `.xonshrc` file:
```python
@events.on_ptk_create
def alt_right_fill_word_suggestion(bindings, **kw):
    import re
    from prompt_toolkit.application.current import get_app
    from prompt_toolkit.filters import Condition, emacs_mode

    @Condition
    def suggestion_available():
        app = get_app()
        return (
            app.current_buffer.suggestion is not None
            and app.current_buffer.document.is_cursor_at_the_end
        )

    @bindings.add("escape", "right", filter=suggestion_available & emacs_mode)
    def _fill(event):
        b = event.current_buffer
        suggestion = b.suggestion
        if suggestion.text:
            t = re.split(r"(\W+\w+)", suggestion.text)
            b.insert_text(next(x for x in t if x))
```
You can, of course, customize the regex to suit your needs (e.g. `r"([^\s/]+[\s/]+)"`).
Thanks, that works like a charm!

I slightly modified it to use <kbd>→</kbd> for one word and <kbd>Alt</kbd>+<kbd>→</kbd> for the whole line:
``` python
@events.on_ptk_create
def alt_right_fill_word_suggestion(bindings, **kw):
    import re
    from prompt_toolkit.application.current import get_app
    from prompt_toolkit.filters import Condition

    @Condition
    def suggestion_available():
        app = get_app()
        return (
            app.current_buffer.suggestion is not None and app.current_buffer.suggestion.text
            and app.current_buffer.document.is_cursor_at_the_end
        )

    @bindings.add("escape", "right", filter=suggestion_available)
    def _accept(event):
        """
        Accept suggestion.
        """
        b = event.current_buffer

        b.insert_text(b.suggestion.text)

    @bindings.add("right", filter=suggestion_available)
    def _fill(event):
        """
        Fill partial suggestion.
        """
        b = event.current_buffer

        t = re.split(r"([^\s/]+[\s/]+)", b.suggestion.text)
        b.insert_text(next(x for x in t if x))

```
What's the `emacs_mode` for? I wasn't sure if it had to do with the "escape" or something else, so I left it out.

For further changes to autosuggestion, like including the common part of the completions, should they be done in prompt_toolkit or can they also be done in xonsh or `.xonshrc`?
> What's the `emacs_mode` for? I wasn't sure if it had to do with the "escape" or something else, so I left it out.

I don't use the Vi mode, so I don't really know how this could conflict with it. The `emacs_mode` filter is used in the original code which I copied verbatim from PTK source.

> For further changes to autosuggestion, like including the common part of the completions, should they be done in prompt_toolkit or can they also be done in xonsh or `.xonshrc`?

Autosuggestion in general is a PTK feature. Xonsh only submits history to PTK and let it do its thing. Although in Python it is possible to monkey-patch whatever customized code to almost anywhere, it is surely better to request meaningful changes in upstream libraries. 
PRs welcome!
hmm, PRs for what? We need to make this configurable in PTK.
xontrib is welcome! :)
Thanks for the info, this is something I really wanted as well! For some reason, <kbd>Alt</kbd>-<kbd>→</kbd> does not work for me on macOS even after adding the binding to `.xonshrc`. The binding works if I actually press <kbd>Esc</kbd> and <kbd>→</kbd> though.

The [prompt_toolkit docs](https://python-prompt-toolkit.readthedocs.io/en/master/pages/advanced_topics/key_bindings.html#binding-alt-something-option-something-or-meta-something) suggest my terminal should translate automatically from <kbd>Alt</kbd> to a leading <kbd>Esc</kbd>, but that doesn't seem to be happening for me. I tried this in both Terminal and iTerm.

Could there be a bug on macOS or do you think something is wrong with my setup? I would appreciate any ideas!

Edit: It turns out that both Terminal and iTerm were translating <kbd>Alt</kbd>-<kbd>→</kbd> to <kbd>Esc</kbd> + <kbd>f</kbd> automatically, so I just had to set the binding for "escape", "f" which is the default binding in prompt_toolkit as @laloch suggested.

Resolving patch:
diff --git a/xonsh/ptk_shell/key_bindings.py b/xonsh/ptk_shell/key_bindings.py
--- a/xonsh/ptk_shell/key_bindings.py
+++ b/xonsh/ptk_shell/key_bindings.py
@@ -208,9 +208,14 @@ def wrap_selection(buffer, left, right=None):
     buffer.selection_state = selection_state
 
 
-def load_xonsh_bindings() -> KeyBindingsBase:
+def load_xonsh_bindings(ptk_bindings: KeyBindingsBase) -> KeyBindingsBase:
     """
     Load custom key bindings.
+
+    Parameters
+    ----------
+    ptk_bindings :
+        The default prompt toolkit bindings. We need these to add aliases to them.
     """
     key_bindings = KeyBindings()
     handle = key_bindings.add
@@ -389,4 +394,12 @@ def _yank(event):
             buff.cut_selection()
         get_by_name("yank").call(event)
 
+    def create_alias(new_keys, original_keys):
+        bindings = ptk_bindings.get_bindings_for_keys(tuple(original_keys))
+        for original_binding in bindings:
+            handle(*new_keys, filter=original_binding.filter)(original_binding.handler)
+
+    # Complete a single auto-suggestion word
+    create_alias([Keys.ControlRight], ["escape", "f"])
+
     return key_bindings
diff --git a/xonsh/ptk_shell/shell.py b/xonsh/ptk_shell/shell.py
--- a/xonsh/ptk_shell/shell.py
+++ b/xonsh/ptk_shell/shell.py
@@ -207,7 +207,8 @@ def __init__(self, **kwargs):
 
         self.prompt_formatter = PTKPromptFormatter(self.prompter)
         self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self)
-        self.key_bindings = load_xonsh_bindings()
+        ptk_bindings = self.prompter.app.key_bindings
+        self.key_bindings = load_xonsh_bindings(ptk_bindings)
         self._overrides_deprecation_warning_shown = False
 
         # Store original `_history_matches` in case we need to restore it


Test patch: