Skip to content

Conversation

@anonhostpi
Copy link

This PR attempts to add a much more clever alternative to vipe

vipe (and many similar solutions) use temp files for interactive handling of stdin and stdout in bash/shell scripts. This has an unnecessary footprint of relying on the file system to temporarily buffer stdin so that it can be edited before being sent back to stdout.

This PR attempts to achieve the goal of vipe without that unnecessary footprint, and does so by taking advantage of the 3rd stdio stream: stderr.

Currently, tome uses stdout for rendering, which makes it un-useful for piping (piping controls stdin and stdout), but if we swap the stdout and stderr streams, we can get an interactive UI while still being able to pipe outputs in a shell script.

To test this change:

git clone https://github.com/anonhostpi/tome
cd tome

npm install -g .

touch real.js

# works as before
tome real.js

# piping now works
tome real.js --stdout > copy.js

# piping is inferred when receiving stdin:
echo "abcd" | tome real.js > copy.js # contents of real.js are preferred, if it exists, otherwise...
echo "abcd" | tome fake.js > copy2.js # contents of stdin is used

# edited file content can also now be captured by variables for automation scripts (very handy):
a=$(tome real.js --stdout)
echo $a # dumps the new real.js

NOTE 1: Specifying a fake file such as fake.js is used effectively only for language detection during pipes

NOTE 2: When piping and pressing control+q, the editor does not autosave nor prompt to save. Saving is still possible, but must be done so manually and can only be done for existing files (no-ops otherwise when piping)

Copy link
Owner

@boutell boutell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, first, thanks for contributing! This is the first PR, and I'm pleased to see it.

And, I see a lot of little improvements here.

However, I'm not convinced it makes sense to make a shell script wrapper a mandatory part of every execution of tome just to eliminate the need for vipe, a tool that seems well designed for its specialized job.

Is there a way to achieve this feature without the need for the shell script wrapper?

@boutell
Copy link
Owner

boutell commented Oct 28, 2025

Also, isn't it problematic to potentially confuse actual errors with normal output by displaying interactive output on stderr?

@anonhostpi
Copy link
Author

anonhostpi commented Oct 29, 2025

Also, isn't it problematic to potentially confuse actual errors with normal output by displaying interactive output on stderr?

I've thought about this. All I can say is "sort of." You can still return errors, it just has to be dumped over stdout. You also can still control the exit codes (what really should be the indicator of a failure).

The cost here is that any program relying on stderr for error reporting might not have the ability to pull errors from stdout, but I can't think of a single situation where this is true and you would allow or want UI interaction

@anonhostpi
Copy link
Author

anonhostpi commented Oct 29, 2025

Some solutions that may enhance determinism of what I said are:

1. Redirect userspace errors:

global.console = new console.Console({
  stdout: process.stderr, // or fs.createWriteStream('./out.log', { flags: 'a' }) or a socket or a pipe or etc...
  stderr: process.stdout // or fs.createWriteStream('./err.log', { flags: 'a' }) or a socket or a pipe or etc...
});

2. Redirect uncaught and internal errors:

const other_stderr_stream = // ... // file, stdout, socket, etc...

process.on('uncaughtException', (err) => {
  other_stderr_stream.write(`Uncaught: ${err.stack || err}\n`);
  // Optional: exit after logging
  process.exit(1);
});

process.on('unhandledRejection', (reason, p) => {
  other_stderr_stream.write(`Unhandled rejection at: ${p}\nReason: ${reason}\n`);
});

3. (Unverified) pollute stderr and stdout

Another solution (I'm not sure if it would work in modern Node.js) would be to pollute process.stderr and process.stdout, but I wouldn't recommend that.

@anonhostpi
Copy link
Author

anonhostpi commented Oct 29, 2025

Is there a way to achieve this feature without the need for the shell script wrapper?

In most cases, you need to manually instruct shells to attach a TTY in order for display to work over stderr. If you don't attach one, properties like process.stderr.rows become unavailable. There are some cases where you don't need to do this, but the majority require it, so its best to wrap it to ensure a TTY is available deterministically.

I didn't write one for windows, as I'm not sure if this project intends to target that platform (I couldn't get the main branch to work on my PC), but I imagine the wrapper would be similar.

EDIT:

One alternative to writing shims is to instruct the user to attach the TTY to stderr if it is absent instead of shimming it automatically with a shell wrapper, but this might be clunkier than just offering the wrapper.

@anonhostpi
Copy link
Author

anonhostpi commented Oct 29, 2025

If anything, I'm only offering up my code as a back-contribution or proof-of-concept. I'm not expecting you to merge (if you don't want to). I just want to show you what I did and what's possible with your tool. So no hard-feelings either way.

I'm using this for a specific purpose that I could see you viewing as an edge-case.

Even if you do, I still thought it was worth showing you what I've done and what I am doing with your work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants