mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 08:24:40 +01:00
Merge branch 'master' into environment_fixes
This commit is contained in:
commit
88bba37c3a
29 changed files with 580 additions and 74 deletions
|
@ -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
23
news/ansi_osc.rst
Normal 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>
|
23
news/cache-executables-in.rst
Normal file
23
news/cache-executables-in.rst
Normal 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>
|
23
news/history_testing-ead.rst
Normal file
23
news/history_testing-ead.rst
Normal 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
23
news/name_in_error.rst
Normal 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
23
news/prompt-speed.rst
Normal 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>
|
25
news/ptk-complete-select-first.rst
Normal file
25
news/ptk-complete-select-first.rst
Normal 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
23
news/shift_del.rst
Normal 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>
|
23
news/windows_app_execution_aliases.rst
Normal file
23
news/windows_app_execution_aliases.rst
Normal 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
23
news/xontrib_descr.rst
Normal 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>
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 """
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue