first commit

This commit is contained in:
2025-04-28 12:35:52 +02:00
commit 08fb0af502
68 changed files with 9626 additions and 0 deletions

22
Makefile Normal file
View File

@@ -0,0 +1,22 @@
CC = gcc
CFLAGS = -Wall -Wextra -g
LIBS = -lm
# Get all .c files
SOURCES = $(wildcard *.c)
# Generate object file names from source file names
OBJECTS = $(SOURCES:.c=.o)
TARGET = shell
all: $(TARGET)
$(TARGET): $(OBJECTS)
$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJECTS) $(TARGET)
.PHONY: all clean

488
aliases.c Normal file
View File

@@ -0,0 +1,488 @@
/**
* aliases.c
* Implementation of shell alias functionality
*/
#include "aliases.h"
#include "builtins.h"
#include <sys/stat.h>
#include <time.h>
// Global variables for alias storage
AliasEntry *aliases = NULL;
int alias_count = 0;
int alias_capacity = 0;
// Path to the aliases file
char aliases_file_path[PATH_MAX];
/**
* Initialize the alias system
*/
void init_aliases(void) {
// Set initial capacity
alias_capacity = 10;
aliases = (AliasEntry*)malloc(alias_capacity * sizeof(AliasEntry));
if (!aliases) {
fprintf(stderr, "lsh: allocation error in init_aliases\n");
return;
}
// Determine aliases file location - in user's home directory
char *home_dir = getenv("HOME");
if (home_dir) {
snprintf(aliases_file_path, PATH_MAX, "%s/.lsh_aliases", home_dir);
} else {
// Fallback to current directory if HOME not available
strcpy(aliases_file_path, ".lsh_aliases");
}
// Load aliases from file
load_aliases();
}
/**
* Clean up the alias system
*/
void cleanup_aliases(void) {
if (!aliases) return;
// Free all alias entries
for (int i = 0; i < alias_count; i++) {
free(aliases[i].name);
free(aliases[i].command);
}
// Free the array
free(aliases);
aliases = NULL;
alias_count = 0;
alias_capacity = 0;
}
/**
* Shutdown the alias system
*/
void shutdown_aliases(void) {
// Save aliases to file
save_aliases();
// Clean up
cleanup_aliases();
}
/**
* Load aliases from file
*/
int load_aliases(void) {
FILE *fp = fopen(aliases_file_path, "r");
if (!fp) {
return 0; // File doesn't exist or can't be opened
}
char line[LSH_RL_BUFSIZE];
char name[LSH_RL_BUFSIZE / 2];
char command[LSH_RL_BUFSIZE / 2];
while (fgets(line, sizeof(line), fp)) {
// Skip comments and empty lines
if (line[0] == '#' || line[0] == '\n') {
continue;
}
// Parse alias definitions (name=command)
if (sscanf(line, "%s=%s", name, command) == 2) {
add_alias(name, command);
}
}
fclose(fp);
return 1;
}
/**
* Save aliases to file
*/
int save_aliases(void) {
FILE *fp = fopen(aliases_file_path, "w");
if (!fp) {
fprintf(stderr, "lsh: error saving aliases to %s\n", aliases_file_path);
return 0;
}
// Write file header
time_t t = time(NULL);
struct tm *tm_info = localtime(&t);
char timestamp[26];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
fprintf(fp, "# LSH Aliases - Last updated: %s\n\n", timestamp);
// Write each alias
for (int i = 0; i < alias_count; i++) {
fprintf(fp, "%s=%s\n", aliases[i].name, aliases[i].command);
}
fclose(fp);
return 1;
}
/**
* Add a new alias
*/
int add_alias(const char *name, const char *command) {
if (!name || !command) {
return 0;
}
// Check if alias already exists, update it if it does
for (int i = 0; i < alias_count; i++) {
if (strcmp(aliases[i].name, name) == 0) {
// Update existing alias
free(aliases[i].command);
aliases[i].command = strdup(command);
return 1;
}
}
// Check if we need to expand the array
if (alias_count >= alias_capacity) {
alias_capacity *= 2;
AliasEntry *new_aliases = (AliasEntry*)realloc(aliases, alias_capacity * sizeof(AliasEntry));
if (!new_aliases) {
fprintf(stderr, "lsh: allocation error in add_alias\n");
return 0;
}
aliases = new_aliases;
}
// Add new alias
aliases[alias_count].name = strdup(name);
aliases[alias_count].command = strdup(command);
alias_count++;
return 1;
}
/**
* Remove an alias
*/
int remove_alias(const char *name) {
if (!name) {
return 0;
}
for (int i = 0; i < alias_count; i++) {
if (strcmp(aliases[i].name, name) == 0) {
// Free memory for this alias
free(aliases[i].name);
free(aliases[i].command);
// Shift remaining aliases down
for (int j = i; j < alias_count - 1; j++) {
aliases[j] = aliases[j + 1];
}
alias_count--;
return 1;
}
}
return 0; // Alias not found
}
/**
* Find an alias by name
*/
AliasEntry* find_alias(const char *name) {
for (int i = 0; i < alias_count; i++) {
if (strcmp(aliases[i].name, name) == 0) {
return &aliases[i];
}
}
return NULL;
}
/**
* Expand a command line by replacing aliases
*/
char* expand_aliases(const char *command) {
if (!command) {
return NULL;
}
char *result = strdup(command);
if (!result) return NULL;
// Copy the command for tokenization
char *cmd_copy = strdup(result);
if (!cmd_copy) {
free(result);
return NULL;
}
// Extract the first word (command name)
char *token = strtok(cmd_copy, " \t");
if (!token) {
free(cmd_copy);
return result;
}
// Look up the alias
AliasEntry *alias = find_alias(token);
if (alias) {
// Get the args after the command
char *args = strchr(command, ' ');
// Allocate new buffer for expanded command
free(result);
if (args) {
// Concatenate alias command and original args
result = malloc(strlen(alias->command) + strlen(args) + 1);
if (result) {
strcpy(result, alias->command);
strcat(result, args);
}
} else {
// No args, just use alias command
result = strdup(alias->command);
}
}
free(cmd_copy);
return result;
}
/**
* Command handler for the "alias" command
*/
int lsh_alias(char **args) {
// No arguments - list all aliases
if (args[1] == NULL) {
for (int i = 0; i < alias_count; i++) {
printf("alias %s='%s'\n", aliases[i].name, aliases[i].command);
}
return 1;
}
// Pre-defined aliases
if (strcmp(args[1], "vim-mode") == 0) {
// Configure aliases for Vim-like operation
add_alias("v", "vim");
// Check for neovim first
FILE *test_nvim = popen("which nvim 2>/dev/null", "r");
if (test_nvim) {
char buffer[128];
if (fgets(buffer, sizeof(buffer), test_nvim) != NULL) {
// Neovim found, use it as preferred editor
pclose(test_nvim);
add_alias("e", "nvim");
printf("Vim mode activated (using Neovim)\n");
return 1;
}
pclose(test_nvim);
// Check for regular vim
FILE *test_vim = popen("which vim 2>/dev/null", "r");
if (test_vim) {
char buffer[128];
if (fgets(buffer, sizeof(buffer), test_vim) != NULL) {
// Vim found
pclose(test_vim);
add_alias("e", "vim");
printf("Vim mode activated\n");
return 1;
}
pclose(test_vim);
}
}
// Fallback to vi if neither vim nor neovim is found
add_alias("e", "vi");
printf("Vim mode activated (using vi)\n");
return 1;
} else if (strcmp(args[1], "emacs-mode") == 0) {
// Configure aliases for Emacs-like operation
FILE *test_emacs = popen("which emacs 2>/dev/null", "r");
if (test_emacs) {
char buffer[128];
if (fgets(buffer, sizeof(buffer), test_emacs) != NULL) {
pclose(test_emacs);
add_alias("e", "emacs -nw");
printf("Emacs mode activated\n");
return 1;
}
pclose(test_emacs);
}
printf("Emacs not found. Emacs mode not activated.\n");
return 1;
}
// Format: alias name=command
char *equals = strchr(args[1], '=');
if (equals) {
// Split at equals sign
*equals = '\0';
char *name = args[1];
char *command = equals + 1;
// Remove quotes around command if present
if (*command == '\'' || *command == '"') {
command++;
char *end = command + strlen(command) - 1;
if (*end == '\'' || *end == '"') {
*end = '\0';
}
}
add_alias(name, command);
printf("Alias added: %s='%s'\n", name, command);
} else if (args[2] != NULL) {
// Format: alias name command
add_alias(args[1], args[2]);
printf("Alias added: %s='%s'\n", args[1], args[2]);
} else {
// Just a name - show this specific alias
AliasEntry *alias = find_alias(args[1]);
if (alias) {
printf("alias %s='%s'\n", alias->name, alias->command);
} else {
printf("Alias '%s' not found\n", args[1]);
}
}
// Save aliases to file
save_aliases();
return 1;
}
/**
* Command handler for the "unalias" command
*/
int lsh_unalias(char **args) {
if (args[1] == NULL) {
printf("unalias: missing argument\n");
return 1;
}
if (remove_alias(args[1])) {
printf("Alias '%s' removed\n", args[1]);
// Save aliases to file
save_aliases();
} else {
printf("Alias '%s' not found\n", args[1]);
}
return 1;
}
/**
* Command handler to show all aliases
*/
int lsh_aliases(char **args) {
for (int i = 0; i < alias_count; i++) {
printf("alias %s='%s'\n", aliases[i].name, aliases[i].command);
}
return 1;
}
/**
* Get alias names for tab completion
*/
char** get_alias_names(int *count) {
if (alias_count == 0 || !count) {
*count = 0;
return NULL;
}
char **names = (char**)malloc(alias_count * sizeof(char*));
if (!names) {
*count = 0;
return NULL;
}
for (int i = 0; i < alias_count; i++) {
names[i] = strdup(aliases[i].name);
}
*count = alias_count;
return names;
}
/**
* Expand alias in args array
*/
char** expand_alias(char **args) {
if (!args || !args[0]) {
return NULL;
}
// Check if the command is an alias
AliasEntry *alias = find_alias(args[0]);
if (!alias) {
return NULL;
}
// Count tokens in alias command
char *alias_copy = strdup(alias->command);
if (!alias_copy) {
return NULL;
}
char **alias_tokens = malloc(LSH_TOK_BUFSIZE * sizeof(char*));
if (!alias_tokens) {
free(alias_copy);
return NULL;
}
char *token, *rest = alias_copy;
int token_count = 0;
while ((token = strtok_r(rest, " \t", &rest)) != NULL) {
alias_tokens[token_count++] = strdup(token);
if (token_count >= LSH_TOK_BUFSIZE) {
fprintf(stderr, "lsh: alias expansion: too many tokens\n");
break;
}
}
free(alias_copy);
// Count original arguments
int arg_count = 0;
while (args[arg_count] != NULL) {
arg_count++;
}
// Allocate space for expanded command
char **expanded_args = malloc((token_count + arg_count) * sizeof(char*));
if (!expanded_args) {
for (int i = 0; i < token_count; i++) {
free(alias_tokens[i]);
}
free(alias_tokens);
return NULL;
}
// Copy alias tokens
for (int i = 0; i < token_count; i++) {
expanded_args[i] = alias_tokens[i];
}
// Copy remaining arguments (skip the command)
for (int i = 1; i < arg_count; i++) {
expanded_args[token_count + i - 1] = strdup(args[i]);
}
// Null-terminate the array
expanded_args[token_count + arg_count - 1] = NULL;
free(alias_tokens);
return expanded_args;
}

63
aliases.h Normal file
View File

@@ -0,0 +1,63 @@
/**
* aliases.h
* Definitions for shell alias functionality
*/
#ifndef ALIASES_H
#define ALIASES_H
#include "common.h"
// Structure to represent an alias
typedef struct AliasEntry {
char *name; // Alias name (command to type)
char *command; // The command it expands to
} AliasEntry;
// Initialize alias system
void init_aliases(void);
// Clean up alias system
void cleanup_aliases(void);
// Shutdown the alias system
void shutdown_aliases(void);
// Load aliases from file
int load_aliases(void);
// Save aliases to file
int save_aliases(void);
// Add a new alias
int add_alias(const char *name, const char *command);
// Remove an alias
int remove_alias(const char *name);
// Find an alias by name
AliasEntry* find_alias(const char *name);
// Expand a command line by replacing aliases
char* expand_aliases(const char *command);
// Command handler for the "alias" command
int lsh_alias(char **args);
// Command handler for the "unalias" command
int lsh_unalias(char **args);
// Command handler to list all aliases
int lsh_aliases(char **args);
// Get alias names for tab completion
char** get_alias_names(int *count);
// Expand alias in args array
char** expand_alias(char **args);
// Global variables for alias storage
extern AliasEntry *aliases;
extern int alias_count;
#endif // ALIASES_H

BIN
aliases.o Normal file

Binary file not shown.

168
autocorrect.c Normal file
View File

@@ -0,0 +1,168 @@
/**
* autocorrect.c
* Implementation of command auto-correction functionality
*/
#include "autocorrect.h"
#include "builtins.h"
/**
* Calculate Levenshtein distance between two strings
* Used to determine similarity between mistyped command and potential
* corrections
*/
int levenshtein_distance(const char *s1, const char *s2) {
int len1 = strlen(s1);
int len2 = strlen(s2);
// Create a matrix to store distances
int **matrix = (int **)malloc((len1 + 1) * sizeof(int *));
if (!matrix) return -1;
for (int i = 0; i <= len1; i++) {
matrix[i] = (int *)malloc((len2 + 1) * sizeof(int));
if (!matrix[i]) {
for (int j = 0; j < i; j++) {
free(matrix[j]);
}
free(matrix);
return -1;
}
}
// Initialize the matrix
for (int i = 0; i <= len1; i++) {
matrix[i][0] = i;
}
for (int j = 0; j <= len2; j++) {
matrix[0][j] = j;
}
// Fill the matrix
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
matrix[i][j] = min3(matrix[i - 1][j] + 1, // deletion
matrix[i][j - 1] + 1, // insertion
matrix[i - 1][j - 1] + cost // substitution
);
}
}
// Get the result and free the matrix
int result = matrix[len1][len2];
for (int i = 0; i <= len1; i++) {
free(matrix[i]);
}
free(matrix);
return result;
}
/**
* Helper function to find minimum of three integers
*/
int min3(int a, int b, int c) {
int min = a;
if (b < min)
min = b;
if (c < min)
min = c;
return min;
}
/**
* Initialize the autocorrect system
*/
void init_autocorrect(void) {
// No initialization required currently
}
/**
* Shutdown the autocorrect system
*/
void shutdown_autocorrect(void) {
// No cleanup required currently
}
/**
* Check a command for possible corrections and suggest alternative
*/
/**
* Check a command for possible corrections and suggest alternative
*/
/**
* Check a command for possible corrections and suggest alternative
*/
char **check_for_corrections(char **args) {
if (!args || !args[0]) {
return NULL;
}
const char *command = args[0];
// Skip very short commands or commands that start with ./ or /
if (strlen(command) < 3 || command[0] == '.' || command[0] == '/') {
return NULL;
}
// Don't try to correct arguments, only the command itself
// Check if the command is valid, if it is, no correction needed
if (is_valid_command(command)) {
return NULL;
}
// List of common commands to check against
const char *common_commands[] = {
"ls", "cd", "grep", "find", "cat", "mv", "cp", "rm", "mkdir", "rmdir",
"chmod", "chown", "ps", "top", "df", "du", "free", "mount", "umount",
"tar", "zip", "unzip", "ssh", "scp", "ping", "netstat", "ifconfig",
"route", "traceroute", "wget", "curl", "apt", "apt-get", "yum", "dnf",
"pacman", "git", "make", "gcc", "g++", "python", "python3", "node",
"npm", "vim", "nano", "history", "clear", "exit", "alias", "man", "help",
"touch", "echo", "pwd", "sudo", "shutdown", "reboot", NULL
};
// Check built-in commands too
int best_distance = 3; // Maximum edit distance to consider a correction
const char *best_match = NULL;
// First check among built-in commands
for (int i = 0; i < lsh_num_builtins(); i++) {
int distance = levenshtein_distance(command, builtin_str[i]);
if (distance < best_distance) {
best_distance = distance;
best_match = builtin_str[i];
}
}
// Then check common commands
for (int i = 0; common_commands[i] != NULL; i++) {
int distance = levenshtein_distance(command, common_commands[i]);
if (distance < best_distance) {
best_distance = distance;
best_match = common_commands[i];
}
}
// If we found a good match, suggest it
if (best_match) {
printf("Command '%s' not found. Did you mean '%s'?\n", command, best_match);
// No longer waiting for input - just return NULL
}
return NULL;
}
/**
* Count the number of arguments in a NULL-terminated array
*/
int count_args(char **args) {
int count = 0;
while (args[count] != NULL) {
count++;
}
return count;
}

50
autocorrect.h Normal file
View File

@@ -0,0 +1,50 @@
/**
* autocorrect.h
* Header for command auto-correction functionality
*/
#ifndef AUTOCORRECT_H
#define AUTOCORRECT_H
#include "common.h"
#include "line_reader.h"
#include <limits.h>
/**
* Calculate Levenshtein distance between two strings
*
* @param s1 First string
* @param s2 Second string
* @return Edit distance between the strings
*/
int levenshtein_distance(const char *s1, const char *s2);
/**
* Helper function to find minimum of three integers
*/
int min3(int a, int b, int c);
/**
* Initialize the autocorrect system
*/
void init_autocorrect(void);
/**
* Shutdown the autocorrect system
*/
void shutdown_autocorrect(void);
/**
* Check a command for possible corrections
*
* @param args Command arguments
* @return Corrected arguments array (must be freed by caller) or NULL if no correction
*/
char **check_for_corrections(char **args);
/**
* Count the number of arguments in a NULL-terminated array
*/
int count_args(char **args);
#endif // AUTOCORRECT_H

BIN
autocorrect.o Normal file

Binary file not shown.

409
bookmarks.c Normal file
View File

@@ -0,0 +1,409 @@
/**
* bookmarks.c
* Implementation of directory bookmarks functionality
*/
#include "bookmarks.h"
#include <fcntl.h>
#include <termios.h>
// Global variables for bookmark storage
BookmarkEntry *bookmarks = NULL;
int bookmark_count = 0;
int bookmark_capacity = 0;
// Path to the bookmarks file
char bookmarks_file_path[PATH_MAX];
/**
* Initialize the bookmark system
*/
void init_bookmarks(void) {
// Set initial capacity
bookmark_capacity = 10;
bookmarks =
(BookmarkEntry *)malloc(bookmark_capacity * sizeof(BookmarkEntry));
if (!bookmarks) {
fprintf(stderr, "lsh: allocation error in init_bookmarks\n");
return;
}
// Determine bookmarks file location - in user's home directory
char *home_dir = getenv("HOME");
if (home_dir) {
snprintf(bookmarks_file_path, PATH_MAX, "%s/.lsh_bookmarks", home_dir);
} else {
// Fallback to current directory if HOME not available
strcpy(bookmarks_file_path, ".lsh_bookmarks");
}
// Load bookmarks from file
load_bookmarks();
}
/**
* Clean up the bookmark system
*/
void cleanup_bookmarks(void) {
if (!bookmarks)
return;
// Free all bookmark entries
for (int i = 0; i < bookmark_count; i++) {
free(bookmarks[i].name);
free(bookmarks[i].path);
}
// Free the array
free(bookmarks);
bookmarks = NULL;
bookmark_count = 0;
bookmark_capacity = 0;
}
/**
* Shutdown the bookmark system
*/
void shutdown_bookmarks(void) {
// Save bookmarks to file
save_bookmarks();
// Clean up
cleanup_bookmarks();
}
/**
* Load bookmarks from file
*/
int load_bookmarks(void) {
FILE *fp = fopen(bookmarks_file_path, "r");
if (!fp) {
return 0; // File doesn't exist or can't be opened
}
char line[LSH_RL_BUFSIZE];
char name[LSH_RL_BUFSIZE / 2];
char path[LSH_RL_BUFSIZE / 2];
while (fgets(line, sizeof(line), fp)) {
// Skip comments and empty lines
if (line[0] == '#' || line[0] == '\n') {
continue;
}
// Try to parse using tab as delimiter first
char *tab = strchr(line, '\t');
if (tab) {
// Split at tab
*tab = '\0';
strncpy(name, line, sizeof(name) - 1);
name[sizeof(name) - 1] = '\0';
strncpy(path, tab + 1, sizeof(path) - 1);
path[sizeof(path) - 1] = '\0';
// Remove newline if present
char *newline = strchr(path, '\n');
if (newline) {
*newline = '\0';
}
add_bookmark(name, path);
} else {
// Parse in name=path format as fallback
if (sscanf(line, "%[^=]=%s", name, path) == 2) {
add_bookmark(name, path);
}
}
}
fclose(fp);
return 1;
}
/**
* Save bookmarks to file
*/
int save_bookmarks(void) {
FILE *fp = fopen(bookmarks_file_path, "w");
if (!fp) {
fprintf(stderr, "lsh: error saving bookmarks to %s\n", bookmarks_file_path);
return 0;
}
// Write file header
time_t t = time(NULL);
struct tm *tm_info = localtime(&t);
char timestamp[26];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
fprintf(fp, "# LSH Bookmarks - Last updated: %s\n\n", timestamp);
// Write each bookmark
for (int i = 0; i < bookmark_count; i++) {
fprintf(fp, "%s\t%s\n", bookmarks[i].name, bookmarks[i].path);
}
fclose(fp);
return 1;
}
/**
* Add a new bookmark
*/
int add_bookmark(const char *name, const char *path) {
if (!name || !path) {
return 0;
}
// Check if bookmark already exists
for (int i = 0; i < bookmark_count; i++) {
if (strcmp(bookmarks[i].name, name) == 0) {
// Update existing bookmark
free(bookmarks[i].path);
bookmarks[i].path = strdup(path);
return 1;
}
}
// Check if we need to expand the array
if (bookmark_count >= bookmark_capacity) {
bookmark_capacity *= 2;
BookmarkEntry *new_bookmarks = (BookmarkEntry *)realloc(
bookmarks, bookmark_capacity * sizeof(BookmarkEntry));
if (!new_bookmarks) {
fprintf(stderr, "lsh: allocation error in add_bookmark\n");
return 0;
}
bookmarks = new_bookmarks;
}
// Add new bookmark
bookmarks[bookmark_count].name = strdup(name);
bookmarks[bookmark_count].path = strdup(path);
bookmark_count++;
return 1;
}
/**
* Remove a bookmark
*/
int remove_bookmark(const char *name) {
if (!name) {
return 0;
}
for (int i = 0; i < bookmark_count; i++) {
if (strcmp(bookmarks[i].name, name) == 0) {
// Free memory for this bookmark
free(bookmarks[i].name);
free(bookmarks[i].path);
// Shift remaining bookmarks down
for (int j = i; j < bookmark_count - 1; j++) {
bookmarks[j] = bookmarks[j + 1];
}
bookmark_count--;
return 1;
}
}
return 0; // Bookmark not found
}
/**
* Find a bookmark by name
*/
BookmarkEntry *find_bookmark(const char *name) {
for (int i = 0; i < bookmark_count; i++) {
if (strcmp(bookmarks[i].name, name) == 0) {
return &bookmarks[i];
}
}
return NULL;
}
/**
* Command handler for the "bookmark" command
*/
int lsh_bookmark(char **args) {
char cwd[PATH_MAX];
// No arguments - add bookmark for current directory
if (args[1] == NULL) {
printf("Usage: bookmark <name> [path]\n");
printf("If path is omitted, the current directory is used.\n");
return 1;
}
// Get current working directory if no path is specified
if (args[2] == NULL) {
if (getcwd(cwd, sizeof(cwd)) == NULL) {
perror("lsh: getcwd");
return 1;
}
add_bookmark(args[1], cwd);
printf("Bookmark added: %s -> %s\n", args[1], cwd);
} else {
add_bookmark(args[1], args[2]);
printf("Bookmark added: %s -> %s\n", args[1], args[2]);
}
// Save bookmarks to file
save_bookmarks();
return 1;
}
/**
* Command handler for the "bookmarks" command
*/
int lsh_bookmarks(char **args) {
if (bookmark_count == 0) {
printf("No bookmarks defined.\n");
printf("Use 'bookmark <name> [path]' to add a bookmark.\n");
return 1;
}
// Check if we should use a pager
if (bookmark_count > 20) {
// Check for neovim first
FILE *test_nvim = popen("which nvim 2>/dev/null", "r");
if (test_nvim) {
char buffer[128];
if (fgets(buffer, sizeof(buffer), test_nvim) != NULL) {
pclose(test_nvim);
FILE *pager = popen("nvim -R -c 'set nonumber' -", "w");
if (pager) {
fprintf(pager, "LSH Bookmarks:\n\n");
for (int i = 0; i < bookmark_count; i++) {
fprintf(pager, " %s -> %s\n", bookmarks[i].name, bookmarks[i].path);
}
pclose(pager);
return 1;
}
}
pclose(test_nvim);
// Try vim
FILE *test_vim = popen("which vim 2>/dev/null", "r");
if (test_vim) {
char buffer[128];
if (fgets(buffer, sizeof(buffer), test_vim) != NULL) {
pclose(test_vim);
FILE *pager = popen("vim -R -c 'set nonumber' -", "w");
if (pager) {
fprintf(pager, "LSH Bookmarks:\n\n");
for (int i = 0; i < bookmark_count; i++) {
fprintf(pager, " %s -> %s\n", bookmarks[i].name, bookmarks[i].path);
}
pclose(pager);
return 1;
}
}
pclose(test_vim);
}
}
}
// Simple output if no pager available or bookmark count is small
printf("LSH Bookmarks:\n\n");
// Use ANSI escape codes for colored output
for (int i = 0; i < bookmark_count; i++) {
printf(" " ANSI_COLOR_GREEN "%s" ANSI_COLOR_RESET " -> %s\n",
bookmarks[i].name, bookmarks[i].path);
}
return 1;
}
/**
* Command handler for the "goto" command
*/
int lsh_goto(char **args) {
if (args[1] == NULL) {
printf("Usage: goto <bookmark_name>\n");
return 1;
}
BookmarkEntry *bookmark = find_bookmark(args[1]);
if (bookmark) {
if (chdir(bookmark->path) != 0) {
perror("lsh: chdir");
return 1;
}
printf("Changed directory to: %s\n", bookmark->path);
} else {
printf("Bookmark '%s' not found.\n", args[1]);
}
return 1;
}
/**
* Command handler for the "unbookmark" command
*/
int lsh_unbookmark(char **args) {
if (args[1] == NULL) {
printf("Usage: unbookmark <bookmark_name>\n");
return 1;
}
if (remove_bookmark(args[1])) {
printf("Bookmark '%s' removed.\n", args[1]);
save_bookmarks();
} else {
printf("Bookmark '%s' not found.\n", args[1]);
}
return 1;
}
/**
* Get bookmark names for tab completion
*/
char **get_bookmark_names(int *count) {
if (bookmark_count == 0 || !count) {
*count = 0;
return NULL;
}
char **names = (char **)malloc(bookmark_count * sizeof(char *));
if (!names) {
*count = 0;
return NULL;
}
for (int i = 0; i < bookmark_count; i++) {
names[i] = strdup(bookmarks[i].name);
}
*count = bookmark_count;
return names;
}
/**
* Find a matching bookmark by partial name
*/
char *find_matching_bookmark(const char *partial_name) {
if (!partial_name || strlen(partial_name) == 0) {
return NULL;
}
// If only one bookmark exists, return it if there's any match
if (bookmark_count == 1) {
return strdup(bookmarks[0].name);
}
// Look for partial matches
for (int i = 0; i < bookmark_count; i++) {
if (strncasecmp(bookmarks[i].name, partial_name, strlen(partial_name)) == 0) {
return strdup(bookmarks[i].name);
}
}
return NULL;
}

