Repository: xonsh/xonsh

Issue:
fish-shell like abbr-expansion support
<!--- 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: https://xon.sh/bash_to_xsh.html
If you don't find an answer there, please do open an issue! -->

## xonfig
I am currently moving from fish to xonsh as my main linux-shell. I miss the fish's feature of [abbr](https://fishshell.com/docs/current/cmds/abbr.html) command. It will be cool to have this on xonsh. It will be aliases that are more explicit. 


Discussion:
Think that's what sequence aliases do?

```
🐚 aliases['spam'] = ['echo', 'eggs']                                                                            
🐚 spam foo bar                                                                                                  
eggs foo bar
```

Unless I'm missing something.
say I have an abbr for 
abbr spam="echo eggs"

when I type `spam`, in the prompt, it will expand to `echo eggs `. it is triggered with space or enter. Also it will not mess with any other binary names. just kind completion/expansion.

https://fishshell.com/docs/current/cmds/abbr.html#examples
ahh, abbreviations are expanded in the prompt, not at execution, ok.
I would love to see a PR for this!
This is surprisingly hard to implement.
@jonathanslenders, do you have any idea about where to hook into prompt_toolkit in order to implement a dictionary-based replacement of the previous token in the buffer? Neither Completer nor Processor seems to be the perfect fit for the task and I don't like the idea of binding <kbd>Space</kbd> key.
@laloch - I think you have to bind it to something. And if people wanted this behavior, space seems like the right thing, I would think.
@laloch - both space and enter(something that accepts) complete the abbreviation in fish. the same could be adopted.
Yes, I know how it shoud work from the user perspective. I was talking about implementation details.
In the prompt_toolkit examples directory I've once done this with the space too: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/examples/prompts/autocorrection.py
@jonathanslenders, thank's for the answer. I have already figured that out. It just doesn't feel right(TM) to me to bind keyboard in order to process the buffer content.

Resolving patch:
diff --git a/xontrib/abbrevs.py b/xontrib/abbrevs.py
new file mode 100644
--- /dev/null
+++ b/xontrib/abbrevs.py
@@ -0,0 +1,66 @@
+"""
+Command abbreviations.
+
+This expands input words from `abbrevs` disctionary as you type.
+"""
+
+import builtins
+from prompt_toolkit.filters import IsMultiline
+from prompt_toolkit.keys import Keys
+from xonsh.built_ins import DynamicAccessProxy
+from xonsh.platform import ptk_shell_type
+from xonsh.tools import check_for_partial_string
+
+__all__ = ()
+
+builtins.__xonsh__.abbrevs = dict()
+proxy = DynamicAccessProxy("abbrevs", "__xonsh__.abbrevs")
+setattr(builtins, "abbrevs", proxy)
+
+
+def expand_abbrev(buffer):
+    abbrevs = getattr(builtins, "abbrevs")
+    if abbrevs is None:
+        return
+    document = buffer.document
+    word = document.get_word_before_cursor()
+    abbrevs = builtins.abbrevs
+    if word in abbrevs.keys():
+        partial = document.text[: document.cursor_position]
+        startix, endix, quote = check_for_partial_string(partial)
+        if startix is not None and endix is None:
+            return
+        buffer.delete_before_cursor(count=len(word))
+        buffer.insert_text(abbrevs[word])
+
+
+@events.on_ptk_create
+def custom_keybindings(bindings, **kw):
+
+    if ptk_shell_type() == "prompt_toolkit2":
+        from xonsh.ptk2.key_bindings import carriage_return
+        from prompt_toolkit.filters import EmacsInsertMode, ViInsertMode
+
+        handler = bindings.add
+        insert_mode = ViInsertMode() | EmacsInsertMode()
+    else:
+        from xonsh.ptk.key_bindings import carriage_return
+        from prompt_toolkit.filters import to_filter
+
+        handler = bindings.registry.add_binding
+        insert_mode = to_filter(True)
+
+    @handler(" ", filter=IsMultiline() & insert_mode)
+    def handle_space(event):
+        buffer = event.app.current_buffer
+        expand_abbrev(buffer)
+        buffer.insert_text(" ")
+
+    @handler(Keys.ControlJ, filter=IsMultiline() & insert_mode)
+    @handler(Keys.ControlM, filter=IsMultiline() & insert_mode)
+    def multiline_carriage_return(event):
+        buffer = event.app.current_buffer
+        current_char = buffer.document.current_char
+        if not current_char or current_char.isspace():
+            expand_abbrev(buffer)
+        carriage_return(buffer, event.cli)


Test patch: