Merge branch 'master' into environment_fixes

This commit is contained in:
Bob Hyman 2020-09-23 04:40:26 -04:00
commit 88bba37c3a
29 changed files with 580 additions and 74 deletions

View file

@ -255,6 +255,21 @@ may be useful to share entries between shell sessions. In such a case, one can u
the ``flush`` action to immediately save the session history to disk and make it
accessible from other shell sessions.
``clear`` action
================
Deletes the history from the current session up until this point. Later commands
will still be saved.
``off`` action
================
Deletes the history from the current session and turns off history saving for the
rest of the session. Only session metadata will be saved, not commands or output.
``on`` action
================
Turns history saving back on. Previous commands won't be saved, but future
commands will be.
``gc`` action
===============
Last, but certainly not least, the ``gc`` action is a manual hook into executing

23
news/ansi_osc.rst Normal file
View file

@ -0,0 +1,23 @@
**Added:**
* Support for ANSI OSC escape sequences in ``$PROMPT``, setting ``$TITLE`` for example. (#374, #1403)
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -0,0 +1,23 @@
**Added:**
* Use command_cache when finding available commands, to speedup command-not-found suggestions
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -0,0 +1,23 @@
**Added:**
* history clear, history off and history on actions, for managing whether history in the current session is saved.
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

23
news/name_in_error.rst Normal file
View file

@ -0,0 +1,23 @@
**Added:**
* ValueErrors from environ.register now report the name of the bad env var
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

23
news/prompt-speed.rst Normal file
View file

@ -0,0 +1,23 @@
**Added:**
* <news item>
**Changed:**
* Minor improvements to the get prompt speed. (Mostly in git.)
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -0,0 +1,25 @@
**Added:**
* <news item>
**Changed:**
* ptk key binding for TAB -- hitting TAB to start completion now automatically selects the first displayed completion (if any).
hitting TAB when in insert mode inserts TAB, as heretofore. This more exactly follows behavior of readline ``menu-complete``.
There is no configuration option for tailoring this behavior.
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

23
news/shift_del.rst Normal file
View file

@ -0,0 +1,23 @@
**Added:**
* Added to xontrib whole_word_jumping: Shift+Delete hotkey to delete whole word.
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -0,0 +1,23 @@
**Added:**
* <news item>
**Changed:**
* <news item>
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* Fix crash when xonsh tries to run windows app execution aliases.
**Security:**
* <news item>

23
news/xontrib_descr.rst Normal file
View file

@ -0,0 +1,23 @@
**Added:**
* <news item>
**Changed:**
* xontrib-argcomplete and xontrib-pipeliner description improvement.
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -4,8 +4,12 @@ from rever.activities.ghrelease import git_archive_asset
$PROJECT = $GITHUB_ORG = $GITHUB_REPO = 'xonsh'
$WEBSITE_URL = 'http://xon.sh'
$ACTIVITIES = ['authors', 'version_bump', 'changelog', 'pytest', 'appimage',
'tag', 'push_tag', 'ghrelease', 'sphinx',
'ghpages', 'pypi', 'conda_forge',
'tag', 'push_tag',
'ghrelease',
'sphinx',
'ghpages',
'pypi',
'conda_forge',
]
$PYPI_SIGN = False

View file

@ -483,3 +483,65 @@ def test__xhj_gc_xx_to_rmfiles(
assert minute_diff <= 60
else:
assert act_size == exp_size
def test_hist_clear_cmd(hist, xonsh_builtins, capsys, tmpdir):
"""Verify that the CLI history clear command works."""
xonsh_builtins.__xonsh__.env.update({"XONSH_DATA_DIR": str(tmpdir)})
xonsh_builtins.__xonsh__.history = hist
xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
for ts, cmd in enumerate(CMDS): # populate the shell history
hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})
assert len(xonsh_builtins.__xonsh__.history) == 6
history_main(["clear"])
out, err = capsys.readouterr()
assert err.rstrip() == "History cleared"
assert len(xonsh_builtins.__xonsh__.history) == 0
def test_hist_off_cmd(hist, xonsh_builtins, capsys, tmpdir):
"""Verify that the CLI history off command works."""
xonsh_builtins.__xonsh__.env.update({"XONSH_DATA_DIR": str(tmpdir)})
xonsh_builtins.__xonsh__.history = hist
xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
for ts, cmd in enumerate(CMDS): # populate the shell history
hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})
assert len(xonsh_builtins.__xonsh__.history) == 6
history_main(["off"])
out, err = capsys.readouterr()
assert err.rstrip() == "History off"
assert len(xonsh_builtins.__xonsh__.history) == 0
for ts, cmd in enumerate(CMDS): # attempt to populate the shell history
hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})
assert len(xonsh_builtins.__xonsh__.history) == 0
def test_hist_on_cmd(hist, xonsh_builtins, capsys, tmpdir):
"""Verify that the CLI history on command works."""
xonsh_builtins.__xonsh__.env.update({"XONSH_DATA_DIR": str(tmpdir)})
xonsh_builtins.__xonsh__.history = hist
xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
for ts, cmd in enumerate(CMDS): # populate the shell history
hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})
assert len(xonsh_builtins.__xonsh__.history) == 6
history_main(["off"])
history_main(["on"])
out, err = capsys.readouterr()
assert err.rstrip().endswith("History on")
assert len(xonsh_builtins.__xonsh__.history) == 0
for ts, cmd in enumerate(CMDS): # populate the shell history
hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})
assert len(xonsh_builtins.__xonsh__.history) == 6

View file

@ -215,3 +215,65 @@ def test_history_getitem(index, exp, hist, xonsh_builtins):
assert [(e.cmd, e.out, e.rtn, e.ts) for e in entry] == exp
else:
assert (entry.cmd, entry.out, entry.rtn, entry.ts) == exp
def test_hist_clear_cmd(hist, xonsh_builtins, capsys, tmpdir):
"""Verify that the CLI history clear command works."""
xonsh_builtins.__xonsh__.env.update({"XONSH_DATA_DIR": str(tmpdir)})
xonsh_builtins.__xonsh__.history = hist
xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
for ts, cmd in enumerate(CMDS): # populate the shell history
hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})
assert len(xonsh_builtins.__xonsh__.history) == 6
history_main(["clear"])
out, err = capsys.readouterr()
assert err.rstrip() == "History cleared"
assert len(xonsh_builtins.__xonsh__.history) == 0
def test_hist_off_cmd(hist, xonsh_builtins, capsys, tmpdir):
"""Verify that the CLI history off command works."""
xonsh_builtins.__xonsh__.env.update({"XONSH_DATA_DIR": str(tmpdir)})
xonsh_builtins.__xonsh__.history = hist
xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
for ts, cmd in enumerate(CMDS): # populate the shell history
hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})
assert len(xonsh_builtins.__xonsh__.history) == 6
history_main(["off"])
out, err = capsys.readouterr()
assert err.rstrip() == "History off"
assert len(xonsh_builtins.__xonsh__.history) == 0
for ts, cmd in enumerate(CMDS): # attempt to populate the shell history
hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})
assert len(xonsh_builtins.__xonsh__.history) == 0
def test_hist_on_cmd(hist, xonsh_builtins, capsys, tmpdir):
"""Verify that the CLI history on command works."""
xonsh_builtins.__xonsh__.env.update({"XONSH_DATA_DIR": str(tmpdir)})
xonsh_builtins.__xonsh__.history = hist
xonsh_builtins.__xonsh__.env["HISTCONTROL"] = set()
for ts, cmd in enumerate(CMDS): # populate the shell history
hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})
assert len(xonsh_builtins.__xonsh__.history) == 6
history_main(["off"])
history_main(["on"])
out, err = capsys.readouterr()
assert err.rstrip().endswith("History on")
assert len(xonsh_builtins.__xonsh__.history) == 0
for ts, cmd in enumerate(CMDS): # populate the shell history
hist.append({"inp": cmd, "rtn": 0, "ts": (ts + 1, ts + 1.5)})
assert len(xonsh_builtins.__xonsh__.history) == 6

View file

@ -8,7 +8,7 @@ from xonsh.platform import minimum_required_ptk_version
# verify error if ptk not installed or below min
from xonsh.ptk_shell.shell import tokenize_ansi
from xonsh.ptk_shell.shell import tokenize_ansi, remove_ansi_osc
from xonsh.shell import Shell
@ -105,4 +105,31 @@ def test_tokenize_ansi(prompt_tokens, ansi_string_parts):
assert token[1] == text
@pytest.mark.parametrize(
"raw_prompt, prompt, osc_tokens",
[
# no title
("test prompt", "test prompt", []),
# starts w/ title
("\033]0;TITLE THIS\007test prompt", "test prompt", ["\033]0;TITLE THIS\007"]),
# ends w/ title
("test prompt\033]0;TITLE THIS\007", "test prompt", ["\033]0;TITLE THIS\007"]),
# title in the middle
("test \033]0;TITLE THIS\007prompt", "test prompt", ["\033]0;TITLE THIS\007"]),
# title + iTerm2 OSC exapmle
(
"test \033]0;TITLE THIS\007prompt \033]133;A\007here",
"test prompt here",
["\033]0;TITLE THIS\007", "\033]133;A\007"],
),
],
)
def test_remove_ansi_osc(raw_prompt, prompt, osc_tokens):
checked_prompt, removed_osc = remove_ansi_osc(raw_prompt)
assert prompt == checked_prompt
assert len(removed_osc) == len(osc_tokens)
for removed, ref in zip(removed_osc, osc_tokens):
assert removed == ref
# someday: initialize PromptToolkitShell and have it actually do something.

View file

@ -179,16 +179,30 @@ def pathsearch(func, s, pymode=False, pathobj=False):
RE_SHEBANG = LazyObject(lambda: re.compile(r"#![ \t]*(.+?)$"), globals(), "RE_SHEBANG")
def is_app_execution_alias(fname):
""" App execution aliases behave strangly on windows and python.
Here try to detect if a file is an app execution alias
"""
fname = pathlib.Path(fname)
return not os.path.exists(fname) and fname.name in os.listdir(fname.parent)
def _is_binary(fname, limit=80):
with open(fname, "rb") as f:
for i in range(limit):
char = f.read(1)
if char == b"\0":
return True
if char == b"\n":
return False
if char == b"":
return False
try:
with open(fname, "rb") as f:
for i in range(limit):
char = f.read(1)
if char == b"\0":
return True
if char == b"\n":
return False
if char == b"":
return
except OSError as e:
if ON_WINDOWS and is_app_execution_alias(fname):
return True
raise e
return False

View file

@ -61,10 +61,7 @@ class RichCompletion(str):
def __repr__(self):
return "RichCompletion({}, prefix_len={}, display={}, description={})".format(
repr(str(self)),
self.prefix_len,
repr(self.display),
repr(self.description),
repr(str(self)), self.prefix_len, repr(self.display), repr(self.description)
)

View file

