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`` - ``_.rtn``
- Returns the exit code, or status, of the previous command. - Returns the exit code, or status, of the previous command.
* - ``N=V command`` * - ``N=V command``
- ``with ${...}.swap(N=V): command`` - ``$N=V command`` or ``with ${...}.swap(N=V): command``
- Set temporary environment variable(s) and execute for command. - Set temporary environment variable(s) and execute the command.
Use an indented block to execute many commands in the same context. Use the second notation with an indented block to execute many commands in the same context.
* - ``!$`` * - ``!$``
- ``__xonsh__.history[-1, -1]`` - ``__xonsh__.history[-1, -1]``
- Get the last argument of the last command - 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"), ("pwd", None, lambda: os.getcwd() + "\n"),
("echo WORKING", None, "WORKING\n"), ("echo WORKING", None, "WORKING\n"),
("ls -f", lambda out: out.splitlines().sort(), os.listdir().sort()), ("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): 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) 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(): def test_echo_comma():
check_xonsh_ast({}, "![echo ,]", False) check_xonsh_ast({}, "![echo ,]", False)

View file

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

View file

@ -2885,6 +2885,26 @@ class BaseParser(object):
# subprocess # 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): def _dollar_rules(self, p):
"""These handle the special xonsh $ shell atoms by looking up """These handle the special xonsh $ shell atoms by looking up
in a special __xonsh__.env dictionary injected in the __builtin__. 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) p0 = xonsh_call("__xonsh__.subproc_uncaptured", p2, lineno=lineno, col=col)
else: else:
assert False 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 return p0
def _envvar_getter_by_name(self, var, lineno=None, col=None): def _envvar_getter_by_name(self, var, lineno=None, col=None):
@ -2974,6 +2999,8 @@ class BaseParser(object):
else: else:
raise ValueError("action not understood: " + action) raise ValueError("action not understood: " + action)
del arg._cliarg_action del arg._cliarg_action
if hasattr(args[0], "_xenvvars"):
setattr(cliargs, "_xenvvars", args[0]._xenvvars)
return cliargs return cliargs
def p_pipe(self, p): def p_pipe(self, p):
@ -3043,6 +3070,18 @@ class BaseParser(object):
arg._cliarg_action = "append" arg._cliarg_action = "append"
p[0] = p0 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 # Subproc atom rules
# #
@ -3142,11 +3181,14 @@ class BaseParser(object):
"""subproc_atom : atdollar_lparen_tok subproc RPAREN """subproc_atom : atdollar_lparen_tok subproc RPAREN
subproc_arg_part : 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( 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" 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 p[0] = p0
def p_subproc_atom_subproc_inject_bang_empty(self, p): def p_subproc_atom_subproc_inject_bang_empty(self, p):
@ -3282,6 +3324,28 @@ class BaseParser(object):
p1 = p[1] p1 = p[1]
p[0] = ast.Str(s=p1.value, lineno=p1.lineno, col_offset=p1.lexpos) 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 # Helpers
# #

View file

@ -3,6 +3,7 @@
import builtins import builtins
from prompt_toolkit import search from prompt_toolkit import search
from prompt_toolkit.application.current import get_app
from prompt_toolkit.enums import DEFAULT_BUFFER from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.filters import ( from prompt_toolkit.filters import (
Condition, Condition,
@ -13,7 +14,8 @@ from prompt_toolkit.filters import (
IsSearching, IsSearching,
) )
from prompt_toolkit.keys import Keys 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.aliases import xonsh_exit
from xonsh.tools import check_for_partial_string, get_line_continuation from xonsh.tools import check_for_partial_string, get_line_continuation
@ -165,7 +167,7 @@ def autopair_condition():
@Condition @Condition
def whitespace_or_bracket_before(): def whitespace_or_bracket_before():
"""Check if there is whitespace or an opening """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 d = get_app().current_buffer.document
return bool( return bool(
d.cursor_position == 0 d.cursor_position == 0
@ -177,7 +179,7 @@ def whitespace_or_bracket_before():
@Condition @Condition
def whitespace_or_bracket_after(): def whitespace_or_bracket_after():
"""Check if there is whitespace or a closing """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 d = get_app().current_buffer.document
return bool( return bool(
d.is_cursor_at_the_end_of_line 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. Load custom key bindings.
""" """
key_bindings = KeyBindings()
handle = key_bindings.add handle = key_bindings.add
has_selection = HasSelection() has_selection = HasSelection()
insert_mode = ViInsertMode() | EmacsInsertMode() insert_mode = ViInsertMode() | EmacsInsertMode()
@ -357,3 +360,25 @@ def load_xonsh_bindings(key_bindings):
during the previous command. during the previous command.
""" """
pass 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.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.lexers import PygmentsLexer from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.enums import EditingMode 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.history import ThreadedHistory
from prompt_toolkit.shortcuts import print_formatted_text as ptk_print from prompt_toolkit.shortcuts import print_formatted_text as ptk_print
from prompt_toolkit.shortcuts import CompleteStyle from prompt_toolkit.shortcuts import CompleteStyle
@ -91,8 +94,13 @@ class PromptToolkitShell(BaseShell):
self.history = ThreadedHistory(PromptToolkitHistory()) self.history = ThreadedHistory(PromptToolkitHistory())
self.prompter = PromptSession(history=self.history) self.prompter = PromptSession(history=self.history)
self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self) self.pt_completer = PromptToolkitCompleter(self.completer, self.ctx, self)
self.key_bindings = KeyBindings() self.key_bindings = merge_key_bindings(
load_xonsh_bindings(self.key_bindings) [
load_xonsh_bindings(),
load_emacs_shift_selection_bindings(),
]
)
# Store original `_history_matches` in case we need to restore it # Store original `_history_matches` in case we need to restore it
self._history_matches_orig = self.prompter.default_buffer._history_matches self._history_matches_orig = self.prompter.default_buffer._history_matches
# This assumes that PromptToolkitShell is a singleton # This assumes that PromptToolkitShell is a singleton
@ -282,8 +290,7 @@ class PromptToolkitShell(BaseShell):
@property @property
def bottom_toolbar_tokens(self): 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"): if builtins.__xonsh__.env.get("BOTTOM_TOOLBAR"):
return self._bottom_toolbar_tokens return self._bottom_toolbar_tokens

View file

@ -17,6 +17,11 @@
"url": "https://github.com/DangerOnTheRanger/xonsh-apt-tabcomplete", "url": "https://github.com/DangerOnTheRanger/xonsh-apt-tabcomplete",
"description": ["Adds tabcomplete functionality to apt-get/apt-cache inside of xonsh."] "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", {"name": "autojump",
"package": "xontrib-autojump", "package": "xontrib-autojump",
"url": "https://github.com/sagartewari01/autojump-xonsh", "url": "https://github.com/sagartewari01/autojump-xonsh",