Reputation: 469
I'm building a console app in C++, and need a way to call a terminal text editor for the user's editing pleasure, and knowing when they are done.
For example, in git when you run git rebase --interactive
, it launches a text editor right there in the terminal (Nano by default) wherein which you can easily edit commits. When you close the editor, git resumes its operations in the console.
I believe what I need to do is launch an editor as a child process, continuously pass through cin
and cout
to it, and finally resume the program when the editor exits.
I've looked into popen
, but that only sends one stream (stdout). I even read through git's rebase implementation, but couldn't figure out how they did it.
Any suggestions?
Upvotes: 1
Views: 751
Reputation: 469
It seems there is an extremely easy and convenient way to do just this:
system("vim file.txt");
The statement runs the command and takes over terminal input and output until the user exits the editor, at which point it unblocks and execution continues in the console.
Thanks to the commenters for inspiration.
If you don't want blocking behaviour, see bk2204's answer.
Upvotes: 0
Reputation: 76449
How you invoke the editor depends on the system. On a Unix system, you would spawn a process with standard input, standard output, and standard error passed through to the child process, since presumably all three of those are connected to the terminal. This is usually the default behavior if you don't redirect these streams.
Usually this is done by using fork
and one of the exec*
family of functions on Unix, but you could also use the posix_spawn
family of functions. There are different ways to do it on Windows.
You should use the VISUAL
environment variable if it is set and TERM
is set to something other than dumb
, and EDITOR
otherwise, falling back to a system default (usually vi
) if neither is set. The value of these environment variables must always be passed to /bin/sh
(or a POSIX sh if that is not one) for evaluation; for example, it is always acceptable to set VISUAL
or EDITOR
to f() { vim "$0" "$@"; };f
and all programs using those variables must support that. Git does the same thing, plus searching for an editor in some additional locations.
Below is a rough C (and valid C++) program that does nothing but spawn an editor with the arguments on the command line. It should demonstrate approximately how to spawn an editor correctly:
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
bool is_dumb(void)
{
const char *term = getenv("TERM");
return !term || !strcmp(term, "dumb");
}
const char *editor(void)
{
const char *ed = NULL;
if (!is_dumb())
ed = getenv("VISUAL");
if (!ed)
ed = getenv("EDITOR");
if (!ed)
ed = "vi";
return ed;
}
int main(int argc, char **argv)
{
const char *ed = editor();
pid_t pid = fork();
if (pid < 0) {
perror("Failed to spawn editor");
exit(127);
}
if (!pid) {
const char *append = " \"$@\"";
size_t len = strlen(ed) + strlen(append) + 1;
char *final_editor = (char *)malloc(len);
snprintf(final_editor, len, "%s%s", ed, append);
const char **args = (const char **)malloc(sizeof(const char *) * (argc + 3));
if (!args)
exit(255);
args[0] = "sh";
args[1] = "-c";
args[2] = final_editor;
for (int i = 1; i < argc; i++)
args[i + 2] = argv[i];
args[argc + 2] = NULL;
execvp("sh", (char *const *)args);
exit(127);
} else {
int wstatus;
waitpid(pid, &wstatus, 0);
if (WIFEXITED(wstatus)) {
exit(wstatus);
} else {
exit(255);
}
}
}
Upvotes: 1