diff --git a/Makefile b/Makefile index 9e7e92d..1a2307a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC = gcc -CFLAGS = -Wall -Wextra -g +CFLAGS = -Wextra -g LIBS = -lm # Get all .c files @@ -19,4 +19,4 @@ $(TARGET): $(OBJECTS) clean: rm -f $(OBJECTS) $(TARGET) -.PHONY: all clean \ No newline at end of file +.PHONY: all clean diff --git a/builtins.c b/builtins.c index 0fbd3c4..77f1a0f 100644 --- a/builtins.c +++ b/builtins.c @@ -36,7 +36,7 @@ char *builtin_str[] = { "bookmark", "bookmarks", "goto", "unbookmark", "focus_timer", "weather", "grep", "grep-text", "ripgrep", "fzf", "clip", "echo", "theme", "loc", "git_status", - "gg"}; + "gg", "ls",}; // Array of function pointers to built-in command implementations int (*builtin_func[])(char **) = { @@ -47,7 +47,7 @@ int (*builtin_func[])(char **) = { &lsh_bookmark, &lsh_bookmarks, &lsh_goto, &lsh_unbookmark, &lsh_focus_timer, &lsh_weather, &lsh_grep, &lsh_actual_grep, &lsh_ripgrep, &lsh_fzf_native, &lsh_clip, &lsh_echo, &lsh_theme, &lsh_loc, &lsh_git_status, - &lsh_gg}; + &lsh_gg, lsh_dir,}; /** * Set the console text color @@ -184,142 +184,125 @@ int lsh_exit(char **args) { return 0; } * Lists files in the current directory */ int lsh_dir(char **args) { - DIR *dir; - struct dirent *entry; - struct stat file_stat; - char cwd[PATH_MAX]; - char file_path[PATH_MAX]; - int count = 0; - int detailed = 0; + DIR *dir; + struct dirent *entry; + struct stat file_stat; + char cwd[PATH_MAX]; + char file_path[PATH_MAX]; + int detailed = 0; - // Check if we should show detailed listing - if (args[1] != NULL && (strcmp(args[1], "-l") == 0 || strcmp(args[1], "--long") == 0)) { - detailed = 1; - } + // Create table headers + char *headers[] = {"Name", "Size", "Type", "Modified"}; + TableData *table = create_table(headers, 4); + if (!table) { + fprintf(stderr, "lsh: failed to create table\n"); + return 1; + } - if (getcwd(cwd, sizeof(cwd)) == NULL) { - perror("lsh: getcwd"); - return 1; - } + if (getcwd(cwd, sizeof(cwd)) == NULL) { + perror("lsh: getcwd"); + free_table(table); + return 1; + } - dir = opendir(cwd); - if (dir == NULL) { - perror("lsh: opendir"); - return 1; - } + dir = opendir(cwd); + if (dir == NULL) { + perror("lsh: opendir"); + free_table(table); + return 1; + } - if (detailed) { - printf("Mode Size Modified Name\n"); - printf("----------------------------------------------------\n"); - } + // Collect all entries + while ((entry = readdir(dir)) != NULL) { + // Skip . and .. entries + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } - // First collect all entries - typedef struct { - char name[256]; - struct stat stat; - int is_dir; - } DirEntry; + snprintf(file_path, sizeof(file_path), "%s/%s", cwd, entry->d_name); + if (stat(file_path, &file_stat) == 0) { + // Create a new row + DataValue *row = (DataValue*)malloc(4 * sizeof(DataValue)); + if (!row) { + fprintf(stderr, "lsh: allocation error\n"); + continue; + } - DirEntry *entries = malloc(1000 * sizeof(DirEntry)); // Arbitrary limit - int entry_count = 0; + // Name column + row[0].type = TYPE_STRING; + row[0].value.str_val = strdup(entry->d_name); + row[0].is_highlighted = S_ISDIR(file_stat.st_mode); // Highlight directories - while ((entry = readdir(dir)) != NULL && entry_count < 1000) { - // Skip . and .. entries - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { - continue; - } + // Size column + row[1].type = TYPE_SIZE; + row[1].value.str_val = malloc(32); + if (S_ISDIR(file_stat.st_mode)) { + strcpy(row[1].value.str_val, ""); + } else if (file_stat.st_size < 1024) { + snprintf(row[1].value.str_val, 32, "%d B", (int)file_stat.st_size); + } else if (file_stat.st_size < 1024 * 1024) { + snprintf(row[1].value.str_val, 32, "%.1f KB", file_stat.st_size / 1024.0); + } else { + snprintf(row[1].value.str_val, 32, "%.1f MB", file_stat.st_size / (1024.0 * + 1024.0)); + } + row[1].is_highlighted = 0; - snprintf(file_path, sizeof(file_path), "%s/%s", cwd, entry->d_name); - if (stat(file_path, &file_stat) == 0) { - strcpy(entries[entry_count].name, entry->d_name); - entries[entry_count].stat = file_stat; - entries[entry_count].is_dir = S_ISDIR(file_stat.st_mode); - entry_count++; - } - } + // Type column + row[2].type = TYPE_STRING; + if (S_ISDIR(file_stat.st_mode)) { + row[2].value.str_val = strdup("Directory"); + } else if (S_ISREG(file_stat.st_mode)) { + // Try to determine file type by extension + char *ext = strrchr(entry->d_name, '.'); + if (ext != NULL) { + ext++; // Skip the dot + if (strcasecmp(ext, "c") == 0 || strcasecmp(ext, "cpp") == 0 || + strcasecmp(ext, "h") == 0 || strcasecmp(ext, "hpp") == 0) { + row[2].value.str_val = strdup("Source"); + } else if (strcasecmp(ext, "exe") == 0 || strcasecmp(ext, "bat") == 0 || + strcasecmp(ext, "sh") == 0 || strcasecmp(ext, "com") == 0) { + row[2].value.str_val = strdup("Executable"); + } else if (strcasecmp(ext, "txt") == 0 || strcasecmp(ext, "md") == 0 || + strcasecmp(ext, "log") == 0) { + row[2].value.str_val = strdup("Text"); + } else if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "png") == 0 || + strcasecmp(ext, "gif") == 0 || strcasecmp(ext, "bmp") == 0) { + row[2].value.str_val = strdup("Image"); + } else { + row[2].value.str_val = strdup("File"); + } + } else { + row[2].value.str_val = strdup("File"); + } + } else { + row[2].value.str_val = strdup("Special"); + } + row[2].is_highlighted = 0; - closedir(dir); + // Modified date column + row[3].type = TYPE_STRING; + row[3].value.str_val = malloc(32); + struct tm *tm_info = localtime(&file_stat.st_mtime); + strftime(row[3].value.str_val, 32, "%Y-%m-%d %H:%M", tm_info); + row[3].is_highlighted = 0; - // Sort entries - directories first then files - for (int i = 0; i < entry_count - 1; i++) { - for (int j = i + 1; j < entry_count; j++) { - if (entries[i].is_dir < entries[j].is_dir || - (entries[i].is_dir == entries[j].is_dir && - strcmp(entries[i].name, entries[j].name) > 0)) { - DirEntry temp = entries[i]; - entries[i] = entries[j]; - entries[j] = temp; - } - } - } + // Add row to table + add_table_row(table, row); + } + } - // Display entries - for (int i = 0; i < entry_count; i++) { - if (detailed) { - // Format mode (simplified) - char mode[11] = "----------"; - if (entries[i].is_dir) mode[0] = 'd'; - if (entries[i].stat.st_mode & S_IRUSR) mode[1] = 'r'; - if (entries[i].stat.st_mode & S_IWUSR) mode[2] = 'w'; - if (entries[i].stat.st_mode & S_IXUSR) mode[3] = 'x'; - if (entries[i].stat.st_mode & S_IRGRP) mode[4] = 'r'; - if (entries[i].stat.st_mode & S_IWGRP) mode[5] = 'w'; - if (entries[i].stat.st_mode & S_IXGRP) mode[6] = 'x'; - if (entries[i].stat.st_mode & S_IROTH) mode[7] = 'r'; - if (entries[i].stat.st_mode & S_IWOTH) mode[8] = 'w'; - if (entries[i].stat.st_mode & S_IXOTH) mode[9] = 'x'; + closedir(dir); - // Format size - char size_str[20]; - if (entries[i].stat.st_size < 1024) { - snprintf(size_str, sizeof(size_str), "%d B", (int)entries[i].stat.st_size); - } else if (entries[i].stat.st_size < 1024 * 1024) { - snprintf(size_str, sizeof(size_str), "%.1f KB", - entries[i].stat.st_size / 1024.0); - } else { - snprintf(size_str, sizeof(size_str), "%.1f MB", - entries[i].stat.st_size / (1024.0 * 1024.0)); - } + // Print the table + print_table(table); - // Format time - char time_str[20]; - struct tm *tm_info = localtime(&entries[i].stat.st_mtime); - strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", tm_info); + // Free the table + free_table(table); - // Print entry with colors - printf("%s %-10s %-17s ", mode, size_str, time_str); - - if (entries[i].is_dir) { - printf(ANSI_COLOR_BLUE "%s" ANSI_COLOR_RESET "\n", entries[i].name); - } else if (entries[i].stat.st_mode & S_IXUSR) { - printf(ANSI_COLOR_GREEN "%s" ANSI_COLOR_RESET "\n", entries[i].name); - } else { - printf("%s\n", entries[i].name); - } - } else { - count++; - if (entries[i].is_dir) { - printf(ANSI_COLOR_BLUE "%-20s" ANSI_COLOR_RESET, entries[i].name); - } else if (entries[i].stat.st_mode & S_IXUSR) { - printf(ANSI_COLOR_GREEN "%-20s" ANSI_COLOR_RESET, entries[i].name); - } else { - printf("%-20s", entries[i].name); - } - - if (count % 4 == 0) { - printf("\n"); - } - } - } + return 1; + } - free(entries); - - if (!detailed && count % 4 != 0) { - printf("\n"); - } - - return 1; -} /** * Built-in command: clear @@ -819,4 +802,4 @@ int lsh_gg(char **args) { } return 1; -} \ No newline at end of file +} diff --git a/builtins.h b/builtins.h index 04101ad..23d01fb 100644 --- a/builtins.h +++ b/builtins.h @@ -1,5 +1,5 @@ /** - * builtins.h + * builtins.h * Declarations for all built-in shell commands */ @@ -97,4 +97,4 @@ extern int (*builtin_func[])(char **); */ char *extract_json_string(const char *json, const char *key); -#endif // BUILTINS_H \ No newline at end of file +#endif // BUILTINS_H diff --git a/builtins.o b/builtins.o index 235df55..6ca505d 100644 Binary files a/builtins.o and b/builtins.o differ diff --git a/shell b/shell index cec6d47..6ea9e30 100755 Binary files a/shell and b/shell differ diff --git a/shell.c b/shell.c index f768e64..19ce6b0 100644 --- a/shell.c +++ b/shell.c @@ -22,13 +22,15 @@ #include #include #include +#include // For DIR, opendir, readdir +#include // For stat // Global variables for status bar static int g_console_width = 80; static int g_status_line = 0; -static WORD g_normal_attributes = 7; // Default white on black -static WORD g_status_attributes = 12; // Red -static BOOL g_status_bar_enabled = FALSE; // Flag to track if status bar is enabled +static int g_normal_attributes = 7; // Default white on black +static int g_status_attributes = 12; // Red +static int g_status_bar_enabled = 0; // Flag to track if status bar is enabled static struct termios g_orig_termios; // Original terminal settings /** @@ -160,7 +162,7 @@ int init_status_bar(int fd) { g_console_width = width; g_status_line = height; - g_status_bar_enabled = TRUE; + g_status_bar_enabled = 1; // Clear status line printf(ANSI_SAVE_CURSOR); @@ -328,6 +330,205 @@ void get_path_display(const char *cwd, char *parent_dir_name, } } +/** + * Create a TableData structure from an ls command + */ +TableData* create_ls_table(char **args) { + DIR *dir; + struct dirent *entry; + struct stat file_stat; + char cwd[PATH_MAX]; + char file_path[PATH_MAX]; + + // Create table headers + char *headers[] = {"Name", "Size", "Type", "Modified"}; + TableData *table = create_table(headers, 4); + if (!table) { + fprintf(stderr, "lsh: failed to create table\n"); + return NULL; + } + + if (getcwd(cwd, sizeof(cwd)) == NULL) { + perror("lsh: getcwd"); + free_table(table); + return NULL; + } + + dir = opendir(cwd); + if (dir == NULL) { + perror("lsh: opendir"); + free_table(table); + return NULL; + } + + // Collect all entries + while ((entry = readdir(dir)) != NULL) { + // Skip . and .. entries + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + snprintf(file_path, sizeof(file_path), "%s/%s", cwd, entry->d_name); + if (stat(file_path, &file_stat) == 0) { + // Create a new row + DataValue *row = (DataValue*)malloc(4 * sizeof(DataValue)); + if (!row) { + fprintf(stderr, "lsh: allocation error\n"); + continue; + } + + // Name column + row[0].type = TYPE_STRING; + row[0].value.str_val = strdup(entry->d_name); + row[0].is_highlighted = S_ISDIR(file_stat.st_mode); // Highlight directories + + // Size column + row[1].type = TYPE_SIZE; + row[1].value.str_val = malloc(32); + if (S_ISDIR(file_stat.st_mode)) { + strcpy(row[1].value.str_val, ""); + } else if (file_stat.st_size < 1024) { + snprintf(row[1].value.str_val, 32, "%d B", (int)file_stat.st_size); + } else if (file_stat.st_size < 1024 * 1024) { + snprintf(row[1].value.str_val, 32, "%.1f KB", file_stat.st_size / 1024.0); + } else { + snprintf(row[1].value.str_val, 32, "%.1f MB", file_stat.st_size / (1024.0 * 1024.0)); + } + row[1].is_highlighted = 0; + + // Type column + row[2].type = TYPE_STRING; + if (S_ISDIR(file_stat.st_mode)) { + row[2].value.str_val = strdup("Directory"); + } else if (S_ISREG(file_stat.st_mode)) { + // Try to determine file type by extension + char *ext = strrchr(entry->d_name, '.'); + if (ext != NULL) { + ext++; // Skip the dot + if (strcasecmp(ext, "c") == 0 || strcasecmp(ext, "cpp") == 0 || + strcasecmp(ext, "h") == 0 || strcasecmp(ext, "hpp") == 0) { + row[2].value.str_val = strdup("Source"); + } else if (strcasecmp(ext, "exe") == 0 || strcasecmp(ext, "bat") == 0 || + strcasecmp(ext, "sh") == 0 || strcasecmp(ext, "com") == 0) { + row[2].value.str_val = strdup("Executable"); + } else if (strcasecmp(ext, "txt") == 0 || strcasecmp(ext, "md") == 0 || + strcasecmp(ext, "log") == 0) { + row[2].value.str_val = strdup("Text"); + } else if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "png") == 0 || + strcasecmp(ext, "gif") == 0 || strcasecmp(ext, "bmp") == 0) { + row[2].value.str_val = strdup("Image"); + } else { + row[2].value.str_val = strdup("File"); + } + } else { + row[2].value.str_val = strdup("File"); + } + } else { + row[2].value.str_val = strdup("Special"); + } + row[2].is_highlighted = 0; + + // Modified date column + row[3].type = TYPE_STRING; + row[3].value.str_val = malloc(32); + struct tm *tm_info = localtime(&file_stat.st_mtime); + strftime(row[3].value.str_val, 32, "%Y-%m-%d %H:%M", tm_info); + row[3].is_highlighted = 0; + + // Add row to table + add_table_row(table, row); + } + } + + closedir(dir); + return table; +} + +/** + * Parse input line into an array of commands (for pipelines) + */ +char*** lsh_split_commands(char *line) { + char ***commands = NULL; + char **command = NULL; + char *cmd_str, *token, *saveptr1, *saveptr2; + int cmd_count = 0, cmd_capacity = 10; + int token_count = 0, token_capacity = 10; + + // Allocate initial commands array + commands = malloc(cmd_capacity * sizeof(char**)); + if (!commands) { + perror("lsh: allocation error"); + return NULL; + } + + // Split by pipe symbol '|' + cmd_str = strtok_r(line, "|", &saveptr1); + while (cmd_str != NULL) { + // Trim whitespace + while (*cmd_str && isspace(*cmd_str)) cmd_str++; + + // Allocate command token array + command = malloc(token_capacity * sizeof(char*)); + if (!command) { + perror("lsh: allocation error"); + // Free previous allocations + for (int i = 0; i < cmd_count; i++) { + for (int j = 0; commands[i][j] != NULL; j++) { + free(commands[i][j]); + } + free(commands[i]); + } + free(commands); + return NULL; + } + + // Split command into tokens + token_count = 0; + token = strtok_r(cmd_str, " \t\r\n\a", &saveptr2); + while (token != NULL) { + // Check if we need to resize tokens array + if (token_count >= token_capacity - 1) { + token_capacity *= 2; + command = realloc(command, token_capacity * sizeof(char*)); + if (!command) { + perror("lsh: allocation error"); + // Free previous allocations + for (int i = 0; i < cmd_count; i++) { + for (int j = 0; commands[i][j] != NULL; j++) { + free(commands[i][j]); + } + free(commands[i]); + } + free(commands); + return NULL; + } + } + + command[token_count] = strdup(token); + token_count++; + token = strtok_r(NULL, " \t\r\n\a", &saveptr2); + } + command[token_count] = NULL; // Null-terminate the tokens array + + // Check if we need to resize commands array + if (cmd_count >= cmd_capacity - 1) { + cmd_capacity *= 2; + commands = realloc(commands, cmd_capacity * sizeof(char**)); + if (!commands) { + perror("lsh: allocation error"); + return NULL; + } + } + + commands[cmd_count] = command; + cmd_count++; + cmd_str = strtok_r(NULL, "|", &saveptr1); + } + + commands[cmd_count] = NULL; // Null-terminate the commands array + return commands; +} + /** * Launch an external program */ @@ -402,6 +603,51 @@ int lsh_execute_piped(char ***commands) { return 1; // Nothing to execute } + // Check if this is a table operation starting with ls/dir + if (strcmp(commands[0][0], "ls") == 0 || strcmp(commands[0][0], "dir") == 0) { + // Create a table from ls command + TableData *table = create_ls_table(commands[0]); + if (!table) { + return 1; // Error already printed + } + + // Apply filters from the pipeline + for (int i = 1; commands[i] != NULL; i++) { + // Find the filter command + char *filter_cmd = commands[i][0]; + int filter_idx = -1; + + for (int j = 0; j < filter_count; j++) { + if (strcmp(filter_cmd, filter_str[j]) == 0) { + filter_idx = j; + break; + } + } + + if (filter_idx == -1) { + fprintf(stderr, "lsh: unknown filter command: %s\n", filter_cmd); + free_table(table); + return 1; + } + + // Apply the filter + TableData *filtered_table = filter_func[filter_idx](table, &commands[i][1]); + free_table(table); // Free the old table + + if (!filtered_table) { + return 1; // Error already printed + } + + table = filtered_table; + } + + // Print the final table + print_table(table); + free_table(table); + + return 1; + } + if (cmd_count == 1) { // No piping needed return lsh_execute(commands[0]); @@ -562,7 +808,7 @@ void lsh_loop(void) { // Check for pipes and parse into multiple commands if present if (strchr(line, '|') != NULL) { - commands = lsh_split_piped_line(line); + commands = lsh_split_commands(line); status = lsh_execute_piped(commands); free_commands(commands); } else { @@ -604,4 +850,4 @@ void lsh_loop(void) { // Restore terminal restore_terminal(terminal_fd, &g_orig_termios); -} +} \ No newline at end of file diff --git a/shell.h b/shell.h index 91c1844..36909c8 100644 --- a/shell.h +++ b/shell.h @@ -30,6 +30,22 @@ int lsh_execute(char **args); */ int lsh_execute_piped(char ***commands); +/** + * Create a TableData structure from an ls command + * + * @param args Command arguments + * @return TableData pointer (must be freed by caller) or NULL on error + */ +TableData* create_ls_table(char **args); + +/** + * Parse input line into an array of commands (for pipelines) + * + * @param line Input command line + * @return Null-terminated array of command arrays + */ +char*** lsh_split_commands(char *line); + /** * Launch an external program * diff --git a/shell.o b/shell.o index 34f2c7f..636386e 100644 Binary files a/shell.o and b/shell.o differ