mirror of
https://github.com/tomasriveral/NoteWrapper.git
synced 2026-08-11 18:18:36 +02:00
664 lines
25 KiB
C
664 lines
25 KiB
C
#include "utils.h"
|
|
|
|
const char *supportedEditor[] = {"helix", "jed", "kakoune", "micro", "nano", "neovim", "vi", "vim"};
|
|
const int numEditors = 8;
|
|
|
|
int compareString(const void *a, const void *b) {
|
|
const char *str1 = *(const char **)a;
|
|
const char *str2 = *(const char **)b;
|
|
return strcmp(str1, str2); // strcmp returns <0, 0, >0
|
|
}
|
|
int reverseCompareString(const void *a, const void *b) {
|
|
const char *str1 = *(const char **)a;
|
|
const char *str2 = *(const char **)b;
|
|
return -1 * strcmp(str1, str2);
|
|
}
|
|
|
|
void getCurrentTime(int *hour, int *minute, int *second) {
|
|
time_t now = time(NULL); // Get current time in seconds since epoch
|
|
struct tm *local = localtime(&now); // Convert to local time structure
|
|
|
|
*hour = local->tm_hour; // Extract hour
|
|
*minute = local->tm_min; // Extract minutes
|
|
*second = local->tm_sec; // Extract seconds
|
|
}
|
|
|
|
void _debug(const int d, const char *file, const int line, const char *function, const char *message, ...) { // use for formatted debug
|
|
if (d) {
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
va_list args; // variadic function stuff
|
|
va_start(args, message);
|
|
int h, m, s;
|
|
getCurrentTime(&h, &m, &s);
|
|
fprintf(stderr, "\e[0;32m[DEBUG -- %d:%d:%d] From file %s line %d function %s:\e[0m\n", h, m, s, file, line, function);
|
|
vfprintf(stderr, message, args);
|
|
fprintf(stderr, "\e[0m\n");
|
|
va_end(args);
|
|
}
|
|
}
|
|
void _altDebug(const int d, const char *message,
|
|
...) { // use for less formal debuggin. (usefull if enumerating or making a list
|
|
if (d) {
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
va_list args;
|
|
va_start(args, message);
|
|
vfprintf(stderr, message, args);
|
|
va_end(args);
|
|
}
|
|
}
|
|
|
|
void _error(const int shouldDebug, const int condition, const char *type, const char *file, const int line, const char *function, const char *message,
|
|
...) { // used for formatted errors
|
|
if (condition) {
|
|
int h, m, s;
|
|
getCurrentTime(&h, &m, &s);
|
|
fprintf(stderr, "\e[0;31m[%s ERROR -- %d:%d:%d] From file %s line %d function %s:\n", type, h, m, s, file, line, function);
|
|
if (errno != 0) {
|
|
fprintf(stderr, " (System-level error message: %s)\n", strerror(errno));
|
|
} else {
|
|
fprintf(stderr, " (No system-level error; issue is application-level)\n");
|
|
}
|
|
va_list args;
|
|
va_start(args, message);
|
|
vfprintf(stderr, message, args);
|
|
va_end(args);
|
|
if (!shouldDebug) {
|
|
fprintf(stderr, "\nRunning notewrapper -V might give you more information.");
|
|
}
|
|
fprintf(stderr, "\e[0m\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void copyDir(const char *source, const char *destination, const char **rsyncArgs, const int rsyncArgsNumber, const int shouldDebug) {
|
|
debug("Backuping... source: %s and destination: %s", source, destination);
|
|
debug("Rsync has %d extra arguments", rsyncArgsNumber);
|
|
pid_t pid = fork();
|
|
|
|
error(pid < 0, "program", "fork() faild. pid = %d", pid);
|
|
|
|
if (pid == 0) {
|
|
// Child process: execute rsync
|
|
char **args = malloc((4 + rsyncArgsNumber) * sizeof(char *));
|
|
args[0] = "rsync";
|
|
for (int i = 0; i < rsyncArgsNumber; i++) {
|
|
args[i + 1] = (char *)rsyncArgs[i];
|
|
if (i == rsyncArgsNumber - 1) { // last loop
|
|
args[i + 2] = (char *)source;
|
|
args[i + 3] = (char *)destination;
|
|
args[i + 4] = NULL; // execvp expect last arg to be NULL
|
|
debug("rsync command:");
|
|
for (int k = 0; k <= i + 4; k++) {
|
|
altDebug("%s ", args[k]);
|
|
}
|
|
altDebug("\n");
|
|
}
|
|
}
|
|
execvp("rsync", args);
|
|
// If execvp returns, there was an error
|
|
error(1, "program", "execvp() failed");
|
|
} else {
|
|
debug("Backup started asynchronously with PID %d\n", pid);
|
|
}
|
|
}
|
|
|
|
static void ensureDir(const char *path, const int shouldDebug) {
|
|
struct stat st = {0};
|
|
|
|
if (stat(path, &st) == -1) {
|
|
debug("Creating the directory %s.", path);
|
|
error(mkdir(path, 0700) != 0, "program", "mkdir failed");
|
|
}
|
|
}
|
|
|
|
void initAppFilesAndDirs(const char *home, const int shouldDebug) {
|
|
// sometimes .cache and .config doesn't exist. Such as with github actions machines
|
|
char config_dir[PATH_MAX];
|
|
char cache_dir[PATH_MAX];
|
|
|
|
snprintf(config_dir, sizeof(config_dir), "%s/.config/", home);
|
|
|
|
snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/", home);
|
|
|
|
ensureDir(config_dir, shouldDebug);
|
|
ensureDir(cache_dir, shouldDebug);
|
|
|
|
strncat(config_dir, "notewrapper/", PATH_MAX);
|
|
strncat(cache_dir, "notewrapper/", PATH_MAX);
|
|
|
|
ensureDir(config_dir, shouldDebug);
|
|
ensureDir(cache_dir, shouldDebug);
|
|
|
|
char config_file[PATH_MAX + 12];
|
|
snprintf(config_file, sizeof(config_file), "%s/config.json", config_dir);
|
|
|
|
FILE *f = fopen(config_file, "r");
|
|
if (f) {
|
|
fclose(f);
|
|
return;
|
|
}
|
|
debug("Creating default config file.");
|
|
FILE *w = fopen(config_file, "w");
|
|
error(!w, "program", "fopen failed opening %s");
|
|
fprintf(w, // TODO CHANGE default json
|
|
"{\n"
|
|
" \"directory\": [\"~/Documents/\"],\n" // I personally use ~/Documents/Notes. But the
|
|
// dir doesn't exist most times. So for the
|
|
// default user it is better to put ~/Documents
|
|
" \"render\": true,\n"
|
|
" \"jumpToEndOfFileOnLaunch\": true,\n"
|
|
" \"editor\": \"neovim\",\n"
|
|
" \"journalRegex\": \".*journal.*\",\n"
|
|
" \"dateEntry\": \"# %%Y %%m %%d %%a\",\n"
|
|
" \"newLineOnOpening\": true,\n"
|
|
" \"backup\": {\n"
|
|
" \"enable\": false,\n"
|
|
" \"directory\": {\n"
|
|
" \"~/Documents/\": \"path/to/backup/\"\n"
|
|
" },\n"
|
|
" \"interval\": \"weekly\",\n"
|
|
" \"rsyncArgs\": [\"-Lqah\", \"--update\"]\n"
|
|
" }\n"
|
|
"}\n");
|
|
|
|
fclose(w);
|
|
}
|
|
|
|
void handleBackups(char **sourceDirectoryArray, const int sourceNumber, char **destinationDirectoryArray, const char *homeDir, const int interval, const char **rsyncArguments, const int rsyncArgumentsNumber,
|
|
const int forceBackup, int *backupMessage, const int shouldDebug) {
|
|
int shouldBackup = 0;
|
|
|
|
time_t now = time(NULL);
|
|
debug("Time since epoch is %ld", (long)now);
|
|
char cacheFilePATH[PATH_MAX];
|
|
snprintf(cacheFilePATH, PATH_MAX, "%s/.cache/notewrapper/backupTime.txt", homeDir);
|
|
|
|
struct stat st;
|
|
if (stat(cacheFilePATH, &st) != 0) { // file does not exist
|
|
shouldBackup = 1;
|
|
debug("Creating (for the first time) %s", cacheFilePATH);
|
|
FILE *cacheFile = fopen(cacheFilePATH, "w");
|
|
if (!cacheFile) {
|
|
error(1, "program", "Failed to open cache file for writing: %s", cacheFilePATH);
|
|
}
|
|
fprintf(cacheFile, "%ld", (long)now);
|
|
fclose(cacheFile);
|
|
} else {
|
|
FILE *cacheFile = fopen(cacheFilePATH, "r");
|
|
debug("reading %s", cacheFilePATH);
|
|
if (!cacheFile) {
|
|
error(1, "program", "Failed to open cache file for reading: %s", cacheFilePATH);
|
|
}
|
|
|
|
char line[64];
|
|
if (fgets(line, sizeof(line), cacheFile) == NULL) {
|
|
error(1, "program", "Failed to read backup cache file (at %s)", cacheFilePATH);
|
|
}
|
|
fclose(cacheFile);
|
|
|
|
char *endptr;
|
|
long lastBackupTime = strtol(line, &endptr, 10);
|
|
if (endptr == line || (*endptr != '\n' && *endptr != '\0')) {
|
|
error(1, "program", "Invalid timestamp in cache file: %s", line);
|
|
}
|
|
double deltaTime = difftime(now, lastBackupTime);
|
|
if (deltaTime > interval || forceBackup) {
|
|
if (!forceBackup) {
|
|
debug("(difftime) %f is greater than (interval) %d -> backuping...", deltaTime, interval);
|
|
} else {
|
|
debug("Backup was forced. backuping...");
|
|
}
|
|
shouldBackup = 1;
|
|
cacheFile = fopen(cacheFilePATH, "w");
|
|
if (!cacheFile) {
|
|
error(1, "program", "Failed to open cache file for writing");
|
|
}
|
|
fprintf(cacheFile, "%ld\n", (long)now);
|
|
fclose(cacheFile);
|
|
} else {
|
|
debug("(difftime) %f is smaller than (interval) %d -> no need to backup.", deltaTime, interval);
|
|
}
|
|
}
|
|
|
|
if (shouldBackup) {
|
|
*backupMessage = 1;
|
|
debug("A backup is needed");
|
|
for (int i = 0; i < sourceNumber; i++) {
|
|
debug("%s", destinationDirectoryArray[i]);
|
|
if (destinationDirectoryArray[i]) { // if we don't want to backup it, it was set to NULL
|
|
copyDir(sourceDirectoryArray[i], destinationDirectoryArray[i], rsyncArguments, rsyncArgumentsNumber, shouldDebug);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int isEditorValid(char *editorToCheck, int useDefaultEditor,
|
|
int shouldDebug) { // check if editor is supported and if it is installed
|
|
// check if supported
|
|
if (useDefaultEditor) { // we use a different error message if it defaulted to $EDITOR
|
|
error(!isStringInArray(editorToCheck, supportedEditor, numEditors), "user",
|
|
"%s is not a supported editor.\n(defaulted to $EDITOR as neither \"editor\" was set "
|
|
"in the configuration file nor -e/--editor <editor> was set.)\n See "
|
|
"https://github.com/tomasriveral/NoteWrapper#editor-support for a list of supported "
|
|
"editors.",
|
|
editorToCheck);
|
|
} else {
|
|
error(!isStringInArray(editorToCheck, supportedEditor, numEditors), "user",
|
|
"%s is not a supported editor.\n See "
|
|
"https://github.com/tomasriveral/NoteWrapper#editor-support for a list of supported "
|
|
"editors.",
|
|
editorToCheck);
|
|
}
|
|
// check if installed
|
|
char *editor;
|
|
if (strcmp(editorToCheck, "neovim") == 0) { // some executables are not name the same as the project
|
|
editor = strdup("nvim"); // we must use strdup and not just copy as we would have modified
|
|
// editorToOpen in main
|
|
} else if (strcmp(editorToCheck, "helix") == 0) {
|
|
editor = strdup("hx");
|
|
} else if (strcmp(editorToCheck, "kakoune") == 0) {
|
|
editor = strdup("kak");
|
|
} else {
|
|
editor = strdup(editorToCheck);
|
|
}
|
|
char *path_env = getenv("PATH");
|
|
error(!path_env, "program",
|
|
"getenv(\"PATH\") failed to get your path. NoteWrapper is unable to check if your "
|
|
"desired editor is installed\n");
|
|
debug("Your PATH is %s", path_env);
|
|
char *paths = strdup(path_env); // duplicate because strtok modifies the string
|
|
char *dir = strtok(paths, ":");
|
|
while (dir) {
|
|
char fullpath[PATH_MAX];
|
|
snprintf(fullpath, sizeof(fullpath), "%s/%s", dir, editor);
|
|
if (access(fullpath, X_OK) == 0) { // program found
|
|
free(paths);
|
|
free(editor);
|
|
return 1;
|
|
}
|
|
dir = strtok(NULL, ":");
|
|
}
|
|
|
|
free(paths);
|
|
free(editor);
|
|
return 0; // program not found
|
|
}
|
|
|
|
char *getFormatedTime(char *format, int shouldDebug) {
|
|
debug("Inputed time format is %s", format);
|
|
time_t t = time(NULL);
|
|
struct tm *tm = localtime(&t);
|
|
char *buf = malloc(BUFFER_SIZE);
|
|
strftime(buf, BUFFER_SIZE, format, tm);
|
|
debug("Formated time is %s", buf);
|
|
return buf;
|
|
}
|
|
|
|
int isStringInArray(const char *string, const char **array, const int len) {
|
|
for (int i = 0; i < len; i++) {
|
|
if (strcmp(string, array[i]) == 0) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int isStringInFile(const char *path, const char *string, const int shouldDebug) {
|
|
FILE *file = fopen(path, "r");
|
|
error(file == NULL, "program", "While searching for \"%s\", we could not open file %s", string, path);
|
|
char buf[BUFFER_SIZE];
|
|
|
|
while (fgets(buf, BUFFER_SIZE, file) != NULL) {
|
|
if (strstr(buf, string) != NULL) {
|
|
debug("%s contains the substring %s", buf, string);
|
|
return 1;
|
|
}
|
|
}
|
|
debug("The string %s is not inside %s", string, path);
|
|
return 0;
|
|
}
|
|
void appendToFile(const char *path, const char *string, const int shouldDebug) {
|
|
FILE *file = fopen(path, "r");
|
|
char lastLine[1024] = {0};
|
|
error(file == NULL, "program", "could not open %s", path);
|
|
char buffer[BUFFER_SIZE]; // it does not matter if we have a small buffer. string is relatively
|
|
// small (most time \n or the name of the file). So it is under
|
|
// BUFFER_SIZE. If the last line is more than BUFFER_SIZE. It can't be
|
|
// equal to string
|
|
|
|
// Read file line by line to get the last one
|
|
while (fgets(buffer, sizeof(buffer), file) != NULL) {
|
|
strncpy(lastLine, buffer, sizeof(lastLine) - 1);
|
|
}
|
|
fclose(file);
|
|
|
|
// Compare last line with string
|
|
if (strcmp(lastLine, string) == 0) {
|
|
if (shouldDebug) {
|
|
debug("Skipping append: last line already matches \"%s\"", string);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Append since it's different
|
|
file = fopen(path, "a");
|
|
error(file == NULL, "program", "could not open %s", path);
|
|
|
|
fprintf(file, "%s", string);
|
|
fclose(file);
|
|
|
|
debug("\"%s\" was appended to %s successfully", string, path);
|
|
}
|
|
|
|
void sanitize(char *string) {
|
|
size_t stringLenght = strlen(string);
|
|
for (size_t i = 0; i < stringLenght; i++) {
|
|
if ((!isalnum((unsigned char)string[i]) && strchr("~/\\:*?\"\'|!$[]{}<>\n\r\t", string[i]))) { // replace unwanted chars by '_'
|
|
string[i] = '_';
|
|
}
|
|
}
|
|
// removes any leading '.
|
|
size_t i = 0;
|
|
while (i < stringLenght && string[i] == '.') {
|
|
string[i++] = '_';
|
|
}
|
|
}
|
|
|
|
// both functions are from https://stackoverflow.com/a/5467788
|
|
// from what i understood:
|
|
// remove() can't delete directories with files
|
|
// so it walks the file tree and deletes it's content before removing the directory
|
|
int unlink_cb(const char *filePath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) {
|
|
(void)sb;
|
|
(void)typeflag;
|
|
(void)ftwbuf;
|
|
|
|
int shouldDebug = 1; // normally shouldDebug should be passed to the function. However, it's to
|
|
// difficult here and the best it to set it to 1.
|
|
|
|
// some sanity checks to be sure that we don't delete something we shouldn't
|
|
error(strcmp(filePath, "") == 0, "program", "filePath is empty. Refusing to delete directory");
|
|
error(filePath[0] == '.', "program", "%s starts with \".\". For security reasons, use absolute path and not relative path.", filePath);
|
|
|
|
int rv = remove(filePath);
|
|
error(rv, "program", "remove() failed to delete %s", filePath);
|
|
return rv;
|
|
}
|
|
|
|
int rmrf(char *path, int shouldDebug) {
|
|
error(strcmp(path, "/") == 0, "program", "Refusing to delete root directory");
|
|
return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
|
|
}
|
|
|
|
int openEditor(char *path, char *editor, int render, int shouldJumpToEndOfFile, int shouldDebug) {
|
|
|
|
// this ensures that ncurses won't affect the editor behaviour
|
|
pid_t editor_pid = fork();
|
|
error(editor_pid < 0, "program", "fork() failed.");
|
|
|
|
// instead of reusing part of the codes, for any new editor copy an example and adapt it. This
|
|
// is in case we need a custom fix for an editor.
|
|
|
|
if (editor_pid == 0) {
|
|
// =========================
|
|
// CHILD: launch editor
|
|
// =========================
|
|
|
|
// ---- NEOVIM / VIM ----
|
|
if (strcmp(editor, "neovim") == 0 || strcmp(editor, "vim") == 0) {
|
|
const char *bin = (strcmp(editor, "neovim") == 0) ? "nvim" : "vim";
|
|
|
|
if (render) {
|
|
if (shouldJumpToEndOfFile) {
|
|
debug("Running %s +:$ +:Vivify %s", bin, path);
|
|
execlp(bin, bin, "+:$", "+:Vivify", path, NULL);
|
|
} else {
|
|
debug("Running %s +:Vivify %s", bin, path);
|
|
execlp(bin, bin, "+:Vivify", path, NULL);
|
|
}
|
|
} else {
|
|
if (shouldJumpToEndOfFile) {
|
|
debug("Running %s +:$ %s", bin, path);
|
|
execlp(bin, bin, "+:$", path, NULL);
|
|
} else {
|
|
debug("Running %s %s", bin, path);
|
|
execlp(bin, bin, path, NULL);
|
|
}
|
|
}
|
|
|
|
error(1, "program", "execlp() failed.");
|
|
// ---- MICRO ----
|
|
} else if (strcmp(editor, "micro") == 0) {
|
|
|
|
if (render) {
|
|
if (shouldJumpToEndOfFile) {
|
|
debug("Running MICRO_VIVIFY=1 micro %s +99999", path);
|
|
setenv("MICRO_VIVIFY", "1", 1);
|
|
execlp("micro", "micro", path, "+999999", NULL);
|
|
} else {
|
|
debug("Running MICRO_VIVIFY=1 micro %s", path);
|
|
setenv("MICRO_VIVIFY", "1", 1);
|
|
execlp("micro", "micro", path, NULL);
|
|
}
|
|
} else {
|
|
if (shouldJumpToEndOfFile) {
|
|
debug("Running micro %s +999999", path);
|
|
execlp("micro", "micro", path, "+999999", NULL);
|
|
} else {
|
|
debug("Running micro %s", path);
|
|
execlp("micro", "micro", path, NULL);
|
|
}
|
|
}
|
|
|
|
error(1, "program", "execlp() failed.");
|
|
}
|
|
|
|
// ---- NANO ----
|
|
else if (strcmp(editor, "nano") == 0) {
|
|
|
|
// If render enabled → spawn viv in parallel
|
|
if (render) {
|
|
debug("Running Vivify...");
|
|
pid_t viv_pid = fork();
|
|
error(viv_pid < 0, "program", "fork() failed.");
|
|
|
|
if (viv_pid == 0) {
|
|
// GRANDCHILD → viv
|
|
|
|
char viv_path[PATH_MAX];
|
|
strncpy(viv_path, path, PATH_MAX - 1);
|
|
viv_path[PATH_MAX - 1] = '\0';
|
|
|
|
if (shouldJumpToEndOfFile) {
|
|
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
|
}
|
|
|
|
debug("Running viv %s", viv_path);
|
|
execlp("viv", "viv", viv_path, NULL);
|
|
error(1, "program", "execlp() failed.");
|
|
}
|
|
// IMPORTANT: do NOT wait for viv
|
|
}
|
|
|
|
// Now run nano (this replaces the child process)
|
|
if (shouldJumpToEndOfFile) {
|
|
debug("Running nano + %s", path);
|
|
execlp("nano", "nano", "+", path, NULL);
|
|
} else {
|
|
debug("Running nano %s", path);
|
|
execlp("nano", "nano", path, NULL);
|
|
}
|
|
|
|
error(1, "program", "execlp() failed.");
|
|
// ---- HELIX ----
|
|
} else if (strcmp(editor, "helix") == 0) {
|
|
|
|
// If render enabled → spawn viv in parallel
|
|
if (render) {
|
|
debug("Running Vivify...");
|
|
pid_t viv_pid = fork();
|
|
error(viv_pid < 0, "program", "fork() failed.");
|
|
|
|
if (viv_pid == 0) {
|
|
// GRANDCHILD → viv
|
|
|
|
char viv_path[PATH_MAX];
|
|
strncpy(viv_path, path, PATH_MAX - 1);
|
|
viv_path[PATH_MAX - 1] = '\0';
|
|
|
|
if (shouldJumpToEndOfFile) {
|
|
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
|
}
|
|
|
|
debug("Running viv %s", viv_path);
|
|
execlp("viv", "viv", viv_path, NULL);
|
|
error(1, "program", "execlp() failed.");
|
|
}
|
|
// IMPORTANT: do NOT wait for viv
|
|
}
|
|
|
|
// Now run helix (this replaces the child process)
|
|
if (shouldJumpToEndOfFile) {
|
|
strncat(path, ":99999", PATH_MAX);
|
|
debug("Running hx %s", path);
|
|
execlp("hx", "hx", path, NULL);
|
|
} else {
|
|
debug("Running hx %s", path);
|
|
execlp("hx", "hx", path, NULL);
|
|
}
|
|
|
|
error(1, "program", "execlp() failed.");
|
|
}
|
|
// ---- kakoune ----
|
|
else if (strcmp(editor, "kakoune") == 0) {
|
|
|
|
// If render enabled → spawn viv in parallel
|
|
if (render) {
|
|
debug("Running Vivify...");
|
|
pid_t viv_pid = fork();
|
|
error(viv_pid < 0, "program", "fork() failed.");
|
|
|
|
if (viv_pid == 0) {
|
|
// GRANDCHILD → viv
|
|
|
|
char viv_path[PATH_MAX];
|
|
strncpy(viv_path, path, PATH_MAX - 1);
|
|
viv_path[PATH_MAX - 1] = '\0';
|
|
|
|
if (shouldJumpToEndOfFile) {
|
|
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
|
}
|
|
|
|
debug("Running viv %s", viv_path);
|
|
execlp("viv", "viv", viv_path, NULL);
|
|
error(1, "program", "execlp() failed.");
|
|
}
|
|
// IMPORTANT: do NOT wait for viv
|
|
}
|
|
|
|
// Now run nano (this replaces the child process)
|
|
if (shouldJumpToEndOfFile) {
|
|
debug("Running kak + %s", path);
|
|
execlp("kak", "kak", "+", path, NULL);
|
|
} else {
|
|
debug("Running kak %s", path);
|
|
execlp("kak", "kak", path, NULL);
|
|
}
|
|
|
|
error(1, "program", "execlp() failed.");
|
|
// ---- VI ----
|
|
} else if (strcmp(editor, "vi") == 0) {
|
|
|
|
// If render enabled → spawn viv in parallel
|
|
if (render) {
|
|
debug("Running Vivify...");
|
|
pid_t viv_pid = fork();
|
|
error(viv_pid < 0, "program", "fork() failed.");
|
|
|
|
if (viv_pid == 0) {
|
|
// GRANDCHILD → viv
|
|
|
|
char viv_path[PATH_MAX];
|
|
strncpy(viv_path, path, PATH_MAX - 1);
|
|
viv_path[PATH_MAX - 1] = '\0';
|
|
|
|
if (shouldJumpToEndOfFile) {
|
|
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
|
}
|
|
|
|
debug("Running viv %s", viv_path);
|
|
execlp("viv", "viv", viv_path, NULL);
|
|
error(1, "program", "execlp() failed.");
|
|
}
|
|
// IMPORTANT: do NOT wait for viv
|
|
}
|
|
|
|
// Now run vi (this replaces the child process)
|
|
if (shouldJumpToEndOfFile) {
|
|
debug("Running vi +:$ %s", path);
|
|
execlp("vi", "vi", "+:$", path, NULL);
|
|
} else {
|
|
debug("Running vi %s", path);
|
|
execlp("vi", "vi", path, NULL);
|
|
}
|
|
|
|
error(1, "program", "execlp() failed.");
|
|
// ---- Jed ----
|
|
} else if (strcmp(editor, "jed") == 0) {
|
|
|
|
// If render enabled → spawn viv in parallel
|
|
if (render) {
|
|
debug("Running Vivify...");
|
|
pid_t viv_pid = fork();
|
|
error(viv_pid < 0, "program", "fork() failed.");
|
|
|
|
if (viv_pid == 0) {
|
|
// GRANDCHILD → viv
|
|
|
|
char viv_path[PATH_MAX];
|
|
strncpy(viv_path, path, PATH_MAX - 1);
|
|
viv_path[PATH_MAX - 1] = '\0';
|
|
|
|
if (shouldJumpToEndOfFile) {
|
|
strncat(viv_path, ":99999", PATH_MAX - strlen(viv_path) - 1);
|
|
}
|
|
|
|
debug("Running viv %s", viv_path);
|
|
execlp("viv", "viv", viv_path, NULL);
|
|
error(1, "program", "execlp() failed.");
|
|
}
|
|
// IMPORTANT: do NOT wait for viv
|
|
}
|
|
|
|
// Now run jed (this replaces the child process)
|
|
if (shouldJumpToEndOfFile) {
|
|
debug("Running jed %s -g 99999999", path);
|
|
execlp("jed", "jed", path, "-g", "99999999", NULL);
|
|
} else {
|
|
debug("Running jed %s", path);
|
|
execlp("jed", "jed", path, NULL);
|
|
}
|
|
|
|
error(1, "program", "execlp() failed.");
|
|
// ---- UNKNOWN EDITOR ----
|
|
} else {
|
|
error(1, "program", "Unknown editor.");
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// PARENT: wait ONLY editor
|
|
// =========================
|
|
int status;
|
|
while (waitpid(editor_pid, &status, 0) == -1) { // we can't just use waitpid(). Because when resizing the terminal, waitpid() is
|
|
// returned so we loop to see if there is not a problem
|
|
if (errno != EINTR) {
|
|
perror("waitpid");
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|