From d8e65b17990967c75f85dbe34cf1071ca7e80e9f Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Tue, 27 Jan 2026 10:24:23 -0400 Subject: [PATCH] Version v0.3.49 - Working on directory naviagation --- dir_nav | Bin 0 -> 16984 bytes src/main.h | 1 + src/ui.c | 290 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/util.c | 37 ++++++- 4 files changed, 313 insertions(+), 15 deletions(-) create mode 100755 dir_nav diff --git a/dir_nav b/dir_nav new file mode 100755 index 0000000000000000000000000000000000000000..cba0efd5b959b76c53898e9c32674038cb8b97d4 GIT binary patch literal 16984 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 == DT_DIR) { + // Skip . and .. + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + if (count >= capacity) { + capacity *= 2; + *subdirs = realloc(*subdirs, capacity * sizeof(char*)); + } + (*subdirs)[count++] = strdup(entry->d_name); + } + } + closedir(dir); + + // Sort alphabetically + if (count > 0) { + qsort(*subdirs, count, sizeof(char*), compare_dir_strings); + } + + return count; +} + +// Function to get a single keypress without echo +static int getch_nav(void) { + struct termios oldt, newt; + int ch; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + ch = getchar(); + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + return ch; +} + +// Navigate directories with arrow keys +static char* navigate_directory_interactive(void) { + char current_path[PATH_MAX]; + char original_path[PATH_MAX]; + getcwd(original_path, sizeof(original_path)); + getcwd(current_path, sizeof(current_path)); + + char **subdirs = NULL; + int subdir_count = 0; + int current_index = 0; + + printf("\nNavigate with arrow keys (UP/DOWN: cycle, RIGHT: enter, LEFT: back, ENTER: select, Q: cancel)\n"); + printf("\033[?25l"); // Hide cursor + + while (1) { + // Clear line and show current path + printf("\r\033[K%s", current_path); + + // If we have subdirectories and an index, show current selection + if (subdir_count > 0 && current_index < subdir_count) { + printf("/%s", subdirs[current_index]); + } + + fflush(stdout); + + int ch = getch_nav(); + + // Handle arrow keys (they come as escape sequences) + if (ch == 27) { // ESC sequence + getch_nav(); // Skip '[' + ch = getch_nav(); + + if (ch == 'A') { // UP arrow + // Load subdirs if not loaded + if (subdirs == NULL) { + subdir_count = get_subdirs(current_path, &subdirs); + current_index = 0; + } else if (subdir_count > 0) { + current_index = (current_index - 1 + subdir_count) % subdir_count; + } + } + else if (ch == 'B') { // DOWN arrow + // Load subdirs if not loaded + if (subdirs == NULL) { + subdir_count = get_subdirs(current_path, &subdirs); + current_index = 0; + } else if (subdir_count > 0) { + current_index = (current_index + 1) % subdir_count; + } + } + else if (ch == 'C') { // RIGHT arrow - go deeper + if (subdir_count > 0 && current_index < subdir_count) { + // Navigate into selected directory + char new_path[PATH_MAX]; + snprintf(new_path, sizeof(new_path), "%s/%s", current_path, subdirs[current_index]); + + if (chdir(new_path) == 0) { + getcwd(current_path, sizeof(current_path)); + + // Free old subdirs + for (int i = 0; i < subdir_count; i++) { + free(subdirs[i]); + } + free(subdirs); + subdirs = NULL; + + // Load subdirs of new directory and show first one + subdir_count = get_subdirs(current_path, &subdirs); + current_index = 0; + } + } + } + else if (ch == 'D') { // LEFT arrow - go up + if (chdir("..") == 0) { + getcwd(current_path, sizeof(current_path)); + + // Free old subdirs + for (int i = 0; i < subdir_count; i++) { + free(subdirs[i]); + } + free(subdirs); + subdirs = NULL; + subdir_count = 0; + current_index = 0; + } + } + } + else if (ch == '\n' || ch == '\r') { // ENTER - confirm selection + // If a subdirectory is displayed, navigate into it first + if (subdir_count > 0 && current_index < subdir_count) { + char new_path[PATH_MAX]; + snprintf(new_path, sizeof(new_path), "%s/%s", current_path, subdirs[current_index]); + + if (chdir(new_path) == 0) { + getcwd(current_path, sizeof(current_path)); + } + } + break; + } + else if (ch == 'q' || ch == 'Q') { // Q to quit + printf("\033[?25h\n"); // Show cursor + // Restore original directory + chdir(original_path); + // Clean up + for (int i = 0; i < subdir_count; i++) { + free(subdirs[i]); + } + free(subdirs); + return NULL; + } + } + + printf("\033[?25h\n"); // Show cursor + + // Clean up + for (int i = 0; i < subdir_count; i++) { + free(subdirs[i]); + } + free(subdirs); + + // Restore original directory before returning + char* result = strdup(current_path); + chdir(original_path); + + return result; +} + int handle_directory_encrypt(void) { printf("\n"); print_centered_header("Directory Encrypt", 0); @@ -571,8 +804,9 @@ int handle_directory_encrypt(void) { // Directory selection options printf("\nDirectory selection options:\n"); printf(" 1. Type directory path directly\n"); - printf(" 2. Use file manager (navigate to directory)\n"); - printf("Enter choice (1-2): "); + printf(" 2. Navigate with arrow keys\n"); + printf(" 3. Use file manager (navigate to directory)\n"); + printf("Enter choice (1-3) or 'esc' to cancel: "); char choice_input[10]; char dir_path[512]; @@ -582,25 +816,59 @@ int handle_directory_encrypt(void) { return 1; } - if (atoi(choice_input) == 2) { + choice_input[strcspn(choice_input, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(choice_input)) { + printf("Returning to main menu...\n"); + return 0; + } + + int choice = atoi(choice_input); + + if (choice == 2) { + // Use arrow key navigation + char* selected = navigate_directory_interactive(); + if (!selected) { + printf("Directory selection cancelled.\n"); + return 0; + } + strncpy(dir_path, selected, sizeof(dir_path) - 1); + dir_path[sizeof(dir_path) - 1] = '\0'; + free(selected); + printf("Selected: %s\n", dir_path); + } + else if (choice == 3) { // Use directory manager if (launch_directory_manager(".", dir_path, sizeof(dir_path)) != 0) { printf("Falling back to manual directory path entry.\n"); - printf("Enter directory path to encrypt: "); + printf("Enter directory path to encrypt (or 'esc' to cancel): "); if (!fgets(dir_path, sizeof(dir_path), stdin)) { printf("Error: Failed to read input\n"); return 1; } dir_path[strcspn(dir_path, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(dir_path)) { + printf("Returning to main menu...\n"); + return 0; + } } } else { // Direct directory path input - printf("Enter directory path to encrypt: "); + printf("Enter directory path to encrypt (or 'esc' to cancel): "); if (!fgets(dir_path, sizeof(dir_path), stdin)) { printf("Error: Failed to read input\n"); return 1; } dir_path[strcspn(dir_path, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(dir_path)) { + printf("Returning to main menu...\n"); + return 0; + } } // Check if directory exists diff --git a/src/util.c b/src/util.c index 4da0756..1d768fc 100644 --- a/src/util.c +++ b/src/util.c @@ -162,7 +162,8 @@ int launch_text_editor(const char* initial_content, char* result_buffer, size_t char* get_preferred_file_manager(void) { // Try file managers in order of preference - const char* file_managers[] = {"ranger", "fzf", "nnn", "lf", NULL}; + // fzf is first because it's more intuitive with fuzzy search + const char* file_managers[] = {"fzf", "ranger", "nnn", "lf", NULL}; for (int i = 0; file_managers[i] != NULL; i++) { char command[512]; @@ -178,7 +179,8 @@ char* get_preferred_file_manager(void) { int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size) { char* fm = get_preferred_file_manager(); if (!fm) { - printf("No file manager found. Please install ranger, fzf, nnn, or lf.\n"); + printf("No file manager found. Please install fzf, ranger, nnn, or lf.\n"); + printf("Recommended: sudo apt install fzf\n"); printf("Falling back to manual file path entry.\n"); return 1; // Fall back to manual entry } @@ -190,6 +192,13 @@ int launch_file_manager(const char* start_directory, char* selected_file, size_t int result = 1; printf("Opening %s for file selection...\n", fm); + + // Show helpful instructions based on file manager + if (strcmp(fm, "fzf") == 0) { + printf("Instructions: Type to search, use arrow keys, press Enter to select\n"); + } else if (strcmp(fm, "ranger") == 0) { + printf("Instructions: Arrow keys or j/k to navigate, Enter or l to select, q to quit\n"); + } if (strcmp(fm, "ranger") == 0) { snprintf(command, sizeof(command), "cd '%s' && ranger --choosefile=%s", @@ -244,7 +253,8 @@ int launch_file_manager(const char* start_directory, char* selected_file, size_t int launch_directory_manager(const char* start_directory, char* selected_dir, size_t buffer_size) { char* fm = get_preferred_file_manager(); if (!fm) { - printf("No file manager found. Please install ranger, fzf, nnn, or lf.\n"); + printf("No file manager found. Please install fzf, ranger, nnn, or lf.\n"); + printf("Recommended: sudo apt install fzf\n"); printf("Falling back to manual directory path entry.\n"); return 1; // Fall back to manual entry } @@ -256,7 +266,13 @@ int launch_directory_manager(const char* start_directory, char* selected_dir, si int result = 1; printf("Opening %s for directory selection...\n", fm); - printf("Navigate INTO the directory you want to encrypt, then press 'q' to quit and select it.\n"); + + // Show helpful instructions based on file manager + if (strcmp(fm, "fzf") == 0) { + printf("Instructions: Type to search, use arrow keys, press Enter to select directory\n"); + } else if (strcmp(fm, "ranger") == 0) { + printf("Instructions: Navigate INTO the directory, then press 'q' to quit and select it\n"); + } if (strcmp(fm, "ranger") == 0) { snprintf(command, sizeof(command), "cd '%s' && ranger --choosedir=%s", @@ -318,6 +334,19 @@ int launch_directory_manager(const char* start_directory, char* selected_dir, si return 1; // Fall back to manual entry } +// Helper function to check if input contains ESC key +int is_escape_input(const char* input) { + // Check for ESC character (ASCII 27) or empty input after ESC + if (input && (input[0] == 27 || (input[0] == '\0' && strlen(input) == 0))) { + return 1; + } + // Also check for literal "esc" or "ESC" typed + if (input && (strcasecmp(input, "esc") == 0 || strcasecmp(input, "q") == 0)) { + return 1; + } + return 0; +} + // Stdin detection functions implementation int has_stdin_data(void) { // Check if stdin is a pipe/redirect (not a terminal)