57
bookmarks.h Normal file
View File

@@ -0,0 +1,57 @@
/**
* bookmarks.h
* Functions for managing directory bookmarks
*/
#ifndef BOOKMARKS_H
#define BOOKMARKS_H
#include "common.h"
// Structure to represent a bookmark
typedef struct {
char *name; // Bookmark name
char *path; // Directory path
} BookmarkEntry;
// Initialize bookmark system
void init_bookmarks(void);
// Clean up bookmark system
void cleanup_bookmarks(void);
// Shutdown the bookmark system
void shutdown_bookmarks(void);
// Load bookmarks from file
int load_bookmarks(void);
// Save bookmarks to file
int save_bookmarks(void);
// Add a new bookmark
int add_bookmark(const char *name, const char *path);
// Remove a bookmark by name
int remove_bookmark(const char *name);
// Find a bookmark by name
BookmarkEntry *find_bookmark(const char *name);
// Command handlers
int lsh_bookmark(char **args); // Add a bookmark
int lsh_bookmarks(char **args); // List all bookmarks
int lsh_goto(char **args); // Jump to a bookmark
int lsh_unbookmark(char **args); // Remove a bookmark
// Get bookmarked directories for tab completion
char **get_bookmark_names(int *count);
// Find a matching bookmark by partial name
char *find_matching_bookmark(const char *partial_name);
// Global bookmarks array
extern BookmarkEntry *bookmarks;
extern int bookmark_count;
#endif // BOOKMARKS_H

BIN
bookmarks.o Normal file

Binary file not shown.

764
builtins.c Normal file
View File

@@ -0,0 +1,764 @@
/**
* builtins.c
* Implementation of all built-in shell commands
*/
#include "builtins.h"
#include "common.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 <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <time.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"};
// 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};
/**
* 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 count = 0;
int detailed = 0;
// Check if we should show detailed listing
if (args[1] != NULL && (strcmp(args[1], "-l") == 0 || strcmp(args[1], "--long") == 0)) {
detailed = 1;
}
if (getcwd(cwd, sizeof(cwd)) == NULL) {
perror("lsh: getcwd");
return 1;
}
dir = opendir(cwd);
if (dir == NULL) {
perror("lsh: opendir");
return 1;
}
if (detailed) {
printf("Mode Size Modified Name\n");
printf("----------------------------------------------------\n");
}
// First collect all entries
typedef struct {
char name[256];
struct stat stat;
int is_dir;
} DirEntry;
DirEntry *entries = malloc(1000 * sizeof(DirEntry)); // Arbitrary limit
int entry_count = 0;
while ((entry = readdir(dir)) != NULL && entry_count < 1000) {
// 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) {
strcpy(entries[entry_count].name, entry->d_name);
entries[entry_count].stat = file_stat;
entries[entry_count].is_dir = S_ISDIR(file_stat.st_mode);
entry_count++;
}
}
closedir(dir);
// Sort entries - directories first then files
for (int i = 0; i < entry_count - 1; i++) {
for (int j = i + 1; j < entry_count; j++) {
if (entries[i].is_dir < entries[j].is_dir ||
(entries[i].is_dir == entries[j].is_dir &&
strcmp(entries[i].name, entries[j].name) > 0)) {
DirEntry temp = entries[i];
entries[i] = entries[j];
entries[j] = temp;
}
}
}
// Display entries
for (int i = 0; i < entry_count; i++) {
if (detailed) {
// Format mode (simplified)
char mode[11] = "----------";
if (entries[i].is_dir) mode[0] = 'd';
if (entries[i].stat.st_mode & S_IRUSR) mode[1] = 'r';
if (entries[i].stat.st_mode & S_IWUSR) mode[2] = 'w';
if (entries[i].stat.st_mode & S_IXUSR) mode[3] = 'x';
if (entries[i].stat.st_mode & S_IRGRP) mode[4] = 'r';
if (entries[i].stat.st_mode & S_IWGRP) mode[5] = 'w';
if (entries[i].stat.st_mode & S_IXGRP) mode[6] = 'x';
if (entries[i].stat.st_mode & S_IROTH) mode[7] = 'r';
if (entries[i].stat.st_mode & S_IWOTH) mode[8] = 'w';
if (entries[i].stat.st_mode & S_IXOTH) mode[9] = 'x';
// Format size
char size_str[20];
if (entries[i].stat.st_size < 1024) {
snprintf(size_str, sizeof(size_str), "%d B", (int)entries[i].stat.st_size);
} else if (entries[i].stat.st_size < 1024 * 1024) {
snprintf(size_str, sizeof(size_str), "%.1f KB",
entries[i].stat.st_size / 1024.0);
} else {
snprintf(size_str, sizeof(size_str), "%.1f MB",
entries[i].stat.st_size / (1024.0 * 1024.0));
}
// Format time
char time_str[20];
struct tm *tm_info = localtime(&entries[i].stat.st_mtime);
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", tm_info);
// Print entry with colors
printf("%s %-10s %-17s ", mode, size_str, time_str);
if (entries[i].is_dir) {
printf(ANSI_COLOR_BLUE "%s" ANSI_COLOR_RESET "\n", entries[i].name);
} else if (entries[i].stat.st_mode & S_IXUSR) {
printf(ANSI_COLOR_GREEN "%s" ANSI_COLOR_RESET "\n", entries[i].name);
} else {
printf("%s\n", entries[i].name);
}
} else {
count++;
if (entries[i].is_dir) {
printf(ANSI_COLOR_BLUE "%-20s" ANSI_COLOR_RESET, entries[i].name);
} else if (entries[i].stat.st_mode & S_IXUSR) {
printf(ANSI_COLOR_GREEN "%-20s" ANSI_COLOR_RESET, entries[i].name);
} else {
printf("%-20s", entries[i].name);
}
if (count % 4 == 0) {
printf("\n");
}
}
}
free(entries);
if (!detailed && count % 4 != 0) {
printf("\n");
}
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;
}
/**
* Built-in command: news
* Placeholder for news functionality
*/
int lsh_news(char **args) {
printf("Fetching news feed...\n");
printf("News functionality not fully implemented yet\n");
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(" b - branch\n");
printf(" ch - checkout\n");
return 1;
}
// Execute the appropriate git command based on shorthand
if (strcmp(args[1], "s") == 0) {
system("git status");
} 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) {
system("git diff");
} 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;
}

100
builtins.h Normal file
View File

@@ -0,0 +1,100 @@
/**
* builtins.h
* Declarations for all built-in shell commands
*/
#ifndef BUILTINS_H
#define BUILTINS_H
#include "common.h"
#include <time.h>
// 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);
// 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

BIN
builtins.o Normal file

Binary file not shown.

115
common.h Normal file
View File

@@ -0,0 +1,115 @@
/**
* common.h
* Common includes, defines and macros used throughout the shell
*/
#ifndef COMMON_H
#define COMMON_H
// Standard library includes
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h> // For isprint
#include <fcntl.h> // For file control
#include <unistd.h> // For POSIX API (replaces direct.h)
#include <sys/types.h>
#include <sys/wait.h> // For waitpid
#include <sys/stat.h> // For stat (replaces fileapi.h)
#include <termios.h> // For terminal control
#include <sys/ioctl.h> // For console dimensions
#include <signal.h> // For signal handling
#include <time.h> // For time functions
#include <limits.h> // 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
// 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

306
countdown_timer.c Normal file
View File

@@ -0,0 +1,306 @@
/**
* countdown_timer.c
* Implementation of a focus timer that integrates with the shell status bar
*/
#include "countdown_timer.h"
#include "common.h"
#include "shell.h"
#include "themes.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
// Global timer state
static struct {
BOOL is_active; // Whether timer is currently running
time_t end_time; // When timer will expire (in seconds since epoch)
char display_text[64]; // Current timer display text
char session_name[128]; // Optional session name
pthread_t timer_thread; // Timer thread ID
BOOL should_exit; // Flag to signal thread exit
BOOL is_temporarily_hidden; // Flag to hide timer while running external programs
} timer_state = {FALSE, 0, "", "", 0, FALSE, FALSE};
/**
* Update timer display text based on remaining time
*/
static void update_timer_display(void) {
if (!timer_state.is_active) {
strcpy(timer_state.display_text, "");
return;
}
// Calculate remaining time
time_t now = time(NULL);
int seconds_left = (int)difftime(timer_state.end_time, now);
if (seconds_left <= 0) {
// Timer has expired
if (strlen(timer_state.session_name) > 0) {
snprintf(timer_state.display_text, sizeof(timer_state.display_text),
"Session '%s' complete!", timer_state.session_name);
} else {
strcpy(timer_state.display_text, "Timer complete!");
}
return;
}
// Format time as MM:SS
int minutes = seconds_left / 60;
int seconds = seconds_left % 60;
if (strlen(timer_state.session_name) > 0) {
snprintf(timer_state.display_text, sizeof(timer_state.display_text),
"%s: %02d:%02d remaining", timer_state.session_name, minutes, seconds);
} else {
snprintf(timer_state.display_text, sizeof(timer_state.display_text),
"Focus: %02d:%02d remaining", minutes, seconds);
}
}
/**
* Timer thread function
*/
static void *timer_thread_func(void *param) {
while (!timer_state.should_exit) {
// Update timer display
update_timer_display();
// Check if timer has expired
time_t now = time(NULL);
if (difftime(timer_state.end_time, now) <= 0) {
// Play a bell sound
printf("\a");
fflush(stdout);
// Update status bar with completion message
update_timer_display();
// Keep thread running to display completion message
}
// Sleep for 1 second
sleep(1);
}
return NULL;
}
/**
* Start a new countdown timer
*/
int start_countdown_timer(int seconds, const char *name) {
// Stop any existing timer
if (timer_state.is_active) {
stop_countdown_timer();
}
// Set timer parameters
timer_state.end_time = time(NULL) + seconds;
timer_state.is_active = TRUE;
timer_state.should_exit = FALSE;
timer_state.is_temporarily_hidden = FALSE;
// Set optional session name
if (name && *name) {
strncpy(timer_state.session_name, name, sizeof(timer_state.session_name) - 1);
timer_state.session_name[sizeof(timer_state.session_name) - 1] = '\0';
} else {
timer_state.session_name[0] = '\0';
}
// Initialize display text
update_timer_display();
// Create timer thread
int ret = pthread_create(&timer_state.timer_thread, NULL, timer_thread_func, NULL);
if (ret != 0) {
fprintf(stderr, "Failed to create timer thread: %s\n", strerror(ret));
timer_state.is_active = FALSE;
return 0;
}
// Detach thread so it cleans up automatically
pthread_detach(timer_state.timer_thread);
return 1;
}
/**
* Stop the current countdown timer
*/
void stop_countdown_timer() {
if (!timer_state.is_active) {
return;
}
// Signal thread to exit
timer_state.should_exit = TRUE;
// Wait for thread to terminate (with timeout)
// Use a more portable approach since pthread_timedjoin_np is GNU-specific
// Set a timeout of 2 seconds
struct timespec timeout;
timeout.tv_sec = 2;
timeout.tv_nsec = 0;
// Sleep for the timeout period - the thread will keep running
// and clean itself up since we detached it
nanosleep(&timeout, NULL);
// Reset timer state
timer_state.is_active = FALSE;
timer_state.display_text[0] = '\0';
timer_state.session_name[0] = '\0';
}
/**
* Check if a timer is currently active
*/
BOOL is_timer_active() {
return timer_state.is_active;
}
/**
* Get the current timer display text
*/
const char *get_timer_display() {
if (timer_state.is_temporarily_hidden || !timer_state.is_active) {
return "";
}
return timer_state.display_text;
}
/**
* Temporarily hide the timer display
*/
void hide_timer_display(void) {
timer_state.is_temporarily_hidden = TRUE;
}
/**
* Restore the timer display after it was hidden
*/
void show_timer_display(void) {
timer_state.is_temporarily_hidden = FALSE;
}
/**
* Parse time string in format "XhYmZs" to seconds
*/
static int parse_time_string(const char *time_str) {
if (!time_str || !*time_str) {
return 0;
}
int total_seconds = 0;
char *str = strdup(time_str);
if (!str) {
return 0;
}
char *pos = str;
char *next_pos;
int value;
while (*pos) {
// Skip non-digits
while (*pos && !isdigit(*pos)) {
pos++;
}
if (!*pos) {
break;
}
// Parse number
value = (int)strtol(pos, &next_pos, 10);
if (pos == next_pos) {
break;
}
// Skip whitespace
pos = next_pos;
while (*pos && isspace(*pos)) {
pos++;
}
// Check unit
if (*pos == 'h' || *pos == 'H') {
total_seconds += value * 3600;
} else if (*pos == 'm' || *pos == 'M') {
total_seconds += value * 60;
} else if (*pos == 's' || *pos == 'S') {
total_seconds += value;
} else {
// No unit specified, assume minutes
total_seconds += value * 60;
}
// Move to next part
if (*pos) {
pos++;
}
}
free(str);
return total_seconds;
}
/**
* Command handler for the focus timer command
*/
int lsh_focus_timer(char **args) {
if (!args[1]) {
// No arguments - show help
printf("Usage: focus_timer [start|stop] [duration] [session name]\n");
printf("Examples:\n");
printf(" focus_timer start 25m \"Coding session\" # Start a 25-minute timer\n");
printf(" focus_timer start 1h30m # Start a 1 hour 30 minute timer\n");
printf(" focus_timer stop # Stop the current timer\n");
printf("Current status: %s\n",
timer_state.is_active ? timer_state.display_text : "No active timer");
return 1;
}
if (strcmp(args[1], "stop") == 0) {
if (timer_state.is_active) {
printf("Stopping timer...\n");
stop_countdown_timer();
} else {
printf("No active timer to stop.\n");
}
return 1;
}
if (strcmp(args[1], "start") == 0) {
if (!args[2]) {
printf("Error: Duration required. Example: focus_timer start 25m\n");
return 1;
}
int seconds = parse_time_string(args[2]);
if (seconds <= 0) {
printf("Error: Invalid duration format. Examples: 25m, 1h30m, 90s\n");
return 1;
}
const char *session_name = args[3] ? args[3] : "";
if (start_countdown_timer(seconds, session_name)) {
printf("Timer started: %s\n", timer_state.display_text);
} else {
printf("Failed to start timer.\n");
}
return 1;
}
printf("Unknown timer command: %s\n", args[1]);
return 1;
}

57
countdown_timer.h Normal file
View File

@@ -0,0 +1,57 @@
/**
* countdown_timer.h
* Header for focus timer functionality
*/
#ifndef COUNTDOWN_TIMER_H
#define COUNTDOWN_TIMER_H
#include "common.h"
/**
* Start a new countdown timer
*
* @param seconds Total duration in seconds
* @param name Optional name for the timer session
* @return 1 if successful, 0 on failure
*/
int start_countdown_timer(int seconds, const char *name);
/**
* Stop the current countdown timer
*/
void stop_countdown_timer();
/**
* Check if a timer is currently active
*
* @return TRUE if timer is active, FALSE otherwise
*/
BOOL is_timer_active();
/**
* Get the current timer display text
*
* @return Pointer to the display text string
*/
const char *get_timer_display();
/**
* Temporarily hide the timer display (used when running external programs)
*/
void hide_timer_display(void);
/**
* Restore the timer display after it was hidden
*/
void show_timer_display(void);
/**
* Command handler for the "timer" command
*
* @param args Command arguments
* @return 1 to continue shell, 0 to exit
*/
int lsh_focus_timer(char **args);
#endif // COUNTDOWN_TIMER_H

BIN
countdown_timer.o Normal file

Binary file not shown.

324
favorite_cities.c Normal file
View File

@@ -0,0 +1,324 @@
/**
* favorite_cities.c
* Implementation of favorite cities management for weather lookup
*/
#include "favorite_cities.h"
#include "builtins.h"
#include "common.h"
// Global variables for favorite cities storage
CityEntry *favorite_cities = NULL;
int favorite_city_count = 0;
int favorite_city_capacity = 0;
// Path to the favorite cities file
char favorite_cities_file_path[PATH_MAX];
/**
* Initialize the favorite cities system
*/
void init_favorite_cities(void) {
// Set initial capacity
favorite_city_capacity = 10;
favorite_cities =
(CityEntry *)malloc(favorite_city_capacity * sizeof(CityEntry));
if (!favorite_cities) {
fprintf(stderr, "lsh: allocation error in init_favorite_cities\n");
return;
}
// Determine favorite cities file location - in user's home directory
char *home_dir = getenv("HOME");
if (home_dir) {
snprintf(favorite_cities_file_path, PATH_MAX, "%s/.lsh_favorite_cities",
home_dir);
} else {
// Fallback to current directory if HOME not available
strcpy(favorite_cities_file_path, ".lsh_favorite_cities");
}
// Load favorite cities from file
load_favorite_cities();
// Add some default cities if none exist
if (favorite_city_count == 0) {
add_favorite_city("New York");
add_favorite_city("London");
add_favorite_city("Tokyo");
add_favorite_city("Paris");
add_favorite_city("Sydney");
save_favorite_cities();
}
}
/**
* Clean up the favorite cities system
*/
void cleanup_favorite_cities(void) {
if (!favorite_cities)
return;
free(favorite_cities);
favorite_cities = NULL;
favorite_city_count = 0;
favorite_city_capacity = 0;
}
/**
* Shutdown favorite cities system - save and cleanup
*/
void shutdown_favorite_cities(void) {
// Save to file first
save_favorite_cities();
// Then clean up resources
cleanup_favorite_cities();
}
/**
* Load favorite cities from file
*/
int load_favorite_cities(void) {
FILE *file = fopen(favorite_cities_file_path, "r");
if (!file) {
// File doesn't exist yet - not an error
return 1;
}
// Clear existing favorite cities
favorite_city_count = 0;
char line[128];
int line_number = 0;
while (fgets(line, sizeof(line), file)) {
line_number++;
// Remove newline
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0';
len--;
}
// Skip empty lines and comments
if (len == 0 || line[0] == '#')
continue;
// Add the city
add_favorite_city(line);
}
fclose(file);
return 1;
}
/**
* Save favorite cities to file
*/
int save_favorite_cities(void) {
FILE *file = fopen(favorite_cities_file_path, "w");
if (!file) {
fprintf(stderr, "lsh: error: could not save favorite cities to %s\n",
favorite_cities_file_path);
return 0;
}
// Write header comment
fprintf(file, "# LSH favorite cities file\n");
fprintf(file,
"# Each line represents a favorite city for weather lookups\n\n");
// Write each city
for (int i = 0; i < favorite_city_count; i++) {
fprintf(file, "%s\n", favorite_cities[i].name);
}
fclose(file);
return 1;
}
/**
* Add a new favorite city
*/
int add_favorite_city(const char *name) {
if (!name)
return 0;
// Check if we need to resize the array
if (favorite_city_count >= favorite_city_capacity) {
favorite_city_capacity *= 2;
CityEntry *new_cities = (CityEntry *)realloc(
favorite_cities, favorite_city_capacity * sizeof(CityEntry));
if (!new_cities) {
fprintf(stderr, "lsh: allocation error in add_favorite_city\n");
return 0;
}
favorite_cities = new_cities;
}
// Check if the city already exists
for (int i = 0; i < favorite_city_count; i++) {
if (strcasecmp(favorite_cities[i].name, name) == 0) {
// City already exists
return 1;
}
}
// Add new city
strncpy(favorite_cities[favorite_city_count].name, name,
sizeof(favorite_cities[favorite_city_count].name) - 1);
favorite_cities[favorite_city_count]
.name[sizeof(favorite_cities[favorite_city_count].name) - 1] = '\0';
favorite_city_count++;
return 1;
}
/**
* Remove a favorite city
*/
int remove_favorite_city(const char *name) {
if (!name)
return 0;
for (int i = 0; i < favorite_city_count; i++) {
if (strcasecmp(favorite_cities[i].name, name) == 0) {
// Shift remaining cities
for (int j = i; j < favorite_city_count - 1; j++) {
strcpy(favorite_cities[j].name, favorite_cities[j + 1].name);
}
favorite_city_count--;
return 1;
}
}
return 0; // City not found
}
/**
* Find a favorite city by name
*/
CityEntry *find_favorite_city(const char *name) {
if (!name)
return NULL;
for (int i = 0; i < favorite_city_count; i++) {
if (strcasecmp(favorite_cities[i].name, name) == 0) {
return &favorite_cities[i];
}
}
return NULL;
}
/**
* Get favorite city names for tab completion
*/
char **get_favorite_city_names(int *count) {
if (favorite_city_count == 0) {
*count = 0;
return NULL;
}
char **names = (char **)malloc(favorite_city_count * sizeof(char *));
if (!names) {
*count = 0;
return NULL;
}
for (int i = 0; i < favorite_city_count; i++) {
names[i] = strdup(favorite_cities[i].name);
}
*count = favorite_city_count;
return names;
}
/**
* Command handler for the "cities" command
* Usage:
* cities add <city>
* cities remove <city>
* cities list
*/
int lsh_cities(char **args) {
if (args[1] == NULL) {
// No subcommand - show usage
printf("Usage: cities <command> [arguments]\n");
printf("Commands:\n");
printf(" add <city> Add a city to favorites\n");
printf(" remove <city> Remove a city from favorites\n");
printf(" list List all favorite cities\n");
return 1;
}
// Handle subcommands
if (strcmp(args[1], "add") == 0) {
if (args[2] == NULL) {
printf("Usage: cities add <city>\n");
return 1;
}
// Combine all arguments after "add" into a single city name
char city_name[256] = "";
strncpy(city_name, args[2], sizeof(city_name) - 1);
for (int i = 3; args[i] != NULL; i++) {
strncat(city_name, " ", sizeof(city_name) - strlen(city_name) - 1);
strncat(city_name, args[i], sizeof(city_name) - strlen(city_name) - 1);
}
if (add_favorite_city(city_name)) {
save_favorite_cities();
printf("Added '%s' to favorite cities\n", city_name);
}
} else if (strcmp(args[1], "remove") == 0) {
if (args[2] == NULL) {
printf("Usage: cities remove <city>\n");
return 1;
}
// Combine all arguments after "remove" into a single city name
char city_name[256] = "";
strncpy(city_name, args[2], sizeof(city_name) - 1);
for (int i = 3; args[i] != NULL; i++) {
strncat(city_name, " ", sizeof(city_name) - strlen(city_name) - 1);
strncat(city_name, args[i], sizeof(city_name) - strlen(city_name) - 1);
}
if (remove_favorite_city(city_name)) {
save_favorite_cities();
printf("Removed '%s' from favorite cities\n", city_name);
} else {
printf("City '%s' not found in favorites\n", city_name);
}
} else if (strcmp(args[1], "list") == 0) {
// List all favorite cities
if (favorite_city_count == 0) {
printf("No favorite cities defined\n");
return 1;
}
printf("\nFavorite Cities:\n\n");
// Display cities in a nice format
for (int i = 0; i < favorite_city_count; i++) {
// Print city name with color
printf(ANSI_COLOR_CYAN " %s" ANSI_COLOR_RESET "\n", favorite_cities[i].name);
}
printf("\n");
} else {
printf("Unknown command: %s\n", args[1]);
printf("Available commands: add, remove, list\n");
}
return 1;
}