@ -2099,7 +2099,7 @@ class Env(cabc.MutableMapping):
pass
else:
raise ValueError(
"Default value does not match type specified by validate"
f"Default value for {name} does not match type specified by validate"
)
self._vars[name] = Var(

View file

@ -71,6 +71,7 @@ class History:
self.last_cmd_out = None
self.hist_size = None
self.hist_units = None
self.remember_history = True
def __len__(self):
"""Return the number of items in current session."""
@ -153,3 +154,9 @@ class History:
If set blocking, then wait until gc action finished.
"""
pass
def clear(self):
"""Clears the history of the current session from both the disk and
memory.
"""
pass

View file

@ -202,7 +202,7 @@ class JsonHistoryGC(threading.Thread):
# info: file size, closing timestamp, number of commands, filename
ts = lj.get("ts", (0.0, None))
files.append(
(ts[1] or ts[0], len(lj.sizes["cmds"]) - 1, f, cur_file_size,),
(ts[1] or ts[0], len(lj.sizes["cmds"]) - 1, f, cur_file_size)
)
lj.close()
if xonsh_debug:
@ -303,6 +303,9 @@ class JsonCommandField(cabc.Sequence):
return len(self.hist)
def __getitem__(self, key):
if not self.hist.remember_history:
return ""
size = len(self)
if isinstance(key, slice):
return [self[i] for i in range(*key.indices(size))]
@ -407,6 +410,8 @@ class JsonHistory(History):
hf : JsonHistoryFlusher or None
The thread that was spawned to flush history
"""
if not self.remember_history:
return
self.buffer.append(cmd)
self._len += 1 # must come before flushing
if len(self.buffer) >= self.buffersize:
@ -429,6 +434,7 @@ class JsonHistory(History):
hf : JsonHistoryFlusher or None
The thread that was spawned to flush history
"""
# Implicitly covers case of self.remember_history being False.
if len(self.buffer) == 0:
return
@ -502,3 +508,18 @@ class JsonHistory(History):
if blocking:
while self.gc.is_alive(): # while waiting for gc.
time.sleep(0.1) # don't monopolize the thread (or Python GIL?)
def clear(self):
"""Clears the current session's history from both memory and disk."""
# Wipe history from memory. Keep sessionid and other metadata.
self.buffer = []
self.tss = JsonCommandField("ts", self)
self.inps = JsonCommandField("inp", self)
self.outs = JsonCommandField("out", self)
self.rtns = JsonCommandField("rtn", self)
self._len = 0
self._skipped = 0
# Flush empty history object to disk, overwriting previous data.
self.flush()

View file

@ -224,7 +224,18 @@ def _XH_HISTORY_SESSIONS():
}
_XH_MAIN_ACTIONS = {"show", "id", "file", "info", "diff", "gc", "flush"}
_XH_MAIN_ACTIONS = {
"show",
"id",
"file",
"info",
"diff",
"gc",
"flush",
"off",
"on",
"clear",
}
@functools.lru_cache()
@ -354,6 +365,17 @@ def _xh_create_parser():
# 'flush' subcommand
subp.add_parser("flush", help="flush the current history to disk")
# 'off' subcommand
subp.add_parser("off", help="history will not be saved for this session")
# 'on' subcommand
subp.add_parser(
"on", help="history will be saved for the rest of the session (default)"
)
# 'clear' subcommand
subp.add_parser("clear", help="one-time wipe of session history")
return p
@ -417,5 +439,17 @@ def history_main(
hf = hist.flush()
if isinstance(hf, threading.Thread):
hf.join()
elif ns.action == "off":
if hist.remember_history:
hist.clear()
hist.remember_history = False
print("History off", file=sys.stderr)
elif ns.action == "on":
if not hist.remember_history:
hist.remember_history = True
print("History on", file=sys.stderr)
elif ns.action == "clear":
hist.clear()
print("History cleared", file=sys.stderr)
else:
print("Unknown history action {}".format(ns.action), file=sys.stderr)

View file

@ -240,6 +240,8 @@ class SqliteHistory(History):
setattr(XH_SQLITE_CACHE, XH_SQLITE_CREATED_SQL_TBL, False)
def append(self, cmd):
if not self.remember_history:
return
envs = builtins.__xonsh__.env
inp = cmd["inp"].rstrip()
self.inps.append(inp)
@ -296,3 +298,18 @@ class SqliteHistory(History):
if blocking:
while self.gc.is_alive():
continue
def clear(self):
"""Clears the current session's history from both memory and disk."""
# Wipe memory
self.inps = []
self.rtns = []
self.outs = []
self.tss = []
# Wipe the current session's entries from the database.
sql = "DELETE FROM xonsh_history WHERE sessionid = ?"
with _xh_sqlite_get_conn(filename=self.filename) as conn:
c = conn.cursor()
_xh_sqlite_create_history_table(c)
c.execute(sql, (str(self.sessionid),))

View file

@ -181,7 +181,7 @@ class FStringAdaptor:
)
)
field_node = ast.Tuple(
elts=elts, ctx=ast.Load(), lineno=lineno, col_offset=col_offset,
elts=elts, ctx=ast.Load(), lineno=lineno, col_offset=col_offset
)
node.args[0] = field_node

View file

