90s/rush.c

398 lines
13 KiB
C
Raw Normal View History

2024-01-31 02:02:32 +01:00
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
2024-02-01 20:18:52 +01:00
#include <linux/limits.h>
2024-01-31 02:02:32 +01:00
#include <time.h>
#include <stdbool.h>
2024-02-01 20:18:52 +01:00
#include <signal.h>
2024-01-31 02:02:32 +01:00
#include "color.h"
#include "constants.h"
#include "history.h"
#include "commands.h"
2024-01-31 02:02:32 +01:00
void quit_sig(int sig) {
2024-02-01 20:18:52 +01:00
exit(0);
2024-01-31 02:02:32 +01:00
}
void change_terminal_attribute(int option) {
static struct termios oldt, newt;
tcgetattr(STDIN_FILENO, &oldt);
if (option) {
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO); // allows getchar without pressing enter key and echoing the character twice
tcsetattr(STDIN_FILENO, TCSANOW, &newt); // set settings to stdin
} else {
tcsetattr(STDIN_FILENO, TCSANOW, &oldt); // restore to old settings
}
}
char **setup_path_variable() {
char *envpath = getenv("PATH");
2024-02-06 19:56:52 +01:00
if (envpath == NULL) {
fprintf(stderr, "rush: PATH environment variable is missing\n");
exit(EXIT_FAILURE);
}
2024-01-31 02:02:32 +01:00
char *path_cpy = malloc(sizeof(char) * (strlen(envpath) + 1));
char *path = malloc(sizeof(char) * (strlen(envpath) + 1));
2024-02-06 19:56:52 +01:00
if (path_cpy == NULL || path == NULL) {
fprintf(stderr, "rush: Error allocating memory\n");
exit(EXIT_FAILURE);
}
2024-01-31 02:02:32 +01:00
strcpy(path_cpy, envpath);
strcpy(path, envpath);
int path_count = 0;
while (*path_cpy != '\0') {
// count number of : to count number of elements
if (*path_cpy == ':') {
path_count++;
}
path_cpy++;
}
path_count += 2; // adding one to be correct and one for terminator
char **paths = malloc(sizeof(char *) * path_count);
2024-02-06 19:56:52 +01:00
if (paths == NULL) {
fprintf(stderr, "rush: Error allocating memory\n");
exit(EXIT_FAILURE);
}
2024-01-31 02:02:32 +01:00
char *token = strtok(path, ":");
int counter = 0;
while (token != NULL) {
paths[counter] = token; // set element to the pointer of start of path
token = strtok(NULL, ":");
counter++;
}
paths[counter] = NULL;
return paths;
}
bool find_command(char **paths, char *command) {
if (strcmp(command, "") == 0) {
return false;
}
while (*paths != NULL) {
char current_path[PATH_MAX];
current_path[0] = '\0';
2024-01-31 02:02:32 +01:00
sprintf(current_path, "%s/%s", *paths, command);
if (access(current_path, X_OK) == 0) {
// command is executable
return true;
} else {
if (is_builtin(command)) {
return true;
}
2024-01-31 02:02:32 +01:00
}
paths++;
}
return false;
}
void shiftleft(int chars) {
printf("\033[%dD", chars);
}
void shiftright(int chars) {
printf("\033[%dC", chars);
}
char *readline(char **paths) {
2024-01-31 02:02:32 +01:00
int bufsize = RL_BUFSIZE;
int position = 0;
char *buffer = malloc(sizeof(char) * bufsize);
bool moved = false;
bool backspaced = false;
bool navigated = false;
bool insertatmiddle = false;
2024-01-31 02:02:32 +01:00
if (!buffer) {
fprintf(stderr, "rush: Error allocating memory\n");
2024-01-31 02:02:32 +01:00
exit(EXIT_FAILURE);
}
buffer[0] = '\0';
while (1) {
int c = getchar(); // read a character
2024-01-31 02:02:32 +01:00
int buf_len = strlen(buffer);
2024-01-31 02:02:32 +01:00
// check each character user has input
switch (c) {
case EOF:
exit(EXIT_SUCCESS);
case 10: {
// enter/new line feed
2024-02-02 17:52:21 +01:00
if (buf_len == 0) {
break;
}
2024-01-31 02:02:32 +01:00
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
2024-01-31 02:02:32 +01:00
save_command_history(buffer);
position = 0;
2024-01-31 02:02:32 +01:00
return buffer;
}
2024-01-31 02:02:32 +01:00
case 127: // backspace
if (buf_len >= 1) {
position--;
for (int i = position; i < buf_len; i++) {
// shift the buffer
buffer[i] = buffer[i + 1];
}
backspaced = true;
moved = false;
2024-01-31 02:02:32 +01:00
}
break;
case 27: // arrow keys comes at three characters, 27, 91, then 65-68
if (getchar() == 91) {
int arrow_key = getchar();
if (arrow_key == 65) { // up
// read history file and fill prompt with latest command
char *last_command = read_command(1);
if (last_command != NULL) {
strcpy(buffer, last_command);
navigated = true;
2024-01-31 02:02:32 +01:00
}
moved = false;
2024-01-31 02:02:32 +01:00
break;
} else if (arrow_key == 66) { // down
char *last_command = read_command(0);
if (last_command != NULL) {
strcpy(buffer, last_command);
navigated = true;
2024-01-31 02:02:32 +01:00
}
moved = false;
2024-01-31 02:02:32 +01:00
break;
} else if (arrow_key == 67) { // right
2024-02-06 19:56:52 +01:00
if (position < buf_len) {
shiftright(1);
2024-02-06 19:56:52 +01:00
position++;
}
moved = true;
break;
2024-01-31 02:02:32 +01:00
} else if (arrow_key == 68) { // left
if (position >= 1) {
shiftleft(1);
2024-02-06 19:56:52 +01:00
position--;
}
moved = true;
break;
}
}
2024-01-31 02:02:32 +01:00
default:
if (c > 31 && c < 127) {
2024-02-06 19:56:52 +01:00
if (position == buf_len) {
// Append character to the end of the buffer
buffer[buf_len] = c;
buffer[buf_len + 1] = '\0';
moved = false;
navigated = false;
2024-02-06 19:56:52 +01:00
} else {
// Insert character at the current position
memmove(&buffer[position + 1], &buffer[position], buf_len - position + 1);
buffer[position] = c;
shiftright(1);
insertatmiddle = true;
2024-02-06 19:56:52 +01:00
}
position++;
2024-01-31 02:02:32 +01:00
}
}
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
}
}
buf_len = strlen(buffer);
if (moved) {
moved = false;
continue;
}
if (!navigated) {
if (position != buf_len) {
// not at normal place
if (backspaced) {
shiftleft(position + 1);
} else {
shiftleft(position); // move cursor to the beginning
}
} else if (buf_len > 1) {
if (backspaced) {
shiftleft(buf_len + 1); // move cursor to the beginning
} else {
shiftleft(buf_len - 1); // move cursor to the beginning
}
} else if (buf_len == 1) {
if (backspaced) {
shiftleft(2);
}
} else if (buf_len == 0) {
if (backspaced) {
shiftleft(1);
}
}
printf("\033[K"); // clear line to the right of cursor
} else {
navigated = false;
}
2024-01-31 02:02:32 +01:00
char *cmd_part = strchr(buffer, ' ');
char *command_without_arg = NULL;
int cmd_len = 0;
2024-01-31 02:02:32 +01:00
bool valid;
2024-01-31 02:02:32 +01:00
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);
}
2024-01-31 02:02:32 +01:00
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';
2024-01-31 02:02:32 +01:00
valid = find_command(paths, cmd);
free(cmd);
2024-01-31 02:02:32 +01:00
} else {
valid = find_command(paths, buffer);
}
2024-01-31 02:02:32 +01:00
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
}
2024-01-31 02:02:32 +01:00
} 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
}
2024-01-31 02:02:32 +01:00
}
free(command_without_arg);
if (backspaced) {
if (buf_len != position) {
shiftleft(buf_len - position);
}
backspaced = false;
}
if (insertatmiddle) {
shiftleft(buf_len - position); // move cursor back to where it was
insertatmiddle = false;
2024-01-31 02:02:32 +01:00
}
2024-01-31 02:02:32 +01:00
// If we have exceeded the buffer, reallocate.
if ((buf_len + 1) >= bufsize) {
bufsize += RL_BUFSIZE;
buffer = realloc(buffer, bufsize);
if (!buffer) {
fprintf(stderr, "rush: Error allocating memory\n");
2024-01-31 02:02:32 +01:00
exit(EXIT_FAILURE);
}
}
}
}
// split line into arguments
char **argsplit(char *line) {
2024-01-31 02:02:32 +01:00
int bufsize = TOK_BUFSIZE, position = 0;
char **tokens = malloc(bufsize * sizeof(char*));
char *token;
if (!tokens) {
2024-02-06 19:56:52 +01:00
fprintf(stderr, "rush: Error allocating memory\n");
2024-01-31 02:02:32 +01:00
exit(EXIT_FAILURE);
}
token = strtok(line, TOK_DELIM);
while (token != NULL) {
tokens[position] = token;
position++;
if (position >= bufsize) {
bufsize += TOK_BUFSIZE;
tokens = realloc(tokens, bufsize * sizeof(char*));
if (!tokens) {
2024-02-06 19:56:52 +01:00
fprintf(stderr, "rush: Error allocating memory\n");
2024-01-31 02:02:32 +01:00
exit(EXIT_FAILURE);
}
}
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++;
2024-01-31 02:02:32 +01:00
tokens[position] = NULL;
return tokens;
}
// continously prompt for command and execute it
void command_loop(char **paths) {
char *line;
char **args;
int status = 1;
while (status) {
time_t t = time(NULL);
struct tm* current_time = localtime(&t); // get current time
char timestr[256];
char cwdstr[PATH_MAX];
if (strftime(timestr, sizeof(timestr), "[%H:%M:%S]", current_time) == 0) { // format time string
return;
}
if (getcwd(cwdstr, sizeof(cwdstr)) == NULL) { // get current working directory
return;
}
char time[256];
strcpy(time, timestr);
color_text(time, lavender); // lavender colored time string
2024-02-06 19:56:52 +01:00
char *cwd = malloc(sizeof(char) * (PATH_MAX + 2));
2024-01-31 02:02:32 +01:00
sprintf(cwd, "[%s]", cwdstr);
color_text(cwd, pink); // pink colored current directory
char arrow[32] = "»";
color_text(arrow, blue);
printf("%s %s %s ", time, cwd, arrow);
line = readline(paths);
args = argsplit(line);
status = execute(args);
2024-01-31 02:02:32 +01:00
free(line);
free(args);
free(cwd);
};
}
int main(int argc, char **argv) {
// setup
signal(SIGINT, quit_sig);
signal(SIGTERM, quit_sig);
signal(SIGQUIT, quit_sig);
2024-01-31 02:02:32 +01:00
check_history_file();
char **paths = setup_path_variable();
change_terminal_attribute(1); // turn off echoing and disabling getchar requires pressing enter key to return
command_loop(paths);
// cleanup
free(paths);
2024-01-31 02:02:32 +01:00
change_terminal_attribute(0); // change back to default settings
return EXIT_SUCCESS;
}