Files
ferrum-old/builtins.c
2025-06-06 10:46:12 +02:00

1205 lines
32 KiB
C

/**
* 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 <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
// 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) {
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 <command>'\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, "<DIR>");
} 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 <command>\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) {
system("git push");
} 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 {
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;
}