first commit
This commit is contained in:
22
Makefile
Normal file
22
Makefile
Normal 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
488
aliases.c
Normal 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
63
aliases.h
Normal 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
|
||||
168
autocorrect.c
Normal file
168
autocorrect.c
Normal 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
50
autocorrect.h
Normal 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
BIN
autocorrect.o
Normal file
Binary file not shown.
409
bookmarks.c
Normal file
409
bookmarks.c
Normal 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
57
bookmarks.h
Normal 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
BIN
bookmarks.o
Normal file
Binary file not shown.
764
builtins.c
Normal file
764
builtins.c
Normal 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
100
builtins.h
Normal 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
BIN
builtins.o
Normal file
Binary file not shown.
115
common.h
Normal file
115
common.h
Normal 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
306
countdown_timer.c
Normal 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
57
countdown_timer.h
Normal 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
BIN
countdown_timer.o
Normal file
Binary file not shown.
324
favorite_cities.c
Normal file
324
favorite_cities.c
Normal 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
50
favorite_cities.h
Normal 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
BIN
favorite_cities.o
Normal file
Binary file not shown.
510
filters.c
Normal file
510
filters.c
Normal 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
66
filters.h
Normal 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
|
||||
571
fzf_native.c
Normal file
571
fzf_native.c
Normal 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
57
fzf_native.h
Normal 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
BIN
fzf_native.o
Normal file
Binary file not shown.
154
git_integration.c
Normal file
154
git_integration.c
Normal 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
43
git_integration.h
Normal 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
BIN
git_integration.o
Normal file
Binary file not shown.
54
grep.c
Normal file
54
grep.c
Normal 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
28
grep.h
Normal 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
|
||||
1039
line_reader.c
Normal file
1039
line_reader.c
Normal file
File diff suppressed because it is too large
Load Diff
57
line_reader.h
Normal file
57
line_reader.h
Normal 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
BIN
line_reader.o
Normal file
Binary file not shown.
26
main.c
Normal file
26
main.c
Normal 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
mingw-get-setup.exe
Normal file
BIN
mingw-get-setup.exe
Normal file
Binary file not shown.
502
persistent_history.c
Normal file
502
persistent_history.c
Normal 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]", ×tamp, 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
85
persistent_history.h
Normal 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
BIN
persistent_history.o
Normal file
Binary file not shown.
113
ps_command.c
Normal file
113
ps_command.c
Normal 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
BIN
ps_command.o
Normal file
Binary file not shown.
0
rg_selection.txt
Normal file
0
rg_selection.txt
Normal file
746
ripgrep.c
Normal file
746
ripgrep.c
Normal 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
26
ripgrep.h
Normal 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
|
||||
591
shell.c
Normal file
591
shell.c
Normal 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);
|
||||
}
|
||||
125
shell.h
Normal file
125
shell.h
Normal 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
|
||||
479
structured_data.c
Normal file
479
structured_data.c
Normal 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
70
structured_data.h
Normal 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
BIN
structured_data.o
Normal file
Binary file not shown.
460
tab_complete.c
Normal file
460
tab_complete.c
Normal 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(¤t_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(¤t_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
58
tab_complete.h
Normal 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
BIN
tab_complete.o
Normal file
Binary file not shown.
481
themes.c
Normal file
481
themes.c
Normal 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(¤t_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(¤t_theme, &default_theme, sizeof(ShellTheme));
|
||||
return 1;
|
||||
} else if (strcmp(theme_name, "rose-pine") == 0) {
|
||||
memcpy(¤t_theme, &rose_pine_theme, sizeof(ShellTheme));
|
||||
return 1;
|
||||
} else if (strcmp(theme_name, "catppuccin-mocha") == 0) {
|
||||
memcpy(¤t_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 ¤t_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
92
themes.h
Normal 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
|
||||
97
timer.c
Normal file
97
timer.c
Normal 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
38
timer.h
Normal 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
|
||||
75
weather.c
Normal file
75
weather.c
Normal 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
8
weather.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef WEATHER_H
|
||||
#define WEATHER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
int lsh_weather(char **args);
|
||||
|
||||
#endif // WEATHER_H
|
||||
Reference in New Issue
Block a user