90s

Minimalist, customizable shell written in C99 with syntax highlighting
git clone https://codeberg.org/night0721/90s
Log | Files | Refs | README | LICENSE

commit 0a8d3ca51c03ed6502602e3e0082841fa99cc455
parent 9b27891d43dbd2f5ee646456efbf3bf309c60f11
Author: night0721 <[email protected]>
Date:   Thu,  8 Feb 2024 19:49:11 +0000

history navigation by !!

Diffstat:
Mcommands.c | 20++++++++++++++------
Mhistory.c | 83+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mhistory.h | 4++--
Mrush.c | 188+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mrush.h | 2+-
5 files changed, 180 insertions(+), 117 deletions(-)

diff --git a/commands.c b/commands.c @@ -6,11 +6,12 @@ #include <errno.h> #include <sys/wait.h> -#include "commands.h" #include "constants.h" #include "history.h" #include "rush.h" +int execute(char **args); + // Function declarations for builtin commands int cd(char **args); int help(char **args); @@ -81,7 +82,7 @@ int quit(char **args) { } int history(char **args) { - char **history = get_all_history(); + char **history = get_all_history(true); for (int i = 0; history[i] != NULL; ++i) { printf("%s\n", history[i]); @@ -158,17 +159,24 @@ int execute(char **args) { return 1; } + save_command_history(args); // save command to history file + // prioritize builtin commands for (int i = 0; i < num_builtins(); i++) { if (strcmp(args[0], builtin_cmds[i]) == 0) { return (*builtin_func[i])(args); } } - /* - while (*args) { - + + int num_arg = 0; + + while (*args != NULL) { + num_arg++; // count number of args + args++; } - */ + + args -= num_arg; + pid_t pid; int status; diff --git a/history.c b/history.c @@ -1,9 +1,11 @@ #include <stdio.h> #include <stdlib.h> +#include <stdbool.h> #include <string.h> #include <unistd.h> #include <linux/limits.h> +#include "history.h" #include "constants.h" FILE *history_file; @@ -26,8 +28,8 @@ void check_history_file() { int path_len = env_home_len + histfilename_len + 2; // 2 for slash and null byte histfile_path = malloc(sizeof(char) * path_len); if (histfile_path == NULL) { - fprintf(stderr, "rush: Error allocating memory\n"); - exit(EXIT_FAILURE); + fprintf(stderr, "rush: Error allocating memory\n"); + exit(EXIT_FAILURE); } histfile_path[0] = '\0'; // initialise string // concatenate home and history file name to a path @@ -45,36 +47,26 @@ void check_history_file() { } } -void save_command_history(char *command) { +void save_command_history(char **args) { history_file = fopen(histfile_path, "a+"); if (history_file == NULL) { fprintf(stderr, "rush: Error opening history file\n"); exit(EXIT_FAILURE); } - int cmd_len = strlen(command); - command[cmd_len] = '\n'; // put new line feed to split commands + char cmd[RL_BUFSIZE]; + cmd[0] = '\0'; + for (int i = 0; args[i] != NULL; ++i) { + strcat(cmd, args[i]); + strcat(cmd, " "); + } + int cmd_len = strlen(cmd); + cmd[cmd_len] = '\n'; // put new line feed to split commands // ptr to first obj, size of each obj, number of obj, file ptr - fwrite(command, sizeof(char), cmd_len + 1, history_file); + fwrite(cmd, sizeof(char), cmd_len + 1, history_file); fclose(history_file); } char *read_command(int direction) { - history_file = fopen(histfile_path, "rb"); // read binary mode - if (history_file == NULL) { - fprintf(stderr, "rush: Error opening history file\n"); - exit(EXIT_FAILURE); - } - // normal bufsize is 1024, we serach for 1025 bytes for new line feed - int search_len = RL_BUFSIZE + 1; - char search[search_len]; - fseek(history_file, -search_len, SEEK_END); // go back 1025 characters from end of file - int count = fread(search, 1, search_len - 1, history_file); // try to read 1025 characters from file, returning count number of bytes - search[count] = '\0'; - char *last_nlf = strrchr(search, '\n'); // locate last occurence of \n in a searching string - if (last_nlf == NULL) { - // no history - return NULL; - } if (direction == 1) { // up cmd_count++; } else { // down @@ -84,24 +76,18 @@ char *read_command(int direction) { cmd_count--; } } - for (int i = 0; i < cmd_count; i++) { - search[last_nlf - search] = '\0'; // terminate string earlier to find second last \n, search points to first char and last_nlf is last \n, difference is the index of \n - last_nlf = strrchr(search, '\n'); // call strrchr 2 times to get second last new line feed in search string as every life is new line feed - if ((last_nlf - search) == (strchr(search, '\n') - search)) { - // check if the first \n is the last \n we searching for, if yes it is first command - cmd_count--; - search[last_nlf - search] = '\0'; // terminate string earlier to find second last \n, search points to first char and last_nlf is last \n, difference is the index of \n - char *first_cmd = malloc(sizeof(char) * (last_nlf - search) + 1); - if (first_cmd == NULL) { - fprintf(stderr, "rush: Error allocating memory\n"); - exit(EXIT_FAILURE); - } - strcpy(first_cmd, search); - return first_cmd; - } + char **history = get_all_history(false); + int num_history = 0; + while (*history != NULL) { + num_history++; + history++; } - fclose(history_file); - return last_nlf + 1; // return the string from the new line feed + if (cmd_count > num_history) { + cmd_count = num_history; + return NULL; + } + history -= num_history; + return history[num_history - cmd_count]; } int is_duplicate(char **history, int line_count, char *line) { @@ -113,7 +99,7 @@ int is_duplicate(char **history, int line_count, char *line) { return 0; } -char **get_all_history() { +char **get_all_history(bool check) { history_file = fopen(histfile_path, "r"); if (history_file == NULL) { fprintf(stderr, "rush: Error opening history file\n"); @@ -130,7 +116,20 @@ char **get_all_history() { while (fgets(buffer, sizeof(buffer), history_file) != NULL) { buffer[strcspn(buffer, "\n")] = '\0'; - if (!is_duplicate(history, line_count, buffer)) { + if (check) { + if (!is_duplicate(history, line_count, buffer)) { + history[line_count] = strdup(buffer); + if (history[line_count] == NULL) { + fprintf(stderr, "Error allocating memory\n"); + exit(EXIT_FAILURE); + } + line_count++; + if (line_count >= MAX_HISTORY) { + fprintf(stderr, "Maximum number of lines reached.\n"); + exit(EXIT_FAILURE); + } + } + } else { history[line_count] = strdup(buffer); if (history[line_count] == NULL) { fprintf(stderr, "Error allocating memory\n"); @@ -141,7 +140,7 @@ char **get_all_history() { fprintf(stderr, "Maximum number of lines reached.\n"); exit(EXIT_FAILURE); } - } + } } fclose(history_file); diff --git a/history.h b/history.h @@ -1,9 +1,9 @@ #ifndef HISTORY_H_ #define HISTORY_H_ -void save_command_history(char *command); +void save_command_history(char **args); void check_history_file(); char *read_command(int direction); -char **get_all_history(); +char **get_all_history(bool check); #endif diff --git a/rush.c b/rush.c @@ -69,7 +69,7 @@ char **setup_path_variable() { } bool find_command(char **paths, char *command) { - if (strcmp(command, "") == 0) { + if (strncmp(command, "", 1) == 0) { return false; } while (*paths != NULL) { @@ -97,6 +97,56 @@ void shiftright(int chars) { printf("\033[%dC", chars); } +void clearline() { + printf("\033[K"); // clear line to the right of cursor +} + +void highlight(char *buffer, char **paths) { + char *cmd_part = strchr(buffer, ' '); + char *command_without_arg = NULL; + int cmd_len = 0; + bool valid; + + if (cmd_part != NULL) { + cmd_len = cmd_part - buffer; + char *cmd = malloc(sizeof(char) * (cmd_len + 1)); + command_without_arg = malloc(sizeof(char) * (cmd_len + 1)); + if (cmd == NULL || command_without_arg == NULL) { + fprintf(stderr, "rush: Error allocating memory\n"); + exit(EXIT_FAILURE); + } + for (int i = 0; i < (cmd_part - buffer); i++) { + cmd[i] = buffer[i]; + } + strcpy(command_without_arg, cmd); + cmd[cmd_len] = '\0'; + command_without_arg[cmd_len] = '\0'; + valid = find_command(paths, cmd); + free(cmd); + } else { + valid = find_command(paths, buffer); + } + + if (valid) { + if (command_without_arg != NULL) { + buffer += cmd_len; + printf("\x1b[38;2;137;180;250m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments + buffer -= cmd_len; + } else { + printf("\x1b[38;2;137;180;250m%s\x1b[0m", buffer); // print green as valid command + } + } else { + if (command_without_arg != NULL) { + buffer += cmd_len; + printf("\x1b[38;2;243;139;168m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments + buffer -= cmd_len; + } else { + printf("\x1b[38;2;243;139;168m%s\x1b[0m", buffer); // print red as invalid command + } + } + free(command_without_arg); +} + char *readline(char **paths) { int bufsize = RL_BUFSIZE; int position = 0; @@ -106,6 +156,7 @@ char *readline(char **paths) { bool backspaced = false; bool navigated = false; bool insertatmiddle = false; + bool replaced = false; if (!buffer) { fprintf(stderr, "rush: Error allocating memory\n"); exit(EXIT_FAILURE); @@ -125,15 +176,39 @@ char *readline(char **paths) { if (buf_len == 0) { break; } - buffer[buf_len] = '\0'; - // clear all characters after the command - for (int start = buf_len + 1; buffer[start] != '\0'; start++) { - buffer[start] = '\0'; + // check if command includes !! + if (strstr(buffer, "!!") != NULL) { + char *last_command = read_command(1); + if (last_command != NULL) { + // replace !! with the last command + char *replace = strstr(buffer, "!!"); + int replace_len = strlen(replace); + int last_command_len = strlen(last_command); + int buffer_len = strlen(buffer); + if (last_command_len > replace_len) { + buffer = realloc(buffer, buffer_len + last_command_len - replace_len + 1); + if (!buffer) { + fprintf(stderr, "rush: Error allocating memory\n"); + exit(EXIT_FAILURE); + } + } + memmove(replace + last_command_len, replace + replace_len, buffer_len - (replace - buffer) - replace_len + 1); + memcpy(replace, last_command, last_command_len); + position += last_command_len - replace_len; + shiftright(last_command_len - replace_len); + replaced = true; + break; + } + } else { + buffer[buf_len] = '\0'; + // clear all characters after the command + for (int start = buf_len + 1; buffer[start] != '\0'; start++) { + buffer[start] = '\0'; + } + printf("\n"); // give space for response + position = 0; + return buffer; } - printf("\n"); // give space for response - save_command_history(buffer); - position = 0; - return buffer; } case 127: // backspace if (buf_len >= 1) { @@ -154,16 +229,16 @@ char *readline(char **paths) { char *last_command = read_command(1); if (last_command != NULL) { strcpy(buffer, last_command); - navigated = true; } + navigated = true; moved = false; break; } else if (arrow_key == 66) { // down char *last_command = read_command(0); if (last_command != NULL) { strcpy(buffer, last_command); - navigated = true; } + navigated = true; moved = false; break; } else if (arrow_key == 67) { // right @@ -200,18 +275,28 @@ char *readline(char **paths) { position++; } } + + buf_len = strlen(buffer); + + if (replaced) { + shiftleft(buf_len); + clearline(); + } + if (navigated && buf_len >= 1) { - if (position != 0) { - shiftleft(buf_len); // move cursor to the beginning - printf("\033[K"); // clear line to the right of cursor + if (position > 0) { + shiftleft(position); // move cursor to the beginning + clearline(); } + position = buf_len; } - buf_len = strlen(buffer); + if (moved) { moved = false; continue; } - if (!navigated) { + + if (!navigated && !replaced) { if (position != buf_len) { // not at normal place if (backspaced) { @@ -234,54 +319,13 @@ char *readline(char **paths) { shiftleft(1); } } - printf("\033[K"); // clear line to the right of cursor + clearline(); } else { navigated = false; } - char *cmd_part = strchr(buffer, ' '); - char *command_without_arg = NULL; - int cmd_len = 0; - bool valid; - - if (cmd_part != NULL) { - cmd_len = cmd_part - buffer; - char *cmd = malloc(sizeof(char) * (cmd_len + 1)); - command_without_arg = malloc(sizeof(char) * (cmd_len + 1)); - if (cmd == NULL || command_without_arg == NULL) { - fprintf(stderr, "rush: Error allocating memory\n"); - exit(EXIT_FAILURE); - } - for (int i = 0; i < (cmd_part - buffer); i++) { - cmd[i] = buffer[i]; - } - strcpy(command_without_arg, cmd); - cmd[cmd_len] = '\0'; - command_without_arg[cmd_len] = '\0'; - valid = find_command(paths, cmd); - free(cmd); - } else { - valid = find_command(paths, buffer); - } - if (valid) { - if (command_without_arg != NULL) { - buffer += cmd_len; - printf("\x1b[38;2;137;180;250m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments - buffer -= cmd_len; - } else { - printf("\x1b[38;2;137;180;250m%s\x1b[0m", buffer); // print green as valid command - } - } else { - if (command_without_arg != NULL) { - buffer += cmd_len; - printf("\x1b[38;2;243;139;168m%s\x1b[0m\x1b[38;2;255;255;255m%s\x1b[0m", command_without_arg, buffer); // print green as valid command, but only color the command, not the arguments - buffer -= cmd_len; - } else { - printf("\x1b[38;2;243;139;168m%s\x1b[0m", buffer); // print red as invalid command - } - } - free(command_without_arg); - + highlight(buffer, paths); + if (backspaced) { if (buf_len != position) { shiftleft(buf_len - position); @@ -291,8 +335,11 @@ char *readline(char **paths) { if (insertatmiddle) { shiftleft(buf_len - position); // move cursor back to where it was insertatmiddle = false; - } + if (replaced) { + replaced = false; + } + // If we have exceeded the buffer, reallocate. if ((buf_len + 1) >= bufsize) { bufsize += RL_BUFSIZE; @@ -332,15 +379,23 @@ char **argsplit(char *line) { token = strtok(NULL, TOK_DELIM); } - // makes ls and diff have color without user typing it - if (strcmp(tokens[0], "ls") == 0 || strcmp(tokens[0], "diff") == 0) { - tokens[position] = "--color=auto"; - } - position++; tokens[position] = NULL; return tokens; } +char **modifyargs(char **args) { + int num_arg = 0; + + // makes ls and diff have color without user typing it + if (strncmp(args[0], "ls", 2) == 0 || strncmp(args[0], "diff", 4) == 0) { + args[num_arg] = "--color=auto"; + num_arg++; + args[num_arg] = NULL; + } + + return args; +} + // continously prompt for command and execute it void command_loop(char **paths) { char *line; @@ -371,6 +426,7 @@ void command_loop(char **paths) { line = readline(paths); args = argsplit(line); + args = modifyargs(args); status = execute(args); free(line); diff --git a/rush.h b/rush.h @@ -1,6 +1,6 @@ #ifndef RUSH_H_ #define RUSH_H_ -char **argsplit(char * line); +char **argsplit(char *line); #endif