diff --git a/include/builtins.h b/include/builtins.h new file mode 100644 index 0000000..291e6f1 --- /dev/null +++ b/include/builtins.h @@ -0,0 +1,102 @@ +/** + * builtins.h + * Declarations for all built-in shell commands + */ + +#ifndef BUILTINS_H +#define BUILTINS_H + +#include "common.h" +#include "system_monitor.h" +#include + +// Syntax highlighting definitions using ANSI colors +#define COLOR_DEFAULT ANSI_COLOR_RESET +#define COLOR_KEYWORD ANSI_COLOR_CYAN +#define COLOR_STRING ANSI_COLOR_GREEN +#define COLOR_COMMENT "\x1b[90m" // Dark gray +#define COLOR_NUMBER ANSI_COLOR_MAGENTA +#define COLOR_PREPROCESSOR ANSI_COLOR_YELLOW +#define COLOR_IDENTIFIER "\x1b[97m" // Bright white + +// Add declarations for color functions +void set_color(int color); +void reset_color(); + +// History command implementation +#define HISTORY_SIZE 10 + +// New struct to hold history entries with timestamps +typedef struct { + char *command; + time_t timestamp; +} HistoryEntry; + +// History variables - made extern to be accessible from line_reader.c +extern HistoryEntry command_history[HISTORY_SIZE]; +extern int history_count; +extern int history_index; + +// Built-in command declarations +int lsh_cd(char **args); +int lsh_help(char **args); +int lsh_exit(char **args); +int lsh_dir(char **args); +int lsh_clear(char **args); +int lsh_mkdir(char **args); +int lsh_rmdir(char **args); +int lsh_del(char **args); +int lsh_touch(char **args); +int lsh_pwd(char **args); +int lsh_cat(char **args); +int lsh_history(char **args); +int lsh_copy(char **args); +int lsh_paste(char **args); +int lsh_move(char **args); +int lsh_ps(char **args); +int lsh_news(char **args); +// Alias command declarations - added for alias support +int lsh_alias(char **args); +int lsh_unalias(char **args); +int lsh_aliases(char **args); // New command to list all aliases +// Bookmark command declarations - added for bookmark support +int lsh_bookmark(char **args); +int lsh_bookmarks(char **args); +int lsh_goto(char **args); +int lsh_unbookmark(char **args); +int lsh_focus_timer(char **args); +int lsh_weather(char **args); +// Text search command +int lsh_grep(char **args); +int lsh_actual_grep(char **args); +int lsh_ripgrep(char **args); +int lsh_fzf_native(char **args); +int lsh_clip(char **args); +int lsh_echo(char **args); +int lsh_self_destruct(); +int lsh_theme(char **args); +int lsh_loc(char **args); +int lsh_git_status(char **args); +int lsh_gg(char **args); +int lsh_stats(char **args); + +// Add command to history +void lsh_add_to_history(const char *command); + +// Get number of builtin commands +int lsh_num_builtins(void); + +// Expose the builtin command strings and function pointers +extern char *builtin_str[]; +extern int (*builtin_func[])(char **); + +/** + * Extract a string value from a JSON object + * + * @param json The JSON string to parse + * @param key The key to find + * @return Allocated string containing the value (caller must free) + */ +char *extract_json_string(const char *json, const char *key); + +#endif // BUILTINS_H diff --git a/include/common.h b/include/common.h new file mode 100644 index 0000000..47609b0 --- /dev/null +++ b/include/common.h @@ -0,0 +1,116 @@ +/** + * common.h + * Common includes, defines and macros used throughout the shell + */ + +#ifndef COMMON_H +#define COMMON_H + +// Standard library includes +#include +#include +#include +#include // For isprint +#include // For file control +#include // For POSIX API (replaces direct.h) +#include +#include // For waitpid +#include // For stat (replaces fileapi.h) +#include // For terminal control +#include // For console dimensions +#include // For signal handling +#include // For time functions +#include // For PATH_MAX + +// Define PATH_MAX if not defined +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +// Common defines +#define LSH_RL_BUFSIZE 1024 +#define LSH_TOK_BUFSIZE 64 +#define LSH_TOK_DELIM " \t\r\n\a" + +// Terminal control and color codes (ANSI sequences) +#define ANSI_COLOR_RESET "\x1b[0m" +#define ANSI_COLOR_BLACK "\x1b[30m" +#define ANSI_COLOR_RED "\x1b[31m" +#define ANSI_COLOR_GREEN "\x1b[32m" +#define ANSI_COLOR_YELLOW "\x1b[33m" +#define ANSI_COLOR_BLUE "\x1b[34m" +#define ANSI_COLOR_MAGENTA "\x1b[35m" +#define ANSI_COLOR_CYAN "\x1b[36m" +#define ANSI_COLOR_WHITE "\x1b[37m" + +// Background colors +#define ANSI_BG_BLACK "\x1b[40m" +#define ANSI_BG_RED "\x1b[41m" +#define ANSI_BG_GREEN "\x1b[42m" +#define ANSI_BG_YELLOW "\x1b[43m" +#define ANSI_BG_BLUE "\x1b[44m" +#define ANSI_BG_MAGENTA "\x1b[45m" +#define ANSI_BG_CYAN "\x1b[46m" +#define ANSI_BG_WHITE "\x1b[47m" + +// Cursor movement and screen control +#define ANSI_CLEAR_SCREEN "\x1b[2J" +#define ANSI_CURSOR_HOME "\x1b[H" +#define ANSI_SAVE_CURSOR "\x1b[s" +#define ANSI_RESTORE_CURSOR "\x1b[u" + +// Key code definitions used by the line reader +#define KEY_BACKSPACE 127 // Different on Linux +#define KEY_TAB 9 +#define KEY_ENTER 10 // Linux: 10 (LF) or 13 (CR) +#define KEY_ESCAPE 27 + +// Special keys (Linux terminal uses escape sequences) +// These will need handling in the line reader +#define KEY_UP 1000 +#define KEY_DOWN 1001 +#define KEY_LEFT 1002 +#define KEY_RIGHT 1003 +#define KEY_SHIFT_ENTER 1010 +#define KEY_SHIFT_TAB 1011 + +// Typedefs for compatibility +typedef unsigned int UINT; +typedef unsigned short WORD; +typedef int BOOL; +typedef int HANDLE; +typedef int DWORD; + +// Define Windows constants +#define FALSE 0 +#define TRUE 1 + +// Struct to replace COORD +typedef struct { + short X; + short Y; +} COORD; + +// Struct to replace SMALL_RECT +typedef struct { + short Left; + short Top; + short Right; + short Bottom; +} SMALL_RECT; + +// Struct to replace CONSOLE_SCREEN_BUFFER_INFO +typedef struct { + COORD dwSize; + COORD dwCursorPosition; + WORD wAttributes; + SMALL_RECT srWindow; +} CONSOLE_SCREEN_BUFFER_INFO; + +// Struct to replace CONSOLE_CURSOR_INFO +typedef struct { + DWORD dwSize; + BOOL bVisible; +} CONSOLE_CURSOR_INFO; + +#endif // COMMON_H diff --git a/include/shell.h b/include/shell.h new file mode 100644 index 0000000..36909c8 --- /dev/null +++ b/include/shell.h @@ -0,0 +1,141 @@ +/** + * shell.h + * Core shell functionality + */ + +#ifndef SHELL_H +#define SHELL_H + +#include "common.h" +#include "structured_data.h" + +/** + * Display a welcome banner with BBQ sauce invention time + */ +void display_welcome_banner(void); + +/** + * Execute a command + * + * @param args Null-terminated array of command arguments + * @return 1 to continue the shell, 0 to exit + */ +int lsh_execute(char **args); + +/** + * Execute a pipeline of commands + * + * @param commands Null-terminated array of command arrays + * @return 1 to continue the shell, 0 to exit + */ +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 + * + * @param args Null-terminated array of command arguments + * @return Always returns 1 (continue the shell) + */ +int lsh_launch(char **args); + +/** + * Main shell loop + */ +void lsh_loop(void); + +/** + * Free memory for a command array from lsh_split_commands + */ +void free_commands(char ***commands); + +/** + * Initialize the status bar at the bottom of the screen + * + * @param fd File descriptor for terminal (STDOUT_FILENO) + * @return 1 if successful, 0 on failure + */ +int init_status_bar(int fd); + +/** + * Temporarily hide the status bar before command execution + * + * @param fd File descriptor for terminal (STDOUT_FILENO) + */ +void hide_status_bar(int fd); + +/** + * Ensures there's space for the status bar by scrolling if needed + * + * @param fd File descriptor for terminal (STDOUT_FILENO) + */ +void ensure_status_bar_space(int fd); + +/** + * Check for terminal window resize and update status bar position + * + * @param fd File descriptor for terminal (STDOUT_FILENO) + */ +void check_console_resize(int fd); + +/** + * Update the status bar with Git information + * + * @param fd File descriptor for terminal (STDOUT_FILENO) + * @param git_info The Git information to display in the status bar + */ +void update_status_bar(int fd, const char *git_info); + +/** + * Get the name of the parent and current directories from a path + * + * @param cwd The current working directory path + * @param parent_dir_name Buffer to store the parent directory name + * @param current_dir_name Buffer to store the current directory name + * @param buf_size Size of the buffers + */ +void get_path_display(const char *cwd, char *parent_dir_name, + char *current_dir_name, size_t buf_size); + +/** + * Initialize the terminal for raw mode + * + * @param orig_termios Original terminal settings to restore later + * @return File descriptor for terminal + */ +int init_terminal(struct termios *orig_termios); + +/** + * Restore terminal to original settings + * + * @param fd File descriptor for terminal + * @param orig_termios Original terminal settings + */ +void restore_terminal(int fd, struct termios *orig_termios); + +/** + * Get console dimensions + * + * @param fd File descriptor for terminal + * @param width Pointer to store width + * @param height Pointer to store height + * @return 1 on success, 0 on failure + */ +int get_console_dimensions(int fd, int *width, int *height); + +#endif // SHELL_H \ No newline at end of file diff --git a/shell b/shell index 6ae20ee..d5b4193 100755 Binary files a/shell and b/shell differ diff --git a/src/builtins.c b/src/builtins.c new file mode 100644 index 0000000..84496e4 --- /dev/null +++ b/src/builtins.c @@ -0,0 +1,1444 @@ +/** + * builtins.c + * Implementation of all built-in shell commands + */ + +#include "builtins.h" +#include "common.h" +#include "diff_viewer.h" +#include "ncurses_diff_viewer.h" +#include "filters.h" +#include "fzf_native.h" +#include "git_integration.h" +#include "grep.h" +#include "persistent_history.h" +#include "structured_data.h" +#include "themes.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// History variables +HistoryEntry command_history[HISTORY_SIZE]; +int history_count = 0; +int history_index = 0; + +// String array of built-in command names +char *builtin_str[] = { + "cd", "help", "exit", "dir", "clear", + "mkdir", "rmdir", "del", "touch", "pwd", + "cat", "history", "copy", "move", "paste", + "ps", "news", "alias", "unalias", "aliases", + "bookmark", "bookmarks", "goto", "unbookmark", "focus_timer", + "weather", "grep", "grep-text", "ripgrep", "fzf", + "clip", "echo", "theme", "loc", "git_status", + "gg", "ls", "stats", "monitor", +}; + +// Array of function pointers to built-in command implementations +int (*builtin_func[])(char **) = { + &lsh_cd, &lsh_help, &lsh_exit, &lsh_dir, + &lsh_clear, &lsh_mkdir, &lsh_rmdir, &lsh_del, + &lsh_touch, &lsh_pwd, &lsh_cat, &lsh_history, + &lsh_copy, &lsh_move, &lsh_paste, &lsh_ps, + &lsh_news, &lsh_alias, &lsh_unalias, &lsh_aliases, + &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_dir, + &lsh_stats, &builtin_monitor, +}; + +/** + * Set the console text color + */ +void set_color(int color) { + switch (color) { + case 0: + printf(ANSI_COLOR_RESET); + break; + case 1: + printf(ANSI_COLOR_RED); + break; + case 2: + printf(ANSI_COLOR_GREEN); + break; + case 3: + printf(ANSI_COLOR_YELLOW); + break; + case 4: + printf(ANSI_COLOR_BLUE); + break; + case 5: + printf(ANSI_COLOR_MAGENTA); + break; + case 6: + printf(ANSI_COLOR_CYAN); + break; + case 7: + printf(ANSI_COLOR_WHITE); + break; + default: + printf(ANSI_COLOR_RESET); + } +} + +/** + * Reset console color to default + */ +void reset_color() { printf(ANSI_COLOR_RESET); } + +/** + * Get the number of built-in commands + */ +int lsh_num_builtins() { return sizeof(builtin_str) / sizeof(char *); } + +/** + * Add a command to the history + */ +void lsh_add_to_history(const char *command) { + // Don't add empty commands or duplicates of the last command + if (!command || *command == '\0' || + (history_count > 0 && + strcmp(command_history[history_index - 1].command, command) == 0)) { + return; + } + + // Free the oldest entry if we're overwriting it + if (history_count == HISTORY_SIZE) { + free(command_history[history_index].command); + } else { + history_count++; + } + + // Add the new command + command_history[history_index].command = strdup(command); + command_history[history_index].timestamp = time(NULL); + + // Update the index + history_index = (history_index + 1) % HISTORY_SIZE; +} + +/** + * Built-in command: cd + * Changes the current directory + */ +int lsh_cd(char **args) { + if (args[1] == NULL) { + // No argument provided, change to home directory + char *home_dir = getenv("HOME"); + if (home_dir == NULL) { + fprintf(stderr, "lsh: HOME environment variable not set\n"); + return 1; + } + if (chdir(home_dir) != 0) { + perror("lsh: cd"); + } + } else { + if (chdir(args[1]) != 0) { + perror("lsh: cd"); + } + } + return 1; +} + +/** + * Built-in command: help + * Displays help information + */ +int lsh_help(char **args) { + if (args[1] != NULL) { + // Command-specific help + if (strcmp(args[1], "cd") == 0) { + printf("cd - Change directory\n"); + printf("Usage: cd [directory]\n"); + printf(" cd - change to home directory\n"); + printf(" cd - change to specified directory\n"); + } else if (strcmp(args[1], "cat") == 0) { + printf("cat - Display file contents\n"); + printf("Usage: cat \n"); + printf(" Displays the contents of the specified file\n"); + } else if (strcmp(args[1], "grep") == 0) { + printf("grep - Search for text patterns in files\n"); + printf("Usage: grep \n"); + printf(" Searches for the specified pattern in the given file\n"); + } else if (strcmp(args[1], "gg") == 0) { + printf("gg - Git command shortcuts\n"); + printf("Usage: gg \n"); + printf("Available commands:\n"); + printf(" s - status (enhanced git status)\n"); + printf(" c - commit\n"); + printf(" p - pull\n"); + printf(" ps - push\n"); + printf(" a - add .\n"); + printf(" l - log\n"); + printf(" d - diff\n"); + printf(" dd - ncurses diff viewer\n"); + printf(" b - branch\n"); + printf(" ch - checkout\n"); + printf(" o - open repository in browser\n"); + } else if (strcmp(args[1], "weather") == 0) { + printf("weather - Shows weather information\n"); + printf("Usage:\n"); + printf(" weather - shows weather for your current location\n"); + printf(" weather - shows weather for a specific city\n"); + printf("Examples:\n"); + printf(" weather\n"); + printf(" weather London\n"); + printf(" weather New York\n"); + } else if (strcmp(args[1], "dir") == 0 || strcmp(args[1], "ls") == 0) { + printf("dir/ls - List directory contents\n"); + printf("Usage: dir\n"); + printf(" Lists files and directories in the current directory\n"); + printf(" Shows file sizes, types, and modification dates in a table format\n"); + } else if (strcmp(args[1], "mkdir") == 0) { + printf("mkdir - Create directory\n"); + printf("Usage: mkdir \n"); + printf(" Creates a new directory with the specified name\n"); + } else if (strcmp(args[1], "rmdir") == 0) { + printf("rmdir - Remove directory\n"); + printf("Usage: rmdir \n"); + printf(" Removes an empty directory\n"); + } else if (strcmp(args[1], "del") == 0) { + printf("del - Delete file\n"); + printf("Usage: del \n"); + printf(" Deletes the specified file\n"); + } else if (strcmp(args[1], "touch") == 0) { + printf("touch - Create file or update timestamp\n"); + printf("Usage: touch \n"); + printf(" Creates a new empty file or updates the timestamp of an existing file\n"); + } else if (strcmp(args[1], "pwd") == 0) { + printf("pwd - Print working directory\n"); + printf("Usage: pwd\n"); + printf(" Displays the current working directory path\n"); + } else if (strcmp(args[1], "history") == 0) { + printf("history - Show command history\n"); + printf("Usage: history\n"); + printf(" Displays the list of previously executed commands with timestamps\n"); + } else if (strcmp(args[1], "copy") == 0) { + printf("copy - Copy file\n"); + printf("Usage: copy \n"); + printf(" Copies a file from source to destination\n"); + } else if (strcmp(args[1], "move") == 0) { + printf("move - Move/rename file\n"); + printf("Usage: move \n"); + printf(" Moves or renames a file from source to destination\n"); + } else if (strcmp(args[1], "clear") == 0) { + printf("clear - Clear screen\n"); + printf("Usage: clear\n"); + printf(" Clears the terminal screen\n"); + } else if (strcmp(args[1], "echo") == 0) { + printf("echo - Display text\n"); + printf("Usage: echo [text...]\n"); + printf(" Displays the specified text to the terminal\n"); + } else if (strcmp(args[1], "alias") == 0) { + printf("alias - Create command alias\n"); + printf("Usage: alias \n"); + printf(" Creates a shortcut alias for a command\n"); + } else if (strcmp(args[1], "bookmark") == 0) { + printf("bookmark - Bookmark current directory\n"); + printf("Usage: bookmark \n"); + printf(" Saves the current directory with a bookmark name\n"); + } else if (strcmp(args[1], "goto") == 0) { + printf("goto - Go to bookmarked directory\n"); + printf("Usage: goto \n"); + printf(" Changes to a previously bookmarked directory\n"); + } else if (strcmp(args[1], "theme") == 0) { + printf("theme - Change shell theme\n"); + printf("Usage: theme \n"); + printf(" Changes the visual appearance of the shell\n"); + } else if (strcmp(args[1], "loc") == 0) { + printf("loc - Count lines of code\n"); + printf("Usage: loc \n"); + printf(" Counts total lines, code lines, comments, and blank lines in a file\n"); + } else if (strcmp(args[1], "monitor") == 0) { + printf("monitor - System monitor\n"); + printf("Usage: monitor\n"); + printf(" Displays real-time system information including CPU, memory, and disk usage\n"); + } else if (strcmp(args[1], "stats") == 0) { + printf("stats - Command usage statistics\n"); + printf("Usage: stats\n"); + printf(" Shows statistics about your most frequently used commands\n"); + } else if (strcmp(args[1], "help") == 0) { + printf("help - Display help information\n"); + printf("Usage:\n"); + printf(" help - show all available commands\n"); + printf(" help - show help for a specific command\n"); + } else if (strcmp(args[1], "exit") == 0) { + printf("exit - Exit the shell\n"); + printf("Usage: exit\n"); + printf(" Terminates the shell session\n"); + } else if (strcmp(args[1], "ps") == 0) { + printf("ps - List running processes\n"); + printf("Usage: ps\n"); + printf(" Displays a list of all running processes on the system\n"); + } else if (strcmp(args[1], "news") == 0) { + printf("news - Show latest repository updates\n"); + printf("Usage: news\n"); + printf(" Fetches and displays the latest commit information from the GitHub repository\n"); + } else if (strcmp(args[1], "unalias") == 0) { + printf("unalias - Remove command alias\n"); + printf("Usage: unalias \n"); + printf(" Removes a previously created command alias\n"); + } else if (strcmp(args[1], "aliases") == 0) { + printf("aliases - List all aliases\n"); + printf("Usage: aliases\n"); + printf(" Displays all currently defined command aliases\n"); + } else if (strcmp(args[1], "bookmarks") == 0) { + printf("bookmarks - List all bookmarks\n"); + printf("Usage: bookmarks\n"); + printf(" Displays all saved directory bookmarks\n"); + } else if (strcmp(args[1], "unbookmark") == 0) { + printf("unbookmark - Remove bookmark\n"); + printf("Usage: unbookmark \n"); + printf(" Removes a previously saved directory bookmark\n"); + } else if (strcmp(args[1], "focus_timer") == 0) { + printf("focus_timer - Productivity timer\n"); + printf("Usage: focus_timer [minutes]\n"); + printf(" Starts a focus/pomodoro timer for productivity sessions\n"); + } else if (strcmp(args[1], "grep-text") == 0) { + printf("grep-text - Alternative text search\n"); + printf("Usage: grep-text \n"); + printf(" Alternative implementation for searching text patterns in files\n"); + } else if (strcmp(args[1], "ripgrep") == 0) { + printf("ripgrep - Fast text search\n"); + printf("Usage: ripgrep [path]\n"); + printf(" Fast recursive text search using ripgrep-like functionality\n"); + } else if (strcmp(args[1], "fzf") == 0) { + printf("fzf - Fuzzy file finder\n"); + printf("Usage: fzf\n"); + printf(" Interactive fuzzy file finder for quick file selection\n"); + } else if (strcmp(args[1], "clip") == 0) { + printf("clip - Clipboard operations\n"); + printf("Usage: clip\n"); + printf(" Clipboard functionality (currently not implemented)\n"); + } else if (strcmp(args[1], "git_status") == 0) { + printf("git_status - Git repository status\n"); + printf("Usage: git_status\n"); + printf(" Shows the current Git repository status\n"); + } else if (strcmp(args[1], "paste") == 0) { + printf("paste - Paste clipboard content\n"); + printf("Usage: paste\n"); + printf(" Paste functionality (currently not implemented)\n"); + } else { + printf("No help available for '%s'\n", args[1]); + printf("Type 'help' to see all available commands\n"); + } + return 1; + } + + // General help - show all commands + printf("LSH Shell - A lightweight shell with modern features\n"); + printf("Type a command and press Enter to execute it.\n"); + printf("The following built-in commands are available:\n\n"); + + // Sort commands alphabetically + char *sorted_commands[lsh_num_builtins()]; + for (int i = 0; i < lsh_num_builtins(); i++) { + sorted_commands[i] = builtin_str[i]; + } + + for (int i = 0; i < lsh_num_builtins() - 1; i++) { + for (int j = i + 1; j < lsh_num_builtins(); j++) { + if (strcmp(sorted_commands[i], sorted_commands[j]) > 0) { + char *temp = sorted_commands[i]; + sorted_commands[i] = sorted_commands[j]; + sorted_commands[j] = temp; + } + } + } + + // Print commands in columns + int columns = 4; + int rows = (lsh_num_builtins() + columns - 1) / columns; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < columns; j++) { + int index = j * rows + i; + if (index < lsh_num_builtins()) { + printf("%-15s", sorted_commands[index]); + } + } + printf("\n"); + } + + printf( + "\nFor more information on specific commands, type 'help '\n"); + printf("Use tab completion for commands and file paths\n"); + printf("Use arrow keys to navigate command history\n"); + printf("Type a partial command followed by '?' for suggestions\n"); + + return 1; +} + +/** + * Built-in command: exit + * Exits the shell + */ +int lsh_exit(char **args) { return 0; } + +/** + * Built-in command: dir + * 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 detailed = 0; + + // 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"); + free_table(table); + return 1; + } + + dir = opendir(cwd); + if (dir == NULL) { + perror("lsh: opendir"); + free_table(table); + return 1; + } + + // 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); + + // Print the table + print_table(table); + + // Free the table + free_table(table); + + return 1; +} + +/** + * Built-in command: clear + * Clears the screen + */ +int lsh_clear(char **args) { + printf(ANSI_CLEAR_SCREEN); + printf(ANSI_CURSOR_HOME); + return 1; +} + +/** + * Built-in command: mkdir + * Creates a new directory + */ +int lsh_mkdir(char **args) { + if (args[1] == NULL) { + fprintf(stderr, "lsh: expected argument to \"mkdir\"\n"); + } else { + if (mkdir(args[1], 0755) != 0) { + perror("lsh: mkdir"); + } + } + return 1; +} + +/** + * Built-in command: rmdir + * Removes a directory + */ +int lsh_rmdir(char **args) { + if (args[1] == NULL) { + fprintf(stderr, "lsh: expected argument to \"rmdir\"\n"); + } else { + if (rmdir(args[1]) != 0) { + perror("lsh: rmdir"); + } + } + return 1; +} + +/** + * Built-in command: del + * Deletes a file + */ +int lsh_del(char **args) { + if (args[1] == NULL) { + fprintf(stderr, "lsh: expected argument to \"del\"\n"); + } else { + if (unlink(args[1]) != 0) { + perror("lsh: del"); + } + } + return 1; +} + +/** + * Built-in command: touch + * Creates a new file or updates timestamp of existing file + */ +int lsh_touch(char **args) { + if (args[1] == NULL) { + fprintf(stderr, "lsh: expected argument to \"touch\"\n"); + } else { + int fd = open(args[1], O_CREAT | O_WRONLY | O_APPEND, 0644); + if (fd == -1) { + perror("lsh: touch"); + } else { + close(fd); + } + } + return 1; +} + +/** + * Built-in command: pwd + * Prints the current working directory + */ +int lsh_pwd(char **args) { + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd)) != NULL) { + printf("%s\n", cwd); + } else { + perror("lsh: getcwd"); + } + return 1; +} + +/** + * Built-in command: cat + * Displays the contents of a file + */ +int lsh_cat(char **args) { + if (args[1] == NULL) { + fprintf(stderr, "lsh: expected argument to \"cat\"\n"); + return 1; + } + + FILE *fp = fopen(args[1], "r"); + if (fp == NULL) { + perror("lsh: cat"); + return 1; + } + + char buffer[1024]; + while (fgets(buffer, sizeof(buffer), fp) != NULL) { + printf("%s", buffer); + } + + fclose(fp); + return 1; +} + +/** + * Built-in command: history + * Displays command history + */ +int lsh_history(char **args) { + char time_str[20]; + struct tm *tm_info; + + printf("Command History:\n"); + printf("----------------\n"); + + // Display history in chronological order + for (int i = 0; i < history_count; i++) { + int idx = (history_index - history_count + i + HISTORY_SIZE) % HISTORY_SIZE; + tm_info = localtime(&command_history[idx].timestamp); + strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); + printf("%3d: [%s] %s\n", i + 1, time_str, command_history[idx].command); + } + + return 1; +} + +/** + * Built-in command: copy + * Copies a file + */ +int lsh_copy(char **args) { + if (args[1] == NULL || args[2] == NULL) { + fprintf(stderr, + "lsh: expected source and destination arguments to \"copy\"\n"); + return 1; + } + + FILE *source = fopen(args[1], "rb"); + if (source == NULL) { + perror("lsh: copy (source)"); + return 1; + } + + FILE *dest = fopen(args[2], "wb"); + if (dest == NULL) { + fclose(source); + perror("lsh: copy (destination)"); + return 1; + } + + char buffer[4096]; + size_t bytes_read; + while ((bytes_read = fread(buffer, 1, sizeof(buffer), source)) > 0) { + fwrite(buffer, 1, bytes_read, dest); + } + + fclose(source); + fclose(dest); + printf("Copied %s to %s\n", args[1], args[2]); + return 1; +} + +/** + * Built-in command: move + * Moves a file + */ +int lsh_move(char **args) { + if (args[1] == NULL || args[2] == NULL) { + fprintf(stderr, + "lsh: expected source and destination arguments to \"move\"\n"); + return 1; + } + + if (rename(args[1], args[2]) != 0) { + perror("lsh: move"); + return 1; + } + + printf("Moved %s to %s\n", args[1], args[2]); + return 1; +} + +/** + * Built-in command: paste + * Placeholder for paste functionality + */ +int lsh_paste(char **args) { + printf("Paste functionality not implemented yet\n"); + return 1; +} + +/** + * Built-in command: ps + * Lists running processes + */ +int lsh_ps(char **args) { + FILE *fp = popen("ps -ef", "r"); + if (fp == NULL) { + perror("lsh: ps"); + return 1; + } + + char buffer[1024]; + while (fgets(buffer, sizeof(buffer), fp) != NULL) { + printf("%s", buffer); + } + + pclose(fp); + return 1; +} + +char *unescape_json_string(const char *str) { + if (!str) + return NULL; + + size_t len = strlen(str); + char *result = malloc(len + 1); + if (!result) + return NULL; + + size_t j = 0; + for (size_t i = 0; i < len; i++) { + if (str[i] == '\\' && i + 1 < len) { + switch (str[i + 1]) { + case 'n': + result[j++] = '\n'; + i++; // Skip the next character + break; + case 't': + result[j++] = '\t'; + i++; + break; + case 'r': + result[j++] = '\r'; + i++; + break; + case '\\': + result[j++] = '\\'; + i++; + break; + case '"': + result[j++] = '"'; + i++; + break; + default: + // Keep the backslash if it's not a recognized escape sequence + result[j++] = str[i]; + } + } else { + result[j++] = str[i]; + } + } + result[j] = '\0'; + + return result; +} + +/** + * Built-in command: news + * Displays information about the last push to the shelltest GitHub repository + */ +int lsh_news(char **args) { + printf("Fetching latest updates from shelltestLinux repository...\n\n"); + + // Use GitHub API to get the latest commit from the main branch + const char *api_url = + "https://api.github.com/repos/marcusDenslow/shelltestLinux/commits/main"; + char command[1024]; + snprintf(command, sizeof(command), + "curl -s -H \"Accept: application/vnd.github.v3+json\" \"%s\"", + api_url); + + FILE *fp = popen(command, "r"); + if (!fp) { + fprintf( + stderr, + "Failed to fetch updates. Please check your internet connection.\n"); + return 1; + } + + // Read the entire response + char response[8192] = ""; + size_t total_read = 0; + size_t chunk_size; + + while ((chunk_size = fread(response + total_read, 1, + sizeof(response) - total_read - 1, fp)) > 0) { + total_read += chunk_size; + if (total_read >= sizeof(response) - 1) + break; + } + response[total_read] = '\0'; + pclose(fp); + + // Check if we got an error response + if (strstr(response, "\"message\":") && + strstr(response, "\"documentation_url\":")) { + fprintf(stderr, "Failed to fetch updates from GitHub. The repository might " + "be private or there might be a connection issue.\n"); + return 1; + } + + // Extract commit information from JSON + char *sha = extract_json_string(response, "sha"); + char *message_raw = extract_json_string(response, "message"); + char *message = NULL; + if (message_raw) { + message = unescape_json_string(message_raw); + free(message_raw); + } + + char *author_name = NULL; + char *author_email = NULL; + char *date = NULL; + + // Find author object + char *author_start = strstr(response, "\"author\":"); + if (author_start) { + author_name = extract_json_string(author_start, "name"); + author_email = extract_json_string(author_start, "email"); + date = extract_json_string(author_start, "date"); + } + + // Format the date if we have it + char formatted_date[64] = "Unknown date"; + if (date) { + // Parse the ISO date (e.g., "2024-01-20T15:30:45Z") + struct tm tm_info = {0}; + if (sscanf(date, "%d-%d-%dT%d:%d:%d", &tm_info.tm_year, &tm_info.tm_mon, + &tm_info.tm_mday, &tm_info.tm_hour, &tm_info.tm_min, + &tm_info.tm_sec) == 6) { + tm_info.tm_year -= 1900; // tm_year is years since 1900 + tm_info.tm_mon -= 1; // tm_mon is 0-based + + time_t commit_time = mktime(&tm_info); + time_t now = time(NULL); + double diff_seconds = difftime(now, commit_time); + + // Format as relative time + if (diff_seconds < 60) { + snprintf(formatted_date, sizeof(formatted_date), "just now"); + } else if (diff_seconds < 3600) { + int minutes = (int)(diff_seconds / 60); + snprintf(formatted_date, sizeof(formatted_date), "%d minute%s ago", + minutes, minutes == 1 ? "" : "s"); + } else if (diff_seconds < 86400) { + int hours = (int)(diff_seconds / 3600); + snprintf(formatted_date, sizeof(formatted_date), "%d hour%s ago", hours, + hours == 1 ? "" : "s"); + } else if (diff_seconds < 604800) { + int days = (int)(diff_seconds / 86400); + snprintf(formatted_date, sizeof(formatted_date), "%d day%s ago", days, + days == 1 ? "" : "s"); + } else { + // Format as absolute date + strftime(formatted_date, sizeof(formatted_date), "%Y-%m-%d %H:%M", + &tm_info); + } + } + } + + // Display the information + printf(ANSI_COLOR_CYAN "Latest Update - shelltestLinux\n" ANSI_COLOR_RESET); + printf("════════════════════════════════════════════════════════════\n\n"); + + if (sha && message) { + // Shorten SHA to first 7 characters + char short_sha[8] = ""; + strncpy(short_sha, sha, 7); + short_sha[7] = '\0'; + + printf(ANSI_COLOR_GREEN "Commit:" ANSI_COLOR_RESET " %s\n", short_sha); + printf(ANSI_COLOR_GREEN "Date:" ANSI_COLOR_RESET " %s\n", formatted_date); + + if (author_name) { + printf(ANSI_COLOR_GREEN "Author:" ANSI_COLOR_RESET " %s", author_name); + if (author_email) { + printf(" <%s>", author_email); + } + printf("\n"); + } + + printf("\n" ANSI_COLOR_YELLOW "Message:" ANSI_COLOR_RESET "\n"); + + // Split the message into title and description + char *first_newline = strchr(message, '\n'); + if (first_newline) { + // We have a title and description + *first_newline = '\0'; // Temporarily terminate the title + printf(" " ANSI_COLOR_CYAN "%s" ANSI_COLOR_RESET "\n\n", message); + + // Print the description + char *description = first_newline + 1; + // Skip any leading newlines in the description + while (*description == '\n') + description++; + + if (*description) { + printf(" %s\n", description); + } + } else { + // Just a title, no description + printf(" " ANSI_COLOR_CYAN "%s" ANSI_COLOR_RESET "\n", message); + } + + printf("\n────────────────────────────────────────────────────────────\n"); + printf("Repository: " ANSI_COLOR_CYAN + "https://github.com/marcusDenslow/shelltestLinux" ANSI_COLOR_RESET + "\n"); + printf("View commit: " + "https://github.com/marcusDenslow/shelltestLinux/commit/%s\n", + short_sha); + + } else { + fprintf(stderr, "Failed to parse update information. The GitHub API " + "response format might have changed.\n"); + } + + // Free allocated strings + if (sha) + free(sha); + if (message) + free(message); + if (author_name) + free(author_name); + if (author_email) + free(author_email); + if (date) + free(date); + + return 1; +} + +/** + * Built-in command: clip + * Placeholder for clipboard functionality + */ +int lsh_clip(char **args) { + printf("Clipboard functionality not implemented yet\n"); + return 1; +} + +/** + * Built-in command: echo + * Echoes arguments to stdout + */ +int lsh_echo(char **args) { + if (args[1] == NULL) { + printf("\n"); + return 1; + } + + for (int i = 1; args[i] != NULL; i++) { + printf("%s", args[i]); + if (args[i + 1] != NULL) { + printf(" "); + } + } + printf("\n"); + return 1; +} + +/** + * Built-in command: loc + * Counts lines of code + */ +int lsh_loc(char **args) { + if (args[1] == NULL) { + fprintf(stderr, "lsh: expected file or directory argument to \"loc\"\n"); + return 1; + } + + struct stat st; + if (stat(args[1], &st) != 0) { + perror("lsh: loc"); + return 1; + } + + if (S_ISREG(st.st_mode)) { + // Count lines in a single file + FILE *file = fopen(args[1], "r"); + if (!file) { + perror("lsh: loc"); + return 1; + } + + int lines = 0, code_lines = 0, blank_lines = 0, comment_lines = 0; + char line[1024]; + int in_comment_block = 0; + + while (fgets(line, sizeof(line), file)) { + lines++; + char *trimmed = line; + while (*trimmed && isspace(*trimmed)) + trimmed++; + + if (*trimmed == '\0') { + blank_lines++; + } else if (strncmp(trimmed, "//", 2) == 0) { + comment_lines++; + } else if (strncmp(trimmed, "/*", 2) == 0) { + comment_lines++; + in_comment_block = 1; + if (strstr(trimmed, "*/")) { + in_comment_block = 0; + } + } else if (in_comment_block) { + comment_lines++; + if (strstr(trimmed, "*/")) { + in_comment_block = 0; + } + } else { + code_lines++; + } + } + + fclose(file); + + printf("File: %s\n", args[1]); + printf("Total lines: %d\n", lines); + printf("Code lines: %d\n", code_lines); + printf("Comment lines: %d\n", comment_lines); + printf("Blank lines: %d\n", blank_lines); + + } else if (S_ISDIR(st.st_mode)) { + printf("Directory LOC counting not implemented yet\n"); + } else { + fprintf(stderr, "lsh: %s is not a file or directory\n", args[1]); + } + + return 1; +} + +/** + * Helper function to extract a string value from a JSON response + */ +char *extract_json_string(const char *json, const char *key) { + char search_key[100]; + sprintf(search_key, "\"%s\":", key); + + char *key_pos = strstr(json, search_key); + if (!key_pos) + return NULL; + + key_pos += strlen(search_key); + + // Skip whitespace + while (*key_pos && isspace(*key_pos)) + key_pos++; + + if (*key_pos != '"') + return NULL; + + key_pos++; // Skip opening quote + + // Find closing quote + char *end = key_pos; + while (*end && *end != '"') { + if (*end == '\\' && *(end + 1)) { + end += 2; // Skip escaped character + } else { + end++; + } + } + + if (!*end) + return NULL; + + // Allocate and copy the string value + int len = end - key_pos; + char *result = malloc(len + 1); + if (!result) + return NULL; + + strncpy(result, key_pos, len); + result[len] = '\0'; + + return result; +} + +/** + * Built-in command: git_status + * Displays the Git status of the current repo + */ +int lsh_git_status(char **args) { + // Show current Git status + char *git_status = get_git_status(); + if (git_status) { + printf("Git Status: %s\n", git_status); + free(git_status); + } else { + printf("Not in a Git repository or Git not available\n"); + } + return 1; +} + +/** + * Built-in command: gg + * Quick access to Git commands + */ +int lsh_gg(char **args) { + if (args[1] == NULL) { + printf("Usage: gg \n"); + printf("Available commands:\n"); + printf(" s - status\n"); + printf(" c - commit\n"); + printf(" p - pull\n"); + printf(" ps - push\n"); + printf(" a - add .\n"); + printf(" l - log\n"); + printf(" d - diff\n"); + printf(" dd - ncurses diff viewer\n"); + printf(" b - branch\n"); + printf(" ch - checkout\n"); + printf(" o - open in GitHub browser\n"); + return 1; + } + + // Execute the appropriate git command based on shorthand + if (strcmp(args[1], "s") == 0) { + // Enhanced git status with more information + char branch_name[100] = ""; + char repo_name[100] = ""; + char last_commit_title[256] = ""; + char last_commit_hash[32] = ""; + char recent_commits[2][256]; + char repo_url[512] = ""; + int is_dirty = 0; + + // Check if we're in a git repo + if (!get_git_branch(branch_name, sizeof(branch_name), &is_dirty)) { + printf("Not in a Git repository\n"); + return 1; + } + + // Get all the information + get_git_repo_name(repo_name, sizeof(repo_name)); + get_last_commit(last_commit_title, sizeof(last_commit_title), + last_commit_hash, sizeof(last_commit_hash)); + int commit_count = get_recent_commit(recent_commits, 2); + get_repo_url(repo_url, sizeof(repo_url)); + + // Display enhanced status + printf("\n" ANSI_COLOR_CYAN "Git Repository Status" ANSI_COLOR_RESET "\n"); + printf("══════════════════════════════════════════════════\n\n"); + + // Repository info + printf(ANSI_COLOR_GREEN "Repo:" ANSI_COLOR_RESET " %s\n", + strlen(repo_name) > 0 ? repo_name : "Unknown"); + + // Branch info with clickable link + if (strlen(repo_url) > 0) { + printf(ANSI_COLOR_GREEN "Branch:" ANSI_COLOR_RESET " \033]8;;%s/tree/%s\033\\%s\033]8;;\033\\%s\n", + repo_url, branch_name, branch_name, is_dirty ? " *" : ""); + } else { + printf(ANSI_COLOR_GREEN "Branch:" ANSI_COLOR_RESET " %s%s\n", + branch_name, is_dirty ? " *" : ""); + } + + // Last commit info + if (strlen(last_commit_hash) > 0 && strlen(last_commit_title) > 0) { + if (strlen(repo_url) > 0) { + printf(ANSI_COLOR_GREEN "Last commit:" ANSI_COLOR_RESET " \033]8;;%s/commit/%s\033\\%s\033]8;;\033\\ - %s\n", + repo_url, last_commit_hash, last_commit_hash, last_commit_title); + } else { + printf(ANSI_COLOR_GREEN "Last commit:" ANSI_COLOR_RESET " %s - %s\n", + last_commit_hash, last_commit_title); + } + } + + // Recent commits + if (commit_count > 0) { + printf(ANSI_COLOR_GREEN "Last %d commits:" ANSI_COLOR_RESET "\n", commit_count); + for (int i = 0; i < commit_count; i++) { + printf(" %d. %s\n", i + 1, recent_commits[i]); + } + } + + // Repository link + if (strlen(repo_url) > 0) { + printf(ANSI_COLOR_GREEN "Repository:" ANSI_COLOR_RESET " \033]8;;%s\033\\%s\033]8;;\033\\\n", + repo_url, repo_url); + } + + printf("\n"); + + // Show regular git status output + printf(ANSI_COLOR_YELLOW "Working Directory Status:" ANSI_COLOR_RESET "\n"); + system("git status --short"); + + if (!is_dirty) { + printf(ANSI_COLOR_GREEN "Working tree clean" ANSI_COLOR_RESET "\n"); + } + + printf("\n"); + } else if (strcmp(args[1], "b") == 0) { + system("git branch"); + } else if (strcmp(args[1], "o") == 0) { + // Get the remote URL + FILE *fp; + char remote_url[1024] = {0}; + fp = popen("git config --get remote.origin.url 2>/dev/null", "r"); + if (fp) { + fgets(remote_url, sizeof(remote_url), fp); + pclose(fp); + + // Trim trailing newline + char *newline = strchr(remote_url, '\n'); + if (newline) + *newline = '\0'; + + if (strlen(remote_url) > 0) { + // Convert SSH URLs to HTTPS + char https_url[1024] = {0}; + if (strstr(remote_url, "git@github.com:")) { + // Convert ssh format (git@github.com:user/repo.git) to https + char *repo_path = strchr(remote_url, ':'); + if (repo_path) { + repo_path++; // Skip the colon + // Remove .git suffix if present + char *git_suffix = strstr(repo_path, ".git"); + if (git_suffix) + *git_suffix = '\0'; + + sprintf(https_url, "https://github.com/%s", repo_path); + } + } else if (strstr(remote_url, "https://github.com/")) { + // Already HTTPS format + strcpy(https_url, remote_url); + // Remove .git suffix if present + char *git_suffix = strstr(https_url, ".git"); + if (git_suffix) + *git_suffix = '\0'; + } + + if (strlen(https_url) > 0) { + // Open the URL in the default browser + char command[1100]; + // Use xdg-open on Linux + sprintf(command, "xdg-open %s >/dev/null 2>&1", https_url); + int result = system(command); + if (result == 0) { + printf("Opening %s in browser\n", https_url); + } else { + printf("Failed to open browser. URL: %s\n", https_url); + } + } else { + printf("Could not parse GitHub URL from: %s\n", remote_url); + } + } else { + printf("No remote URL found. Is this a Git repository with a GitHub " + "remote?\n"); + } + } else { + printf("Not in a Git repository or no remote configured\n"); + } + } else if (strcmp(args[1], "c") == 0) { + if (args[2] != NULL) { + char command[1024]; + sprintf(command, "git commit -m \"%s\"", args[2]); + system(command); + } else { + system("git commit"); + } + } else if (strcmp(args[1], "p") == 0) { + system("git pull"); + } else if (strcmp(args[1], "ps") == 0) { + // Try push without credentials first - force git to fail without prompting + int result = system("GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 git push /dev/null"); + + // If push failed, try with credentials + if (result != 0) { + char username[256] = ""; + char token[256] = ""; + + printf("Push failed. Authentication required.\n"); + printf("GitHub Username: "); + fflush(stdout); + + if (fgets(username, sizeof(username), stdin)) { + // Remove newline + username[strcspn(username, "\n")] = 0; + + printf("Personal Access Token: "); + fflush(stdout); + + // Hide token input + system("stty -echo"); + if (fgets(token, sizeof(token), stdin)) { + token[strcspn(token, "\n")] = 0; + system("stty echo"); + printf("\n"); + + printf("Attempting authenticated push...\n"); + result = execute_git_with_auth("git push", username, token); + + // Clear credentials from memory for security + memset(username, 0, sizeof(username)); + memset(token, 0, sizeof(token)); + + if (result == 0) { + printf("Push successful!\n"); + } else { + printf("Push failed. Please check your credentials and try again.\n"); + } + } else { + system("stty echo"); + printf("\nAuthentication cancelled.\n"); + } + } else { + printf("Authentication cancelled.\n"); + } + } else { + printf("Push successful!\n"); + } + } else if (strcmp(args[1], "a") == 0) { + system("git add ."); + } else if (strcmp(args[1], "l") == 0) { + system("git log --oneline -10"); + } else if (strcmp(args[1], "d") == 0) { + run_diff_viewer(); + } else if (strcmp(args[1], "dd") == 0) { + run_ncurses_diff_viewer(); + } else if (strcmp(args[1], "b") == 0) { + system("git branch"); + } else if (strcmp(args[1], "ch") == 0) { + if (args[2] != NULL) { + char command[1024]; + sprintf(command, "git checkout %s", args[2]); + system(command); + } else { + printf("Please specify a branch to checkout\n"); + } + } else if (strcmp(args[1], "debug") == 0) { + printf("=== Git Debug Log ===\n"); + if (access("/tmp/git_debug.log", F_OK) == 0) { + system("cat /tmp/git_debug.log"); + } else { + printf("No debug log found. Try pushing to generate debug info.\n"); + } + printf("\n=== End Debug Log ===\n"); + } else if (strcmp(args[1], "debug-clear") == 0) { + system("rm -f /tmp/git_debug.log"); + printf("Debug log cleared.\n"); + } else { + printf("Unknown git command shorthand: %s\n", args[1]); + } + + return 1; +} + +int lsh_stats(char **args) { + printf("Command Statistics\n"); + printf("=================\n\n"); + + if (frequency_count == 0) { + printf("No command history available\n"); + return 1; + } + + typedef struct { + char *command; + int count; + double score; + } CommandStat; + + CommandStat *stats = malloc(frequency_count * sizeof(CommandStat)); + if (!stats) + return 1; + + time_t current_time = time(NULL); + for (int i = 0; i < frequency_count; i++) { + stats[i].command = command_frequencies[i].command; + stats[i].count = command_frequencies[i].count; + + time_t most_recent = 0; + for (int j = history_size - 1; j >= 0; j--) { + if (strcmp(history_entries[j].command, stats[i].command) == 0) { + most_recent = history_entries[j].timestamp; + break; + } + } + + double hours_ago = (current_time - most_recent) / 3600.0; + double recency_weight = hours_ago > 0 ? 1.0 / (1.0 + hours_ago * 0.1) : 1.0; + + stats[i].score = stats[i].count * recency_weight; + + } + + for (int i = 0; i < frequency_count - 1; i++) { + for (int j = i + 1; j < frequency_count; j++) { + if (stats[j].score > stats[i].score) { + CommandStat temp = stats[i]; + stats[i] = stats[j]; + stats[j] = temp; + } + } + } + + printf("Top Commands (by frequency + recency):\n"); + printf("Rank Command Count Score\n"); + printf("---- -------------------- ----- -----\n"); + + int display_count = frequency_count < 10 ? frequency_count : 10; + for (int i = 0; i < display_count; i++) { + printf("%-4d %-20s %-5d %.2f\n", i + 1, stats[i].command, + stats[i].count, stats[i].score); + } + + printf("\nNext Command Prediction: \n"); + if (frequency_count > 0) { + printf("Most likely: %s (score: %.2f)\n", stats[0].command, stats[0].score); + } + free(stats); + return 1; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..efc6a5c --- /dev/null +++ b/src/main.c @@ -0,0 +1,26 @@ +/** + * main.c + * Entry point for the LSH shell program + */ + +#include "common.h" +#include "shell.h" +#include + +/** + * Main entry point + */ + +int main(int argc, char **argv) { + // Set locale to support UTF-8 + setlocale(LC_ALL, "en_US.UTF-8"); + + // Setup terminal for proper UTF-8 handling + // This is Linux equivalent of SetConsoleOutputCP(65001) + printf("\033%%G"); // Tell terminal to use UTF-8 + + // Start the shell loop + lsh_loop(); + + return EXIT_SUCCESS; +} diff --git a/src/shell.c b/src/shell.c new file mode 100644 index 0000000..6fd93d3 --- /dev/null +++ b/src/shell.c @@ -0,0 +1,1068 @@ +/** + * shell.c + * Implementation of core shell functionality + */ + +#include "shell.h" +#include "aliases.h" // Added for alias support +#include "autocorrect.h" +#include "bookmarks.h" // Added for bookmark support +#include "builtins.h" +#include "countdown_timer.h" +#include "favorite_cities.h" +#include "filters.h" +#include "git_integration.h" // Added for Git repository detection +#include "line_reader.h" +#include "persistent_history.h" +#include "structured_data.h" +#include "tab_complete.h" // Added for tab completion support +#include "themes.h" +#include +#include // Added for time functions +#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 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 + +/** + * Initialize terminal for raw mode + */ +int init_terminal(struct termios *orig_termios) { + int fd = STDIN_FILENO; + + if (!isatty(fd)) { + fprintf(stderr, "Not running in a terminal\n"); + return -1; + } + + // Get current terminal settings + if (tcgetattr(fd, orig_termios) == -1) { + perror("tcgetattr"); + return -1; + } + + // Create raw terminal settings (make a copy first) + struct termios raw = *orig_termios; + + // Disable canonical mode and echo, but preserve some control characters + raw.c_iflag &= ~(ICRNL | IXON); // Disable CTRL-S and CTRL-Q and CR to NL translation + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); // Disable echo, canonical mode, and signal keys + raw.c_cc[VMIN] = 1; // Wait for at least one character + raw.c_cc[VTIME] = 0; // No timeout + + // Apply modified terminal settings + if (tcsetattr(fd, TCSAFLUSH, &raw) == -1) { + perror("tcsetattr"); + return -1; + } + + printf("\033[?25h"); // Ensure cursor is visible + + return fd; +} + +/** + * Restore terminal to original settings + */ +void restore_terminal(int fd, struct termios *orig_termios) { + // Restore cursor and terminal settings + printf("\033[?25h"); // Ensure cursor is visible + printf("\033c"); // Reset terminal state + + if (tcsetattr(fd, TCSAFLUSH, orig_termios) == -1) { + perror("tcsetattr"); + } +} + +/** + * Get console dimensions + */ +int get_console_dimensions(int fd, int *width, int *height) { + struct winsize ws; + + if (ioctl(fd, TIOCGWINSZ, &ws) == -1) { + perror("ioctl"); + return 0; + } + + *width = ws.ws_col; + *height = ws.ws_row; + + return 1; +} + +/** + * Temporarily hide the status bar before command execution + */ +void hide_status_bar(int fd) { + // Skip if status bar is not enabled + if (!g_status_bar_enabled) + return; + + int width, height; + if (!get_console_dimensions(fd, &width, &height)) { + return; + } + + // Save cursor position + printf(ANSI_SAVE_CURSOR); + + // Move to status bar line and clear it + printf("\033[%d;1H", height); + printf("\033[2K"); // Clear entire line + + // Restore cursor position + printf(ANSI_RESTORE_CURSOR); + fflush(stdout); +} + +/** + * This function scrolls the console buffer up one line to make room for the + * status bar when we're at the bottom of the screen. + */ +void ensure_status_bar_space(int fd) { + int width, height; + if (!get_console_dimensions(fd, &width, &height)) { + return; + } + + // First clear the status bar if it exists + printf(ANSI_SAVE_CURSOR); + + // Move to status bar line and clear it + printf("\033[%d;1H", height); + printf("\033[2K"); // Clear entire line + + // Add a blank line above status bar for spacing + printf("\033[%d;1H", height - 1); + printf("\033[2K"); // Clear entire line + + // Restore cursor position + printf(ANSI_RESTORE_CURSOR); + fflush(stdout); +} + +/** + * Initialize the status bar at the bottom of the screen + */ +int init_status_bar(int fd) { + int width, height; + if (!get_console_dimensions(fd, &width, &height)) { + return 0; + } + + g_console_width = width; + g_status_line = height; + g_status_bar_enabled = 1; + + // Clear status line + printf(ANSI_SAVE_CURSOR); + printf("\033[%d;1H", height); + printf("\033[2K"); // Clear entire line + printf(ANSI_RESTORE_CURSOR); + fflush(stdout); + + return 1; +} + +/** + * Check for console window resize and update status bar position + */ +void check_console_resize(int fd) { + if (!g_status_bar_enabled) + return; + + int width, height; + if (!get_console_dimensions(fd, &width, &height)) { + return; + } + + // Check if dimensions have changed + if (width != g_console_width || height != g_status_line) { + g_console_width = width; + g_status_line = height; + + // Redraw status bar at new position + hide_status_bar(fd); + } +} + +/** + * Update the status bar with Git information + */ +void update_status_bar(int fd, const char *git_info) { + if (!g_status_bar_enabled) + return; + + int width, height; + if (!get_console_dimensions(fd, &width, &height)) { + return; + } + + g_console_width = width; + g_status_line = height; + + // Get current time for status bar + time_t rawtime; + struct tm *timeinfo; + char time_buffer[10]; + + time(&rawtime); + timeinfo = localtime(&rawtime); + strftime(time_buffer, sizeof(time_buffer), "%H:%M:%S", timeinfo); + + // Get current directory for display + char cwd[LSH_RL_BUFSIZE]; + char parent_dir[LSH_RL_BUFSIZE / 2]; + char current_dir[LSH_RL_BUFSIZE / 2]; + + if (getcwd(cwd, sizeof(cwd)) != NULL) { + get_path_display(cwd, parent_dir, current_dir, LSH_RL_BUFSIZE / 2); + } else { + strcpy(parent_dir, "unknown"); + strcpy(current_dir, "dir"); + } + + printf(ANSI_SAVE_CURSOR); + + // Move cursor to status line + printf("\033[%d;1H", height); + + // Clear the status line + printf("\033[2K"); + + // Set status bar color (cyan background) + printf(ANSI_BG_CYAN ANSI_COLOR_BLACK); + + // Format: [time] parent_dir/current_dir [git_info] + printf(" %s ", time_buffer); + printf(" %s/%s ", parent_dir, current_dir); + + if (git_info != NULL && strlen(git_info) > 0) { + printf(" %s ", git_info); + } + + // Fill the rest of line with space to color the entire bar + printf("%*s", width - (int)strlen(time_buffer) - + (int)strlen(parent_dir) - (int)strlen(current_dir) - + (git_info ? (int)strlen(git_info) + 3 : 0) - 8, ""); + + // Reset color + printf(ANSI_COLOR_RESET); + + // Restore cursor position + printf(ANSI_RESTORE_CURSOR); + fflush(stdout); +} + +/** + * Get the name of the parent and current directories from a path + */ + +void get_path_display(const char *cwd, char *parent_dir_name, + char *current_dir_name, size_t buf_size) { + char path_copy[PATH_MAX]; + strncpy(path_copy, cwd, PATH_MAX - 1); + path_copy[PATH_MAX - 1] = '\0'; + + // Default values if we can't parse the path + strncpy(parent_dir_name, ".", buf_size - 1); + strncpy(current_dir_name, "unknown", buf_size - 1); + parent_dir_name[buf_size - 1] = '\0'; + current_dir_name[buf_size - 1] = '\0'; + + // Handle root directory special case + if (strcmp(path_copy, "/") == 0) { + strncpy(parent_dir_name, "/", buf_size - 1); + strncpy(current_dir_name, "", buf_size - 1); + return; + } + + // Remove trailing slash if present + size_t len = strlen(path_copy); + if (len > 1 && path_copy[len - 1] == '/') { + path_copy[len - 1] = '\0'; + } + + // Find the last component (current directory) + char *last_slash = strrchr(path_copy, '/'); + if (!last_slash) { + // No slash found, must be a relative path with no slashes + strncpy(current_dir_name, path_copy, buf_size - 1); + return; + } + + // Extract current directory name + strncpy(current_dir_name, last_slash + 1, buf_size - 1); + + // Handle paths directly under root + if (last_slash == path_copy) { + strncpy(parent_dir_name, "/", buf_size - 1); + return; + } + + // Temporarily terminate the string at the last slash + *last_slash = '\0'; + + // Find the previous slash to identify parent directory + char *prev_slash = strrchr(path_copy, '/'); + + if (prev_slash) { + // Extract just the parent directory name + strncpy(parent_dir_name, prev_slash + 1, buf_size - 1); + + // Special case: parent is root + if (prev_slash == path_copy) { + strncpy(parent_dir_name, "/", buf_size - 1); + } + } else { + // No previous slash, parent is the remaining part + strncpy(parent_dir_name, path_copy, buf_size - 1); + } +} + +/** + * 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 **cmd_groups = NULL; + char *cmd_group, *saveptr0; + int group_count = 0, group_capacity = 10; + 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 command groups array + cmd_groups = malloc(group_capacity * sizeof(char*)); + if (!cmd_groups) { + perror("lsh: allocation error"); + return NULL; + } + + // Split by && symbol + cmd_group = strtok_r(line, "&&", &saveptr0); + while (cmd_group != NULL) { + // Trim whitespace + while (*cmd_group && isspace(*cmd_group)) cmd_group++; + cmd_groups[group_count] = strdup(cmd_group); + group_count++; + + if (group_count >= group_capacity - 1) { + group_capacity *= 2; + cmd_groups = realloc(cmd_groups, group_capacity * sizeof(char*)); + if (!cmd_groups) { + perror("lsh: allocation error"); + return NULL; + } + } + cmd_group = strtok_r(NULL, "&&", &saveptr0); + } + cmd_groups[group_count] = NULL; + + char *current_cmd = cmd_groups[0]; + + // Allocate initial commands array + commands = malloc(cmd_capacity * sizeof(char**)); + if (!commands) { + perror("lsh: allocation error"); + for (int i = 0; i < group_count; i++) { + free(cmd_groups[i]); + } + free(cmd_groups); + return NULL; + } + + // Split by pipe symbol '|' + cmd_str = strtok_r(current_cmd, "|", &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); + for (int i = 0; i < group_count; i++) { + free(cmd_groups[i]); + } + free(cmd_groups); + 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 + + // Store the remaining command groups in the last position (if any) + if (group_count > 1) { + // Create a special command array for the command groups marker + char **marker = malloc(sizeof(char*) * 2); + if (!marker) { + perror("lsh: allocation error"); + for (int i = 0; i < group_count; i++) { + free(cmd_groups[i]); + } + free(cmd_groups); + return NULL; + } + marker[0] = strdup("&&_COMMAND_GROUPS"); + marker[1] = NULL; + + // Create a special command array for the remaining groups + char **cmd_group_array = malloc(sizeof(char*) * group_count); + if (!cmd_group_array) { + perror("lsh: allocation error"); + free(marker[0]); + free(marker); + for (int i = 0; i < group_count; i++) { + free(cmd_groups[i]); + } + free(cmd_groups); + return NULL; + } + + // Copy all remaining command groups + for (int i = 1; i < group_count; i++) { + cmd_group_array[i-1] = strdup(cmd_groups[i]); + } + cmd_group_array[group_count-1] = NULL; + + // Allocate space for cmd_count + 2 commands + // (original commands + cmd_group_array + marker) + char ***new_commands = malloc((cmd_count + 3) * sizeof(char**)); + if (!new_commands) { + perror("lsh: allocation error"); + for (int i = 0; cmd_group_array[i] != NULL; i++) { + free(cmd_group_array[i]); + } + free(cmd_group_array); + free(marker[0]); + free(marker); + for (int i = 0; i < group_count; i++) { + free(cmd_groups[i]); + } + free(cmd_groups); + return NULL; + } + + // Copy original commands + for (int i = 0; i < cmd_count; i++) { + new_commands[i] = commands[i]; + } + + // Add the command groups and marker + new_commands[cmd_count] = cmd_group_array; + new_commands[cmd_count+1] = marker; + new_commands[cmd_count+2] = NULL; + + // Free the old commands array but not its contents + free(commands); + commands = new_commands; + } + + // Free the command groups array + for (int i = 0; i < group_count; i++) { + free(cmd_groups[i]); + } + free(cmd_groups); + + return commands; +} + +/** + * Launch an external program + */ +int lsh_launch(char **args) { + pid_t pid, wpid; + int status; + + // Fork a child process + pid = fork(); + + if (pid == 0) { + // Child process + if (execvp(args[0], args) == -1) { + perror("lsh"); + } + exit(EXIT_FAILURE); + } else if (pid < 0) { + // Error forking + perror("lsh"); + } else { + // Parent process + do { + wpid = waitpid(pid, &status, WUNTRACED); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + } + + return 1; +} + +/** + * Execute a command + */ +int lsh_execute(char **args) { + if (args[0] == NULL) { + // An empty command was entered + return 1; + } + + // Check if it's a built-in command + for (int i = 0; i < lsh_num_builtins(); i++) { + if (strcmp(args[0], builtin_str[i]) == 0) { + return (*builtin_func[i])(args); + } + } + + // If not a built-in, check aliases and launch external + char **alias_expansion = expand_alias(args); + if (alias_expansion != NULL) { + int result = lsh_execute(alias_expansion); + // Free the memory allocated for alias expansion + for (int i = 0; alias_expansion[i] != NULL; i++) { + free(alias_expansion[i]); + } + free(alias_expansion); + return result; + } + + return lsh_launch(args); +} + +/** + * Execute a pipeline of commands + */ +int lsh_execute_piped(char ***commands) { + // Count the number of commands in the pipeline + int cmd_count = 0; + while (commands[cmd_count] != NULL) { + cmd_count++; + } + + if (cmd_count == 0) { + 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]); + } + + // Create pipes + int pipes[cmd_count - 1][2]; + for (int i = 0; i < cmd_count - 1; i++) { + if (pipe(pipes[i]) == -1) { + perror("pipe"); + return 1; + } + } + + // Create processes + pid_t pids[cmd_count]; + for (int i = 0; i < cmd_count; i++) { + pids[i] = fork(); + + if (pids[i] == -1) { + perror("fork"); + return 1; + } else if (pids[i] == 0) { + // Child process + + // Set up input from previous pipe + if (i > 0) { + if (dup2(pipes[i - 1][0], STDIN_FILENO) == -1) { + perror("dup2"); + exit(EXIT_FAILURE); + } + } + + // Set up output to next pipe + if (i < cmd_count - 1) { + if (dup2(pipes[i][1], STDOUT_FILENO) == -1) { + perror("dup2"); + exit(EXIT_FAILURE); + } + } + + // Close all pipe fds + for (int j = 0; j < cmd_count - 1; j++) { + close(pipes[j][0]); + close(pipes[j][1]); + } + + // Execute the command + if (execvp(commands[i][0], commands[i]) == -1) { + perror("execvp"); + exit(EXIT_FAILURE); + } + } + } + + // Parent process closes all pipe fds + for (int i = 0; i < cmd_count - 1; i++) { + close(pipes[i][0]); + close(pipes[i][1]); + } + + // Wait for all children + for (int i = 0; i < cmd_count; i++) { + int status; + waitpid(pids[i], &status, 0); + } + + return 1; +} + +/** + * Free memory for a command array from lsh_split_commands + */ +void free_commands(char ***commands) { + for (int i = 0; commands[i] != NULL; i++) { + for (int j = 0; commands[i][j] != NULL; j++) { + free(commands[i][j]); + } + free(commands[i]); + } + free(commands); +} + +/** + * Display a welcome banner + */ +void display_welcome_banner(void) { + printf(ANSI_COLOR_CYAN); + printf("╔════════════════════════════════════════════════════════════╗\n"); + printf("║ Welcome to the LSH Shell (Linux) ║\n"); + printf("║ ║\n"); + printf("║ Type 'help' to see available commands ║\n"); + printf("╚════════════════════════════════════════════════════════════╝\n"); + printf(ANSI_COLOR_RESET); +} + +/** + * Main shell loop + */ +void lsh_loop(void) { + char *line; + char **args; + char ***commands = NULL; + int status = 1; + char git_info[LSH_RL_BUFSIZE] = {0}; + int terminal_fd; + + // Initialize terminal for raw mode + terminal_fd = init_terminal(&g_orig_termios); + + // Initialize the status bar + //init_status_bar(STDOUT_FILENO); + + // Initialize subsystems + init_aliases(); + init_bookmarks(); + init_tab_completion(); + init_persistent_history(); + init_favorite_cities(); + init_themes(); + init_autocorrect(); + init_git_integration(); + + // Display the welcome banner + display_welcome_banner(); + + do { + // Check for console resize + check_console_resize(STDOUT_FILENO); + + // Get Git status for current directory + // If git_status() returns null, git_info[0] will remain 0 + char *git_status_info = get_git_status(); + if (git_status_info != NULL) { + strncpy(git_info, git_status_info, LSH_RL_BUFSIZE - 1); + git_info[LSH_RL_BUFSIZE - 1] = '\0'; + free(git_status_info); + } else { + git_info[0] = '\0'; + } + + // Update status bar with Git information + update_status_bar(STDOUT_FILENO, git_info); + + // Get input from the user + line = lsh_read_line(); + + // Empty line check + if (line == NULL || line[0] == '\0') { + if (line != NULL) { + free(line); + } + continue; + } + + // Add line to persistent history + add_to_history(line); + + // Check for pipes or && and parse into multiple commands if present + if (strchr(line, '|') != NULL || strchr(line, '&') != NULL) { + commands = lsh_split_commands(line); + + // Find if there are command groups (&&) + int has_cmd_groups = 0; + char **remaining_cmd_groups = NULL; + char **marker = NULL; + int cmd_count = 0; + + // Count commands and check for the special command groups marker + while (commands[cmd_count] != NULL) { + cmd_count++; + } + + // We need at least 2 positions for command groups: + // 1. The groups themselves + // 2. The marker with "&&_COMMAND_GROUPS" + if (cmd_count > 1) { + // Get the last non-null command + marker = commands[cmd_count-1]; + + // Check if it's the marker + if (marker != NULL && marker[0] != NULL && + strcmp(marker[0], "&&_COMMAND_GROUPS") == 0) { + + has_cmd_groups = 1; + + // Get the command groups from the previous position + remaining_cmd_groups = commands[cmd_count-2]; + + // Set these positions to NULL so they don't get processed + // as regular commands + commands[cmd_count-1] = NULL; + commands[cmd_count-2] = NULL; + } + } + + // Execute the first command or pipeline + status = lsh_execute_piped(commands); + + // If successful and we have command groups, execute them sequentially + if (status && has_cmd_groups && remaining_cmd_groups != NULL) { + for (int i = 0; remaining_cmd_groups[i] != NULL; i++) { + char *cmd_group = remaining_cmd_groups[i]; + + // Parse this command group + char *cmd_copy = strdup(cmd_group); + + // Check if it contains pipes + if (strchr(cmd_copy, '|') != NULL) { + char ***cmd_commands = lsh_split_commands(cmd_copy); + if (cmd_commands) { + status = lsh_execute_piped(cmd_commands); + free_commands(cmd_commands); + } + } else { + // Simple command without pipes + char **args = lsh_split_line(cmd_copy); + + // Check for corrections before executing + char **corrected_args = check_for_corrections(args); + if (corrected_args != NULL) { + for (int j = 0; args[j] != NULL; j++) { + free(args[j]); + } + free(args); + args = corrected_args; + } + + // Execute command + status = lsh_execute(args); + + // Free allocated memory + for (int j = 0; args[j] != NULL; j++) { + free(args[j]); + } + free(args); + } + + free(cmd_copy); + + // If a command failed, stop execution + if (!status) { + break; + } + } + } + + // Clean up marker and command groups if they exist + if (has_cmd_groups && marker) { + // Free marker + if (marker[0]) free(marker[0]); + free(marker); + + // Free command groups + if (remaining_cmd_groups) { + for (int i = 0; remaining_cmd_groups[i] != NULL; i++) { + free(remaining_cmd_groups[i]); + } + free(remaining_cmd_groups); + } + } + + free_commands(commands); + } else { + // Normal command parsing + args = lsh_split_line(line); + + // Check for corrections before executing + char **corrected_args = check_for_corrections(args); + if (corrected_args != NULL) { + // Free the original args + for (int i = 0; args[i] != NULL; i++) { + free(args[i]); + } + free(args); + args = corrected_args; + } + + // Execute command + status = lsh_execute(args); + + // Free allocated memory + for (int i = 0; args[i] != NULL; i++) { + free(args[i]); + } + free(args); + } + + free(line); + } while (status); + + // Shutdown subsystems + shutdown_aliases(); + shutdown_bookmarks(); + shutdown_tab_completion(); + shutdown_persistent_history(); + shutdown_favorite_cities(); + shutdown_themes(); + shutdown_autocorrect(); + + // Restore terminal + restore_terminal(terminal_fd, &g_orig_termios); +}