50
favorite_cities.h Normal file
View File

@@ -0,0 +1,50 @@
/**
* favorite_cities.h
* Functions for managing favorite cities for weather lookups
*/
#ifndef FAVORITE_CITIES_H
#define FAVORITE_CITIES_H
#include "common.h"
// Structure to represent a favorite city
typedef struct {
char name[128]; // City name
} CityEntry;
// Initialize favorite cities system
void init_favorite_cities(void);
// Clean up favorite cities system
void cleanup_favorite_cities(void);
// Shutdown favorite cities system (save and cleanup)
void shutdown_favorite_cities(void);
// Load favorite cities from file
int load_favorite_cities(void);
// Save favorite cities to file
int save_favorite_cities(void);
// Add a new favorite city
int add_favorite_city(const char *name);
// Remove a favorite city
int remove_favorite_city(const char *name);
// Find a favorite city by name
CityEntry *find_favorite_city(const char *name);
// Get favorite city names for tab completion
char **get_favorite_city_names(int *count);
// Command handlers
int lsh_cities(char **args); // Main cities command handler
// Global favorite cities array
extern CityEntry *favorite_cities;
extern int favorite_city_count;
#endif // FAVORITE_CITIES_H

BIN
favorite_cities.o Normal file

Binary file not shown.

510
filters.c Normal file
View File

@@ -0,0 +1,510 @@
/**
* filters.c
* Implementation of filter commands (where, sort-by, etc.)
*/
#include "filters.h"
#include <string.h>
#include <strings.h> // For strcasecmp and strncasecmp
/**
* Filter a table based on a condition (e.g., where size > 10kb)
*/
TableData* lsh_where(TableData *input, char **args) {
if (!input || !args || !args[0]) {
fprintf(stderr, "lsh: where: missing arguments\n");
fprintf(stderr, "Usage: ... | where FIELD OPERATOR VALUE\n");
fprintf(stderr, " e.g.: ls | where size > 10kb\n");
return NULL;
}
// Parse filter condition (field, operator, value)
char *field = args[0];
char *op = NULL;
char *value = NULL;
// Find operator and value
if (args[1] != NULL) {
if (strcmp(args[1], ">") == 0 ||
strcmp(args[1], "<") == 0 ||
strcmp(args[1], "==") == 0 ||
strcmp(args[1], ">=") == 0 ||
strcmp(args[1], "<=") == 0) {
op = args[1];
value = args[2];
}
}
if (!op || !value) {
fprintf(stderr, "lsh: where: invalid filter condition\n");
fprintf(stderr, "Usage: ... | where FIELD OPERATOR VALUE\n");
fprintf(stderr, " e.g.: ls | where size > 10kb\n");
return NULL;
}
// Find field index
int field_idx = -1;
for (int i = 0; i < input->header_count; i++) {
if (strcasecmp(input->headers[i], field) == 0) {
field_idx = i;
break;
}
}
if (field_idx == -1) {
fprintf(stderr, "lsh: where: unknown field '%s'\n", field);
fprintf(stderr, "Available fields: ");
for (int i = 0; i < input->header_count; i++) {
fprintf(stderr, "%s%s", i > 0 ? ", " : "", input->headers[i]);
}
fprintf(stderr, "\n");
return NULL;
}
// Create new filtered table
return filter_table(input, field, op, value);
}
/**
* Sort a table based on a column (e.g., sort-by size desc)
*/
TableData* lsh_sort_by(TableData *input, char **args) {
if (!input || !args || !args[0]) {
fprintf(stderr, "lsh: sort-by: missing arguments\n");
fprintf(stderr, "Usage: ... | sort-by FIELD [asc|desc]\n");
fprintf(stderr, " e.g.: ls | sort-by size desc\n");
return NULL;
}
// Parse sort field and direction
char *field = args[0];
int descending = 0;
// Check if direction is specified
if (args[1] != NULL) {
if (strcasecmp(args[1], "desc") == 0 ||
strcasecmp(args[1], "descending") == 0) {
descending = 1;
}
}
// Find field index
int field_idx = -1;
for (int i = 0; i < input->header_count; i++) {
if (strcasecmp(input->headers[i], field) == 0) {
field_idx = i;
break;
}
}
if (field_idx == -1) {
fprintf(stderr, "lsh: sort-by: unknown field '%s'\n", field);
fprintf(stderr, "Available fields: ");
for (int i = 0; i < input->header_count; i++) {
fprintf(stderr, "%s%s", i > 0 ? ", " : "", input->headers[i]);
}
fprintf(stderr, "\n");
return NULL;
}
// Create a copy of the table
TableData *result = create_table(input->headers, input->header_count);
if (!result) {
return NULL;
}
// Copy all rows to the new table
for (int i = 0; i < input->row_count; i++) {
DataValue *row_copy = (DataValue*)malloc(input->header_count * sizeof(DataValue));
if (!row_copy) {
fprintf(stderr, "lsh: allocation error in sort_by\n");
free_table(result);
return NULL;
}
for (int j = 0; j < input->header_count; j++) {
row_copy[j] = copy_data_value(&input->rows[i][j]);
}
add_table_row(result, row_copy);
}
// Now sort the rows based on the specified column
// Bubble sort for simplicity (not efficient for large datasets)
for (int i = 0; i < result->row_count - 1; i++) {
for (int j = 0; j < result->row_count - i - 1; j++) {
int compare_result = 0;
// Compare based on data type
if (result->rows[j][field_idx].type == TYPE_STRING ||
result->rows[j][field_idx].type == TYPE_SIZE) {
// Handle special case for sizes (KB, MB, etc.)
if (strcasecmp(result->headers[field_idx], "Size") == 0 ||
strcasecmp(result->headers[field_idx], "Memory") == 0) {
long size1 = extract_size_bytes(result->rows[j][field_idx].value.str_val);
long size2 = extract_size_bytes(result->rows[j+1][field_idx].value.str_val);
compare_result = (size1 > size2) ? 1 : (size1 < size2) ? -1 : 0;
} else {
// Regular string comparison
compare_result = strcasecmp(
result->rows[j][field_idx].value.str_val,
result->rows[j+1][field_idx].value.str_val
);
}
} else if (result->rows[j][field_idx].type == TYPE_INT) {
int val1 = result->rows[j][field_idx].value.int_val;
int val2 = result->rows[j+1][field_idx].value.int_val;
compare_result = (val1 > val2) ? 1 : (val1 < val2) ? -1 : 0;
} else if (result->rows[j][field_idx].type == TYPE_FLOAT) {
float val1 = result->rows[j][field_idx].value.float_val;
float val2 = result->rows[j+1][field_idx].value.float_val;
compare_result = (val1 > val2) ? 1 : (val1 < val2) ? -1 : 0;
}
// If descending, invert comparison result
if (descending) {
compare_result = -compare_result;
}
// Swap if needed
if (compare_result > 0) {
DataValue *temp = result->rows[j];
result->rows[j] = result->rows[j+1];
result->rows[j+1] = temp;
}
}
}
return result;
}
/**
* Select specific columns from a table
* Supports both space-separated and comma-separated field names
*/
TableData* lsh_select(TableData *input, char **args) {
if (!input || !args || !args[0]) {
fprintf(stderr, "lsh: select: missing arguments\n");
fprintf(stderr, "Usage: ... | select FIELD1 FIELD2 ...\n");
fprintf(stderr, " e.g.: ls | select Name Size\n");
return NULL;
}
// Count how many fields we have
int num_fields = 0;
int arg_index = 0;
// First pass: count fields
while (args[arg_index] != NULL) {
// Check if this argument contains commas
char *p = args[arg_index];
int commas_in_arg = 0;
while (*p) {
if (*p == ',') {
commas_in_arg++;
}
p++;
}
if (commas_in_arg > 0) {
// Add number of comma-separated fields (commas + 1)
num_fields += commas_in_arg + 1;
} else {
// Just one field in this argument
num_fields++;
}
arg_index++;
}
// Allocate array for field indices
int *field_indices = (int*)malloc(num_fields * sizeof(int));
if (!field_indices) {
fprintf(stderr, "lsh: allocation error in select\n");
return NULL;
}
// Second pass: process field names
int field_count = 0;
arg_index = 0;
while (args[arg_index] != NULL && field_count < num_fields) {
// Make a copy since we'll modify it with strtok
char *arg_copy = strdup(args[arg_index]);
if (!arg_copy) {
fprintf(stderr, "lsh: allocation error in select\n");
free(field_indices);
return NULL;
}
// Check if this argument contains commas
if (strchr(arg_copy, ',')) {
// Handle comma-separated fields
char *field_token;
char *saveptr;
field_token = strtok_r(arg_copy, ",", &saveptr);
while (field_token && field_count < num_fields) {
// Trim whitespace
while (isspace(*field_token)) field_token++;
char *end = field_token + strlen(field_token) - 1;
while (end > field_token && isspace(*end)) end--;
*(end + 1) = '\0';
// Find field index
int found = 0;
for (int i = 0; i < input->header_count; i++) {
if (strcasecmp(input->headers[i], field_token) == 0) {
field_indices[field_count++] = i;
found = 1;
break;
}
}
if (!found) {
fprintf(stderr, "lsh: select: unknown field '%s'\n", field_token);
free(field_indices);
free(arg_copy);
return NULL;
}
field_token = strtok_r(NULL, ",", &saveptr);
}
} else {
// This is a single field name (no commas)
char *field = arg_copy;
// Trim whitespace
while (isspace(*field)) field++;
char *end = field + strlen(field) - 1;
while (end > field && isspace(*end)) end--;
*(end + 1) = '\0';
// Find field index
int found = 0;
for (int i = 0; i < input->header_count; i++) {
if (strcasecmp(input->headers[i], field) == 0) {
field_indices[field_count++] = i;
found = 1;
break;
}
}
if (!found) {
fprintf(stderr, "lsh: select: unknown field '%s'\n", field);
free(arg_copy);
free(field_indices);
return NULL;
}
}
free(arg_copy);
arg_index++;
}
// Create new headers for result table
char **new_headers = (char**)malloc(field_count * sizeof(char*));
if (!new_headers) {
fprintf(stderr, "lsh: allocation error in select\n");
free(field_indices);
return NULL;
}
for (int i = 0; i < field_count; i++) {
new_headers[i] = strdup(input->headers[field_indices[i]]);
}
// Create result table with new headers
TableData *result = create_table(new_headers, field_count);
// Free temporary header array
for (int i = 0; i < field_count; i++) {
free(new_headers[i]);
}
free(new_headers);
if (!result) {
free(field_indices);
return NULL;
}
// Copy rows with only the selected columns
for (int i = 0; i < input->row_count; i++) {
DataValue *row = (DataValue*)malloc(field_count * sizeof(DataValue));
if (!row) {
fprintf(stderr, "lsh: allocation error in select\n");
free_table(result);
free(field_indices);
return NULL;
}
for (int j = 0; j < field_count; j++) {
row[j] = copy_data_value(&input->rows[i][field_indices[j]]);
}
add_table_row(result, row);
}
free(field_indices);
return result;
}
/**
* Case-insensitive substring search (strcasestr equivalent for Windows)
*/
char *my_strcasestr(const char *haystack, const char *needle) {
if (!haystack || !needle) return NULL;
size_t needle_len = strlen(needle);
if (needle_len == 0) return (char*)haystack;
size_t haystack_len = strlen(haystack);
if (haystack_len < needle_len) return NULL;
for (size_t i = 0; i <= haystack_len - needle_len; i++) {
if (strncasecmp(haystack + i, needle, needle_len) == 0) {
return (char*)(haystack + i);
}
}
return NULL;
}
/**
* Filter rows where a column contains a substring
*/
TableData* lsh_contains(TableData *input, char **args) {
if (!input || !args || !args[0] || !args[1]) {
fprintf(stderr, "lsh: contains: missing arguments\n");
fprintf(stderr, "Usage: ... | contains FIELD VALUE\n");
fprintf(stderr, " e.g.: ls | contains Name .exe\n");
return NULL;
}
// Parse arguments
char *field = args[0];
char *value = args[1];
// Find field index
int field_idx = -1;
for (int i = 0; i < input->header_count; i++) {
if (strcasecmp(input->headers[i], field) == 0) {
field_idx = i;
break;
}
}
if (field_idx == -1) {
fprintf(stderr, "lsh: contains: unknown field '%s'\n", field);
fprintf(stderr, "Available fields: ");
for (int i = 0; i < input->header_count; i++) {
fprintf(stderr, "%s%s", i > 0 ? ", " : "", input->headers[i]);
}
fprintf(stderr, "\n");
return NULL;
}
// Create result table with same headers
TableData *result = create_table(input->headers, input->header_count);
if (!result) {
return NULL;
}
// Filter rows based on whether the specified column contains the substring
for (int i = 0; i < input->row_count; i++) {
int include_row = 0;
// Only string fields can be checked with contains
if (input->rows[i][field_idx].type == TYPE_STRING ||
input->rows[i][field_idx].type == TYPE_SIZE) {
char *cell_value = input->rows[i][field_idx].value.str_val;
// Case-insensitive substring check
char *found = my_strcasestr(cell_value, value);
include_row = (found != NULL);
}
if (include_row) {
// Create a copy of the row for the result table
DataValue *row_copy = (DataValue*)malloc(input->header_count * sizeof(DataValue));
if (!row_copy) {
fprintf(stderr, "lsh: allocation error in contains\n");
free_table(result);
return NULL;
}
for (int j = 0; j < input->header_count; j++) {
row_copy[j] = copy_data_value(&input->rows[i][j]);
}
add_table_row(result, row_copy);
}
}
return result;
}
/**
* Limit the number of rows displayed
*/
TableData* lsh_limit(TableData *input, char **args) {
if (!input || !args || !args[0]) {
fprintf(stderr, "lsh: limit: missing arguments\n");
fprintf(stderr, "Usage: ... | limit N\n");
fprintf(stderr, " e.g.: ls | sort-by Size desc | limit 5\n");
return NULL;
}
// Parse limit
int limit = atoi(args[0]);
if (limit <= 0) {
fprintf(stderr, "lsh: limit: invalid limit '%s', must be a positive number\n", args[0]);
return NULL;
}
// Create result table with same headers
TableData *result = create_table(input->headers, input->header_count);
if (!result) {
return NULL;
}
// Copy only the specified number of rows
int rows_to_copy = (limit < input->row_count) ? limit : input->row_count;
for (int i = 0; i < rows_to_copy; i++) {
DataValue *row_copy = (DataValue*)malloc(input->header_count * sizeof(DataValue));
if (!row_copy) {
fprintf(stderr, "lsh: allocation error in limit\n");
free_table(result);
return NULL;
}
for (int j = 0; j < input->header_count; j++) {
row_copy[j] = copy_data_value(&input->rows[i][j]);
}
add_table_row(result, row_copy);
}
return result;
}
// Define the filter arrays here
char *filter_str[] = {
"where",
"sort-by",
"select",
"contains",
"limit"
};
TableData* (*filter_func[]) (TableData*, char**) = {
&lsh_where,
&lsh_sort_by,
&lsh_select,
&lsh_contains,
&lsh_limit
};
int filter_count = sizeof(filter_str) / sizeof(char*);

66
filters.h Normal file
View File

@@ -0,0 +1,66 @@
/**
* filters.h
* Declarations for filter commands (where, sort-by, etc.)
*/
#ifndef FILTERS_H
#define FILTERS_H
#include "structured_data.h"
/**
* Filter a table based on a condition (e.g., where size > 10kb)
*
* @param input The input table
* @param args Command arguments
* @return Filtered table or NULL on error
*/
TableData* lsh_where(TableData *input, char **args);
/**
* Sort a table based on a column (e.g., sort-by size desc)
*
* @param input The input table
* @param args Command arguments
* @return Sorted table or NULL on error
*/
TableData* lsh_sort_by(TableData *input, char **args);
/**
* Select specific columns from a table
*
* @param input The input table
* @param args Command arguments
* @return Table with selected columns or NULL on error
*/
TableData* lsh_select(TableData *input, char **args);
/**
* Filter rows where a column contains a substring
*
* @param input The input table
* @param args Command arguments
* @return Filtered table or NULL on error
*/
TableData* lsh_contains(TableData *input, char **args);
/**
* Limit the number of rows displayed
*
* @param input The input table
* @param args Command arguments
* @return Limited table or NULL on error
*/
TableData* lsh_limit(TableData *input, char **args);
/**
* Case-insensitive substring search (strcasestr equivalent for Windows)
*/
char *my_strcasestr(const char *haystack, const char *needle);
// Export the filter arrays for the shell
extern char *filter_str[];
extern TableData* (*filter_func[]) (TableData*, char**);
extern int filter_count;
#endif // FILTERS_H

BIN
filters.o Normal file

Binary file not shown.

571
fzf_native.c Normal file
View File

