Merge branch 'master' into environment_fixes

This commit is contained in:
Bob Hyman 2020-09-11 01:15:49 -04:00
commit 06986fc0d4
11 changed files with 218 additions and 30 deletions

View file

@ -55,9 +55,9 @@ line is ``#!/usr/bin/env xonsh``.
- ``_.rtn``
- Returns the exit code, or status, of the previous command.
* - ``N=V command``
- ``with ${...}.swap(N=V): command``
- Set temporary environment variable(s) and execute for command.
Use an indented block to execute many commands in the same context.
- ``$N=V command`` or ``with ${...}.swap(N=V): command``
- Set temporary environment variable(s) and execute the command.
Use the second notation with an indented block to execute many commands in the same context.
* - ``!$``
- ``__xonsh__.history[-1, -1]``
- Get the last argument of the last command

23
news/argcomplete.rst Normal file
View file

@ -0,0 +1,23 @@
**Added:**
* Added xontrib-argcomplete to support kislyuk/argcomplete - tab completion for argparse.
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

23
news/shift-arrow.rst Normal file
View file

@ -0,0 +1,23 @@
**Added:**
* Borrow shift-selection from prompt-toolkit. Shift-arrow (selects a letter) and control-shift-arrow (selects a word) should now be supported.
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

24
news/simple-variables.rst Normal file
View file

@ -0,0 +1,24 @@
**Added:**
* Xonsh now supports bash-style variable assignments preceding
subprocess commands (e.g. ``$FOO = "bar" bash -c r"echo $FOO"``).
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -499,6 +499,7 @@ def test_script_stderr(case):
("pwd", None, lambda: os.getcwd() + "\n"),
("echo WORKING", None, "WORKING\n"),
("ls -f", lambda out: out.splitlines().sort(), os.listdir().sort()),
("$FOO='foo' $BAR=2 xonsh -c r'echo -n $FOO$BAR'", None, "foo2",),
],
)
def test_single_command_no_windows(cmd, fmt, exp):

View file

@ -2501,6 +2501,10 @@ def test_ls_quotes_3_space():
check_xonsh_ast({}, '$[ls "wakka jawaka baraka"]', False)
def test_leading_envvar_assignment():
check_xonsh_ast({}, "![$FOO= 'foo' $BAR =2 echo r'$BAR']", False)
def test_echo_comma():
check_xonsh_ast({}, "![echo ,]", False)

View file

@ -390,6 +390,7 @@ class SubprocSpec:
universal_newlines=False,
close_fds=False,
captured=False,
env=None,
):
"""
Parameters
@ -413,6 +414,8 @@ class SubprocSpec:
The flag for if the subprocess is captured, may be one of:
False for $[], 'stdout' for $(), 'hiddenobject' for ![], or
'object' for !().
env : dict
Replacement environment to run the subporcess in.
Attributes
----------
@ -451,6 +454,13 @@ class SubprocSpec:
self.universal_newlines = universal_newlines
self.close_fds = close_fds
self.captured = captured
if env is not None:
self.env = {
k: v if not (isinstance(v, list)) or len(v) > 1 else v[0]
for (k, v) in env.items()
}
else:
self.env = None
# pure attrs
self.args = list(cmd)
self.alias = None
@ -579,7 +589,8 @@ class SubprocSpec:
def prep_env(self, kwargs):
"""Prepares the environment to use in the subprocess."""
denv = builtins.__xonsh__.env.detype()
with builtins.__xonsh__.env.swap(self.env) as env:
denv = env.detype()
if ON_WINDOWS:
# Over write prompt variable as xonsh's $PROMPT does
# not make much sense for other subprocs
@ -878,7 +889,7 @@ def _update_last_spec(last):
last.captured_stderr = last.captured_stdout
def cmds_to_specs(cmds, captured=False):
def cmds_to_specs(cmds, captured=False, envs=None):
"""Converts a list of cmds to a list of SubprocSpec objects that are
ready to be executed.
"""
@ -886,11 +897,12 @@ def cmds_to_specs(cmds, captured=False):
i = 0
specs = []
redirects = []
for cmd in cmds:
for i, cmd in enumerate(cmds):
if isinstance(cmd, str):
redirects.append(cmd)
else:
spec = SubprocSpec.build(cmd, captured=captured)
env = envs[i] if envs is not None else None
spec = SubprocSpec.build(cmd, captured=captured, env=env)
spec.pipeline_index = i
specs.append(spec)
i += 1
@ -921,7 +933,7 @@ def _should_set_title(captured=False):
)
def run_subproc(cmds, captured=False):
def run_subproc(cmds, captured=False, envs=None):
"""Runs a subprocess, in its many forms. This takes a list of 'commands,'
which may be a list of command line arguments or a string, representing
a special connecting character. For example::
@ -937,7 +949,7 @@ def run_subproc(cmds, captured=False):
if builtins.__xonsh__.env.get("XONSH_TRACE_SUBPROC"):
print("TRACE SUBPROC: %s" % str(cmds), file=sys.stderr)
specs = cmds_to_specs(cmds, captured=captured)
specs = cmds_to_specs(cmds, captured=captured, envs=envs)
captured = specs[-1].captured
if captured == "hiddenobject":
command = HiddenCommandPipeline(specs)
@ -982,20 +994,20 @@ def run_subproc(cmds, captured=False):
return
def subproc_captured_stdout(*cmds):
def subproc_captured_stdout(*cmds, envs=None):
"""Runs a subprocess, capturing the output. Returns the stdout
that was produced as a str.
"""
return run_subproc(cmds, captured="stdout")
return run_subproc(cmds, captured="stdout", envs=envs)
def subproc_captured_inject(*cmds):
def subproc_captured_inject(*cmds, envs=None):
"""Runs a subprocess, capturing the output. Returns a list of
whitespace-separated strings of the stdout that was produced.
The string is split using xonsh's lexer, rather than Python's str.split()
or shlex.split().
"""
o = run_subproc(cmds, captured="object")
o = run_subproc(cmds, captured="object", envs=envs)
o.end()
toks = []
for line in o:
@ -1004,26 +1016,26 @@ def subproc_captured_inject(*cmds):
return toks
def subproc_captured_object(*cmds):
def subproc_captured_object(*cmds, envs=None):
"""
Runs a subprocess, capturing the output. Returns an instance of
CommandPipeline representing the completed command.
"""
return run_subproc(cmds, captured="object")
return run_subproc(cmds, captured="object", envs=envs)
def subproc_captured_hiddenobject(*cmds):
def subproc_captured_hiddenobject(*cmds, envs=None):
"""Runs a subprocess, capturing the output. Returns an instance of
HiddenCommandPipeline representing the completed command.
"""
return run_subproc(cmds, captured="hiddenobject")
return run_subproc(cmds, captured="hiddenobject", envs=envs)
def subproc_uncaptured(*cmds):
def subproc_uncaptured(*cmds, envs=None):
"""Runs a subprocess, without capturing the output. Returns the stdout
that was produced as a str.
"""
return run_subproc(cmds, captured=False)
return run_subproc(cmds, captured=False, envs=envs)
def ensure_list_of_strs(x):

View file

