Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,42 @@ require("wiremux").send({

Each item in the picker can have:

| Field | What it does | Example |
| --------- | ------------------------------- | ------------------------------------------------ |
| `value` | **(Required)** The text to send | `"Explain {file}"` |
| `label` | Display name in the picker | `"Explain file"` |
| `submit` | Auto-press Enter after sending | `true` (useful for commands) |
| `visible` | Show/hide this item dynamically | `function() return vim.bo.filetype == "lua" end` |
| Field | What it does | Example |
| ----------- | ------------------------------------- | ------------------------------------------------ |
| `value` | **(Required)** The text to send | `"Explain {file}"` |
| `label` | Display name in the picker | `"Explain file"` |
| `submit` | Auto-press Enter after sending | `true` (useful for commands) |
| `visible` | Show/hide this item dynamically | `function() return vim.bo.filetype == "lua" end` |
| `pre_keys` | Keystrokes to send before pasting | `"C-c"`, `{"C-c", "i"}` |
| `post_keys` | Keystrokes to send after pasting | `"Escape"`, `{"Escape", "Enter"}` |

### Sending Keystrokes Before/After

Some TUI apps need keystrokes sent before/after the pasted text — for example, `C-c` to cancel any in-progress input, or `Escape` to return to a neutral state after pasting:

```lua
-- Cancel current input before pasting, return to normal state after
require("wiremux").send({
value = "my text",
pre_keys = { "C-c" },
post_keys = { "Escape" },
})

-- Vim-mode editors: enter insert mode before pasting, Escape after
require("wiremux").send({
value = "my text",
pre_keys = { "i" },
post_keys = { "Escape" },
})

-- Per-call opts: all items in this keymap use the same keys
require("wiremux").send({
{ label = "Explain", value = "Explain {this}" },
{ label = "Review", value = "Review {changes}" },
}, { pre_keys = { "i" }, target = "claude" })
```

Item-level `pre_keys`/`post_keys` override opts-level when both are set.

## Placeholders

Expand Down
30 changes: 30 additions & 0 deletions doc/wiremux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ SendItem fields ~
| title | string (optional) | Custom tmux window / zellij tab name |
| submit | boolean (optional) | Auto-submit after sending |
| visible | boolean or function | Show/hide item based on condition |
| pre_keys | string or string[] | Keystrokes to send before pasting |
| post_keys | string or string[] | Keystrokes to send after pasting |

Commands with auto-submit:
>lua
Expand Down Expand Up @@ -128,6 +130,34 @@ Conditional visibility:
})
<

Sending keystrokes before/after ~
*wiremux-howto-pre-post-keys*

Some TUI apps need keystrokes sent before/after the pasted text. For example,
`C-c` to cancel in-progress input, or `Escape` to return to a neutral state:
>lua
-- Cancel current input before pasting, return to normal state after
require("wiremux").send({
value = "my text",
pre_keys = { "C-c" },
post_keys = { "Escape" },
})

-- Vim-mode editors: enter insert mode before pasting, Escape after
require("wiremux").send({
value = "my text",
pre_keys = { "i" },
post_keys = { "Escape" },
})

-- Per-call opts (fallback for all items)
require("wiremux").send({
{ label = "Explain", value = "Explain {this}" },
{ label = "Review", value = "Review {changes}" },
}, { pre_keys = { "i" }, target = "claude" })
<
Item-level `pre_keys`/`post_keys` override opts-level when both are set.

Dynamic labels for targets ~

You can use a function for `label` to create dynamic display names:
Expand Down
49 changes: 41 additions & 8 deletions lua/wiremux/action/send.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ local M = {}
---@field submit? boolean Auto-submit after sending (default: false)
---@field visible? boolean|fun(): boolean Show this item in picker (default: true)
---@field title? string Custom tmux window / zellij tab name when creating
---@field pre_keys? string|string[] Keystrokes to send before pasting (e.g. {"C-c"}, {"i"})
---@field post_keys? string|string[] Keystrokes to send after pasting (e.g. {"Escape"})

---Check if item should be visible
---@param item wiremux.action.SendItem
Expand Down Expand Up @@ -50,12 +52,20 @@ local function build_picker_items(items)
return picker_items
end

---Append "Enter" to post_keys when submit is enabled
---@param post_keys? string|string[]
---@return string[]
local function append_submit(post_keys)
local keys = type(post_keys) == "table" and { unpack(post_keys) } or (post_keys and { post_keys } or {})
table.insert(keys, "Enter")
return keys
end