@@ -0,0 +1,571 @@
/**
* fzf_native.c
* Wrapper for the native fzf command-line fuzzy finder
*/
#include "fzf_native.h"
#include "common.h"
#include "line_reader.h"
#include "shell.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> // For stat
/**
* Check if native fzf is installed on the system
*
* @return 1 if installed, 0 if not
*/
int is_fzf_installed(void) {
// Try to run fzf --version to check if it's installed
FILE *fp = popen("fzf --version 2>/dev/null", "r");
if (fp == NULL) {
return 0;
}
// Read output (if any)
char buffer[128];
int has_output = (fgets(buffer, sizeof(buffer), fp) != NULL);
// Close the process
pclose(fp);
return has_output;
}
/**
* Display instructions for installing fzf
*/
void show_fzf_install_instructions(void) {
printf("\nfzf is not installed on this system. To use this feature, install "
"fzf:\n\n");
printf("Installation options:\n");
printf("1. Using Git:\n");
printf(" git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf\n");
printf(" ~/.fzf/install\n\n");
printf("2. Using Chocolatey (Windows):\n");
printf(" choco install fzf\n\n");
printf("3. Using Scoop (Windows):\n");
printf(" scoop install fzf\n\n");
printf("4. Download prebuilt binary from: "
"https://github.com/junegunn/fzf/releases\n\n");
printf("After installation, restart your shell.\n");
}
/**
* Run fzf with files from the current directory
*
* @param preview Enable preview window if 1
* @param args Additional arguments for fzf
* @return Selected filename or NULL if canceled
*/
char *run_native_fzf_files(int preview, char **args) {
// Check if fzf is installed
if (!is_fzf_installed()) {
show_fzf_install_instructions();
return NULL;
}
// Build the command
char command[1024] = "find . -type f -not -path \"*/\.*\" -printf \"%P\\n\" | fzf";
// Add proper keybindings for both navigation and search toggle
strcat(command, " --bind=\"ctrl-j:down,ctrl-k:up,/:toggle-search\"");
// Add preview if requested
if (preview) {
strcat(command, " --preview=\"cat {}\"");
}
// Add any additional arguments
if (args && args[1] != NULL) {
int i = 1;
while (args[i] != NULL) {
strcat(command, " ");
// If the argument contains spaces, quote it
if (strchr(args[i], ' ') != NULL) {
strcat(command, "\"");
strcat(command, args[i]);
strcat(command, "\"");
} else {
strcat(command, args[i]);
}
i++;
}
}
// Redirect output to a temporary file to capture selection
char tempfile[PATH_MAX];
strcpy(tempfile, "/tmp/fzf_result.txt");
// Append output redirection to the command
strcat(command, " > ");
strcat(command, tempfile);
// Run the command
int result = system(command);
// Check if user canceled (fzf returns non-zero)
if (result != 0) {
remove(tempfile); // Clean up temp file even on cancel
return NULL;
}
// Read the selected filename from the temporary file
FILE *fp = fopen(tempfile, "r");
if (!fp) {
remove(tempfile);
return NULL;
}
// Read the selected line
char *selected = (char *)malloc(PATH_MAX);
if (!selected) {
fclose(fp);
remove(tempfile);
return NULL;
}
if (fgets(selected, PATH_MAX, fp) == NULL) {
fclose(fp);
remove(tempfile);
free(selected);
return NULL;
}
// Remove newline if present
size_t len = strlen(selected);
if (len > 0 && selected[len - 1] == '\n') {
selected[len - 1] = '\0';
}
// Close the file and delete it
fclose(fp);
remove(tempfile);
return selected;
}
/**
* Run fzf with all files and directories from the current directory
*
* @param recursive Search recursively if 1
* @param args Additional arguments for fzf
* @return Selected path or NULL if canceled
*/
char *run_native_fzf_all(int recursive, char **args) {
// Check if fzf is installed
if (!is_fzf_installed()) {
show_fzf_install_instructions();
return NULL;
}
// Build the command using find
char command[1024];
if (recursive) {
strcpy(command, "find . -not -path \"*/\.*\" -printf \"%P\\n\" | fzf");
} else {
strcpy(command, "find . -maxdepth 1 -not -path \"*/\.*\" -printf \"%P\\n\" | fzf");
}
// Add proper keybindings for both navigation and search toggle
strcat(command, " --bind=\"ctrl-j:down,ctrl-k:up,/:toggle-search\"");
// Add preview window showing file info or directory contents
strcat(command, " --preview=\"if [ -d {} ]; then ls -la {}; else bat "
"--color=always {} 2>/dev/null || cat {} 2>/dev/null; fi\"");
// Add any additional arguments
if (args && args[1] != NULL) {
int i = 1;
while (args[i] != NULL) {
if (strcmp(args[i], "-r") != 0 && strcmp(args[i], "--recursive") != 0) {
strcat(command, " ");
// If the argument contains spaces, quote it
if (strchr(args[i], ' ') != NULL) {
strcat(command, "\"");
strcat(command, args[i]);
strcat(command, "\"");
} else {
strcat(command, args[i]);
}
}
i++;
}
}
// Redirect output to a temporary file to capture selection
char tempfile[PATH_MAX];
strcpy(tempfile, "/tmp/fzf_result.txt");
// Append output redirection to the command
strcat(command, " > ");
strcat(command, tempfile);
// Run the command
int result = system(command);
// Check if user canceled (fzf returns non-zero)
if (result != 0) {
remove(tempfile); // Clean up temp file even on cancel
return NULL;
}
// Read the selected path from the temporary file
FILE *fp = fopen(tempfile, "r");
if (!fp) {
remove(tempfile);
return NULL;
}
// Read the selected line
char *selected = (char *)malloc(PATH_MAX);
if (!selected) {
fclose(fp);
remove(tempfile);
return NULL;
}
if (fgets(selected, PATH_MAX, fp) == NULL) {
fclose(fp);
remove(tempfile);
free(selected);
return NULL;
}
// Remove newline if present
size_t len = strlen(selected);
if (len > 0 && selected[len - 1] == '\n') {
selected[len - 1] = '\0';
}
// Close the file and delete it
fclose(fp);
remove(tempfile);
return selected;
}
/**
* Run fzf with command history
*
* @return Selected command or NULL if canceled
*/
char *run_native_fzf_history(void) {
// Check if fzf is installed
if (!is_fzf_installed()) {
show_fzf_install_instructions();
return NULL;
}
// Create a temporary file with command history
char tempfile_in[PATH_MAX];
strcpy(tempfile_in, "/tmp/fzf_history.txt");
FILE *fp_in = fopen(tempfile_in, "w");
if (!fp_in) {
return NULL;
}
// Write history to temporary file
int num_to_display =
(history_count < HISTORY_SIZE) ? history_count : HISTORY_SIZE;
int start_idx;
if (history_count <= HISTORY_SIZE) {
// Haven't filled the buffer yet
start_idx = 0;
} else {
// History buffer is full, start from the oldest command
start_idx = history_index; // Next slot to overwrite contains oldest command
}
for (int i = 0; i < num_to_display; i++) {
int idx = (start_idx + i) % HISTORY_SIZE;
if (command_history[idx].command) {
fprintf(fp_in, "%s\n", command_history[idx].command);
}
}
fclose(fp_in);
// Create a temporary file for the result
char tempfile_out[PATH_MAX];
strcpy(tempfile_out, "/tmp/fzf_result.txt");
// Build the command
char command[1024];
sprintf(command,
"cat \"%s\" | fzf --tac --no-sort "
"--bind=\"ctrl-j:down,ctrl-k:up,/:toggle-search\" > \"%s\"",
tempfile_in, tempfile_out);
// Run the command
int result = system(command);
// Delete the input file
remove(tempfile_in);
// Check if user canceled
if (result != 0) {
remove(tempfile_out); // Clean up temp file even on cancel
return NULL;
}
// Read the selected command from the output file
FILE *fp_out = fopen(tempfile_out, "r");
if (!fp_out) {
remove(tempfile_out);
return NULL;
}
// Read the selected line
char *selected = (char *)malloc(PATH_MAX);
if (!selected) {
fclose(fp_out);
remove(tempfile_out);
return NULL;
}
if (fgets(selected, PATH_MAX, fp_out) == NULL) {
fclose(fp_out);
remove(tempfile_out);
free(selected);
return NULL;
}
// Remove newline if present
size_t len = strlen(selected);
if (len > 0 && selected[len - 1] == '\n') {
selected[len - 1] = '\0';
}
// Close and delete the output file
fclose(fp_out);
remove(tempfile_out);
return selected;
}
/**
* Check if a specific editor is available
*
* @param editor Name of the editor executable
* @return 1 if available, 0 if not
*/
int is_editor_available(const char *editor) {
char command[256];
snprintf(command, sizeof(command), "%s --version >/dev/null 2>&1", editor);
return (system(command) == 0);
}
/**
* Open a file in the best available editor in the current terminal
*
* @param file_path Path to the file to open
* @param line_number Optional line number to jump to (0 to ignore)
* @return 1 if successful, 0 if failed
*/
int open_in_best_editor(const char *file_path, int line_number) {
char command[2048] = {0};
int success = 0;
// Try to detect available editors (in order of preference)
if (is_editor_available("nvim")) {
// Neovim is available - construct command with +line_number
if (line_number > 0) {
snprintf(command, sizeof(command), "nvim +%d \"%s\"", line_number,
file_path);
} else {
snprintf(command, sizeof(command), "nvim \"%s\"", file_path);
}
success = 1;
} else if (is_editor_available("vim")) {
// Vim is available
if (line_number > 0) {
snprintf(command, sizeof(command), "vim +%d \"%s\"", line_number,
file_path);
} else {
snprintf(command, sizeof(command), "vim \"%s\"", file_path);
}
success = 1;
} else if (is_editor_available("nano")) {
// Try nano if available
snprintf(command, sizeof(command), "nano \"%s\"", file_path);
success = 1;
} else if (is_editor_available("code")) {
// VSCode in the terminal if possible
if (line_number > 0) {
snprintf(command, sizeof(command), "code -g \"%s:%d\" -r", file_path,
line_number);
} else {
snprintf(command, sizeof(command), "code \"%s\" -r", file_path);
}
success = 1;
} else if (is_editor_available("notepad")) {
// Notepad as a last resort (will still open in a new window)
snprintf(command, sizeof(command), "notepad \"%s\"", file_path);
success = 1;
}
if (success) {
// Clear the screen before launching the editor
system("clear");
// Execute the command directly in the current terminal
system(command);
// Return directly to prompt without requiring key press
return 1;
} else {
// No suitable editor found
printf("No compatible editor (neovim, vim, nano or VSCode) found.\n");
return 0;
}
}
/**
* Command handler for the fzf command
*
* @param args Command arguments
* @return 1 to continue shell execution, 0 to exit shell
*/
int lsh_fzf_native(char **args) {
// Check if fzf is installed
if (!is_fzf_installed()) {
show_fzf_install_instructions();
return 1;
}
// Parse options
int recursive = 0;
int mode = 0; // 0 = all, 1 = files only, 2 = history
int no_open = 0; // Don't open files automatically if set
int arg_index = 1;
if (args[arg_index] && strcmp(args[arg_index], "--help") == 0) {
printf("Usage: fzf [options] [pattern]\n");
printf("Interactive fuzzy finder.\n");
printf("Options:\n");
printf(" -r, --recursive Search directories recursively\n");
printf(" -f, --files Search only files (not directories)\n");
printf(" -h, --history Search command history\n");
printf(" --no-open Don't automatically open selected files\n");
printf("\nControls:\n");
printf(" Ctrl+j/Ctrl+k Move down/up (vim-style navigation)\n");
printf(" Type directly To search (default mode)\n");
printf(" / Toggle search mode (allows searching for 'j' "
"and 'k')\n");
printf(" Enter Select item (and open file)\n");
printf(" Ctrl+C/Esc Cancel\n");
printf(" ? Toggle preview window\n");
return 1;
}
// Process options
while (args[arg_index] != NULL && args[arg_index][0] == '-') {
if (strcmp(args[arg_index], "-r") == 0 ||
strcmp(args[arg_index], "--recursive") == 0) {
recursive = 1;
arg_index++;
} else if (strcmp(args[arg_index], "-f") == 0 ||
strcmp(args[arg_index], "--files") == 0) {
mode = 1; // Files only
arg_index++;
} else if (strcmp(args[arg_index], "-h") == 0 ||
strcmp(args[arg_index], "--history") == 0) {
mode = 2; // History
arg_index++;
} else if (strcmp(args[arg_index], "--no-open") == 0) {
no_open = 1;
arg_index++;
} else {
break; // Unknown option, treat as pattern
}
}
// Run fzf based on the selected mode
char *result = NULL;
if (mode == 2) {
// History mode
result = run_native_fzf_history();
} else if (mode == 1) {
// Files only mode
result = run_native_fzf_files(1, args);
} else {
// All files and directories
result = run_native_fzf_all(recursive, args);
}
// Handle the result
if (result) {
if (mode == 2) {
// Execute the selected command from history
printf("Executing: %s\n", result);
// Parse and execute the command
char *cmd_copy = strdup(result);
if (cmd_copy) {
char **cmd_args = lsh_split_line(cmd_copy);
lsh_execute(cmd_args);
// Clean up
free(cmd_args);
free(cmd_copy);
}
} else {
// File or directory selected
// Check if it's a regular file (not a directory)
struct stat file_stat;
int stat_result = stat(result, &file_stat);
if (stat_result == 0) {
int is_directory = S_ISDIR(file_stat.st_mode);
if (is_directory) {
// It's a directory, just print it
printf("Selected directory: %s\n", result);
// Optionally change to that directory
printf("Do you want to change to this directory? (y/n): ");
char response = getchar();
// Clear input buffer
while (getchar() != '\n')
;
if (response == 'y' || response == 'Y') {
chdir(result);
printf("Changed directory to: %s\n", result);
}
} else {
// It's a file
printf("Selected file: %s\n", result);
// Open in editor if allowed
if (!no_open) {
int success = open_in_best_editor(result, 0);
if (success) {
printf("File opened in editor.\n");
}
}
}
} else {
// Could not determine file type, just print the path
printf("Selected: %s\n", result);
}
}
free(result);
} else {
printf("No selection made.\n");
}
return 1;
}

57
fzf_native.h Normal file
View File

@@ -0,0 +1,57 @@
/**
* fzf_native.h
* Header for native fzf command-line fuzzy finder wrapper
*/
#ifndef FZF_NATIVE_H
#define FZF_NATIVE_H
#include "builtins.h"
#include "common.h"
/**
* Check if native fzf is installed on the system
*
* @return 1 if installed, 0 if not
*/
int is_fzf_installed(void);
/**
* Display instructions for installing fzf
*/
void show_fzf_install_instructions(void);
/**
* Run fzf with files from the current directory
*
* @param preview Enable preview window if 1
* @param args Additional arguments for fzf
* @return Selected filename or NULL if canceled
*/
char *run_native_fzf_files(int preview, char **args);
/**
* Run fzf with all files and directories from the current directory
*
* @param recursive Search recursively if 1
* @param args Additional arguments for fzf
* @return Selected path or NULL if canceled
*/
char *run_native_fzf_all(int recursive, char **args);
/**
* Run fzf with command history
*
* @return Selected command or NULL if canceled
*/
char *run_native_fzf_history(void);
/**
* Command handler for the fzf command
*
* @param args Command arguments
* @return 1 to continue shell execution, 0 to exit shell
*/
int lsh_fzf_native(char **args);
#endif // FZF_NATIVE_H

BIN
fzf_native.o Normal file

Binary file not shown.

154
git_integration.c Normal file
View File

@@ -0,0 +1,154 @@
/**
* git_integration.c
* Implementation of Git repository detection and information
*/
#include "git_integration.h"
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
/**
* Initialize Git integration
*/
void init_git_integration(void) {
// No initialization needed for Linux
}
/**
* Check if the current directory is in a Git repository and get branch info
*/
int get_git_branch(char *branch_name, size_t buffer_size, int *is_dirty) {
char git_dir[PATH_MAX] = "";
char cmd[PATH_MAX] = "";
FILE *fp;
int status = 0;
// Initialize output parameters
if (branch_name && buffer_size > 0) {
branch_name[0] = '\0';
}
if (is_dirty) {
*is_dirty = 0;
}
// First check if .git directory exists (faster than running git command)
strcpy(git_dir, ".git");
struct stat st;
if (stat(git_dir, &st) != 0 || !S_ISDIR(st.st_mode)) {
// Try to find .git in parent directories
// (Note: this is more complex, simplified here)
return 0;
}
// Get current branch
if (branch_name && buffer_size > 0) {
strcpy(cmd, "git rev-parse --abbrev-ref HEAD 2>/dev/null");
fp = popen(cmd, "r");
if (fp) {
if (fgets(branch_name, buffer_size, fp) != NULL) {
// Remove trailing newline
char *newline = strchr(branch_name, '\n');
if (newline) {
*newline = '\0';
}
status = 1;
}
pclose(fp);
}
}
// Check if working directory is dirty
if (is_dirty && status) {
strcpy(cmd, "git status --porcelain 2>/dev/null");
fp = popen(cmd, "r");
if (fp) {
char ch;
if ((ch = fgetc(fp)) != EOF) {
*is_dirty = 1;
}
pclose(fp);
}
}
return status;
}
/**
* Get the name of the Git repository
*/
int get_git_repo_name(char *repo_name, size_t buffer_size) {
if (!repo_name || buffer_size == 0) {
return 0;
}
repo_name[0] = '\0';
char cmd[PATH_MAX] = "git rev-parse --show-toplevel 2>/dev/null";
FILE *fp = popen(cmd, "r");
if (!fp) {
return 0;
}
char path[PATH_MAX];
if (fgets(path, sizeof(path), fp) == NULL) {
pclose(fp);
return 0;
}
pclose(fp);
// Remove trailing newline
char *newline = strchr(path, '\n');
if (newline) {
*newline = '\0';
}
// Extract the directory name from the full path
char *last_slash = strrchr(path, '/');
if (last_slash) {
strncpy(repo_name, last_slash + 1, buffer_size - 1);
repo_name[buffer_size - 1] = '\0';
return 1;
}
return 0;
}
/**
* Get Git status for the current repository
*/
char *get_git_status(void) {
char branch_name[100] = "";
int is_dirty = 0;
char repo_name[100] = "";
// Check if we're in a Git repo and get branch info
if (!get_git_branch(branch_name, sizeof(branch_name), &is_dirty)) {
return NULL; // Not in a Git repo
}
// Get repo name
get_git_repo_name(repo_name, sizeof(repo_name));
// Format status string
char *status = malloc(256);
if (!status) {
return NULL;
}
if (strlen(repo_name) > 0) {
sprintf(status, "%s%s (%s%s%s)",
repo_name,
branch_name[0] ? ":" : "",
branch_name,
is_dirty ? " *" : "",
"");
} else {
sprintf(status, "%s%s",
branch_name,
is_dirty ? " *" : "");
}
return status;
}

43
git_integration.h Normal file
View File

@@ -0,0 +1,43 @@
/**
* git_integration.h
* Functions for Git repository detection and information
*/
#ifndef GIT_INTEGRATION_H
#define GIT_INTEGRATION_H
#include "common.h"
/**
* Initialize Git integration
*/
void init_git_integration(void);
/**
* Check if the current directory is in a Git repository and get branch info
*
* @param branch_name Buffer to store branch name if found
* @param buffer_size Size of the branch_name buffer
* @param is_dirty Pointer to store dirty status flag (1 if repo has changes)
* @return 1 if in a Git repo, 0 otherwise
*/
int get_git_branch(char *branch_name, size_t buffer_size, int *is_dirty);
/**
* Get the name of the Git repository
*
* @param repo_name Buffer to store repo name if found
* @param buffer_size Size of the repo_name buffer
* @return 1 if name was retrieved, 0 otherwise
*/
int get_git_repo_name(char *repo_name, size_t buffer_size);
/**
* Get Git status for the current repository
*
* @return A string with the Git status or NULL if not in a repo
* (caller must free the returned string)
*/
char *get_git_status(void);
#endif // GIT_INTEGRATION_H

BIN
git_integration.o Normal file

Binary file not shown.

54
grep.c Normal file
View File

@@ -0,0 +1,54 @@
/**
* grep.c
* Simple implementation of text searching for Linux
*/
#include "grep.h"
#include "builtins.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <strings.h>
/**
* Simple grep implementation
*/
int lsh_actual_grep(char **args) {
if (args[1] == NULL) {
printf("Usage: grep [pattern] [file...]\n");
return 1;
}
// Use the system's grep command
char command[1024] = "grep ";
// Add the pattern (first argument)
strcat(command, "\"");
strcat(command, args[1]);
strcat(command, "\"");
// Add any additional files specified
for (int i = 2; args[i] != NULL; i++) {
strcat(command, " ");
strcat(command, args[i]);
}
// If no files specified, use all files in current directory
if (args[2] == NULL) {
strcat(command, " .");
}
// Execute the grep command
return system(command);
}
/**
* Wrapper for grep
*/
int lsh_grep(char **args) {
return lsh_actual_grep(args);
}

28
grep.h Normal file
View File

@@ -0,0 +1,28 @@
/**
* grep.h
* Header for fast text searching in files with fuzzy matching capabilities
*/
#ifndef GREP_H
#define GREP_H
#include "common.h"
/**
* Command handler for the "grep" command
*
* Usage: grep [options] pattern [file/directory]
* Options:
* -n, --line-numbers Show line numbers
* -i, --ignore-case Ignore case distinctions
* -r, --recursive Search directories recursively
* -f, --fuzzy Use fuzzy matching instead of exact
* --file Specify files/directories to search (otherwise searches
* current dir)
*
* @param args Command arguments
* @return 1 to continue shell execution, 0 to exit shell
*/
int lsh_grep(char **args);
#endif // GREP_H

BIN
grep.o Normal file

Binary file not shown.

1039
line_reader.c Normal file

File diff suppressed because it is too large Load Diff

57
line_reader.h Normal file
View File

@@ -0,0 +1,57 @@
/**
* line_reader.h
* Functions for reading and parsing command lines
*/
#ifndef LINE_READER_H
#define LINE_READER_H
#include "common.h"
/**
* Check if a command is valid
*
* @param cmd The command to check
* @return 1 if valid, 0 if not
*/
int is_valid_command(const char *cmd);
/**
* Read a key from the terminal
*
* Handles special keys and escape sequences
*/
int read_key(void);
/**
* Read a line of input from the user with tab completion
*
* @return The line read from stdin (must be freed by caller)
*/
char *lsh_read_line(void);
/**
* Split a line into tokens
*
* @param line The line to split
* @return An array of tokens (must be freed by caller)
*/
char **lsh_split_line(char *line);
/**
* Parse a token from a string, handling quoted strings
*
* @param str_ptr Pointer to the string to parse, will be updated
* @return The parsed token, or NULL if no more tokens
*/
char *parse_token(char **str_ptr);
/**
* Split a line with pipes into separate command arrays
*
* @param line The line to split
* @return NULL-terminated array of NULL-terminated token arrays
*/
char ***lsh_split_piped_line(char *line);
#endif // LINE_READER_H

BIN
line_reader.o Normal file

Binary file not shown.

26
main.c Normal file
View File

