mirror of
https://github.com/RGBCube/serenity
synced 2025-05-16 20:15:07 +00:00
Shell: Move line editing to a separate class.
To be clear, there isn't really any line editing yet. But there is going to be, so let's have it in its own class.
This commit is contained in:
parent
fe73543d41
commit
ba7364b43b
6 changed files with 167 additions and 100 deletions
|
@ -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
|
||||
|
|
18
Shell/GlobalState.h
Normal file
18
Shell/GlobalState.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/AKString.h>
|
||||
#include <termios.h>
|
||||
|
||||
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;
|
94
Shell/LineEditor.cpp
Normal file
94
Shell/LineEditor.cpp
Normal file
|
@ -0,0 +1,94 @@
|
|||
#include "LineEditor.h"
|
||||
#include "GlobalState.h"
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <ctype.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
Shell/LineEditor.h
Normal file
23
Shell/LineEditor.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/AKString.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
class LineEditor {
|
||||
public:
|
||||
LineEditor();
|
||||
~LineEditor();
|
||||
|
||||
String get_line();
|
||||
|
||||
void add_to_history(const String&);
|
||||
const Vector<String>& history() const { return m_history; }
|
||||
|
||||
private:
|
||||
Vector<char, 1024> m_buffer;
|
||||
int m_cursor { 0 };
|
||||
|
||||
// FIXME: This should be something more take_first()-friendly.
|
||||
Vector<String> m_history;
|
||||
int m_history_capacity { 100 };
|
||||
};
|
|
@ -2,6 +2,7 @@ include ../Makefile.common
|
|||
|
||||
OBJS = \
|
||||
Parser.o \
|
||||
LineEditor.o \
|
||||
main.o
|
||||
|
||||
APP = Shell
|
||||
|
|
125
Shell/main.cpp
125
Shell/main.cpp
|
@ -7,43 +7,33 @@
|
|||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <termios.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <AK/FileSystemPath.h>
|
||||
#include <LibCore/CElapsedTimer.h>
|
||||
#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<char*>(String::format("HOME=%s", pw->pw_dir).characters()));
|
||||
}
|
||||
endpwent();
|
||||
|
@ -392,81 +381,21 @@ int main(int argc, char** argv)
|
|||
return 1;
|
||||
}
|
||||
|
||||
Vector<char, 256> 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();
|
||||
auto line = editor.get_line();
|
||||
if (line.is_empty())
|
||||
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();
|
||||
}
|
||||
}
|
||||
run_command(line);
|
||||
editor.add_to_history(line);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue