Compare commits

...

7 Commits

Author SHA1 Message Date
Bananymous 3e68981b0b userspace: Remove start-gui from shell builtins and make it an alias
This makes way more sense :D
2024-10-07 18:01:33 +03:00
Bananymous 6fd76e8d1e Shell: Rewrite alias parsing to allow chained commands
You can now have "chained" commands in alias
e.g. `alias foo='echo hello && ls`
2024-10-07 18:01:33 +03:00
Bananymous dfcd15e7c4 Shell: Implement background processes with & 2024-10-07 18:01:33 +03:00
Bananymous 5fa359c28d ls: Print file name in quotes if it contains spaces 2024-10-07 18:01:32 +03:00
Bananymous 5bcfc9dd50 Shell: Always print prompt to new line
This is only done on non banan-os targets if terminal is detected to
support cursor position querying
2024-10-07 18:01:32 +03:00
Bananymous f67cad326a Shell: Don't crash when there are no tab completions available :D 2024-10-07 14:26:07 +03:00
Bananymous 9775e83374 Shell: Fix tab space escaping in tab completion 2024-10-07 04:19:36 +03:00
3 changed files with 251 additions and 68 deletions

Binary file not shown.

View File

@ -11,6 +11,7 @@
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <termios.h>
@ -35,6 +36,7 @@ struct SingleCommand
struct PipedCommand
{
bool background;
BAN::Vector<SingleCommand> commands;
};
@ -61,7 +63,7 @@ struct BuiltinCommand
};
static BAN::HashMap<BAN::String, BuiltinCommand> s_builtin_commands;
static BAN::HashMap<BAN::String, BAN::Vector<BAN::String>> s_aliases;
static BAN::HashMap<BAN::String, BAN::String> s_aliases;
static BAN::StringView strip_whitespace(BAN::StringView sv)
{
@ -208,7 +210,7 @@ static BAN::Optional<BAN::String> parse_dollar(BAN::StringView command, size_t&
return temp;
}
static SingleCommand parse_single_command(BAN::StringView command_view, bool parse_aliases = true)
static SingleCommand parse_single_command(BAN::StringView command_view)
{
constexpr auto can_escape =
[](char c)
@ -332,27 +334,19 @@ static SingleCommand parse_single_command(BAN::StringView command_view, bool par
MUST(result.arguments.push_back(BAN::move(current_argument)));
if (parse_aliases)
{
BAN::HashSet<BAN::String> matched_aliases;
while (!result.arguments.empty() && !matched_aliases.contains(result.arguments.front()))
{
auto it = s_aliases.find(result.arguments.front());
if (it == s_aliases.end())
break;
MUST(matched_aliases.insert(result.arguments.front()));
result.arguments.remove(0);
for (size_t i = 0; i < it->value.size(); i++)
MUST(result.arguments.insert(i, it->value[i]));
}
}
return BAN::move(result);
}
static PipedCommand parse_piped_command(BAN::StringView command_view)
{
while (!command_view.empty() && isspace(command_view.back()))
command_view = command_view.substring(0, command_view.size() - 1);
const bool background = !command_view.empty() && command_view.back() == '&';
if (background)
command_view = command_view.substring(0, command_view.size() - 1);
PipedCommand result;
result.background = background;
for (size_t i = 0; i < command_view.size(); i++)
{
@ -385,8 +379,88 @@ static PipedCommand parse_piped_command(BAN::StringView command_view)
return BAN::move(result);
}
static BAN::String parse_aliases(BAN::StringView command_view)
{
while (!command_view.empty() && isspace(command_view.front()))
command_view = command_view.substring(1);
BAN::String result;
MUST(result.append(command_view));
BAN::HashSet<BAN::String> matched_aliases;
for (size_t i = 0; i < result.size();)
{
size_t command_len = 0;
for (; command_len < result.size() - i; command_len++)
if (isspace(result[i + command_len]))
break;
auto command = result.sv().substring(i, command_len);
if (!matched_aliases.contains(command))
{
auto it = s_aliases.find(command);
if (it != s_aliases.end())
{
MUST(matched_aliases.insert(command));
for (size_t j = 0; j < command_len; j++)
result.remove(i);
MUST(result.insert(it->value, i));
continue;
}
}
matched_aliases.clear();
for (; i < result.size(); i++)
{
bool should_break = false;
const char current = result[i];
switch (current)
{
case '\\':
i++;
break;
case '\'':
case '"':
while (++i < result.size())
{
if (result[i] == current)
break;
if (result[i] == '\\')
i++;
}
break;
case '|':
case '&':
if (i + 1 < result.size() && result[i + 1] == current)
i++;
else if (current == '&')
break;
// fall through
case ';':
i++;
should_break = true;
break;
}
if (should_break)
break;
}
while (i < result.size() && isspace(result[i]))
i++;
}
return BAN::move(result);
}
static CommandList parse_command_list(BAN::StringView command_view)
{
const auto command_with_aliases_parsed = parse_aliases(command_view);
command_view = command_with_aliases_parsed.sv();
CommandList result;
CommandList::Condition next_condition = CommandList::Condition::Always;
for (size_t i = 0; i < command_view.size(); i++)
@ -447,7 +521,7 @@ static CommandList parse_command_list(BAN::StringView command_view)
return BAN::move(result);
}
static int execute_command(const SingleCommand& command, int fd_in, int fd_out);
static int execute_command(const SingleCommand& command, int fd_in, int fd_out, bool background);
static int source_script(const BAN::String& path);
@ -533,23 +607,10 @@ static void install_builtin_commands()
MUST(s_builtin_commands.emplace("alias"_sv,
[](const SingleCommand& command, FILE* fout, int, int) -> int
{
const auto print_alias =
[fout](const BAN::String& alias, const BAN::Vector<BAN::String>& value)
{
fprintf(fout, "%s='", alias.data());
for (size_t i = 0; i < value.size(); i++)
{
if (i != 0)
fprintf(fout, " ");
fprintf(fout, "%s", value[i].data());
}
fprintf(fout, "'\n");
};
if (command.arguments.size() == 1)
{
for (const auto& [alias, value] : s_aliases)
print_alias(alias, value);
fprintf(fout, "%s='%s'\n", alias.data(), value.data());
return 0;
}
@ -562,17 +623,16 @@ static void install_builtin_commands()
{
auto it = s_aliases.find(command.arguments[i]);
if (it != s_aliases.end())
print_alias(command.arguments[i], it->value);
fprintf(fout, "%s='%s'\n", command.arguments[i].data(), it->value.data());
}
else
{
auto alias = command.arguments[i].sv().substring(0, idx.value());
auto value = command.arguments[i].sv().substring(idx.value() + 1);
auto parsed_alias = parse_single_command(value, false);
if (s_aliases.contains(alias))
s_aliases.remove(alias);
MUST(s_aliases.insert(alias, BAN::move(parsed_alias.arguments)));
MUST(s_aliases.insert(alias, value));
}
}
@ -643,7 +703,7 @@ static void install_builtin_commands()
if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
ERROR_RETURN("clock_gettime", 1);
int ret = execute_command(timed_command, fd_in, fd_out);
int ret = execute_command(timed_command, fd_in, fd_out, false);
if (clock_gettime(CLOCK_MONOTONIC, &end) == -1)
ERROR_RETURN("clock_gettime", 1);
@ -660,24 +720,9 @@ static void install_builtin_commands()
return ret;
}
));
MUST(s_builtin_commands.emplace("start-gui"_sv,
[](const SingleCommand&, FILE*, int, int) -> int
{
const pid_t pid = fork();
if (pid == -1)
return 1;
if (pid == 0)
execl("/bin/WindowServer", "WindowServer", NULL);
if (fork() == 0)
execl("/bin/Terminal", "Terminal", NULL);
waitpid(pid, nullptr, 0);
return 0;
}
));
}
static pid_t execute_command_no_wait(const SingleCommand& command, int fd_in, int fd_out, pid_t pgrp)
static pid_t execute_command_no_wait(const SingleCommand& command, int fd_in, int fd_out, pid_t pgrp, bool background)
{
ASSERT(!command.arguments.empty());
@ -760,7 +805,9 @@ static pid_t execute_command_no_wait(const SingleCommand& command, int fd_in, in
if (pid == -1)
ERROR_RETURN("fork", -1);
if (pgrp == 0 && isatty(0))
if (background)
;
else if (pgrp == 0 && isatty(0))
{
if(setpgid(pid, pid) == -1)
perror("setpgid");
@ -775,11 +822,13 @@ static pid_t execute_command_no_wait(const SingleCommand& command, int fd_in, in
return pid;
}
static int execute_command(const SingleCommand& command, int fd_in, int fd_out)
static int execute_command(const SingleCommand& command, int fd_in, int fd_out, bool background)
{
const pid_t pid = execute_command_no_wait(command, fd_in, fd_out, 0);
const pid_t pid = execute_command_no_wait(command, fd_in, fd_out, 0, background);
if (pid == -1)
return 1;
if (background)
return 0;
int status;
if (waitpid(pid, &status, 0) == -1)
@ -804,7 +853,7 @@ static int execute_piped_commands(const PipedCommand& piped_command)
auto& command = piped_command.commands.front();
if (auto ret = execute_builtin(command, STDIN_FILENO, STDOUT_FILENO); ret.has_value())
return ret.value();
return execute_command(command, STDIN_FILENO, STDOUT_FILENO);
return execute_command(command, STDIN_FILENO, STDOUT_FILENO, piped_command.background);
}
BAN::Vector<int> exit_codes(piped_command.commands.size(), 0);
@ -830,7 +879,7 @@ static int execute_piped_commands(const PipedCommand& piped_command)
exit_codes[i] = builtin_ret.value();
else
{
pid_t pid = execute_command_no_wait(piped_command.commands[i], next_stdin, pipefd[1], pgrp);
const pid_t pid = execute_command_no_wait(piped_command.commands[i], next_stdin, pipefd[1], pgrp, piped_command.background);
processes[i] = pid;
if (pgrp == 0)
pgrp = pid;
@ -843,6 +892,9 @@ static int execute_piped_commands(const PipedCommand& piped_command)
next_stdin = pipefd[0];
}
if (piped_command.background)
return 0;
for (size_t i = 0; i < piped_command.commands.size(); i++)
{
if (processes[i] == -1)
@ -882,8 +934,18 @@ static int parse_and_execute_command(BAN::StringView command)
tcsetattr(0, TCSANOW, &old_termios);
last_return = 0;
for (const auto& [expression, condition] : command_list.commands)
for (size_t i = 0; i < command_list.commands.size(); i++)
{
const auto& [expression, condition] = command_list.commands[i];
const auto parsed_command = parse_piped_command(expression);
if (parsed_command.background && i + 1 < command_list.commands.size() && command_list.commands[i + 1].condition != CommandList::Condition::Always)
{
printf("invalid background command with conditional execution\n");
break;
}
bool should_run = false;
switch (condition)
{
@ -901,7 +963,9 @@ static int parse_and_execute_command(BAN::StringView command)
if (!should_run)
continue;
last_return = execute_piped_commands(parse_piped_command(expression));
int return_value = execute_piped_commands(parsed_command);
if (!parsed_command.background)
last_return = return_value;
}
tcsetattr(0, TCSANOW, &new_termios);
@ -1243,6 +1307,101 @@ static void print_prompt()
fflush(stdout);
}
static bool detect_cursor_position_support()
{
constexpr auto getchar_nonblock =
[]() -> char
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 100'000;
int nselect = select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &timeout);
if (nselect != 1)
return '\0';
char ch;
if (read(STDIN_FILENO, &ch, 1) != 1)
return '\0';
return ch;
};
if (write(STDOUT_FILENO, "\e[6n", 4) != 4)
return false;
char ch = getchar_nonblock();
if (ch != '\e')
{
if (ch != '\0')
ungetc(ch, stdin);
return false;
}
if (getchar_nonblock() != '[')
return false;
int cur;
while (isdigit(cur = getchar_nonblock()))
;
if (cur != ';')
return false;
while (isdigit(cur = getchar_nonblock()))
;
if (cur != 'R')
return false;
return true;
}
struct CursorPosition
{
int x;
int y;
};
static BAN::Optional<CursorPosition> try_read_cursor_position()
{
#if __banan_os__
return {};
#endif
static BAN::Optional<bool> s_supports_cursor_position;
if (!s_supports_cursor_position.has_value())
s_supports_cursor_position = detect_cursor_position_support();
if (!s_supports_cursor_position.value())
return {};
if (write(STDOUT_FILENO, "\e[6n", 4) != 4)
return {};
char ch = getchar();
if (ch != '\e')
{
ungetc(ch, stdin);
return {};
}
if (getchar() != '[')
return {};
int cur, x = 0, y = 0;
while (isdigit(cur = getchar()))
y = (y * 10) + (cur - '0');
if (cur != ';')
return {};
while (isdigit(cur = getchar()))
x = (x * 10) + (cur - '0');
if (cur != 'R')
return {};
if (x > 0) x--;
if (y > 0) y--;
return CursorPosition { x, y };
}
int main(int argc, char** argv)
{
realpath(argv[0], s_shell_path);
@ -1466,6 +1625,10 @@ int main(int argc, char** argv)
MUST(history.push_back(buffers[index]));
buffers = history;
MUST(buffers.emplace_back(""_sv));
auto cursor_pos = try_read_cursor_position();
if (cursor_pos.has_value() && cursor_pos->x > 0)
printf("\e[7m%%\e[m\n");
}
print_prompt();
index = buffers.size() - 1;
@ -1509,12 +1672,12 @@ int main(int argc, char** argv)
}
);
for (size_t i = 0; i < completions.size() - 1; i++)
for (size_t i = 1; i < completions.size();)
{
if (completions[i] != completions[i + 1])
continue;
completions.remove(i + 1);
i--;
if (completions[i - 1] == completions[i])
completions.remove(i);
else
i++;
}
if (completions.empty())
@ -1543,9 +1706,28 @@ int main(int argc, char** argv)
if (all_match_len)
{
col += all_match_len;
MUST(buffers[index].append(completions.front().sv().substring(0, all_match_len)));
printf("%.*s", (int)all_match_len, completions.front().data());
auto completion = completions.front().sv().substring(0, all_match_len);
BAN::String temp_escaped;
if (should_escape_spaces)
{
MUST(temp_escaped.append(completion));
for (size_t i = 0; i < temp_escaped.size(); i++)
{
if (!isspace(temp_escaped[i]))
continue;
MUST(temp_escaped.insert('\\', i));
i++;
}
completion = temp_escaped.sv();
if (!buffers[index].empty() && buffers[index].back() == '\\' && completion.front() == '\\')
completion = completion.substring(1);
}
col += completion.size();
MUST(buffers[index].append(completion));
printf("%.*s", (int)completion.size(), completion.data());
fflush(stdout);
break;
}

View File

@ -202,7 +202,8 @@ int list_directory(const BAN::String& path, config_t config)
{
if (i > 0)
printf(" ");
printf("%s%s\e[m", entry_color(entries[i].st.st_mode), entries[i].name.data());
const char* format = entries[i].name.sv().contains(' ') ? "'%s%s\e[m'" : "%s%s\e[m";
printf(format, entry_color(entries[i].st.st_mode), entries[i].name.data());
}
printf("\n");
return ret;