@@ -0,0 +1,26 @@
/**
* main.c
* Entry point for the LSH shell program
*/
#include "common.h"
#include "shell.h"
#include <locale.h>
/**
* 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;
}

BIN
main.o Normal file

Binary file not shown.

BIN
mingw-get-setup.exe Normal file

Binary file not shown.

502
persistent_history.c Normal file
View File

@@ -0,0 +1,502 @@
/**
* persistent_history.c
* Implementation of persistent command history and frequency tracking
*/
#include "persistent_history.h"
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// Global variables for persistent history
static PersistentHistoryEntry *history_entries = NULL;
static int history_size = 0;
static int history_capacity = 0;
static int history_position = 0;
// Global variables for frequency tracking
static CommandFrequency *command_frequencies = NULL;
static int frequency_count = 0;
static int frequency_capacity = 0;
// Paths for history and frequency files
static char history_file_path[PATH_MAX];
static char frequency_file_path[PATH_MAX];
/**
* Initialize the persistent history system
*/
void init_persistent_history(void) {
// Allocate initial history capacity
history_capacity = PERSISTENT_HISTORY_SIZE;
history_entries = (PersistentHistoryEntry *)malloc(history_capacity * sizeof(PersistentHistoryEntry));
if (!history_entries) {
fprintf(stderr, "Failed to allocate memory for history entries\n");
return;
}
// Allocate initial frequency capacity
frequency_capacity = 100;
command_frequencies = (CommandFrequency *)malloc(frequency_capacity * sizeof(CommandFrequency));
if (!command_frequencies) {
fprintf(stderr, "Failed to allocate memory for command frequencies\n");
free(history_entries);
history_entries = NULL;
return;
}
// Initialize history and frequency counters
history_size = 0;
history_position = -1;
frequency_count = 0;
// Set up file paths
char *home_dir = getenv("HOME");
if (home_dir) {
// Create .lsh directory if it doesn't exist
char lsh_dir[PATH_MAX];
snprintf(lsh_dir, sizeof(lsh_dir), "%s/.lsh", home_dir);
struct stat st = {0};
if (stat(lsh_dir, &st) == -1) {
mkdir(lsh_dir, 0700);
}
snprintf(history_file_path, sizeof(history_file_path), "%s/.lsh/history", home_dir);
snprintf(frequency_file_path, sizeof(frequency_file_path), "%s/.lsh/frequency", home_dir);
} else {
// Fallback to current directory
strcpy(history_file_path, ".lsh_history");
strcpy(frequency_file_path, ".lsh_frequency");
}
// Load history and frequency data
load_history_from_file();
load_frequencies_from_file();
}
/**
* Clean up persistent history resources
*/
void cleanup_persistent_history(void) {
if (history_entries) {
for (int i = 0; i < history_size; i++) {
free(history_entries[i].command);
}
free(history_entries);
history_entries = NULL;
}
if (command_frequencies) {
for (int i = 0; i < frequency_count; i++) {
free(command_frequencies[i].command);
}
free(command_frequencies);
command_frequencies = NULL;
}
history_size = 0;
history_capacity = 0;
frequency_count = 0;
frequency_capacity = 0;
}
/**
* Shutdown persistent history
*/
void shutdown_persistent_history(void) {
save_history_to_file();
save_frequencies_to_file();
cleanup_persistent_history();
}
/**
* Add a command to the persistent history
*/
void add_to_history(const char *command) {
if (!command || !*command || !history_entries) {
return; // Skip empty commands
}
// Check for duplicates (don't add the same command twice in a row)
if (history_size > 0 && strcmp(history_entries[history_size - 1].command, command) == 0) {
return;
}
// Update command frequency
update_command_frequency(command);
// Add to history
if (history_size >= history_capacity) {
// History is full, shift entries
free(history_entries[0].command);
memmove(&history_entries[0], &history_entries[1],
(history_capacity - 1) * sizeof(PersistentHistoryEntry));
history_size = history_capacity - 1;
}
// Add new entry
history_entries[history_size].command = strdup(command);
history_entries[history_size].timestamp = time(NULL);
history_size++;
// Reset history position for navigation
history_position = -1;
}
/**
* Update command frequency statistics
*/
void update_command_frequency(const char *command) {
if (!command || !*command || !command_frequencies) {
return;
}
// Check if command exists in frequencies
for (int i = 0; i < frequency_count; i++) {
if (strcmp(command_frequencies[i].command, command) == 0) {
// Update existing entry
command_frequencies[i].count++;
return;
}
}
// Add new frequency entry
if (frequency_count >= frequency_capacity) {
// Expand capacity
frequency_capacity *= 2;
CommandFrequency *new_freq = (CommandFrequency *)realloc(
command_frequencies, frequency_capacity * sizeof(CommandFrequency));
if (!new_freq) {
fprintf(stderr, "Failed to allocate memory for command frequencies\n");
return;
}
command_frequencies = new_freq;
}
// Add new entry
command_frequencies[frequency_count].command = strdup(command);
command_frequencies[frequency_count].count = 1;
frequency_count++;
}
/**
* Save history to file
*/
void save_history_to_file(void) {
if (!history_entries || history_size == 0) {
return;
}
FILE *fp = fopen(history_file_path, "w");
if (!fp) {
fprintf(stderr, "Failed to open history file for writing: %s\n", history_file_path);
return;
}
// Write version and metadata
fprintf(fp, "# LSH Persistent History\n");
fprintf(fp, "# Version: 1.0\n");
fprintf(fp, "# Format: timestamp command\n\n");
// Write entries
for (int i = 0; i < history_size; i++) {
fprintf(fp, "%ld %s\n", (long)history_entries[i].timestamp, history_entries[i].command);
}
fclose(fp);
}
/**
* Load history from file
*/
void load_history_from_file(void) {
FILE *fp = fopen(history_file_path, "r");
if (!fp) {
return; // File doesn't exist or can't be opened
}
char line[1024];
time_t timestamp;
char command[900]; // Allow space for timestamp and whitespace
// Skip header lines
while (fgets(line, sizeof(line), fp) && line[0] == '#') {
// Skip header line
}
// Clear history
for (int i = 0; i < history_size; i++) {
free(history_entries[i].command);
}
history_size = 0;
// Read entries
while (fgets(line, sizeof(line), fp)) {
// Parse line: timestamp command
if (sscanf(line, "%ld %[^\n]", &timestamp, command) == 2) {
if (history_size < history_capacity) {
history_entries[history_size].command = strdup(command);
history_entries[history_size].timestamp = timestamp;
history_size++;
}
}
}
fclose(fp);
}
/**
* Save command frequencies to file
*/
void save_frequencies_to_file(void) {
if (!command_frequencies || frequency_count == 0) {
return;
}
FILE *fp = fopen(frequency_file_path, "w");
if (!fp) {
fprintf(stderr, "Failed to open frequency file for writing: %s\n", frequency_file_path);
return;
}
// Write version and metadata
fprintf(fp, "# LSH Command Frequencies\n");
fprintf(fp, "# Version: 1.0\n");
fprintf(fp, "# Format: count command\n\n");
// Write entries
for (int i = 0; i < frequency_count; i++) {
fprintf(fp, "%d %s\n", command_frequencies[i].count, command_frequencies[i].command);
}
fclose(fp);
}
/**
* Load command frequencies from file
*/
void load_frequencies_from_file(void) {
FILE *fp = fopen(frequency_file_path, "r");
if (!fp) {
return; // File doesn't exist or can't be opened
}
char line[1024];
int count;
char command[900]; // Allow space for count and whitespace
// Skip header lines
while (fgets(line, sizeof(line), fp) && line[0] == '#') {
// Skip header line
}
// Clear frequencies
for (int i = 0; i < frequency_count; i++) {
free(command_frequencies[i].command);
}
frequency_count = 0;
// Read entries
while (fgets(line, sizeof(line), fp)) {
// Parse line: count command
if (sscanf(line, "%d %[^\n]", &count, command) == 2) {
if (frequency_count < frequency_capacity) {
command_frequencies[frequency_count].command = strdup(command);
command_frequencies[frequency_count].count = count;
frequency_count++;
}
}
}
fclose(fp);
}
/**
* Get the history entry at the specified index
*/
PersistentHistoryEntry *get_history_entry(int index) {
if (index < 0 || index >= history_size) {
return NULL;
}
return &history_entries[index];
}
/**
* Get the total number of history entries
*/
int get_history_count(void) {
return history_size;
}
/**
* Find the best matching command based on frequency
*/
char *find_best_frequency_match(const char *prefix) {
if (!prefix || !*prefix || !command_frequencies) {
return NULL;
}
int best_freq = 0;
int best_index = -1;
// Find the most frequent command that starts with the prefix
for (int i = 0; i < frequency_count; i++) {
if (strncasecmp(command_frequencies[i].command, prefix, strlen(prefix)) == 0) {
if (command_frequencies[i].count > best_freq) {
best_freq = command_frequencies[i].count;
best_index = i;
}
}
}
if (best_index >= 0) {
return strdup(command_frequencies[best_index].command);
}
return NULL;
}
/**
* Debug function to print frequency data
*/
void debug_print_frequencies(void) {
printf("Command Frequencies:\n");
for (int i = 0; i < frequency_count; i++) {
printf("%3d: %s (%d)\n", i + 1, command_frequencies[i].command, command_frequencies[i].count);
}
}
/**
* Case-insensitive substring search
*/
char *_stristr(const char *haystack, const char *needle) {
if (!haystack || !needle)
return NULL;
char *h = strdup(haystack);
char *n = strdup(needle);
if (!h || !n) {
if (h) free(h);
if (n) free(n);
return NULL;
}
// Convert both strings to lowercase
for (char *p = h; *p; p++)
*p = tolower(*p);
for (char *p = n; *p; p++)
*p = tolower(*p);
char *result = strstr(h, n);
char *original = NULL;
// If found, map back to original string position
if (result) {
original = (char *)haystack + (result - h);
}
free(h);
free(n);
return original;
}
/**
* Get previous history entry for up-arrow key navigation
*/
char *get_previous_history_entry(int *position) {
if (!history_entries || history_size == 0) {
return NULL;
}
if (*position < 0) {
// First time accessing history, start from the most recent entry
*position = history_size - 1;
} else if (*position > 0) {
// Move to previous entry
(*position)--;
} else {
// Already at the oldest entry, can't go back further
return history_entries[0].command;
}
return history_entries[*position].command;
}
/**
* Get next history entry for down-arrow key navigation
*/
char *get_next_history_entry(int *position) {
if (!history_entries || history_size == 0 || *position < 0) {
return NULL;
}
if (*position < history_size - 1) {
// Move to next (more recent) entry
(*position)++;
return history_entries[*position].command;
} else {
// We've reached the end of history, return NULL to indicate
// user should get an empty prompt
*position = -1;
return NULL;
}
}
/**
* Get matching history entries for tab completion
*/
char **get_matching_history_entries(const char *prefix) {
if (!prefix || !history_entries) {
return NULL;
}
// Count matching entries
int matches = 0;
for (int i = 0; i < history_size; i++) {
if (strncasecmp(history_entries[i].command, prefix, strlen(prefix)) == 0) {
matches++;
}
}
if (matches == 0) {
return NULL;
}
// Allocate result array
char **result = (char **)malloc((matches + 1) * sizeof(char *));
if (!result) {
return NULL;
}
// Fill the array with matching commands
int count = 0;
for (int i = 0; i < history_size && count < matches; i++) {
if (strncasecmp(history_entries[i].command, prefix, strlen(prefix)) == 0) {
result[count++] = strdup(history_entries[i].command);
}
}
result[count] = NULL;
return result;
}
/**
* Free matching entries array
*/
void free_matching_entries(char **entries) {
if (!entries) {
return;
}
for (int i = 0; entries[i] != NULL; i++) {
free(entries[i]);
}
free(entries);
}

85
persistent_history.h Normal file
View File

@@ -0,0 +1,85 @@
/**
* persistent_history.h
* Functions for persistent command history and frequency tracking
*/
#ifndef PERSISTENT_HISTORY_H
#define PERSISTENT_HISTORY_H
#include "common.h"
#include "shell.h"
#include <time.h>
// Increase history size for persistent storage
#define PERSISTENT_HISTORY_SIZE 1000
// Structure for a history entry
typedef struct {
char *command;
time_t timestamp;
} PersistentHistoryEntry;
// Structure for command frequency tracking
typedef struct {
char *command;
int count;
} CommandFrequency;
// Initialize persistent history
void init_persistent_history(void);
// Clean up persistent history
void cleanup_persistent_history(void);
// Shutdown persistent history
void shutdown_persistent_history(void);
// Add a command to persistent history
void add_to_history(const char *command);
// Get command suggestions based on prefix and frequency
char **get_frequency_suggestions(const char *prefix, int *num_suggestions);
// Save history to file
void save_history_to_file(void);
// Load history from file
void load_history_from_file(void);
// Update command frequency
void update_command_frequency(const char *command);
// Save frequencies to file
void save_frequencies_to_file(void);
// Load frequencies from file
void load_frequencies_from_file(void);
// Get the history entry at specified index
PersistentHistoryEntry *get_history_entry(int index);
// Get the total number of history entries
int get_history_count(void);
// Find the best matching command based on frequency
char *find_best_frequency_match(const char *prefix);
// Debug print frequency data
void debug_print_frequencies(void);
// String search (case insensitive)
char *_stristr(const char *haystack, const char *needle);
// Get previous history entry
char *get_previous_history_entry(int *position);
// Get next history entry
char *get_next_history_entry(int *position);
// Get matching history entries
char **get_matching_history_entries(const char *prefix);
// Free matching entries
void free_matching_entries(char **entries);
#endif // PERSISTENT_HISTORY_H

BIN
persistent_history.o Normal file

Binary file not shown.

113
ps_command.c Normal file
View File

@@ -0,0 +1,113 @@
/**
* ps_command.c
* Implementation of the 'ps' command for listing running processes
* Simple implementation for Linux
*/
#include "builtins.h"
#include "common.h"
#include "structured_data.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* Function to generate structured data for running processes
* This enables piping and filtering of process information
*
* @param args Command arguments
* @return TableData structure with process information
*/
TableData* lsh_ps_structured(char **args) {
// Define our table headers
char *headers[] = {"PID", "Name", "Memory", "Threads"};
int header_count = 4;
// Create our table
TableData *table = create_table(headers, header_count);
if (!table) {
fprintf(stderr, "lsh: allocation error in ps_structured\n");
return NULL;
}
// Use system's ps command to get process info
FILE *fp = popen("ps -e -o pid,comm,vsz,nlwp --no-headers", "r");
if (!fp) {
fprintf(stderr, "lsh: failed to execute ps command\n");
free_table(table);
return NULL;
}
char line[1024];
while (fgets(line, sizeof(line), fp)) {
// Parse the line
char name[256] = {0};
unsigned long pid = 0, vsz = 0, threads = 0;
sscanf(line, "%lu %255s %lu %lu", &pid, name, &vsz, &threads);
// Create a new row for this process
DataValue *row = (DataValue*)malloc(header_count * sizeof(DataValue));
if (!row) {
fprintf(stderr, "lsh: allocation error in ps_structured\n");
free_table(table);
pclose(fp);
return NULL;
}
// Set PID (as a string for compatibility)
char pidStr[20];
sprintf(pidStr, "%lu", pid);
row[0].type = TYPE_STRING;
row[0].value.str_val = strdup(pidStr);
// Set process name
row[1].type = TYPE_STRING;
row[1].value.str_val = strdup(name);
// Format memory usage string (important for filtering)
char memoryString[32];
if (vsz < 1024) {
sprintf(memoryString, "%lu B", vsz);
} else if (vsz < 1024 * 1024) {
sprintf(memoryString, "%.1f KB", vsz / 1024.0);
} else {
// Format as MB for consistency in filtering
sprintf(memoryString, "%.1f MB", vsz / (1024.0 * 1024.0));
}
row[2].type = TYPE_SIZE; // Use the special SIZE type for filtering
row[2].value.str_val = strdup(memoryString);
// Set thread count
char threadStr[20];
sprintf(threadStr, "%lu", threads);
row[3].type = TYPE_STRING;
row[3].value.str_val = strdup(threadStr);
// Add the row to the table
add_table_row(table, row);
}
pclose(fp);
return table;
}
/**
* Implementation of the ps command to display process information
* Uses structured data to show processes in a formatted table
*/
int lsh_ps_fancy(char **args) {
// Create structured data table for processes
TableData *table = lsh_ps_structured(args);
if (table) {
// Print the table using our nice formatting
print_table(table);
// Free the table
free_table(table);
}
return 1;
}

BIN
ps_command.o Normal file

Binary file not shown.

0
rg_selection.txt Normal file
View File

746
ripgrep.c Normal file
View File

@@ -0,0 +1,746 @@
/**
* ripgrep.c
* Interactive wrapper for the ripgrep (rg) command-line search tool
*/
#include "ripgrep.h"
#include "common.h"
#include "line_reader.h"
#include "shell.h" // Add this for lsh_execute
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h> // For PATH_MAX
#include <unistd.h> // For access function
#include <termios.h> // For terminal control
/**
* Check if ripgrep (rg) is installed on the system
*
* @return 1 if installed, 0 if not
*/
int is_rg_installed(void) {
// Try to run rg --version to check if it's installed
FILE *fp = popen("rg --version 2>/dev/null", "r");
if (fp == NULL) {
return 0;
}
// Read output (if any)
char buffer[128];
int has_output = (fgets(buffer, sizeof(buffer), fp) != NULL);
// Close the process
pclose(fp);
return has_output;
}
/**
* Display instructions for installing ripgrep
*/
void show_rg_install_instructions(void) {
printf("\nripgrep (rg) is not installed on this system. To use this feature, "
"install ripgrep:\n\n");
printf("Installation options:\n");
printf("1. Using package manager (Debian/Ubuntu):\n");
printf(" sudo apt install ripgrep\n\n");
printf("2. Using package manager (Fedora):\n");
printf(" sudo dnf install ripgrep\n\n");
printf("3. Using package manager (Arch Linux):\n");
printf(" sudo pacman -S ripgrep\n\n");
printf("4. Download prebuilt binary from: "
"https://github.com/BurntSushi/ripgrep/releases\n\n");
printf("After installation, restart your shell.\n");
}
/**
* Check if a specific editor is available
*
* @param editor Name of the editor executable
* @return 1 if available, 0 if not
*/
int is_editor_available_for_rg(const char *editor) {
char command[256];
snprintf(command, sizeof(command), "%s --version >/dev/null 2>&1", editor);
return (system(command) == 0);
}
/**
* Open a file at a specific line in the best available editor
*
* @param file_path Path to the file to open
* @param line_number Line number to position cursor at
* @return 1 if successful, 0 if failed
*/
int rg_open_in_editor(const char *file_path, int line_number) {
char command[2048] = {0};
int success = 0;
// Try to detect available editors (in order of preference)
if (is_editor_available_for_rg("nvim")) {
// Neovim is available - construct command with +line_number
snprintf(command, sizeof(command), "nvim +%d \"%s\"", line_number,
file_path);
success = 1;
} else if (is_editor_available_for_rg("vim")) {
// Vim is available
snprintf(command, sizeof(command), "vim +%d \"%s\"", line_number,
file_path);
success = 1;
} else if (is_editor_available_for_rg("nano")) {
// Try nano if available
// Note: nano doesn't have perfect line number navigation
snprintf(command, sizeof(command), "nano +%d \"%s\"", line_number,
file_path);
success = 1;
} else if (is_editor_available_for_rg("code")) {
// VSCode with line number
snprintf(command, sizeof(command), "code -g \"%s:%d\" -r", file_path,
line_number);
success = 1;
} else if (is_editor_available_for_rg("gedit")) {
// gedit as last resort (limited line number support)
snprintf(command, sizeof(command), "gedit +%d \"%s\"", line_number, file_path);
success = 1;
}
if (success) {
// Clear the screen before launching the editor
system("clear");
// Execute the command directly in the current terminal
int result = system(command);
success = (result == 0);
return success;
} else {
// No suitable editor found
printf("No compatible editor (neovim, vim, nano, VSCode, gedit) found.\n");
return 0;
}
}
/**
* Parse a ripgrep result line to extract file path and line number
*
* @param result_line The result line from ripgrep (format:
* file:line:column:text)
* @param file_path Buffer to store extracted file path
* @param file_path_size Size of file_path buffer
* @param line_number Pointer to store extracted line number
* @return 1 if parsing successful, 0 if failed
*/
static int parse_rg_result(const char *result_line, char *file_path,
size_t file_path_size, int *line_number) {
// Ripgrep output format is: file:line:column:text
const char *first_colon = strchr(result_line, ':');
if (!first_colon)
return 0;
const char *second_colon = strchr(first_colon + 1, ':');
if (!second_colon)
return 0;
// Extract file path
size_t path_length = first_colon - result_line;
if (path_length >= file_path_size)
path_length = file_path_size - 1;
strncpy(file_path, result_line, path_length);
file_path[path_length] = '\0';
// Extract line number
char line_str[16] = {0};
size_t line_str_length = second_colon - (first_colon + 1);
if (line_str_length >= sizeof(line_str))
line_str_length = sizeof(line_str) - 1;
strncpy(line_str, first_colon + 1, line_str_length);
line_str[line_str_length] = '\0';
*line_number = atoi(line_str);
return 1;
}
/**
* Run ripgrep with interactive filtering
*
* @param args Command arguments or NULL for interactive mode
* @return Selected filename/line or NULL if canceled
*/
char *run_interactive_ripgrep(char **args) {
// Check if ripgrep is installed
if (!is_rg_installed()) {
show_rg_install_instructions();
return NULL;
}
// Create temporary files for the search results
char temp_rg_results[PATH_MAX] = "/tmp/rg_results.txt";
char temp_fzf_output[PATH_MAX] = "/tmp/rg_selected.txt";
// Build the initial command
char command[4096] = {0};
// Base ripgrep command with sensible defaults
strcpy(command,
"rg --line-number --column --no-heading --color=always --smart-case");
// Add any user-provided arguments
if (args && args[1] != NULL) {
int i = 1;
while (args[i] != NULL) {
strcat(command, " ");
// If the argument contains spaces, quote it
if (strchr(args[i], ' ') != NULL) {
strcat(command, "\"");
strcat(command, args[i]);
strcat(command, "\"");
} else {
strcat(command, args[i]);
}
i++;
}
} else {
// If no search pattern provided, use empty string to search all files
// Will be filtered by fzf as user types
strcat(command, " \"\"");
}
// Pipe to fzf for interactive selection with preview
strcat(command, " | fzf --ansi");
// Add keybindings for navigation
strcat(command, " --bind=\"ctrl-j:down,ctrl-k:up,/:toggle-search\"");
// Add preview window showing file content with line highlighted
strcat(command, " --preview=\"bat --color=always --style=numbers "
"--highlight-line={2} {1}\"");
strcat(command, " --preview-window=+{2}-10");
// Add output redirection
strcat(command, " > ");
strcat(command, temp_fzf_output);
// Run the command
printf("Starting interactive ripgrep search...\n");
int result = system(command);
// Check if user canceled (fzf returns non-zero)
if (result != 0) {
unlink(temp_fzf_output);
return NULL;
}
// Read the selected result from the temporary file
FILE *fp = fopen(temp_fzf_output, "r");
if (!fp) {
unlink(temp_fzf_output);
return NULL;
}
// Read the selected line
char *selected = (char *)malloc(PATH_MAX * 2);
if (!selected) {
fclose(fp);
unlink(temp_fzf_output);
return NULL;
}
if (fgets(selected, PATH_MAX * 2, fp) == NULL) {
fclose(fp);
unlink(temp_fzf_output);
free(selected);
return NULL;
}
// Remove newline if present
size_t len = strlen(selected);
if (len > 0 && selected[len - 1] == '\n') {
selected[len - 1] = '\0';
}
// Close and delete the temporary file
fclose(fp);
unlink(temp_fzf_output);
return selected;
}
/**
* Run ripgrep with interactive editing capabilities for a custom Neovim-like
* experience
*/
void run_ripgrep_interactive_session(void) {
// Check if ripgrep and fzf are installed
if (!is_rg_installed()) {
show_rg_install_instructions();
return;
}
// Initialize the search query
char search_query[256] = "";
char last_query[256] = "";
// Create a temporary file for results
char temp_results[PATH_MAX] = "/tmp/ripgrep_results.txt";
// Save original terminal settings to restore later
struct termios old_tio, new_tio;
tcgetattr(STDIN_FILENO, &old_tio);
// Copy the old settings and modify for raw input
new_tio = old_tio;
new_tio.c_lflag &= ~(ICANON | ECHO); // Disable canonical mode and echo
new_tio.c_cc[VMIN] = 1; // Read one character at a time
new_tio.c_cc[VTIME] = 0; // No timeout
tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
// Clear screen and show initial UI
system("clear");
printf("--- Interactive Ripgrep Search ---\n");
printf(
"Type to search | Ctrl+N/P: navigate | Enter: open | Ctrl+C: exit\n\n");
printf("Search: ");
int running = 1;
int selected_index = 0;
int results_count = 0;
char **results = NULL;
// Main interaction loop
while (running) {
// Display the current search query
printf("\rSearch: %s", search_query);
fflush(stdout);
// Check if the query has changed and we need to run ripgrep again
if (strcmp(search_query, last_query) != 0) {
// Free previous results if any
if (results) {
for (int i = 0; i < results_count; i++) {
free(results[i]);
}
free(results);
results = NULL;
results_count = 0;
}
// Execute ripgrep with the current query if not empty
if (strlen(search_query) > 0) {
// Build ripgrep command
char command[4096];
snprintf(command, sizeof(command),
"rg --line-number --column --no-heading --color=never "
"--smart-case \"%s\" > %s",
search_query, temp_results);
system(command);
// Read results from the temp file
FILE *fp = fopen(temp_results, "r");
if (fp) {
// Count lines first
char line[1024];
results_count = 0;
while (fgets(line, sizeof(line), fp) != NULL) {
results_count++;
}
// Allocate memory for results
rewind(fp);
results = (char **)malloc(results_count * sizeof(char *));
if (results) {
int i = 0;
while (i < results_count && fgets(line, sizeof(line), fp) != NULL) {
// Remove newline
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0';
}
results[i] = strdup(line);
i++;
}
}
fclose(fp);
}
}
// Update last query
strcpy(last_query, search_query);
// Reset selection to first result
selected_index = 0;
// Clear the results display area
printf("\n\033[J"); // Clear from cursor to end of screen
// Display results count
printf("\nFound %d matches\n\n", results_count);
// Display results with selection
int max_display = 10; // Show at most 10 results at a time
for (int i = 0; i < results_count && i < max_display; i++) {
if (i == selected_index) {
printf("\033[7m> %s\033[0m\n",
results[i]); // Invert colors for selection
} else {
printf(" %s\n", results[i]);
}
}
}
// Process user input
unsigned char c;
if (read(STDIN_FILENO, &c, 1) > 0) {
if (c == 3) { // Ctrl+C
running = 0;
} else if (c == 13 || c == 10) { // Enter or newline
// Open the selected file if there are results
if (results_count > 0 && selected_index < results_count) {
char file_path[PATH_MAX];
int line_number;
if (parse_rg_result(results[selected_index], file_path,
sizeof(file_path), &line_number)) {
rg_open_in_editor(file_path, line_number);
// After returning from editor, refresh the UI
system("clear");
printf("--- Interactive Ripgrep Search ---\n");
printf("Type to search | Ctrl+N/P: navigate | Enter: open | Ctrl+C: "
"exit\n\n");
printf("Search: %s\n\n", search_query);
// Re-display results count
printf("Found %d matches\n\n", results_count);
// Re-display results with selection
int max_display = 10;
for (int i = 0; i < results_count && i < max_display; i++) {
if (i == selected_index) {
printf("\033[7m> %s\033[0m\n", results[i]);
} else {
printf(" %s\n", results[i]);
}
}
}
}
} else if (c == 14) { // Ctrl+N
if (results_count > 0) {
selected_index = (selected_index + 1) % results_count;
// Redraw results with new selection
printf("\033[%dA",
results_count > 10
? 10
: results_count); // Move cursor up to results start
int max_display = 10;
for (int i = 0; i < results_count && i < max_display; i++) {
if (i == selected_index) {
printf("\033[2K\033[7m> %s\033[0m\n",
results[i]); // Clear line and invert colors
} else {
printf("\033[2K %s\n", results[i]); // Clear line
}
}
}
} else if (c == 16) { // Ctrl+P
if (results_count > 0) {
selected_index = (selected_index - 1 + results_count) % results_count;
// Redraw results with new selection
printf("\033[%dA",
results_count > 10
? 10
: results_count); // Move cursor up to results start
int max_display = 10;
for (int i = 0; i < results_count && i < max_display; i++) {
if (i == selected_index) {
printf("\033[2K\033[7m> %s\033[0m\n",
results[i]); // Clear line and invert colors
} else {
printf("\033[2K %s\n", results[i]); // Clear line
}
}
}
} else if (c == 127 || c == 8) { // Backspace (127 on Linux, 8 on some terminals)
// Remove last character from search query
size_t len = strlen(search_query);
if (len > 0) {
search_query[len - 1] = '\0';
}
} else if (isprint(c)) { // Printable character
// Add character to search query
size_t len = strlen(search_query);
if (len < sizeof(search_query) - 1) {
search_query[len] = c;
search_query[len + 1] = '\0';
}
}
}
}
// Clean up
if (results) {
for (int i = 0; i < results_count; i++) {
free(results[i]);
}
free(results);
}
unlink(temp_results);
// Restore original terminal settings
tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
// Final screen clear
system("clear");
}
/**
* Command handler for the ripgrep command
*
* @param args Command arguments
* @return 1 to continue shell execution, 0 to exit shell
*/
int lsh_ripgrep(char **args) {
// Check if ripgrep is installed
if (!is_rg_installed()) {
printf("Ripgrep (rg) is not installed. Falling back to custom "
"implementation.\n");
printf("For better performance, consider installing ripgrep:\n");
show_rg_install_instructions();
printf("\nRunning with custom implementation...\n\n");
// Fall back to our custom implementation
run_ripgrep_interactive_session();
return 1;
}
// Also check if fzf is installed for the interactive UI
int fzf_available = is_fzf_installed(); // Reuse from fzf_native.h
// Check for help flag
if (args[1] &&
(strcmp(args[1], "--help") == 0 || strcmp(args[1], "-h") == 0)) {
printf("Usage: ripgrep [pattern] [options]\n");
printf("Interactive code search using ripgrep (rg) with fzf.\n\n");
printf("If called without arguments, launches fzf with ripgrep for "
"interactive searching.\n\n");
printf("Options:\n");
printf(" -t, --type [TYPE] Only search files matching TYPE (e.g., -t "
"cpp)\n");
printf(" -i, --ignore-case Case insensitive search\n");
printf(" -w, --word-regexp Only match whole words\n");
printf(" -e, --regexp Treat pattern as a regular expression\n");
printf(" -f, --fixed-strings Treat pattern as a literal string\n");
printf(" -g, --glob [GLOB] Include/exclude files matching the glob\n");
return 1;
}
// If no arguments provided, use fzf with ripgrep
if (!args[1]) {
if (!fzf_available) {
printf("fzf is not installed. Falling back to custom implementation.\n");
show_fzf_install_instructions();
printf("\nRunning with custom implementation...\n\n");
// Fall back to custom implementation if fzf is not available
run_ripgrep_interactive_session();
return 1;
}
// Create a temporary file for the fzf preview script
char preview_script[PATH_MAX] = "/tmp/fzf_preview.sh";
// Create the preview script as a shell script with highlighting support
FILE *preview_fp = fopen(preview_script, "w");
if (preview_fp) {
// Write a shell script that highlights searched content
fprintf(preview_fp, "#!/bin/bash\n");
fprintf(preview_fp, "file=\"$1\"\n");
fprintf(preview_fp, "line=\"$2\"\n");
fprintf(preview_fp, "query=\"$3\"\n");
fprintf(preview_fp, "if [ -z \"$query\" ]; then\n");
// If no query, just show the file with line highlighting
fprintf(preview_fp, " bat --color=always --highlight-line \"$line\" \"$file\" 2>/dev/null || cat \"$file\"\n");
fprintf(preview_fp, "else\n");
// If query provided, highlight both the line and the search term
fprintf(preview_fp, " if grep -i \"$query\" \"$file\" >/dev/null 2>&1; then\n");
fprintf(preview_fp, " rg --color=always --context 3 --line-number \"$query\" \"$file\" 2>/dev/null || ");
fprintf(preview_fp, "bat --color=always --highlight-line \"$line\" \"$file\" 2>/dev/null || cat \"$file\"\n");
fprintf(preview_fp, " else\n");
fprintf(preview_fp, " bat --color=always --highlight-line \"$line\" \"$file\" 2>/dev/null || cat \"$file\"\n");
fprintf(preview_fp, " fi\n");
fprintf(preview_fp, "fi\n");
fclose(preview_fp);
// Make the script executable
chmod(preview_script, 0755);
}
// Use fzf with ripgrep to provide an interactive UI similar to Neovim
char command[4096] = {0};
// Use ripgrep with an empty pattern to search all text files
// and pipe to fzf for interactive filtering - now with fullscreen mode
snprintf(
command, sizeof(command),
"clear && rg --line-number --column --no-heading --color=always \"\" | "
"fzf --ansi --delimiter : "
"--preview \"%s {1} {2} {q}\" "
"--preview-window=right:60%%:wrap "
"--bind \"ctrl-j:down,ctrl-k:up,enter:accept\" "
"--border "
"--height=100%% > %s",
preview_script, "/tmp/rg_selection.txt");
// Execute the command
int result = system(command);
// Check if user selected something (fzf returns non-zero on cancel)
if (result == 0) {
// Read the selected file/line from the temporary file
FILE *fp = fopen("/tmp/rg_selection.txt", "r");
if (fp) {
char selected[1024];
if (fgets(selected, sizeof(selected), fp)) {
// Remove newline if present
size_t len = strlen(selected);
if (len > 0 && selected[len - 1] == '\n') {
selected[len - 1] = '\0';
}
// Parse the selection to extract file path and line number
char file_path[PATH_MAX];
int line_number;
if (parse_rg_result(selected, file_path, sizeof(file_path),
&line_number)) {
printf("Opening %s at line %d\n", file_path, line_number);
rg_open_in_editor(file_path, line_number);
}
}
fclose(fp);
unlink("/tmp/rg_selection.txt"); // Clean up
}
}
// Remove the preview script
unlink(preview_script);
return 1;
}
// If options are provided but no pattern, run regular ripgrep
if (args[1][0] == '-') {
// Build command to pass to system
char command[4096] = "rg";
// Add all arguments
for (int i = 1; args[i] != NULL; i++) {
strcat(command, " ");
// If argument contains spaces, quote it
if (strchr(args[i], ' ') != NULL) {
strcat(command, "\"");
strcat(command, args[i]);
strcat(command, "\"");
} else {
strcat(command, args[i]);
}
}
// Execute standard ripgrep command
system(command);
return 1;
}
// If a pattern is provided, run ripgrep with fzf for selection
if (fzf_available) {
// Create a temporary file for the fzf preview script
char preview_script[PATH_MAX] = "/tmp/fzf_preview.sh";
// Create the preview script as a shell script with highlighting
FILE *preview_fp = fopen(preview_script, "w");
if (preview_fp) {
// Write a shell script that highlights searched content
fprintf(preview_fp, "#!/bin/bash\n");
fprintf(preview_fp, "file=\"$1\"\n");
fprintf(preview_fp, "line=\"$2\"\n");
fprintf(preview_fp, "search_term=\"%s\"\n", args[1]); // Use the search term provided
// Show file with both line highlighting and search term highlighting
fprintf(preview_fp,
"rg --color=always --context 3 --line-number \"%s\" \"$file\" 2>/dev/null || ",
args[1]);
fprintf(preview_fp, "bat --color=always --highlight-line \"$line\" \"$file\" 2>/dev/null || cat \"$file\"\n");
fclose(preview_fp);
// Make the script executable
chmod(preview_script, 0755);
}
// Build command with fzf for better interaction
char command[4096] = {0};
// Now with better UI but using compatible options
snprintf(
command, sizeof(command),
"clear && rg --line-number --column --no-heading --color=always \"%s\" | "
"fzf --ansi --delimiter : "
"--preview \"%s {1} {2}\" "
"--preview-window=right:60%%:wrap "
"--bind \"ctrl-j:down,ctrl-k:up,enter:accept\" "
"--border "
"--height=100%% > %s",
args[1], preview_script, "/tmp/rg_selection.txt");
// Execute the command
int result = system(command);
// Check if user selected something
if (result == 0) {
// Read the selected file/line from the temporary file
FILE *fp = fopen("/tmp/rg_selection.txt", "r");
if (fp) {
char selected[1024];
if (fgets(selected, sizeof(selected), fp)) {
// Remove newline if present
size_t len = strlen(selected);
if (len > 0 && selected[len - 1] == '\n') {
selected[len - 1] = '\0';
}
// Parse the selection to extract file path and line number
char file_path[PATH_MAX];
int line_number;
if (parse_rg_result(selected, file_path, sizeof(file_path),
&line_number)) {
printf("Opening %s at line %d\n", file_path, line_number);
rg_open_in_editor(file_path, line_number);
}
}
fclose(fp);
unlink("/tmp/rg_selection.txt"); // Clean up
}
}
// Remove the preview script
unlink(preview_script);
} else {
// If fzf is not available, fall back to custom implementation
printf("fzf is not installed. Falling back to custom implementation.\n");
run_ripgrep_interactive_session();
}
return 1;
}

