feat: line editor
This commit is contained in:
4
Makefile
4
Makefile
@@ -9,11 +9,11 @@ ifeq ($(RELEASE),true)
|
|||||||
CFLAGS += -O3
|
CFLAGS += -O3
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean release-$(RELEASE)
|
||||||
|
|
||||||
all: dist/msh
|
all: dist/msh
|
||||||
|
|
||||||
dist/msh: $(SOURCES) dist
|
dist/msh: $(SOURCES) dist release-$(RELEASE)
|
||||||
$(CC) -o $@ $(SOURCES) $(CFLAGS)
|
$(CC) -o $@ $(SOURCES) $(CFLAGS)
|
||||||
|
|
||||||
dist:
|
dist:
|
||||||
|
|||||||
216
src/msh.c
216
src/msh.c
@@ -7,6 +7,7 @@
|
|||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <sys/utsname.h>
|
#include <sys/utsname.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
|
|
||||||
#define ESC \x1B
|
#define ESC \x1B
|
||||||
@@ -35,13 +36,112 @@ void pretty_path_home(char** path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define ANSI_UNKNOWN -1
|
||||||
|
#define ANSI_CURSOR_LEFT 0
|
||||||
|
#define ANSI_CURSOR_RIGHT 1
|
||||||
|
#define ANSI_CURSOR_UP 2
|
||||||
|
#define ANSI_CURSOR_DOWN 3
|
||||||
|
#define ANSI_DELETE_WORD 4
|
||||||
|
#define ANSI_CURSOR_LEFT_WORD 5
|
||||||
|
#define ANSI_CURSOR_RIGHT_WORD 6
|
||||||
|
#define ANSI_CURSOR_UP_WORD 7
|
||||||
|
#define ANSI_CURSOR_DOWN_WORD 8
|
||||||
|
|
||||||
|
|
||||||
|
int read_ansi() {
|
||||||
|
switch (getchar()) {
|
||||||
|
case '[':
|
||||||
|
switch(getchar()) {
|
||||||
|
case 'A':
|
||||||
|
return ANSI_CURSOR_UP;
|
||||||
|
case 'B':
|
||||||
|
return ANSI_CURSOR_DOWN;
|
||||||
|
case 'C':
|
||||||
|
return ANSI_CURSOR_RIGHT;
|
||||||
|
case 'D':
|
||||||
|
return ANSI_CURSOR_LEFT;
|
||||||
|
case '1':
|
||||||
|
switch(getchar()) {
|
||||||
|
case ';':
|
||||||
|
switch(getchar()) {
|
||||||
|
// CTRL
|
||||||
|
case '5':
|
||||||
|
// ALT
|
||||||
|
case '3':
|
||||||
|
switch (getchar()) {
|
||||||
|
case 'C':
|
||||||
|
return ANSI_CURSOR_RIGHT_WORD;
|
||||||
|
case 'D':
|
||||||
|
return ANSI_CURSOR_LEFT_WORD;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 127:
|
||||||
|
case 8:
|
||||||
|
return ANSI_DELETE_WORD;
|
||||||
|
default:
|
||||||
|
return ANSI_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ANSI_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Find the length of the prefix until the last occurrence of a rejected character
|
||||||
|
size_t strnrcspn(char* str, size_t n, char* reject) {
|
||||||
|
size_t i;
|
||||||
|
int seenaccept = 0;
|
||||||
|
|
||||||
|
for (i = n - 1; i > 0; i--) {
|
||||||
|
// If this is NOT a reject character
|
||||||
|
if(strchr(reject, str[i]) == NULL) {
|
||||||
|
seenaccept = 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
if (seenaccept == 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the length of the prefix until the first occurrence of a rejected character after an offset
|
||||||
|
size_t strnocspn(char* str, size_t n, size_t o, char* reject) {
|
||||||
|
size_t i;
|
||||||
|
int seenaccept = 0;
|
||||||
|
|
||||||
|
for (i = o; i < n; i++) {
|
||||||
|
// If this is NOT a reject character
|
||||||
|
if(strchr(reject, str[i]) == NULL) {
|
||||||
|
seenaccept = 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
if (seenaccept == 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
|
int retval = 0;
|
||||||
char* tokens[LINESIZE] = {};
|
char* tokens[LINESIZE] = {};
|
||||||
char** args;
|
char** args;
|
||||||
char line[LINESIZE] = {};
|
char line[LINESIZE] = {};
|
||||||
|
char prompt[LINESIZE] = {};
|
||||||
|
int linelen = 0;
|
||||||
char* tokenwork;
|
char* tokenwork;
|
||||||
char* token;
|
char* token;
|
||||||
char* newline;
|
|
||||||
int ntokens = 0;
|
int ntokens = 0;
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
@@ -63,15 +163,27 @@ int main(int argc, char** argv) {
|
|||||||
int result = uname(&uts);
|
int result = uname(&uts);
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
fprintf(stderr, "Error: %s\n", strerror(errno));
|
fprintf(stderr, "Error: %s\n", strerror(errno));
|
||||||
return errno;
|
retval = errno;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
host = uts.nodename;
|
host = uts.nodename;
|
||||||
|
|
||||||
|
|
||||||
|
struct termios term, orig_term;
|
||||||
|
tcgetattr(fileno(stdin), &term);
|
||||||
|
orig_term = term;
|
||||||
|
|
||||||
|
// disable echo and icanon (don't wait for newline)
|
||||||
|
term.c_lflag &= ~(ECHO | ICANON);
|
||||||
|
|
||||||
|
|
||||||
|
// Main loop
|
||||||
while(1) {
|
while(1) {
|
||||||
char* result = getcwd(pwd, LINESIZE - 1);
|
char* result = getcwd(pwd, LINESIZE - 1);
|
||||||
if (result == NULL) {
|
if (result == NULL) {
|
||||||
return 1;
|
retval = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
pwd_disp = pwd;
|
pwd_disp = pwd;
|
||||||
@@ -81,19 +193,95 @@ int main(int argc, char** argv) {
|
|||||||
if (laststatus != 0) {
|
if (laststatus != 0) {
|
||||||
fprintf(stdout, "❌");
|
fprintf(stdout, "❌");
|
||||||
}
|
}
|
||||||
fprintf(stdout, "%s@%s %s$ ", user, host, pwd_disp);
|
snprintf(prompt, LINESIZE, "%s@%s %s$ ", user, host, pwd_disp);
|
||||||
char* fgets_res = fgets(line, LINESIZE, stdin);
|
printf("%s", prompt);
|
||||||
|
|
||||||
if (fgets_res == NULL) {
|
|
||||||
|
// prepare for input
|
||||||
|
linelen = 0;
|
||||||
|
int readc;
|
||||||
|
int cursor = 0;
|
||||||
|
|
||||||
|
tcsetattr(fileno(stdin), 0, &term);
|
||||||
|
|
||||||
|
memset(line, 0, LINESIZE);
|
||||||
|
|
||||||
|
while ((readc = getchar()) != '\n') {
|
||||||
|
if (readc == EOF) {
|
||||||
printf("^D\n");
|
printf("^D\n");
|
||||||
return 0;
|
retval = 0;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
newline = strrchr(line, '\n');
|
if (readc == '\x1b') {
|
||||||
if (newline != NULL) {
|
int ansi = read_ansi();
|
||||||
newline[0] = 0;
|
if (ansi == ANSI_CURSOR_LEFT) {
|
||||||
|
if (cursor > 0) {
|
||||||
|
cursor--;
|
||||||
|
}
|
||||||
|
} else if (ansi == ANSI_CURSOR_RIGHT) {
|
||||||
|
if (cursor < linelen) {
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
} else if (ansi == ANSI_DELETE_WORD) {
|
||||||
|
if (cursor > 0) {
|
||||||
|
size_t index = strnrcspn(line, cursor, " \t");
|
||||||
|
memmove(line + index, line + cursor, LINESIZE - cursor);
|
||||||
|
linelen -= cursor - index;
|
||||||
|
cursor = index;
|
||||||
|
}
|
||||||
|
} else if (ansi == ANSI_CURSOR_LEFT_WORD) {
|
||||||
|
size_t index = strnrcspn(line, cursor, " \t");
|
||||||
|
cursor = index;
|
||||||
|
} else if (ansi == ANSI_CURSOR_RIGHT_WORD) {
|
||||||
|
size_t index = strnocspn(line, linelen, cursor, " \t");
|
||||||
|
cursor = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
goto print_prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// backspace
|
||||||
|
if (readc == 127) {
|
||||||
|
if (cursor > 0) {
|
||||||
|
cursor--;
|
||||||
|
linelen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor == linelen) {
|
||||||
|
line[cursor] = 0;
|
||||||
|
} else {
|
||||||
|
memmove(line + cursor, line + cursor + 1, LINESIZE - (cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't want to change the line
|
||||||
|
goto print_prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor != linelen) {
|
||||||
|
memmove(line + cursor + 1, line + cursor, LINESIZE - cursor - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
line[cursor] = readc;
|
||||||
|
cursor++;
|
||||||
|
linelen++;
|
||||||
|
|
||||||
|
print_prompt:
|
||||||
|
// Manual echo
|
||||||
|
printf("\x1b[2K\x1b[0G%s%s", prompt, line);
|
||||||
|
for (int j = 0; j < linelen - cursor; j++) {
|
||||||
|
printf("\x1b[D");
|
||||||
|
}
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
putc('\n', stdout);
|
||||||
|
|
||||||
|
tcsetattr(fileno(stdin), 0, &orig_term);
|
||||||
|
|
||||||
|
line[linelen] = 0;
|
||||||
|
|
||||||
|
// Split into tokens
|
||||||
for (tokenwork = line, i = 0; ; tokenwork = NULL, i++) {
|
for (tokenwork = line, i = 0; ; tokenwork = NULL, i++) {
|
||||||
token = strtok(tokenwork, " \t");
|
token = strtok(tokenwork, " \t");
|
||||||
if (token == NULL) {
|
if (token == NULL) {
|
||||||
@@ -114,7 +302,8 @@ int main(int argc, char** argv) {
|
|||||||
// Shell builtins
|
// Shell builtins
|
||||||
if (strcmp("exit", tokens[0]) == 0) {
|
if (strcmp("exit", tokens[0]) == 0) {
|
||||||
printf("Goodbye!\n");
|
printf("Goodbye!\n");
|
||||||
return 0;
|
retval = 0;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp("cd", tokens[0]) == 0) {
|
if (strcmp("cd", tokens[0]) == 0) {
|
||||||
@@ -134,5 +323,10 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
tcsetattr(fileno(stdin), 0, &orig_term);
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user