From 950dde2bacad72a12395fe0df718e536f51a4dad Mon Sep 17 00:00:00 2001 From: Amolith Date: Sat, 24 Jan 2026 10:22:38 -0700 Subject: [PATCH] fix(shell): capture stdout only for gum commands gum draws its UI to stderr and outputs selections to stdout. The existing run_cmd() merges stderr into stdout with 2>&1, which pipes the UI away from the terminal and breaks interactivity. Add run_cmd_interactive() that captures stdout only, leaving stderr connected to the terminal. Update all gum choose/table calls to use this new function. Assisted-by: Claude Opus 4.5 via Amp Amp-Thread-ID: https://ampcode.com/threads/T-019bf101-334c-71af-bfae-eaa5eefdc17d Co-authored-by: Amp --- dist/wt | 34 ++++++++++++++++++++++++---------- src/wt/cmd/clone.lua | 4 ++-- src/wt/cmd/list.lua | 8 ++------ src/wt/cmd/new.lua | 4 ++-- src/wt/shell.lua | 18 ++++++++++++++++++ 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/dist/wt b/dist/wt index 0b8def69e4843f15eed33d30520e0a4622f5dfc6..06a05907e88efafdf0745fad3b99439ea5d198f6 100755 --- a/dist/wt +++ b/dist/wt @@ -71,6 +71,24 @@ function M.run_cmd(cmd) return output, code or exit.EXIT_SYSTEM_ERROR end +---Execute command capturing stdout only (stderr stays on terminal) +---Use for interactive commands like gum that draw UI to stderr +---@param cmd string +---@return string output +---@return integer code +function M.run_cmd_interactive(cmd) + local handle = io.popen(cmd) + if not handle then + return "", exit.EXIT_SYSTEM_ERROR + end + local output = handle:read("*a") or "" + local success, _, code = handle:close() + if success then + return output, 0 + end + return output, code or exit.EXIT_SYSTEM_ERROR +end + ---Execute command silently, return success boolean ---@param cmd string ---@return boolean success @@ -1065,7 +1083,7 @@ function M.cmd_clone(args) local input = table.concat(keys, "\n") local choose_type = own and "" or " --no-limit" local cmd = "echo '" .. input .. "' | gum choose" .. choose_type - output, code = shell.run_cmd(cmd) + output, code = shell.run_cmd_interactive(cmd) if code == 0 and output ~= "" then for line in output:gmatch("[^\n]+") do table.insert(selected_remotes, line) @@ -1084,7 +1102,7 @@ function M.cmd_clone(args) local input = table.concat(keys, "\n") local choose_type = own and "" or " --no-limit" local cmd = "echo '" .. input .. "' | gum choose" .. choose_type - output, code = shell.run_cmd(cmd) + output, code = shell.run_cmd_interactive(cmd) if code == 0 and output ~= "" then for line in output:gmatch("[^\n]+") do table.insert(selected_remotes, line) @@ -1332,7 +1350,7 @@ function M.cmd_new(args) if #keys > 0 then local input = table.concat(keys, "\n") local cmd = "echo '" .. input .. "' | gum choose --no-limit" - local output, code = shell.run_cmd(cmd) + local output, code = shell.run_cmd_interactive(cmd) if code == 0 and output ~= "" then for line in output:gmatch("[^\n]+") do table.insert(selected_remotes, line) @@ -1351,7 +1369,7 @@ function M.cmd_new(args) if #keys > 0 then local input = table.concat(keys, "\n") local cmd = "echo '" .. input .. "' | gum choose --no-limit" - local output, code = shell.run_cmd(cmd) + local output, code = shell.run_cmd_interactive(cmd) if code == 0 and output ~= "" then for line in output:gmatch("[^\n]+") do table.insert(selected_remotes, line) @@ -1837,12 +1855,8 @@ function M.cmd_list() local table_input = "Path,Branch,HEAD,Status\n" .. table.concat(rows, "\n") table_input = table_input:gsub("EOF", "eof") local table_cmd = "gum table --print <<'EOF'\n" .. table_input .. "\nEOF" - local table_handle = io.popen(table_cmd, "r") - if not table_handle then - return - end - io.write(table_handle:read("*a") or "") - table_handle:close() + local table_output, _ = shell.run_cmd_interactive(table_cmd) + io.write(table_output) end return M diff --git a/src/wt/cmd/clone.lua b/src/wt/cmd/clone.lua index 86c972598871a5dbde586abdada31ed3ec03f708..ed81cccb06955dd8ff5cb95f46a34171d395f07c 100644 --- a/src/wt/cmd/clone.lua +++ b/src/wt/cmd/clone.lua @@ -116,7 +116,7 @@ function M.cmd_clone(args) local input = table.concat(keys, "\n") local choose_type = own and "" or " --no-limit" local cmd = "echo '" .. input .. "' | gum choose" .. choose_type - output, code = shell.run_cmd(cmd) + output, code = shell.run_cmd_interactive(cmd) if code == 0 and output ~= "" then for line in output:gmatch("[^\n]+") do table.insert(selected_remotes, line) @@ -135,7 +135,7 @@ function M.cmd_clone(args) local input = table.concat(keys, "\n") local choose_type = own and "" or " --no-limit" local cmd = "echo '" .. input .. "' | gum choose" .. choose_type - output, code = shell.run_cmd(cmd) + output, code = shell.run_cmd_interactive(cmd) if code == 0 and output ~= "" then for line in output:gmatch("[^\n]+") do table.insert(selected_remotes, line) diff --git a/src/wt/cmd/list.lua b/src/wt/cmd/list.lua index cbec797e771b74600928f26a0f48d61217bc5a27..d22fd321b113d868893939d3ccc387907dfaa169 100644 --- a/src/wt/cmd/list.lua +++ b/src/wt/cmd/list.lua @@ -71,12 +71,8 @@ function M.cmd_list() local table_input = "Path,Branch,HEAD,Status\n" .. table.concat(rows, "\n") table_input = table_input:gsub("EOF", "eof") local table_cmd = "gum table --print <<'EOF'\n" .. table_input .. "\nEOF" - local table_handle = io.popen(table_cmd, "r") - if not table_handle then - return - end - io.write(table_handle:read("*a") or "") - table_handle:close() + local table_output, _ = shell.run_cmd_interactive(table_cmd) + io.write(table_output) end return M diff --git a/src/wt/cmd/new.lua b/src/wt/cmd/new.lua index bd0e5156306d574529c4e1219ffe08783ea3ebed..c074b5e1fb92b78bff666f3788c2309c57fd71d3 100644 --- a/src/wt/cmd/new.lua +++ b/src/wt/cmd/new.lua @@ -87,7 +87,7 @@ function M.cmd_new(args) if #keys > 0 then local input = table.concat(keys, "\n") local cmd = "echo '" .. input .. "' | gum choose --no-limit" - local output, code = shell.run_cmd(cmd) + local output, code = shell.run_cmd_interactive(cmd) if code == 0 and output ~= "" then for line in output:gmatch("[^\n]+") do table.insert(selected_remotes, line) @@ -106,7 +106,7 @@ function M.cmd_new(args) if #keys > 0 then local input = table.concat(keys, "\n") local cmd = "echo '" .. input .. "' | gum choose --no-limit" - local output, code = shell.run_cmd(cmd) + local output, code = shell.run_cmd_interactive(cmd) if code == 0 and output ~= "" then for line in output:gmatch("[^\n]+") do table.insert(selected_remotes, line) diff --git a/src/wt/shell.lua b/src/wt/shell.lua index fddecfc589181000d1e4d004626e072e403f2654..2330286d93cc64dcb63aefc24e27492ba4b4363d 100644 --- a/src/wt/shell.lua +++ b/src/wt/shell.lua @@ -24,6 +24,24 @@ function M.run_cmd(cmd) return output, code or exit.EXIT_SYSTEM_ERROR end +---Execute command capturing stdout only (stderr stays on terminal) +---Use for interactive commands like gum that draw UI to stderr +---@param cmd string +---@return string output +---@return integer code +function M.run_cmd_interactive(cmd) + local handle = io.popen(cmd) + if not handle then + return "", exit.EXIT_SYSTEM_ERROR + end + local output = handle:read("*a") or "" + local success, _, code = handle:close() + if success then + return output, 0 + end + return output, code or exit.EXIT_SYSTEM_ERROR +end + ---Execute command silently, return success boolean ---@param cmd string ---@return boolean success