summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthieu LAURENT <matthieu.laurent69@protonmail.com>2024-01-29 13:17:36 +0100
committerGitHub <noreply@github.com>2024-01-29 12:17:36 +0000
commitc56f8ff736b8369b9b5cb3bdb42718396247f212 (patch)
tree264200175dab82272878a135d9e7f31b02f65067
parent0faf414cd958137ac60a1f37288994f3a1441780 (diff)
Add xonsh support (#1375)
* Add basic xonsh support * Add init xonsh command * Add Xonsh install instructions in docs * Add xonsh ctrl-R search * update xonsh script and instructions Summary of changes: * Added duration to postcommand hook * Switched main search operation to use `subproccess.run()` rather than running as an xonsh shell command - this a) allows us to capture stderr without needing a temporary file and b) avoids a weird broken-buffer state that results from running a fullscreen TUI and then programmatically editing the buffer * Added support for immediately executing chosen command via `__atuin_accept__:` (like bash/zsh/fish) * strip newline from command before sending to atuin * Add basic xonsh support * Add init xonsh command * Add xonsh ctrl-R search * Remove advanced-install guide (was accidentally re-added during rebase) * Clean up Xonsh doesn't import private functions into the local namespace when sourcing a file * Add xonsh ro readme * Respect ATUIN_NOBIND * Format with black, and improve PEP8 compliance * Add up search * Format rust code --------- Co-authored-by: Joseph Montanaro <jfmonty2@gmail.com>
-rw-r--r--README.md9
-rw-r--r--atuin-common/src/utils.rs5
-rw-r--r--atuin/src/command/client/search/interactive.rs4
-rw-r--r--atuin/src/command/init.rs21
-rw-r--r--atuin/src/shell/atuin.xsh67
5 files changed, 105 insertions, 1 deletions
diff --git a/README.md b/README.md
index 1b893f49..dff561e2 100644
--- a/README.md
+++ b/README.md
@@ -89,6 +89,7 @@ I wanted to. And I **really** don't want to.
- bash
- fish
- nushell
+- xonsh
## Community
@@ -327,6 +328,14 @@ Add to `config.nu`:
source ~/.local/share/atuin/init.nu
```
+### Xonsh
+
+Add
+```
+execx($(atuin init xonsh))
+```
+to the end of your `~/.xonshrc`
+
# Contributors
<a href="https://github.com/atuinsh/atuin/graphs/contributors">
diff --git a/atuin-common/src/utils.rs b/atuin-common/src/utils.rs
index 87c4b14c..889c7811 100644
--- a/atuin-common/src/utils.rs
+++ b/atuin-common/src/utils.rs
@@ -101,6 +101,11 @@ pub fn is_bash() -> bool {
env::var("ATUIN_SHELL_BASH").is_ok()
}
+pub fn is_xonsh() -> bool {
+ // only set on xonsh
+ env::var("ATUIN_SHELL_XONSH").is_ok()
+}
+
/// Extension trait for anything that can behave like a string to make it easy to escape control
/// characters.
///
diff --git a/atuin/src/command/client/search/interactive.rs b/atuin/src/command/client/search/interactive.rs
index 1fc37b57..27c82f2c 100644
--- a/atuin/src/command/client/search/interactive.rs
+++ b/atuin/src/command/client/search/interactive.rs
@@ -967,7 +967,9 @@ pub async fn history(
match result {
InputAction::Accept(index) if index < results.len() => {
let mut command = results.swap_remove(index).command;
- if accept && (utils::is_zsh() || utils::is_fish() || utils::is_bash()) {
+ if accept
+ && (utils::is_zsh() || utils::is_fish() || utils::is_bash() || utils::is_xonsh())
+ {
command = String::from("__atuin_accept__:") + &command;
}
diff --git a/atuin/src/command/init.rs b/atuin/src/command/init.rs
index eb841c9f..3eb50d68 100644
--- a/atuin/src/command/init.rs
+++ b/atuin/src/command/init.rs
@@ -23,6 +23,8 @@ pub enum Shell {
Fish,
/// Nu setup
Nu,
+ /// Xonsh setup
+ Xonsh,
}
impl Cmd {
@@ -140,12 +142,31 @@ bind -M insert \e\[A _atuin_bind_up";
}
}
+ fn init_xonsh(&self) {
+ let base = include_str!("../shell/atuin.xsh");
+ let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() {
+ (false, false)
+ } else {
+ (!self.disable_ctrl_r, !self.disable_up_arrow)
+ };
+ println!(
+ "_ATUIN_BIND_CTRL_R={}",
+ if bind_ctrl_r { "True" } else { "False" }
+ );
+ println!(
+ "_ATUIN_BIND_UP_ARROW={}",
+ if bind_up_arrow { "True" } else { "False" }
+ );
+ println!("{base}");
+ }
+
pub fn run(self) {
match self.shell {
Shell::Zsh => self.init_zsh(),
Shell::Bash => self.init_bash(),
Shell::Fish => self.init_fish(),
Shell::Nu => self.init_nu(),
+ Shell::Xonsh => self.init_xonsh(),
}
}
}
diff --git a/atuin/src/shell/atuin.xsh b/atuin/src/shell/atuin.xsh
new file mode 100644
index 00000000..5df26b26
--- /dev/null
+++ b/atuin/src/shell/atuin.xsh
@@ -0,0 +1,67 @@
+import subprocess
+from prompt_toolkit.keys import Keys
+
+$ATUIN_SESSION=$(atuin uuid).rstrip('\n')
+
+
+@events.on_precommand
+def _atuin_precommand(cmd: str):
+ cmd = cmd.rstrip("\n")
+ $ATUIN_HISTORY_ID = $(atuin history start -- @(cmd)).rstrip("\n")
+
+
+@events.on_postcommand
+def _atuin_postcommand(cmd: str, rtn: int, out, ts):
+ if "ATUIN_HISTORY_ID" not in ${...}:
+ return
+
+ duration = ts[1] - ts[0]
+ # Duration is float representing seconds, but atuin expects integer of nanoseconds
+ nanos = round(duration * 10 ** 9)
+ with ${...}.swap(ATUIN_LOG="error"):
+ # This causes the entire .xonshrc to be re-executed, which is incredibly slow
+ # This happens when using a subshell and using output redirection at the same time
+ # For more details, see https://github.com/xonsh/xonsh/issues/5224
+ # (atuin history end --exit @(rtn) -- $ATUIN_HISTORY_ID &) > /dev/null 2>&1
+ atuin history end --exit @(rtn) --duration @(nanos) -- $ATUIN_HISTORY_ID > /dev/null 2>&1
+ del $ATUIN_HISTORY_ID
+
+
+def _search(event, extra_args: list[str]):
+ buffer = event.current_buffer
+ cmd = ["atuin", "search", "--interactive", *extra_args, "--", buffer.text]
+ # We need to explicitly pass in xonsh env, in case user has set XDG_HOME or something else that matters
+ env = ${...}.detype()
+ env["ATUIN_SHELL_XONSH"] = "t"
+
+ p = subprocess.run(cmd, stderr=subprocess.PIPE, encoding="utf-8", env=env)
+ result = p.stderr.rstrip("\n")
+ # redraw prompt - necessary if atuin is configured to run inline, rather than fullscreen
+ event.cli.renderer.erase()
+
+ if not result:
+ return
+
+ buffer.reset()
+ if result.startswith("__atuin_accept__:"):
+ buffer.insert_text(result[17:])
+ buffer.validate_and_handle()
+ else:
+ buffer.insert_text(result)
+
+
+@events.on_ptk_create
+def _custom_keybindings(bindings, **kw):
+ @bindings.add(Keys.ControlR, filter=_ATUIN_BIND_CTRL_R)
+ def r_search(event):
+ _search(event, extra_args=[])
+
+ @bindings.add(Keys.Up, filter=_ATUIN_BIND_UP_ARROW)
+ def up_search(event):
+ # Only trigger if the buffer is a single line
+ if not '\n' in buffer.text:
+ _search(event, extra_args=["--shell-up-key-binding"])
+ return
+
+ # Run the default behavior for up arrow
+ event.current_buffer.auto_up(count=event.arg)