Compare commits
7 Commits
264d1798dc
...
3e68981b0b
Author | SHA1 | Date |
---|---|---|
Bananymous | 3e68981b0b | |
Bananymous | 6fd76e8d1e | |
Bananymous | dfcd15e7c4 | |
Bananymous | 5fa359c28d | |
Bananymous | 5bcfc9dd50 | |
Bananymous | f67cad326a | |
Bananymous | 9775e83374 |
Binary file not shown.
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue