Shell: Added pushd, popd and dirs builtins

Added a few builtin functions to the shell to make navigating a bit
easier in the terminal.

`pushd` allows a user to "push" the current directory to the directory
stack, and then `cd` to the new directory.
`popd` allows the used to take the directory on the top of the stack
off before `cd`'ing to it.
`dirs` gives the state of the current directory stack.

This is only a partial implementation of the `bash` version
(gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html)
, and doesn't include any of the +N or -N commands as of yet.
This commit is contained in:
Jesse Buhagiar 2019-09-15 18:54:20 +10:00 committed by Andreas Kling
parent 85d629103d
commit ecdaf991c6
Notes: sideshowbarker 2024-07-19 12:06:23 +09:00
2 changed files with 211 additions and 1 deletions

View file

@ -1,6 +1,7 @@
#pragma once
#include <AK/String.h>
#include <AK/Vector.h>
#include <termios.h>
struct GlobalState {
@ -15,6 +16,7 @@ struct GlobalState {
bool was_interrupted { false };
bool was_resized { false };
int last_return_code { 0 };
Vector<String> directory_stack;
};
extern GlobalState g;

View file

@ -153,6 +153,200 @@ static int sh_umask(int argc, char** argv)
return 0;
}
static int sh_popd(int argc, char** argv)
{
if (g.directory_stack.size() <= 1) {
fprintf(stderr, "Shell: popd: directory stack empty\n");
return 1;
}
bool should_switch = true;
String path = g.directory_stack.take_last();
// When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory.
if (argc == 1) {
int rc = chdir(path.characters());
if (rc < 0) {
fprintf(stderr, "chdir(%s) failed: %s", path.characters(), strerror(errno));
return 1;
}
g.cwd = path;
return 0;
}
for (int i = 0; i < argc; i++) {
const char* arg = argv[i];
if (!strcmp(arg, "-n")) {
should_switch = false;
}
}
FileSystemPath canonical_path(path.characters());
if (!canonical_path.is_valid()) {
fprintf(stderr, "FileSystemPath failed to canonicalize '%s'\n", path.characters());
return 1;
}
const char* real_path = canonical_path.string().characters();
g.directory_stack.append(g.cwd.characters());
struct stat st;
int rc = stat(real_path, &st);
if (rc < 0) {
fprintf(stderr, "stat(%s) failed: %s\n", real_path, strerror(errno));
return 1;
}
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "Not a directory: %s\n", real_path);
return 1;
}
if (should_switch) {
int rc = chdir(real_path);
if (rc < 0) {
fprintf(stderr, "chdir(%s) failed: %s\n", real_path, strerror(errno));
return 1;
}
g.cwd = canonical_path.string();
}
return 0;
}
static int sh_pushd(int argc, char** argv)
{
StringBuilder path_builder;
bool should_switch = true;
// From the BASH reference manual: https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html
// With no arguments, pushd exchanges the top two directories and makes the new top the current directory.
if (argc == 1) {
if (g.directory_stack.size() < 2) {
fprintf(stderr, "pushd: no other directory\n");
return 1;
}
String dir1 = g.directory_stack.take_first();
String dir2 = g.directory_stack.take_first();
g.directory_stack.insert(0, dir2);
g.directory_stack.insert(1, dir1);
int rc = chdir(dir2.characters());
if (rc < 0) {
fprintf(stderr, "chdir(%s) failed: %s", dir2.characters(), strerror(errno));
return 1;
}
g.cwd = dir2;
return 0;
}
// Let's assume the user's typed in 'pushd <dir>'
if (argc == 2) {
if (argv[1][0] == '/') {
path_builder.append(argv[1]);
}
else
path_builder.appendf("%s/%s", g.cwd.characters(), argv[1]);
} else if (argc == 3) {
for (int i = 0; i < argc; i++) {
const char* arg = argv[i];
if (arg[0] != '-') {
if (arg[0] == '/') {
path_builder.append(arg);
}
else
path_builder.appendf("%s/%s", g.cwd.characters(), arg);
}
if (!strcmp(arg, "-n"))
should_switch = false;
}
}
FileSystemPath canonical_path(path_builder.to_string());
if (!canonical_path.is_valid()) {
fprintf(stderr, "FileSystemPath failed to canonicalize '%s'\n", path_builder.to_string().characters());
return 1;
}
const char* real_path = canonical_path.string().characters();
g.directory_stack.append(real_path);
struct stat st;
int rc = stat(real_path, &st);
if (rc < 0) {
fprintf(stderr, "stat(%s) failed: %s\n", real_path, strerror(errno));
return 1;
}
if (!S_ISDIR(st.st_mode)) {
fprintf(stderr, "Not a directory: %s\n", real_path);
return 1;
}
if (should_switch) {
int rc = chdir(real_path);
if (rc < 0) {
fprintf(stderr, "chdir(%s) failed: %s\n", real_path, strerror(errno));
return 1;
}
g.cwd = canonical_path.string();
}
return 0;
}
static int sh_dirs(int argc, char** argv)
{
// The first directory in the stack is ALWAYS the current directory
g.directory_stack.at(0) = g.cwd.characters();
if (argc == 1) {
for (String dir : g.directory_stack)
printf("%s ", dir.characters());
printf("\n");
return 0;
}
bool printed = false;
for (int i = 0; i < argc; i++) {
const char* arg = argv[i];
if (!strcmp(arg, "-c")) {
for (int i = 1; i < g.directory_stack.size(); i++)
g.directory_stack.remove(i);
printed = true;
continue;
}
if (!strcmp(arg, "-p") && !printed) {
for (auto& directory : g.directory_stack)
printf("%s\n", directory.characters());
printed = true;
continue;
}
if (!strcmp(arg, "-v") && !printed) {
int idx = 0;
for (auto& directory : g.directory_stack) {
printf("%d %s\n", idx++, directory.characters());
}
printed = true;
continue;
}
}
return 0;
}
static bool handle_builtin(int argc, char** argv, int& retval)
{
if (argc == 0)
@ -185,6 +379,18 @@ static bool handle_builtin(int argc, char** argv, int& retval)
retval = sh_umask(argc, argv);
return true;
}
if (!strcmp(argv[0], "dirs")) {
retval = sh_dirs(argc, argv);
return true;
}
if (!strcmp(argv[0], "pushd")) {
retval = sh_pushd(argc, argv);
return true;
}
if (!strcmp(argv[0], "popd")) {
retval = sh_popd(argc, argv);
return true;
}
return false;
}
@ -228,7 +434,7 @@ static bool is_glob(const StringView& s)
return false;
}
static Vector<StringView> split_path(const StringView &path)
static Vector<StringView> split_path(const StringView& path)
{
Vector<StringView> parts;
@ -667,6 +873,8 @@ int main(int argc, char** argv)
free(cwd);
}
g.directory_stack.append(g.cwd);
load_history();
atexit(save_history);