#include "ui.h" #include "utils.h" void createNewVault(char **directoriesArray, int directoryCount, char **vaultsArray, int vaultCount, int bypass, char *bypassvalue, int shouldDebug) { int duplicateWarning = 0; // set to 1 later if the vault you tried to create already existed int emptyWarning = 0; // set to 1 later if inpted empty name input_screen: // choose in which directory the vault will be created char *dirToVault = ncursesSelect(directoriesArray, "Select a directory, in which the vault will remain (Use arrows or WASD, Enter to select or use 1-9 to quick select)", directoryCount, 0, "", "", " ", shouldDebug); char *vaultName = malloc(PATH_MAX); if (!bypass) { // if won't bypass (if -v or --vault weren't set) echo(); keypad(stdscr, FALSE); // color code from https://stackoverflow.com/a/73396575 start_color(); clear(); use_default_colors(); init_pair(1, COLOR_WHITE, -1); // -1 is blank init_pair(2, COLOR_RED, -1); attron(COLOR_PAIR(1)); printw("Enter the name of the new vault: "); mvprintw(3, 0, "Unsafe characters such as \\ and / will be replaced by _"); attroff(COLOR_PAIR(1)); if (duplicateWarning) { attron(COLOR_PAIR(2)); mvprintw(4, 0, "A vault with this name already exists. Please input another name"); attroff(COLOR_PAIR(2)); } if (emptyWarning) { attron(COLOR_PAIR(2)); mvprintw(5, 0, "The vault's name must not be empty. Please input another name"); attroff(COLOR_PAIR(2)); } move(1, 1); // replace cursor wgetnstr(stdscr, vaultName, PATH_MAX - 1); refresh(); endwin(); reset_shell_mode(); fflush(stdout); fflush(stderr); } else { strncpy(vaultName, bypassvalue, PATH_MAX - 2); // -2 (and later -1) because indexing vaultName[PATH_MAX - 1] = '\0'; } // check if no empty string if (strcmp(vaultName, "") == 0) { emptyWarning = 1; goto input_screen; } debug("Inputed vaultName=%s", vaultName); sanitize(vaultName); debug("Sanitized vaultName=%s", vaultName); if (!isStringInArray(vaultName, (const char **)vaultsArray, vaultCount)) { // if vault doesnt already exists. We avoid vaults with the // same name even if they are from different directories. char vaultFullPath[PATH_MAX]; // recreating the full path sprintf(vaultFullPath, "%s/%s/", dirToVault, vaultName); mkdir(vaultFullPath, 0744); } else { duplicateWarning = 1; goto input_screen; } free(vaultName); } char *createNewNote(char dirToVault[PATH_MAX], char *vaultFromDir, int bypass, char *bypassvalue, char *journalRegex, int shouldDebug) { // input from user for the name char *fileName = malloc(BUFFER_SIZE); if (!bypass) { // if we don't bypass. (if -n or --note weren't set.) echo(); keypad(stdscr, FALSE); clear(); printw("Enter the name of the new note: "); mvprintw(3, 0, "Unsafe characters such as \\, and / will be replaced by _"); mvprintw(4, 0, "If the name matches with the regex for a journal (%s), it will create a journal " "instead of a note.", journalRegex); move(1, 1); wgetnstr(stdscr, fileName, BUFFER_SIZE - 4); // limits the buffer to prevent overflow (-4 to account indexing // and from ".md" in case we need to add it later) refresh(); endwin(); reset_shell_mode(); fflush(stdout); fflush(stderr); } else { // bypasses user input if we bypass is set to 1 strncpy(fileName, bypassvalue, BUFFER_SIZE); fileName[BUFFER_SIZE - 1] = '\0'; } error(strcmp(fileName, "") == 0, "user", "fileName is empty"); // replace this with a warning and add a warning if duplicate file and // handle case where multiple warnings (if such case is possible) // check/sanitize the input debug("Inputed fileName=%s", fileName); sanitize(fileName); debug("Sanitized fileName=%s (We might append .md later", fileName); // we trow an error if the file already exists (previous behaviour was overwriting the file) char path[PATH_MAX]; snprintf(path, PATH_MAX, "%s/%s/%s", dirToVault, vaultFromDir, fileName); struct stat st; error(stat(path, &st) == 0, "user", "%s already exists", fileName); // if it matches with the journalRegex we treat it as a journal instead of a note regex_t regex; int regexReturn = regcomp(®ex, journalRegex, 0); error(regexReturn, "program", "Regex compilation failed."); regexReturn = regexec(®ex, fileName, 0, NULL, 0); if (!regexReturn) { debug("%s matches with %s treating it as a journal", fileName, journalRegex); char **options = malloc(32); // the number of bytes is exactly what in the two strings options[0] = "Divided journal"; options[1] = "Unified journal"; char *optionSelected = ncursesSelect(options, "Select which type of journal you want to create " "(Use arrows or WASD, Enter to select):", 2, 0, "", "", " ", shouldDebug); debug("%s was selected to be a %s", fileName, optionSelected); if (strcmp(optionSelected, options[0]) == 0) { // if it is a divided journal char *fileFullPath = malloc(PATH_MAX); snprintf(fileFullPath, PATH_MAX, "%s/%s/%s/", dirToVault, vaultFromDir, fileName); struct stat st = {0}; if (stat(fileFullPath, &st) == -1) { error(mkdir(fileFullPath, 0744), "program", "mkdir failed"); } else { error(1, "program", "%s could not be created", fileFullPath); } free(fileFullPath); } else { // if it is a unified journal char *fileFullPath = malloc(PATH_MAX); // we append .md directly to fileName and not only to fileFullPath to avoid passing the wrong fileName later. int lenName = strlen(fileName); if (fileName[lenName - 3] != '.' && fileName[lenName - 2] != 'm' && fileName[lenName - 2] != '1') { strcat(fileName, ".md"); debug("We just appended .md to %s", fileName); } snprintf(fileFullPath, PATH_MAX, "%s/%s/%s", dirToVault, vaultFromDir, fileName); // we verify if the file doesn't already exist as we might just have changed the fileName and (by extension) the file path. struct stat metadata; error(stat(fileFullPath, &metadata) == 0, "user", "%s already exists", fileFullPath); FILE *filePointer; filePointer = fopen(fileFullPath, "w"); // creates and opens the file error(filePointer == NULL, "program", "The %s couldn't be created.", fileFullPath); fprintf(filePointer, "### %s\n", fileName); fclose(filePointer); // closes the file so that nvim could open it free(fileFullPath); } free(options); // they are useless now } else { // normal process for a note debug("%s does not match with %s treating it as a note", fileName, journalRegex); // if there is no .md add an .md int len = strlen(fileName); if (fileName[len - 3] != '.' || fileName[len - 2] != 'm' || fileName[len - 1] != 'd') { // there might be a cleaner way to do this strcat(fileName, ".md"); // this should not cause an overflow issue as we get at most // 252 chars (+'.'+'m'+'d'+'\0' makes it to 256) with wgetnstr } char *fileFullPath = malloc(PATH_MAX); // this dinamically allocated because we use it in // the main function to call openEditor sprintf(fileFullPath, "%s/%s/%s", dirToVault, vaultFromDir, fileName); FILE *filePointer; filePointer = fopen(fileFullPath, "w"); // creates and opens the file error(filePointer == NULL, "program", "The %s couldn't be created.", fileFullPath); fprintf(filePointer, "### %s\n", fileName); fclose(filePointer); // closes the file so that nvim could open it free(fileFullPath); } return fileName; } char *fzfSelect(char *pathToFiles, char *selectText, int shouldDebug) { // we are gonna write each line of each files (with the filename and the line number to an // index) char command[CMD_BUFFER]; char indexFile[] = "/tmp/notewrapper_index_XXXXXX"; int fd = mkstemp(indexFile); error(fd == -1, "program", "mkstemp failed"); close(fd); snprintf(command, sizeof(command), "rg --line-number --no-heading --color=never . \"%s\" > %s", pathToFiles, indexFile); debug("INDEX CMD: %s", command); if (system(command) != 0) { unlink(indexFile); error(1, "program", "rg indexing failed"); } snprintf(command, sizeof(command), "cat %s | fzf --delimiter ':' --prompt='%s' " "--preview 'file={1}; line={2}; nl -ba \"$file\" | sed -n \"$((line-5)),$((line+5))p\" | " "sed \"$((line-$(($((line-5))))+1))s/^/\\x1b[31m-> \\x1b[0m/\"' " "--preview-window=right:60%%:hidden " "--bind 'change:show-preview'", indexFile, selectText ? selectText : "> "); debug("FZF CMD: %s", command); FILE *fzfPipe = popen(command, "r"); if (!fzfPipe) { unlink(indexFile); error(1, "program", "popen failed"); } char buffer[RESULT_BUFFER]; char *result = NULL; if (fgets(buffer, sizeof(buffer), fzfPipe)) { buffer[strcspn(buffer, "\n")] = '\0'; result = strdup(buffer); } debug("Buffer: %s", buffer); debug("result: %s", result); // cut at first ':' result = strtok(result, ":"); // we need to cut out the whole pathToFiles int pathToFilesLen = strlen(pathToFiles); int cutOut = 0; for (int i = 0; i < pathToFilesLen; i++) { if (pathToFiles[i] == result[i]) { cutOut = i; } } result += cutOut + 1; // the + 1 cuts the first / // in case we select a journal, we won't open the entry just yet. We only need to return the journal name (everything before [now the first] / result = strtok(result, "/"); debug("Cleaned note: %s", result); // clean up pclose(fzfPipe); unlink(indexFile); return result; } char *ncursesSelect(char **options, char *optionsText, int optionsNumber, int extraOptionsNumber, char *bottomText, char *middleText, char *topText, int shouldDebug) { // TODO we should see how it handles larges strings (like large directories) int highlight = 0; // curently highlighted option int key; cbreak(); // disable line buffering noecho(); // don't echo key presses keypad(stdscr, TRUE); // enable arrow keys start_color(); curs_set(0); // sets the cursor to invisible. (We will emulate the cursor with a hightlight that // go up and down). use_default_colors(); init_pair(1, COLOR_WHITE, -1); // color for optionsNumber (so notes, vaults, etc.) init_pair(2, COLOR_BLUE, -1); // color for extraOptionsNumber (so settings, delete notes, create vault, etc.) while (1) { clear(); attron(COLOR_PAIR(1)); mvprintw(0, 0, "%s", optionsText); int offset = 1; if (strcmp(bottomText, "") != 0) { mvprintw(offset, 1, "%s", bottomText); offset++; } // Print options with highlighting for (int i = 0; i < optionsNumber; i++) { if (i == highlight) { attron(A_REVERSE); } // highlight selected mvprintw(i + offset, 2, "%s", options[i]); if (i == highlight) { attroff(A_REVERSE); } } attroff(COLOR_PAIR(1)); if (strcmp(middleText, "") != 0) { mvprintw(offset + optionsNumber, 1, "%s", middleText); offset++; } // Printf extraOptions with highlighting attron(COLOR_PAIR(2)); for (int k = optionsNumber; k < optionsNumber + extraOptionsNumber; k++) { if (k == highlight) { attron(A_REVERSE); } mvprintw(k + offset, 2, "%s", options[k]); if (k == highlight) { attroff(A_REVERSE); } } attroff(COLOR_PAIR(2)); attron(COLOR_PAIR(1)); mvprintw(offset + optionsNumber + extraOptionsNumber, 1, "%s", topText); attroff(COLOR_PAIR(1)); key = getch(); // get some int which value correspond to some key being pressed switch (key) { case KEY_UP: case 'w': case 'W': highlight--; if (highlight < 0) { highlight = optionsNumber + extraOptionsNumber - 1; // can't select the -1nth option } break; case 's': case 'S': case KEY_DOWN: highlight++; if (highlight >= optionsNumber + extraOptionsNumber) { highlight = 0; } break; case '1': if (optionsNumber + extraOptionsNumber >= 1) { debug("Quickselected option 1."); highlight = 0; goto end_loop; } break; case '2': if (optionsNumber + extraOptionsNumber >= 1) { debug("Quickselected option 2."); highlight = 1; goto end_loop; } break; case '3': if (optionsNumber + extraOptionsNumber >= 1) { debug("Quickselected option 3."); highlight = 2; goto end_loop; } break; case '4': if (optionsNumber + extraOptionsNumber >= 1) { debug("Quickselected option 4."); highlight = 3; goto end_loop; } break; case '5': if (optionsNumber + extraOptionsNumber >= 1) { debug("Quickselected option 5."); highlight = 4; goto end_loop; } break; case '6': if (optionsNumber + extraOptionsNumber >= 1) { debug("Quickselected option 6."); highlight = 5; goto end_loop; } break; case '7': if (optionsNumber + extraOptionsNumber >= 1) { debug("Quickselected option 7."); highlight = 6; goto end_loop; } break; case '8': if (optionsNumber + extraOptionsNumber >= 1) { debug("Quickselected option 8."); highlight = 7; goto end_loop; } break; case '9': if (optionsNumber + extraOptionsNumber >= 1) { debug("Quickselected option 9."); highlight = 8; goto end_loop; } break; case 10: //\n goto end_loop; } } end_loop: endwin(); // end ncurses mode fflush(stderr); debug("Selected option: %s", options[highlight]); return options[highlight]; }