diff --git a/main.c b/main.c index 7967a57..933be2e 100644 --- a/main.c +++ b/main.c @@ -5,23 +5,142 @@ #include #include #include +#include #define MAX_SIZE 1024 +#define HISTORY_SIZE 5 -// int LogCommandHistory(char* buf, char** command_log, int n) { -// command_log[n] = buf; -// } +// RAW TERMINAL INPUT / LOGGING HISTORY IMPLEMENTATION ----------- -void PrintCurrentDir() { // pwd +// Global variable for termios +struct termios orig_termios; + +// Termios functions +void EnableRawMode(struct termios* orig_termios) { + struct termios raw = *orig_termios; + raw.c_lflag &= ~(ECHO | ICANON); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); +} + +void DisableRawMode(struct termios* orig_termios) { + tcsetattr(STDIN_FILENO, TCSAFLUSH, orig_termios); +} + +void DisableRawModeWrapper() { + DisableRawMode(&orig_termios); +} + +int LogCommandHistory(char* buf, char** command_log, int* n) { + int index = *n % HISTORY_SIZE; + int prev_index = (*n == 0) ? HISTORY_SIZE - 1 : (*n - 1) % HISTORY_SIZE; + + if (command_log[prev_index] != NULL && strcmp(buf, command_log[prev_index]) == 0) { + return 0; + } + if (command_log[index] != NULL) { + free(command_log[index]); + } + + command_log[index] = strdup(buf); + (*n)++; + + return 0; +} + +void FreeCommandHistory(char** command_log, int n) { + for (int i = 0; i < n; i++) { + free(command_log[i]); + } +} + +void ClearBufferContent(int length) { // Function to clear buffer + for (int i = 0; i < length; i++) { + printf("\b \b"); + } +} + +// Get raw input from terminal + arrow key history navigation +void GetRawInput(char* buf, char** command_log, int n) { + int index = n % HISTORY_SIZE; + int history_counter = 0; + int pos = 0; + char c; + + while (1) { + c = getchar(); + + if (c == '\n') { // Enter key + buf[pos] = '\0'; + printf("\n"); + return; + } else if (c == 127 || c == '\b') { // Backspace + if (pos > 0) { + pos--; + buf[pos] = '\0'; + printf("\b \b"); + } + } else if (c == '\033') { // Inputting arrow keys + getchar(); // Skip '[' character + char arrow = getchar(); + + if (arrow == 'A') { // Up arrow + int prev_index = (index == 0) ? HISTORY_SIZE - 1 : (index - 1) % HISTORY_SIZE; + if (command_log[prev_index] != NULL) { + ClearBufferContent(pos); // Clear current buffer + index = (index == 0) ? HISTORY_SIZE - 1 : index - 1; + history_counter++; + + strcpy(buf, command_log[index]); + printf("%s", buf); + pos = strlen(buf); + } + } + else if (arrow == 'B') { // Down arrow + if (history_counter > 0) { + index = (index + 1) % HISTORY_SIZE; + history_counter--; + ClearBufferContent(pos); + if (history_counter == 0) { + buf[0] = '\0'; // Clear buffer + pos = 0; + } else { + strcpy(buf, command_log[index]); + printf("%s", buf); + pos = strlen(buf); + } + } + } + } else { // Regular character + if (pos < MAX_SIZE - 1) { + buf[pos++] = c; + buf[pos] = '\0'; + printf("%c", c); + } + } + } +} + + +// MAIN MICROSHELL FUNCTIONS ----------- + +char* GetCurrentUserAndDir() { // return current user + working directory char cwd[MAX_SIZE]; if (getcwd(cwd, sizeof(cwd)) == NULL) { perror("getcwd() error"); + return NULL; } char* user = getenv("USER"); if (user == NULL) { perror("getenv() error"); + return NULL; } - printf("[%s@%s] $ ", user, cwd); + + size_t result_size = strlen(user) + strlen(cwd) + 7; // Allocate memory (include extra space for format characters) + char* result = (char*)malloc(result_size); + + snprintf(result, result_size, "[%s@%s] $ ", user, cwd); // Format the prompt string + + return result; } int ChangeDirectory(char** args) { // cd @@ -38,14 +157,12 @@ int ChangeDirectory(char** args) { // cd } void PrintHelp() { // help - printf("-------------------------------------------------"); + printf("-------------------------------------------------\n"); printf("C Microshell implementation by Maciej Życzyński\n"); - printf("\"exit\" - exit the program"); - printf("\"help\" - display this menu"); - printf("Other bash commands work accordingly in child processes"); - printf("Currently logged user / working directory:"); - PrintCurrentDir(); - printf("-------------------------------------------------"); + printf("\"exit\" - exit the program\n"); + printf("\"help\" - display this menu\n"); + printf("Other bash commands work accordingly in child processes\n"); + printf("-------------------------------------------------\n"); } int HandleBuiltInCommands(char* command, char** args) { // Manual implementation of commands unsupported by execvp() @@ -71,10 +188,9 @@ int HandleCommand(char* command, char** args) { // Execute commands supported by return 0; } -int ParseCommand(char* buf, char** command, char** args) { +int ParseCommand(char* buf, char** command, char** args) { // Parse input string into command and array of arguments char buf_cp[MAX_SIZE]; strcpy(buf_cp, buf); // Leave the original buffer (important for command history implementation) - buf_cp[strlen(buf_cp) - 1] = '\0'; // Remove newline from input string char* token = strtok(buf_cp, " "); int tok_count = 0; @@ -94,25 +210,39 @@ int ParseCommand(char* buf, char** command, char** args) { return 0; } + int main() { char buf[MAX_SIZE]; - char* command_log[MAX_SIZE]; + char* command; + char* args[MAX_SIZE]; int n = 0; + char* command_log[HISTORY_SIZE] = {NULL}; + + // Save original terminal settings and restore them on exit + if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) { + perror("tcgetattr() error"); + return 1; + } + atexit(DisableRawModeWrapper); // call DisableRawModeWrapper when program exits while (1) { - PrintCurrentDir(); + char* prompt = GetCurrentUserAndDir(); // Display prompt + if (prompt != NULL) { + printf("%s", prompt); + free(prompt); + } - char* command; - char* args[MAX_SIZE]; - fgets(buf, sizeof(buf), stdin); + EnableRawMode(&orig_termios); + GetRawInput(buf, command_log, n); + DisableRawMode(&orig_termios); - if (ParseCommand(buf, &command, args) < 0) { + + if (ParseCommand(buf, &command, args) < 0) { // Parse buffer input printf("Invalid command\n"); continue; } - // LogCommandHistory(buf, &command_log, n); - // n++; + LogCommandHistory(buf, command_log, &n); // Add buffer to logging history int handler_value = HandleBuiltInCommands(command, args); if (handler_value == 2) { // Exit command @@ -140,5 +270,6 @@ int main() { } } + FreeCommandHistory(command_log, n); return 0; } \ No newline at end of file