diff --git a/Kernel/makeall.sh b/Kernel/makeall.sh index c1ddc9c952..1d9442a308 100755 --- a/Kernel/makeall.sh +++ b/Kernel/makeall.sh @@ -46,6 +46,8 @@ $make_cmd -C ../Games/Minesweeper clean && \ $make_cmd -C ../Games/Minesweeper && \ $make_cmd -C ../Games/Snake clean && \ $make_cmd -C ../Games/Snake && \ +$make_cmd -C ../Shell clean && \ +$make_cmd -C ../Shell && \ $make_cmd clean &&\ $make_cmd && \ sudo ./sync.sh diff --git a/Shell/GlobalState.h b/Shell/GlobalState.h new file mode 100644 index 0000000000..4fd85330a6 --- /dev/null +++ b/Shell/GlobalState.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +struct GlobalState { + String cwd; + String username; + String home; + char ttyname[32]; + char hostname[32]; + pid_t sid; + uid_t uid; + struct termios termios; + bool was_interrupted { false }; +}; + +extern GlobalState g; diff --git a/Shell/LineEditor.cpp b/Shell/LineEditor.cpp new file mode 100644 index 0000000000..e32e4d7f5d --- /dev/null +++ b/Shell/LineEditor.cpp @@ -0,0 +1,94 @@ +#include "LineEditor.h" +#include "GlobalState.h" +#include +#include +#include + +LineEditor::LineEditor() +{ +} + +LineEditor::~LineEditor() +{ +} + +void LineEditor::add_to_history(const String& line) +{ + if ((m_history.size() + 1) > m_history_capacity) + m_history.take_first(); + m_history.append(line); +} + +String LineEditor::get_line() +{ + for (;;) { + char keybuf[16]; + ssize_t nread = read(0, keybuf, sizeof(keybuf)); + // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead. + if (nread == 0) + exit(0); + if (nread < 0) { + if (errno == EINTR) { + if (g.was_interrupted) { + if (!m_buffer.is_empty()) + printf("^C"); + } + g.was_interrupted = false; + m_buffer.clear(); + putchar('\n'); + return String::empty(); + } else { + perror("read failed"); + // FIXME: exit()ing here is a bit off. Should communicate failure to caller somehow instead. + exit(2); + } + } + + for (ssize_t i = 0; i < nread; ++i) { + char ch = keybuf[i]; + if (ch == 0) + continue; + if (ch == 8 || ch == g.termios.c_cc[VERASE]) { + if (m_buffer.is_empty()) + continue; + m_buffer.take_last(); + putchar(8); + fflush(stdout); + continue; + } + if (ch == g.termios.c_cc[VWERASE]) { + bool has_seen_nonspace = false; + while (!m_buffer.is_empty()) { + if (isspace(m_buffer.last())) { + if (has_seen_nonspace) + break; + } else { + has_seen_nonspace = true; + } + putchar(0x8); + m_buffer.take_last(); + } + fflush(stdout); + continue; + } + if (ch == g.termios.c_cc[VKILL]) { + if (m_buffer.is_empty()) + continue; + for (int i = 0; i < m_buffer.size(); ++i) + putchar(0x8); + m_buffer.clear(); + fflush(stdout); + continue; + } + putchar(ch); + fflush(stdout); + if (ch != '\n') { + m_buffer.append(ch); + } else { + auto string = String::copy(m_buffer); + m_buffer.clear(); + return string; + } + } + } +} diff --git a/Shell/LineEditor.h b/Shell/LineEditor.h new file mode 100644 index 0000000000..e5668ff349 --- /dev/null +++ b/Shell/LineEditor.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class LineEditor { +public: + LineEditor(); + ~LineEditor(); + + String get_line(); + + void add_to_history(const String&); + const Vector& history() const { return m_history; } + +private: + Vector m_buffer; + int m_cursor { 0 }; + + // FIXME: This should be something more take_first()-friendly. + Vector m_history; + int m_history_capacity { 100 }; +}; diff --git a/Shell/Makefile b/Shell/Makefile index 535b2b75f5..fd04ce37a2 100644 --- a/Shell/Makefile +++ b/Shell/Makefile @@ -2,6 +2,7 @@ include ../Makefile.common OBJS = \ Parser.o \ + LineEditor.o \ main.o APP = Shell diff --git a/Shell/main.cpp b/Shell/main.cpp index 4d0e9611b1..38763fe001 100644 --- a/Shell/main.cpp +++ b/Shell/main.cpp @@ -7,43 +7,33 @@ #include #include #include -#include #include #include #include #include #include +#include "GlobalState.h" #include "Parser.h" +#include "LineEditor.h" //#define SH_DEBUG -struct GlobalState { - String cwd; - String username; - String home; - char ttyname[32]; - char hostname[32]; - pid_t sid; - uid_t uid; - struct termios termios; - bool was_interrupted { false }; -}; -static GlobalState* g; +GlobalState g; static void prompt() { - if (g->uid == 0) + if (g.uid == 0) printf("# "); else { - printf("\033]0;%s@%s:%s\007", g->username.characters(), g->hostname, g->cwd.characters()); - printf("\033[31;1m%s\033[0m@\033[37;1m%s\033[0m:\033[32;1m%s\033[0m$> ", g->username.characters(), g->hostname, g->cwd.characters()); + printf("\033]0;%s@%s:%s\007", g.username.characters(), g.hostname, g.cwd.characters()); + printf("\033[31;1m%s\033[0m@\033[37;1m%s\033[0m:\033[32;1m%s\033[0m$> ", g.username.characters(), g.hostname, g.cwd.characters()); } fflush(stdout); } static int sh_pwd(int, char**) { - printf("%s\n", g->cwd.characters()); + printf("%s\n", g.cwd.characters()); return 0; } @@ -57,7 +47,7 @@ void did_receive_signal(int signum) void handle_sigint(int) { - g->was_interrupted = true; + g.was_interrupted = true; } static int sh_exit(int, char**) @@ -88,12 +78,12 @@ static int sh_cd(int argc, char** argv) char pathbuf[PATH_MAX]; if (argc == 1) { - strcpy(pathbuf, g->home.characters()); + strcpy(pathbuf, g.home.characters()); } else { if (argv[1][0] == '/') memcpy(pathbuf, argv[1], strlen(argv[1]) + 1); else - sprintf(pathbuf, "%s/%s", g->cwd.characters(), argv[1]); + sprintf(pathbuf, "%s/%s", g.cwd.characters(), argv[1]); } FileSystemPath canonical_path(pathbuf); @@ -118,7 +108,7 @@ static int sh_cd(int argc, char** argv) printf("chdir(%s) failed: %s\n", path, strerror(errno)); return 1; } - g->cwd = canonical_path.string(); + g.cwd = canonical_path.string(); return 0; } @@ -277,7 +267,7 @@ static int run_command(const String& cmd) tcsetpgrp(0, getpid()); for (auto& redirection : subcommand.redirections) { if (redirection.type == Redirection::Rewire) { -#ifdef SH_DEBUG +#ifdef SH_DEBUGsh dbgprintf("in %s<%d>, dup2(%d, %d)\n", argv[0], getpid(), redirection.rewire_fd, redirection.fd); #endif int rc = dup2(redirection.rewire_fd, redirection.fd); @@ -355,11 +345,10 @@ static int run_command(const String& cmd) int main(int argc, char** argv) { - g = new GlobalState; - g->uid = getuid(); - g->sid = setsid(); + g.uid = getuid(); + g.sid = setsid(); tcsetpgrp(0, getpgrp()); - tcgetattr(0, &g->termios); + tcgetattr(0, &g.termios); { struct sigaction sa; @@ -370,18 +359,18 @@ int main(int argc, char** argv) assert(rc == 0); } - int rc = gethostname(g->hostname, sizeof(g->hostname)); + int rc = gethostname(g.hostname, sizeof(g.hostname)); if (rc < 0) perror("gethostname"); - rc = ttyname_r(0, g->ttyname, sizeof(g->ttyname)); + rc = ttyname_r(0, g.ttyname, sizeof(g.ttyname)); if (rc < 0) perror("ttyname_r"); { auto* pw = getpwuid(getuid()); if (pw) { - g->username = pw->pw_name; - g->home = pw->pw_dir; + g.username = pw->pw_name; + g.home = pw->pw_dir; putenv(const_cast(String::format("HOME=%s", pw->pw_dir).characters())); } endpwent(); @@ -392,81 +381,21 @@ int main(int argc, char** argv) return 1; } - Vector line_buffer; - { auto* cwd = getcwd(nullptr, 0); - g->cwd = cwd; + g.cwd = cwd; free(cwd); } - prompt(); + + LineEditor editor; for (;;) { - char keybuf[16]; - ssize_t nread = read(0, keybuf, sizeof(keybuf)); - if (nread == 0) - return 0; - if (nread < 0) { - if (errno == EINTR) { - if (g->was_interrupted) { - if (!line_buffer.is_empty()) - printf("^C"); - } - g->was_interrupted = false; - line_buffer.clear(); - putchar('\n'); - prompt(); - continue; - } else { - perror("read failed"); - return 2; - } - } - for (ssize_t i = 0; i < nread; ++i) { - char ch = keybuf[i]; - if (ch == 0) - continue; - if (ch == 8 || ch == g->termios.c_cc[VERASE]) { - if (line_buffer.is_empty()) - continue; - line_buffer.take_last(); - putchar(8); - fflush(stdout); - continue; - } - if (ch == g->termios.c_cc[VWERASE]) { - bool has_seen_nonspace = false; - while (!line_buffer.is_empty()) { - if (isspace(line_buffer.last())) { - if (has_seen_nonspace) - break; - } else { - has_seen_nonspace = true; - } - putchar(0x8); - line_buffer.take_last(); - } - fflush(stdout); - continue; - } - if (ch == g->termios.c_cc[VKILL]) { - if (line_buffer.is_empty()) - continue; - for (int i = 0; i < line_buffer.size(); ++i) - putchar(0x8); - line_buffer.clear(); - fflush(stdout); - continue; - } - putchar(ch); - fflush(stdout); - if (ch != '\n') { - line_buffer.append(ch); - } else { - run_command(String::copy(line_buffer)); - line_buffer.clear(); - prompt(); - } - } + prompt(); + auto line = editor.get_line(); + if (line.is_empty()) + continue; + run_command(line); + editor.add_to_history(line); } + return 0; }