Fixes.
This commit is contained in:
+41
-13
@@ -64,37 +64,41 @@ void BEd::handle(std::string_view cmd, bool eof) {
|
||||
return;
|
||||
}
|
||||
internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this);
|
||||
if (c.temp_address) {
|
||||
marks.get(251) = marks.get(250);
|
||||
temporary_current = true;
|
||||
}
|
||||
internal::buffer::Address address;
|
||||
switch (c.function->address_kind) {
|
||||
case internal::functions::Function::AddressKind::None: {
|
||||
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
|
||||
if (a.has_value()) {
|
||||
address = a->buffername;
|
||||
} else {
|
||||
if (!a.has_value()) {
|
||||
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||
a = internal::parser::AddressPromise::get_line(*this, vec);
|
||||
address = a->buffername;
|
||||
if (!a.has_value())
|
||||
a = current();
|
||||
}
|
||||
address = a->buffername;
|
||||
} break;
|
||||
case internal::functions::Function::AddressKind::Line: {
|
||||
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
|
||||
if (a.has_value()) {
|
||||
address = *a;
|
||||
} else {
|
||||
if (!a.has_value()) {
|
||||
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||
a = internal::parser::AddressPromise::get_line(*this, vec);
|
||||
address = *a;
|
||||
if (!a.has_value())
|
||||
a = current();
|
||||
}
|
||||
address = *a;
|
||||
} break;
|
||||
case internal::functions::Function::AddressKind::Range: {
|
||||
auto a = internal::parser::AddressPromise::get_range(*this, c.addresses);
|
||||
if (a.has_value()) {
|
||||
address = *a;
|
||||
} else {
|
||||
if (!a.has_value()) {
|
||||
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||
a = internal::parser::AddressPromise::get_range(*this, vec);
|
||||
address = *a;
|
||||
if (!a.has_value())
|
||||
a = internal::buffer::Range(current(), current());
|
||||
}
|
||||
address = *a;
|
||||
} break;
|
||||
}
|
||||
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
|
||||
@@ -110,12 +114,36 @@ void BEd::handle(std::string_view cmd, bool eof) {
|
||||
else
|
||||
c.argument = internal::buffer::Range(current(), current());
|
||||
}
|
||||
if (!c.function->accept_zero) {
|
||||
if (std::holds_alternative<internal::buffer::Line>(address)) {
|
||||
if (std::get<internal::buffer::Line>(address).number == 0)
|
||||
throw ed_error("Line number can't be zero.");
|
||||
} else if (std::holds_alternative<internal::buffer::Range>(address)) {
|
||||
auto r = std::get<internal::buffer::Range>(address);
|
||||
if (r.start == 0 || r.end == 0)
|
||||
throw ed_error("Line number can't be zero.");
|
||||
}
|
||||
}
|
||||
c.function->handle(*this, address, nullptr, c.argument, nullptr);
|
||||
if (c.suffix)
|
||||
c.suffix->handle(*this);
|
||||
if (c.temp_address)
|
||||
temporary_current = false;
|
||||
for (auto it = buffers.begin(); it != buffers.end();) {
|
||||
internal::buffer::Buffer *buf = it->second;
|
||||
if (buf->save_path.empty()
|
||||
&& buf->root == nullptr) {
|
||||
delete buf;
|
||||
it = buffers.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal::buffer::Buffer &BEd::buffer(const std::string &name) {
|
||||
if (name.empty())
|
||||
throw ed_error("can't have empty buffer name");
|
||||
auto it = buffers.find(name);
|
||||
if (it != buffers.end())
|
||||
return *it->second;
|
||||
@@ -125,7 +153,7 @@ internal::buffer::Buffer &BEd::buffer(const std::string &name) {
|
||||
}
|
||||
|
||||
internal::buffer::Line &BEd::current() {
|
||||
return marks.get(250);
|
||||
return marks.get(250 + temporary_current);
|
||||
}
|
||||
|
||||
void BEd::mark(char m, internal::buffer::Line line) {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
namespace bed::internal::buffer {
|
||||
Buffer::Buffer(std::string name)
|
||||
: state(Unmodified), root(nullptr), name(name) {
|
||||
parser.emplace(root, lines(), syntax::ruby::lang_ruby()); // just for debug.
|
||||
}
|
||||
|
||||
Buffer::~Buffer() {
|
||||
@@ -17,6 +16,24 @@ uint64_t Buffer::lines() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t Buffer::bytes() {
|
||||
if (root)
|
||||
return root->length + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Buffer::load(BEd &, vase::Shard *text) {
|
||||
try {
|
||||
vase::Shard::release(root);
|
||||
root = text;
|
||||
state = buffer::Buffer::Unmodified;
|
||||
parser.emplace(root, lines(), syntax::ruby::lang_ruby());
|
||||
} catch (...) {
|
||||
vase::Shard::release(text);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
|
||||
ctx.prev.buffername = name;
|
||||
ctx.prev.start = line;
|
||||
@@ -31,8 +48,8 @@ void Buffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
|
||||
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.prev.start = lines() ? 1 : 0;
|
||||
ctx.prev.end = lines();
|
||||
ctx.marks.erase(name, start_line, end_line - start_line + 1);
|
||||
if (parser)
|
||||
parser->erase(root, start_line, end_line - start_line + 1);
|
||||
@@ -40,7 +57,7 @@ void Buffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_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");
|
||||
root = vase::join(root, start_line, end_line);
|
||||
ctx.prev.buffername = name;
|
||||
ctx.prev.start = start_line;
|
||||
ctx.prev.end = start_line;
|
||||
@@ -54,7 +71,7 @@ void Buffer::substitute(
|
||||
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||
std::string ®ex, std::string &replacement, std::string &options
|
||||
) {
|
||||
// make substitue return a list of modifications made.
|
||||
// TODO: 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;
|
||||
|
||||
@@ -150,12 +150,13 @@ void Function::register_posix(BEd &ctx) {
|
||||
.default_address = "$",
|
||||
.accept_zero = true,
|
||||
.pre_text_mode = nullptr,
|
||||
.handle = [](BEd &, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
|
||||
.handle = [](BEd &ctx, 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.current() = {addr.buffername, addr.end};
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -204,17 +205,8 @@ void Function::register_posix(BEd &ctx) {
|
||||
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);
|
||||
buf.load(ctx, s);
|
||||
std::cout << buf.bytes() << std::endl;
|
||||
ctx.current() = {addr, buf.lines()};
|
||||
}
|
||||
}
|
||||
@@ -244,17 +236,8 @@ void Function::register_posix(BEd &ctx) {
|
||||
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);
|
||||
buf.load(ctx, s);
|
||||
std::cout << buf.bytes() << std::endl;
|
||||
ctx.current() = {addr, buf.lines()};
|
||||
}
|
||||
}
|
||||
@@ -291,5 +274,23 @@ void Function::register_posix(BEd &ctx) {
|
||||
}
|
||||
}
|
||||
);
|
||||
ctx.functions.insert(
|
||||
"P",
|
||||
Function{
|
||||
.address_kind = Function::AddressKind::None,
|
||||
.argument_kind = Function::ArgumentKind::None,
|
||||
.input_mode = Function::InputMode::None,
|
||||
.desc = "Toggle prompt.",
|
||||
.default_address = "",
|
||||
.accept_zero = false,
|
||||
.pre_text_mode = nullptr,
|
||||
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
|
||||
ctx.prompt_mode = !ctx.prompt_mode;
|
||||
if (ctx.prompt_mode && !ctx.prompt) {
|
||||
ctx.prompt = [](BEd &) { return "*"; };
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
} // namespace bed::internal::functions
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#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
|
||||
+5
-151
@@ -1,156 +1,10 @@
|
||||
#include "internal/ui/command.h"
|
||||
#include "bed.h"
|
||||
#include "internal/io/io.h"
|
||||
|
||||
namespace bed::internal::io {
|
||||
/*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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// 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;
|
||||
}
|
||||
break;
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
||||
for (uint16_t i = 1; i < height; ++i) {
|
||||
io.move_cursor(start + i, 1);
|
||||
io.write("\x1b[2K", 4);
|
||||
}
|
||||
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);
|
||||
std::pair<std::string, bool> IO::get_command(BEd &ctx) {
|
||||
ui::CommandIO cio(ctx, *this);
|
||||
return cio.run();
|
||||
}
|
||||
} // namespace bed::internal::io
|
||||
|
||||
+19
-11
@@ -2,7 +2,8 @@
|
||||
|
||||
namespace bed::internal::io {
|
||||
termios IO::orig_termios{};
|
||||
bool IO::cleaned = false;
|
||||
termios IO::raw_termios{};
|
||||
bool IO::cleaned = true;
|
||||
volatile std::atomic_bool IO::resized(false);
|
||||
|
||||
IO::IO() {
|
||||
@@ -14,16 +15,13 @@ IO::IO() {
|
||||
sa.sa_flags = 0;
|
||||
if (sigaction(SIGWINCH, &sa, nullptr) == -1)
|
||||
throw fatal_error("Can't install SIGWINCH handler.", 1);
|
||||
struct termios raw = orig_termios;
|
||||
raw.c_iflag &= ~(BRKINT | ISTRIP | IXON);
|
||||
raw.c_cflag |= (CS8);
|
||||
raw.c_lflag &= ~(ECHO | ICANON | ISIG);
|
||||
raw.c_cc[VMIN] = 1;
|
||||
raw.c_cc[VTIME] = 0;
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
|
||||
throw fatal_error("Can't set terminal state.", 1);
|
||||
std::string os = "\x1b[?2004h";
|
||||
write_all(STDOUT_FILENO, os.c_str(), os.size());
|
||||
raw_termios = orig_termios;
|
||||
raw_termios.c_iflag &= ~(BRKINT | ISTRIP | IXON);
|
||||
raw_termios.c_cflag |= (CS8);
|
||||
raw_termios.c_lflag &= ~(ECHO | ICANON | ISIG);
|
||||
raw_termios.c_cc[VMIN] = 1;
|
||||
raw_termios.c_cc[VTIME] = 0;
|
||||
enable_raw();
|
||||
atexit(cleanup);
|
||||
}
|
||||
|
||||
@@ -70,6 +68,16 @@ std::pair<uint16_t, uint16_t> IO::cursor_position() {
|
||||
return {(uint16_t)row, (uint16_t)col};
|
||||
}
|
||||
|
||||
void IO::enable_raw() {
|
||||
if (!cleaned)
|
||||
return;
|
||||
std::string os = "\x1b[?2004h";
|
||||
write_all(STDOUT_FILENO, os.c_str(), os.size());
|
||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_termios) == -1)
|
||||
throw fatal_error("Can't set raw terminal state.", 1);
|
||||
cleaned = false;
|
||||
}
|
||||
|
||||
void IO::cleanup() {
|
||||
if (cleaned)
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
#include "bed.h"
|
||||
#include "internal/parser/parser.h"
|
||||
|
||||
namespace bed::internal::parser {
|
||||
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();
|
||||
}
|
||||
} // namespace bed::internal::parser
|
||||
@@ -6,13 +6,16 @@ namespace bed::internal::parser {
|
||||
buffer::Line AddressPromise::resolve(BEd &ctx) {
|
||||
buffer::Line result = std::visit(
|
||||
[&](auto const &addr) -> buffer::Line {
|
||||
if (!bufname.has_value())
|
||||
if (!bufname.has_value() || bufname->empty())
|
||||
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");
|
||||
if (line.buffername == ctx.current().buffername)
|
||||
line.number = ctx.current().number;
|
||||
else
|
||||
line.number = buf.lines();
|
||||
} else if constexpr (std::is_same_v<T, Current>) {
|
||||
if (line.buffername == ctx.current().buffername)
|
||||
line.number = ctx.current().number;
|
||||
@@ -133,15 +136,18 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
|
||||
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;
|
||||
if (prev_set) {
|
||||
if (prev_given) {
|
||||
curr.base = prev.base;
|
||||
curr.offset = prev.offset;
|
||||
} else {
|
||||
curr.base = Last();
|
||||
curr.offset = 0;
|
||||
}
|
||||
}
|
||||
} else if (std::holds_alternative<LastRange>(curr.base)) {
|
||||
curr.bufname = ctx.prev.buffername;
|
||||
curr.base = Number(ctx.prev.end);
|
||||
}
|
||||
return curr.resolve(ctx);
|
||||
}
|
||||
@@ -193,16 +199,18 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
|
||||
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;
|
||||
if (prev_set) {
|
||||
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));
|
||||
}
|
||||
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
|
||||
} else if (std::holds_alternative<LastRange>(curr.base)) {
|
||||
return ctx.prev;
|
||||
}
|
||||
if (prev_given)
|
||||
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
#include "bed.h"
|
||||
#include "internal/parser/parser.h"
|
||||
|
||||
namespace bed::internal::parser {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
} // namespace bed::internal::parser
|
||||
@@ -19,455 +19,6 @@ void Parser::skip_ws() {
|
||||
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() == '@') {
|
||||
@@ -486,14 +37,14 @@ void Parser::parse() {
|
||||
|
||||
Parser::Parser(
|
||||
std::string_view cmd, BEd &bed, Command *command,
|
||||
std::vector<io::Token> *tokens, CompletionContext *completion
|
||||
std::vector<ui::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;
|
||||
std::vector<ui::Token> tokens;
|
||||
CompletionContext completion;
|
||||
Parser p(cmd, bed, &c, &tokens, &completion);
|
||||
p.parse();
|
||||
@@ -502,7 +53,7 @@ Command Parser::get_command(std::string_view cmd, BEd &bed) {
|
||||
|
||||
std::vector<AddressPromise> Parser::get_addresses(std::string_view cmd, BEd &bed) {
|
||||
std::vector<AddressPromise> result;
|
||||
std::vector<io::Token> tokens;
|
||||
std::vector<ui::Token> tokens;
|
||||
CompletionContext completion;
|
||||
Parser p(cmd, bed, nullptr, &tokens, &completion);
|
||||
p.addresses(result);
|
||||
|
||||
@@ -1,42 +1,6 @@
|
||||
#include "internal/syntax/parser.h"
|
||||
|
||||
namespace bed::internal::syntax {
|
||||
void dump_events(ParseState *root) {
|
||||
if (!root)
|
||||
return;
|
||||
uint64_t offset = 0;
|
||||
TreeCursor c(root, 0, &offset);
|
||||
uint64_t line_offset = 0;
|
||||
int depth = 0;
|
||||
while (c.leaf) {
|
||||
auto *leaf = c.leaf;
|
||||
for (uint32_t i = 0; i < leaf->n; ++i) {
|
||||
uint32_t block = leaf->blocks[i];
|
||||
uint32_t pos = block & ParseStateLeaf::LINE_MASK;
|
||||
bool closing = block & ParseStateLeaf::IS_CLOSING;
|
||||
if (closing) {
|
||||
if (depth > 0)
|
||||
--depth;
|
||||
else
|
||||
std::cout << "!! UNMATCHED CLOSE !! ";
|
||||
}
|
||||
std::cout << std::string(static_cast<size_t>(depth) * 2, ' ')
|
||||
<< (closing ? "}" : "{")
|
||||
<< " line " << (line_offset + pos)
|
||||
<< '\n';
|
||||
if (!closing)
|
||||
++depth;
|
||||
}
|
||||
line_offset += leaf->lines();
|
||||
c.next();
|
||||
}
|
||||
if (depth != 0) {
|
||||
std::cout << "!! UNBALANCED: depth = "
|
||||
<< depth
|
||||
<< " !!\n";
|
||||
}
|
||||
}
|
||||
|
||||
static void destroy_tree(ParseState *node, Language &lang) {
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#include "internal/ui/command.h"
|
||||
#include "bed.h"
|
||||
|
||||
namespace bed::internal::ui {
|
||||
/*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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 &io) : bed(bed), io(io) {
|
||||
if (bed.prompt_mode)
|
||||
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();
|
||||
|
||||
// main loop.
|
||||
io::KeyEvent res;
|
||||
bool running = true;
|
||||
while (running) {
|
||||
res = io.read_key();
|
||||
switch (res.type) {
|
||||
case io::KeyEvent::KeyType::EOF_:
|
||||
running = false;
|
||||
break;
|
||||
case io::KeyEvent::KeyType::MOUSE:
|
||||
case io::KeyEvent::KeyType::RESIZE:
|
||||
break;
|
||||
case io::KeyEvent::KeyType::CHAR:
|
||||
switch (res.modifier) {
|
||||
case io::KeyEvent::Modifier::SHIFT:
|
||||
case io::KeyEvent::Modifier::ALT:
|
||||
case io::KeyEvent::Modifier::CTRL_ALT:
|
||||
case io::KeyEvent::Modifier::CTRL:
|
||||
break;
|
||||
case io::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 io::KeyEvent::KeyType::PASTE:
|
||||
cmd.insert(cursor, res.text);
|
||||
cursor += res.text.size();
|
||||
break;
|
||||
case io::KeyEvent::KeyType::SPECIAL:
|
||||
switch (res.special_key) {
|
||||
case io::KeyEvent::SpecialKey::UNKNOWN:
|
||||
case io::KeyEvent::SpecialKey::UP:
|
||||
case io::KeyEvent::SpecialKey::DOWN:
|
||||
break;
|
||||
case io::KeyEvent::SpecialKey::RIGHT:
|
||||
if (cursor < cmd.size())
|
||||
cursor++;
|
||||
break;
|
||||
case io::KeyEvent::SpecialKey::LEFT:
|
||||
if (cursor > 0)
|
||||
cursor--;
|
||||
break;
|
||||
case io::KeyEvent::SpecialKey::DELETE:
|
||||
if (cursor < cmd.size())
|
||||
cmd.erase(cursor, 1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
redraw();
|
||||
}
|
||||
|
||||
for (uint16_t i = 1; i < height; ++i) {
|
||||
io.move_cursor(start + i, 1);
|
||||
io.write("\x1b[2K", 4);
|
||||
}
|
||||
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::ui
|
||||
@@ -80,20 +80,15 @@ Shard *substitute(
|
||||
(end + 1 == line_count)
|
||||
? root->length
|
||||
: offset_of(root, end + 1);
|
||||
|
||||
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);
|
||||
|
||||
Shard *remaining = root;
|
||||
Shard::retain(remaining);
|
||||
uint64_t cursor = 0;
|
||||
|
||||
for (const RegexMatch &match : matches) {
|
||||
uint64_t gap = match.start - cursor;
|
||||
if (gap > 0) {
|
||||
@@ -102,11 +97,9 @@ Shard *substitute(
|
||||
pieces.push_back(keep);
|
||||
remaining = rest;
|
||||
}
|
||||
|
||||
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
|
||||
Shard::release(remaining);
|
||||
remaining = rest2;
|
||||
|
||||
for (size_t i = 0; i < replace_parts.size(); ++i) {
|
||||
const ReplacePart &part = replace_parts[i];
|
||||
switch (part.type) {
|
||||
@@ -141,11 +134,9 @@ Shard *substitute(
|
||||
cursor = match.end;
|
||||
}
|
||||
pieces.push_back(remaining);
|
||||
|
||||
for (auto &part : replace_parts)
|
||||
if (part.type == ReplacePart::PartType::Constant)
|
||||
Shard::release(std::get<Shard *>(part.value));
|
||||
|
||||
std::vector<Shard *> compact;
|
||||
compact.reserve(pieces.size());
|
||||
for (Shard *p : pieces) {
|
||||
@@ -154,7 +145,6 @@ Shard *substitute(
|
||||
else if (p)
|
||||
Shard::release(p);
|
||||
}
|
||||
|
||||
Shard *new_root = compact.empty() ? nullptr : Shard::build(compact.data(), 0, compact.size());
|
||||
Shard::release(root);
|
||||
return new_root;
|
||||
|
||||
+11
-52
@@ -1,3 +1,4 @@
|
||||
#include "internal/io/io.h"
|
||||
#include "internal/vase/vase.h"
|
||||
|
||||
namespace bed::internal::vase {
|
||||
@@ -192,10 +193,10 @@ Shard *Shard::append(Shard *root, Shard *leaf) {
|
||||
Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
|
||||
if (hi - lo == 1)
|
||||
return pieces[lo];
|
||||
size_t mid = lo + (hi - lo) / 2;
|
||||
uint64_t mid = lo + (hi - lo) / 2;
|
||||
Shard *left = build(pieces, lo, mid);
|
||||
Shard *right = build(pieces, mid, hi);
|
||||
Shard *node = new Branch(left, right);
|
||||
Shard *node = Shard::concat(left, right);
|
||||
Shard::release(left);
|
||||
Shard::release(right);
|
||||
return node;
|
||||
@@ -208,9 +209,11 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
|
||||
delete o;
|
||||
return nullptr;
|
||||
}
|
||||
io::IO::cleanup();
|
||||
FILE *pipe = popen(cmd, "r");
|
||||
if (!pipe) {
|
||||
delete o;
|
||||
io::IO::enable_raw();
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<Shard *> pieces;
|
||||
@@ -245,6 +248,7 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
|
||||
if (!write_all(dest_fd, buf, buf_cursor)) {
|
||||
pclose(pipe);
|
||||
delete o;
|
||||
io::IO::enable_raw();
|
||||
return nullptr;
|
||||
}
|
||||
pieces.push_back(new Petal(buf_cursor, lines, o, pos));
|
||||
@@ -254,20 +258,24 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
|
||||
break;
|
||||
if (ferror(pipe)) {
|
||||
delete o;
|
||||
io::IO::enable_raw();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
int status = pclose(pipe);
|
||||
if (status == -1) {
|
||||
delete o;
|
||||
io::IO::enable_raw();
|
||||
return nullptr;
|
||||
}
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
delete o;
|
||||
io::IO::enable_raw();
|
||||
return nullptr;
|
||||
}
|
||||
if (pieces.empty()) {
|
||||
delete o;
|
||||
io::IO::enable_raw();
|
||||
return nullptr;
|
||||
}
|
||||
if (posix_ending) {
|
||||
@@ -287,6 +295,7 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
|
||||
}
|
||||
}
|
||||
o->initialize();
|
||||
io::IO::enable_raw();
|
||||
if (pieces.size() == 1)
|
||||
return pieces[0];
|
||||
return build(pieces.data(), 0, pieces.size());
|
||||
@@ -421,54 +430,4 @@ Shard *Shard::from_string(const char *data, uint64_t len, bool posix_ending) {
|
||||
return pieces[0];
|
||||
return build(pieces.data(), 0, pieces.size());
|
||||
}
|
||||
|
||||
void Shard::dump(Shard *node, int depth) {
|
||||
if (!node) {
|
||||
std::cout << std::string(depth * 2, ' ') << "<null>\n";
|
||||
return;
|
||||
}
|
||||
std::string indent(depth * 2, ' ');
|
||||
std::cout << indent
|
||||
<< "Shard@" << node
|
||||
<< " kind=";
|
||||
switch (node->kind) {
|
||||
case Shard::Kind::Branch:
|
||||
std::cout << "Branch";
|
||||
break;
|
||||
case Shard::Kind::Petal:
|
||||
std::cout << "Petal";
|
||||
break;
|
||||
}
|
||||
std::cout
|
||||
<< " height=" << unsigned(node->height)
|
||||
<< " length=" << node->length
|
||||
<< " lines=" << node->lines
|
||||
<< " refs=" << node->refs.load()
|
||||
<< "\n";
|
||||
if (node->kind == Shard::Kind::Branch) {
|
||||
auto *branch = (Branch *)node;
|
||||
std::cout << indent << " left:\n";
|
||||
dump(branch->left, depth + 2);
|
||||
std::cout << indent << " right:\n";
|
||||
dump(branch->right, depth + 2);
|
||||
} else {
|
||||
auto *petal = static_cast<Petal *>(node);
|
||||
constexpr auto clean = [](const std::string &text) {
|
||||
std::string result = text;
|
||||
size_t pos = 0;
|
||||
while ((pos = result.find('\n', pos)) != std::string::npos) {
|
||||
result.replace(pos, 1, "\\n");
|
||||
pos += 2;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
std::cout
|
||||
<< indent << " source=" << petal->source
|
||||
<< " pos=" << petal->pos
|
||||
<< " length=" << petal->length
|
||||
<< " lines=" << petal->lines
|
||||
<< " text=\"" << clean(std::string(petal->source->read(petal->pos), petal->length))
|
||||
<< "\"\n";
|
||||
}
|
||||
}
|
||||
} // namespace bed::internal::vase
|
||||
|
||||
@@ -222,6 +222,38 @@ Shard *erase(Shard *root, uint64_t start, uint64_t end) {
|
||||
return new_root;
|
||||
}
|
||||
|
||||
Shard *join(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 offset = offset_of(root, start);
|
||||
std::vector<Shard *> pieces;
|
||||
pieces.reserve(end - start + 3);
|
||||
auto [a, b] = Shard::split(root, offset);
|
||||
pieces.push_back(a);
|
||||
for (uint64_t line = start; line < end; line++) {
|
||||
uint64_t nl_pos = offset_of(b, 1) - 1;
|
||||
auto [content, rest] = Shard::split(b, nl_pos);
|
||||
Shard::release(b);
|
||||
auto [nl, next_b] = Shard::split(rest, 1);
|
||||
Shard::release(rest);
|
||||
Shard::release(nl);
|
||||
pieces.push_back(content);
|
||||
b = next_b;
|
||||
}
|
||||
pieces.push_back(b);
|
||||
Shard *joined = Shard::build(pieces.data(), 0, pieces.size());
|
||||
Shard::release(root);
|
||||
return joined;
|
||||
}
|
||||
|
||||
Shard *copy(Shard *root, uint64_t start, uint64_t end) {
|
||||
if (!start || !end)
|
||||
throw ed_error("Invalid range.");
|
||||
|
||||
Reference in New Issue
Block a user