From e44267a6fe55b041efeee4093ca8cc725b2bf280 Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Mon, 31 Aug 2026 10:11:29 +0100 Subject: [PATCH] Cleanup io calls and add cd function. --- include/bed.h | 1 + include/internal/functions/functions.h | 1 + include/internal/io/io.h | 1 + include/pch.h | 1 - src/bed/run.cc | 35 ++++++- src/internal/buffer/generic.cc | 80 ++++++---------- src/internal/buffer/special/clip.cc | 6 +- src/internal/functions/extended.cc | 40 ++++++++ .../functions/{functions.cc => posix.cc} | 94 +++++++------------ src/internal/io/io.cc | 12 ++- src/main.cc | 4 +- 11 files changed, 157 insertions(+), 118 deletions(-) create mode 100644 src/internal/functions/extended.cc rename src/internal/functions/{functions.cc => posix.cc} (94%) diff --git a/include/bed.h b/include/bed.h index f3af9d2..a3ea53f 100644 --- a/include/bed.h +++ b/include/bed.h @@ -50,5 +50,6 @@ struct BEd { void handle(std::string_view cmd, bool eof); void run(); void suffix_handle(char s); + bool escape_command(std::string &cmd, std::string_view filename); }; } // namespace bed diff --git a/include/internal/functions/functions.h b/include/internal/functions/functions.h index 96b9c16..d4e64a1 100644 --- a/include/internal/functions/functions.h +++ b/include/internal/functions/functions.h @@ -81,5 +81,6 @@ struct Function { handle; static void register_posix(BEd &ctx); + static void register_extented(BEd &ctx); }; } // namespace bed::internal::functions diff --git a/include/internal/io/io.h b/include/internal/io/io.h index f6bc445..bdaf923 100644 --- a/include/internal/io/io.h +++ b/include/internal/io/io.h @@ -74,6 +74,7 @@ struct IO { KeyEvent read_key(); void write(const char *, uint64_t); void write(std::string_view); + void write_line(std::string_view); void run_pty(const std::string &); std::deque input_queue; diff --git a/include/pch.h b/include/pch.h index f48f1b0..4c15ebf 100644 --- a/include/pch.h +++ b/include/pch.h @@ -29,7 +29,6 @@ extern "C" { #include #include #include -#include #include #include #include diff --git a/src/bed/run.cc b/src/bed/run.cc index 9386cc7..2907e52 100644 --- a/src/bed/run.cc +++ b/src/bed/run.cc @@ -5,6 +5,7 @@ namespace bed { BEd::BEd(std::vector args, internal::io::IO &io) : theme(internal::theme::Theme::default_theme()), io(io) { internal::functions::Function::register_posix(*this); + internal::functions::Function::register_extented(*this); internal::functions::Suffix::register_suffixes(*this); std::string prompt_ = ""; std::string file = ""; @@ -36,9 +37,9 @@ BEd::BEd(std::vector args, internal::io::IO &io) if (file != "") handle(":default:E " + file, false); } catch (ed_error &e) { - std::cout << "?" << std::endl; + io.write_line("?"); if (help_mode) - std::cout << e.what() << std::endl; + io.write_line(e.what()); last_help = e.what(); } } @@ -55,9 +56,9 @@ void BEd::run() { try { handle(cmd, eof); } catch (ed_error &e) { - std::cout << "?" << std::endl; + io.write_line("?"); if (help_mode) - std::cout << e.what() << std::endl; + io.write_line(e.what()); last_help = e.what(); } } @@ -183,4 +184,30 @@ internal::buffer::Range &BEd::prev() { void BEd::mark(uint8_t m, internal::buffer::Line line) { marks.get(m) = line; } + +bool BEd::escape_command(std::string &cmd, std::string_view filename) { + bool modified = false; + if (cmd == "!") { + cmd = last_shell; + modified = true; + } + 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; +} } // namespace bed diff --git a/src/internal/buffer/generic.cc b/src/internal/buffer/generic.cc index 535dc56..941137e 100644 --- a/src/internal/buffer/generic.cc +++ b/src/internal/buffer/generic.cc @@ -155,36 +155,30 @@ uint64_t GenericBuffer::prev_closing(uint64_t start) { } } -inline void apply(std::ostream &out, const Highlight &hl) { - out << "\x1b[0m"; +inline void apply(io::IO &io, const Highlight &hl) { + io.write("\x1b[0m"); const uint8_t r = (hl.fg >> 16) & 0xff; const uint8_t g = (hl.fg >> 8) & 0xff; const uint8_t b = hl.fg & 0xff; - out << "\x1b[38;2;" - << (unsigned)r << ';' - << (unsigned)g << ';' - << (unsigned)b << 'm'; + io.write(std::format("\x1b[38;2;{};{};{}m", r, g, b)); if (hl.bg != 0) { const uint8_t br = (hl.bg >> 16) & 0xff; const uint8_t bg = (hl.bg >> 8) & 0xff; const uint8_t bb = hl.bg & 0xff; - out << "\x1b[48;2;" - << (unsigned)br << ';' - << (unsigned)bg << ';' - << (unsigned)bb << 'm'; + io.write(std::format("\x1b[48;2;{};{};{}m", br, bg, bb)); } if (hl.flags & Highlight::Bold) - out << "\x1b[1m"; + io.write("\x1b[1m"); if (hl.flags & Highlight::Italic) - out << "\x1b[3m"; + io.write("\x1b[3m"); if (hl.flags & Highlight::Underline) - out << "\x1b[4m"; + io.write("\x1b[4m"); if (hl.flags & Highlight::Strikethrough) - out << "\x1b[9m"; + io.write("\x1b[9m"); } -inline void reset(std::ostream &out) { - out << "\x1b[0m"; +inline void reset(io::IO &io) { + io.write("\x1b[0m"); } void GenericBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) { @@ -192,7 +186,7 @@ void GenericBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) { ctx.prev().start = start_line; ctx.prev().end = end_line; if (parser) { - std::optional it_o = parser->get_hl(root, start_line - 1); + auto it_o = parser->get_hl(root, start_line - 1); auto &it = *it_o; while (start_line <= end_line) { it.next(); @@ -202,31 +196,25 @@ void GenericBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) { for (const auto &token : tokens) { const uint32_t start = token.start; const uint32_t end = token.end; - if (start > line.size()) + if (start > line.size() || end > line.size()) break; - if (end > line.size()) - break; - if (cursor < start) { - std::cout.write( - line.data() + cursor, - start - cursor - ); - } + if (cursor < start) + ctx.io.write(line.data() + cursor, start - cursor); const auto highlight = ctx.theme.get(token); - apply(std::cout, highlight); - std::cout.write(line.data() + start, end - start); - reset(std::cout); + apply(ctx.io, highlight); + ctx.io.write(line.data() + start, end - start); + reset(ctx.io); cursor = end; } if (cursor < line.size()) - std::cout.write(line.data() + cursor, line.size() - cursor); - std::cout << std::endl; + ctx.io.write(line.data() + cursor, line.size() - cursor); + ctx.io.write_line(""); ++start_line; } } else { vase::Iterator it(root, start_line - 1, Direction::Forward); while (it.next() && start_line++ <= end_line) - std::cout << it.line << std::endl; + ctx.io.write_line(it.line); } } @@ -242,38 +230,32 @@ void GenericBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_lin auto &it = *it_o; while (start_line <= end_line) { it.next(); - std::cout << std::setw(width) << start_line << "\t"; + ctx.io.write(std::format("{:>{}}\t", start_line, width)); const std::string &line = it.it->line; const auto &tokens = it.tokens; uint32_t cursor = 0; for (const auto &token : tokens) { const uint32_t start = token.start; const uint32_t end = token.end; - if (start > line.size()) + if (start > line.size() || end > line.size()) break; - if (end > line.size()) - break; - if (cursor < start) { - std::cout.write( - line.data() + cursor, - start - cursor - ); - } + if (cursor < start) + ctx.io.write(line.data() + cursor, start - cursor); const auto highlight = ctx.theme.get(token); - apply(std::cout, highlight); - std::cout.write(line.data() + start, end - start); - reset(std::cout); + apply(ctx.io, highlight); + ctx.io.write(line.data() + start, end - start); + reset(ctx.io); cursor = end; } if (cursor < line.size()) - std::cout.write(line.data() + cursor, line.size() - cursor); - std::cout << std::endl; + ctx.io.write(line.data() + cursor, line.size() - cursor); + ctx.io.write_line(""); ++start_line; } } else { vase::Iterator it(root, start_line - 1, Direction::Forward); while (it.next() && start_line <= end_line) - std::cout << std::setw(width) << start_line++ << "\t" << it.line << std::endl; + ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line)); } } @@ -283,6 +265,6 @@ void GenericBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_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; + ctx.io.write_line(list_string(it.line)); } } // namespace bed::internal::buffer diff --git a/src/internal/buffer/special/clip.cc b/src/internal/buffer/special/clip.cc index a713eb5..7ae2c09 100644 --- a/src/internal/buffer/special/clip.cc +++ b/src/internal/buffer/special/clip.cc @@ -154,7 +154,7 @@ void ClipBuffer::print(BEd &ctx, uint64_t start_line, uint64_t 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 << it.line << std::endl; + ctx.io.write_line(it.line); vase::Shard::release(s); } @@ -168,7 +168,7 @@ void ClipBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t 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 << std::setw(width) << start_line++ << "\t" << it.line << std::endl; + ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line)); vase::Shard::release(s); } @@ -179,7 +179,7 @@ void ClipBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t 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; + ctx.io.write_line(list_string(it.line)); vase::Shard::release(s); } } // namespace bed::internal::buffer diff --git a/src/internal/functions/extended.cc b/src/internal/functions/extended.cc new file mode 100644 index 0000000..b61a054 --- /dev/null +++ b/src/internal/functions/extended.cc @@ -0,0 +1,40 @@ +#include "bed.h" +#include "internal/functions/functions.h" +#include "internal/functions/suffixes.h" + +namespace bed::internal::functions { +void Function::register_extented(BEd &ctx) { + ctx.functions.insert( + "cd", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::Any, + .input_mode = Function::InputMode::None, + .desc = "Change directory.", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &arg_, std::vector *) { + auto path = std::get(arg_); + const auto first = path.find_first_not_of(" \t"); + const auto last = path.find_last_not_of(" \t"); + path = path.substr(first, last - first + 1); + if (path == "~") + path = std::getenv("HOME"); + if (path.starts_with("~/")) { + const char *home = getenv("HOME"); + if (home) + path = std::string(home) + path.substr(1); + } + if (first != std::string::npos) + if (chdir(path.c_str()) == -1) + throw ed_error("Can't change directory."); + char cwd[PATH_MAX]; + if (!getcwd(cwd, sizeof(cwd))) + throw ed_error("Directory changed, but couldn't determine current directory."); + ctx.io.write_line(cwd); + }, + } + ); +} +} // namespace bed::internal::functions diff --git a/src/internal/functions/functions.cc b/src/internal/functions/posix.cc similarity index 94% rename from src/internal/functions/functions.cc rename to src/internal/functions/posix.cc index 20a47fa..14b0f50 100644 --- a/src/internal/functions/functions.cc +++ b/src/internal/functions/posix.cc @@ -1,34 +1,8 @@ -#include "internal/functions/functions.h" #include "bed.h" +#include "internal/functions/functions.h" #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.", @@ -54,24 +28,6 @@ void Suffix::register_suffixes(BEd &ctx) { } void Function::register_posix(BEd &ctx) { - ctx.eof_op = Function{ - .address_kind = Function::AddressKind::None, - .argument_kind = Function::ArgumentKind::None, - .input_mode = Function::InputMode::None, - .desc = "Try quitting.", - .default_address = "", - .accept_zero = false, - .pre_text_mode = nullptr, - .handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector *) { - for (auto &[name, buffer] : ctx.buffers) { - if (buffer->state == buffer::Buffer::Modified) { - buffer->state = buffer::Buffer::Warned; - throw ed_error("Buffer " + name + " modified."); - } - } - throw fatal_error("Quitting", 0); - } - }; ctx.functions.insert( "a", Function{ @@ -150,7 +106,7 @@ void Function::register_posix(BEd &ctx) { buf.set_filename(path); } else if (std::holds_alternative(arg)) { auto cmd = std::get(arg).cmd; - escape_command(ctx, cmd, buf.filename().string()); + ctx.escape_command(cmd, buf.filename().string()); s = vase::Shard::from_command(cmd.c_str(), true); } else { auto path = buf.filename(); @@ -165,7 +121,7 @@ void Function::register_posix(BEd &ctx) { vase::Shard::release(s); throw; } - std::cout << buf.bytes() << std::endl; + ctx.io.write_line(std::format("{}", buf.bytes())); ctx.current() = {addr, buf.lines()}; } } @@ -190,7 +146,7 @@ void Function::register_posix(BEd &ctx) { buf.set_filename(path); } else if (std::holds_alternative(arg)) { auto cmd = std::get(arg).cmd; - escape_command(ctx, cmd, buf.filename().string()); + ctx.escape_command(cmd, buf.filename().string()); s = vase::Shard::from_command(cmd.c_str(), true); } else { auto path = buf.filename(); @@ -205,7 +161,7 @@ void Function::register_posix(BEd &ctx) { vase::Shard::release(s); throw; } - std::cout << buf.bytes() << std::endl; + ctx.io.write_line(std::format("{}", buf.bytes())); ctx.current() = {addr, buf.lines()}; } } @@ -232,7 +188,7 @@ void Function::register_posix(BEd &ctx) { 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.io.write_line(buf.filename().string()); } } ); @@ -247,7 +203,7 @@ void Function::register_posix(BEd &ctx) { .accept_zero = false, .pre_text_mode = nullptr, .handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector *) { - std::cout << ctx.last_help << std::endl; + ctx.io.write_line(ctx.last_help); } } ); @@ -264,7 +220,7 @@ void Function::register_posix(BEd &ctx) { .handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector *) { ctx.help_mode = !ctx.help_mode; if (ctx.help_mode) - std::cout << ctx.last_help << std::endl; + ctx.io.write_line(ctx.last_help); } } ); @@ -490,7 +446,7 @@ void Function::register_posix(BEd &ctx) { buf.set_filename(path); } else if (std::holds_alternative(arg)) { auto cmd = std::get(arg).cmd; - escape_command(ctx, cmd, buf.filename().string()); + ctx.escape_command(cmd, buf.filename().string()); s = vase::Shard::from_command(cmd.c_str(), true); } else { auto path = buf.filename(); @@ -500,7 +456,7 @@ void Function::register_posix(BEd &ctx) { }; try { buf.append(ctx, s, addr.number); - std::cout << (s ? s->length + 1 : 0) << std::endl; + ctx.io.write_line(std::format("{}", s ? s->length + 1 : 0)); ctx.current() = {addr.buffername, addr.number + (s ? s->lines + 1 : 0)}; vase::Shard::release(s); } catch (...) { @@ -601,7 +557,7 @@ void Function::register_posix(BEd &ctx) { buf.set_filename(path); } else if (std::holds_alternative(arg)) { auto cmd = std::get(arg).cmd; - escape_command(ctx, cmd, buf.filename().string()); + ctx.escape_command(cmd, buf.filename().string()); vase::write_command(cmd.c_str(), text); } else { auto path = buf.filename(); @@ -631,9 +587,9 @@ void Function::register_posix(BEd &ctx) { .handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector *) { auto &addr = std::get(addr_); if (addr.start == addr.end) - std::cout << ':' << addr.buffername << ':' << addr.start << "\n"; + ctx.io.write_line(std::format(":{}:{}", addr.buffername, addr.start)); else - std::cout << ':' << addr.buffername << ':' << addr.start << "," << addr.end << "\n"; + ctx.io.write_line(std::format(":{}:{},{}", addr.buffername, addr.start, addr.end)); ctx.current() = {addr.buffername, addr.end}; }, } @@ -653,7 +609,7 @@ void Function::register_posix(BEd &ctx) { auto filename = ctx.buffer(addr).filename(); auto &arg = std::get(arg_); auto cmd = arg.cmd; - if (escape_command(ctx, cmd, filename.string())) + if (ctx.escape_command(cmd, filename.string())) ctx.io.write(cmd + "\n"); ctx.io.run_pty(cmd); ctx.io.write("!\n"); @@ -675,5 +631,27 @@ void Function::register_posix(BEd &ctx) { ctx.current() = addr; } }; + ctx.eof_op = Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Try quitting.", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector *) { + 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."); + } + }; } } // namespace bed::internal::functions diff --git a/src/internal/io/io.cc b/src/internal/io/io.cc index e39e3ea..14723bb 100644 --- a/src/internal/io/io.cc +++ b/src/internal/io/io.cc @@ -106,6 +106,11 @@ void IO::write(std::string_view s) { write_all(STDOUT_FILENO, s.data(), s.size()); } +void IO::write_line(std::string_view s) { + write(s); + write("\n", 1); +} + void IO::run_pty(const std::string &cmd) { int master_fd = -1; struct winsize ws{}; @@ -120,7 +125,12 @@ void IO::run_pty(const std::string &cmd) { if (pid == -1) throw fatal_error("Can't create PTY.", 1); if (pid == 0) { - execl("/bin/sh", "sh", "-c", cmd.c_str(), (char *)nullptr); + const char *shell = getenv("BED_SHELL"); + if (!shell || !*shell) + shell = getenv("SHELL"); + if (!shell || !*shell) + shell = "/bin/sh"; + execl(shell, shell, "-i", "-c", cmd.c_str(), (char *)nullptr); _exit(127); } struct pollfd fds[2]; diff --git a/src/main.cc b/src/main.cc index 7178d7b..88f2a20 100644 --- a/src/main.cc +++ b/src/main.cc @@ -9,12 +9,12 @@ int main(int argc, char *argv[]) { ed.run(); } catch (bed::fatal_error &e) { if (e.code) - std::cout << "Fatal error: " << e.what() << std::endl; + printf("Fatal error: %s\n", e.what()); return e.code; } #ifndef DEBUG catch (...) { - std::cout << "Unexpected error." << std::endl; + printf("Unexpected error.\n"); return 1; } #endif