]> Trent Huber's Code - thus.git/commitdiff
Raw mode, arrow keys, history
authorTrent Huber <trentmhuber@gmail.com>
Tue, 13 May 2025 09:28:41 +0000 (05:28 -0400)
committerTrent Huber <trentmhuber@gmail.com>
Tue, 13 May 2025 09:28:41 +0000 (05:28 -0400)
README.md
external/cbs
main.c

index fa0c22bef337115bca43d8cf1e31d76df6cb1e7e..f0fae1a8ec7b97faa7d167814976f08c0c23b77b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,5 +2,5 @@
 
 ## Resources
 
-[TTY Demystified](http://www.linusakesson.net/programming/tty/)
-[Process Groups and Terminal Signaling](https://cs162.org/static/readings/ic221_s16_lec17.html)
+[TTY Demystified](http://www.linusakesson.net/programming/tty/)
+[Process Groups and Terminal Signaling](https://cs162.org/static/readings/ic221_s16_lec17.html)
index 51d2489cfcf1c245db14c3a9e7c9e40e6a550f4c..a2cb47c262bee92a2f7cab689e42ae8452c9e952 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 51d2489cfcf1c245db14c3a9e7c9e40e6a550f4c
+Subproject commit a2cb47c262bee92a2f7cab689e42ae8452c9e952
diff --git a/main.c b/main.c
index 6647dea0288b8bc46f4d0f28685bfefda97a25c9..f833d413fc79ec95a7c733f3c684ea15e3c59363 100644 (file)
--- a/main.c
+++ b/main.c
 #include <string.h>
 #include <sys/errno.h>
 #include <sys/wait.h>
+#include <termios.h>
 #include <unistd.h>
 
-void prompt(int sig) {
-   if (sig == SIGINT) printf("\n");
-   printf("%% ");
-   fflush(stdout);
+struct termios canonical;
+
+void ashexit(void) {
+   if (tcsetattr(STDIN_FILENO, TCSANOW, &canonical) == -1)
+       warn("Unable to return to initial terminal settings");
 }
 
 #define BUFLEN 1024
+#define HISTLEN 1000
+#define PROMPT "% "
+
+enum {
+   CTRLC = '\003',
+   CTRLD,
+   ESCAPE = '\033',
+   UP = 'A',
+   DOWN,
+   RIGHT,
+   LEFT,
+   BACKSPACE = '\177',
+};
+
+char *getinput(void) {
+   static char history[HISTLEN][BUFLEN], result[BUFLEN],
+               (*hb)[BUFLEN] = history, (*hc)[BUFLEN] = history,
+               (*ht)[BUFLEN] = history;
+   char *cursor, *end;
+   int c, i;
+
+   fputs(PROMPT, stdout);
+   *result = '\0';
+   for (end = cursor = result; (c = getchar()) != '\n';) {
+       if (c >= ' ' && c <= '~') {
+           if (end - result == BUFLEN - 1) continue;
+           memmove(cursor + 1, cursor, end - cursor);
+           *cursor++ = c;
+           *++end = '\0';
+
+           putchar(c);
+           fputs(cursor, stdout);
+           for (i = end - cursor; i > 0; --i) putchar('\b');
+       } else switch (c) {
+       case CTRLC:
+           puts("^C");
+           return NULL;
+       case CTRLD:
+           puts("^D");
+           exit(EXIT_SUCCESS);
+       case ESCAPE:
+           if ((c = getchar()) != '[') {
+               ungetc(c, stdin);
+               break;
+           }
+           switch ((c = getchar())) {
+           case UP:
+           case DOWN:
+               if (hc == (c == UP ? hb : ht)) continue;
+               if (strcmp(*hc, result) != 0) strcpy(*ht, result);
+
+               putchar('\r');
+               for (i = end - result + strlen(PROMPT); i > 0; --i) putchar(' ');
+               putchar('\r');
+               fputs(PROMPT, stdout);
+
+               hc = history + ((hc - history + (c == UP ? -1 : 1)) % HISTLEN + HISTLEN)
+                    % HISTLEN;
+               strcpy(result, *hc);
+               end = cursor = result + strlen(result);
+
+               fputs(result, stdout);
+               break;
+           case LEFT:
+               if (cursor > result) {
+                   putchar('\b');
+                   --cursor;
+               }
+               break;
+           case RIGHT:
+               if (cursor < end) putchar(*cursor++);
+               break;
+           }
+           break;
+       case BACKSPACE:
+           if (cursor == result) continue;
+           memmove(cursor - 1, cursor, end - cursor);
+           --cursor;
+           *--end = '\0';
+
+           putchar('\b');
+           fputs(cursor, stdout);
+           putchar(' ');
+           for (i = end - cursor + 1; i > 0; --i) putchar('\b');
+
+           break;
+       }
+   }
+   strcpy(*ht, result);
+   ht = history + (ht - history + 1) % HISTLEN;
+   hc = ht;
+   if (ht == hb) hb = history + (hb - history + 1) % HISTLEN;
+
+   return end > result ? result : NULL;
+}
 
 char **tokenize(char *p) {
    size_t i;
    static char *result[BUFLEN / 2];
 
    while (*p == ' ') ++p;
-   for (i = 0; *p != '\n'; ++i) {
+   for (i = 0; *p; ++i) {
        result[i] = p;
-       while (*p != ' ' && *p != '\n') ++p;
-       *p++ = '\0';
+       while (*p != ' ' && *p != '\0') ++p;
        if (*p == '\0') {
            ++i;
            break;
@@ -38,7 +134,7 @@ char **tokenize(char *p) {
 struct {
    pid_t pgids[BGMAX];
    size_t bp, sp;
-} bgps;
+} bgps; // TODO: These are stopped jobs, not background jobs
 
 void pushbgps(pid_t pgid) {
    if ((bgps.sp + 1) % BGMAX == bgps.bp) killpg(bgps.pgids[bgps.bp++], SIGKILL);
@@ -63,7 +159,7 @@ void await(pid_t cpgid) {
    if (tcsetpgrp(STDIN_FILENO, self) == -1) exit(EXIT_FAILURE);
    if (signal(SIGTTOU, SIG_DFL) == SIG_ERR) warn("Ignoring signal SIGTTOU");
 
-   if (WIFSIGNALED(status)) printf("\n");
+   if (WIFSIGNALED(status)) putchar('\n');
    if (WIFSTOPPED(status)) pushbgps(cpgid);
 }
 
@@ -94,30 +190,31 @@ int builtin(char **tokens) {
 }
 
 int main(void) {
-   char buffer[BUFLEN];
-   char **tokens;
+   char *input, **tokens;
    pid_t cpgid;
-
-   signal(SIGINT, prompt);
-   if ((self = getpgid(0)) == -1)
-       err(EXIT_FAILURE, "Unable to get pgid of self");
+   struct termios raw;
+
+   if (tcgetattr(STDIN_FILENO, &canonical) == -1)
+       err(EXIT_FAILURE, "Unable to get termios structure");
+   raw = canonical;
+   raw.c_lflag &= ~ICANON & ~ECHO & ~ISIG;
+   raw.c_lflag |= ECHONL;
+   if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1)
+       err(EXIT_FAILURE, "Unable to set termios structure");
+   if (atexit(ashexit) == -1)
+       err(EXIT_FAILURE, "Unable to set `atexit()'");
+
+   if ((self = getpgid(0)) == -1) err(EXIT_FAILURE, "Unable to get pgid of self");
    for (;;) {
-       prompt(0);
-       if (fgets(buffer, BUFLEN, stdin) == NULL) {
-           if (feof(stdin)) {
-               printf("\n");
-               exit(EXIT_SUCCESS);
-           }
-           warn("Unable to read user input");
-           continue;
-       }
-       tokens = tokenize(buffer);
+       if (!(input = getinput())) continue;
+       tokens = tokenize(input);
 
        if (!*tokens || builtin(tokens)) continue;
 
-       if ((cpgid = fork()) == 0)
-           if (execvp(tokens[0], tokens) == -1)
-               err(EXIT_FAILURE, "Unable to run command `%s'", tokens[0]);
+       if ((cpgid = fork()) == 0 && execvp(tokens[0], tokens) == -1) {
+           warn("Unable to run command `%s'", tokens[0]);
+           _Exit(EXIT_FAILURE);
+       }
        if (setpgid(cpgid, cpgid) == -1) {
            warn("Unable to set process group of process %d", cpgid);
            if (kill(cpgid, SIGKILL) == -1)
@@ -140,5 +237,6 @@ int main(void) {
        }
        await(cpgid);
    }
+
    return 0;
 }