Add {input} placeholder for interactive user input#14
Add {input} placeholder for interactive user input#14jxyyz wants to merge 4 commits intoMSmaili:mainfrom
{input} placeholder for interactive user input#14Conversation
Add a new context/input module that handles {input}, {input:Prompt},
and {input:Prompt:default} placeholders in send templates. These
placeholders prompt the user via vim.ui.input() at send time, enabling
dynamic values like branch names or arguments.
- Add context/input.lua with parse, find, replace, and resolve functions
- Integrate input resolution into action/send's send_single_item flow
- Skip {input} keys during sync context.expand() to avoid errors
Add unit tests for context.input module (parse, find, has_inputs, replace, resolve) and integration tests for send action with input placeholders. Update helpers_send with context.input mock.
Document the {input} placeholder syntax in both README.md and
doc/wiremux.txt with usage examples.
- Add interactive input section to README with syntax table, code
examples, and behavioral notes
- Add matching section to vimdoc with {input} syntax list and examples
|
Hello @jxyyz, thanks for the contribution, really appreciate it. It seems like an interesting and nice idea. This is not a small change/addition. I will take a look, play around with it, and analyze. Most probably by the end of this week, will contact back. Sorry for taking so long, I am a bit busier this week. Once again, thanks. |
|
Cool, that's the appropriate path. If you were not happy with some design decisions, I'm open to discussing them and adjusting my code. In the meantime, I'll open some issues with bugs/possible enhancements I encountered while using this tool. |
|
Back ref: I think it would be beneficial to resolve it before merging this PR (or via this PR). |
|
I've been thinking about this quite a bit. My concern with {input} is that it mixes two different concepts: data placeholders (like {file}, {selection}) and user interaction (prompting for input). When you have multiple {input} placeholders scattered in your text, it's not immediately clear when or in what order you'll be prompted, especially if you're reusing the same send command later. It feels that I have an alternative proposal that might address the same use case. Would love to hear if this would work for you. What is my proposal:I'm thinking about an option we can call When edit = true, a floating buffer opens with the text (placeholders still unresolved). You can:
Why I think this has some pros over {input} placeholder:
Also, in the future, we could just make: What do you think? Does this approach work for your use case? The main difference is that instead of inline {input} prompts, you get a full editor buffer where you can compose your message. If you still prefer the {input} approach, I'm open to discussing it further. I tried a quick POC of the edit approach to see how it would feel in practice, and I'll add a small recording underneath to show what it looks like: Cap.2026-02-26.at.19.20.48.mp4I prototyped this to see how it feels. Implementation-wise, it's quite simple: all the edit UI logic lives in one module, which is only called when needed. The existing placeholder system and send flow remain untouched. This can start simple and evolve incrementally. Potential extensions:
Not all of these are trivial, but we can design the architecture to support gradual enhancement. What do you think @jxyyz? Do you like this idea? |
|
If what I proposed solves your issue, I can make a commit for it, since I have been trying it and I like it; it is useful. Then we can think about whether we need something more. That would mean that my proposal replaces this. @jxyyz please let me know |
|
Hello, I'll reach out to you on this topic later today, since I haven't been active lately. |
|
Hey @MSmaili, thanks for the detailed response and the prototype — the edit buffer looks really nice in practice. I see myself using it in certain situations. I agree with your main concern: I thought about this and have one suggestion: Ship the edit buffer exactly as you proposed ( -- Built-in edit buffer, exactly as you proposed:
require("wiremux").send("context:\n{this}", { edit = true })
-- User-defined hook — receives resolved text, controls dispatch:
require("wiremux").send("context:\n{this}", {
on_resolved = function(text, send)
vim.ui.input({ prompt = "Question: " }, function(q)
if q then send("question: " .. q .. "\n" .. text) end
end)
end
})Of course the above is only an example / proof of concept. Final implementation might be different. Why I care about this: My use case is treating Also, I can already edit the prompt directly in the target tmux pane, so the full-buffer editing is somewhat redundant for me. Especially when target instance supports vim-mode and/or ability to edit in external editor I'm not married to the naming or exact signature — just the idea that there's a stage after placeholder resolution where the user can step in. What do you think? btw. little context why I created this PR:My first instinct was to write a custom resolver, since wiremux already supports them. But async user prompts interfered with state-dependent placeholders like |
|
Thanks for the comment. I agree that at first, the edit buffer can seem redundant and not very useful. However, once we start adding more features, for example, an extra file picker that allows selecting files and pasting them directly into the buffer (i tried it and it is really simple to do), using placeholders directly becomes more compelling. I have been experimenting with this approach, and so far it feels clean and practical. That said, we definitely need to evaluate it more thoroughly. I also understand your point, and it makes a lot of sense. We need to think carefully about how to implement this feasibly. The hook-based approach still seems promising. Perhaps we can expose a single public Give me a bit of time, and I will come back with a proposal that balances both perspectives. |
|
I understand. The final decision is yours On the other hand, if there were an editable buffer for the prompt with a unique buftype or filetype (when I still prefer a hook or one of the solutions mentioned earlier. But definitely there are different pathways to achieve the same goals. |
|
@jxyyz could you try this branch with the I also handled the I’m also planning to add adapters for other pickers, so selecting filename insertion works better. |
Hello, I like this project. Good job. Because of that, I've thought about a little contribution. I've implemented a feature that will improve my own workflow as a user.
It's a complete implementation with tests and docs, but please consider this as a proposition or proof of concept for a change. I'm willing to change or adjust something in this approach.
What does this PR change?
This contribution introduces the
{input}placeholder, which interactively asks the user for a value viavim.ui.input().For example:
It will display an input widget for the user with "Question" as a prompt. Text provided by the user will be used in place of the placeholder.
There is also the possibility to provide a default value:
Why?
Some placeholders (for example
{selection}) won't work well whenwiremux.send()is wrapped invim.ui.input().This snippet will not work well:
vim.ui.input()is asynchronous. I'm using Snack's input. When the prompt window is created, Neovim's state is changed and the text selected in the buffer is lost. So wiremux can't expand the{selection}placeholder.Solution
A new
{input}placeholder that prompts the user viavim.ui.input()at send time.The placeholder expansion/resolving is done in this order:
context.expand()resolves all standard placeholders ({file},{selection}, etc.) immediately, capturing the current editor state before any UI interaction occurs.input.resolve()then prompts the user for each{input}placeholder sequentially.This guarantees that editor-state-dependent placeholders are captured while the state is still valid, regardless of what happens during user input.
Syntax
The placeholder supports optional prompt labels and default values:
Cancellation and deduplication
nilfromvim.ui.input()) aborts the entire send. No partial text is dispatched.{input}expressions are deduplicated -- prompted once, substituted everywhere they appear.Implementation details
New module:
lua/wiremux/context/input.luaSelf-contained module with four functions:
find(text)-- extracts unique{input...}keys from text, preserving order of first appearance. Rejects false positives like{input_var}or{input2}by requiring either bare"input"or the"input:"prefix.parse(key)-- splits a key into prompt label and optional default value. The first colon afterinput:separates the prompt from the default, so defaults can contain colons (e.g. URLs).replace(text, values)-- substitutes resolved values back into text. Unresolved keys are left intact.resolve(keys, on_done)-- chainsvim.ui.input()calls sequentially. Callson_done(values)on completion oron_done(nil)on cancellation.Test coverage
tests/context_input_spec.lua-- unit tests forparse,find,has_inputs,replace, andresolve, including some edge casestests/send_input_spec.lua-- integration tests for the send action, verifying resolution order, cancellation aborting send, etc.Usage examples