Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e19a8e6bba
|
||
|
|
afa60dd12f
|
+2
-2
@@ -4,11 +4,10 @@
|
|||||||
#include "internal/buffer/buffer.h"
|
#include "internal/buffer/buffer.h"
|
||||||
#include "internal/functions/functions.h"
|
#include "internal/functions/functions.h"
|
||||||
#include "internal/functions/suffixes.h"
|
#include "internal/functions/suffixes.h"
|
||||||
#include "internal/io/command.h"
|
|
||||||
#include "internal/io/io.h"
|
#include "internal/io/io.h"
|
||||||
#include "internal/marks/marks.h"
|
#include "internal/marks/marks.h"
|
||||||
#include "internal/theme/theme.h"
|
#include "internal/theme/theme.h"
|
||||||
#include "internal/ui/autocomp.h"
|
#include "internal/ui/command.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed {
|
namespace bed {
|
||||||
@@ -27,6 +26,7 @@ struct BEd {
|
|||||||
bool prompt_mode = true;
|
bool prompt_mode = true;
|
||||||
std::function<std::string(BEd &)> prompt = nullptr;
|
std::function<std::string(BEd &)> prompt = nullptr;
|
||||||
bool suppress_mode = false;
|
bool suppress_mode = false;
|
||||||
|
bool temporary_current = false;
|
||||||
std::string last_regex = "";
|
std::string last_regex = "";
|
||||||
std::string last_symbol = "";
|
std::string last_symbol = "";
|
||||||
std::string last_replacement = "";
|
std::string last_replacement = "";
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ struct Buffer {
|
|||||||
~Buffer();
|
~Buffer();
|
||||||
|
|
||||||
uint64_t lines();
|
uint64_t lines();
|
||||||
|
uint64_t bytes();
|
||||||
|
void load(BEd &ctx, vase::Shard *text);
|
||||||
vase::Shard *copy(uint64_t start_line, uint64_t end_line);
|
vase::Shard *copy(uint64_t start_line, uint64_t end_line);
|
||||||
void substitute(
|
void substitute(
|
||||||
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ struct KeyEvent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct IO {
|
struct IO {
|
||||||
|
static termios orig_termios;
|
||||||
|
static termios raw_termios;
|
||||||
|
static bool cleaned;
|
||||||
|
static void cleanup();
|
||||||
|
static void enable_raw();
|
||||||
|
static volatile std::atomic_bool resized;
|
||||||
|
static void handle_sigwinch(int);
|
||||||
|
|
||||||
IO();
|
IO();
|
||||||
~IO();
|
~IO();
|
||||||
|
|
||||||
@@ -70,13 +78,6 @@ struct IO {
|
|||||||
void write(const char *, uint64_t);
|
void write(const char *, uint64_t);
|
||||||
void write(std::string_view);
|
void write(std::string_view);
|
||||||
|
|
||||||
private:
|
|
||||||
static termios orig_termios;
|
|
||||||
static bool cleaned;
|
|
||||||
static void cleanup();
|
|
||||||
static volatile std::atomic_bool resized;
|
|
||||||
static void handle_sigwinch(int);
|
|
||||||
|
|
||||||
std::deque<char> input_queue;
|
std::deque<char> input_queue;
|
||||||
|
|
||||||
KeyEvent::ReadResult get_next_byte(char &out);
|
KeyEvent::ReadResult get_next_byte(char &out);
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ struct MarksEngine {
|
|||||||
continue;
|
continue;
|
||||||
if (marks[i].number == UINT64_MAX)
|
if (marks[i].number == UINT64_MAX)
|
||||||
continue;
|
continue;
|
||||||
if (marks[i].number >= start) {
|
if (marks[i].number > start) {
|
||||||
if (marks[i].number < start + count)
|
if (marks[i].number <= start + count)
|
||||||
marks[i].number = start;
|
marks[i].number = start;
|
||||||
else
|
else
|
||||||
marks[i].number -= count;
|
marks[i].number -= count;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include "internal/functions/functions.h"
|
#include "internal/functions/functions.h"
|
||||||
#include "internal/functions/suffixes.h"
|
#include "internal/functions/suffixes.h"
|
||||||
#include "internal/io/command.h"
|
#include "internal/ui/command.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::parser {
|
namespace bed::internal::parser {
|
||||||
@@ -85,12 +85,12 @@ struct Parser {
|
|||||||
std::string_view cmd;
|
std::string_view cmd;
|
||||||
uint16_t i;
|
uint16_t i;
|
||||||
Command *command;
|
Command *command;
|
||||||
std::vector<io::Token> *tokens;
|
std::vector<ui::Token> *tokens;
|
||||||
CompletionContext *completion;
|
CompletionContext *completion;
|
||||||
|
|
||||||
explicit Parser(
|
explicit Parser(
|
||||||
std::string_view cmd, BEd &bed, Command *command,
|
std::string_view cmd, BEd &bed, Command *command,
|
||||||
std::vector<io::Token> *tokens, CompletionContext *completion
|
std::vector<ui::Token> *tokens, CompletionContext *completion
|
||||||
);
|
);
|
||||||
|
|
||||||
char peek(uint16_t = 0); // == \0 if at eof.
|
char peek(uint16_t = 0); // == \0 if at eof.
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::syntax {
|
namespace bed::internal::syntax {
|
||||||
void dump_events(ParseState *node);
|
|
||||||
|
|
||||||
struct Parser {
|
struct Parser {
|
||||||
static constexpr uint64_t MAX_CHUNK = 512;
|
static constexpr uint64_t MAX_CHUNK = 512;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "internal/io/io.h"
|
#include "internal/io/io.h"
|
||||||
#include "internal/ui/autocomp.h"
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::io {
|
namespace bed::internal::ui {
|
||||||
struct Token {
|
struct Token {
|
||||||
enum struct Type : uint8_t {
|
enum struct Type : uint8_t {
|
||||||
TempCurrent, // @
|
TempCurrent, // @
|
||||||
@@ -39,10 +38,10 @@ struct CommandIO {
|
|||||||
uint16_t term_height;
|
uint16_t term_height;
|
||||||
uint16_t term_width;
|
uint16_t term_width;
|
||||||
BEd &bed;
|
BEd &bed;
|
||||||
IO &io;
|
io::IO &io;
|
||||||
|
|
||||||
CommandIO(BEd &, IO &);
|
CommandIO(BEd &, io::IO &);
|
||||||
std::pair<std::string, bool> run();
|
std::pair<std::string, bool> run();
|
||||||
void redraw();
|
void redraw();
|
||||||
};
|
};
|
||||||
} // namespace bed::internal::io
|
} // namespace bed::internal::ui
|
||||||
@@ -27,14 +27,12 @@ struct Shard {
|
|||||||
static Shard *from_file(const std::filesystem::path &path, bool posix_ending);
|
static Shard *from_file(const std::filesystem::path &path, bool posix_ending);
|
||||||
static Shard *from_string(const char *data, uint64_t len, bool posix_ending);
|
static Shard *from_string(const char *data, uint64_t len, bool posix_ending);
|
||||||
static Shard *from_command(const char *cmd, bool posix_ending);
|
static Shard *from_command(const char *cmd, bool posix_ending);
|
||||||
// static std::vector<Shard *> from_swap(std::filesystem::path &path, OriginalStorage *b);
|
|
||||||
|
|
||||||
static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset);
|
static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset);
|
||||||
static Shard *concat(Shard *a, Shard *b);
|
static Shard *concat(Shard *a, Shard *b);
|
||||||
static Shard *merge_leaves(Shard *a, Shard *b);
|
static Shard *merge_leaves(Shard *a, Shard *b);
|
||||||
static Shard *append(Shard *root, Shard *leaf);
|
static Shard *append(Shard *root, Shard *leaf);
|
||||||
static Shard *build(Shard **pieces, uint64_t lo, uint64_t hi);
|
static Shard *build(Shard **pieces, uint64_t lo, uint64_t hi);
|
||||||
static void dump(Shard *node, int depth = 0);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Branch : Shard {
|
struct Branch : Shard {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ std::string to_string(Shard *root, Range range);
|
|||||||
|
|
||||||
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line);
|
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line);
|
||||||
Shard *erase(Shard *root, uint64_t start, uint64_t end);
|
Shard *erase(Shard *root, uint64_t start, uint64_t end);
|
||||||
|
Shard *join(Shard *root, uint64_t start, uint64_t end);
|
||||||
Shard *copy(Shard *root, uint64_t start, uint64_t end);
|
Shard *copy(Shard *root, uint64_t start, uint64_t end);
|
||||||
|
|
||||||
uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start);
|
uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start);
|
||||||
|
|||||||
+41
-13
@@ -64,37 +64,41 @@ void BEd::handle(std::string_view cmd, bool eof) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this);
|
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;
|
internal::buffer::Address address;
|
||||||
switch (c.function->address_kind) {
|
switch (c.function->address_kind) {
|
||||||
case internal::functions::Function::AddressKind::None: {
|
case internal::functions::Function::AddressKind::None: {
|
||||||
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
|
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
|
||||||
if (a.has_value()) {
|
if (!a.has_value()) {
|
||||||
address = a->buffername;
|
|
||||||
} else {
|
|
||||||
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||||
a = internal::parser::AddressPromise::get_line(*this, vec);
|
a = internal::parser::AddressPromise::get_line(*this, vec);
|
||||||
address = a->buffername;
|
if (!a.has_value())
|
||||||
|
a = current();
|
||||||
}
|
}
|
||||||
|
address = a->buffername;
|
||||||
} break;
|
} break;
|
||||||
case internal::functions::Function::AddressKind::Line: {
|
case internal::functions::Function::AddressKind::Line: {
|
||||||
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
|
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
|
||||||
if (a.has_value()) {
|
if (!a.has_value()) {
|
||||||
address = *a;
|
|
||||||
} else {
|
|
||||||
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||||
a = internal::parser::AddressPromise::get_line(*this, vec);
|
a = internal::parser::AddressPromise::get_line(*this, vec);
|
||||||
address = *a;
|
if (!a.has_value())
|
||||||
|
a = current();
|
||||||
}
|
}
|
||||||
|
address = *a;
|
||||||
} break;
|
} break;
|
||||||
case internal::functions::Function::AddressKind::Range: {
|
case internal::functions::Function::AddressKind::Range: {
|
||||||
auto a = internal::parser::AddressPromise::get_range(*this, c.addresses);
|
auto a = internal::parser::AddressPromise::get_range(*this, c.addresses);
|
||||||
if (a.has_value()) {
|
if (!a.has_value()) {
|
||||||
address = *a;
|
|
||||||
} else {
|
|
||||||
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||||
a = internal::parser::AddressPromise::get_range(*this, vec);
|
a = internal::parser::AddressPromise::get_range(*this, vec);
|
||||||
address = *a;
|
if (!a.has_value())
|
||||||
|
a = internal::buffer::Range(current(), current());
|
||||||
}
|
}
|
||||||
|
address = *a;
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
|
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
|
||||||
@@ -110,12 +114,36 @@ void BEd::handle(std::string_view cmd, bool eof) {
|
|||||||
else
|
else
|
||||||
c.argument = internal::buffer::Range(current(), current());
|
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);
|
c.function->handle(*this, address, nullptr, c.argument, nullptr);
|
||||||
if (c.suffix)
|
if (c.suffix)
|
||||||
c.suffix->handle(*this);
|
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) {
|
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);
|
auto it = buffers.find(name);
|
||||||
if (it != buffers.end())
|
if (it != buffers.end())
|
||||||
return *it->second;
|
return *it->second;
|
||||||
@@ -125,7 +153,7 @@ internal::buffer::Buffer &BEd::buffer(const std::string &name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal::buffer::Line &BEd::current() {
|
internal::buffer::Line &BEd::current() {
|
||||||
return marks.get(250);
|
return marks.get(250 + temporary_current);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BEd::mark(char m, internal::buffer::Line line) {
|
void BEd::mark(char m, internal::buffer::Line line) {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
namespace bed::internal::buffer {
|
namespace bed::internal::buffer {
|
||||||
Buffer::Buffer(std::string name)
|
Buffer::Buffer(std::string name)
|
||||||
: state(Unmodified), root(nullptr), name(name) {
|
: state(Unmodified), root(nullptr), name(name) {
|
||||||
parser.emplace(root, lines(), syntax::ruby::lang_ruby()); // just for debug.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer::~Buffer() {
|
Buffer::~Buffer() {
|
||||||
@@ -17,6 +16,24 @@ uint64_t Buffer::lines() {
|
|||||||
return 0;
|
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) {
|
void Buffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
|
||||||
ctx.prev.buffername = name;
|
ctx.prev.buffername = name;
|
||||||
ctx.prev.start = line;
|
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) {
|
void Buffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
root = vase::erase(root, start_line, end_line);
|
root = vase::erase(root, start_line, end_line);
|
||||||
ctx.prev.buffername = name;
|
ctx.prev.buffername = name;
|
||||||
ctx.prev.start = start_line;
|
ctx.prev.start = lines() ? 1 : 0;
|
||||||
ctx.prev.end = start_line;
|
ctx.prev.end = lines();
|
||||||
ctx.marks.erase(name, start_line, end_line - start_line + 1);
|
ctx.marks.erase(name, start_line, end_line - start_line + 1);
|
||||||
if (parser)
|
if (parser)
|
||||||
parser->erase(root, start_line, end_line - start_line + 1);
|
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) {
|
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.buffername = name;
|
||||||
ctx.prev.start = start_line;
|
ctx.prev.start = start_line;
|
||||||
ctx.prev.end = start_line;
|
ctx.prev.end = start_line;
|
||||||
@@ -54,7 +71,7 @@ void Buffer::substitute(
|
|||||||
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
std::string ®ex, std::string &replacement, std::string &options
|
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);
|
root = vase::substitute(&ctx.append, root, regex, start_line, end_line, replacement, options);
|
||||||
/*prev_range.start = start_line;
|
/*prev_range.start = start_line;
|
||||||
prev_range.end = start_line;
|
prev_range.end = start_line;
|
||||||
|
|||||||
@@ -150,12 +150,13 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.default_address = "$",
|
.default_address = "$",
|
||||||
.accept_zero = true,
|
.accept_zero = true,
|
||||||
.pre_text_mode = nullptr,
|
.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_);
|
auto addr = std::get<buffer::Range>(addr_);
|
||||||
if (addr.start == addr.end)
|
if (addr.start == addr.end)
|
||||||
std::cout << ':' << addr.buffername << ':' << addr.start << "\n";
|
std::cout << ':' << addr.buffername << ':' << addr.start << "\n";
|
||||||
else
|
else
|
||||||
std::cout << ':' << addr.buffername << ':' << addr.start << "," << addr.end << "\n";
|
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
|
else
|
||||||
throw ed_error("Need filename to load.");
|
throw ed_error("Need filename to load.");
|
||||||
}
|
}
|
||||||
try {
|
buf.load(ctx, s);
|
||||||
if (buf.lines())
|
std::cout << buf.bytes() << std::endl;
|
||||||
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.current() = {addr, buf.lines()};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,17 +236,8 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
else
|
else
|
||||||
throw ed_error("Need filename to load.");
|
throw ed_error("Need filename to load.");
|
||||||
}
|
}
|
||||||
try {
|
buf.load(ctx, s);
|
||||||
if (buf.lines())
|
std::cout << buf.bytes() << std::endl;
|
||||||
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.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
|
} // 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 "bed.h"
|
||||||
|
#include "internal/io/io.h"
|
||||||
|
|
||||||
namespace bed::internal::io {
|
namespace bed::internal::io {
|
||||||
/*template <typename F>
|
std::pair<std::string, bool> IO::get_command(BEd &ctx) {
|
||||||
static void for_each_cluster(std::string_view s, F &&f) {
|
ui::CommandIO cio(ctx, *this);
|
||||||
unicode_width_state_t state;
|
return cio.run();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
} // namespace bed::internal::io
|
} // namespace bed::internal::io
|
||||||
|
|||||||
+19
-11
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
namespace bed::internal::io {
|
namespace bed::internal::io {
|
||||||
termios IO::orig_termios{};
|
termios IO::orig_termios{};
|
||||||
bool IO::cleaned = false;
|
termios IO::raw_termios{};
|
||||||
|
bool IO::cleaned = true;
|
||||||
volatile std::atomic_bool IO::resized(false);
|
volatile std::atomic_bool IO::resized(false);
|
||||||
|
|
||||||
IO::IO() {
|
IO::IO() {
|
||||||
@@ -14,16 +15,13 @@ IO::IO() {
|
|||||||
sa.sa_flags = 0;
|
sa.sa_flags = 0;
|
||||||
if (sigaction(SIGWINCH, &sa, nullptr) == -1)
|
if (sigaction(SIGWINCH, &sa, nullptr) == -1)
|
||||||
throw fatal_error("Can't install SIGWINCH handler.", 1);
|
throw fatal_error("Can't install SIGWINCH handler.", 1);
|
||||||
struct termios raw = orig_termios;
|
raw_termios = orig_termios;
|
||||||
raw.c_iflag &= ~(BRKINT | ISTRIP | IXON);
|
raw_termios.c_iflag &= ~(BRKINT | ISTRIP | IXON);
|
||||||
raw.c_cflag |= (CS8);
|
raw_termios.c_cflag |= (CS8);
|
||||||
raw.c_lflag &= ~(ECHO | ICANON | ISIG);
|
raw_termios.c_lflag &= ~(ECHO | ICANON | ISIG);
|
||||||
raw.c_cc[VMIN] = 1;
|
raw_termios.c_cc[VMIN] = 1;
|
||||||
raw.c_cc[VTIME] = 0;
|
raw_termios.c_cc[VTIME] = 0;
|
||||||
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
|
enable_raw();
|
||||||
throw fatal_error("Can't set terminal state.", 1);
|
|
||||||
std::string os = "\x1b[?2004h";
|
|
||||||
write_all(STDOUT_FILENO, os.c_str(), os.size());
|
|
||||||
atexit(cleanup);
|
atexit(cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +68,16 @@ std::pair<uint16_t, uint16_t> IO::cursor_position() {
|
|||||||
return {(uint16_t)row, (uint16_t)col};
|
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() {
|
void IO::cleanup() {
|
||||||
if (cleaned)
|
if (cleaned)
|
||||||
return;
|
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 AddressPromise::resolve(BEd &ctx) {
|
||||||
buffer::Line result = std::visit(
|
buffer::Line result = std::visit(
|
||||||
[&](auto const &addr) -> buffer::Line {
|
[&](auto const &addr) -> buffer::Line {
|
||||||
if (!bufname.has_value())
|
if (!bufname.has_value() || bufname->empty())
|
||||||
throw ed_error("Buffer name can't be empty.");
|
throw ed_error("Buffer name can't be empty.");
|
||||||
buffer::Line line = {*bufname, 0};
|
buffer::Line line = {*bufname, 0};
|
||||||
auto &buf = ctx.buffer(line.buffername);
|
auto &buf = ctx.buffer(line.buffername);
|
||||||
using T = std::decay_t<decltype(addr)>;
|
using T = std::decay_t<decltype(addr)>;
|
||||||
if constexpr (std::is_same_v<T, None>) {
|
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>) {
|
} else if constexpr (std::is_same_v<T, Current>) {
|
||||||
if (line.buffername == ctx.current().buffername)
|
if (line.buffername == ctx.current().buffername)
|
||||||
line.number = ctx.current().number;
|
line.number = ctx.current().number;
|
||||||
@@ -133,8 +136,7 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
|
|||||||
prev_set = true;
|
prev_set = true;
|
||||||
} else {
|
} else {
|
||||||
if (std::holds_alternative<None>(curr.base)) {
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
if (!prev_set)
|
if (prev_set) {
|
||||||
return std::nullopt;
|
|
||||||
if (prev_given) {
|
if (prev_given) {
|
||||||
curr.base = prev.base;
|
curr.base = prev.base;
|
||||||
curr.offset = prev.offset;
|
curr.offset = prev.offset;
|
||||||
@@ -143,6 +145,10 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
|
|||||||
curr.offset = 0;
|
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);
|
return curr.resolve(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,8 +199,7 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
|
|||||||
prev_set = true;
|
prev_set = true;
|
||||||
} else {
|
} else {
|
||||||
if (std::holds_alternative<None>(curr.base)) {
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
if (!prev_set)
|
if (prev_set) {
|
||||||
return std::nullopt;
|
|
||||||
if (prev_given) {
|
if (prev_given) {
|
||||||
curr.base = prev.base;
|
curr.base = prev.base;
|
||||||
curr.offset = prev.offset;
|
curr.offset = prev.offset;
|
||||||
@@ -204,6 +209,9 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
|
|||||||
}
|
}
|
||||||
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)
|
if (prev_given)
|
||||||
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
|
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
|
||||||
buffer::Line only = curr.resolve(ctx);
|
buffer::Line only = 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();
|
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() {
|
void Parser::parse() {
|
||||||
skip_ws();
|
skip_ws();
|
||||||
if (peek() == '@') {
|
if (peek() == '@') {
|
||||||
@@ -486,14 +37,14 @@ void Parser::parse() {
|
|||||||
|
|
||||||
Parser::Parser(
|
Parser::Parser(
|
||||||
std::string_view cmd, BEd &bed, Command *command,
|
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) {
|
) : bed(bed), cmd(cmd), command(command), tokens(tokens), completion(completion) {
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Command Parser::get_command(std::string_view cmd, BEd &bed) {
|
Command Parser::get_command(std::string_view cmd, BEd &bed) {
|
||||||
Command c;
|
Command c;
|
||||||
std::vector<io::Token> tokens;
|
std::vector<ui::Token> tokens;
|
||||||
CompletionContext completion;
|
CompletionContext completion;
|
||||||
Parser p(cmd, bed, &c, &tokens, &completion);
|
Parser p(cmd, bed, &c, &tokens, &completion);
|
||||||
p.parse();
|
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> Parser::get_addresses(std::string_view cmd, BEd &bed) {
|
||||||
std::vector<AddressPromise> result;
|
std::vector<AddressPromise> result;
|
||||||
std::vector<io::Token> tokens;
|
std::vector<ui::Token> tokens;
|
||||||
CompletionContext completion;
|
CompletionContext completion;
|
||||||
Parser p(cmd, bed, nullptr, &tokens, &completion);
|
Parser p(cmd, bed, nullptr, &tokens, &completion);
|
||||||
p.addresses(result);
|
p.addresses(result);
|
||||||
|
|||||||
@@ -1,42 +1,6 @@
|
|||||||
#include "internal/syntax/parser.h"
|
#include "internal/syntax/parser.h"
|
||||||
|
|
||||||
namespace bed::internal::syntax {
|
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) {
|
static void destroy_tree(ParseState *node, Language &lang) {
|
||||||
if (!node)
|
if (!node)
|
||||||
return;
|
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)
|
(end + 1 == line_count)
|
||||||
? root->length
|
? root->length
|
||||||
: offset_of(root, end + 1);
|
: offset_of(root, end + 1);
|
||||||
|
|
||||||
std::vector<RegexMatch> matches = _regex_search(root, pattern, start_offset, end_offset, options);
|
std::vector<RegexMatch> matches = _regex_search(root, pattern, start_offset, end_offset, options);
|
||||||
if (matches.empty())
|
if (matches.empty())
|
||||||
return root;
|
return root;
|
||||||
|
|
||||||
std::vector<ReplacePart> replace_parts = parse_replace(ap, replace);
|
std::vector<ReplacePart> replace_parts = parse_replace(ap, replace);
|
||||||
|
|
||||||
std::vector<Shard *> pieces;
|
std::vector<Shard *> pieces;
|
||||||
pieces.reserve(matches.size() * 2 + 1);
|
pieces.reserve(matches.size() * 2 + 1);
|
||||||
|
|
||||||
Shard *remaining = root;
|
Shard *remaining = root;
|
||||||
Shard::retain(remaining);
|
Shard::retain(remaining);
|
||||||
uint64_t cursor = 0;
|
uint64_t cursor = 0;
|
||||||
|
|
||||||
for (const RegexMatch &match : matches) {
|
for (const RegexMatch &match : matches) {
|
||||||
uint64_t gap = match.start - cursor;
|
uint64_t gap = match.start - cursor;
|
||||||
if (gap > 0) {
|
if (gap > 0) {
|
||||||
@@ -102,11 +97,9 @@ Shard *substitute(
|
|||||||
pieces.push_back(keep);
|
pieces.push_back(keep);
|
||||||
remaining = rest;
|
remaining = rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
|
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
|
||||||
Shard::release(remaining);
|
Shard::release(remaining);
|
||||||
remaining = rest2;
|
remaining = rest2;
|
||||||
|
|
||||||
for (size_t i = 0; i < replace_parts.size(); ++i) {
|
for (size_t i = 0; i < replace_parts.size(); ++i) {
|
||||||
const ReplacePart &part = replace_parts[i];
|
const ReplacePart &part = replace_parts[i];
|
||||||
switch (part.type) {
|
switch (part.type) {
|
||||||
@@ -141,11 +134,9 @@ Shard *substitute(
|
|||||||
cursor = match.end;
|
cursor = match.end;
|
||||||
}
|
}
|
||||||
pieces.push_back(remaining);
|
pieces.push_back(remaining);
|
||||||
|
|
||||||
for (auto &part : replace_parts)
|
for (auto &part : replace_parts)
|
||||||
if (part.type == ReplacePart::PartType::Constant)
|
if (part.type == ReplacePart::PartType::Constant)
|
||||||
Shard::release(std::get<Shard *>(part.value));
|
Shard::release(std::get<Shard *>(part.value));
|
||||||
|
|
||||||
std::vector<Shard *> compact;
|
std::vector<Shard *> compact;
|
||||||
compact.reserve(pieces.size());
|
compact.reserve(pieces.size());
|
||||||
for (Shard *p : pieces) {
|
for (Shard *p : pieces) {
|
||||||
@@ -154,7 +145,6 @@ Shard *substitute(
|
|||||||
else if (p)
|
else if (p)
|
||||||
Shard::release(p);
|
Shard::release(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
Shard *new_root = compact.empty() ? nullptr : Shard::build(compact.data(), 0, compact.size());
|
Shard *new_root = compact.empty() ? nullptr : Shard::build(compact.data(), 0, compact.size());
|
||||||
Shard::release(root);
|
Shard::release(root);
|
||||||
return new_root;
|
return new_root;
|
||||||
|
|||||||
+11
-52
@@ -1,3 +1,4 @@
|
|||||||
|
#include "internal/io/io.h"
|
||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
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) {
|
Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
|
||||||
if (hi - lo == 1)
|
if (hi - lo == 1)
|
||||||
return pieces[lo];
|
return pieces[lo];
|
||||||
size_t mid = lo + (hi - lo) / 2;
|
uint64_t mid = lo + (hi - lo) / 2;
|
||||||
Shard *left = build(pieces, lo, mid);
|
Shard *left = build(pieces, lo, mid);
|
||||||
Shard *right = build(pieces, mid, hi);
|
Shard *right = build(pieces, mid, hi);
|
||||||
Shard *node = new Branch(left, right);
|
Shard *node = Shard::concat(left, right);
|
||||||
Shard::release(left);
|
Shard::release(left);
|
||||||
Shard::release(right);
|
Shard::release(right);
|
||||||
return node;
|
return node;
|
||||||
@@ -208,9 +209,11 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
|
|||||||
delete o;
|
delete o;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
io::IO::cleanup();
|
||||||
FILE *pipe = popen(cmd, "r");
|
FILE *pipe = popen(cmd, "r");
|
||||||
if (!pipe) {
|
if (!pipe) {
|
||||||
delete o;
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
std::vector<Shard *> pieces;
|
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)) {
|
if (!write_all(dest_fd, buf, buf_cursor)) {
|
||||||
pclose(pipe);
|
pclose(pipe);
|
||||||
delete o;
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
pieces.push_back(new Petal(buf_cursor, lines, o, pos));
|
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;
|
break;
|
||||||
if (ferror(pipe)) {
|
if (ferror(pipe)) {
|
||||||
delete o;
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int status = pclose(pipe);
|
int status = pclose(pipe);
|
||||||
if (status == -1) {
|
if (status == -1) {
|
||||||
delete o;
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||||
delete o;
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (pieces.empty()) {
|
if (pieces.empty()) {
|
||||||
delete o;
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (posix_ending) {
|
if (posix_ending) {
|
||||||
@@ -287,6 +295,7 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
o->initialize();
|
o->initialize();
|
||||||
|
io::IO::enable_raw();
|
||||||
if (pieces.size() == 1)
|
if (pieces.size() == 1)
|
||||||
return pieces[0];
|
return pieces[0];
|
||||||
return build(pieces.data(), 0, pieces.size());
|
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 pieces[0];
|
||||||
return build(pieces.data(), 0, pieces.size());
|
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
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -222,6 +222,38 @@ Shard *erase(Shard *root, uint64_t start, uint64_t end) {
|
|||||||
return new_root;
|
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) {
|
Shard *copy(Shard *root, uint64_t start, uint64_t end) {
|
||||||
if (!start || !end)
|
if (!start || !end)
|
||||||
throw ed_error("Invalid range.");
|
throw ed_error("Invalid range.");
|
||||||
|
|||||||
Reference in New Issue
Block a user