xonsh/xontrib/abbrevs.py
Noorhteen Raja NJ 38295a1dd9
Remove globals (#4280)
* refactor: remove usage of global variables in abbrevs.py

* chore: add flake8-mutable to prevent mutable defaults

* fix: abbrevs expand test

* refactor: add xonsh session singleton

* refactor: fix circular errors when using xonshSession as singleton

* refactor: remove black magicked builtin attributes

* style: black format tests as well

* refactor: update tests to use xonsh-session singleton

* refactor: update abbrevs to not use builtins

* test: remove DummyCommandsCache and patch orig class

* fix: failing test_command_completers

* test: use monkeypatch to update xession fixture

* fix: failing test_pipelines

* fix: failing test_main

* chore: run test suit as single invocation

* test: fix tests/test_xonsh.xsh

* refactor: remove builtins from docs/conf.py

* fix: mypy error in jobs

* fix: test error from test_main

* test: close xession error in test_command_completers

* chore: use pytest-cov for reporting coverage

this will include subprocess calls, and will increase coverage

* style:
2021-05-20 13:14:26 +03:00

144 lines
4.5 KiB
Python

"""
Command abbreviations.
This expands input words from `abbrevs` disctionary as you type.
Adds ``abbrevs`` dictionary to hold user-defined "command abbreviations.
The dictionary is searched as you type the matching words are replaced
at the command line by the corresponding dictionary contents once you hit
'Space' or 'Return' key.
For instance a frequently used command such as ``git status`` can be abbreviated to ``gst`` as follows::
$ xontrib load abbrevs
$ abbrevs['gst'] = 'git status'
$ gst # Once you hit <space> or <return> 'gst' gets expanded to 'git status'.
one can set a callback function that receives current buffer and word to customize the expanded word based on context
.. code-block:: python
$ abbrevs['ps'] = lambda buffer, word: "procs" if buffer.text.startswith(word) else word
It is also possible to set the cursor position after expansion with,
$ abbrevs['gp'] = "git push <edit> --force"
"""
import builtins
import typing as tp
from prompt_toolkit.buffer import Buffer
from prompt_toolkit.filters import completion_is_selected, IsMultiline
from prompt_toolkit.keys import Keys
from xonsh.built_ins import DynamicAccessProxy, XSH
from xonsh.events import events
from xonsh.tools import check_for_partial_string
__all__ = ()
# todo: do not assign .abbrevs and directly use abbrevs as mutable const.
XSH.abbrevs = abbrevs = dict()
proxy = DynamicAccessProxy("abbrevs", "__xonsh__.abbrevs")
setattr(builtins, "abbrevs", proxy)
class _LastExpanded(tp.NamedTuple):
word: str
expanded: str
class Abbreviation:
"""A container class to handle state related to abbreviating keywords"""
last_expanded: tp.Optional[_LastExpanded] = None
def expand(self, buffer: Buffer) -> bool:
"""expand the given abbr text. Return true if cursor position changed."""
if not abbrevs:
return False
document = buffer.document
word = document.get_word_before_cursor(WORD=True)
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 False
text = get_abbreviated(word, buffer)
buffer.delete_before_cursor(count=len(word))
buffer.insert_text(text)
self.last_expanded = _LastExpanded(word, text)
if EDIT_SYMBOL in text:
set_cursor_position(buffer, text)
return True
return False
def revert(self, buffer) -> bool:
if self.last_expanded is None:
return False
document = buffer.document
expansion = self.last_expanded.expanded + " "
if not document.text_before_cursor.endswith(expansion):
return False
buffer.delete_before_cursor(count=len(expansion))
buffer.insert_text(self.last_expanded.word)
self.last_expanded = None
return True
EDIT_SYMBOL = "<edit>"
def get_abbreviated(key: str, buffer) -> str:
abbr = abbrevs[key]
if callable(abbr):
text = abbr(buffer=buffer, word=key)
else:
text = abbr
return text
def set_cursor_position(buffer, expanded: str) -> None:
pos = expanded.rfind(EDIT_SYMBOL)
if pos == -1:
return
buffer.cursor_position = buffer.cursor_position - (len(expanded) - pos)
buffer.delete(len(EDIT_SYMBOL))
@events.on_ptk_create
def custom_keybindings(bindings, **kw):
from xonsh.ptk_shell.key_bindings import carriage_return
from prompt_toolkit.filters import EmacsInsertMode, ViInsertMode
handler = bindings.add
insert_mode = ViInsertMode() | EmacsInsertMode()
abbrev = Abbreviation()
@handler(" ", filter=IsMultiline() & insert_mode)
def handle_space(event):
buffer = event.app.current_buffer
add_space = True
if not abbrev.revert(buffer):
position_changed = abbrev.expand(buffer)
if position_changed:
add_space = False
if add_space:
buffer.insert_text(" ")
@handler(
Keys.ControlJ, filter=IsMultiline() & insert_mode & ~completion_is_selected
)
@handler(
Keys.ControlM, filter=IsMultiline() & insert_mode & ~completion_is_selected
)
def multiline_carriage_return(event):
buffer = event.app.current_buffer
current_char = buffer.document.current_char
if not current_char or current_char.isspace():
abbrev.expand(buffer)
carriage_return(buffer, event.cli)