Major updates:

- A lotta cleanup
- BEd command parser rewrite
- Vase class removed
- Proper IO system.
- A lot more.
This commit is contained in:
2026-08-29 22:30:01 +01:00
parent d7278251ec
commit b8eac7b2da
42 changed files with 2153 additions and 1733 deletions
-146
View File
@@ -1,146 +0,0 @@
#include "internal/address/address.h"
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())
return;
switch (cmd[i]) {
case '.':
base = Current();
i++;
break;
case '$':
base = Last();
i++;
break;
case ']':
base = Block(Direction::Forward);
i++;
break;
case '[':
base = Block(Direction::Backward);
i++;
break;
case '\'': {
i++;
if (i < cmd.size()
&& (('a' <= cmd[i] && cmd[i] <= 'z') || ('A' <= cmd[i] && cmd[i] <= 'Z')))
i++;
else
throw address_error("Invalid mark.");
base = Mark(cmd[i - 1]);
} break;
case '/': {
i++;
uint64_t start = i;
while (true) {
if (i >= cmd.size())
break;
if (cmd[i] == '/')
break;
else if (cmd[i] == '\\')
i += 2;
else if (cmd[i] == '[' && i + 1 < cmd.size() && cmd[i + 1] == '[')
while (i < cmd.size() && !(cmd[i - 1] == ']' && cmd[i] == ']'))
i++;
else if (cmd[i] == '[')
while (i < cmd.size() && cmd[i] != ']')
i++;
else
i++;
}
base = Regex(Direction::Forward, cmd.substr(start, i - start));
if (i < cmd.size())
i++;
} break;
case '?': {
i++;
uint64_t start = i;
while (true) {
if (i >= cmd.size())
break;
if (cmd[i] == '?')
break;
else if (cmd[i] == '\\')
i += 2;
else if (cmd[i] == '[' && i + 1 < cmd.size() && cmd[i + 1] == '[')
while (i < cmd.size() && !(cmd[i - 1] == ']' && cmd[i] == ']'))
i++;
else if (cmd[i] == '[')
while (i < cmd.size() && cmd[i] != ']')
i++;
else
i++;
}
base = Regex(Direction::Backward, cmd.substr(start, i - start));
if (i < cmd.size())
i++;
} break;
case '+': {
base = Current();
i++;
skip_space();
uint64_t start = i;
int64_t num = 0;
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
num = num * 10 + (cmd[i] - '0');
i++;
}
if (start == i)
num = 1;
offset += num;
} break;
case '-': {
base = Current();
i++;
skip_space();
uint64_t start = i;
int64_t num = 0;
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
num = num * 10 + (cmd[i] - '0');
i++;
}
if (start == i)
num = 1;
offset -= num;
} break;
default: {
if ('0' <= cmd[i] && cmd[i] <= '9') {
uint64_t num = 0;
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
num = num * 10 + (cmd[i] - '0');
i++;
}
base = Number(num);
} else {
return;
}
break;
}
}
skip_space();
while (i < cmd.size() && (cmd[i] == '+' || cmd[i] == '-' || ('0' <= cmd[i] && cmd[i] <= '9'))) {
bool positive = cmd[i] != '-';
if (cmd[i] == '+' || cmd[i] == '-') {
i++;
skip_space();
}
uint64_t start = i;
int64_t num = 0;
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
num = num * 10 + (cmd[i] - '0');
i++;
}
if (start == i)
num = 1;
offset += positive ? num : -num;
skip_space();
}
}
} // namespace bed::internal::address
-58
View File
@@ -1,58 +0,0 @@
#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;
i++;
} 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] == ';') {
uint64_t resolved = curr.resolve(ctx);
ctx.active->jump(resolved);
curr.base = Number(resolved);
curr.offset = 0;
}
prev_given = true;
}
i++;
prev = std::move(curr);
} else {
if (std::holds_alternative<None>(curr.base)) {
if (std::holds_alternative<std::monostate>(prev.base))
return {{}, 0};
if (prev_given) {
curr = prev;
} else {
curr.base = Last();
curr.offset = 0;
}
return {{prev.resolve(ctx), curr.resolve(ctx)}, 2};
}
if (prev_given)
return {{prev.resolve(ctx), curr.resolve(ctx)}, 2};
return {{curr.resolve(ctx)}, 1};
}
}
return {};
}
}; // namespace bed::internal::address
-76
View File
@@ -1,76 +0,0 @@
#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 if constexpr (std::is_same_v<T, Mark>) {
line = ctx.active->marks.get(addr.m);
if (line == UINT64_MAX)
throw address_error("Mark not set.");
} else if constexpr (std::is_same_v<T, Regex>) {
std::string_view re = addr.re;
if (re.size() == 0)
re = ctx.last_regex;
if (re.size() == 0)
throw address_error("No regex given.");
if (addr.dir == Direction::Forward)
line = ctx.active->vase.find_next(re, ctx.active->line - 1) + 1;
else
line = ctx.active->vase.find_prev(re, ctx.active->line - 1) + 1;
ctx.last_regex = re;
} else if constexpr (std::is_same_v<T, Block>) {
uint64_t current_line = ctx.active->line;
if (ctx.active->vase.lines() > 0 && current_line == 0)
current_line = 1;
if (addr.dir == Direction::Forward) {
if (ctx.active->parser) {
uint64_t closing = ctx.active->parser->next_closing(current_line - 1);
if (closing == UINT64_MAX)
line = ctx.active->vase.lines();
else
line = closing + 1;
} else {
line = current_line + 10;
if (line > ctx.active->vase.lines())
line = ctx.active->vase.lines();
}
} else {
if (ctx.active->parser) {
line = ctx.active->parser->prev_opening(current_line - 1) + 1;
} else {
if (current_line > 10)
line = current_line - 10;
else
line = 0;
}
}
} 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;
}
}; // namespace bed::internal::address
+60 -96
View File
@@ -2,108 +2,70 @@
#include "bed.h"
namespace bed::internal::buffer {
Buffer::Buffer() : vase("/tmp") {
parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby());
line = vase.lines();
modified = false;
Buffer::Buffer(std::string name)
: state(Unmodified), root(nullptr), name(name) {
parser.emplace(root, lines(), syntax::ruby::lang_ruby()); // just for debug.
}
Buffer::Buffer(std::string command) : vase(command, "/tmp") {
parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby());
line = vase.lines();
if (!line)
return;
prev_range.start = 1;
prev_range.end = line;
modified = false;
Buffer::~Buffer() {
vase::Shard::release(root);
}
Buffer::Buffer(std::filesystem::path path) : vase(path, "/tmp") {
parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby());
line = vase.lines();
if (!line)
return;
prev_range.start = 1;
prev_range.end = line;
save_path = path;
modified = false;
uint64_t Buffer::lines() {
if (root)
return root->lines + 1;
return 0;
}
void Buffer::load(std::string command) {
vase::Vase new_vase = vase::Vase(command, "/tmp");
vase = std::move(new_vase);
parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby());
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);
parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby());
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;
}
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;
marks.insert(prev_range.start, prev_range.end);
void Buffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
ctx.prev.buffername = name;
ctx.prev.start = line;
ctx.prev.end = line + text->lines;
root = vase::insert(&ctx.append, root, text, line);
ctx.marks.insert(name, ctx.prev.start, ctx.prev.end);
if (parser)
parser->insert(vase, prev_range.start, prev_range.end);
modified = true;
parser->insert(root, ctx.prev.start, ctx.prev.end);
state = Modified;
}
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;
marks.erase(start_line, end_line - start_line + 1);
void Buffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
root = vase::erase(root, start_line, end_line);
ctx.prev.buffername = name;
ctx.prev.start = start_line;
ctx.prev.end = start_line;
ctx.marks.erase(name, start_line, end_line - start_line + 1);
if (parser)
parser->erase(vase, start_line, end_line - start_line + 1);
modified = true;
parser->erase(root, start_line, end_line - start_line + 1);
state = Modified;
}
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;
void Buffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
root = vase::substitute(&ctx.append, root, R"(\n)", start_line, end_line, "", "g");
ctx.prev.buffername = name;
ctx.prev.start = start_line;
ctx.prev.end = start_line;
ctx.marks.collapse(name, start_line, end_line - start_line);
if (parser)
parser->erase(root, start_line, end_line - start_line);
state = Modified;
}
void Buffer::substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) {
// make substitue return a list of modifications made.
root = vase::substitute(&ctx.append, root, regex, start_line, end_line, replacement, options);
/*prev_range.start = start_line;
prev_range.end = start_line;
marks.collapse(start_line, end_line - start_line);
if (parser)
parser->erase(vase, start_line, end_line - start_line);
modified = true;
parser->erase(vase, start_line, end_line - start_line);*/
state = Modified;
}
vase::Shard *Buffer::copy(uint64_t start_line, uint64_t end_line) {
return vase::copy(root, start_line, end_line);
}
inline void apply(std::ostream &out, const Highlight &hl) {
@@ -139,10 +101,11 @@ inline void reset(std::ostream &out) {
}
void Buffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
prev_range.start = start_line;
prev_range.end = end_line;
ctx.prev.buffername = name;
ctx.prev.start = start_line;
ctx.prev.end = end_line;
if (parser) {
std::optional<syntax::Parser::Iterator> it_o = parser->get_hl(vase, start_line - 1);
std::optional<syntax::Parser::Iterator> it_o = parser->get_hl(root, start_line - 1);
auto &it = *it_o;
while (start_line <= end_line) {
it.next();
@@ -174,24 +137,25 @@ void Buffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
++start_line;
}
} else {
vase::Iterator it = vase.iterate(start_line - 1, Direction::Forward);
vase::Iterator it(root, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
std::cout << it.line << std::endl;
}
}
void Buffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
prev_range.start = start_line;
prev_range.end = end_line;
ctx.prev.buffername = name;
ctx.prev.start = start_line;
ctx.prev.end = end_line;
uint8_t width = 1;
for (uint64_t n = end_line; n >= 10; n /= 10)
++width;
if (parser) {
std::optional<syntax::Parser::Iterator> it_o = parser->get_hl(vase, start_line - 1);
std::optional<syntax::Parser::Iterator> it_o = parser->get_hl(root, start_line - 1);
auto &it = *it_o;
while (start_line <= end_line) {
it.next();
std::cout << std::setw(width) << it.at << "\t";
std::cout << std::setw(width) << start_line << "\t";
const std::string &line = it.it->line;
const auto &tokens = it.tokens;
uint32_t cursor = 0;
@@ -220,7 +184,7 @@ void Buffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
++start_line;
}
} else {
vase::Iterator it = vase.iterate(start_line - 1, Direction::Forward);
vase::Iterator it(root, start_line - 1, Direction::Forward);
while (it.next() && start_line <= end_line)
std::cout << std::setw(width) << start_line++ << "\t" << it.line << std::endl;
}
-226
View File
@@ -1,226 +0,0 @@
#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, std::span<const uint64_t> addresses) {
ctx.active->print(ctx, addresses[0], addresses[1]);
}
};
ctx.suffixes['n' - 'a'] = Suffix{
.desc = "Prints current line with line number.",
.handle = [](BEd &ctx, std::span<const uint64_t> addresses) {
ctx.active->number_print(ctx, addresses[0], addresses[1]);
}
};
}
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(ctx, 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("Quitting", 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(
"j",
Command{
.address_mode = Command::AddressMode::Range,
.suffix = Command::SuffixKind::Suffix,
.desc = "Join a set of lines (default: .,.+1)",
.accept_zero = true,
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
uint64_t start_line = ctx.active->line;
uint64_t end_line = ctx.active->line + 1;
if (addresses.size() == 1)
return;
else if (addresses.size() == 2)
start_line = addresses[0], end_line = addresses[1];
ctx.active->join(start_line, end_line);
ctx.active->jump(start_line);
}
}
);
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("Quitting", 0);
}
}
);
ctx.commands.insert(
"Q",
Command{
.address_mode = Command::AddressMode::None,
.suffix = Command::SuffixKind::None,
.desc = "Force quitting.",
.accept_zero = false,
.handle = [](BEd &, std::span<const uint64_t>, std::string_view) {
throw fatal_error("Force Quitting", 0);
}
}
);
ctx.commands.insert(
"p",
Command{
.address_mode = Command::AddressMode::Range,
.suffix = Command::SuffixKind::Suffix,
.desc = "Print range (default .,.)",
.accept_zero = false,
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
uint64_t start_line = ctx.active->line;
uint64_t end_line = ctx.active->line;
if (addresses.size() == 1)
start_line = addresses[0], end_line = addresses[0];
else if (addresses.size() == 2)
start_line = addresses[0], end_line = addresses[1];
ctx.active->print(ctx, start_line, end_line);
ctx.active->jump(end_line);
}
}
);
ctx.commands.insert(
"n",
Command{
.address_mode = Command::AddressMode::Range,
.suffix = Command::SuffixKind::Suffix,
.desc = "Print range with line numbers (default .,.)",
.accept_zero = false,
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
uint64_t start_line = ctx.active->line;
uint64_t end_line = ctx.active->line;
if (addresses.size() == 1)
start_line = addresses[0], end_line = addresses[0];
else if (addresses.size() == 2)
start_line = addresses[0], end_line = addresses[1];
ctx.active->number_print(ctx, start_line, end_line);
ctx.active->jump(end_line);
}
}
);
ctx.commands.insert(
"=",
Command{
.address_mode = Command::AddressMode::Range,
.suffix = Command::SuffixKind::Suffix,
.desc = "Print line number(s)",
.accept_zero = true,
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
if (!addresses.size())
std::cout << ctx.active->vase.lines() << std::endl;
else if (addresses.size() == 1)
std::cout << addresses[0] << std::endl;
else
std::cout << addresses[0] << "," << addresses[1] << std::endl;
}
}
);
ctx.commands.insert(
"k",
Command{
.address_mode = Command::AddressMode::Single,
.suffix = Command::SuffixKind::Continuation,
.desc = "Mark a line.",
.accept_zero = true,
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view args) {
if (args.size() < 1 || args.size() > 2)
throw ed_error("Malformed mark command");
if (addresses.size())
ctx.active->marks.set(args[0], addresses[0]);
else
ctx.active->marks.set(args[0], ctx.active->line);
if (args.size() > 1)
ctx.suffix_handle(args[1]);
}
}
);
ctx.commands.insert(
"debug",
Command{
.address_mode = Command::AddressMode::None,
.suffix = Command::SuffixKind::None,
.desc = "",
.accept_zero = false,
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view) {
syntax::dump_events(ctx.active->parser->root);
}
}
);
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
+295
View File
@@ -0,0 +1,295 @@
#include "internal/functions/functions.h"
#include "bed.h"
#include "internal/functions/suffixes.h"
namespace bed::internal::functions {
void Suffix::register_suffixes(BEd &ctx) {
ctx.suffixes['p' - 'a'] = Suffix{
.desc = "Prints current line.",
.handle = [](BEd &ctx) {
auto &addr = ctx.current();
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
}
};
ctx.suffixes['n' - 'a'] = Suffix{
.desc = "Prints current line with line number.",
.handle = [](BEd &ctx) {
auto &addr = ctx.current();
ctx.buffer(addr.buffername).number_print(ctx, addr.number, addr.number);
}
};
}
void Function::register_posix(BEd &ctx) {
ctx.no_op = Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Prints a line and jumps to it.",
.default_address = ".+1",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Line>(addr_);
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
ctx.current() = addr;
}
};
ctx.eof_op = Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Try quitting.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
for (auto &[name, buffer] : ctx.buffers) {
if (buffer->state == buffer::Buffer::Modified) {
buffer->state = buffer::Buffer::Warned;
throw ed_error("Buffer " + name + " modified.");
}
}
throw fatal_error("Quitting", 0);
}
};
ctx.functions.insert(
"j",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Join a set of lines.",
.default_address = ".,.+1",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).join(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.start};
}
}
);
ctx.functions.insert(
"q",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Try quitting.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
for (auto &[name, buffer] : ctx.buffers) {
if (buffer->state == buffer::Buffer::Modified) {
buffer->state = buffer::Buffer::Warned;
throw ed_error("Buffer " + name + " modified.");
}
}
throw fatal_error("Quitting", 0);
}
}
);
ctx.functions.insert(
"Q",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Force quit.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
throw fatal_error("Force Quitting", 0);
}
}
);
ctx.functions.insert(
"p",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print range",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).print(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.end};
},
}
);
ctx.functions.insert(
"n",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print range with line numbers",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).number_print(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.end};
},
}
);
ctx.functions.insert(
"=",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print line numbers",
.default_address = "$",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
if (addr.start == addr.end)
std::cout << ':' << addr.buffername << ':' << addr.start << "\n";
else
std::cout << ':' << addr.buffername << ':' << addr.start << "," << addr.end << "\n";
},
}
);
ctx.functions.insert(
"k",
Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::Mark,
.input_mode = Function::InputMode::None,
.desc = "Mark a line.",
.default_address = ".",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Line>(addr_);
ctx.mark(std::get<char>(arg), addr);
}
}
);
ctx.functions.insert(
"e",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Try load a file into the current buffer.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto &addr = std::get<std::string>(addr_);
auto &buf = ctx.buffer(addr);
if (buf.state == buffer::Buffer::Modified) {
buf.state = buffer::Buffer::Warned;
throw ed_error("Buffer modified.");
}
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
s = vase::Shard::from_file(std::get<std::filesystem::path>(arg), true);
buf.save_path = std::get<std::filesystem::path>(arg);
} else if (std::holds_alternative<ShellArg>(arg)) {
s = vase::Shard::from_command(std::get<ShellArg>(arg).cmd.c_str(), true);
} else if (std::holds_alternative<std::monostate>(arg)) {
if (buf.save_path != "")
s = vase::Shard::from_file(buf.save_path, true);
else
throw ed_error("Need filename to load.");
}
try {
if (buf.lines())
buf.remove(ctx, 1, buf.lines());
buf.append(ctx, s, 0);
buf.state = buffer::Buffer::Unmodified;
} catch (...) {
vase::Shard::release(s);
throw;
}
std::cout << (s->length + 1) << std::endl;
vase::Shard::release(s);
ctx.current() = {addr, buf.lines()};
}
}
);
ctx.functions.insert(
"E",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Load a file into the current buffer.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto &addr = std::get<std::string>(addr_);
auto &buf = ctx.buffer(addr);
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
s = vase::Shard::from_file(std::get<std::filesystem::path>(arg), true);
buf.save_path = std::get<std::filesystem::path>(arg);
} else if (std::holds_alternative<ShellArg>(arg)) {
s = vase::Shard::from_command(std::get<ShellArg>(arg).cmd.c_str(), true);
} else if (std::holds_alternative<std::monostate>(arg)) {
if (buf.save_path != "")
s = vase::Shard::from_file(buf.save_path, true);
else
throw ed_error("Need filename to load.");
}
try {
if (buf.lines())
buf.remove(ctx, 1, buf.lines());
buf.append(ctx, s, 0);
buf.state = buffer::Buffer::Unmodified;
} catch (...) {
vase::Shard::release(s);
throw;
}
std::cout << (s->length + 1) << std::endl;
vase::Shard::release(s);
ctx.current() = {addr, buf.lines()};
}
}
);
ctx.functions.insert(
"H",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Toggle help mode.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
ctx.help_mode = !ctx.help_mode;
if (ctx.help_mode)
std::cout << ctx.last_help << std::endl;
}
}
);
ctx.functions.insert(
"h",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print last help message",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
std::cout << ctx.last_help << std::endl;
}
}
);
}
} // namespace bed::internal::functions
+10
View File
@@ -0,0 +1,10 @@
#include "bed.h"
#include "internal/io/command.h"
#include "internal/io/io.h"
namespace bed::internal::io {
std::pair<std::string, bool> IO::get_command(BEd &ctx) {
CommandIO cio(ctx, *this);
return cio.run();
}
} // namespace bed::internal::io
+138 -164
View File
@@ -1,182 +1,156 @@
#include "bed.h"
#include "internal/io/io.h"
namespace bed::internal::io {
std::pair<std::string, bool> IO::get_command(BEd &ctx) {
uint16_t start;
uint16_t height;
{
auto [row, col] = cursor_position();
auto [rows, cols] = terminal_size();
if (row > rows)
throw fatal_error("Invalid cursor position.", 1);
start = row;
height = rows - row + 1;
/*template <typename F>
static void for_each_cluster(std::string_view s, F &&f) {
unicode_width_state_t state;
unicode_width_init(&state);
size_t i = 0;
while (i < s.size()) {
unsigned char c = static_cast<unsigned char>(s[i]);
size_t bytes = 1;
int width = 0;
if (c < 128) {
width = unicode_width_process(&state, c);
} else {
uint_least32_t cp;
size_t decoded = grapheme_decode_utf8(s.data() + i, s.size() - i, &cp);
bytes = decoded > 0 ? decoded : 1;
width = unicode_width_process(&state, cp);
}
if (width < 0)
width = 0;
f(i, bytes, width);
i += bytes;
}
// uint16_t width = cols;
// TODO: support multiline wrapped commands.
// also handle ctrl+l to try and clear screen.
}
std::string cmd;
size_t cursor = 0;
static int display_width(std::string_view s) {
int w = 0;
for_each_cluster(s, [&](size_t, size_t, int cw) { w += cw; });
return w;
}
std::string prompt;
if (ctx.prompt_mode)
prompt = ctx.prompt(ctx);
static uint16_t count_clusters(std::string_view s) {
uint16_t n = 0;
for_each_cluster(s, [&](size_t, size_t, int) { ++n; });
return n;
}
auto redraw = [&] {
move_cursor(start, 1);
write_all(STDOUT_FILENO, "\x1b[2K", 4);
write_all(STDOUT_FILENO, prompt.c_str(), prompt.size());
write_all(STDOUT_FILENO, cmd.c_str(), cmd.size());
move_cursor(start, cursor + prompt.size() + 1);
};
static std::vector<uint16_t> wrap_offsets(std::string_view line, uint16_t avail) {
std::vector<uint16_t> offsets{0};
int col = 0;
for_each_cluster(line, [&](uint16_t i, uint16_t, int w) {
if (col + w > avail && col > 0) {
offsets.push_back(i);
col = 0;
}
col += w;
});
return offsets;
}
// The word under/before `byte_pos`, split on plain ASCII spaces. Used to
// pick what prefix to hand the suggestion trie.
// TODO: change to use libgrapheme word break here.
static std::string current_word(const std::string &line, size_t byte_pos) {
size_t start = (byte_pos == 0) ? std::string::npos : line.rfind(' ', byte_pos - 1);
start = (start == std::string::npos) ? 0 : start + 1;
if (byte_pos < start)
byte_pos = start;
return line.substr(start, byte_pos - start);
}*/
CommandIO::CommandIO(BEd &bed, IO &io) : bed(bed), io(io) {
prompt = bed.prompt(bed);
cursor = 0;
}
std::pair<std::string, bool> CommandIO::run() {
auto [row, col] = io.cursor_position();
auto [rows, cols] = io.terminal_size();
if (row > rows)
throw fatal_error("Invalid cursor position.", 1);
start = row;
height = rows - row + 1;
term_width = cols;
term_height = rows;
redraw();
bool eof = false;
while (true) {
KeyEvent ev = read_key();
if (ev.type == KeyEvent::KeyType::RESIZE) {
{
auto [row, col] = cursor_position();
auto [rows, cols] = terminal_size();
if (row > rows)
throw fatal_error("Invalid cursor position.", 1);
start = row;
height = rows - row + 1;
// main loop.
KeyEvent res;
bool running = true;
while (running) {
res = io.read_key();
switch (res.type) {
case KeyEvent::KeyType::EOF_:
running = false;
break;
case KeyEvent::KeyType::MOUSE:
case KeyEvent::KeyType::RESIZE:
break;
case KeyEvent::KeyType::CHAR:
switch (res.modifier) {
case KeyEvent::Modifier::SHIFT:
case KeyEvent::Modifier::ALT:
case KeyEvent::Modifier::CTRL_ALT:
case KeyEvent::Modifier::CTRL:
break;
case KeyEvent::Modifier::NONE:
if (res.text[0] == '\b' || res.text[0] == 0x7f) {
if (cursor > 0)
cmd.erase(--cursor, 1);
} else if (res.text[0] == '\n') {
running = false;
} else {
cmd.insert(cursor++, res.text);
}
break;
}
break;
case KeyEvent::KeyType::PASTE:
cmd.insert(cursor, res.text);
cursor += res.text.size();
break;
case KeyEvent::KeyType::SPECIAL:
switch (res.special_key) {
case KeyEvent::SpecialKey::UNKNOWN:
case KeyEvent::SpecialKey::UP:
case KeyEvent::SpecialKey::DOWN:
break;
case KeyEvent::SpecialKey::RIGHT:
if (cursor < cmd.size())
cursor++;
break;
case KeyEvent::SpecialKey::LEFT:
if (cursor > 0)
cursor--;
break;
case KeyEvent::SpecialKey::DELETE:
if (cursor < cmd.size())
cmd.erase(cursor, 1);
break;
}
redraw();
continue;
}
if (
(ev.type == KeyEvent::KeyType::CHAR
&& ev.modifier == KeyEvent::Modifier::CTRL
&& ev.text[0] == 'd')
|| ev.type == KeyEvent::KeyType::NONE
) {
eof = true;
break;
}
if (ev.type == KeyEvent::KeyType::CHAR
&& ev.modifier == KeyEvent::Modifier::CTRL
&& ev.text[0] == 'w') {
// TODO: delete previous word here.
if (cursor == 0)
continue;
cmd.erase(cursor -= 1, 1);
redraw();
continue;
}
if (ev.type == KeyEvent::KeyType::CHAR
&& ev.modifier == KeyEvent::Modifier::NONE) {
if (ev.text == "\r" || ev.text == "\n")
break;
if (ev.text.size() == 1
&& (ev.text[0] == '\x7f' || ev.text[0] == '\x08')) {
if (cursor == 0)
continue;
cmd.erase(cursor -= 1, 1);
redraw();
continue;
}
cmd.insert(cursor, ev.text);
cursor += ev.text.size();
redraw();
continue;
}
if (ev.type == KeyEvent::KeyType::SPECIAL) {
if (ev.special_key == KeyEvent::SpecialKey::LEFT) {
if (cursor == 0)
continue;
cursor -= 1;
} else if (ev.special_key == KeyEvent::SpecialKey::RIGHT) {
if (cursor >= cmd.size())
continue;
cursor += 1;
} else if (ev.special_key == KeyEvent::SpecialKey::DELETE) {
if (cursor >= cmd.size())
continue;
cmd.erase(cursor, 1);
}
redraw();
continue;
}
if (ev.type == KeyEvent::KeyType::PASTE) {
std::string text = ev.text;
if (!text.empty() && text.front() == '\n')
text.erase(text.begin());
if (!text.empty() && text.back() == '\n')
text.pop_back();
bool multiline = text.find('\n') != std::string::npos;
if (multiline) {
size_t line_count = 1 + std::count(text.begin(), text.end(), '\n');
if (height == 1) {
write_all(STDOUT_FILENO, "\n", 1);
--start;
++height;
}
bool go_ahead;
std::string msg =
"* paste "
+ std::to_string(line_count)
+ " lines into a single-line command? (y/n) ";
move_cursor(start + 1, 1);
write_all(STDOUT_FILENO, "\x1b[2K", 4);
write_all(STDOUT_FILENO, msg.c_str(), msg.size());
while (true) {
KeyEvent ev = read_key();
if (ev.type == KeyEvent::KeyType::CHAR && ev.text.size() == 1) {
if (ev.text[0] == 'y' || ev.text[0] == 'Y') {
go_ahead = true;
break;
}
if (ev.text[0] == 'n' || ev.text[0] == 'N') {
go_ahead = false;
break;
}
}
if (ev.type == KeyEvent::KeyType::NONE) {
go_ahead = false;
break;
}
if (ev.type == KeyEvent::KeyType::RESIZE) {
{
auto [row, col] = cursor_position();
auto [rows, cols] = terminal_size();
if (row - 1 > rows)
throw fatal_error("Invalid cursor position.", 1);
start = row - 1;
height = rows - row;
}
redraw();
move_cursor(start + 1, 1);
write_all(STDOUT_FILENO, "\x1b[2K", 4);
write_all(STDOUT_FILENO, msg.c_str(), msg.size());
continue;
}
}
move_cursor(start + 1, 1);
write_all(STDOUT_FILENO, "\x1b[2K", 4);
if (!go_ahead) {
redraw();
continue;
}
std::replace(text.begin(), text.end(), '\n', ' ');
}
cmd.insert(cursor, text);
cursor += text.size();
redraw();
continue;
}
redraw();
}
for (uint16_t i = 1; i < height; ++i) {
move_cursor(start + i, 1);
write_all(STDOUT_FILENO, "\x1b[2K", 4);
io.move_cursor(start + i, 1);
io.write("\x1b[2K", 4);
}
move_cursor(start, 1);
write_all(STDOUT_FILENO, "\n", 1);
return {cmd, eof};
io.move_cursor(start, 1);
io.write("\n", 1);
return {cmd, false};
}
void CommandIO::redraw() {
io.move_cursor(start, 1);
io.write("\x1b[2K", 4);
io.move_cursor(start, 1);
io.write(prompt);
io.write(cmd);
io.move_cursor(start, prompt.size() + cursor + 1);
}
} // namespace bed::internal::io
+3 -3
View File
@@ -216,7 +216,7 @@ KeyEvent IO::read_key() {
KeyEvent ev;
switch (res) {
case KeyEvent::ReadResult::EOF_:
ev.type = KeyEvent::KeyType::NONE;
ev.type = KeyEvent::KeyType::EOF_;
return ev;
case KeyEvent::ReadResult::RESIZE:
resized.store(false);
@@ -233,7 +233,7 @@ KeyEvent IO::read_key() {
ev.text = std::move(pasted);
break;
case KeyEvent::ReadResult::EOF_:
ev.type = KeyEvent::KeyType::NONE;
ev.type = KeyEvent::KeyType::EOF_;
break;
case KeyEvent::ReadResult::RESIZE:
resized.store(false);
@@ -244,7 +244,7 @@ KeyEvent IO::read_key() {
}
if (buf.size() >= 3 && buf[0] == '\x1b' && buf[1] == '[' && buf[2] == 'M') {
ev = parse_mouse(buf);
if (ev.type == KeyEvent::KeyType::NONE)
if (ev.type == KeyEvent::KeyType::EOF_)
continue;
return ev;
}
+8
View File
@@ -89,4 +89,12 @@ void IO::move_cursor(uint16_t row, uint16_t col) {
int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col);
write_all(STDOUT_FILENO, buf, n);
}
void IO::write(const char *buf, uint64_t n) {
write_all(STDOUT_FILENO, buf, n);
}
void IO::write(std::string_view s) {
write_all(STDOUT_FILENO, s.data(), s.size());
}
} // namespace bed::internal::io
+10 -8
View File
@@ -3,16 +3,18 @@
namespace bed::internal::io {
std::pair<std::string, bool> IO::get_text(BEd &) {
auto [row, col] = cursor_position();
auto [rows, cols] = terminal_size();
if (row > rows)
throw fatal_error("Invalid cursor position.", 1);
uint16_t start = row;
uint16_t height = rows - row + 1;
uint16_t start;
uint16_t height;
{
auto [row, col] = cursor_position();
auto [rows, cols] = terminal_size();
if (row > rows)
throw fatal_error("Invalid cursor position.", 1);
start = row;
height = rows - row + 1;
}
enable_mouse();
// uint16_t width = cols;
// TODO: support multiline wrapped commands.
// also handle ctrl+l to try and clear screen.
disable_mouse();
for (uint16_t i = 1; i < height; ++i) {
+215
View File
@@ -0,0 +1,215 @@
#include "bed.h"
#include "internal/parser/parser.h"
#include "internal/vase/vase.h"
namespace bed::internal::parser {
buffer::Line AddressPromise::resolve(BEd &ctx) {
buffer::Line result = std::visit(
[&](auto const &addr) -> buffer::Line {
if (!bufname.has_value())
throw ed_error("Buffer name can't be empty.");
buffer::Line line = {*bufname, 0};
auto &buf = ctx.buffer(line.buffername);
using T = std::decay_t<decltype(addr)>;
if constexpr (std::is_same_v<T, None>) {
throw ed_error("no address");
} else if constexpr (std::is_same_v<T, Current>) {
if (line.buffername == ctx.current().buffername)
line.number = ctx.current().number;
else
line.number = buf.lines();
} else if constexpr (std::is_same_v<T, Last>) {
line.number = buf.lines();
} else if constexpr (std::is_same_v<T, Number>) {
line.number = addr.i;
} else if constexpr (std::is_same_v<T, Mark>) {
line = ctx.marks.get(addr.m);
if (line.number == UINT64_MAX)
throw ed_error("Mark not set.");
} else if constexpr (std::is_same_v<T, Regex>) {
if (line.buffername == ctx.current().buffername)
line.number = ctx.current().number;
else
line.number = buf.lines();
if (buf.lines() > 0 && line.number == 0)
line.number = 1;
if (line.number == 0)
throw ed_error("Can't search empty buffer.");
std::string_view re = addr.re;
if (re.size() == 0)
re = ctx.last_regex;
if (re.size() == 0)
throw ed_error("No regex given.");
if (addr.dir == Direction::Forward)
line.number = vase::find_next(buf.root, re, line.number);
else
line.number = vase::find_prev(buf.root, re, line.number);
ctx.last_regex = re;
} else if constexpr (std::is_same_v<T, Block>) {
if (line.buffername == ctx.current().buffername)
line.number = ctx.current().number;
else
line.number = buf.lines();
if (buf.lines() > 0 && line.number == 0)
line.number = 1;
if (addr.dir == Direction::Forward) {
if (buf.parser.has_value()) {
uint64_t closing = buf.parser->next_closing(line.number - 1);
if (closing == UINT64_MAX)
line.number = buf.lines();
else
line.number = closing + 1;
} else {
line.number += 10;
if (line.number > buf.lines())
line.number = buf.lines();
}
} else {
if (buf.parser.has_value()) {
line.number = buf.parser->prev_opening(line.number - 1) + 1;
} else {
if (line.number > 10)
line.number -= 10;
else
line.number = 0;
}
}
} else {
throw ed_error("Unhandled address given.");
}
if (offset < 0 && line.number < (uint64_t)-offset)
throw ed_error("Can't have negative addresses");
line.number += offset;
if (line.number > ctx.buffer(line.buffername).lines())
throw ed_error("Line number too high.");
return line;
},
base
);
return result;
}
std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<AddressPromise> &list) {
std::string bufname = ctx.current().buffername;
bool prev_given = false;
bool prev_set = false;
AddressPromise prev;
for (std::size_t idx = 0; idx < list.size(); idx++) {
AddressPromise &curr = list[idx];
if (curr.bufname.has_value()) {
if (curr.bufname->empty()) {
bufname = ctx.current().buffername;
curr.bufname = bufname;
} else {
bufname = *curr.bufname;
}
} else {
curr.bufname = bufname;
}
bool is_final = idx + 1 == list.size();
if (!is_final) {
if (std::holds_alternative<None>(curr.base)) {
if (!curr.jumping)
curr.base = Number(1);
else
curr.base = Current();
curr.offset = 0;
prev_given = false;
} else if (std::holds_alternative<LastRange>(curr.base)) {
curr.bufname = ctx.prev.buffername;
curr.base = Number(ctx.prev.end);
prev_given = true;
} else {
prev_given = true;
}
if (curr.jumping) {
buffer::Line resolved = curr.resolve(ctx);
ctx.current() = resolved;
curr.bufname = resolved.buffername;
curr.base = Number(resolved.number);
curr.offset = 0;
}
prev = curr;
prev_set = true;
} else {
if (std::holds_alternative<None>(curr.base)) {
if (!prev_set)
return std::nullopt;
if (prev_given) {
curr.base = prev.base;
curr.offset = prev.offset;
} else {
curr.base = Last();
curr.offset = 0;
}
}
return curr.resolve(ctx);
}
}
return std::nullopt;
}
std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<AddressPromise> &list) {
std::string bufname = ctx.current().buffername;
bool prev_given = false;
bool prev_set = false;
AddressPromise prev;
for (std::size_t idx = 0; idx < list.size(); idx++) {
AddressPromise &curr = list[idx];
if (curr.bufname.has_value()) {
if (curr.bufname->empty()) {
bufname = ctx.current().buffername;
curr.bufname = bufname;
} else {
bufname = *curr.bufname;
}
} else {
curr.bufname = bufname;
}
bool is_final = idx + 1 == list.size();
if (!is_final) {
if (std::holds_alternative<None>(curr.base)) {
if (!curr.jumping)
curr.base = Number(1);
else
curr.base = Current();
curr.offset = 0;
prev_given = false;
} else if (std::holds_alternative<LastRange>(curr.base)) {
curr.bufname = ctx.prev.buffername;
curr.base = Number(ctx.prev.end);
prev_given = true;
} else {
prev_given = true;
}
if (curr.jumping) {
buffer::Line resolved = curr.resolve(ctx);
ctx.current() = resolved;
curr.bufname = resolved.buffername;
curr.base = Number(resolved.number);
curr.offset = 0;
}
prev = curr;
prev_set = true;
} else {
if (std::holds_alternative<None>(curr.base)) {
if (!prev_set)
return std::nullopt;
if (prev_given) {
curr.base = prev.base;
curr.offset = prev.offset;
} else {
curr.base = Last();
curr.offset = 0;
}
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
}
if (prev_given)
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
buffer::Line only = curr.resolve(ctx);
return buffer::Range(only, only);
}
}
return std::nullopt;
}
} // namespace bed::internal::parser
+514
View File
@@ -0,0 +1,514 @@
#include "internal/parser/parser.h"
#include "bed.h"
namespace bed::internal::parser {
char Parser::peek(uint16_t o) {
return i + o < cmd.size() ? cmd[i + o] : '\0';
}
std::string_view Parser::peek_str(uint16_t len) {
return cmd.substr(i, len);
}
void Parser::advance(uint16_t c) {
i += c;
}
void Parser::skip_ws() {
while (peek() == ' ' || peek() == '\t')
advance();
}
void Parser::locator(AddressPromise &addr) {
addr.base = AddressPromise::None{};
switch (peek()) {
case '.':
advance();
addr.base = AddressPromise::Current{};
break;
case '$':
advance();
addr.base = AddressPromise::Last{};
break;
case '%':
advance();
addr.base = AddressPromise::LastRange{};
break;
case '[':
advance();
addr.base = AddressPromise::Block{Direction::Backward};
break;
case ']':
advance();
addr.base = AddressPromise::Block{Direction::Forward};
break;
case '^':
advance();
addr.base = AddressPromise::Diagnostic{Direction::Backward};
break;
case '~':
advance();
addr.base = AddressPromise::Diagnostic{Direction::Forward};
break;
case '\'':
advance();
if (('a' <= peek() && peek() <= 'z')
|| ('A' <= peek() && peek() <= 'Z'))
addr.base = AddressPromise::Mark{peek()};
else
throw ed_error("Valid mark needed after \'");
advance();
break;
case '{': {
advance();
uint16_t j = 0;
std::string func;
std::string arg;
while (peek(j) != '}') {
if (peek(j) == '\0')
throw ed_error("Scripted address not terminated");
if (peek(j) == '\\')
++j;
if (peek(j) == ':') {
func = peek_str(j);
advance(j + 1);
j = 0;
continue;
}
++j;
}
if (func.size())
arg = peek_str(j);
else
func = peek_str(j);
addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)};
advance(j + 1);
} break;
case '/': {
advance();
uint64_t j = 0;
while (true) {
if (peek(j) == '\0')
break;
if (peek(j) == '/')
break;
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']')
j++;
else
j++;
}
addr.base = AddressPromise::Regex(
Direction::Forward,
std::string(peek_str(j))
);
advance(j + 1);
} break;
case '?': {
advance();
uint16_t j = 0;
while (true) {
if (peek(j) == '\0')
break;
if (peek(j) == '?')
break;
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']')
j++;
else
j++;
}
addr.base = AddressPromise::Regex(
Direction::Backward,
std::string(peek_str(j))
);
advance(j + 1);
} break;
case '<': {
advance();
uint16_t j = 0;
while (peek(j) != '>'
&& peek(j) != '<'
&& peek(j) != '\0')
j++;
switch (peek(j)) {
case '\0':
case '>':
addr.base = AddressPromise::SymbolDefinition{
std::string(peek_str(j))
};
break;
case '<':
addr.base = AddressPromise::SymbolReference{
Direction::Backward,
std::string(peek_str(j))
};
break;
}
advance(j + 1);
} break;
case '>': {
advance();
uint16_t j = 0;
while (peek(j) != '>' && peek(j) != '\0')
j++;
switch (peek(j)) {
case '\0':
throw ed_error("Unterminated symbol reference addressing.");
case '>':
addr.base = AddressPromise::SymbolReference{
Direction::Forward,
std::string(peek_str(j))
};
break;
}
advance(j + 1);
} break;
case '+': {
advance();
addr.base = AddressPromise::Current{};
uint16_t j = 0;
uint64_t num = 0;
while ('0' <= peek(j) && peek(j) <= '9') {
num = num * 10 + (peek(j) - '0');
j++;
}
if (j == 0)
num = 1;
advance(j);
addr.offset += num;
} break;
case '-': {
advance();
addr.base = AddressPromise::Current{};
uint16_t j = 0;
uint64_t num = 0;
while ('0' <= peek(j) && peek(j) <= '9') {
num = num * 10 + (peek(j) - '0');
j++;
}
if (j == 0)
num = 1;
advance(j);
addr.offset -= num;
} break;
default:
if ('0' <= peek() && peek() <= '9') {
uint64_t num = 0;
while ('0' <= peek() && peek() <= '9') {
num = num * 10 + (peek() - '0');
advance();
}
addr.base = AddressPromise::Number{num};
}
}
}
int64_t Parser::offset() {
int64_t offset = 0;
while (peek() == '+' || peek() == '-'
|| ('0' <= peek() && peek() <= '9')) {
bool positive = peek() != '-';
if (peek() == '+' || peek() == '-')
advance();
uint16_t j = 0;
int64_t num = 0;
while ('0' <= peek(j) && peek(j) <= '9')
num = num * 10 + (peek(j++) - '0');
if (j == 0)
num = 1;
advance(j);
offset += positive ? num : -num;
skip_ws();
}
skip_ws();
return offset;
}
void Parser::address(AddressPromise &addr) {
if (peek() == ':') {
advance();
uint16_t j = 0;
while (peek(j) != ':' && peek(j) != '\0')
j++;
addr.bufname = peek_str(j);
advance(j);
if (peek() == ':')
advance();
}
skip_ws();
if (peek() == '\0')
return;
locator(addr);
skip_ws();
addr.offset += offset();
}
void Parser::addresses(std::vector<AddressPromise> &addresses) {
skip_ws();
addresses.push_back({});
auto *addr = &addresses.back();
address(*addr);
while (peek() == ',' || peek() == ';') {
addr->jumping = peek() == ';';
advance();
skip_ws();
addresses.push_back({});
addr = &addresses.back();
address(*addr);
}
if (addresses.size() == 1
&& addr->offset == 0 && addr->bufname.has_value()
&& std::holds_alternative<AddressPromise::None>(addr->base))
addresses.pop_back();
}
void Parser::operation() {
if (peek() == '\0') {
command->function = &bed.no_op;
return;
}
uint64_t len = bed.functions.longest_match(peek_str());
if (len == 0)
throw ed_error("Function not found.");
functions::Function *function = bed.functions.get_ptr(peek_str(len));
advance(len);
command->function = function;
char suffix = '\0';
switch (command->function->argument_kind) {
case functions::Function::ArgumentKind::None:
break;
case functions::Function::ArgumentKind::Number:
skip_ws();
command->argument = offset();
break;
case functions::Function::ArgumentKind::Mark:
if (('a' <= peek() && peek() <= 'z')
|| ('A' <= peek() && peek() <= 'Z'))
command->argument = peek();
else
throw ed_error("Valid mark needed.");
advance();
break;
case functions::Function::ArgumentKind::Any:
command->argument = std::string(peek_str());
advance(peek_str().size());
break;
case functions::Function::ArgumentKind::Global: {
char delim;
std::string val;
switch (peek()) {
case '\0':
throw ed_error("Command needs a delimited value.");
case '{': {
advance();
delim = '}';
uint16_t j = 0;
while (peek(j) != '}' && peek(j) != '\0') {
if (peek(j) == '\\')
j++;
j++;
}
switch (peek(j)) {
case '\0':
throw ed_error("Unterminated {");
case '}':
val = peek_str(j);
break;
}
advance(j + 1);
} break;
case '<': {
advance();
delim = '<';
uint16_t j = 0;
while (peek(j) != '>' || peek(j) != '\0') {
if (peek(j) == '\\')
j++;
j++;
}
switch (peek(j)) {
case '\0':
throw ed_error("Unterminated <");
case '>':
val = peek_str(j);
break;
}
advance(j + 1);
} break;
case '^':
case '~':
advance();
delim = '^';
break;
default:
delim = peek();
advance();
uint64_t j = 0;
while (true) {
if (peek(j) == '\0')
break;
if (peek(j) == delim)
break;
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']')
j++;
else
j++;
}
val = peek_str(j);
advance(j + 1);
}
command->argument = functions::Function::GlobalArg(delim, std::move(val));
} break;
case functions::Function::ArgumentKind::File:
skip_ws();
switch (peek()) {
case '!':
advance();
command->argument = functions::Function::ShellArg(std::string(peek_str()));
advance(peek_str().size());
break;
case '\0':
command->argument = std::monostate();
break;
default:
command->argument = std::filesystem::path(peek_str());
advance(peek_str().size());
break;
}
break;
case functions::Function::ArgumentKind::Line:
command->argument = buffer::Line();
addresses(command->argument_addresses);
break;
case functions::Function::ArgumentKind::Range:
command->argument = buffer::Range();
addresses(command->argument_addresses);
break;
case functions::Function::ArgumentKind::Regex: {
char delim = peek();
if (delim == '\0')
throw ed_error("regex expected");
advance();
uint64_t j = 0;
while (true) {
if (peek(j) == '\0')
throw ed_error("Unterminated regex");
if (peek(j) == delim)
break;
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']')
j++;
else
j++;
}
std::string expression(peek_str(j));
advance(j + 1);
j = 0;
while (true) {
if (peek(j) == '\0')
break;
if (peek(j) == delim)
break;
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']')
j++;
else
j++;
}
std::string replacement;
if (peek(j) != '\0') {
replacement = peek_str(j);
advance(j + 1);
}
std::string options;
if (peek() != '\0') {
options = std::string(peek_str());
advance(peek_str().size());
}
std::erase_if(options, [&](char c) {
if (bed.suffixes[c - 'a'].has_value()) {
suffix = c;
return true;
}
return false;
});
command->argument = functions::Function::RegexArg(expression, replacement, options);
} break;
case functions::Function::ArgumentKind::Ruby:
command->argument = functions::Function::RubyArg(std::string(peek_str()));
advance(peek_str().size());
break;
case functions::Function::ArgumentKind::Shell:
command->argument = functions::Function::ShellArg(std::string(peek_str()));
advance(peek_str().size());
break;
}
if (!suffix) {
suffix = peek();
advance();
}
if (suffix) {
auto &s = bed.suffixes[suffix - 'a'];
if (s.has_value())
command->suffix = &s.value();
else
throw ed_error("Invalid suffix.");
}
}
void Parser::parse() {
skip_ws();
if (peek() == '@') {
advance();
command->temp_address = true;
} else {
command->temp_address = false;
}
skip_ws();
addresses(command->addresses);
operation();
skip_ws();
if (peek() != '\0')
throw ed_error("Malformed command");
}
Parser::Parser(
std::string_view cmd, BEd &bed, Command *command,
std::vector<io::Token> *tokens, CompletionContext *completion
) : bed(bed), cmd(cmd), command(command), tokens(tokens), completion(completion) {
i = 0;
}
Command Parser::get_command(std::string_view cmd, BEd &bed) {
Command c;
std::vector<io::Token> tokens;
CompletionContext completion;
Parser p(cmd, bed, &c, &tokens, &completion);
p.parse();
return c;
}
std::vector<AddressPromise> Parser::get_addresses(std::string_view cmd, BEd &bed) {
std::vector<AddressPromise> result;
std::vector<io::Token> tokens;
CompletionContext completion;
Parser p(cmd, bed, nullptr, &tokens, &completion);
p.addresses(result);
p.skip_ws();
if (p.peek() != '\0')
throw ed_error("Malformed address");
return result;
}
} // namespace bed::internal::parser
+9 -9
View File
@@ -79,7 +79,7 @@ static ParseState *build_tree(std::vector<ParseStateLeaf *> &leaves, size_t begi
return make_branch(left, right);
}
Parser::Parser(vase::Vase &vase, uint64_t lines, Language lang)
Parser::Parser(vase::Shard *vase, uint64_t lines, Language lang)
: root(nullptr), lang(lang) {
reset(vase, lines, lang);
}
@@ -88,7 +88,7 @@ Parser::~Parser() {
destroy_tree(root, lang);
}
void Parser::reset(vase::Vase &vase, uint64_t lines, Language lang_) {
void Parser::reset(vase::Shard *vase, uint64_t lines, Language lang_) {
destroy_tree(root, lang);
root = nullptr;
if (lines == 0)
@@ -158,7 +158,7 @@ ParseState *Parser::join_tree(ParseState *a, ParseState *b) {
return make_branch(a, b);
}
void Parser::erase(vase::Vase &vase, uint64_t start, uint64_t count) {
void Parser::erase(vase::Shard *vase, uint64_t start, uint64_t count) {
if (count == 0 || !root)
return;
auto [a, remaining] = split_tree(root, start);
@@ -168,7 +168,7 @@ void Parser::erase(vase::Vase &vase, uint64_t start, uint64_t count) {
modify(vase, start, 1);
}
void Parser::insert(vase::Vase &vase, uint64_t start, uint64_t count) {
void Parser::insert(vase::Shard *vase, uint64_t start, uint64_t count) {
if (count == 0)
return;
std::vector<ParseStateLeaf *> leaves;
@@ -191,7 +191,7 @@ void Parser::insert(vase::Vase &vase, uint64_t start, uint64_t count) {
modify(vase, start, count);
}
void Parser::modify(vase::Vase &vase, uint64_t target, uint64_t count) {
void Parser::modify(vase::Shard *vase, uint64_t target, uint64_t count) {
if (count == 0 || !root)
return;
std::vector<Token> tokens;
@@ -218,7 +218,7 @@ void Parser::modify(vase::Vase &vase, uint64_t target, uint64_t count) {
c = TreeCursor(root, 0, &offset);
}
}
vase::Iterator it = vase.iterate(at, Direction::Forward);
vase::Iterator it(vase, at, Direction::Forward);
uint64_t chunk_start = at;
uint64_t next_boundary = at + c.leaf->lines();
c.leaf->n = 0;
@@ -318,13 +318,13 @@ uint64_t Parser::prev_opening(uint64_t line) {
return 0;
}
std::optional<Parser::Iterator> Parser::get_hl(vase::Vase &vase, uint64_t target) {
std::optional<Parser::Iterator> Parser::get_hl(vase::Shard *vase, uint64_t target) {
if (!root)
return std::nullopt;
return Parser::Iterator(target, this, vase);
}
Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Vase &vase) : p(p) {
Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Shard *vase) : p(p) {
uint64_t offset;
TreeCursor c = TreeCursor(p->root, target, &offset);
at = target - offset;
@@ -346,7 +346,7 @@ Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Vase &vase) : p(p)
c = TreeCursor(p->root, 0, &offset);
}
}
it = vase.iterate(at, Direction::Forward);
it = vase::Iterator(vase, at, Direction::Forward);
while (at < target) {
it->next();
tokens.clear();
+39 -30
View File
@@ -1,7 +1,7 @@
#include "internal/vase/vase.h"
namespace bed::internal::vase {
std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
std::vector<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s) {
std::vector<ReplacePart> parts;
std::string constant;
auto flush_constant = [&]() {
@@ -13,11 +13,11 @@ std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
++lines;
++p;
}
uint64_t pos = append->append(constant.data(), (uint64_t)constant.size());
uint64_t pos = ap->append(constant.data(), (uint64_t)constant.size());
parts.push_back(
ReplacePart{
.type = ReplacePart::PartType::Constant,
.value = new Petal((uint64_t)constant.size(), lines, append, pos)
.value = new Petal((uint64_t)constant.size(), lines, ap, pos)
}
);
constant.clear();
@@ -61,15 +61,31 @@ std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
return parts;
}
void Vase::regex_search_replace(
std::string_view pattern, Range range,
Shard *substitute(
AppendStorage *ap, Shard *root,
std::string_view pattern, uint64_t start, uint64_t end,
std::string_view replace, std::string_view options
) {
std::vector<RegexMatch> matches = _regex_search(pattern, range, options);
if (matches.empty())
return;
if (!start || !end)
throw ed_error("Invalid range.");
start--;
end--;
if (!root)
throw ed_error("line range out of bounds");
uint64_t line_count = root->lines + 1;
if (start > end || end >= line_count)
throw ed_error("line range out of bounds");
uint64_t start_offset = offset_of(root, start);
uint64_t end_offset =
(end + 1 == line_count)
? root->length
: offset_of(root, end + 1);
std::vector<ReplacePart> replace_parts = parse_replace(replace);
std::vector<RegexMatch> matches = _regex_search(root, pattern, start_offset, end_offset, options);
if (matches.empty())
return root;
std::vector<ReplacePart> replace_parts = parse_replace(ap, replace);
std::vector<Shard *> pieces;
pieces.reserve(matches.size() * 2 + 1);
@@ -141,23 +157,13 @@ void Vase::regex_search_replace(
Shard *new_root = compact.empty() ? nullptr : Shard::build(compact.data(), 0, compact.size());
Shard::release(root);
root = new_root;
return new_root;
}
std::vector<Range> Vase::regex_search(
std::string_view pattern, Range range, std::string_view options
) {
std::vector<RegexMatch> matches = _regex_search(pattern, range, options);
if (matches.empty())
return {};
std::vector<Range> result;
result.reserve(matches.size());
for (auto match : matches)
result.push_back({point_of(match.start), point_of(match.end)});
return result;
}
uint64_t Vase::find_next(std::string_view pattern, uint64_t start) {
uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) {
if (start == 0 || start > root->lines)
throw ed_error("Invalid line number.");
start--;
std::vector<RegexMatch> results;
int errornumber;
PCRE2_SIZE erroroffset;
@@ -176,7 +182,7 @@ uint64_t Vase::find_next(std::string_view pattern, uint64_t start) {
pcre2_code_free(re);
throw ed_error("Can't create regex match data.");
}
uint64_t at = (start + 1) % lines();
uint64_t at = (start + 1) % (root->lines + 1);
LineIterator it(root, at, Direction::Forward);
std::string line;
while (it.next(&line)) {
@@ -196,7 +202,7 @@ uint64_t Vase::find_next(std::string_view pattern, uint64_t start) {
at = 0;
LineIterator it2(root, at, Direction::Forward);
while (it2.next(&line)) {
if (at >= start)
if (at > start)
break;
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
if (rc >= 0) {
@@ -216,7 +222,10 @@ uint64_t Vase::find_next(std::string_view pattern, uint64_t start) {
throw ed_error("No line matched.");
}
uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) {
uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start) {
if (start == 0 || start > root->lines)
throw ed_error("Invalid line number.");
start--;
std::vector<RegexMatch> results;
int errornumber;
PCRE2_SIZE erroroffset;
@@ -235,7 +244,7 @@ uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) {
pcre2_code_free(re);
throw ed_error("Can't create regex match data.");
}
uint64_t at = (start == 0 ? lines() : start) - 1;
uint64_t at = (start == 0 ? root->lines : start - 1);
LineIterator it(root, at, Direction::Backward);
std::string line;
while (it.next(&line)) {
@@ -252,10 +261,10 @@ uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) {
}
at--;
}
at = lines() - 1;
at = root->lines;
LineIterator it2(root, at, Direction::Backward);
while (it2.next(&line)) {
if (at <= start)
if (at < start)
break;
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
if (rc >= 0) {
+2 -5
View File
@@ -1,8 +1,8 @@
#include "internal/vase/vase.h"
namespace bed::internal::vase {
std::vector<Vase::RegexMatch> Vase::_regex_search(
std::string_view pattern, Range range, std::string_view options
std::vector<RegexMatch> _regex_search(
Shard *root, std::string_view pattern, uint64_t start_offset, uint64_t end_offset, std::string_view options
) {
bool global = false;
uint64_t flags = PCRE2_MULTILINE | PCRE2_UTF;
@@ -52,9 +52,6 @@ std::vector<Vase::RegexMatch> Vase::_regex_search(
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);
uint64_t start_offset = offset_of(range.start);
uint64_t end_offset = offset_of(range.end);
PetalIterator it(root, Direction::Forward);
it.seek_offset(start_offset);
+95 -32
View File
@@ -14,6 +14,7 @@ void Shard::release(Shard *n) {
release(((Branch *)n)->right);
delete (Branch *)n;
} else {
((Petal *)n)->source->release();
delete (Petal *)n;
}
}
@@ -200,28 +201,27 @@ Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
return node;
}
Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending) {
Shard *Shard::from_command(const char *cmd, bool posix_ending) {
auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd;
if (dest_fd == -1)
if (dest_fd == -1) {
delete o;
return nullptr;
}
FILE *pipe = popen(cmd, "r");
if (!pipe)
if (!pipe) {
delete o;
return nullptr;
}
std::vector<Shard *> pieces;
pieces.reserve(16);
uint64_t pos = 0;
char buf[PETAL_SIZE_MAX];
uint64_t buf_cursor = 0;
char ending[2] = {'\0', '\0'};
while (true) {
size_t got = fread(buf + buf_cursor, 1, sizeof(buf) - buf_cursor, pipe);
buf_cursor += got;
if (buf_cursor == PETAL_SIZE_MAX || feof(pipe)) {
if (buf_cursor == 0)
break;
@@ -244,6 +244,7 @@ Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending
}
if (!write_all(dest_fd, buf, buf_cursor)) {
pclose(pipe);
delete o;
return nullptr;
}
pieces.push_back(new Petal(buf_cursor, lines, o, pos));
@@ -251,19 +252,24 @@ Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending
}
if (feof(pipe))
break;
if (ferror(pipe))
if (ferror(pipe)) {
delete o;
return nullptr;
}
}
int status = pclose(pipe);
if (status == -1)
if (status == -1) {
delete o;
return nullptr;
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
delete o;
return nullptr;
if (pieces.empty())
}
if (pieces.empty()) {
delete o;
return nullptr;
}
if (posix_ending) {
if (ending[1] == '\n') {
Petal *last = (Petal *)pieces.back();
@@ -280,52 +286,58 @@ Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending
last->length--;
}
}
o->initialize();
if (pieces.size() == 1)
return pieces[0];
return build(pieces.data(), 0, pieces.size());
}
Shard *Shard::from_file(std::filesystem::path &path, OriginalBuffer *o, bool posix_ending) {
Shard *Shard::from_file(const std::filesystem::path &path, bool posix_ending) {
auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd;
if (dest_fd == -1)
if (dest_fd == -1) {
delete o;
return nullptr;
}
int src_fd = open(path.c_str(), O_RDONLY);
if (src_fd == -1)
if (src_fd == -1) {
delete o;
return nullptr;
}
uint64_t total = std::filesystem::file_size(path);
if (posix_ending && total > 0) {
char last;
if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1)
if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) {
delete o;
return nullptr;
}
if (last == '\n') {
total--;
if (total > 0) {
char s_last;
if (pread(src_fd, &s_last, 1, (off_t)(total - 1)) != 1)
if (pread(src_fd, &s_last, 1, (off_t)(total - 1)) != 1) {
delete o;
return nullptr;
}
if (s_last == '\r')
total--;
}
}
}
if (total == 0)
if (total == 0) {
delete o;
return nullptr;
}
std::vector<Shard *> pieces;
uint64_t pos = 0;
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
char buf[PETAL_SIZE_MAX];
while (pos < total) {
uint64_t want = std::min(PETAL_SIZE_MAX, total - pos);
ssize_t got = pread(src_fd, buf, want, pos);
if (got <= 0) {
close(src_fd);
delete o;
return nullptr;
}
uint64_t take = (uint64_t)got;
@@ -341,19 +353,70 @@ Shard *Shard::from_file(std::filesystem::path &path, OriginalBuffer *o, bool pos
}
if (!write_all(dest_fd, buf, take)) {
close(src_fd);
delete o;
return nullptr;
}
pieces.push_back(new Petal(take, lines, o, pos));
pos += take;
}
close(src_fd);
if (pieces.empty())
if (pieces.empty()) {
delete o;
return nullptr;
}
o->initialize();
if (pieces.size() == 1)
return pieces[0];
return build(pieces.data(), 0, pieces.size());
}
Shard *Shard::from_string(const char *data, uint64_t len, bool posix_ending) {
auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd;
if (dest_fd == -1 || data == nullptr) {
delete o;
return nullptr;
}
uint64_t total = len;
if (posix_ending && total > 0) {
if (data[total - 1] == '\n') {
total--;
if (total > 0 && data[total - 1] == '\r')
total--;
}
}
if (total == 0) {
delete o;
return nullptr;
}
std::vector<Shard *> pieces;
uint64_t pos = 0;
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
while (pos < total) {
uint64_t take = std::min(PETAL_SIZE_MAX, total - pos);
const char *buf = data + pos;
uint64_t lines = 0;
const char *p = buf;
const char *end = buf + take;
while (p < end) {
const void *nl = memchr(p, '\n', end - p);
if (!nl)
break;
lines++;
p = (const char *)nl + 1;
}
if (!write_all(dest_fd, buf, take)) {
delete o;
return nullptr;
}
pieces.push_back(new Petal(take, lines, o, pos));
pos += take;
}
if (pieces.empty()) {
delete o;
return nullptr;
}
o->initialize();
if (pieces.size() == 1)
return pieces[0];
return build(pieces.data(), 0, pieces.size());
@@ -1,7 +1,7 @@
#include "internal/vase/vase.h"
namespace bed::internal::vase {
AppendBuffer::AppendBuffer(std::filesystem::path base_dir) {
AppendStorage::AppendStorage(std::filesystem::path base_dir) {
base_dir /= "tapp.XXXXXX";
char *s = strdup(base_dir.c_str());
fd = mkstemp(s);
@@ -17,14 +17,14 @@ AppendBuffer::AppendBuffer(std::filesystem::path base_dir) {
throw std::runtime_error("mmap failed");
}
AppendBuffer::~AppendBuffer() {
AppendStorage::~AppendStorage() {
if (buf && buf != MAP_FAILED)
munmap(buf, allocated_capacity);
if (fd != -1)
close(fd);
}
void AppendBuffer::grow(uint64_t len) {
void AppendStorage::grow(uint64_t len) {
if (current_size + len > allocated_capacity) {
uint64_t new_capacity = allocated_capacity * 2;
if (new_capacity < current_size + len)
@@ -46,13 +46,13 @@ void AppendBuffer::grow(uint64_t len) {
}
}
uint64_t AppendBuffer::append(const char c) {
uint64_t AppendStorage::append(const char c) {
grow(1);
buf[current_size++] = c;
return current_size - 1;
}
uint64_t AppendBuffer::append(const char *text, uint64_t len) {
uint64_t AppendStorage::append(const char *text, uint64_t len) {
grow(len);
memcpy(buf + current_size, text, len);
uint64_t old_pos = current_size;
@@ -60,13 +60,13 @@ uint64_t AppendBuffer::append(const char *text, uint64_t len) {
return old_pos;
}
const char *AppendBuffer::read(uint64_t pos) {
const char *AppendStorage::read(uint64_t pos) {
if (pos >= current_size)
return nullptr;
return buf + pos;
}
uint64_t AppendBuffer::length() {
uint64_t AppendStorage::length() {
return current_size;
}
} // namespace bed::internal::vase
@@ -1,7 +1,7 @@
#include "internal/vase/vase.h"
namespace bed::internal::vase {
OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) {
OriginalStorage::OriginalStorage(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.");
base_dir /= "tbuf.XXXXXX";
@@ -13,14 +13,14 @@ OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) {
free(s);
}
OriginalBuffer::~OriginalBuffer() {
OriginalStorage::~OriginalStorage() {
if (buf)
munmap((char *)buf, len);
if (fd != -1)
close(fd);
}
void OriginalBuffer::initialize() {
void OriginalStorage::initialize() {
struct stat st;
if (fstat(fd, &st) == -1)
throw std::runtime_error("fstat failed");
@@ -35,13 +35,13 @@ void OriginalBuffer::initialize() {
fd = -1;
}
const char *OriginalBuffer::read(uint64_t pos) {
const char *OriginalStorage::read(uint64_t pos) {
if (pos >= len)
return nullptr;
return buf + pos;
}
uint64_t OriginalBuffer::length() {
uint64_t OriginalStorage::length() {
return len;
}
} // namespace bed::internal::vase
+135 -397
View File
@@ -1,108 +1,7 @@
#include "internal/vase/vase.h"
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))
throw std::runtime_error("Swap directory does not exist or is not a directory.");
append = new AppendBuffer(swapdir);
original = new OriginalBuffer(swapdir);
if (std::filesystem::is_regular_file(path))
root = Shard::from_file(path, original, posix_ending);
else
root = nullptr;
history_top = 0;
history.push_back(root);
Shard::retain(root);
}
Vase::Vase(std::string cmd, std::filesystem::path swapdir)
: path(""), swapdir(swapdir) {
if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir))
throw std::runtime_error("Swap directory does not exist or is not a directory.");
append = new AppendBuffer(swapdir);
original = new OriginalBuffer(swapdir);
root = Shard::from_command(cmd.c_str(), original, posix_ending);
history_top = 0;
history.push_back(root);
Shard::retain(root);
}
Vase::Vase(std::filesystem::path swapdir)
: path(""), swapdir(swapdir) {
if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir))
throw std::runtime_error("Swap directory does not exist or is not a directory.");
append = new AppendBuffer(swapdir);
original = new OriginalBuffer(swapdir);
root = nullptr;
history_top = 0;
history.push_back(root);
Shard::retain(root);
}
Vase::~Vase() {
Shard::release(root);
for (auto s : history)
Shard::release(s);
if (original)
delete original;
if (append)
delete append;
}
Vase::Vase(Vase &&other) noexcept
: original(other.original),
append(other.append),
root(other.root),
posix_ending(other.posix_ending),
using_crlf(other.using_crlf),
path(std::move(other.path)),
swapdir(std::move(other.swapdir)),
history(std::move(other.history)),
history_top(other.history_top) {
other.original = nullptr;
other.append = nullptr;
other.root = nullptr;
other.history_top = 0;
}
Vase &Vase::operator=(Vase &&other) noexcept {
if (this == &other)
return *this;
Shard::release(root);
for (auto s : history)
Shard::release(s);
delete original;
delete append;
original = other.original;
append = other.append;
root = other.root;
posix_ending = other.posix_ending;
using_crlf = other.using_crlf;
path = std::move(other.path);
swapdir = std::move(other.swapdir);
history = std::move(other.history);
history_top = other.history_top;
other.original = nullptr;
other.append = nullptr;
other.root = nullptr;
other.history_top = 0;
return *this;
}
uint64_t Vase::length() {
if (!root)
return 0;
return root->length + posix_ending;
}
uint64_t Vase::lines() {
if (!root)
return 0;
return root->lines + 1;
}
std::string Vase::to_string() {
std::string to_string(Shard *root) {
std::string out;
if (!root)
return out;
@@ -112,23 +11,19 @@ std::string Vase::to_string() {
uint64_t len;
while (it.next(&data, &len))
out.append(data, len);
if (posix_ending)
out.append("\n");
return out;
}
std::string Vase::to_string(Range range) {
clamp(&range.start);
clamp(&range.end);
std::string to_string(Shard *root, Range range) {
std::string out;
if (!root)
return out;
PetalIterator it(root, Direction::Forward);
uint64_t start = offset_of(range.start);
uint64_t start = offset_of(root, range.start);
it.seek_offset(start);
const char *data;
uint64_t len;
uint64_t remaining = offset_of(range.end) - start;
uint64_t remaining = offset_of(root, range.end) - start;
while (remaining && it.next(&data, &len)) {
uint64_t n = std::min(len, remaining);
out.append(data, n);
@@ -137,85 +32,10 @@ std::string Vase::to_string(Range range) {
return out;
}
Iterator Vase::iterate(uint64_t line, Direction dir) {
return Iterator(root, line, dir);
}
bool Vase::undo() {
if (history_top == 0)
return false;
Shard::release(root);
history_top--;
root = history[history_top];
Shard::retain(root);
return true;
}
bool Vase::redo() {
if (history_top + 1 >= history.size())
return false;
Shard::release(root);
history_top++;
root = history[history_top];
Shard::retain(root);
return true;
}
void Vase::snapshot() {
if (history[history_top] == root)
return;
while (history.size() > history_top + 1) {
Shard::release(history.back());
history.pop_back();
}
Shard::retain(root);
history.push_back(root);
history_top++;
}
void Vase::prune_history(uint64_t n) {
uint64_t keep = std::min(history.size(), n + 1);
if (keep == history.size())
return;
uint64_t remove = history.size() - keep;
for (uint64_t i = 0; i < remove; ++i)
Shard::release(history[i]);
history.erase(history.begin(), history.begin() + remove);
history_top -= remove;
}
bool Vase::save() {
if (!root)
return true;
if (path == "")
return false;
std::ofstream file(path, std::ios::binary);
if (!file)
return false;
PetalIterator it(root, Direction::Forward);
it.seek_offset(0);
const char *data;
uint64_t len;
while (it.next(&data, &len)) {
file.write(data, len);
if (!file)
return false;
}
if (posix_ending)
file.write("\n", 1);
if (!file)
return false;
return true;
}
bool Vase::save_swap() {
return false;
}
void Vase::insert(Point *point, char key) {
uint64_t pos = append->append(key);
Shard *inserted = new Petal(1, key == '\n', append, pos);
auto [left, right] = Shard::split(root, offset_of(*point));
Shard *insert(AppendStorage *ap, Shard *root, Point *point, char key) {
uint64_t pos = ap->append(key);
Shard *inserted = new Petal(1, key == '\n', ap, pos);
auto [left, right] = Shard::split(root, offset_of(root, *point));
Shard *left2 = Shard::append(left, inserted);
Shard::release(left);
Shard::release(inserted);
@@ -223,31 +43,28 @@ void Vase::insert(Point *point, char key) {
Shard::release(left2);
Shard::release(right);
Shard::release(root);
root = new_root;
if (key == '\n')
*point = {point->row + 1, 0};
else
point->col++;
return new_root;
}
void Vase::insert(Point *point, std::string_view str) {
insert(point, str.data(), str.size());
}
void Vase::insert(Point *point, const char *data, uint64_t len) {
Shard *insert(AppendStorage *ap, Shard *root, Point *point, const char *data, uint64_t len) {
while (len) {
uint64_t chunk_size = std::min<uint64_t>(len, PETAL_SIZE_MAX);
_insert(point, data, chunk_size);
_insert(ap, &root, point, data, chunk_size);
len -= chunk_size;
data += chunk_size;
}
return root;
}
void Vase::_insert(Point *point, const char *data, uint64_t len) {
void _insert(AppendStorage *ap, Shard **root, Point *point, const char *data, uint64_t len) {
if (len == 0)
return;
uint64_t offset = offset_of(*point);
uint64_t pos = append->append(data, len);
uint64_t offset = offset_of(*root, *point);
uint64_t pos = ap->append(data, len);
uint64_t lines = 0;
const char *start = data;
const char *last_line = start;
@@ -270,50 +87,23 @@ void Vase::_insert(Point *point, const char *data, uint64_t len) {
} else {
point->col += col;
}
Shard *inserted = new Petal(len, lines, append, pos);
auto [left, right] = Shard::split(root, offset);
Shard *inserted = new Petal(len, lines, ap, pos);
auto [left, right] = Shard::split(*root, offset);
Shard *left2 = Shard::append(left, inserted);
Shard::release(left);
Shard::release(inserted);
Shard *new_root = Shard::concat(left2, right);
Shard::release(left2);
Shard::release(right);
Shard::release(root);
root = new_root;
Shard::release(*root);
*root = new_root;
}
void Vase::erase(Point *point, uint64_t amount, Direction dir) {
if (amount == 0)
return;
Point start = *point;
Point end = *point;
if (dir == Direction::Forward)
move_clusters(&end, amount, Direction::Forward);
else
move_clusters(&start, amount, Direction::Backward);
uint64_t start_offset = offset_of(start);
uint64_t end_offset = offset_of(end);
if (start_offset > end_offset)
std::swap(start_offset, end_offset);
uint64_t count = end_offset - start_offset;
auto [a, b] = Shard::split(root, start_offset);
auto [d, c] = Shard::split(b, count);
Shard *new_root = Shard::concat(a, c);
Shard::release(a);
Shard::release(b);
Shard::release(c);
Shard::release(d);
Shard::release(root);
root = new_root;
if (dir == Direction::Backward)
*point = start;
}
void Vase::erase(Range range) {
Shard *erase(Shard *root, Range range) {
Point start = range.start;
Point end = range.end;
uint64_t start_offset = offset_of(start);
uint64_t end_offset = offset_of(end);
uint64_t start_offset = offset_of(root, start);
uint64_t end_offset = offset_of(root, end);
uint64_t count = end_offset - start_offset;
auto [a, b] = Shard::split(root, start_offset);
auto [d, c] = Shard::split(b, count);
@@ -323,188 +113,136 @@ void Vase::erase(Range range) {
Shard::release(c);
Shard::release(d);
Shard::release(root);
root = new_root;
return new_root;
}
void Vase::replace(Range range, std::string_view str) {
replace(range, str.data(), str.size());
Shard *replace(AppendStorage *ap, Shard *root, Range range, const char *data, uint64_t len) {
root = erase(root, range);
return insert(ap, root, &range.start, data, len);
}
void Vase::replace(Range range, const char *data, uint64_t len) {
erase(range);
insert(&range.start, data, len);
uint64_t offset_of(Shard *root, Point point) {
return offset_of(root, point.row) + point.col;
}
uint64_t Vase::offset_of(Point point) {
clamp(&point);
LineIterator it(root, point.row, Direction::Forward);
std::string line;
uint64_t offset_of(Shard *root, uint64_t line) {
if (!root)
return 0;
if (line > root->lines)
throw ed_error("line out of range");
uint64_t offset = 0;
if (it.next(&line)) {
const char *ptr = line.data();
uint64_t remaining = line.length();
while (point.col && remaining) {
uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining);
remaining -= next_len;
ptr += next_len;
offset += next_len;
point.col--;
Shard *curr = root;
while (curr->kind == Shard::Kind::Branch) {
auto *b = (Branch *)curr;
if (line <= b->left->lines) {
curr = b->left;
} else {
line -= b->left->lines;
offset += b->left->length;
curr = b->right;
}
}
return it.byte_offset() + offset;
auto *petal = (Petal *)curr;
if (line == 0)
return offset;
const char *text = petal->source->read(petal->pos);
uint64_t local = 0;
while (line--) {
const char *nl = (const char *)memchr(text + local, '\n', petal->length - local);
if (!nl)
throw std::runtime_error("leaf line count is wrong.");
local = (nl - text) + 1;
}
return offset + local;
}
Point Vase::point_of(uint64_t offset) {
PetalIterator it(root, Direction::Backward);
it.seek_offset(offset);
Point p;
p.row = it.global_line;
std::string line;
const char *chunk;
uint64_t len = 0;
if (!it.next(&chunk, &len))
return p;
while (true) {
#if defined(__GLIBC__) || defined(__APPLE__)
const char *nl = (const char *)memrchr(chunk, '\n', len);
#else
const char *nl = nullptr;
const char *p = chunk + len;
while (!nl && p != chunk)
if (*(--p) == '\n')
nl = p;
#endif
if (!nl) {
if (len && chunk[len - 1] == '\r')
--len;
line.insert(0, chunk, len);
if (!it.next(&chunk, &len))
break;
continue;
}
const char *end = chunk + len;
const char *start = nl + 1;
const char *line_end = end;
if (line_end > start && *(line_end - 1) == '\r')
--line_end;
line.insert(0, start, line_end - start);
break;
}
const char *ptr = line.data();
uint64_t remaining = line.length();
while (remaining) {
uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining);
remaining -= next_len;
ptr += next_len;
p.col++;
}
clamp(&p);
return p;
static Shard *newline(AppendStorage *ap) {
uint64_t pos = ap->append('\n');
return new Petal(1, true, ap, pos);
}
void Vase::move_clusters(Point *point, uint64_t amount, Direction dir) {
if (amount == 0)
return;
if (dir == Direction::Backward) {
LineIterator it(root, point->row, Direction::Backward);
while (amount) {
std::string line;
if (!it.next(&line))
return;
std::vector<uint64_t> clusters;
const char *ptr = line.data();
uint64_t remaining = line.size();
uint64_t byte = 0;
while (remaining) {
clusters.push_back(byte);
uint64_t len =
grapheme_next_character_break_utf8(ptr, remaining);
ptr += len;
remaining -= len;
byte += len;
}
while (amount && point->col) {
point->col--;
amount--;
}
if (amount == 0)
return;
if (point->row == 0)
return;
point->row--;
point->col = clusters.size();
amount--;
}
} else {
LineIterator it(root, point->row, Direction::Forward);
while (amount) {
std::string line;
if (!it.next(&line))
return;
if (point->col || amount) {
const char *ptr = line.data();
uint64_t remaining = line.size();
uint64_t col = 0;
while (col < point->col && remaining) {
uint64_t len = grapheme_next_character_break_utf8(ptr, remaining);
ptr += len;
remaining -= len;
col++;
}
while (amount && remaining) {
uint64_t len = grapheme_next_character_break_utf8(ptr, remaining);
ptr += len;
remaining -= len;
point->col++;
amount--;
}
}
if (amount) {
point->row++;
point->col = 0;
amount--;
}
}
}
clamp(point);
}
void Vase::clamp(Point *point) {
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line) {
if (!root) {
point->row = 0;
point->col = 0;
return;
if (line != 0)
throw ed_error("line out of range");
Shard::retain(text);
return text;
}
if (point->row > root->lines) {
point->row = root->lines;
point->col = UINT64_MAX;
if (line > root->lines + 1)
throw ed_error("line out of range");
Shard::retain(text);
Shard *nl = newline(ap);
if (line <= root->lines) {
uint64_t offset = offset_of(root, line);
auto [left, right] = Shard::split(root, offset);
Shard *middle = Shard::concat(text, nl);
Shard::release(text);
Shard::release(nl);
Shard *new_root = Shard::concat(left, middle);
Shard::release(left);
Shard::release(middle);
middle = Shard::concat(new_root, right);
Shard::release(new_root);
Shard::release(right);
Shard::release(root);
return middle;
}
LineIterator it(root, point->row, Direction::Forward);
std::string line;
uint64_t clusters = 0;
if (it.next(&line)) {
const char *ptr = line.data();
uint64_t remaining = line.length();
while (remaining) {
uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining);
remaining -= next_len;
ptr += next_len;
clusters++;
}
}
if (point->col > clusters)
point->col = clusters;
Shard *new_root = Shard::concat(root, nl);
Shard::release(root);
Shard::release(nl);
Shard *result = Shard::concat(new_root, text);
Shard::release(new_root);
Shard::release(text);
return result;
}
void Vase::move_lines(Point *point, uint64_t amount, Direction dir) {
if (dir == Direction::Forward) {
point->row += amount;
} else {
if (amount > point->row)
point->row = 0;
else
point->row -= amount;
}
clamp(point);
Shard *erase(Shard *root, uint64_t start, uint64_t end) {
if (!start || !end)
throw ed_error("Invalid range.");
start--;
end--;
if (!root)
throw ed_error("line range out of bounds");
uint64_t line_count = root->lines + 1;
if (start > end || end >= line_count)
throw ed_error("line range out of bounds");
uint64_t start_offset = offset_of(root, start);
uint64_t end_offset =
(end + 1 == line_count)
? root->length
: offset_of(root, end + 1);
auto [left, rest] = Shard::split(root, start_offset);
auto [middle, right] = Shard::split(rest, end_offset - start_offset);
Shard *new_root = Shard::concat(left, right);
Shard::release(left);
Shard::release(rest);
Shard::release(middle);
Shard::release(right);
Shard::release(root);
return new_root;
}
Shard *copy(Shard *root, uint64_t start, uint64_t end) {
if (!start || !end)
throw ed_error("Invalid range.");
start--;
end--;
if (!root)
throw ed_error("line range out of bounds");
uint64_t line_count = root->lines + 1;
if (start > end || end >= line_count)
throw ed_error("line range out of bounds");
uint64_t start_offset = offset_of(root, start);
uint64_t end_offset =
(end + 1 == line_count)
? root->length
: offset_of(root, end + 1);
auto [left, rest] = Shard::split(root, start_offset);
auto [middle, right] = Shard::split(rest, end_offset - start_offset);
Shard::release(left);
Shard::release(rest);
Shard::release(right);
Shard::release(root);
return middle;
}
} // namespace bed::internal::vase