26
ripgrep.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef RIPGREP_H
#define RIPGREP_H
#include "builtins.h"
#include "common.h"
#include "fzf_native.h"
int is_rg_installed(void);
void show_rg_install_instructions(void);
void show_fzf_install_instructions(void);
char *run_interactive_ripgrep(char **args);
int lsh_grep(char **args);
int lsh_ripgrep(char **args);
void run_ripgrep_interactive_session(void);
int rg_open_in_editor(const char *file_path, int line_number);
int is_editor_available_for_rg(const char *editor);
#endif // RIPGREP_H

BIN
ripgrep.o Normal file

Binary file not shown.

BIN
shell Executable file

Binary file not shown.

2
shell.bat Normal file
View File

@@ -0,0 +1,2 @@
@echo off
"%~dp0shell.exe" %*

591
shell.c Normal file
View File

@@ -0,0 +1,591 @@
/**
* 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 <stdio.h>
#include <time.h> // Added for time functions
#include <termios.h>
#include <sys/ioctl.h>
#include <signal.h>
// Global variables for status bar
static int g_console_width = 80;
static int g_status_line = 0;
static WORD g_normal_attributes = 7; // Default white on black
static WORD g_status_attributes = 12; // Red
static BOOL g_status_bar_enabled = FALSE; // Flag to track if status bar is enabled
static 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 = TRUE;
// 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 *last_slash = strrchr(cwd, '/');
if (last_slash != NULL) {
strncpy(current_dir_name, last_slash + 1, buf_size - 1);
current_dir_name[buf_size - 1] = '\0';
// If we're in root directory
if (last_slash == cwd) {
strncpy(parent_dir_name, "/", buf_size - 1);
parent_dir_name[buf_size - 1] = '\0';
return;
}
// Find the second-to-last slash for parent directory
char *parent_slash = last_slash;
while (parent_slash > cwd && *(parent_slash - 1) != '/') {
parent_slash--;
}
if (parent_slash > cwd) {
size_t parent_len = parent_slash - cwd - 1;
if (parent_len >= buf_size) {
parent_len = buf_size - 1;
}
// If parent is root
if (parent_len == 0) {
strncpy(parent_dir_name, "/", buf_size - 1);
} else {
strncpy(parent_dir_name, parent_slash - parent_len, parent_len);
parent_dir_name[parent_len] = '\0';
}
} else {
strncpy(parent_dir_name, "/", buf_size - 1);
}
parent_dir_name[buf_size - 1] = '\0';
} else {
// Fall back if path doesn't contain slashes
strncpy(current_dir_name, cwd, buf_size - 1);
strncpy(parent_dir_name, ".", buf_size - 1);
current_dir_name[buf_size - 1] = '\0';
parent_dir_name[buf_size - 1] = '\0';
}
}
/**
* 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
}
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 and parse into multiple commands if present
if (strchr(line, '|') != NULL) {
commands = lsh_split_piped_line(line);
status = lsh_execute_piped(commands);
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);
}

BIN
shell.exe Normal file

Binary file not shown.

125
shell.h Normal file
View File

@@ -0,0 +1,125 @@
/**
* 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);
/**
* 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

BIN
shell.o Normal file

Binary file not shown.

479
structured_data.c Normal file
View File

@@ -0,0 +1,479 @@
/**
* structured_data.c
* Implementation of structured data operations
*/
#include "structured_data.h"
#include "builtins.h" // For set_color and reset_color functions
#include <strings.h>
/**
* Create a new table with the given headers
*/
TableData* create_table(char **headers, int header_count) {
TableData *table = (TableData*)malloc(sizeof(TableData));
if (!table) {
fprintf(stderr, "lsh: allocation error in create_table\n");
return NULL;
}
// Copy headers
table->headers = (char**)malloc(header_count * sizeof(char*));
if (!table->headers) {
fprintf(stderr, "lsh: allocation error in create_table (headers)\n");
free(table);
return NULL;
}
for (int i = 0; i < header_count; i++) {
table->headers[i] = strdup(headers[i]);
}
table->header_count = header_count;
table->row_count = 0;
table->row_capacity = 10; // Initial capacity for 10 rows
// Allocate memory for rows
table->rows = (DataValue**)malloc(table->row_capacity * sizeof(DataValue*));
if (!table->rows) {
fprintf(stderr, "lsh: allocation error in create_table (rows)\n");
for (int i = 0; i < header_count; i++) {
free(table->headers[i]);
}
free(table->headers);
free(table);
return NULL;
}
return table;
}
/**
* Add a row to a table
*/
void add_table_row(TableData *table, DataValue *row) {
if (!table || !row) return;
// Resize if needed
if (table->row_count >= table->row_capacity) {
table->row_capacity *= 2;
table->rows = (DataValue**)realloc(table->rows, table->row_capacity * sizeof(DataValue*));
if (!table->rows) {
fprintf(stderr, "lsh: allocation error in add_table_row\n");
return;
}
}
// Add the row
table->rows[table->row_count] = row;
table->row_count++;
}
/**
* Free all memory associated with a table
*/
void free_table(TableData *table) {
if (!table) return;
// Free headers
for (int i = 0; i < table->header_count; i++) {
free(table->headers[i]);
}
free(table->headers);
// Free rows
for (int i = 0; i < table->row_count; i++) {
for (int j = 0; j < table->header_count; j++) {
free_data_value(&table->rows[i][j]);
}
free(table->rows[i]);
}
free(table->rows);
// Free table structure
free(table);
}
/**
* Create a copy of a DataValue
*/
DataValue copy_data_value(const DataValue *src) {
DataValue dest;
dest.type = src->type;
dest.is_highlighted = src->is_highlighted; // Make sure we preserve the highlighting flag
switch (src->type) {
case TYPE_STRING:
case TYPE_SIZE:
dest.value.str_val = strdup(src->value.str_val);
break;
case TYPE_INT:
dest.value.int_val = src->value.int_val;
break;
case TYPE_FLOAT:
dest.value.float_val = src->value.float_val;
break;
}
return dest;
}
/**
* Free a DataValue
*/
void free_data_value(DataValue *value) {
if (!value) return;
if ((value->type == TYPE_STRING || value->type == TYPE_SIZE) && value->value.str_val) {
free(value->value.str_val);
value->value.str_val = NULL;
}
}
/**
* Parse human-readable sizes (e.g., "10kb", "2.5MB") into bytes
*/
long parse_size(const char *size_str) {
char *unit;
double size = strtod(size_str, &unit);
// Skip whitespace between number and unit
while (*unit && isspace(*unit)) unit++;
// Handle case insensitive units
if (strcasecmp(unit, "kb") == 0 || strcasecmp(unit, "k") == 0) {
return (long)(size * 1024);
} else if (strcasecmp(unit, "mb") == 0 || strcasecmp(unit, "m") == 0) {
return (long)(size * 1024 * 1024);
} else if (strcasecmp(unit, "gb") == 0 || strcasecmp(unit, "g") == 0) {
return (long)(size * 1024 * 1024 * 1024);
} else if (strcasecmp(unit, "b") == 0 || *unit == '\0') {
return (long)size;
}
// Try to handle cases like "2.5 KB" with a space
char num_str[32];
char unit_str[8];
if (sscanf(size_str, "%31s %7s", num_str, unit_str) == 2) {
double num = atof(num_str);
if (strcasecmp(unit_str, "kb") == 0 || strcasecmp(unit_str, "k") == 0) {
return (long)(num * 1024);
} else if (strcasecmp(unit_str, "mb") == 0 || strcasecmp(unit_str, "m") == 0) {
return (long)(num * 1024 * 1024);
} else if (strcasecmp(unit_str, "gb") == 0 || strcasecmp(unit_str, "g") == 0) {
return (long)(num * 1024 * 1024 * 1024);
} else if (strcasecmp(unit_str, "b") == 0) {
return (long)num;
}
}
return (long)size; // Default to just the number if no unit matches
}
/**
* Extract size in bytes from a formatted size string (e.g., "10.5 KB")
*/
long extract_size_bytes(const char *size_str) {
double size_val = 0;
char unit[8] = "";
// Try the format with float + space + unit (e.g., "10.5 MB")
if (sscanf(size_str, "%lf %7s", &size_val, unit) == 2) {
if (strcasecmp(unit, "B") == 0) {
return (long)size_val;
} else if (strcasecmp(unit, "KB") == 0) {
return (long)(size_val * 1024);
} else if (strcasecmp(unit, "MB") == 0) {
return (long)(size_val * 1024 * 1024);
} else if (strcasecmp(unit, "GB") == 0) {
return (long)(size_val * 1024 * 1024 * 1024);
}
}
// Fallback to direct parsing if the format doesn't match
return parse_size(size_str);
}
/**
* Filter a table based on a condition
*/
TableData* filter_table(TableData *input, char *field, char *op, char *value) {
if (!input || !field || !op || !value) {
return NULL;
}
// Create a new table with the same headers
TableData *result = create_table(input->headers, input->header_count);
if (!result) {
return NULL;
}
// Find field index
int field_idx = -1;
for (int i = 0; i < input->header_count; i++) {
if (strcasecmp(input->headers[i], field) == 0) {
field_idx = i;
break;
}
}
if (field_idx == -1) {
fprintf(stderr, "filter_table: unknown field '%s'\n", field);
free_table(result);
return NULL;
}
// Special handling for size field - parse human-readable sizes
// Modified to handle both "size" and "Memory" columns
int is_size_field = (strcasecmp(field, "size") == 0 || strcasecmp(field, "Memory") == 0);
long value_size = is_size_field ? parse_size(value) : 0;
// Filter rows based on condition
for (int i = 0; i < input->row_count; i++) {
int include_row = 0;
// Handle based on data type
if (input->rows[i][field_idx].type == TYPE_STRING ||
input->rows[i][field_idx].type == TYPE_SIZE) {
char *row_value = input->rows[i][field_idx].value.str_val;
// Special handling for size field with values like "10.5 KB"
if (is_size_field) {
long row_size = extract_size_bytes(row_value);
// Compare sizes
if (strcmp(op, ">") == 0) {
include_row = (row_size > value_size);
} else if (strcmp(op, "<") == 0) {
include_row = (row_size < value_size);
} else if (strcmp(op, ">=") == 0) {
include_row = (row_size >= value_size);
} else if (strcmp(op, "<=") == 0) {
include_row = (row_size <= value_size);
} else if (strcmp(op, "==") == 0) {
include_row = (row_size == value_size);
}
} else {
// String comparison
int cmp = strcasecmp(row_value, value);
if (strcmp(op, ">") == 0) {
include_row = (cmp > 0);
} else if (strcmp(op, "<") == 0) {
include_row = (cmp < 0);
} else if (strcmp(op, "==") == 0) {
include_row = (cmp == 0);
} else if (strcmp(op, ">=") == 0) {
include_row = (cmp >= 0);
} else if (strcmp(op, "<=") == 0) {
include_row = (cmp <= 0);
}
}
} else if (input->rows[i][field_idx].type == TYPE_INT) {
int row_value = input->rows[i][field_idx].value.int_val;
int val = atoi(value);
if (strcmp(op, ">") == 0) {
include_row = (row_value > val);
} else if (strcmp(op, "<") == 0) {
include_row = (row_value < val);
} else if (strcmp(op, ">=") == 0) {
include_row = (row_value >= val);
} else if (strcmp(op, "<=") == 0) {
include_row = (row_value <= val);
} else if (strcmp(op, "==") == 0) {
include_row = (row_value == val);
}
} else if (input->rows[i][field_idx].type == TYPE_FLOAT) {
float row_value = input->rows[i][field_idx].value.float_val;
float val = (float)atof(value);
if (strcmp(op, ">") == 0) {
include_row = (row_value > val);
} else if (strcmp(op, "<") == 0) {
include_row = (row_value < val);
} else if (strcmp(op, ">=") == 0) {
include_row = (row_value >= val);
} else if (strcmp(op, "<=") == 0) {
include_row = (row_value <= val);
} else if (strcmp(op, "==") == 0) {
include_row = (row_value == val);
}
}
if (include_row) {
// Create a copy of the row for the filtered table
DataValue *row_copy = (DataValue*)malloc(input->header_count * sizeof(DataValue));
if (!row_copy) {
fprintf(stderr, "lsh: allocation error in filter_table\n");
free_table(result);
return NULL;
}
for (int j = 0; j < input->header_count; j++) {
row_copy[j] = copy_data_value(&input->rows[i][j]);
}
add_table_row(result, row_copy);
}
}
return result;
}
/**
* Print a table to the console with nice formatting using Unicode box characters
* Linux version using ANSI escape sequences for colors
*/
void print_table(TableData *table) {
if (!table || table->row_count == 0) {
printf("(empty table)\n");
return;
}
// Calculate column widths
int *col_widths = (int*)malloc(table->header_count * sizeof(int));
if (!col_widths) {
fprintf(stderr, "lsh: allocation error in print_table\n");
return;
}
// Initialize with header widths
for (int i = 0; i < table->header_count; i++) {
col_widths[i] = strlen(table->headers[i]);
}
// Check cell widths
for (int i = 0; i < table->row_count; i++) {
for (int j = 0; j < table->header_count; j++) {
if (table->rows[i][j].type == TYPE_STRING || table->rows[i][j].type == TYPE_SIZE) {
int len = strlen(table->rows[i][j].value.str_val);
if (len > col_widths[j]) {
col_widths[j] = len;
}
} else if (table->rows[i][j].type == TYPE_INT) {
char buf[32];
int len = snprintf(buf, sizeof(buf), "%d", table->rows[i][j].value.int_val);
if (len > col_widths[j]) {
col_widths[j] = len;
}
} else if (table->rows[i][j].type == TYPE_FLOAT) {
char buf[32];
int len = snprintf(buf, sizeof(buf), "%.2f", table->rows[i][j].value.float_val);
if (len > col_widths[j]) {
col_widths[j] = len;
}
}
}
}
// Add padding to column widths
for (int i = 0; i < table->header_count; i++) {
col_widths[i] += 4; // 2 spaces on each side
}
// Calculate total table width
int totalWidth = 1; // Start with 1 for the left border
for (int i = 0; i < table->header_count; i++) {
totalWidth += col_widths[i] + 1; // Add column width and right border
}
// Create a buffer big enough for one line of the table
char *lineBuffer = (char*)malloc(totalWidth * 4 + 1); // Unicode chars can be up to 4 bytes
if (!lineBuffer) {
fprintf(stderr, "lsh: allocation error in print_table\n");
free(col_widths);
return;
}
// Begin output with a newline
printf("\n");
// ---- Print top border ----
lineBuffer[0] = '\0';
strcat(lineBuffer, "┌"); // Top-left corner
for (int i = 0; i < table->header_count; i++) {
for (int j = 0; j < col_widths[i]; j++) {
strcat(lineBuffer, "─"); // Horizontal line
}
if (i < table->header_count - 1) {
strcat(lineBuffer, "┬"); // Top T-junction
}
}
strcat(lineBuffer, "┐"); // Top-right corner
printf("%s\n", lineBuffer);
// ---- Print header row ----
lineBuffer[0] = '\0';
strcat(lineBuffer, "│"); // Vertical line
for (int i = 0; i < table->header_count; i++) {
char cellBuffer[256];
snprintf(cellBuffer, sizeof(cellBuffer), " %-*s ", col_widths[i] - 2, table->headers[i]);
strcat(lineBuffer, cellBuffer);
strcat(lineBuffer, "│"); // Vertical line
}
printf("%s\n", lineBuffer);
// ---- Print header/data separator ----
lineBuffer[0] = '\0';
strcat(lineBuffer, "├"); // Left T-junction
for (int i = 0; i < table->header_count; i++) {
for (int j = 0; j < col_widths[i]; j++) {
strcat(lineBuffer, "─"); // Horizontal line
}
if (i < table->header_count - 1) {
strcat(lineBuffer, "┼"); // Cross junction
}
}
strcat(lineBuffer, "┤"); // Right T-junction
printf("%s\n", lineBuffer);
// Print data rows
for (int i = 0; i < table->row_count; i++) {
lineBuffer[0] = '\0';
strcat(lineBuffer, "│"); // Left border
for (int j = 0; j < table->header_count; j++) {
// Format cell content based on data type
char cellBuffer[256];
if (table->rows[i][j].type == TYPE_STRING || table->rows[i][j].type == TYPE_SIZE) {
snprintf(cellBuffer, sizeof(cellBuffer), " %-*s ", col_widths[j] - 2, table->rows[i][j].value.str_val);
} else if (table->rows[i][j].type == TYPE_INT) {
snprintf(cellBuffer, sizeof(cellBuffer), " %-*d ", col_widths[j] - 2, table->rows[i][j].value.int_val);
} else if (table->rows[i][j].type == TYPE_FLOAT) {
snprintf(cellBuffer, sizeof(cellBuffer), " %-*.2f ", col_widths[j] - 2, table->rows[i][j].value.float_val);
}
// Apply color if the cell is highlighted
if (table->rows[i][j].is_highlighted) {
strcat(lineBuffer, ANSI_COLOR_GREEN); // Green text for highlighted cells
strcat(lineBuffer, cellBuffer);
strcat(lineBuffer, ANSI_COLOR_RESET); // Reset color
} else {
strcat(lineBuffer, cellBuffer);
}
strcat(lineBuffer, "│"); // Right border of cell
}
printf("%s\n", lineBuffer);
}
// ---- Print bottom border ----
lineBuffer[0] = '\0';
strcat(lineBuffer, "└"); // Bottom-left corner
for (int i = 0; i < table->header_count; i++) {
for (int j = 0; j < col_widths[i]; j++) {
strcat(lineBuffer, "─"); // Horizontal line
}
if (i < table->header_count - 1) {
strcat(lineBuffer, "┴"); // Bottom T-junction
}
}
strcat(lineBuffer, "┘"); // Bottom-right corner
printf("%s\n\n", lineBuffer);
// Clean up
free(lineBuffer);
free(col_widths);
}