---Execute the send action with expanded text
---@param expanded string The text with placeholders expanded
---@param opts wiremux.config.ActionConfig
---@param submit boolean Whether to auto-submit
---@param title? string Custom tmux window / zellij tab name when creating
local function do_send(expanded, opts, submit, title)
---@param send_opts { title?: string, pre_keys?: string|string[], post_keys?: string|string[] }
local function do_send(expanded, opts, send_opts)
local config = require("wiremux.config")
local action = require("wiremux.core.action")
local backend = require("wiremux.backend").get()
Expand All @@ -65,6 +75,7 @@ local function do_send(expanded, opts, submit, title)
end

local focus = opts.focus or config.opts.actions.send.focus
local backend_opts = vim.tbl_extend("force", send_opts, { focus = focus })

action.run({
prompt = "Send to",
Expand All @@ -74,18 +85,18 @@ local function do_send(expanded, opts, submit, title)
target = opts.target,
}, {
on_targets = function(targets, state)
backend.send(expanded, targets, { focus = focus, submit = submit }, state)
backend.send(expanded, targets, backend_opts, state)
end,
on_definition = function(name, def, state)
local has_own_cmd = def.cmd ~= nil
local modified_def = vim.tbl_extend("force", {}, def, {
cmd = def.cmd or expanded,
title = title,
title = send_opts.title,
})
local inst = backend.create(name, modified_def, state)
if inst and has_own_cmd then
backend.wait_for_ready(inst, { timeout_ms = def.startup_timeout }, function()
backend.send(expanded, { inst }, { focus = focus, submit = submit }, state)
backend.send(expanded, { inst }, backend_opts, state)
end)
end
end,
Expand All @@ -110,7 +121,18 @@ local function send_single_item(item, opts)
submit = opts.submit or config.opts.actions.send.submit
end

do_send(expanded, opts, submit, item.title)
local pre_keys = item.pre_keys or opts.pre_keys
local post_keys = item.post_keys or opts.post_keys

if submit then
post_keys = append_submit(post_keys)
end

do_send(expanded, opts, {
title = item.title,
pre_keys = pre_keys,
post_keys = post_keys,
})
end

---Send from send library (picker)
Expand Down Expand Up @@ -154,7 +176,18 @@ local function send_from_library(items, opts)
submit = opts.submit or config.opts.actions.send.submit
end

do_send(expanded[item] or item.value, opts, submit, item.title)
local pre_keys = item.pre_keys or opts.pre_keys
local post_keys = item.post_keys or opts.post_keys

if submit then
post_keys = append_submit(post_keys)
end

do_send(expanded[item] or item.value, opts, {
title = item.title,
pre_keys = pre_keys,
post_keys = post_keys,
})
end)
end

Expand Down
14 changes: 9 additions & 5 deletions lua/wiremux/backend/tmux/operation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ local function get_focus_cmds(target)
end

---@param targets wiremux.Instance[]
local function submit(targets)
---@param opts { post_keys: string|string[] }
local function _send_deferred(targets, opts)
vim.defer_fn(function()
local batch = {}
for _, t in ipairs(targets) do
table.insert(batch, action.send_keys(t.id, "Enter"))
table.insert(batch, action.send_keys(t.id, opts.post_keys))
end
client.execute(batch)
end, 300)
end

---@param text string
---@param targets wiremux.Instance[]
---@param opts? { focus?: boolean, submit?: boolean }
---@param opts? { focus?: boolean, pre_keys?: string|string[], post_keys?: string|string[] }
---@param st wiremux.State
function M.send(text, targets, opts, st)
opts = opts or {}
Expand All @@ -35,6 +36,9 @@ function M.send(text, targets, opts, st)
local batch = { action.load_buffer(BUFFER_NAME) }

for _, t in ipairs(targets) do
if opts.pre_keys then
table.insert(batch, action.send_keys(t.id, opts.pre_keys))
end
table.insert(batch, action.paste_buffer(BUFFER_NAME, t.id))
end

Expand All @@ -58,8 +62,8 @@ function M.send(text, targets, opts, st)
return
end

if opts.submit then
submit(targets)
if opts.post_keys then
_send_deferred(targets, opts)
end

notify.debug("send: sent to %d targets", #targets)
Expand Down
2 changes: 2 additions & 0 deletions lua/wiremux/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ local M = {}
---@field submit? boolean
---@field filter? wiremux.config.FilterConfig
---@field target? string Target definition name. Sends directly to matching instance, auto-creates if none exist.
---@field pre_keys? string|string[] Keystrokes to send before action (e.g. {"C-c"}, {"i"})
---@field post_keys? string|string[] Keystrokes to send after action (e.g. {"Escape"})

---@class wiremux.target.definition
---@field cmd? string Command to run in the new pane/window
Expand Down
Loading
Loading