@ -2885,6 +2885,26 @@ class BaseParser(object):
# subprocess
#
def _get_envvars(self, p, lineno, col):
"""Get replacement environment from subproc_atoms, return None or
ast.List containing ast.Dict for each subproc_atom in the pipeline.
"""
if not isinstance(p, list):
return None
has_env = False
envs = empty_list(lineno=lineno, col=col)
for subproc in p:
if hasattr(subproc, "_xenvvars"):
has_env = True
envs.elts.append(subproc._xenvvars)
else:
envs.elts.append(
ast.Constant(
value=None, lineno=subproc.lineno, col_offset=subproc.col_offset
)
)
return envs if has_env else None
def _dollar_rules(self, p):
"""These handle the special xonsh $ shell atoms by looking up
in a special __xonsh__.env dictionary injected in the __builtin__.
@ -2920,6 +2940,11 @@ class BaseParser(object):
p0 = xonsh_call("__xonsh__.subproc_uncaptured", p2, lineno=lineno, col=col)
else:
assert False
envs = self._get_envvars(p2, lineno, col)
if envs is not None:
p0.keywords.append(ast.keyword(arg="envs", value=envs))
return p0
def _envvar_getter_by_name(self, var, lineno=None, col=None):
@ -2974,6 +2999,8 @@ class BaseParser(object):
else:
raise ValueError("action not understood: " + action)
del arg._cliarg_action
if hasattr(args[0], "_xenvvars"):
setattr(cliargs, "_xenvvars", args[0]._xenvvars)
return cliargs
def p_pipe(self, p):
@ -3043,6 +3070,18 @@ class BaseParser(object):
arg._cliarg_action = "append"
p[0] = p0
def p_envvar_assign_subproc_atoms(self, p):
"""subproc_atoms : envvar_assign subproc_atoms
| envvar_assign subproc_atoms WS
"""
p1, p20 = p[1], p[2][0]
if hasattr(p20, "_xenvvars"):
p20._xenvvars.keys.append(p1.keys[0])
p20._xenvvars.values.append(p1.values[0])
else:
setattr(p20, "_xenvvars", p1)
p[0] = p[2]
#
# Subproc atom rules
#
@ -3142,11 +3181,14 @@ class BaseParser(object):
"""subproc_atom : atdollar_lparen_tok subproc RPAREN
subproc_arg_part : atdollar_lparen_tok subproc RPAREN
"""
p1 = p[1]
p1, p2 = p[1], p[2]
p0 = xonsh_call(
"__xonsh__.subproc_captured_inject", p[2], lineno=p1.lineno, col=p1.lexpos
"__xonsh__.subproc_captured_inject", p2, lineno=p1.lineno, col=p1.lexpos
)
p0._cliarg_action = "extend"
envs = self._get_envvars(p2, lineno=p2[0].lineno, col=p2[0].col_offset)
if envs is not None:
p0.keywords.append(ast.keyword(arg="envs", value=envs))
p[0] = p0
def p_subproc_atom_subproc_inject_bang_empty(self, p):
@ -3282,6 +3324,28 @@ class BaseParser(object):
p1 = p[1]
p[0] = ast.Str(s=p1.value, lineno=p1.lineno, col_offset=p1.lexpos)
def p_envvar_assign_left(self, p):
"""envvar_assign_left : dollar_name_tok EQUALS
| dollar_name_tok WS EQUALS
| dollar_name_tok EQUALS WS
| dollar_name_tok WS EQUALS WS
"""
p[0] = p[1]
def p_envvar_assign(self, p):
"""envvar_assign : envvar_assign_left test WS
| envvar_assign_left subproc_atom WS
"""
p1, p2 = p[1], p[2]
p[0] = ast.Dict(
keys=[
ast.Constant(value=p1.value[1:], lineno=p1.lineno, col_offset=p1.lexpos)
],
values=[p2],
lineno=p1.lineno,
col_offset=p1.lexpos,
)
#
# Helpers
#

View file

@ -3,6 +3,7 @@
import builtins
from prompt_toolkit import search
from prompt_toolkit.application.current import get_app
from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.filters import (
Condition,
@ -13,7 +14,8 @@ from prompt_toolkit.filters import (
IsSearching,
)
from prompt_toolkit.keys import Keys
from prompt_toolkit.application.current import get_app
from prompt_toolkit.key_binding.key_bindings import KeyBindings, KeyBindingsBase
from prompt_toolkit.key_binding.bindings.named_commands import get_by_name
from xonsh.aliases import xonsh_exit
from xonsh.tools import check_for_partial_string, get_line_continuation
@ -165,7 +167,7 @@ def autopair_condition():
@Condition
def whitespace_or_bracket_before():
"""Check if there is whitespace or an opening
bracket to the left of the cursor"""
bracket to the left of the cursor"""
d = get_app().current_buffer.document
return bool(
d.cursor_position == 0
@ -177,7 +179,7 @@ def whitespace_or_bracket_before():
@Condition
def whitespace_or_bracket_after():
"""Check if there is whitespace or a closing
bracket to the right of the cursor"""
bracket to the right of the cursor"""
d = get_app().current_buffer.document
return bool(
d.is_cursor_at_the_end_of_line
@ -186,10 +188,11 @@ def whitespace_or_bracket_after():
)
def load_xonsh_bindings(key_bindings):
def load_xonsh_bindings() -> KeyBindingsBase:
"""
Load custom key bindings.
"""
key_bindings = KeyBindings()
handle = key_bindings.add
has_selection = HasSelection()
insert_mode = ViInsertMode() | EmacsInsertMode()
@ -357,3 +360,25 @@ def load_xonsh_bindings(key_bindings):
during the previous command.
"""
pass
@handle(Keys.ControlX, Keys.ControlX, filter=has_selection)
def _cut(event):
""" Cut selected text. """
data = event.current_buffer.cut_selection()
event.app.clipboard.set_data(data)
@handle(Keys.ControlX, Keys.ControlC, filter=has_selection)
def _copy(event):
""" Copy selected text. """
data = event.current_buffer.copy_selection()
event.app.clipboard.set_data(data)
@handle(Keys.ControlV, filter=insert_mode | has_selection)
def _yank(event):
""" Paste selected text. """
buff = event.current_buffer
if buff.selection_state:
buff.cut_selection()
get_by_name("yank").call(event)
return key_bindings

View file

@ -21,7 +21,10 @@ from prompt_toolkit import ANSI
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.key_binding.bindings.emacs import (
load_emacs_shift_selection_bindings,
)
from prompt_toolkit.key_binding.key_bindings import merge_key_bindings
from prompt_toolkit.history import ThreadedHistory
from prompt_toolkit.shortcuts import print_formatted_text as ptk_print
from prompt_toolkit.shortcuts import CompleteStyle
@ -91,8 +94,13 @@ class PromptToolkitShell(BaseShell):
self.history = ThreadedHistory(PromptToolkitHistory())
self.prompter = PromptSession(history=self.history)
self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self)
self.key_bindings = KeyBindings()
load_xonsh_bindings(self.key_bindings)
self.key_bindings = merge_key_bindings(
[
load_xonsh_bindings(),
load_emacs_shift_selection_bindings(),
]
)
# Store original `_history_matches` in case we need to restore it
self._history_matches_orig = self.prompter.default_buffer._history_matches
# This assumes that PromptToolkitShell is a singleton
@ -282,8 +290,7 @@ class PromptToolkitShell(BaseShell):
@property
def bottom_toolbar_tokens(self):
"""Returns self._bottom_toolbar_tokens if it would yield a result
"""
"""Returns self._bottom_toolbar_tokens if it would yield a result"""
if builtins.__xonsh__.env.get("BOTTOM_TOOLBAR"):
return self._bottom_toolbar_tokens

View file

@ -17,6 +17,11 @@
"url": "https://github.com/DangerOnTheRanger/xonsh-apt-tabcomplete",
"description": ["Adds tabcomplete functionality to apt-get/apt-cache inside of xonsh."]
},
{"name": "argcomplete",
"package": "xontrib-argcomplete",
"url": "https://github.com/anki-code/xontrib-argcomplete",
"description": ["Adding support of kislyuk/argcomplete to xonsh."]
},
{"name": "autojump",
"package": "xontrib-autojump",
"url": "https://github.com/sagartewari01/autojump-xonsh",