@ -24,26 +24,15 @@ RE_REMOVE_ANSI = LazyObject(
def _get_git_branch(q):
denv = builtins.__xonsh__.env.detype()
try:
branches = xt.decode_bytes(
subprocess.check_output(
["git", "branch"], env=denv, stderr=subprocess.DEVNULL
)
).splitlines()
cmd = ["git", "rev-parse", "--abbrev-ref", "HEAD"]
branch = xt.decode_bytes(
subprocess.check_output(cmd, env=denv, stderr=subprocess.DEVNULL)
)
branch = branch.splitlines()[0] or None
except (subprocess.CalledProcessError, OSError, FileNotFoundError):
q.put(None)
else:
for branch in branches:
if not branch.startswith("* "):
continue
elif branch.endswith(")"):
branch = branch.split()[-1][:-1]
else:
branch = branch.split()[-1]
q.put(branch)
break
else:
q.put(None)
q.put(branch)
def get_git_branch():
@ -59,7 +48,6 @@ def get_git_branch():
t.join(timeout=timeout)
try:
branch = q.get_nowait()
# branch = RE_REMOVE_ANSI.sub("", branch or "")
if branch:
branch = RE_REMOVE_ANSI.sub("", branch)
except queue.Empty:
@ -149,6 +137,15 @@ def _first_branch_timeout_message():
)
def _vc_has(binary):
""" This allows us to locate binaries after git only if necessary """
cmds = builtins.__xonsh__.commands_cache
if cmds.is_empty():
return bool(cmds.locate_binary(binary, ignore_alias=True))
else:
return bool(cmds.lazy_locate_binary(binary, ignore_alias=True))
def current_branch():
"""Gets the branch for a current working directory. Returns an empty string
if the cwd is not a repository. This currently only works for git and hg
@ -156,17 +153,9 @@ def current_branch():
'<branch-timeout>' is returned.
"""
branch = None
cmds = builtins.__xonsh__.commands_cache
# check for binary only once
if cmds.is_empty():
has_git = bool(cmds.locate_binary("git", ignore_alias=True))
has_hg = bool(cmds.locate_binary("hg", ignore_alias=True))
else:
has_git = bool(cmds.lazy_locate_binary("git", ignore_alias=True))
has_hg = bool(cmds.lazy_locate_binary("hg", ignore_alias=True))
if has_git:
if _vc_has("git"):
branch = get_git_branch()
if not branch and has_hg:
if not branch and _vc_has("hg"):
branch = get_hg_branch()
if isinstance(branch, subprocess.TimeoutExpired):
branch = "<branch-timeout>"
@ -175,19 +164,31 @@ def current_branch():
def _git_dirty_working_directory(q, include_untracked):
status = None
denv = builtins.__xonsh__.env.detype()
try:
cmd = ["git", "status", "--porcelain"]
# Borrowed from this conversation
# https://github.com/sindresorhus/pure/issues/115
if include_untracked:
cmd.append("--untracked-files=normal")
cmd = [
"git",
"status",
"--porcelain",
"--quiet",
"--untracked-files=normal",
]
else:
cmd.append("--untracked-files=no")
status = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, env=denv)
unindexed = ["git", "diff", "--no-ext-diff", "--quiet"]
indexed = unindexed + ["--cached", "HEAD"]
cmd = unindexed + ["||"] + indexed
child = subprocess.run(cmd, stderr=subprocess.DEVNULL, env=denv)
# "--quiet" git commands imply "--exit-code", which returns:
# 1 if there are differences
# 0 if there are no differences
dwd = bool(child.returncode)
except (subprocess.CalledProcessError, OSError, FileNotFoundError):
q.put(None)
if status is not None:
return q.put(bool(status))
else:
q.put(dwd)
def git_dirty_working_directory(include_untracked=False):
@ -241,10 +242,9 @@ def dirty_working_directory():
None. Currently supports git and hg.
"""
dwd = None
cmds = builtins.__xonsh__.commands_cache
if cmds.lazy_locate_binary("git", ignore_alias=True):
if _vc_has("git"):
dwd = git_dirty_working_directory()
if cmds.lazy_locate_binary("hg", ignore_alias=True) and dwd is None:
if dwd is None and _vc_has("hg"):
dwd = hg_dirty_working_directory()
return dwd

View file

@ -206,6 +206,15 @@ def load_xonsh_bindings() -> KeyBindingsBase:
env = builtins.__xonsh__.env
event.cli.current_buffer.insert_text(env.get("INDENT"))
@handle(Keys.Tab, filter=~tab_insert_indent)
def start_complete(event):
"""If starting completions, automatically move to first option"""
buff = event.app.current_buffer
if buff.complete_state:
buff.complete_next()
else:
buff.start_completion(select_first=True)
@handle(Keys.ControlX, Keys.ControlE, filter=~has_selection)
def open_editor(event):
""" Open current buffer in editor """

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""The prompt_toolkit based xonsh shell."""
import os
import re
import sys
import builtins
from types import MethodType
@ -37,6 +38,7 @@ from prompt_toolkit.styles.pygments import (
)
ANSI_OSC_PATTERN = re.compile("\x1b].*?\007")
Token = _TokenType()
events.transmogrify("on_ptk_create", "LoadEvent")
@ -76,6 +78,19 @@ def tokenize_ansi(tokens):
return ansi_tokens
def remove_ansi_osc(prompt):
"""Removes the ANSI OSC escape codes - ``prompt_toolkit`` does not support them.
Some terminal emulators - like iTerm2 - uses them for various things.
See: https://www.iterm2.com/documentation-escape-codes.html
"""
osc_tokens = ANSI_OSC_PATTERN.findall(prompt)
prompt = ANSI_OSC_PATTERN.sub("", prompt)
return prompt, osc_tokens
class PromptToolkitShell(BaseShell):
"""The xonsh shell for prompt_toolkit v2 and later."""
@ -250,10 +265,21 @@ class PromptToolkitShell(BaseShell):
p = self.prompt_formatter(p)
except Exception: # pylint: disable=broad-except
print_exception()
p, osc_tokens = remove_ansi_osc(p)
toks = partial_color_tokenize(p)
if self._first_prompt:
carriage_return()
self._first_prompt = False
# handle OSC tokens
for osc in osc_tokens:
if osc[2:4] == "0;":
builtins.__xonsh__.env["TITLE"] = osc[4:-1]
else:
print(osc, file=sys.__stdout__, flush=True)
self.settitle()
return tokenize_ansi(PygmentsTokens(toks))

