Reqrite project as BEd specific.

This commit is contained in:
2026-08-13 16:17:32 +01:00
parent cc5d475ab8
commit cd13557e15
46 changed files with 1115 additions and 1026 deletions
+5
View File
@@ -0,0 +1,5 @@
#include "bed.h"
namespace bed {
void run(std::vector<std::string>) {}
} // namespace bed
+95
View File
@@ -0,0 +1,95 @@
#include "bed.h"
#include "internal/address/address.h"
#include "internal/commands/commands.h"
namespace bed {
void BEd::handle(std::string_view cmd_, bool eof) {
using namespace internal::commands;
std::string cmd(cmd_);
if (eof) {
eof_op.handle(*this, {}, "");
return;
}
uint64_t i = 0;
auto skip_space = [&] {
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
++i;
};
auto addresses = internal::address::Address::handle(*this, cmd, i);
if (i >= cmd.size()) {
if (!no_op.accept_zero)
for (auto l : addresses.span())
if (l == 0)
throw ed_error("Invalid address given.");
switch (no_op.address_mode) {
case Command::AddressMode::None:
no_op.handle(*this, addresses.span(0), "");
break;
case Command::AddressMode::Single:
no_op.handle(*this, addresses.span(1), "");
break;
case Command::AddressMode::Range:
no_op.handle(*this, addresses.span(2), "");
break;
}
return;
}
uint64_t len = commands.longest_match(cmd.substr(i));
if (len == 0)
throw ed_error("Command not found.");
std::optional<Command> command_opt = commands.get(cmd.substr(i, len));
i += len;
if (!command_opt)
throw ed_error("Command error.");
Command command = command_opt.value();
std::string argument;
Suffix *suffix = nullptr;
switch (command.suffix) {
case Command::SuffixKind::None:
skip_space();
if (i < cmd.size())
throw ed_error("Command error.");
break;
case Command::SuffixKind::Suffix:
if (i < cmd.size()) {
if (suffixes[cmd[i] - 'a'].has_value())
suffix = &(suffixes[cmd[i++] - 'a'].value());
skip_space();
std::cout << i << " " << cmd << std::endl;
if (i < cmd.size())
throw ed_error("Command error.");
}
break;
case Command::SuffixKind::Argument:
if (i < cmd.size()) {
if (cmd[i] == ' ' || cmd[i] == '\t')
skip_space();
else
throw ed_error("Command Error.");
argument = cmd.substr(i);
}
break;
case Command::SuffixKind::Continuation:
argument = cmd.substr(i);
break;
}
if (!command.accept_zero)
for (auto l : addresses.span())
if (l == 0)
throw ed_error("Invalid address given.");
switch (command.address_mode) {
case Command::AddressMode::None:
command.handle(*this, addresses.span(0), argument);
break;
case Command::AddressMode::Single:
command.handle(*this, addresses.span(1), argument);
break;
case Command::AddressMode::Range:
command.handle(*this, addresses.span(2), argument);
break;
}
if (suffix)
suffix->handle(*this);
return;
}
} // namespace bed
+53
View File
@@ -0,0 +1,53 @@
#include "bed.h"
namespace bed {
BEd::BEd(std::vector<std::string> args) {
internal::commands::Command::register_posix(*this);
internal::commands::Suffix::register_suffixes(*this);
std::string prompt_ = "";
std::string file = "";
bool suppress = false;
for (size_t i = 1; i < args.size(); i++) {
if (args[i] == "-p") {
i++;
if (i >= args.size())
throw fatal_error("Prompt not specified!", 1);
prompt_ = args[i];
} else if (args[i] == "-s") {
suppress = true;
} else {
if (file.size())
throw fatal_error("Invalid arguments given.", 1);
file = args[i];
}
}
if (prompt_ != "")
prompt = [p = std::move(prompt_)](BEd &) { return p; };
else
prompt_mode = false;
suppress_mode = suppress;
active = new internal::buffer::Buffer();
buffers.emplace("0", active);
if (file != "")
handle("E " + file, false);
}
BEd::~BEd() {
for (auto &[_, buffer] : buffers)
delete buffer;
}
void BEd::run() {
while (true) {
std::string cmd;
if (prompt_mode)
std::cout << prompt(*this);
bool eof = !std::getline(std::cin, cmd);
try {
handle(cmd, eof);
} catch (ed_error &e) {
std::cout << e.what() << std::endl;
}
}
}
} // namespace bed
-72
View File
@@ -1,72 +0,0 @@
#include "cli.h"
#include "commands/bed/bed.h"
namespace crib::cli {
static const std::unordered_map<std::string, Command> commands = {
{"bed",
{crib::commands::bed::run,
crib::commands::bed::summary,
crib::commands::bed::help}},
};
bool use_color() {
if (std::getenv("NO_COLOR"))
return false;
if (std::getenv("CLICOLOR_FORCE"))
return true;
if (!isatty(STDERR_FILENO))
return false;
const char *term = std::getenv("TERM");
if (!term || std::string_view(term) == "dumb" || std::string_view(term) == "xterm")
return false;
if (const char *clicolor = std::getenv("CLICOLOR"))
return std::string_view(clicolor) != "0";
return true;
}
bool use_colors = use_color();
void help() {
std::cout << "Usage: crib <command> [options]\n\n"
<< "Where command is one of\n";
for (auto &[name, cmd] : commands)
std::cout << " " << name << " - " << cmd.summary() << "\n";
}
int execute(const std::string &name, const Command &cmd, int argc, char *argv[]) {
try {
std::vector<std::string> args(argv, argv + argc);
cmd.run(args);
return 0;
} catch (const cli_error &e) {
std::cerr << "Error running " << name << ": " << e.what() << '\n';
return e.exit_code;
} catch (const std::exception &e) {
std::cerr << "Error running " << name << ": " << e.what() << '\n';
return 1;
} catch (...) {
std::cerr << "Unknown error running " << name << '\n';
return 1;
}
}
int run(int argc, char *argv[]) {
std::string invoked_as = std::filesystem::path(argv[0]).filename();
if (auto it = commands.find(invoked_as); it != commands.end())
return execute(it->first, it->second, argc, argv);
if (argc > 1)
if (auto it = commands.find(argv[1]); it != commands.end())
return execute(it->first, it->second, argc - 1, argv + 1);
if (argc > 1 && std::string(argv[1]) == "--help") {
help();
return 0;
}
std::cerr << "Invalid command given.\n\n";
help();
return 2;
}
} // namespace crib::cli
-40
View File
@@ -1,40 +0,0 @@
#include "commands/bed/bed.h"
#include "cli.h"
namespace crib::commands::bed {
std::string summary() {
return "Better Ed, a modern take on a posix compliant line editor.";
}
void help() {}
void run(std::vector<std::string> args) {
std::string prompt = "";
std::string file;
bool suppress = false;
for (size_t i = 1; i < args.size(); i++) {
if (args[i] == "-p") {
i++;
if (i >= args.size())
throw crib::cli::cli_error("Prompt not specified!", 1);
prompt = args[i];
} else if (args[i] == "-s") {
suppress = true;
} else {
if (file.size())
throw crib::cli::cli_error("Invalid arguments given.", 1);
file = args[i];
}
}
BEd bed(file, suppress, prompt);
std::string command;
while (true) {
std::string cmd;
if (bed.prompt_mode)
std::cout << bed.prompt;
bool eof = !std::getline(std::cin, cmd);
if (!bed.handle(cmd, eof))
return;
}
}
} // namespace crib::commands::bed
-384
View File
@@ -1,384 +0,0 @@
#include "commands/bed/bed.h"
namespace crib::commands::bed {
bool BEd::handle(std::string cmd, bool eof) {
using namespace crib::internal::vase;
try {
if (line > vase.lines())
line = vase.lines();
Command command = parse(cmd, eof);
bool was_quitting = quitting;
quitting = false;
bool was_editing = editing;
editing = false;
switch (command.type) {
case Command::Type::Quit:
if (modified && !was_quitting) {
quitting = true;
throw bed_error("Buffer modified.");
} else {
return false;
}
case Command::Type::ForceQuit:
return false;
case Command::Type::None: {
if (command.start.type != Command::Address::Type::None) {
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &line);
else
resolve_address(command.start, &line);
} else {
line++;
}
if (line == 0)
throw bed_error("Line 0 is invalid.");
if (line > vase.lines())
throw bed_error("Line position too high.");
Iterator it = vase.iterate(line - 1, Direction::Forward);
it.next();
std::cout << it.line << std::endl;
} break;
case Command::Type::Invalid:
throw bed_error("Invalid command.");
case Command::Type::HelpToggle:
help_mode = !help_mode;
if (help_mode && last_message != "")
std::cout << last_message << std::endl;
break;
case Command::Type::Help:
if (last_message != "")
std::cout << last_message << std::endl;
break;
case Command::Type::PromptToggle:
prompt_mode = !prompt_mode;
break;
case Command::Type::Print: {
uint64_t line_start = line;
uint64_t line_end = line;
if (command.start.type != Command::Address::Type::None) {
resolve_address(command.start, &line_start);
line_end = line_start;
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &line_end);
}
if (line_start == 0)
throw bed_error("Line 0 is invalid.");
if (line_end < line_start)
throw bed_error("Invalid address range.");
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
while (it.next() && line_start++ <= line_end)
std::cout << it.line << std::endl;
line = line_end;
} break;
case Command::Type::List: {
uint64_t line_start = line;
uint64_t line_end = line;
if (command.start.type != Command::Address::Type::None) {
resolve_address(command.start, &line_start);
line_end = line_start;
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &line_end);
}
if (line_start == 0)
throw bed_error("Line 0 is invalid.");
if (line_end < line_start)
throw bed_error("Invalid address range.");
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
while (it.next() && line_start++ <= line_end)
std::cout << list_string(it.line) << std::endl;
line = line_end;
} break;
case Command::Type::Number: {
uint64_t line_start = line;
uint64_t line_end = line;
if (command.start.type != Command::Address::Type::None) {
resolve_address(command.start, &line_start);
line_end = line_start;
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &line_end);
}
if (line_start == 0)
throw bed_error("Line 0 is invalid.");
if (line_end < line_start)
throw bed_error("Invalid address range.");
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
while (it.next() && line_start <= line_end)
std::cout << line_start++ << "\t" << it.line << std::endl;
line = line_end;
} break;
case Command::Type::Append: {
if (command.start.type != Command::Address::Type::None) {
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &line);
else
resolve_address(command.start, &line);
}
std::string text;
std::string cline;
uint64_t line_count = 0;
while (std::getline(std::cin, cline)) {
if (cline == ".")
break;
line_count++;
text.append(cline);
text.push_back('\n');
}
if (text.empty()) {
if (line == 0)
line = 1;
break;
}
vase.snapshot();
append(text, line);
modified = true;
line += line_count;
} break;
case Command::Type::Insert: {
if (command.start.type != Command::Address::Type::None) {
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &line);
else
resolve_address(command.start, &line);
}
if (line == 0)
line = 1;
std::string text;
std::string cline;
uint64_t line_count = 0;
while (std::getline(std::cin, cline)) {
if (cline == ".")
break;
line_count++;
text.append(cline);
text.push_back('\n');
}
if (text.empty())
break;
vase.snapshot();
append(text, line - 1);
modified = true;
line += line_count - 1;
} break;
case Command::Type::Change: {
uint64_t line_start = line;
uint64_t line_end = line;
if (command.start.type != Command::Address::Type::None) {
resolve_address(command.start, &line_start);
if (line_start == 0)
line_start = 1;
line_end = line_start;
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &line_end);
}
if (line_end < line_start)
throw bed_error("Invalid address range.");
std::string text;
std::string cline;
uint64_t line_count = 0;
while (std::getline(std::cin, cline)) {
if (cline == ".")
break;
line_count++;
text.append(cline);
text.push_back('\n');
}
vase.snapshot();
remove(line_start, line_end);
line = line_start;
if (text.empty())
break;
append(text, line);
modified = true;
line += line_count;
} break;
case Command::Type::Delete: {
uint64_t line_start = line;
uint64_t line_end = line;
if (command.start.type != Command::Address::Type::None) {
resolve_address(command.start, &line_start);
if (line_start == 0)
line_start = 1;
line_end = line_start;
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &line_end);
}
if (line_end < line_start)
throw bed_error("Invalid address range.");
vase.snapshot();
vase.erase({{line_start - 1, 0}, {line_end, 0}});
modified = true;
line = line_start;
if (line > vase.lines())
line = vase.lines();
} break;
case Command::Type::Write: {
std::string path = command.argument;
if (path.empty()) {
path = save_path;
if (path.empty())
throw bed_error("Need file to write to.");
}
uint64_t line_start = 1;
uint64_t line_end = vase.lines();
if (vase.lines() == 0)
line_start = line_end = 0;
if (command.start.type != Command::Address::Type::None) {
resolve_address(command.start, &line_start);
line_end = line_start;
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &line_end);
}
if (vase.lines() && line_start == 0)
throw bed_error("Line 0 is invalid.");
if (line_end < line_start)
throw bed_error("Invalid address range.");
uint64_t bytes = 0;
if (path[0] == '!') {
FILE *pipe = popen(path.c_str() + 1, "w");
if (!pipe)
throw bed_error("Error starting command.");
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
while (it.next() && line_start++ <= line_end) {
it.line += '\n';
bytes += it.line.size();
if (fputs(it.line.c_str(), pipe) == EOF) {
pclose(pipe);
throw bed_error("Error writing to command.");
}
}
if (pclose(pipe) == -1)
throw bed_error("Error closing command.");
} else {
std::ofstream file(path, std::ios::out | std::ios::trunc);
if (!file)
throw bed_error("Error writing to file.");
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
while (it.next() && line_start++ <= line_end) {
file << it.line << '\n';
bytes += it.line.size() + 1;
}
if (!file)
throw bed_error("Error writing to file.");
save_path = path;
modified = false;
}
if (!suppress_mode)
std::cout << bytes << std::endl;
} break;
case Command::Type::Join: {
uint64_t line_start = line;
uint64_t line_end = line + 1;
if (command.start.type != Command::Address::Type::None) {
resolve_address(command.start, &line_start);
line_end = line_start;
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &line_end);
}
if (line_end < line_start)
throw bed_error("Invalid address range.");
if (line_start == 0)
throw bed_error("Invalid line number given.");
if (line_end == line_start)
break;
vase.snapshot();
join(line_start, line_end);
line = line_start;
modified = true;
} break;
case Command::Type::Mark: {
uint64_t mark_line = line;
if (command.start.type != Command::Address::Type::None) {
if (command.address_flags & Command::RANGE)
resolve_address(command.end, &mark_line);
else
resolve_address(command.start, &mark_line);
}
if (mark_line == 0)
throw bed_error("Invalid line number given.");
char mark = command.argument[0];
marks[mark - 'a'] = mark_line;
} break;
case Command::Type::Edit: {
if (modified && !was_editing) {
editing = true;
throw bed_error("Buffer modified.");
}
std::string path = command.argument;
if (path.empty()) {
path = save_path;
if (path.empty())
throw bed_error("Need file to read from.");
}
if (path[0] == '!') {
path.erase(1);
Vase new_vase(std::string(path), "/tmp");
vase = std::move(new_vase);
modified = true;
} else {
Vase new_vase(std::filesystem::path(path), "/tmp");
vase = std::move(new_vase);
save_path = path;
modified = false;
}
line = vase.lines();
if (!suppress_mode)
std::cout << vase.length() << std::endl;
} break;
case Command::Type::ForceEdit: {
std::string path = command.argument;
if (path.empty()) {
path = save_path;
if (path.empty())
throw bed_error("Need file to read from.");
}
if (path[0] == '!') {
path.erase(0, 1);
Vase new_vase(std::string(path), "/tmp");
vase = std::move(new_vase);
modified = true;
} else {
Vase new_vase(std::filesystem::path(path), "/tmp");
vase = std::move(new_vase);
save_path = path;
modified = false;
}
line = vase.lines();
std::fill(std::begin(marks), std::end(marks), '\0');
if (!suppress_mode)
std::cout << vase.length() << std::endl;
} break;
case Command::Type::Filename: {
std::string path = command.argument;
if (path.empty())
throw bed_error("No filename given.");
save_path = path;
std::cout << save_path.string() << std::endl;
} break;
case Command::Type::Dump:
Shard::dump(vase.root);
break;
}
if (command.suffix) {
Iterator it = vase.iterate(line - 1, Direction::Forward);
it.next();
switch (command.suffix) {
case 'p':
std::cout << it.line << std::endl;
break;
case 'n':
std::cout << line << "\t" << it.line << std::endl;
break;
case 'l':
std::cout << list_string(it.line) << std::endl;
break;
}
}
} catch (bed_error &e) {
last_message = e.what();
std::cout << "?" << std::endl;
if (help_mode)
std::cout << e.what() << std::endl;
}
return true;
}
} // namespace crib::commands::bed
-83
View File
@@ -1,83 +0,0 @@
#include "commands/bed/bed.h"
namespace crib::commands::bed {
void BEd::append(std::string text, uint64_t line) {
using namespace crib::internal::vase;
Point p = {line, 0};
if (!vase.lines()) {
text.pop_back();
} else if (line == vase.lines()) {
p.row--;
p.col = UINT64_MAX;
text = "\n" + text;
text.pop_back();
}
vase.insert(&p, text);
}
void BEd::remove(uint64_t start_line, uint64_t end_line) {
vase.erase({{start_line - 1, 0}, {end_line, 0}});
}
void BEd::join(uint64_t start_line, uint64_t end_line) {
vase.regex_search_replace("\\n", {{start_line - 1, 0}, {end_line, 0}}, "", "");
}
std::string BEd::list_string(std::string_view s) {
uint32_t width = 80;
winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0)
width = ws.ws_col;
std::string out;
out.reserve(s.size());
const uint32_t max_width = width > 1 ? width - 1 : 1;
uint32_t column = 0;
auto append = [&](std::string_view text) {
if (column + text.size() > max_width) {
out += "\\\n";
column = 0;
}
out += text;
column += text.size();
};
for (unsigned char c : s) {
switch (c) {
case '\\':
append("\\\\");
break;
case '$':
append("\\$");
break;
case '\a':
append("\\a");
break;
case '\b':
append("\\b");
break;
case '\f':
append("\\f");
break;
case '\r':
append("\\r");
break;
case '\t':
append("\\t");
break;
case '\v':
append("\\v");
break;
default:
if (!std::isprint(c)) {
char buf[5];
std::snprintf(buf, sizeof(buf), "\\%03o", c);
append(buf);
} else {
append(std::string_view((const char *)&c, 1));
}
break;
}
}
out += '$';
return out;
}
} // namespace crib::commands::bed
-176
View File
@@ -1,176 +0,0 @@
#include "commands/bed/bed.h"
namespace crib::commands::bed {
Command BEd::parse(std::string cmd, bool eof) {
Command command = {};
if (eof)
return (command.type = Command::Type::Quit, command);
uint64_t i = 0;
auto skip_space = [&] {
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
++i;
};
Command::Address first;
Command::Address second;
parse_address(cmd, i, first);
bool have_range = false;
while (i < cmd.size() && (cmd[i] == ',' || cmd[i] == ';')) {
char sep = cmd[i++];
if (sep == ';')
resolve_address(first, &line);
command.address_flags = sep == ';' ? Command::SEMICOLON : 0;
if (have_range)
first = second;
parse_address(cmd, i, second);
have_range = true;
}
if (have_range) {
if (first.type != Command::Address::Type::None
&& second.type == Command::Address::Type::None) {
second = first;
} else if (first.type == Command::Address::Type::None) {
if (command.address_flags & Command::SEMICOLON) {
first.type = Command::Address::Type::Current;
} else {
first.type = Command::Address::Type::Number;
first.number = 1;
}
if (second.type == Command::Address::Type::None)
second.type = Command::Address::Type::Last;
}
command.address_flags |= Command::RANGE;
command.start = first;
command.end = second;
} else {
command.start = first;
}
if (i >= cmd.size()) {
command.type = Command::Type::None;
return command;
}
switch (cmd[i]) {
case 'q':
command.type = Command::Type::Quit;
skip_space();
if (i >= cmd.size())
return command;
throw bed_error("Invalid command.");
case 'Q':
command.type = Command::Type::ForceQuit;
skip_space();
if (i >= cmd.size())
return command;
throw bed_error("Invalid command.");
case 'p':
command.type = Command::Type::Print;
i++;
break;
case 'l':
command.type = Command::Type::List;
i++;
break;
case 'n':
command.type = Command::Type::Number;
i++;
break;
case 'P':
command.type = Command::Type::PromptToggle;
i++;
break;
case 'H':
command.type = Command::Type::HelpToggle;
i++;
break;
case 'h':
command.type = Command::Type::Help;
i++;
break;
case 'a':
command.type = Command::Type::Append;
i++;
break;
case 'i':
command.type = Command::Type::Insert;
i++;
break;
case 'c':
command.type = Command::Type::Change;
i++;
break;
case 'd':
command.type = Command::Type::Delete;
i++;
break;
case 'j':
command.type = Command::Type::Join;
i++;
break;
case 'w':
command.type = Command::Type::Write;
i++;
if (i >= cmd.size())
return command;
if (cmd[i] == ' ' || cmd[i] == '\t')
skip_space();
else
throw bed_error("Invalid command.");
command.argument = cmd.substr(i);
return command;
case 'e':
command.type = Command::Type::Edit;
i++;
if (i >= cmd.size())
return command;
if (cmd[i] == ' ' || cmd[i] == '\t')
skip_space();
else
throw bed_error("Invalid command.");
command.argument = cmd.substr(i);
return command;
case 'E':
command.type = Command::Type::ForceEdit;
i++;
if (i >= cmd.size())
return command;
if (cmd[i] == ' ' || cmd[i] == '\t')
skip_space();
else
throw bed_error("Invalid command.");
command.argument = cmd.substr(i);
return command;
case 'f':
command.type = Command::Type::Filename;
i++;
if (i >= cmd.size())
return command;
if (cmd[i] == ' ' || cmd[i] == '\t')
skip_space();
else
throw bed_error("Invalid command.");
command.argument = cmd.substr(i);
return command;
case 'k':
command.type = Command::Type::Mark;
i++;
if (i >= cmd.size())
throw bed_error("Invalid command.");
if ('a' <= cmd[i] && cmd[i] <= 'z')
command.argument = cmd[i];
else
throw bed_error("Invalid mark used.");
break;
case '#':
command.type = Command::Type::Dump;
i++;
break;
}
if (i < cmd.size()) {
if (cmd[i] == 'l' || cmd[i] == 'n' || cmd[i] == 'p')
command.suffix = cmd[i++];
skip_space();
if (i != cmd.size())
throw bed_error("Invalid command.");
}
return command;
}
} // namespace crib::commands::bed
View File
@@ -1,36 +1,33 @@
#include "commands/bed/bed.h"
#include "internal/address/address.h"
namespace crib::commands::bed {
void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &addr) {
namespace bed::internal::address {
Address::Address(std::string &cmd, uint64_t &i) {
base = None{};
auto skip_space = [&] {
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
++i;
};
skip_space();
if (i >= cmd.size()) {
addr.type = Command::Address::Type::None;
if (i >= cmd.size())
return;
}
switch (cmd[i]) {
case '.':
addr.type = Command::Address::Type::Current;
base = Current();
i++;
break;
case '$':
addr.type = Command::Address::Type::Last;
base = Last();
i++;
break;
case '\'':
addr.type = Command::Address::Type::Mark;
case '\'': {
i++;
if (i < cmd.size() && 'a' <= cmd[i] && cmd[i] <= 'z')
i++;
else
throw bed_error("Invalid mark.");
addr.mark = cmd[i];
break;
throw address_error("Invalid mark.");
base = Mark(cmd[i]);
} break;
case '/': {
addr.type = Command::Address::Type::SearchForward;
i++;
uint64_t start = i;
while (true) {
@@ -49,10 +46,9 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add
else
i++;
}
addr.regex = cmd.substr(start, i - start);
base = RegexLine(Direction::Forward, cmd.substr(start, i - start));
} break;
case '?': {
addr.type = Command::Address::Type::SearchBackward;
i++;
uint64_t start = i;
while (true) {
@@ -71,9 +67,10 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add
else
i++;
}
addr.regex = cmd.substr(start, i - start);
base = RegexLine(Direction::Backward, cmd.substr(start, i - start));
} break;
case '+': {
base = Current();
i++;
skip_space();
uint64_t start = i;
@@ -84,9 +81,10 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add
}
if (start == i)
num = 1;
addr.offset += num;
offset += num;
} break;
case '-': {
base = Current();
i++;
skip_space();
uint64_t start = i;
@@ -97,19 +95,17 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add
}
if (start == i)
num = 1;
addr.offset -= num;
offset -= num;
} break;
default: {
if ('0' <= cmd[i] && cmd[i] <= '9') {
int64_t num = 0;
addr.type = Command::Address::Type::Number;
uint64_t num = 0;
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
num = num * 10 + (cmd[i] - '0');
i++;
}
addr.number = num;
base = Number(num);
} else {
addr.type = Command::Address::Type::None;
return;
}
break;
@@ -130,49 +126,8 @@ void BEd::parse_address(std::string_view cmd, uint64_t &i, Command::Address &add
}
if (start == i)
num = 1;
addr.offset += positive ? num : -num;
offset += positive ? num : -num;
skip_space();
}
}
void BEd::resolve_address(Command::Address addr, uint64_t *out_line) {
switch (addr.type) {
case Command::Address::Type::None:
break;
case Command::Address::Type::Current:
*out_line = line;
if (*out_line > vase.lines())
*out_line = vase.lines();
break;
case Command::Address::Type::Number:
*out_line = addr.number;
break;
case Command::Address::Type::Last:
*out_line = vase.lines();
break;
case Command::Address::Type::Mark:
*out_line = marks[addr.mark - 'a'];
if (!*out_line)
throw bed_error("Unset mark used.");
break;
case Command::Address::Type::SearchForward:
if (addr.regex == "")
addr.regex = last_regex;
last_regex = addr.regex;
// TODO: use vase.regex_search(regex, range, options);
break;
case Command::Address::Type::SearchBackward:
if (addr.regex == "")
addr.regex = last_regex;
last_regex = addr.regex;
// TODO
break;
}
if ((int64_t)*out_line + addr.offset < 0)
throw bed_error("Line position can't be negative.");
else
*out_line += addr.offset;
if (*out_line > vase.lines())
throw bed_error("Line position too high.");
}
} // namespace crib::commands::bed
} // namespace bed::internal::address
+50
View File
@@ -0,0 +1,50 @@
#include "bed.h"
#include "internal/address/address.h"
namespace bed::internal::address {
Address::Result Address::handle(BEd &ctx, std::string &cmd, uint64_t &i) {
bool prev_given = false;
Address prev;
Address curr;
while (i < cmd.size()) {
if (cmd[i] == '%') {
prev_given = true;
prev.base = Number(ctx.active->prev_range.start);
prev.offset = 0;
curr.base = Number(ctx.active->prev_range.end);
curr.offset = 0;
} else {
curr = Address(cmd, i);
}
if (i < cmd.size() && (cmd[i] == ',' || cmd[i] == ';')) {
if (std::holds_alternative<None>(curr.base)) {
if (cmd[i] == ',')
curr.base = Number(1);
else
curr.base = Current();
curr.offset = 0;
prev_given = false;
} else {
if (cmd[i] == ';')
ctx.active->jump(curr.resolve(ctx));
prev_given = true;
}
prev = std::move(curr);
} else {
if (std::holds_alternative<None>(curr.base)) {
if (std::holds_alternative<std::monostate>(prev.base))
return {};
if (prev_given) {
curr = prev;
} else {
curr.base = Last();
curr.offset = 0;
}
return {{prev.resolve(ctx), curr.resolve(ctx)}, 2};
}
return {{curr.resolve(ctx)}, 1};
}
}
return {};
}
}; // namespace bed::internal::address
+40
View File
@@ -0,0 +1,40 @@
#include "bed.h"
#include "internal/address/address.h"
namespace bed::internal::address {
uint64_t Address::resolve(BEd &ctx) {
uint64_t result = std::visit(
[&](auto const &addr) -> uint64_t {
uint64_t line = 0;
using T = std::decay_t<decltype(addr)>;
if constexpr (std::is_same_v<T, std::monostate>) {
throw address_error("empty address");
} else if constexpr (std::is_same_v<T, None>) {
throw address_error("no address");
} else if constexpr (std::is_same_v<T, Current>) {
line = ctx.active->line;
} else if constexpr (std::is_same_v<T, Last>) {
line = ctx.active->vase.lines();
} else if constexpr (std::is_same_v<T, Number>) {
line = addr.i;
} else {
throw ed_error("Unhandled address given.");
}
if (offset < 0 && line < (uint64_t)-offset)
throw address_error("Can't have negative addresses");
line += offset;
if (line > ctx.active->vase.lines())
throw address_error("Line number too high.");
return line;
},
base
);
return result + offset;
}
}; // namespace bed::internal::address
+161
View File
@@ -0,0 +1,161 @@
#include "internal/buffer/buffer.h"
namespace bed::internal::buffer {
Buffer::Buffer() : vase("/tmp") {
line = vase.lines();
modified = false;
}
Buffer::Buffer(std::string command) : vase(command, "/tmp") {
line = vase.lines();
if (!line)
return;
prev_range.start = 1;
prev_range.end = line;
modified = false;
}
Buffer::Buffer(std::filesystem::path path) : vase(path, "/tmp") {
line = vase.lines();
if (!line)
return;
prev_range.start = 1;
prev_range.end = line;
save_path = path;
modified = false;
}
void Buffer::load(std::string command) {
vase::Vase new_vase = vase::Vase(command, "/tmp");
vase = std::move(new_vase);
line = vase.lines();
if (!line) {
prev_range.start = 0;
prev_range.end = 0;
} else {
prev_range.start = 1;
prev_range.end = line;
}
modified = false;
}
void Buffer::load(std::filesystem::path path) {
vase::Vase new_vase = vase::Vase(path, "/tmp");
vase = std::move(new_vase);
line = vase.lines();
if (!line) {
prev_range.start = 0;
prev_range.end = 0;
} else {
prev_range.start = 1;
prev_range.end = line;
}
save_path = path;
modified = false;
}
void Buffer::jump(uint64_t n_line) {
if (n_line > vase.lines())
throw ed_error("Line number too high.");
line = n_line;
prev_range.start = line;
prev_range.end = line;
}
void Buffer::append(std::string text, uint64_t line) {
using namespace bed::internal::vase;
Point p = {line, 0};
if (!vase.lines()) {
text.pop_back();
} else if (line == vase.lines()) {
p.row--;
p.col = UINT64_MAX;
text = "\n" + text;
text.pop_back();
}
prev_range.start = p.row + 1;
vase.insert(&p, text);
prev_range.end = p.row + 1;
modified = true;
}
void Buffer::remove(uint64_t start_line, uint64_t end_line) {
vase.erase({{start_line - 1, 0}, {end_line, 0}});
prev_range.start = start_line;
prev_range.end = start_line;
modified = true;
}
void Buffer::join(uint64_t start_line, uint64_t end_line) {
vase.regex_search_replace(R"(\n)", {{start_line - 1, 0}, {end_line, 0}}, "", "g");
prev_range.start = start_line;
prev_range.end = start_line;
modified = true;
}
void Buffer::print(uint64_t start_line, uint64_t end_line) {
vase::Iterator it = vase.iterate(start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
std::cout << it.line << std::endl;
prev_range.start = start_line;
prev_range.end = end_line;
}
std::string Buffer::list_string(std::string_view s) {
uint32_t width = 80;
winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0)
width = ws.ws_col;
std::string out;
out.reserve(s.size());
const uint32_t max_width = width > 1 ? width - 1 : 1;
uint32_t column = 0;
auto append = [&](std::string_view text) {
if (column + text.size() > max_width) {
out += "\\\n";
column = 0;
}
out += text;
column += text.size();
};
for (unsigned char c : s) {
switch (c) {
case '\\':
append("\\\\");
break;
case '$':
append("\\$");
break;
case '\a':
append("\\a");
break;
case '\b':
append("\\b");
break;
case '\f':
append("\\f");
break;
case '\r':
append("\\r");
break;
case '\t':
append("\\t");
break;
case '\v':
append("\\v");
break;
default:
if (!std::isprint(c)) {
char buf[5];
std::snprintf(buf, sizeof(buf), "\\%03o", c);
append(buf);
} else {
append(std::string_view((const char *)&c, 1));
}
break;
}
}
out += '$';
return out;
}
} // namespace bed::internal::buffer
+104
View File
@@ -0,0 +1,104 @@
#include "internal/commands/commands.h"
#include "bed.h"
#include "internal/commands/suffixes.h"
namespace bed::internal::commands {
void Suffix::register_suffixes(BEd &ctx) {
ctx.suffixes['p' - 'a'] = Suffix{
.desc = "Prints current line.",
.handle = [](BEd &ctx) {
auto line = ctx.active->line;
ctx.active->print(line, line);
}
};
}
void Command::register_posix(BEd &ctx) {
ctx.no_op = Command{
.address_mode = Command::AddressMode::Single,
.suffix = Command::SuffixKind::None,
.desc = "Prints a line and jumps to it (default: .+1)",
.accept_zero = false,
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
uint64_t line;
if (addresses.size())
line = addresses[0];
else
line = ctx.active->line + 1;
if (line == 0)
throw ed_error("Line 0 is invalid.");
ctx.active->jump(line);
ctx.active->print(line, line);
}
};
ctx.eof_op = Command{
.address_mode = Command::AddressMode::None,
.suffix = Command::SuffixKind::None,
.desc = "Try quitting.",
.accept_zero = false,
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view) {
for (auto &[name, buffer] : ctx.buffers)
if (buffer->modified)
throw ed_error("Buffer " + name + " modified.");
throw fatal_error("", 0);
}
};
ctx.commands.insert(
"a",
Command{
.address_mode = Command::AddressMode::Single,
.suffix = Command::SuffixKind::Suffix,
.desc = "Append lines at address (default: .)",
.accept_zero = true,
.handle = [](BEd &, std::span<const uint64_t>, std::string_view) {
// TODO: start a text editing session, then append its stuff.
}
}
);
ctx.commands.insert(
"q",
Command{
.address_mode = Command::AddressMode::None,
.suffix = Command::SuffixKind::None,
.desc = "Try quitting.",
.accept_zero = false,
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view) {
for (auto &[name, buffer] : ctx.buffers)
if (buffer->modified)
throw ed_error("Buffer " + name + " modified.");
throw fatal_error("", 0);
}
}
);
ctx.commands.insert(
"E",
Command{
.address_mode = Command::AddressMode::None,
.suffix = Command::SuffixKind::Argument,
.desc = "Load a file into the current buffer.",
.accept_zero = true,
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view file) {
bool empty = false;
if (file.empty())
empty = true;
uint64_t i = 0;
while (i < file.length() && (file[i] == ' ' || file[i] == '\t'))
i++;
if (i >= file.size())
empty = true;
if (empty) {
if (ctx.active->save_path == "")
throw ed_error("Need filename!");
ctx.active->load(ctx.active->save_path);
} else if (file[i] == '!') {
file = file.substr(1);
ctx.active->load(file);
} else {
ctx.active->load(std::filesystem::path(file));
}
std::cout << ctx.active->vase.length() << std::endl;
}
}
);
}
} // namespace bed::internal::commands
+2 -2
View File
@@ -1,6 +1,6 @@
#include "internal/vase/vase.h"
namespace crib::internal::vase {
namespace bed::internal::vase {
AppendBuffer::AppendBuffer(std::filesystem::path base_dir) {
base_dir /= "tapp.XXXXXX";
char *s = strdup(base_dir.c_str());
@@ -69,4 +69,4 @@ const char *AppendBuffer::read(uint64_t pos) {
uint64_t AppendBuffer::length() {
return current_size;
}
} // namespace crib::internal::vase
} // namespace bed::internal::vase
+2 -2
View File
@@ -1,6 +1,6 @@
#include "internal/vase/vase.h"
namespace crib::internal::vase {
namespace bed::internal::vase {
OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) {
if (!std::filesystem::exists(base_dir) || !std::filesystem::is_directory(base_dir))
throw std::runtime_error("Swap directory does not exist or is not a directory.");
@@ -44,4 +44,4 @@ const char *OriginalBuffer::read(uint64_t pos) {
uint64_t OriginalBuffer::length() {
return len;
}
} // namespace crib::internal::vase
} // namespace bed::internal::vase
+2 -2
View File
@@ -1,6 +1,6 @@
#include "internal/vase/vase.h"
namespace crib::internal::vase {
namespace bed::internal::vase {
LineIterator::LineIterator(Shard *root, uint64_t line_num, Direction dir)
: it(root, dir), dir(dir) {
it.seek_line(line_num);
@@ -72,4 +72,4 @@ bool LineIterator::next(std::string *line) {
uint64_t LineIterator::byte_offset() {
return last_line_offset;
}
} // namespace crib::internal::vase
} // namespace bed::internal::vase
+2 -2
View File
@@ -1,6 +1,6 @@
#include "internal/vase/vase.h"
namespace crib::internal::vase {
namespace bed::internal::vase {
PetalIterator::PetalIterator(Shard *r, Direction dir)
: dir(dir), root(r) {
if (!root)
@@ -168,4 +168,4 @@ bool PetalIterator::next(const char **data, uint64_t *out_len) {
uint64_t PetalIterator::byte_offset() {
return last_offset;
}
} // namespace crib::internal::vase
} // namespace bed::internal::vase
+2 -2
View File
@@ -1,6 +1,6 @@
#include "internal/vase/vase.h"
namespace crib::internal::vase {
namespace bed::internal::vase {
std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
std::vector<ReplacePart> parts;
std::string constant;
@@ -156,4 +156,4 @@ std::vector<Range> Vase::regex_search(
result.push_back({point_of(match.start), point_of(match.end)});
return result;
}
} // namespace crib::internal::vase
} // namespace bed::internal::vase
+2 -2
View File
@@ -1,6 +1,6 @@
#include "internal/vase/vase.h"
namespace crib::internal::vase {
namespace bed::internal::vase {
std::vector<Vase::RegexMatch> Vase::_regex_search(
std::string_view pattern, Range range, std::string_view options
) {
@@ -244,4 +244,4 @@ std::vector<Vase::RegexMatch> Vase::_regex_search(
return results;
}
} // namespace crib::internal::vase
} // namespace bed::internal::vase
+2 -2
View File
@@ -1,6 +1,6 @@
#include "internal/vase/vase.h"
namespace crib::internal::vase {
namespace bed::internal::vase {
void Shard::retain(Shard *n) {
if (n)
n->refs++;
@@ -424,4 +424,4 @@ void Shard::dump(Shard *node, int depth) {
<< "\"\n";
}
}
} // namespace crib::internal::vase
} // namespace bed::internal::vase
+2 -2
View File
@@ -1,6 +1,6 @@
#include "internal/vase/vase.h"
namespace crib::internal::vase {
namespace bed::internal::vase {
Vase::Vase(std::filesystem::path path, std::filesystem::path swapdir)
: path(path), swapdir(swapdir) {
if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir))
@@ -507,4 +507,4 @@ void Vase::move_lines(Point *point, uint64_t amount, Direction dir) {
}
clamp(point);
}
} // namespace crib::internal::vase
} // namespace bed::internal::vase
+14 -2
View File
@@ -1,6 +1,18 @@
#include "cli.h"
#include "bed.h"
#include "pch.h"
int main(int argc, char *argv[]) {
return crib::cli::run(argc, argv);
std::vector<std::string> args(argv, argv + argc);
try {
bed::BEd ed(args);
ed.run();
} catch (bed::fatal_error &e) {
if (std::string_view(e.what()) != "")
std::cout << "Fatal error: " << e.what() << std::endl;
return e.code;
} catch (...) {
std::cout << "Unexpected error." << std::endl;
return 1;
}
return 0;
}