Add all basic ed functions
- except global and history ones
This commit is contained in:
+3
-2
@@ -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 ¤t();
|
||||
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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 '!':
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user