Add all basic ed functions

- except global and history ones
This commit is contained in:
2026-08-31 08:14:12 +01:00
parent 010f916d35
commit a996beb4aa
15 changed files with 732 additions and 362 deletions
+3 -2
View File
@@ -23,14 +23,15 @@ struct BEd {
internal::vase::AppendStorage append{"/tmp"};
bool help_mode = false;
std::string last_help = "";
bool prompt_mode = true;
std::function<std::string(BEd &)> prompt = nullptr;
bool suppress_mode = false;
bool temporary_current = false;
std::string last_help = "";
std::string last_regex = "";
std::string last_symbol = "";
std::string last_replacement = "";
std::string last_shell = "";
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
@@ -44,7 +45,7 @@ struct BEd {
internal::buffer::Buffer &buffer(const std::string &);
internal::buffer::Line &current();
internal::buffer::Range &prev();
void mark(char, internal::buffer::Line);
void mark(uint8_t, internal::buffer::Line);
void handle(std::string_view cmd, bool eof);
void run();
+3 -3
View File
@@ -13,10 +13,9 @@ struct ClipBuffer : Buffer {
bool waste() override;
uint64_t lines() override;
uint64_t bytes() override;
void load(BEd &ctx) override;
void load(BEd &ctx, vase::Shard *text) override;
void load(BEd &ctx, const char *cmd) override;
void load(BEd &ctx, std::filesystem::path path) override;
void set_filename(std::filesystem::path path) override;
std::filesystem::path filename() override;
vase::Shard *copy(uint64_t start_line, uint64_t end_line) override;
void substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
@@ -27,6 +26,7 @@ struct ClipBuffer : Buffer {
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
uint64_t next_closing(uint64_t start) override;
uint64_t prev_closing(uint64_t start) override;
uint64_t find_next(std::string_view pattern, uint64_t start) override;
+61 -3
View File
@@ -32,10 +32,9 @@ struct Buffer {
virtual bool waste() = 0;
virtual uint64_t lines() = 0;
virtual uint64_t bytes() = 0;
virtual void set_filename(std::filesystem::path path) = 0;
virtual std::filesystem::path filename() = 0;
virtual void load(BEd &ctx, vase::Shard *text) = 0;
virtual void load(BEd &ctx, const char *cmd) = 0;
virtual void load(BEd &ctx, std::filesystem::path path) = 0;
virtual void load(BEd &ctx) = 0;
virtual vase::Shard *copy(uint64_t start_line, uint64_t end_line) = 0;
virtual void substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
@@ -46,6 +45,7 @@ struct Buffer {
virtual void append(BEd &ctx, vase::Shard *text, uint64_t line) = 0;
virtual void print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
virtual void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
virtual void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
virtual uint64_t find_next(std::string_view pattern, uint64_t start) = 0;
virtual uint64_t find_prev(std::string_view pattern, uint64_t start) = 0;
virtual uint64_t next_closing(uint64_t start) = 0;
@@ -85,4 +85,62 @@ using Address = std::variant<
std::string,
buffer::Range,
buffer::Line>;
inline static std::string list_string(std::string_view s) {
uint32_t width = 80;
winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0)
width = ws.ws_col;
std::string out;
out.reserve(s.size());
const uint32_t max_width = width > 1 ? width - 1 : 1;
uint32_t column = 0;
auto append = [&](std::string_view text) {
if (column + text.size() > max_width) {
out += "\\\n";
column = 0;
}
out += text;
column += text.size();
};
for (unsigned char c : s) {
switch (c) {
case '\\':
append("\\\\");
break;
case '$':
append("\\$");
break;
case '\a':
append("\\a");
break;
case '\b':
append("\\b");
break;
case '\f':
append("\\f");
break;
case '\r':
append("\\r");
break;
case '\t':
append("\\t");
break;
case '\v':
append("\\v");
break;
default:
if (!std::isprint(c)) {
char buf[5];
std::snprintf(buf, sizeof(buf), "\\%03o", c);
append(buf);
} else {
append(std::string_view((const char *)&c, 1));
}
break;
}
}
out += '$';
return out;
}
} // namespace bed::internal::buffer
+3 -3
View File
@@ -17,10 +17,9 @@ struct GenericBuffer : Buffer {
bool waste() override;
uint64_t lines() override;
uint64_t bytes() override;
void load(BEd &ctx) override;
void load(BEd &ctx, vase::Shard *text) override;
void load(BEd &ctx, const char *cmd) override;
void load(BEd &ctx, std::filesystem::path path) override;
void set_filename(std::filesystem::path path) override;
std::filesystem::path filename() override;
vase::Shard *copy(uint64_t start_line, uint64_t end_line) override;
void substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
@@ -31,6 +30,7 @@ struct GenericBuffer : Buffer {
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
uint64_t next_closing(uint64_t start) override;
uint64_t prev_closing(uint64_t start) override;
uint64_t find_next(std::string_view pattern, uint64_t start) override;
+1
View File
@@ -74,6 +74,7 @@ struct IO {
KeyEvent read_key();
void write(const char *, uint64_t);
void write(std::string_view);
void run_pty(const std::string &);
std::deque<char> input_queue;
+2
View File
@@ -50,6 +50,8 @@ Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line);
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);
void write_file(std::filesystem::path path, Shard *text);
void write_command(const char *cmd, Shard *text);
uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start);
uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start);
+1
View File
@@ -35,6 +35,7 @@ extern "C" {
#include <mutex>
#include <optional>
#include <poll.h>
#include <pty.h>
#include <queue>
#include <set>
#include <shared_mutex>
+8 -1
View File
@@ -17,6 +17,8 @@ BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
prompt_ = args[i];
} else if (args[i] == "-s") {
suppress = true;
} else if (args[i] == "-v" || args[i] == "--verbose") {
help_mode = true;
} else {
if (file.size())
throw fatal_error("Invalid arguments given.", 1);
@@ -29,6 +31,7 @@ BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
prompt_mode = false;
suppress_mode = suppress;
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
current() = {"default", 0};
try {
if (file != "")
handle(":default:E " + file, false);
@@ -105,12 +108,16 @@ void BEd::handle(std::string_view cmd, bool eof) {
} break;
}
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_line(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = current();
} else if (std::holds_alternative<internal::buffer::Range>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_range(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
@@ -173,7 +180,7 @@ internal::buffer::Range &BEd::prev() {
return prev_1;
}
void BEd::mark(char m, internal::buffer::Line line) {
void BEd::mark(uint8_t m, internal::buffer::Line line) {
marks.get(m) = line;
}
} // namespace bed
+16 -115
View File
@@ -24,10 +24,10 @@ uint64_t GenericBuffer::bytes() {
}
void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
try {
if (lines())
ctx.marks.erase(name, 1, lines());
vase::Shard::release(root);
vase::Shard::retain(text);
root = text;
state = buffer::GenericBuffer::Unmodified;
if (!text) {
@@ -40,65 +40,15 @@ void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
ctx.prev().end = text->lines + 1;
}
parser.emplace(root, lines(), syntax::ruby::lang_ruby());
} catch (...) {
vase::Shard::release(text);
throw;
}
}
void GenericBuffer::load(BEd &ctx) {
if (save_path.empty())
throw ed_error("File cannot be implied.");
load(ctx, save_path);
void GenericBuffer::set_filename(std::filesystem::path path) {
save_path = path;
}
void GenericBuffer::load(BEd &ctx, const char *cmd) {
auto text = vase::Shard::from_command(cmd, true);
try {
if (lines())
ctx.marks.erase(name, 1, lines());
vase::Shard::release(root);
root = text;
state = buffer::GenericBuffer::Unmodified;
if (!text) {
ctx.prev().buffername = name;
ctx.prev().start = 0;
ctx.prev().end = 0;
} else {
ctx.prev().buffername = name;
ctx.prev().start = 1;
ctx.prev().end = text->lines + 1;
}
parser.emplace(root, lines(), syntax::ruby::lang_ruby());
} catch (...) {
vase::Shard::release(text);
throw;
}
}
void GenericBuffer::load(BEd &ctx, std::filesystem::path path) {
auto text = vase::Shard::from_file(path, true);
try {
if (lines())
ctx.marks.erase(name, 1, lines());
vase::Shard::release(root);
root = text;
state = buffer::GenericBuffer::Unmodified;
if (!text) {
ctx.prev().buffername = name;
ctx.prev().start = 0;
ctx.prev().end = 0;
} else {
ctx.prev().buffername = name;
ctx.prev().start = 1;
ctx.prev().end = text->lines + 1;
}
parser.emplace(root, lines(), syntax::ruby::lang_ruby());
} catch (...) {
vase::Shard::release(text);
throw;
}
}
std::filesystem::path GenericBuffer::filename() {
return save_path;
};
void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
ctx.prev().buffername = name;
@@ -114,8 +64,8 @@ void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
void GenericBuffer::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 = lines() ? 1 : 0;
ctx.prev().end = lines();
ctx.prev().start = std::min(start_line, lines());
ctx.prev().end = std::min(start_line, lines());
ctx.marks.erase(name, start_line, end_line - start_line + 1);
if (parser)
parser->erase(root, start_line, end_line - start_line + 1);
@@ -327,61 +277,12 @@ void GenericBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_lin
}
}
/*std::string GenericBuffer::list_string(std::string_view s) {
uint32_t width = 80;
winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0)
width = ws.ws_col;
std::string out;
out.reserve(s.size());
const uint32_t max_width = width > 1 ? width - 1 : 1;
uint32_t column = 0;
auto append = [&](std::string_view text) {
if (column + text.size() > max_width) {
out += "\\\n";
column = 0;
}
out += text;
column += text.size();
};
for (unsigned char c : s) {
switch (c) {
case '\\':
append("\\\\");
break;
case '$':
append("\\$");
break;
case '\a':
append("\\a");
break;
case '\b':
append("\\b");
break;
case '\f':
append("\\f");
break;
case '\r':
append("\\r");
break;
case '\t':
append("\\t");
break;
case '\v':
append("\\v");
break;
default:
if (!std::isprint(c)) {
char buf[5];
std::snprintf(buf, sizeof(buf), "\\%03o", c);
append(buf);
} else {
append(std::string_view((const char *)&c, 1));
}
break;
}
}
out += '$';
return out;
}*/
void GenericBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
vase::Iterator it(root, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
std::cout << list_string(it.line) << std::endl;
}
} // namespace bed::internal::buffer
+17 -50
View File
@@ -33,7 +33,6 @@ void ClipBuffer::clip_write(vase::Shard *text) {
}
void ClipBuffer::load(BEd &ctx, vase::Shard *text) {
try {
clip_write(text);
if (!text) {
ctx.prev().buffername = name;
@@ -44,56 +43,13 @@ void ClipBuffer::load(BEd &ctx, vase::Shard *text) {
ctx.prev().start = 1;
ctx.prev().end = text->lines + 1;
}
vase::Shard::release(text);
} catch (...) {
vase::Shard::release(text);
throw;
}
}
void ClipBuffer::load(BEd &) {
throw ed_error("File cannot be implied.");
}
void ClipBuffer::set_filename(std::filesystem::path) {}
void ClipBuffer::load(BEd &ctx, const char *cmd) {
auto text = vase::Shard::from_command(cmd, true);
try {
clip_write(text);
if (!text) {
ctx.prev().buffername = name;
ctx.prev().start = 0;
ctx.prev().end = 0;
} else {
ctx.prev().buffername = name;
ctx.prev().start = 1;
ctx.prev().end = text->lines + 1;
}
vase::Shard::release(text);
} catch (...) {
vase::Shard::release(text);
throw;
}
}
void ClipBuffer::load(BEd &ctx, std::filesystem::path path) {
auto text = vase::Shard::from_file(path, true);
try {
clip_write(text);
if (!text) {
ctx.prev().buffername = name;
ctx.prev().start = 0;
ctx.prev().end = 0;
} else {
ctx.prev().buffername = name;
ctx.prev().start = 1;
ctx.prev().end = text->lines + 1;
}
vase::Shard::release(text);
} catch (...) {
vase::Shard::release(text);
throw;
}
}
std::filesystem::path ClipBuffer::filename() {
return "";
};
void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
ctx.prev().buffername = name;
@@ -111,8 +67,8 @@ void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
s = vase::erase(s, start_line, end_line);
clip_write(s);
ctx.prev().buffername = name;
ctx.prev().start = (s ? s->length + 1 : 0) ? 1 : 0;
ctx.prev().end = s ? s->length + 1 : 0;
ctx.prev().start = std::min(start_line, s ? s->lines + 1 : 0);
ctx.prev().end = std::min(start_line, s ? s->lines + 1 : 0);
vase::Shard::release(s);
ctx.marks.erase(name, start_line, end_line - start_line + 1);
}
@@ -215,4 +171,15 @@ void ClipBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line)
std::cout << std::setw(width) << start_line++ << "\t" << it.line << std::endl;
vase::Shard::release(s);
}
void ClipBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
std::cout << list_string(it.line) << std::endl;
vase::Shard::release(s);
}
} // namespace bed::internal::buffer
+467 -160
View File
@@ -3,6 +3,32 @@
#include "internal/functions/suffixes.h"
namespace bed::internal::functions {
static bool escape_command(BEd &ctx, std::string &cmd, std::string_view filename) {
bool modified = false;
if (cmd == "!") {
cmd = ctx.last_shell;
modified = true;
}
ctx.last_shell = cmd;
for (size_t i = 0; i < cmd.size();) {
if (cmd[i] == '\\') {
if (i + 1 >= cmd.size())
break;
cmd.erase(i++, 1);
continue;
}
if (cmd[i] == '%') {
cmd.erase(i, 1);
cmd.insert(i, filename);
i += filename.size();
modified = true;
continue;
}
i++;
}
return modified;
}
void Suffix::register_suffixes(BEd &ctx) {
ctx.suffixes['p' - 'a'] = Suffix{
.desc = "Prints current line.",
@@ -18,23 +44,16 @@ void Suffix::register_suffixes(BEd &ctx) {
ctx.buffer(addr.buffername).number_print(ctx, addr.number, addr.number);
}
};
ctx.suffixes['l' - 'a'] = Suffix{
.desc = "Prints current line unambiguously.",
.handle = [](BEd &ctx) {
auto &addr = ctx.current();
ctx.buffer(addr.buffername).list_print(ctx, addr.number, addr.number);
}
};
}
void Function::register_posix(BEd &ctx) {
ctx.no_op = Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Prints a line and jumps to it.",
.default_address = ".+1",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Line>(addr_);
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
ctx.current() = addr;
}
};
ctx.eof_op = Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
@@ -71,6 +90,25 @@ void Function::register_posix(BEd &ctx) {
}
}
);
ctx.functions.insert(
"c",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Change set of lines.",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
ctx.buffer(addr.buffername).append(ctx, text, addr.start - 1);
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
vase::Shard::release(text);
}
}
);
ctx.functions.insert(
"d",
Function{
@@ -89,19 +127,162 @@ void Function::register_posix(BEd &ctx) {
}
);
ctx.functions.insert(
"c",
"e",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Change set of lines.",
.default_address = ".,.",
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Try load a file into the current buffer.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto &addr = std::get<std::string>(addr_);
auto &buf = ctx.buffer(addr);
if (buf.state == buffer::Buffer::Modified) {
buf.state = buffer::Buffer::Warned;
throw ed_error("Buffer modified.");
}
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true);
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
escape_command(ctx, cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true);
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true);
};
try {
buf.load(ctx, s);
vase::Shard::release(s);
} catch (...) {
vase::Shard::release(s);
throw;
}
std::cout << buf.bytes() << std::endl;
ctx.current() = {addr, buf.lines()};
}
}
);
ctx.functions.insert(
"E",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Load a file into the current buffer.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto &addr = std::get<std::string>(addr_);
auto &buf = ctx.buffer(addr);
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true);
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
escape_command(ctx, cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true);
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true);
};
try {
buf.load(ctx, s);
vase::Shard::release(s);
} catch (...) {
vase::Shard::release(s);
throw;
}
std::cout << buf.bytes() << std::endl;
ctx.current() = {addr, buf.lines()};
}
}
);
ctx.functions.insert(
"f",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Set a save path.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto &addr = std::get<std::string>(addr_);
auto &buf_ = ctx.buffer(addr);
if (buf_.kind != buffer::Buffer::Kind::Generic)
return;
auto &buf = *(buffer::GenericBuffer *)&buf_;
if (std::holds_alternative<std::filesystem::path>(arg))
buf.set_filename(std::get<std::filesystem::path>(arg));
else if (std::holds_alternative<ShellArg>(arg))
throw ed_error("Can't save shell command as save path.");
if (buf.filename().empty())
throw ed_error("Filename needed.");
std::cout << buf.filename() << std::endl;
}
}
);
ctx.functions.insert(
"h",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print last help message",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
std::cout << ctx.last_help << std::endl;
}
}
);
ctx.functions.insert(
"H",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Toggle help mode.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
ctx.help_mode = !ctx.help_mode;
if (ctx.help_mode)
std::cout << ctx.last_help << std::endl;
}
}
);
ctx.functions.insert(
"i",
Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Insert text before a line.",
.default_address = ".",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
ctx.buffer(addr.buffername).append(ctx, text, addr.start - 1);
auto addr = std::get<buffer::Line>(addr_);
if (addr.number)
addr.number--;
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
vase::Shard::release(text);
}
@@ -125,58 +306,77 @@ void Function::register_posix(BEd &ctx) {
}
);
ctx.functions.insert(
"q",
"k",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::Mark,
.input_mode = Function::InputMode::None,
.desc = "Try quitting.",
.default_address = "",
.desc = "Mark a line.",
.default_address = ".",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
for (auto &[name, buffer] : ctx.buffers) {
if (buffer->state == buffer::Buffer::Modified) {
buffer->state = buffer::Buffer::Warned;
throw ed_error("Buffer " + name + " modified.");
}
}
throw fatal_error("Quitting", 0);
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto &addr = std::get<buffer::Line>(addr_);
ctx.mark(std::get<char>(arg), addr);
}
}
);
ctx.functions.insert(
"Q",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Force quit.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
throw fatal_error("Force Quitting", 0);
}
}
);
ctx.functions.insert(
"p",
"l",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print range",
.desc = "List (print unambiguous) range",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).print(ctx, addr.start, addr.end);
ctx.buffer(addr.buffername).list_print(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.end};
},
}
);
ctx.functions.insert(
"m",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Line,
.input_mode = Function::InputMode::None,
.desc = "Move a range of lines.",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
auto arg = std::get<buffer::Line>(arg_);
if (arg.buffername == addr.buffername
&& addr.start <= arg.number
&& addr.end < arg.number)
throw ed_error("Can't move lines within themselves.");
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
ctx.mark(252, arg);
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
arg = ctx.marks.get(252);
try {
if (arg.number == UINT64_MAX)
throw ed_error("Unexpected error when moving lines.");
ctx.buffer(arg.buffername).append(ctx, text, arg.number);
vase::Shard::release(text);
} catch (...) {
if (!addr.start) {
vase::Shard::release(text);
throw;
}
ctx.buffer(addr.buffername).append(ctx, text, --addr.start);
vase::Shard::release(text);
throw;
}
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
},
}
);
ctx.functions.insert(
"n",
Function{
@@ -195,38 +395,118 @@ void Function::register_posix(BEd &ctx) {
}
);
ctx.functions.insert(
"=",
"p",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print line numbers",
.default_address = "$",
.accept_zero = true,
.desc = "Print range",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto &addr = std::get<buffer::Range>(addr_);
if (addr.start == addr.end)
std::cout << ':' << addr.buffername << ':' << addr.start << "\n";
else
std::cout << ':' << addr.buffername << ':' << addr.start << "," << addr.end << "\n";
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).print(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.end};
},
}
);
ctx.functions.insert(
"k",
"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 "*"; };
}
}
}
);
ctx.functions.insert(
"q",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Try quitting.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
std::string modified_buffers;
for (auto &[name, buffer] : ctx.buffers) {
if (buffer->state == buffer::Buffer::Modified) {
buffer->state = buffer::Buffer::Warned;
modified_buffers.append(name + ", ");
}
}
if (!modified_buffers.size())
throw fatal_error("Quitting", 0);
modified_buffers.erase(modified_buffers.size() - 2);
throw ed_error("Buffer(s) " + modified_buffers + " modified.");
}
}
);
ctx.functions.insert(
"Q",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Force quit.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
throw fatal_error("Force Quitting", 0);
}
}
);
ctx.functions.insert(
"r",
Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::Mark,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Mark a line.",
.default_address = ".",
.desc = "Read from file into buffer.",
.default_address = "$",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto &addr = std::get<buffer::Line>(addr_);
ctx.mark(std::get<char>(arg), addr);
auto &buf = ctx.buffer(addr.buffername);
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true);
if (buf.filename().empty())
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
escape_command(ctx, cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true);
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true);
};
try {
buf.append(ctx, s, addr.number);
std::cout << (s ? s->length + 1 : 0) << std::endl;
ctx.current() = {addr.buffername, addr.number + (s ? s->lines + 1 : 0)};
vase::Shard::release(s);
} catch (...) {
vase::Shard::release(s);
throw;
}
}
}
);
@@ -264,109 +544,136 @@ void Function::register_posix(BEd &ctx) {
}
);
ctx.functions.insert(
"e",
"t",
Function{
.address_kind = Function::AddressKind::None,
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Line,
.input_mode = Function::InputMode::None,
.desc = "Copy a range of lines.",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
auto arg = std::get<buffer::Line>(arg_);
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
try {
ctx.buffer(arg.buffername).append(ctx, text, arg.number);
vase::Shard::release(text);
} catch (...) {
if (!addr.start) {
vase::Shard::release(text);
throw;
}
ctx.buffer(addr.buffername).append(ctx, text, --addr.start);
vase::Shard::release(text);
throw;
}
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
},
}
);
ctx.functions.insert(
"w",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Try load a file into the current buffer.",
.default_address = "",
.accept_zero = false,
.desc = "Write buffer to disk.",
.default_address = "0,$",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
auto &buf = ctx.buffer(addr.buffername);
vase::Shard *text;
if (!addr.start && !addr.end) {
text = nullptr;
} else {
if (!addr.start && addr.end)
addr.start = 1;
text = buf.copy(addr.start, addr.end);
}
try {
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
vase::write_file(path, text);
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
escape_command(ctx, cmd, buf.filename().string());
vase::write_command(cmd.c_str(), text);
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
vase::write_file(path, text);
};
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
vase::Shard::release(text);
} catch (...) {
vase::Shard::release(text);
throw;
}
}
}
);
ctx.functions.insert(
"=",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print line numbers",
.default_address = "$",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &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};
},
}
);
ctx.functions.insert(
"!",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Shell,
.input_mode = Function::InputMode::None,
.desc = "Run a shell command.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
auto &addr = std::get<std::string>(addr_);
auto &buf = ctx.buffer(addr);
if (buf.state == buffer::Buffer::Modified) {
buf.state = buffer::Buffer::Warned;
throw ed_error("Buffer modified.");
}
if (std::holds_alternative<std::filesystem::path>(arg))
buf.load(ctx, std::get<std::filesystem::path>(arg));
else if (std::holds_alternative<ShellArg>(arg))
buf.load(ctx, std::get<ShellArg>(arg).cmd.c_str());
else if (std::holds_alternative<std::monostate>(arg))
buf.load(ctx);
std::cout << buf.bytes() << std::endl;
ctx.current() = {addr, buf.lines()};
}
auto filename = ctx.buffer(addr).filename();
auto &arg = std::get<ShellArg>(arg_);
auto cmd = arg.cmd;
if (escape_command(ctx, cmd, filename.string()))
ctx.io.write(cmd + "\n");
ctx.io.run_pty(cmd);
ctx.io.write("!\n");
},
}
);
ctx.functions.insert(
"E",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::File,
.input_mode = Function::InputMode::None,
.desc = "Load a file into the current buffer.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto &addr = std::get<std::string>(addr_);
auto &buf_ = ctx.buffer(addr);
if (buf_.kind != buffer::Buffer::Kind::Generic)
return;
auto &buf = *(buffer::GenericBuffer *)&buf_;
if (std::holds_alternative<std::filesystem::path>(arg))
buf.load(ctx, std::get<std::filesystem::path>(arg));
else if (std::holds_alternative<ShellArg>(arg))
buf.load(ctx, std::get<ShellArg>(arg).cmd.c_str());
else if (std::holds_alternative<std::monostate>(arg))
buf.load(ctx);
std::cout << buf.bytes() << std::endl;
ctx.current() = {addr, buf.lines()};
}
}
);
ctx.functions.insert(
"H",
Function{
.address_kind = Function::AddressKind::None,
ctx.no_op = Function{
.address_kind = Function::AddressKind::Line,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Toggle help mode.",
.default_address = "",
.accept_zero = false,
.desc = "Prints a line and jumps to it.",
.default_address = ".+1",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
ctx.help_mode = !ctx.help_mode;
if (ctx.help_mode)
std::cout << ctx.last_help << std::endl;
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Line>(addr_);
if (addr.number != 0)
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
ctx.current() = addr;
}
}
);
ctx.functions.insert(
"h",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Print last help message",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
std::cout << ctx.last_help << std::endl;
}
}
);
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
+62
View File
@@ -105,4 +105,66 @@ void IO::write(const char *buf, uint64_t n) {
void IO::write(std::string_view s) {
write_all(STDOUT_FILENO, s.data(), s.size());
}
void IO::run_pty(const std::string &cmd) {
int master_fd = -1;
struct winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
throw fatal_error("Can't get terminal size.", 1);
pid_t pid = forkpty(
&master_fd,
nullptr,
&orig_termios,
&ws
);
if (pid == -1)
throw fatal_error("Can't create PTY.", 1);
if (pid == 0) {
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char *)nullptr);
_exit(127);
}
struct pollfd fds[2];
while (true) {
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;
fds[1].fd = master_fd;
fds[1].events = POLLIN;
int rc = poll(fds, 2, -1);
if (rc == -1) {
if (errno == EINTR)
continue;
break;
}
if (resized.exchange(false)) {
struct winsize new_ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &new_ws) == 0)
ioctl(master_fd, TIOCSWINSZ, &new_ws);
}
if (fds[0].revents & POLLIN) {
char buf[4096];
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
if (n > 0)
write_all(master_fd, buf, n);
else if (n == 0)
break;
}
if (fds[1].revents & POLLIN) {
char buf[8192];
ssize_t n = read(master_fd, buf, sizeof(buf));
if (n > 0)
write_all(STDOUT_FILENO, buf, n);
else
break;
}
if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
break;
if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL))
break;
}
close(master_fd);
int status;
while (waitpid(pid, &status, 0) == -1)
if (errno != EINTR)
break;
}
} // namespace bed::internal::io
+13 -1
View File
@@ -192,7 +192,19 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
}
} else if (std::holds_alternative<LastRange>(curr.base)) {
return ctx.prev();
auto previous = ctx.prev();
auto &buf = ctx.buffer(previous.buffername);
if (curr.offset < 0 && previous.start < (uint64_t)-curr.offset)
throw ed_error("Can't have negative addresses");
previous.start += curr.offset;
if (previous.start > buf.lines())
throw ed_error("Line number too high.");
if (curr.offset < 0 && previous.end < (uint64_t)-curr.offset)
throw ed_error("Can't have negative addresses");
previous.end += curr.offset;
if (previous.end > buf.lines())
throw ed_error("Line number too high.");
return previous;
}
if (prev_given)
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
+2
View File
@@ -103,6 +103,8 @@ void Parser::operation() {
command->argument = functions::Function::GlobalArg(delim, std::move(val));
} break;
case functions::Function::ArgumentKind::File:
if (!(peek() == '\t' || peek() == ' ' || peek() == '\0'))
throw ed_error("Incorrect file input usage.");
skip_ws();
switch (peek()) {
case '!':
+55 -6
View File
@@ -1,4 +1,5 @@
#include "internal/vase/vase.h"
#include "internal/io/io.h"
namespace bed::internal::vase {
std::string to_string(Shard *root) {
@@ -128,7 +129,11 @@ uint64_t offset_of(Shard *root, Point point) {
uint64_t offset_of(Shard *root, uint64_t line) {
if (!root)
return 0;
if (line > root->lines)
if (line == 0)
return 0;
if (line == root->lines + 1)
return root->length;
if (line > root->lines + 1)
throw ed_error("line out of range");
uint64_t offset = 0;
Shard *curr = root;
@@ -207,10 +212,9 @@ Shard *erase(Shard *root, uint64_t start, uint64_t end) {
if (start > end || end >= line_count)
throw ed_error("line range out of bounds");
uint64_t start_offset = offset_of(root, start);
uint64_t end_offset =
(end + 1 == line_count)
? root->length
: offset_of(root, end + 1);
uint64_t end_offset = offset_of(root, end + 1);
if (end == root->lines && start_offset)
start_offset--;
auto [left, rest] = Shard::split(root, start_offset);
auto [middle, right] = Shard::split(rest, end_offset - start_offset);
Shard *new_root = Shard::concat(left, right);
@@ -267,7 +271,7 @@ Shard *copy(Shard *root, uint64_t start, uint64_t end) {
uint64_t end_offset =
(end + 1 == line_count)
? root->length
: offset_of(root, end + 1);
: offset_of(root, end + 1) - 1;
auto [left, rest] = Shard::split(root, start_offset);
auto [middle, right] = Shard::split(rest, end_offset - start_offset);
Shard::release(left);
@@ -275,4 +279,49 @@ Shard *copy(Shard *root, uint64_t start, uint64_t end) {
Shard::release(right);
return middle;
}
void write_file(std::filesystem::path path, Shard *text) {
std::ofstream file(path, std::ios::binary);
if (!text)
return;
PetalIterator it(text, Direction::Forward);
it.seek_offset(0);
const char *data;
uint64_t len;
while (it.next(&data, &len)) {
if (!file)
throw ed_error("Failed while writing file: " + path.string());
file.write(data, len);
}
file.write("\n", 1);
if (!file)
throw ed_error("Failed while writing file: " + path.string());
}
void write_command(const char *cmd, Shard *text) {
io::IO::cleanup();
FILE *pipe = popen(cmd, "w");
if (!pipe) {
io::IO::enable_raw();
throw ed_error("Failed to start command: " + std::string(cmd));
}
if (!text) {
int status = pclose(pipe);
if (status == -1)
throw ed_error("Failed to wait for command: " + std::string(cmd));
return;
}
PetalIterator it(text, Direction::Forward);
it.seek_offset(0);
const char *data;
uint64_t len;
while (it.next(&data, &len))
fwrite(data, 1, len, pipe);
fwrite("\n", 1, 1, pipe);
int status = pclose(pipe);
io::IO::enable_raw();
if (status == -1)
throw ed_error("Failed to wait for command: " + std::string(cmd));
return;
}
} // namespace bed::internal::vase