70
structured_data.h Normal file
View File

@@ -0,0 +1,70 @@
/**
* structured_data.h
* Definitions for structured data representation and manipulation
*/
#ifndef STRUCTURED_DATA_H
#define STRUCTURED_DATA_H
#include "common.h"
// Value types for structured data
typedef enum {
TYPE_STRING,
TYPE_INT,
TYPE_FLOAT,
TYPE_SIZE // Special type for file sizes with units
} ValueType;
// A single data value that can be of different types
typedef struct {
ValueType type;
union {
char *str_val;
int int_val;
float float_val;
struct {
double value;
char unit[8];
} size_val;
} value;
int is_highlighted; // Flag for highlighting in tables
} DataValue;
// Table structure to hold rows and columns of data
typedef struct {
char **headers; // Column names
int header_count; // Number of columns
DataValue **rows; // Array of rows (each row is an array of DataValues)
int row_count; // Current number of rows
int row_capacity; // Allocated capacity for rows
} TableData;
// Function to create a new table with the given headers
TableData* create_table(char **headers, int header_count);
// Function to add a row to a table
void add_table_row(TableData *table, DataValue *row);
// Function to free all memory associated with a table
void free_table(TableData *table);
// Function to create a copy of a DataValue
DataValue copy_data_value(const DataValue *src);
// Function to free a DataValue
void free_data_value(DataValue *value);
// Function to filter a table based on a condition
TableData* filter_table(TableData *input, char *field, char *op, char *value);
// Function to print a table to the console
void print_table(TableData *table);
// Function to parse human-readable sizes
long parse_size(const char *size_str);
// Function to extract size in bytes from a formatted size string
long extract_size_bytes(const char *size_str);
#endif // STRUCTURED_DATA_H

BIN
structured_data.o Normal file

Binary file not shown.

460
tab_complete.c Normal file
View File

@@ -0,0 +1,460 @@
/**
* tab_complete.c
* Linux implementation of tab completion
*/
#include "tab_complete.h"
#include "aliases.h"
#include "bookmarks.h"
#include "builtins.h"
#include "favorite_cities.h"
#include "themes.h"
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <unistd.h>
// Predefined command argument types for better completion
static CommandArgInfo command_arg_info[] = {
{"cd", ARG_TYPE_DIRECTORY, "Change current directory"},
{"ls", ARG_TYPE_DIRECTORY, "List directory contents"},
{"cat", ARG_TYPE_FILE, "Display file contents"},
{"rm", ARG_TYPE_FILE, "Remove file"},
{"mkdir", ARG_TYPE_DIRECTORY, "Make directory"},
{"rmdir", ARG_TYPE_DIRECTORY, "Remove directory"},
{"cp", ARG_TYPE_FILE, "Copy file or directory"},
{"mv", ARG_TYPE_BOTH, "Move file or directory"},
{"grep", ARG_TYPE_FILE, "Search file contents"},
{"less", ARG_TYPE_FILE, "View file contents"},
{"more", ARG_TYPE_FILE, "View file contents"},
{"find", ARG_TYPE_DIRECTORY, "Find files"},
{"chmod", ARG_TYPE_FILE, "Change file permissions"},
{"chown", ARG_TYPE_FILE, "Change file owner"},
{"tar", ARG_TYPE_FILE, "Archive utility"},
{"gzip", ARG_TYPE_FILE, "Compress files"},
{"gunzip", ARG_TYPE_FILE, "Decompress files"},
{"zip", ARG_TYPE_FILE, "Compress files"},
{"unzip", ARG_TYPE_FILE, "Decompress files"},
{"bash", ARG_TYPE_FILE, "Run bash script"},
{"sh", ARG_TYPE_FILE, "Run shell script"},
{"python", ARG_TYPE_FILE, "Run Python script"},
{"perl", ARG_TYPE_FILE, "Run Perl script"},
{"java", ARG_TYPE_FILE, "Run Java program"},
{"gcc", ARG_TYPE_FILE, "C compiler"},
{"make", ARG_TYPE_FILE, "Build utility"},
{"diff", ARG_TYPE_FILE, "Compare files"},
{"patch", ARG_TYPE_FILE, "Apply patch file"},
{"man", ARG_TYPE_ANY, "Display manual page"},
{"help", ARG_TYPE_ANY, "Display help"},
{"alias", ARG_TYPE_ALIAS, "Define or list aliases"},
{"unalias", ARG_TYPE_ALIAS, "Remove alias"},
{"bookmark", ARG_TYPE_BOOKMARK, "Bookmark directories"},
{"weather", ARG_TYPE_FAVORITE_CITY, "Weather information"},
{"theme", ARG_TYPE_THEME, "Shell theme settings"},
{NULL, ARG_TYPE_ANY, NULL} // End marker
};
// Global state
static CommandContext current_context;
/**
* Initialize tab completion system
*/
void init_tab_completion(void) {
// Initialize context to defaults
memset(&current_context, 0, sizeof(CommandContext));
}
/**
* Shutdown tab completion system
*/
void shutdown_tab_completion(void) {
// Nothing to clean up for now
}
/**
* Get the type of argument expected for a command
*/
static ArgumentType get_argument_type(const char *cmd) {
if (!cmd || !*cmd) return ARG_TYPE_ANY;
for (int i = 0; command_arg_info[i].command != NULL; i++) {
if (strcmp(command_arg_info[i].command, cmd) == 0) {
return command_arg_info[i].arg_type;
}
}
return ARG_TYPE_ANY;
}
/**
* Parse the command line to determine context
*/
static void parse_command_context(const char *buffer) {
// Reset the context
memset(&current_context, 0, sizeof(CommandContext));
if (!buffer || !*buffer) return;
// Make a copy of the buffer to tokenize
char buffer_copy[1024];
strncpy(buffer_copy, buffer, sizeof(buffer_copy) - 1);
buffer_copy[sizeof(buffer_copy) - 1] = '\0';
// Tokenize the buffer
char *token = strtok(buffer_copy, " \t");
int token_index = 0;
// Extract the command if present
if (token) {
strncpy(current_context.filter_command, token, sizeof(current_context.filter_command) - 1);
current_context.filter_command[sizeof(current_context.filter_command) - 1] = '\0';
token = strtok(NULL, " \t");
token_index++;
}
// Get the last token (current word being completed)
char *last_token = NULL;
while (token) {
last_token = token;
token_index++;
token = strtok(NULL, " \t");
}
if (last_token) {
strncpy(current_context.current_token, last_token, sizeof(current_context.current_token) - 1);
current_context.current_token[sizeof(current_context.current_token) - 1] = '\0';
current_context.token_index = token_index - 1; // Adjust token index to be 0-based
} else {
// This handles the case where there's only one token (the command)
// In this case, we're completing the command itself
strncpy(current_context.current_token, current_context.filter_command,
sizeof(current_context.current_token) - 1);
current_context.current_token[sizeof(current_context.current_token) - 1] = '\0';
current_context.token_index = 0;
}
}
/**
* Find all files/directories that match the beginning of path
*/
static char *find_path_completions(const char *path) {
if (!path || !*path) return NULL;
char dir_path[PATH_MAX] = "."; // Default to current directory
char name_prefix[PATH_MAX] = "";
// Split path into directory part and name prefix part
char *last_slash = strrchr(path, '/');
if (last_slash) {
// Path contains a directory part
int dir_len = last_slash - path;
strncpy(dir_path, path, dir_len);
dir_path[dir_len] = '\0';
// Handle absolute path vs. relative path with subdirectories
if (dir_len == 0) {
// Handle case where path starts with '/'
strcpy(dir_path, "/");
}
// Extract the name prefix part (after the last slash)
strncpy(name_prefix, last_slash + 1, sizeof(name_prefix) - 1);
name_prefix[sizeof(name_prefix) - 1] = '\0';
} else {
// No directory part, just a name prefix
strncpy(name_prefix, path, sizeof(name_prefix) - 1);
name_prefix[sizeof(name_prefix) - 1] = '\0';
}
// Open the directory
DIR *dir = opendir(dir_path);
if (!dir) {
return NULL;
}
// Find the first matching entry
struct dirent *entry;
char *completion = NULL;
int name_prefix_len = strlen(name_prefix);
while ((entry = readdir(dir)) != NULL) {
// Skip "." and ".." if there's already a name prefix
if (name_prefix_len > 0 && (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)) {
continue;
}
// Check if this entry matches the name prefix
if (strncasecmp(entry->d_name, name_prefix, name_prefix_len) == 0) {
// Construct the completion
char full_path[PATH_MAX];
// Check if we're completing from root or relative
if (dir_path[0] == '/' && dir_path[1] == '\0') {
snprintf(full_path, sizeof(full_path), "/%s", entry->d_name);
} else if (strcmp(dir_path, ".") == 0) {
snprintf(full_path, sizeof(full_path), "%s", entry->d_name);
} else {
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
}
// Check if it's a directory, append slash if it is
struct stat st;
char path_to_check[PATH_MAX];
if (full_path[0] == '/') {
// Absolute path
strncpy(path_to_check, full_path, sizeof(path_to_check) - 1);
} else {
// Relative path, construct with current directory
char cwd[PATH_MAX];
if (getcwd(cwd, sizeof(cwd))) {
snprintf(path_to_check, sizeof(path_to_check), "%s/%s", cwd, full_path);
} else {
strncpy(path_to_check, full_path, sizeof(path_to_check) - 1);
}
}
path_to_check[sizeof(path_to_check) - 1] = '\0';
if (stat(path_to_check, &st) == 0 && S_ISDIR(st.st_mode)) {
char *dir_completion = (char *)malloc(strlen(full_path) + 2); // +2 for slash and null
if (dir_completion) {
strcpy(dir_completion, full_path);
strcat(dir_completion, "/");
completion = dir_completion;
}
} else {
completion = strdup(full_path);
}
break;
}
}
closedir(dir);
return completion;
}
/**
* Get the completion for a command
*/
static char *complete_command(const char *prefix) {
if (!prefix || !*prefix) return NULL;
// First check builtins
int builtin_count = lsh_num_builtins();
for (int i = 0; i < builtin_count; i++) {
if (strncasecmp(builtin_str[i], prefix, strlen(prefix)) == 0) {
return strdup(builtin_str[i]);
}
}
// Then check for aliases
int alias_count;
char **aliases = get_alias_names(&alias_count);
if (aliases) {
for (int i = 0; i < alias_count; i++) {
if (strncasecmp(aliases[i], prefix, strlen(prefix)) == 0) {
char *result = strdup(aliases[i]);
// Free the alias names
for (int j = 0; j < alias_count; j++) {
free(aliases[j]);
}
free(aliases);
return result;
}
}
// Free the alias names if we didn't find a match
for (int i = 0; i < alias_count; i++) {
free(aliases[i]);
}
free(aliases);
}
// Finally check for executables in PATH
char *path = getenv("PATH");
if (!path) return NULL;
char *path_copy = strdup(path);
if (!path_copy) return NULL;
char *result = NULL;
char *dir = strtok(path_copy, ":");
while (dir && !result) {
DIR *dirp = opendir(dir);
if (dirp) {
struct dirent *entry;
while ((entry = readdir(dirp)) != NULL) {
if (strncasecmp(entry->d_name, prefix, strlen(prefix)) == 0) {
// Check if it's executable
char full_path[PATH_MAX];
snprintf(full_path, sizeof(full_path), "%s/%s", dir, entry->d_name);
struct stat st;
if (stat(full_path, &st) == 0 && (st.st_mode & S_IXUSR)) {
result = strdup(entry->d_name);
break;
}
}
}
closedir(dirp);
}
dir = strtok(NULL, ":");
}
free(path_copy);
return result;
}
/**
* Get completion for the current context
*/
char *get_tab_completion(const char *buffer) {
if (!buffer) return NULL;
// Parse the command context
parse_command_context(buffer);
// If we're completing the first token, it's a command
if (current_context.token_index == 0) {
return complete_command(current_context.current_token);
}
// For subsequent tokens, look at the command to determine what to complete
ArgumentType arg_type = get_argument_type(current_context.filter_command);
switch (arg_type) {
case ARG_TYPE_FILE:
// Complete file names
return find_path_completions(current_context.current_token);
case ARG_TYPE_DIRECTORY:
// Complete directory names
return find_path_completions(current_context.current_token);
case ARG_TYPE_BOTH:
// Complete both files and directories
return find_path_completions(current_context.current_token);
case ARG_TYPE_BOOKMARK:
// Complete bookmark names
if (current_context.current_token[0]) {
int bookmark_count;
char **bookmarks = get_bookmark_names(&bookmark_count);
if (bookmarks) {
char *result = NULL;
for (int i = 0; i < bookmark_count; i++) {
if (strncasecmp(bookmarks[i], current_context.current_token, strlen(current_context.current_token)) == 0) {
result = strdup(bookmarks[i]);
break;
}
}
// Free the bookmark names
for (int i = 0; i < bookmark_count; i++) {
free(bookmarks[i]);
}
free(bookmarks);
return result;
}
}
break;
case ARG_TYPE_ALIAS:
// Complete alias names
if (current_context.current_token[0]) {
int alias_count;
char **aliases = get_alias_names(&alias_count);
if (aliases) {
char *result = NULL;
for (int i = 0; i < alias_count; i++) {
if (strncasecmp(aliases[i], current_context.current_token, strlen(current_context.current_token)) == 0) {
result = strdup(aliases[i]);
break;
}
}
// Free the alias names
for (int i = 0; i < alias_count; i++) {
free(aliases[i]);
}
free(aliases);
return result;
}
}
break;
case ARG_TYPE_FAVORITE_CITY:
// Complete favorite city names
if (current_context.current_token[0]) {
int city_count;
char **cities = get_favorite_city_names(&city_count);
if (cities) {
char *result = NULL;
for (int i = 0; i < city_count; i++) {
if (strncasecmp(cities[i], current_context.current_token, strlen(current_context.current_token)) == 0) {
result = strdup(cities[i]);
break;
}
}
// Free the city names
for (int i = 0; i < city_count; i++) {
free(cities[i]);
}
free(cities);
return result;
}
}
break;
case ARG_TYPE_THEME:
// Complete theme names
if (current_context.current_token[0]) {
int theme_count;
char **themes = get_theme_names(&theme_count);
if (themes) {
char *result = NULL;
for (int i = 0; i < theme_count; i++) {
if (strncasecmp(themes[i], current_context.current_token, strlen(current_context.current_token)) == 0) {
result = strdup(themes[i]);
break;
}
}
// Free the theme names
for (int i = 0; i < theme_count; i++) {
free(themes[i]);
}
free(themes);
return result;
}
}
break;
case ARG_TYPE_ANY:
default:
// By default, complete file and directory names
return find_path_completions(current_context.current_token);
}
return NULL;
}

58
tab_complete.h Normal file
View File

@@ -0,0 +1,58 @@
/**
* tab_complete.h
* Functions for tab completion and path suggestions
*/
#ifndef TAB_COMPLETE_H
#define TAB_COMPLETE_H
#include "common.h"
/**
* Command context structure
*/
typedef struct {
int is_after_pipe; // Flag if cursor is right after a pipe
int is_filter_command; // Flag if current command is a filter command
char cmd_before_pipe[64]; // Command before the pipe
char filter_command[64]; // Current filter command
char current_token[1024]; // Current token being typed
int token_position; // Position within current token
int token_index; // Index of current token in command
int filter_arg_index; // Argument index for filter command
int has_current_field; // Flag if current field is set
int has_current_operator; // Flag if current operator is set
char current_field[64]; // Current field if known
char current_operator[16]; // Current operator if known
} CommandContext;
typedef enum {
ARG_TYPE_ANY, // Any argument
ARG_TYPE_FILE, // Files only
ARG_TYPE_DIRECTORY, // Directories only
ARG_TYPE_BOOKMARK, // Bookmark names
ARG_TYPE_ALIAS, // Alias names
ARG_TYPE_BOTH, // Both files and directories
ARG_TYPE_FAVORITE_CITY,
ARG_TYPE_THEME,
} ArgumentType;
/**
* Structure for command argument type information
*/
typedef struct {
char *command; // Command name
ArgumentType arg_type; // Expected argument type
char *description; // Optional description
} CommandArgInfo;
// Main tab completion function
char *get_tab_completion(const char *buffer);
// Initialize tab completion system
void init_tab_completion(void);
// Shutdown tab completion system
void shutdown_tab_completion(void);
#endif // TAB_COMPLETE_H

BIN
tab_complete.o Normal file

Binary file not shown.

481
themes.c Normal file
View File

