Compare commits

..

3 Commits

Author SHA1 Message Date
cada002cbf I can't take it anymore. 2025-08-02 23:53:08 +02:00
e80e835939 Progress. 2025-08-02 22:43:40 +02:00
ffec86df05 Added note. 2025-08-02 13:06:38 +02:00
4 changed files with 194 additions and 53 deletions

View File

@ -4,6 +4,8 @@ Welcome to **RZF**, the most powerful, fast, and versatile terminal-based file e
Development is mostly done manually, but later completed and refactored with several LLM's (Gemini Pro, ChatGPT Plus and Claude Pro). Development is mostly done manually, but later completed and refactored with several LLM's (Gemini Pro, ChatGPT Plus and Claude Pro).
And yes, this is a fully vibed README. I'm terrible at documenting and don't like to do it.
--- ---
## Key Features ## Key Features

215
main.c
View File

@ -5,6 +5,7 @@
#include <ncurses.h> #include <ncurses.h>
#include <pthread.h> #include <pthread.h>
#include <regex.h> #include <regex.h>
#include <signal.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -13,7 +14,6 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include <signal.h>
typedef struct { typedef struct {
char *path; char *path;
@ -87,6 +87,36 @@ char *rzf_get_file_extension(const char *filename) {
return (char *)(dot + 1); return (char *)(dot + 1);
} }
bool rzf_is_text_file(const char *path) {
const char *ext = rzf_get_file_extension(path);
// Common text file extensions
const char *text_exts[] = {
"txt", "c", "h", "cpp", "hpp", "cc", "cxx", "py", "js", "ts", "java",
"rb", "go", "rs", "sh", "bash", "zsh", "fish", "pl", "php", "html",
"htm", "css", "xml", "json", "yaml", "yml", "toml", "ini", "conf",
"cfg", "log", "md", "markdown", "rst", "tex", "vim", "el", "lisp",
"scm", "clj", "lua", "r", "m", "swift", "kt", "scala", "hs", "ml",
"fs", "pas", "d", "nim", "cr", "jl", "dart", "ex", "exs", "erl",
"hrl", "zig", "v", "sql", "cmake", "make", "dockerfile", "gitignore",
NULL
};
// Check if it has no extension (often scripts or config files)
if (strlen(ext) == 0) {
return true;
}
// Check against known text extensions
for (int i = 0; text_exts[i] != NULL; i++) {
if (strcasecmp(ext, text_exts[i]) == 0) {
return true;
}
}
return false;
}
bool rzf_matches_file_type_filter(const char *path, const char *filter) { bool rzf_matches_file_type_filter(const char *path, const char *filter) {
if (!filter || strlen(filter) < 2 || filter[0] != ':') if (!filter || strlen(filter) < 2 || filter[0] != ':')
return true; return true;
@ -419,7 +449,8 @@ void rzf_sort_files(void) {
} }
void rzf_draw_file_preview(WINDOW *win, const char *filepath) { void rzf_draw_file_preview(WINDOW *win, const char *filepath) {
if (!win) return; if (!win)
return;
werase(win); werase(win);
box(win, 0, 0); box(win, 0, 0);
@ -581,11 +612,17 @@ void *rzf_indexing_worker_func(void *arg) {
struct stat statbuf; struct stat statbuf;
if (lstat(path, &statbuf) == 0) { if (lstat(path, &statbuf) == 0) {
int is_dir = S_ISDIR(statbuf.st_mode); int is_dir = S_ISDIR(statbuf.st_mode);
char real[PATH_MAX];
if (realpath(path, real) == NULL)
continue;
local_batch[local_count].path = strdup(path); local_batch[local_count].path = strdup(path);
local_batch[local_count].lower_path = rzf_to_lower(path); local_batch[local_count].lower_path = rzf_to_lower(path);
if (!local_batch[local_count].path || !local_batch[local_count].lower_path) { if (!local_batch[local_count].path ||
if (local_batch[local_count].path) free(local_batch[local_count].path); !local_batch[local_count].lower_path) {
if (local_batch[local_count].lower_path) free(local_batch[local_count].lower_path); if (local_batch[local_count].path)
free(local_batch[local_count].path);
if (local_batch[local_count].lower_path)
free(local_batch[local_count].lower_path);
continue; continue;
} }
local_batch[local_count].size = statbuf.st_size; local_batch[local_count].size = statbuf.st_size;
@ -762,7 +799,8 @@ void rzf_draw_help_window(int height, int width) {
int start_y = (height - h) / 2; int start_y = (height - h) / 2;
int start_x = (width - w) / 2; int start_x = (width - w) / 2;
WINDOW *win = newwin(h, w, start_y, start_x); WINDOW *win = newwin(h, w, start_y, start_x);
if (!win) return; if (!win)
return;
box(win, 0, 0); box(win, 0, 0);
wattron(win, A_BOLD); wattron(win, A_BOLD);
mvwprintw(win, 1, (w - 10) / 2, "Shortcuts"); mvwprintw(win, 1, (w - 10) / 2, "Shortcuts");
@ -795,11 +833,13 @@ void rzf_draw_help_window(int height, int width) {
bool rzf_show_confirmation(int height, int width, const char *message) { bool rzf_show_confirmation(int height, int width, const char *message) {
int h = 3, w = strlen(message) + 8; int h = 3, w = strlen(message) + 8;
if (w > width - 4) w = width - 4; if (w > width - 4)
w = width - 4;
int start_y = (height - h) / 2; int start_y = (height - h) / 2;
int start_x = (width - w) / 2; int start_x = (width - w) / 2;
WINDOW *win = newwin(h, w, start_y, start_x); WINDOW *win = newwin(h, w, start_y, start_x);
if (!win) return false; if (!win)
return false;
box(win, 0, 0); box(win, 0, 0);
mvwprintw(win, 1, 2, "%.*s (y/n)", w - 8, message); mvwprintw(win, 1, 2, "%.*s (y/n)", w - 8, message);
wrefresh(win); wrefresh(win);
@ -820,11 +860,13 @@ bool rzf_show_confirmation(int height, int width, const char *message) {
char *rzf_prompt_for_command(int height, int width) { char *rzf_prompt_for_command(int height, int width) {
int h = 3, w = 60; int h = 3, w = 60;
if (w > width - 4) w = width - 4; if (w > width - 4)
w = width - 4;
int start_y = (height - h) / 2; int start_y = (height - h) / 2;
int start_x = (width - w) / 2; int start_x = (width - w) / 2;
WINDOW *win = newwin(h, w, start_y, start_x); WINDOW *win = newwin(h, w, start_y, start_x);
if (!win) return NULL; if (!win)
return NULL;
box(win, 0, 0); box(win, 0, 0);
mvwprintw(win, 1, 2, "Command: "); mvwprintw(win, 1, 2, "Command: ");
wrefresh(win); wrefresh(win);
@ -952,12 +994,79 @@ char *rzf_run_interface(char **selected_file_path) {
pthread_mutex_lock(&files_mutex); pthread_mutex_lock(&files_mutex);
int file_idx = filtered_indices[selected_index]; int file_idx = filtered_indices[selected_index];
if (file_idx < file_count) { if (file_idx < file_count) {
if (files[file_idx].is_dir) {
// Navigate into directory
char *dir_path = strdup(files[file_idx].path);
pthread_mutex_unlock(&files_mutex);
if (dir_path && chdir(dir_path) == 0) {
// Clear the queue
pthread_mutex_lock(&queue_mutex);
for (int i = 0; i < queue_count; i++) {
int idx = (queue_head + i) % DIR_QUEUE_CAPACITY;
if (dir_queue[idx]) {
free(dir_queue[idx]);
dir_queue[idx] = NULL;
}
}
queue_head = 0;
queue_tail = 0;
queue_count = 0;
pthread_mutex_unlock(&queue_mutex);
// Clear files
pthread_mutex_lock(&files_mutex);
if (files) {
for (int i = 0; i < file_count; i++) {
free(files[i].path);
free(files[i].lower_path);
}
free(files);
}
files = NULL;
file_count = 0;
file_capacity = 0;
pthread_mutex_unlock(&files_mutex);
// Cancel and restart indexing
if (indexing_started && !indexing_complete) {
pthread_cancel(indexing_thread);
pthread_join(indexing_thread, NULL);
}
indexing_complete = false;
indexing_started = false;
producer_finished = false;
active_workers = 0;
pthread_create(&indexing_thread, NULL, rzf_indexing_thread_func, NULL);
search_query[0] = '\0';
selected_index = 0;
scroll_offset = 0;
}
if (dir_path) free(dir_path);
} else {
// Open file
char abs[PATH_MAX];
if (realpath(files[file_idx].path, abs))
*selected_file_path = strdup(abs);
else
*selected_file_path = strdup(files[file_idx].path); *selected_file_path = strdup(files[file_idx].path);
command = strdup(files[file_idx].is_dir ? "xdg-open" : "vim");
// Determine whether to use vim or xdg-open
if (rzf_is_text_file(*selected_file_path)) {
command = strdup("vim");
} else {
command = strdup("xdg-open");
} }
pthread_mutex_unlock(&files_mutex); pthread_mutex_unlock(&files_mutex);
}
goto end_loop; goto end_loop;
}
} else {
pthread_mutex_unlock(&files_mutex);
}
}
break;
case 27: case 27:
case 3: case 3:
goto end_loop; goto end_loop;
@ -988,13 +1097,10 @@ char *rzf_run_interface(char **selected_file_path) {
if (dot) { if (dot) {
snprintf(backup_path, sizeof(backup_path), "%.*s/.%.*s%s%s.bak", snprintf(backup_path, sizeof(backup_path), "%.*s/.%.*s%s%s.bak",
(int)dir_len, path, (int)dir_len, path, (int)(dot - fname), fname, dt, dot);
(int)(dot - fname), fname,
dt, dot);
} else { } else {
snprintf(backup_path, sizeof(backup_path), "%.*s/.%s%s.bak", snprintf(backup_path, sizeof(backup_path), "%.*s/.%s%s.bak",
(int)dir_len, path, (int)dir_len, path, fname, dt);
fname, dt);
} }
char cmd[PATH_MAX * 2 + 10]; char cmd[PATH_MAX * 2 + 10];
@ -1097,7 +1203,8 @@ char *rzf_run_interface(char **selected_file_path) {
pthread_mutex_lock(&files_mutex); pthread_mutex_lock(&files_mutex);
for (int i = 0; i < selected_count; i++) { for (int i = 0; i < selected_count; i++) {
if (selected_indices[i] < file_count) { if (selected_indices[i] < file_count) {
paths_to_delete[delete_count] = strdup(files[selected_indices[i]].path); paths_to_delete[delete_count] =
strdup(files[selected_indices[i]].path);
is_dir_flags[delete_count] = files[selected_indices[i]].is_dir; is_dir_flags[delete_count] = files[selected_indices[i]].is_dir;
if (paths_to_delete[delete_count]) if (paths_to_delete[delete_count])
delete_count++; delete_count++;
@ -1164,7 +1271,8 @@ char *rzf_run_interface(char **selected_file_path) {
indexing_started = false; indexing_started = false;
producer_finished = false; producer_finished = false;
active_workers = 0; active_workers = 0;
pthread_create(&indexing_thread, NULL, rzf_indexing_thread_func, NULL); pthread_create(&indexing_thread, NULL, rzf_indexing_thread_func,
NULL);
} }
} }
break; break;
@ -1244,7 +1352,8 @@ char *rzf_run_interface(char **selected_file_path) {
indexing_started = false; indexing_started = false;
producer_finished = false; producer_finished = false;
active_workers = 0; active_workers = 0;
pthread_create(&indexing_thread, NULL, rzf_indexing_thread_func, NULL); pthread_create(&indexing_thread, NULL, rzf_indexing_thread_func,
NULL);
search_query[0] = '\0'; search_query[0] = '\0';
selected_index = 0; selected_index = 0;
scroll_offset = 0; scroll_offset = 0;
@ -1280,7 +1389,8 @@ char *rzf_run_interface(char **selected_file_path) {
free(fp); free(fp);
} }
} }
if (cmd) free(cmd); if (cmd)
free(cmd);
} }
break; break;
case 22: case 22:
@ -1312,7 +1422,8 @@ char *rzf_run_interface(char **selected_file_path) {
current_file_count = file_count; current_file_count = file_count;
if (current_file_count > filtered_capacity) { if (current_file_count > filtered_capacity) {
filtered_capacity = current_file_count * 2; filtered_capacity = current_file_count * 2;
int *new_filtered = realloc(filtered_indices, filtered_capacity * sizeof(int)); int *new_filtered =
realloc(filtered_indices, filtered_capacity * sizeof(int));
if (!new_filtered) { if (!new_filtered) {
pthread_mutex_unlock(&files_mutex); pthread_mutex_unlock(&files_mutex);
continue; continue;
@ -1324,7 +1435,8 @@ char *rzf_run_interface(char **selected_file_path) {
char search_pattern[256]; char search_pattern[256];
if (filter_start) { if (filter_start) {
size_t len = filter_start - search_query; size_t len = filter_start - search_query;
if (len >= sizeof(search_pattern)) len = sizeof(search_pattern) - 1; if (len >= sizeof(search_pattern))
len = sizeof(search_pattern) - 1;
strncpy(search_pattern, search_query, len); strncpy(search_pattern, search_query, len);
search_pattern[len] = '\0'; search_pattern[len] = '\0';
} else { } else {
@ -1379,7 +1491,8 @@ char *rzf_run_interface(char **selected_file_path) {
for (int i = 0; i < list_height && (i + scroll_offset) < filtered_count; for (int i = 0; i < list_height && (i + scroll_offset) < filtered_count;
++i) { ++i) {
int current_index = i + scroll_offset; int current_index = i + scroll_offset;
if (current_index >= filtered_count) continue; if (current_index >= filtered_count)
continue;
int file_idx = filtered_indices[current_index]; int file_idx = filtered_indices[current_index];
if (file_idx >= file_count) if (file_idx >= file_count)
continue; continue;
@ -1502,13 +1615,44 @@ int main() {
signal(SIGHUP, rzf_cleanup_terminal); signal(SIGHUP, rzf_cleanup_terminal);
pthread_create(&indexing_thread, NULL, rzf_indexing_thread_func, NULL); pthread_create(&indexing_thread, NULL, rzf_indexing_thread_func, NULL);
char *file_to_open = NULL; char *file_to_open = NULL;
char *command = rzf_run_interface(&file_to_open); char *command = rzf_run_interface(&file_to_open);
if (command && file_to_open) {
// Fork and execute
pid_t pid = fork();
if (pid == 0) {
// Child process
if (strcmp(command, "xdg-open") == 0) {
// For xdg-open, we need to detach from the terminal
setsid();
// Redirect stdout/stderr to /dev/null to avoid terminal corruption
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "w", stderr);
}
execlp(command, command, file_to_open, (char *)NULL);
perror("execlp failed");
exit(EXIT_FAILURE);
} else if (pid > 0) {
// Parent process
if (strcmp(command, "vim") == 0) {
// Wait for vim to finish
wait(NULL);
}
// For xdg-open, don't wait - let it run in background
} else {
perror("fork failed");
}
}
return 0;
// Clean up indexing thread
if (indexing_started && !indexing_complete) { if (indexing_started && !indexing_complete) {
pthread_cancel(indexing_thread); pthread_cancel(indexing_thread);
} }
pthread_join(indexing_thread, NULL); pthread_join(indexing_thread, NULL);
// Clean up queue
pthread_mutex_lock(&queue_mutex); pthread_mutex_lock(&queue_mutex);
for (int i = 0; i < queue_count; i++) { for (int i = 0; i < queue_count; i++) {
int idx = (queue_head + i) % DIR_QUEUE_CAPACITY; int idx = (queue_head + i) % DIR_QUEUE_CAPACITY;
@ -1519,29 +1663,24 @@ int main() {
} }
pthread_mutex_unlock(&queue_mutex); pthread_mutex_unlock(&queue_mutex);
if (command && file_to_open) { // Execute the command if we have one
reset_shell_mode();
pid_t pid = fork(); // Clean up memory
if (pid == 0) { if (file_to_open)
execlp(command, command, file_to_open, NULL);
perror("execlp failed");
exit(EXIT_FAILURE);
} else if (pid > 0) {
wait(NULL);
} else {
perror("fork failed");
}
free(file_to_open); free(file_to_open);
} if (command)
if (command) free(command); free(command);
rzf_free_files(); rzf_free_files();
rzf_free_bookmarks(); rzf_free_bookmarks();
// Destroy mutexes
pthread_mutex_destroy(&files_mutex); pthread_mutex_destroy(&files_mutex);
pthread_mutex_destroy(&queue_mutex); pthread_mutex_destroy(&queue_mutex);
pthread_mutex_destroy(&git_queue_mutex); pthread_mutex_destroy(&git_queue_mutex);
pthread_mutex_destroy(&git_root_mutex); pthread_mutex_destroy(&git_root_mutex);
pthread_mutex_destroy(&preview_mutex); pthread_mutex_destroy(&preview_mutex);
pthread_cond_destroy(&queue_cond); pthread_cond_destroy(&queue_cond);
return 0; return 0;
} }

BIN
main.o

Binary file not shown.

BIN
rzf

Binary file not shown.