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).
And yes, this is a fully vibed README. I'm terrible at documenting and don't like to do it.
---
## Key Features

245
main.c
View File

@ -5,6 +5,7 @@
#include <ncurses.h>
#include <pthread.h>
#include <regex.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@ -13,7 +14,6 @@
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
typedef struct {
char *path;
@ -87,6 +87,36 @@ char *rzf_get_file_extension(const char *filename) {
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) {
if (!filter || strlen(filter) < 2 || filter[0] != ':')
return true;
@ -419,7 +449,8 @@ void rzf_sort_files(void) {
}
void rzf_draw_file_preview(WINDOW *win, const char *filepath) {
if (!win) return;
if (!win)
return;
werase(win);
box(win, 0, 0);
@ -581,11 +612,17 @@ void *rzf_indexing_worker_func(void *arg) {
struct stat statbuf;
if (lstat(path, &statbuf) == 0) {
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].lower_path = rzf_to_lower(path);
if (!local_batch[local_count].path || !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);
if (!local_batch[local_count].path ||
!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;
}
local_batch[local_count].size = statbuf.st_size;
@ -714,7 +751,7 @@ int rzf_recursive_delete(const char *path) {
struct stat path_stat;
if (lstat(path, &path_stat) != 0)
return -1;
if (!S_ISDIR(path_stat.st_mode)) {
return remove(path);
}
@ -762,7 +799,8 @@ void rzf_draw_help_window(int height, int width) {
int start_y = (height - h) / 2;
int start_x = (width - w) / 2;
WINDOW *win = newwin(h, w, start_y, start_x);
if (!win) return;
if (!win)
return;
box(win, 0, 0);
wattron(win, A_BOLD);
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) {
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_x = (width - w) / 2;
WINDOW *win = newwin(h, w, start_y, start_x);
if (!win) return false;
if (!win)
return false;
box(win, 0, 0);
mvwprintw(win, 1, 2, "%.*s (y/n)", w - 8, message);
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) {
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_x = (width - w) / 2;
WINDOW *win = newwin(h, w, start_y, start_x);
if (!win) return NULL;
if (!win)
return NULL;
box(win, 0, 0);
mvwprintw(win, 1, 2, "Command: ");
wrefresh(win);
@ -952,12 +994,79 @@ char *rzf_run_interface(char **selected_file_path) {
pthread_mutex_lock(&files_mutex);
int file_idx = filtered_indices[selected_index];
if (file_idx < file_count) {
*selected_file_path = strdup(files[file_idx].path);
command = strdup(files[file_idx].is_dir ? "xdg-open" : "vim");
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);
// 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);
goto end_loop;
}
} else {
pthread_mutex_unlock(&files_mutex);
}
pthread_mutex_unlock(&files_mutex);
}
goto end_loop;
break;
case 27:
case 3:
goto end_loop;
@ -988,13 +1097,10 @@ char *rzf_run_interface(char **selected_file_path) {
if (dot) {
snprintf(backup_path, sizeof(backup_path), "%.*s/.%.*s%s%s.bak",
(int)dir_len, path,
(int)(dot - fname), fname,
dt, dot);
(int)dir_len, path, (int)(dot - fname), fname, dt, dot);
} else {
snprintf(backup_path, sizeof(backup_path), "%.*s/.%s%s.bak",
(int)dir_len, path,
fname, dt);
(int)dir_len, path, fname, dt);
}
char cmd[PATH_MAX * 2 + 10];
@ -1090,21 +1196,22 @@ char *rzf_run_interface(char **selected_file_path) {
}
if (confirmed) {
if (selected_count > 0) {
char **paths_to_delete = malloc(selected_count * sizeof(char*));
char **paths_to_delete = malloc(selected_count * sizeof(char *));
int *is_dir_flags = malloc(selected_count * sizeof(int));
int delete_count = 0;
pthread_mutex_lock(&files_mutex);
for (int i = 0; i < selected_count; i++) {
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;
if (paths_to_delete[delete_count])
delete_count++;
}
}
pthread_mutex_unlock(&files_mutex);
for (int i = 0; i < delete_count; i++) {
if (is_dir_flags[i])
rzf_recursive_delete(paths_to_delete[i]);
@ -1129,7 +1236,7 @@ char *rzf_run_interface(char **selected_file_path) {
free(p);
}
}
pthread_mutex_lock(&queue_mutex);
for (int i = 0; i < queue_count; i++) {
int idx = (queue_head + i) % DIR_QUEUE_CAPACITY;
@ -1142,7 +1249,7 @@ char *rzf_run_interface(char **selected_file_path) {
queue_tail = 0;
queue_count = 0;
pthread_mutex_unlock(&queue_mutex);
pthread_mutex_lock(&files_mutex);
if (files) {
for (int i = 0; i < file_count; i++) {
@ -1155,7 +1262,7 @@ char *rzf_run_interface(char **selected_file_path) {
file_count = 0;
file_capacity = 0;
pthread_mutex_unlock(&files_mutex);
if (indexing_started && !indexing_complete) {
pthread_cancel(indexing_thread);
pthread_join(indexing_thread, NULL);
@ -1164,7 +1271,8 @@ char *rzf_run_interface(char **selected_file_path) {
indexing_started = false;
producer_finished = false;
active_workers = 0;
pthread_create(&indexing_thread, NULL, rzf_indexing_thread_func, NULL);
pthread_create(&indexing_thread, NULL, rzf_indexing_thread_func,
NULL);
}
}
break;
@ -1222,7 +1330,7 @@ char *rzf_run_interface(char **selected_file_path) {
queue_tail = 0;
queue_count = 0;
pthread_mutex_unlock(&queue_mutex);
pthread_mutex_lock(&files_mutex);
if (files) {
for (int i = 0; i < file_count; i++) {
@ -1235,7 +1343,7 @@ char *rzf_run_interface(char **selected_file_path) {
file_count = 0;
file_capacity = 0;
pthread_mutex_unlock(&files_mutex);
if (indexing_started && !indexing_complete) {
pthread_cancel(indexing_thread);
pthread_join(indexing_thread, NULL);
@ -1244,7 +1352,8 @@ char *rzf_run_interface(char **selected_file_path) {
indexing_started = false;
producer_finished = false;
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';
selected_index = 0;
scroll_offset = 0;
@ -1280,7 +1389,8 @@ char *rzf_run_interface(char **selected_file_path) {
free(fp);
}
}
if (cmd) free(cmd);
if (cmd)
free(cmd);
}
break;
case 22:
@ -1312,7 +1422,8 @@ char *rzf_run_interface(char **selected_file_path) {
current_file_count = file_count;
if (current_file_count > filtered_capacity) {
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) {
pthread_mutex_unlock(&files_mutex);
continue;
@ -1324,7 +1435,8 @@ char *rzf_run_interface(char **selected_file_path) {
char search_pattern[256];
if (filter_start) {
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);
search_pattern[len] = '\0';
} 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;
++i) {
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];
if (file_idx >= file_count)
continue;
@ -1500,15 +1613,46 @@ int main() {
signal(SIGINT, rzf_cleanup_terminal);
signal(SIGTERM, rzf_cleanup_terminal);
signal(SIGHUP, rzf_cleanup_terminal);
pthread_create(&indexing_thread, NULL, rzf_indexing_thread_func, NULL);
char *file_to_open = NULL;
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) {
pthread_cancel(indexing_thread);
}
pthread_join(indexing_thread, NULL);
// Clean up queue
pthread_mutex_lock(&queue_mutex);
for (int i = 0; i < queue_count; i++) {
int idx = (queue_head + i) % DIR_QUEUE_CAPACITY;
@ -1518,30 +1662,25 @@ int main() {
}
}
pthread_mutex_unlock(&queue_mutex);
if (command && file_to_open) {
reset_shell_mode();
pid_t pid = fork();
if (pid == 0) {
execlp(command, command, file_to_open, NULL);
perror("execlp failed");
exit(EXIT_FAILURE);
} else if (pid > 0) {
wait(NULL);
} else {
perror("fork failed");
}
// Execute the command if we have one
// Clean up memory
if (file_to_open)
free(file_to_open);
}
if (command) free(command);
if (command)
free(command);
rzf_free_files();
rzf_free_bookmarks();
// Destroy mutexes
pthread_mutex_destroy(&files_mutex);
pthread_mutex_destroy(&queue_mutex);
pthread_mutex_destroy(&git_queue_mutex);
pthread_mutex_destroy(&git_root_mutex);
pthread_mutex_destroy(&preview_mutex);
pthread_cond_destroy(&queue_cond);
return 0;
}

BIN
main.o

Binary file not shown.

BIN
rzf

Binary file not shown.