View file

@ -882,13 +882,10 @@ def suggest_commands(cmd, env, aliases):
if levenshtein(alias.lower(), cmd, thresh) < thresh:
suggested[alias] = "Alias"
for path in filter(os.path.isdir, env.get("PATH")):
for _file in executables_in(path):
if (
_file not in suggested
and levenshtein(_file.lower(), cmd, thresh) < thresh
):
suggested[_file] = "Command ({0})".format(os.path.join(path, _file))
for _cmd in builtins.__xonsh__.commands_cache.all_commands:
if _cmd not in suggested:
if levenshtein(_cmd.lower(), cmd, thresh) < thresh:
suggested[_cmd] = "Command ({0})".format(_cmd)
suggested = collections.OrderedDict(
sorted(

View file

@ -20,7 +20,7 @@
{"name": "argcomplete",
"package": "xontrib-argcomplete",
"url": "https://github.com/anki-code/xontrib-argcomplete",
"description": ["Adding support of kislyuk/argcomplete to xonsh."]
"description": ["Argcomplete support to tab completion of python and xonsh scripts in xonsh."]
},
{"name": "autojump",
"package": "xontrib-autojump",
@ -222,7 +222,7 @@
{"name": "pipeliner",
"package": "xontrib-pipeliner",
"url": "https://github.com/anki-code/xontrib-pipeliner",
"description": ["Easily process the lines using pipes."]
"description": ["Let your pipe lines flow thru the Python code in xonsh."]
},
{"name": "vox",
"package": "xonsh",

View file

@ -159,13 +159,7 @@ def _ul_add_action(actions, opt, res_type, stderr):
actions.append(
[
_ul_show,
{
"res": r[0],
"res_type": res_type,
"desc": r[3],
"unit": r[4],
"opt": opt,
},
{"res": r[0], "res_type": res_type, "desc": r[3], "unit": r[4], "opt": opt},
]
)
return True

View file

@ -28,3 +28,14 @@ def custom_keybindings(bindings, **kw):
pos = buff.document.find_next_word_ending(count=event.arg, WORD=True)
if pos:
buff.cursor_position += pos
@bindings.add(Keys.ShiftDelete)
def shift_delete(event):
buff = event.current_buffer
startpos, endpos = buff.document.find_boundaries_of_current_word(WORD=True)
startpos = buff.cursor_position + startpos - 1
startpos = 0 if startpos < 0 else startpos
endpos = buff.cursor_position + endpos
endpos = endpos + 1 if startpos == 0 else endpos
buff.text = buff.text[:startpos] + buff.text[endpos:]
buff.cursor_position = startpos