我正在用C++构建一个控制台应用程序,需要一种方法来调用终端文本编辑器,以满足用户的编辑乐趣,并知道何时完成。
例如,在git中,当您运行git rebase-interactive
时,它会在终端中启动一个文本编辑器(默认情况下是Nano),您可以轻松地在其中编辑提交。当您关闭编辑器时,git将继续在控制台中进行操作。
我相信我需要做的是将一个编辑器作为子进程启动,不断地通过cin
和cout
传递给它,最后在编辑器退出时恢复程序。
我研究了popen
,但它只发送一个流(stdout)。我甚至通读了Git的重基实现,但搞不清楚他们是怎么做到的。
有什么建议吗?
如何调用编辑器取决于系统。在Unix系统上,您将生成一个进程,并将标准输入、标准输出和标准错误传递给子进程,因为可能所有这三个进程都连接到终端。如果不重定向这些流,这通常是默认行为。
这通常是通过使用fork
和Unix上的exec*
系列函数之一来完成的,但也可以使用posix_spawn
系列函数。在Windows上有不同的方法。
如果已设置visual
环境变量,并且term
设置为dumb
以外的其他内容,则应使用该环境变量,否则应使用editor
;如果两者都未设置,则返回到系统默认值(通常为vi
)。这些环境变量的值必须始终传递给/bin/sh
(如果不是POSIX sh,则传递给POSIX sh)以求值;例如,将visual
或editor
设置为f(){vim“$0”“$@”;};f
总是可以接受的,并且使用这些变量的所有程序都必须支持这一点。Git也会做同样的事情,还会在其他位置搜索编辑器。
下面是一个粗略的C(和有效的C++)程序,它什么也不做,只是通过命令行中的参数生成一个编辑器。它应该大致演示如何正确生成编辑器:
#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);
}
}
}