@@ -0,0 +1,481 @@
/**
* themes.c
* Implementation of theme system for shell appearance
*/
#include "themes.h"
#include <stdio.h>
#include <string.h>
// Define Windows colors for compatibility
#define FOREGROUND_BLUE 0x0001
#define FOREGROUND_GREEN 0x0002
#define FOREGROUND_RED 0x0004
#define FOREGROUND_INTENSITY 0x0008
#define FOREGROUND_CYAN (FOREGROUND_GREEN | FOREGROUND_BLUE)
#define FOREGROUND_MAGENTA (FOREGROUND_RED | FOREGROUND_BLUE)
// Current active theme
ShellTheme current_theme;
// Define built-in themes
static ShellTheme default_theme = {
// Standard console colors
.PRIMARY_COLOR =
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // White
.SECONDARY_COLOR =
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // White (dimmer)
.ACCENT_COLOR = FOREGROUND_GREEN | FOREGROUND_INTENSITY, // Bright Green
.SUCCESS_COLOR = FOREGROUND_GREEN | FOREGROUND_INTENSITY, // Bright Green
.ERROR_COLOR = FOREGROUND_RED | FOREGROUND_INTENSITY, // Bright Red
.WARNING_COLOR = FOREGROUND_RED | FOREGROUND_GREEN |
FOREGROUND_INTENSITY, // Bright Yellow
.HEADER_COLOR = FOREGROUND_GREEN | FOREGROUND_INTENSITY, // Bright Green
.STATUS_BAR_COLOR = 0, // Black
.STATUS_TEXT_COLOR = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
FOREGROUND_INTENSITY, // Bright White
.PROMPT_COLOR = FOREGROUND_CYAN | FOREGROUND_INTENSITY, // Bright Cyan
.DIRECTORY_COLOR = FOREGROUND_GREEN | FOREGROUND_INTENSITY, // Bright Green
.EXECUTABLE_COLOR = FOREGROUND_RED | FOREGROUND_GREEN |
FOREGROUND_INTENSITY, // Bright Yellow
.TEXT_FILE_COLOR =
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // White
.IMAGE_FILE_COLOR = FOREGROUND_RED | FOREGROUND_BLUE |
FOREGROUND_INTENSITY, // Bright Magenta
.CODE_FILE_COLOR = FOREGROUND_BLUE | FOREGROUND_INTENSITY, // Bright Blue
.ARCHIVE_FILE_COLOR = FOREGROUND_RED | FOREGROUND_INTENSITY, // Bright Red
.SYNTAX_KEYWORD = FOREGROUND_CYAN | FOREGROUND_INTENSITY, // Bright Cyan
.SYNTAX_STRING = FOREGROUND_GREEN | FOREGROUND_INTENSITY, // Bright Green
.SYNTAX_COMMENT = FOREGROUND_INTENSITY, // Gray
.SYNTAX_NUMBER =
FOREGROUND_MAGENTA | FOREGROUND_INTENSITY, // Bright Magenta
.SYNTAX_PREPROCESSOR =
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // Yellow
// ANSI colors for default theme (basic colors, not true full 24-bit colors)
.ANSI_BASE = "\033[37m", // White
.ANSI_SURFACE = "\033[37m", // White
.ANSI_OVERLAY = "\033[37m", // White
.ANSI_MUTED = "\033[37m", // White
.ANSI_SUBTLE = "\033[37m", // White
.ANSI_TEXT = "\033[97m", // Bright White
.ANSI_LOVE = "\033[91m", // Bright Red
.ANSI_GOLD = "\033[93m", // Bright Yellow
.ANSI_ROSE = "\033[95m", // Bright Magenta
.ANSI_PINE = "\033[92m", // Bright Green
.ANSI_FOAM = "\033[96m", // Bright Cyan
.ANSI_IRIS = "\033[94m", // Bright Blue
.ANSI_HIGHLIGHT = "\033[37m", // White
// Don't use ANSI colors for default theme
.use_ansi_colors = FALSE,
.name = "default"};
static ShellTheme rose_pine_theme = {
// Legacy console colors (for backward compatibility)
.PRIMARY_COLOR =
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // White (base)
.SECONDARY_COLOR =
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // Muted
.ACCENT_COLOR = FOREGROUND_BLUE, // Blue for git syntax
.SUCCESS_COLOR = FOREGROUND_GREEN,
.ERROR_COLOR = FOREGROUND_RED | FOREGROUND_INTENSITY,
.WARNING_COLOR = FOREGROUND_RED | FOREGROUND_GREEN,
.HEADER_COLOR = FOREGROUND_RED | FOREGROUND_BLUE,
.STATUS_BAR_COLOR = 0,
.STATUS_TEXT_COLOR = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
FOREGROUND_INTENSITY,
.PROMPT_COLOR =
FOREGROUND_RED | FOREGROUND_INTENSITY, // Bright red for repo name
.DIRECTORY_COLOR =
FOREGROUND_RED | FOREGROUND_INTENSITY, // Bright pink for directory
.EXECUTABLE_COLOR = FOREGROUND_RED | FOREGROUND_GREEN,
.TEXT_FILE_COLOR = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
.IMAGE_FILE_COLOR = FOREGROUND_BLUE,
.CODE_FILE_COLOR = FOREGROUND_BLUE,
.ARCHIVE_FILE_COLOR = FOREGROUND_RED,
.SYNTAX_KEYWORD = FOREGROUND_RED | FOREGROUND_BLUE,
.SYNTAX_STRING = FOREGROUND_GREEN,
.SYNTAX_COMMENT = FOREGROUND_RED | FOREGROUND_GREEN | 0x8,
.SYNTAX_NUMBER = FOREGROUND_RED,
.SYNTAX_PREPROCESSOR = FOREGROUND_RED | FOREGROUND_GREEN,
// ANSI true color definitions - exact Rose Pine colors
.ANSI_BASE = "\033[38;2;25;23;36m", // Base color
.ANSI_SURFACE = "\033[38;2;31;29;46m", // Surface color
.ANSI_OVERLAY = "\033[38;2;38;35;58m", // Overlay color
.ANSI_MUTED = "\033[38;2;110;106;134m", // Muted color
.ANSI_SUBTLE = "\033[38;2;144;140;170m", // Subtle color
.ANSI_TEXT = "\033[38;2;224;222;244m", // Text color
.ANSI_LOVE = "\033[38;2;235;111;146m", // Love/Red color
.ANSI_GOLD = "\033[38;2;246;193;119m", // Gold color
// Updated Rose/pink color to a soft peach
.ANSI_ROSE = "\033[38;2;255;195;195m", // Soft Peach
.ANSI_PINE = "\033[38;2;49;116;143m", // Pine color
.ANSI_FOAM = "\033[38;2;156;207;216m", // Foam color
.ANSI_IRIS = "\033[38;2;196;167;231m", // Iris color
.ANSI_HIGHLIGHT = "\033[38;2;68;65;90m", // Highlight color
.ANSI_INVALID_COMMAND = "\033[38;2;205;120;120m",
// Enable ANSI colors for Rose Pine theme
.use_ansi_colors = TRUE,
.name = "rose-pine"};
static ShellTheme catppuccin_mocha_theme = {
// Legacy console colors (for backward compatibility)
.PRIMARY_COLOR = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
FOREGROUND_INTENSITY, // Bright white for text
.SECONDARY_COLOR =
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // White (dimmer)
.ACCENT_COLOR =
FOREGROUND_BLUE | FOREGROUND_INTENSITY, // Bright Blue for accent
.SUCCESS_COLOR = FOREGROUND_GREEN | FOREGROUND_INTENSITY, // Bright Green
.ERROR_COLOR = FOREGROUND_RED | FOREGROUND_INTENSITY, // Bright Red
.WARNING_COLOR =
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // Yellow
.HEADER_COLOR = FOREGROUND_RED | FOREGROUND_BLUE |
FOREGROUND_INTENSITY, // Mauve color for headers
.STATUS_BAR_COLOR = 0, // Black/dark background
.STATUS_TEXT_COLOR = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
FOREGROUND_INTENSITY, // Bright White
.PROMPT_COLOR = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
FOREGROUND_INTENSITY, // Blue text for prompt
.DIRECTORY_COLOR = FOREGROUND_BLUE | FOREGROUND_GREEN |
FOREGROUND_INTENSITY, // Teal for directories
.EXECUTABLE_COLOR = FOREGROUND_RED | FOREGROUND_GREEN |
FOREGROUND_INTENSITY, // Yellow for executables
.TEXT_FILE_COLOR = FOREGROUND_RED | FOREGROUND_GREEN |
FOREGROUND_BLUE, // White for text files
.IMAGE_FILE_COLOR = FOREGROUND_RED | FOREGROUND_INTENSITY, // Red for images
.CODE_FILE_COLOR = FOREGROUND_BLUE | FOREGROUND_INTENSITY, // Blue for code
.ARCHIVE_FILE_COLOR =
FOREGROUND_GREEN | FOREGROUND_INTENSITY, // Green for archives
.SYNTAX_KEYWORD =
FOREGROUND_BLUE | FOREGROUND_INTENSITY, // Blue for keywords
.SYNTAX_STRING =
FOREGROUND_GREEN | FOREGROUND_INTENSITY, // Green for strings
.SYNTAX_COMMENT = FOREGROUND_INTENSITY, // Gray for comments
.SYNTAX_NUMBER = FOREGROUND_RED | FOREGROUND_INTENSITY, // Red for numbers
.SYNTAX_PREPROCESSOR = FOREGROUND_GREEN | FOREGROUND_BLUE |
FOREGROUND_INTENSITY, // Cyan for preprocessor
// ANSI true color definitions for Catppuccin Mocha
.ANSI_BASE = "\033[38;2;30;30;46m", // Base color #1e1e2e
.ANSI_SURFACE = "\033[38;2;49;50;68m", // Surface color #313244
.ANSI_OVERLAY = "\033[38;2;69;71;90m", // Overlay color #45475a
.ANSI_MUTED = "\033[38;2;186;194;222m", // Muted color #bac2de (subtext1)
.ANSI_SUBTLE = "\033[38;2;166;173;200m", // Subtle color #a6adc8 (subtext0)
.ANSI_TEXT = "\033[38;2;205;214;244m", // Text color #cdd6f4
.ANSI_LOVE = "\033[38;2;243;139;168m", // Love/Red color #f38ba8
.ANSI_GOLD = "\033[38;2;249;226;175m", // Gold/Yellow color #f9e2af
.ANSI_ROSE = "\033[38;2;245;194;231m", // Pink color #f5c2e7
.ANSI_PINE = "\033[38;2;166;227;161m", // Green color #a6e3a1
.ANSI_FOAM = "\033[38;2;148;226;213m", // Teal color #94e2d5
.ANSI_IRIS = "\033[38;2;203;166;247m", // Mauve/Purple color #cba6f7
.ANSI_HIGHLIGHT = "\033[38;2;180;190;254m", // Lavender highlight #b4befe
// Enable ANSI colors for Catppuccin Mocha theme
.use_ansi_colors = TRUE,
.name = "catppuccin-mocha"};
// Path to the theme configuration file
char theme_config_path[PATH_MAX];
/**
* Initialize the theme system
*/
void init_theme_system(void) {
// Determine theme config file location in user's home directory
char *home_dir = getenv("HOME");
if (home_dir) {
snprintf(theme_config_path, PATH_MAX, "%s/.lsh_theme", home_dir);
} else {
// Fallback to current directory if HOME not available
strcpy(theme_config_path, ".lsh_theme");
}
// Set default theme initially
memcpy(&current_theme, &default_theme, sizeof(ShellTheme));
// Try to load theme from config file
FILE *config_file = fopen(theme_config_path, "r");
if (config_file) {
char theme_name[32];
if (fscanf(config_file, "theme=%31s", theme_name) == 1) {
// We found a theme name, try to load it
load_theme(theme_name);
}
fclose(config_file);
} else {
// Config file doesn't exist, create it with default theme
config_file = fopen(theme_config_path, "w");
if (config_file) {
fprintf(config_file, "theme=default\n");
fclose(config_file);
}
}
}
/**
* Load a specific theme by name
*
* @param theme_name Name of the theme to load
* @return 1 if successful, 0 if theme not found
*/
int load_theme(const char *theme_name) {
if (strcmp(theme_name, "default") == 0) {
memcpy(&current_theme, &default_theme, sizeof(ShellTheme));
return 1;
} else if (strcmp(theme_name, "rose-pine") == 0) {
memcpy(&current_theme, &rose_pine_theme, sizeof(ShellTheme));
return 1;
} else if (strcmp(theme_name, "catppuccin-mocha") == 0) {
memcpy(&current_theme, &catppuccin_mocha_theme, sizeof(ShellTheme));
return 1;
}
// Theme not found
fprintf(stderr, "Theme '%s' not found\n", theme_name);
return 0;
}
/**
* Apply the current theme settings
* Called whenever theme-dependent UI elements need to be updated
*/
void apply_current_theme(void) {
// When called, the shell can update any on-screen elements that depend on
// theme colors. This function would be called when changing themes or at startup.
// In Linux, we'll always use ANSI colors, as the terminal emulator handles this natively
if (current_theme.use_ansi_colors) {
// Use ANSI true color
printf("%s", current_theme.ANSI_TEXT);
} else {
// Use standard ANSI color approximation of the theme's primary color
// Linux terminals always support basic ANSI colors, so we'll map the Windows color attributes
// to the closest ANSI colors
int fg = 37; // Default to white text
if (current_theme.PRIMARY_COLOR & 0x01) fg = 34; // Blue
if (current_theme.PRIMARY_COLOR & 0x02) fg = 32; // Green
if (current_theme.PRIMARY_COLOR & 0x04) fg = 31; // Red
// Combinations
if ((current_theme.PRIMARY_COLOR & 0x03) == 0x03) fg = 36; // Cyan (Green + Blue)
if ((current_theme.PRIMARY_COLOR & 0x05) == 0x05) fg = 35; // Magenta (Red + Blue)
if ((current_theme.PRIMARY_COLOR & 0x06) == 0x06) fg = 33; // Yellow (Red + Green)
if ((current_theme.PRIMARY_COLOR & 0x07) == 0x07) fg = 37; // White (Red + Green + Blue)
// Bright variants
if (current_theme.PRIMARY_COLOR & 0x08) fg += 60; // Add intensity
printf("\033[%dm", fg); // Set the color
}
}
/**
* Get pointer to the current theme
*
* @return Pointer to the current theme structure
*/
const ShellTheme *get_current_theme(void) { return &current_theme; }
/**
* List all available themes
*/
void list_available_themes(void) {
printf("Available themes:\n");
printf(" default - Standard shell colors\n");
printf(
" rose-pine - Soothing, warm color scheme with true color support\n");
printf(" catppuccin-mocha - Deep dark theme with vibrant accent colors");
// Display which theme is currently active
printf("\nCurrent theme: %s", current_theme.name);
// Display ANSI color status for current theme
if (current_theme.use_ansi_colors) {
printf(" (using true color)\n");
} else {
printf(" (using standard console colors)\n");
}
}
/**
* Initialize themes at shell startup
*/
void init_themes(void) {
// Call the theme system initialization
init_theme_system();
// Apply the initial theme
apply_current_theme();
}
/**
* Shutdown theme system
*/
void shutdown_themes(void) {
// Reset terminal color to default
printf(ANSI_COLOR_RESET);
// Currently, no other cleanup is needed for the theme system
}
/**
* Command handler for the "theme" command
*
* @param args Command arguments
* @return 1 to continue shell execution, 0 to exit shell
*/
int lsh_theme(char **args) {
if (args[1] == NULL) {
// No arguments, show usage info and current theme
printf("Usage: theme <command> [arguments]\n");
printf("Commands:\n");
printf(" list List available themes\n");
printf(" set NAME Set the current theme to NAME\n");
printf(" show Show current theme details\n");
printf("\nCurrent theme: %s", current_theme.name);
// Display ANSI color status for current theme
if (current_theme.use_ansi_colors) {
printf(" (using true color)\n");
} else {
printf(" (using standard console colors)\n");
}
return 1;
}
// Handle subcommands
if (strcmp(args[1], "list") == 0) {
list_available_themes();
return 1;
} else if (strcmp(args[1], "set") == 0) {
if (args[2] == NULL) {
printf("Usage: theme set <theme_name>\n");
printf("Try 'theme list' to see available themes\n");
return 1;
}
// Try to load the requested theme
if (load_theme(args[2])) {
// Update theme config file
FILE *config_file = fopen(theme_config_path, "w");
if (config_file) {
fprintf(config_file, "theme=%s\n", args[2]);
fclose(config_file);
// Apply the theme
apply_current_theme();
printf("Theme set to '%s'\n", args[2]);
// Display ANSI color status for the selected theme
if (current_theme.use_ansi_colors) {
printf("This theme uses true color for better visual appearance.\n");
}
} else {
fprintf(stderr, "Could not save theme setting\n");
}
}
return 1;
} else if (strcmp(args[1], "show") == 0) {
printf("Current theme: %s\n", current_theme.name);
// Display ANSI color status
if (current_theme.use_ansi_colors) {
printf("Using true color mode");
// Show a color demo if using ANSI colors
printf("\nColor sample:\n");
printf("%sBase %sText %sLove %sGold %sRose %sPine %sFoam %sIris%s\n",
current_theme.ANSI_BASE, current_theme.ANSI_TEXT,
current_theme.ANSI_LOVE, current_theme.ANSI_GOLD,
current_theme.ANSI_ROSE, current_theme.ANSI_PINE,
current_theme.ANSI_FOAM, current_theme.ANSI_IRIS,
"\033[0m"); // Reset
} else {
printf("Using standard console colors\n");
}
return 1;
} else {
printf("Unknown theme command: %s\n", args[1]);
printf("Try 'theme list' or 'theme set <name>'\n");
return 1;
}
return 1;
}
char **get_theme_names(int *theme_count) {
// Define the hardcoded themes
char *builtin_themes[] = {"default", "rose-pine", "catppuccin-mocha"};
int builtin_count = sizeof(builtin_themes) / sizeof(builtin_themes[0]);
// Try to open the theme config file to check for custom themes
FILE *config_file = fopen(theme_config_path, "r");
char custom_theme[32];
int has_custom_theme = 0;
if (config_file) {
char theme_line[64];
while (fgets(theme_line, sizeof(theme_line), config_file)) {
if (sscanf(theme_line, "theme=%31s", custom_theme) == 1) {
// Check if this is a custom theme (not one of our builtin themes)
int is_builtin = 0;
for (int i = 0; i < builtin_count; i++) {
if (strcmp(custom_theme, builtin_themes[i]) == 0) {
is_builtin = 1;
break;
}
}
if (!is_builtin) {
has_custom_theme = 1;
break;
}
}
}
fclose(config_file);
}
// Allocate space for themes (builtin + potential custom theme)
int total_themes = builtin_count + (has_custom_theme ? 1 : 0);
char **theme_names = (char **)malloc(total_themes * sizeof(char *));
if (!theme_names) {
fprintf(stderr, "lsh: allocation error in get_theme_names\n");
*theme_count = 0;
return NULL;
}
// Copy builtin theme names
for (int i = 0; i < builtin_count; i++) {
theme_names[i] = strdup(builtin_themes[i]);
}
// Add custom theme if found
if (has_custom_theme) {
theme_names[builtin_count] = strdup(custom_theme);
}
*theme_count = total_themes;
return theme_names;
}

92
themes.h Normal file
View File

@@ -0,0 +1,92 @@
#ifndef THEMES_H
#define THEMES_H
#include "common.h"
typedef struct {
// Standard console colors
WORD PRIMARY_COLOR; // Main text color
WORD SECONDARY_COLOR; // Secondary text color
WORD ACCENT_COLOR; // Color for highlights/important elements
WORD SUCCESS_COLOR; // Success messages
WORD ERROR_COLOR; // Error messages
WORD WARNING_COLOR; // Warning messages
WORD INVALID_COMMAND_COLOR; // unrecognized command
// UI element colors
WORD HEADER_COLOR; // Headers and titles
WORD STATUS_BAR_COLOR; // Status bar background
WORD STATUS_TEXT_COLOR; // Status bar text
WORD PROMPT_COLOR; // Command prompt
// File system colors
WORD DIRECTORY_COLOR; // Directory names
WORD EXECUTABLE_COLOR; // Executable files
WORD TEXT_FILE_COLOR; // Text files
WORD IMAGE_FILE_COLOR; // Image files
WORD CODE_FILE_COLOR; // Code files (non-executable)
WORD ARCHIVE_FILE_COLOR; // Archive files
// Syntax highlighting colors
WORD SYNTAX_KEYWORD; // Keywords in syntax highlighting
WORD SYNTAX_STRING; // Strings in syntax highlighting
WORD SYNTAX_COMMENT; // Comments in syntax highlighting
WORD SYNTAX_NUMBER; // Numbers in syntax highlighting
WORD SYNTAX_PREPROCESSOR; // Preprocessor in syntax highlighting
// ANSI true color definitions (for modern terminals)
char ANSI_BASE[20]; // Base color
char ANSI_SURFACE[20]; // Surface color
char ANSI_OVERLAY[20]; // Overlay color
char ANSI_MUTED[20]; // Muted color
char ANSI_SUBTLE[20]; // Subtle color
char ANSI_TEXT[20]; // Text color
char ANSI_LOVE[20]; // Love/Red color
char ANSI_GOLD[20]; // Gold color
char ANSI_ROSE[20]; // Rose/pink color
char ANSI_PINE[20]; // Pine color
char ANSI_FOAM[20]; // Foam color
char ANSI_IRIS[20]; // Iris color
char ANSI_HIGHLIGHT[20]; // Highlight color
char ANSI_INVALID_COMMAND[20];
// Flag to indicate if ANSI colors should be used
BOOL use_ansi_colors;
// Name of the theme
char name[32];
} ShellTheme;
// Initialize theme system
void init_theme_system(void);
// Shutdown theme system
void shutdown_themes(void);
// Initialize themes for shell startup
void init_themes(void);
// Load a theme by name
int load_theme(const char *theme_name);
// Apply current theme settings
void apply_current_theme(void);
// Get current theme
const ShellTheme *get_current_theme(void);
// List available themes
void list_available_themes(void);
// Get theme names for tab completion
char **get_theme_names(int *theme_count);
// Command handler for "theme" command
int lsh_theme(char **args);
// Current active theme
extern ShellTheme current_theme;
#endif // THEMES_H

BIN
themes.o Normal file

Binary file not shown.

97
timer.c Normal file
View File

@@ -0,0 +1,97 @@
/**
* timer.c
* Implements a command to measure execution time of other commands
*/
#include "builtins.h"
#include "common.h"
#include "shell.h"
#include <time.h>
/**
* Format a time interval in milliseconds to a readable string
*
* @param ms Time in milliseconds
* @param buffer Buffer to store formatted time
* @param buffer_size Size of the buffer
*/
void format_time(double ms, char *buffer, size_t buffer_size) {
if (ms < 1.0) {
// Microseconds
snprintf(buffer, buffer_size, "%.2f μs", ms * 1000);
} else if (ms < 1000.0) {
// Milliseconds
snprintf(buffer, buffer_size, "%.2f ms", ms);
} else if (ms < 60000.0) {
// Seconds
snprintf(buffer, buffer_size, "%.2f s", ms / 1000);
} else {
// Minutes and seconds
int minutes = (int)(ms / 60000);
double seconds = (ms - minutes * 60000) / 1000;
snprintf(buffer, buffer_size, "%d min %.2f s", minutes, seconds);
}
}
/**
* Implementation of the timer command
* Usage: timer COMMAND [ARGS...]
*
* @param args Command arguments
* @return 1 to continue, 0 to exit
*/
int lsh_timer(char **args) {
if (!args[1]) {
printf("timer: usage: timer COMMAND [ARGS...]\n");
printf("Measures execution time of a command\n");
return 1;
}
// Create a new array for the command to execute (without "timer" prefix)
char **cmd_args = &args[1];
// Start timing
clock_t start_time = clock();
// Execute the command
int result;
if (strcmp(cmd_args[0], "cd") == 0 || strcmp(cmd_args[0], "exit") == 0 ||
strcmp(cmd_args[0], "timer") == 0) {
// Handle built-in commands that might affect shell state
printf("timer: can't time built-in command: %s\n", cmd_args[0]);
result = 1;
} else {
// Execute the command normally
result = lsh_execute(cmd_args);
}
// End timing
clock_t end_time = clock();
// Calculate execution time in milliseconds
double execution_time_ms =
((double)(end_time - start_time) / CLOCKS_PER_SEC) * 1000;
// Format the execution time
char time_str[64];
format_time(execution_time_ms, time_str, sizeof(time_str));
// Print the execution time with color
printf("\n");
printf(ANSI_COLOR_GREEN); // Green text for highlighted display
printf("╭───────────────────────────────╮\n");
printf("│ Execution time: %-14s │\n", time_str);
printf("╰───────────────────────────────╯\n");
printf(ANSI_COLOR_RESET); // Reset color
return result;
}
/**
* Implementation of the time command (alias for timer)
* Usage: time COMMAND [ARGS...]
*
* @param args Command arguments
* @return 1 to continue, 0 to exit
*/
int lsh_time(char **args) { return lsh_timer(args); }

38
timer.h Normal file
View File

@@ -0,0 +1,38 @@
/**
* timer.h
* Header for command execution timer functionality
*/
#ifndef TIMER_H
#define TIMER_H
#include "common.h"
/**
* Format a time interval in milliseconds to a readable string
*
* @param ms Time in milliseconds
* @param buffer Buffer to store formatted time
* @param buffer_size Size of the buffer
*/
void format_time(double ms, char *buffer, size_t buffer_size);
/**
* Implementation of the timer command
* Usage: timer COMMAND [ARGS...]
*
* @param args Command arguments
* @return 1 to continue, 0 to exit
*/
int lsh_timer(char **args);
/**
* Implementation of the time command (alias for timer)
* Usage: time COMMAND [ARGS...]
*
* @param args Command arguments
* @return 1 to continue, 0 to exit
*/
int lsh_time(char **args);
#endif // TIMER_H

BIN
timer.o Normal file

Binary file not shown.

75
weather.c Normal file
View File

@@ -0,0 +1,75 @@
/**
* weather.c
* Implementation of weather information fetching and display for Linux
* This is a simplified version that provides basic information about using curl/wget
* instead of implementing full HTTP functionality
*/
#include "weather.h"
#include "favorite_cities.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* Command handler for the "weather" command
* In this Linux version, we'll provide instructions for using curl/wget
*/
int lsh_weather(char **args) {
printf("\n");
printf("╭─────────────────────────────────────────────────────────────╮\n");
printf("│ Weather Command (Linux Version) │\n");
printf("├─────────────────────────────────────────────────────────────┤\n");
printf("│ This is a simplified version of the weather command for │\n");
printf("│ Linux. The full version would use libcurl for HTTP requests. │\n");
printf("│ │\n");
printf("│ To get weather information on Linux: │\n");
printf("│ │\n");
printf("│ 1. Using curl: │\n");
printf("│ curl wttr.in/[city] │\n");
printf("│ │\n");
printf("│ 2. Using wget: │\n");
printf("│ wget -O - wttr.in/[city] │\n");
printf("│ │\n");
printf("│ For example: │\n");
printf("│ curl wttr.in/London │\n");
printf("│ curl wttr.in/\"New York\" │\n");
printf("│ │\n");
printf("│ For a shorter format: │\n");
printf("│ curl wttr.in/London?format=3 │\n");
printf("│ │\n");
printf("├─────────────────────────────────────────────────────────────┤\n");
// Check if a city was provided as an argument
if (args[1]) {
char command[256];
// Use wttr.in for a simple demonstration
snprintf(command, sizeof(command), "curl -s wttr.in/%s?format=3", args[1]);
printf("│ Running: %-47s │\n", command);
printf("├─────────────────────────────────────────────────────────────┤\n");
printf("│ Result: │\n");
printf("│ │\n");
// Execute the command
FILE *fp = popen(command, "r");
if (fp) {
char buffer[256];
if (fgets(buffer, sizeof(buffer), fp)) {
// Print the result with padding
printf("│ %-53s │\n", buffer);
} else {
printf("│ No result returned │\n");
}
pclose(fp);
} else {
printf("│ Error executing command │\n");
}
printf("│ │\n");
}
printf("╰─────────────────────────────────────────────────────────────╯\n\n");
return 1;
}

8
weather.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef WEATHER_H
#define WEATHER_H
#include "common.h"
int lsh_weather(char **args);
#endif // WEATHER_H

BIN
weather.o Normal file

Binary file not shown.