From 7176755e314dc724dc0a11ad8924fbdeeb4dd014 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Fri, 20 Mar 2026 11:39:46 +0000 Subject: [PATCH 1/3] wire up shell_exec to run a shell command from the command bar --- src/controller.cpp | 18 ++++++++++++++++++ src/view.cpp | 1 + 2 files changed, 19 insertions(+) diff --git a/src/controller.cpp b/src/controller.cpp index 8b78bb0..50898f3 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -515,6 +515,24 @@ bool Controller::parse_command() { // Empty command if (cmd.size() < 2) { return false; } + // Run shell command + if (cmd.at(1) == '!') { + const std::optional opt_resp = shell_exec(cmd.substr(2, cmd.size())); + if (!opt_resp.has_value()) { return false; } + const Response resp = opt_resp.value(); + if (resp.out.size()) { + std::size_t nlPos = resp.out.find('\n'); + std::string msg = (nlPos == std::string::npos) ? resp.out : resp.out.substr(0, nlPos); + view.display_message(msg, rawterm::Colors::green); + } else { + std::size_t nlPos = resp.err.find('\n'); + std::string msg = (nlPos == std::string::npos) ? resp.err : resp.err.substr(0, nlPos); + view.display_message(msg, rawterm::Colors::red); + } + + return false; + } + if (std::isdigit(cmd.at(1))) { const unsigned int offset = uint32_t(std::stoi(cmd.substr(1, cmd.size()))); view.set_current_line(offset); diff --git a/src/view.cpp b/src/view.cpp index 5a5a1bb..6764243 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -336,6 +336,7 @@ const std::string View::render_status_bar() const { return prev_pos; } +// TODO: Modify this to take a string_view instead void View::display_message(std::string msg, std::optional color) { rawterm::Pos prev_pos = cur; cur.move({view_size.vertical, 1}); From f7647961996a9caf9500922cb12ebd0ac5b10f5a Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Fri, 20 Mar 2026 14:04:08 +0000 Subject: [PATCH 2/3] tests for running shell commands --- tests/integration/command_mode_test.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/integration/command_mode_test.py b/tests/integration/command_mode_test.py index 00680cb..d72f1c8 100644 --- a/tests/integration/command_mode_test.py +++ b/tests/integration/command_mode_test.py @@ -24,7 +24,7 @@ def test_quit_with_modified_buffer(r: TmuxRunner): r.iris_cmd("q") err_line: str = r.color_screenshot()[-1] assert "Unsaved changes. Use `;q!` to discard" in err_line - assert "\x1b[49m" in err_line # red text + assert "255;0;0" in err_line # red text @setup("tests/fixture/test_file_2.txt", multi_file=True) @@ -399,3 +399,19 @@ def test_find_next_str_repeat_input_command(r: TmuxRunner): r.iris_cmd("f") r.await_cursor_pos(12, 10) assert r.await_statusbar_parts()[-1] == "46:7" + + +@setup() +def test_run_shell_cmd_stdout(r: TmuxRunner): + r.iris_cmd("!echo 'hello'") + msg: str = r.color_screenshot()[-1] + assert "hello" in msg + assert "0;128;0" in msg # green text + + +@setup() +def test_run_shell_cmd_stderr(r: TmuxRunner): + r.iris_cmd("!echo 'hello' 1>&2") + msg: str = r.color_screenshot()[-1] + assert "hello" in msg + assert "255;0;0" in msg # red text From aea5ba7d31ee7b0bf4e11d5287932ee0cdd7ffb1 Mon Sep 17 00:00:00 2001 From: Ttibsi Date: Tue, 24 Mar 2026 08:29:29 +0000 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7028c74..2a5f0e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ### main / head +* Implement `;!` to execute a shell command from the command bar + ### v0.0.3 | 17/Mar/2026 * Use `;e` to open a different file from within iris * Keyboard commands `tt`, `tn`, `tp` to create and switch between tabs