diff --git a/include/internal/buffer/types/generic.h b/include/internal/buffer/types/generic.h index bc99cc6..a0aa133 100644 --- a/include/internal/buffer/types/generic.h +++ b/include/internal/buffer/types/generic.h @@ -29,7 +29,7 @@ struct GenericBuffer : ShardBuffer { void snapshot(std::string action); bool undo(BEd &ctx); bool redo(BEd &ctx); - void prune(int = 0); + uint64_t prune(int); bool waste() override; void load(BEd &ctx, vase::Shard *text) override; void set_filename(std::filesystem::path path) override; diff --git a/src/bed/run.cc b/src/bed/run.cc index eb7fac0..7686f61 100644 --- a/src/bed/run.cc +++ b/src/bed/run.cc @@ -174,7 +174,7 @@ internal::buffer::Buffer &BEd::buffer(const std::string &name) { if (name.starts_with(prefix)) { std::string_view path{name}; path.remove_prefix(prefix.size()); - auto slash = path.rfind('/'); + auto slash = path.rfind('_'); if (slash == std::string_view::npos || slash == 0 || slash == path.size() - 1) throw ed_error("invalid history"); std::string bufname(path.substr(0, slash)); @@ -197,6 +197,15 @@ internal::buffer::Buffer &BEd::buffer(const std::string &name) { } throw ed_error("Buffer has no history."); } + for (auto &c : name) + if (!(('0' <= c && c <= '9') + || ('a' <= c && c <= 'z') + || ('A' <= c && c <= 'Z') + || c == '-' || c == '_' + || c == '+' || c == '.' + || c == ',' || c == '$' + || c == '/' || c == '~')) + throw ed_error("Invalid buffer name."); auto *buf = new internal::buffer::GenericBuffer(name); buffers.emplace(name, buf); return *buf; diff --git a/src/internal/buffer/generic.cc b/src/internal/buffer/generic.cc index 7a822e4..8815078 100644 --- a/src/internal/buffer/generic.cc +++ b/src/internal/buffer/generic.cc @@ -178,7 +178,7 @@ bool GenericBuffer::redo(BEd &ctx) { return true; } -void GenericBuffer::prune(int keep) { +uint64_t GenericBuffer::prune(int keep) { size_t drop = undo_stack.size() > (size_t)keep ? undo_stack.size() - keep @@ -197,11 +197,13 @@ void GenericBuffer::prune(int keep) { vase::Shard::release(item.text); } redo_stack.clear(); + return undo_stack.size(); } bool GenericBuffer::waste() { return save_path.empty() - && root == nullptr; + && root == nullptr + && undo_stack.empty(); } void GenericBuffer::load(BEd &ctx, vase::Shard *text) { diff --git a/src/internal/buffer/special/clip.cc b/src/internal/buffer/special/clip.cc index ea60171..afa9e68 100644 --- a/src/internal/buffer/special/clip.cc +++ b/src/internal/buffer/special/clip.cc @@ -128,7 +128,7 @@ void ClipBuffer::substitute( if (old_lines) ctx.marks.erase(name, line, old_lines); if (new_lines) - ctx.marks.insert(name, line, new_lines - 1); + ctx.marks.insert(name, line, new_lines); } ); clip_write(s); diff --git a/src/internal/functions/extended.cc b/src/internal/functions/extended.cc index c0795e9..c554716 100644 --- a/src/internal/functions/extended.cc +++ b/src/internal/functions/extended.cc @@ -3,14 +3,134 @@ #include "internal/functions/suffixes.h" namespace bed::internal::functions { +namespace ansi { +constexpr auto reset = "\033[0m"; +constexpr auto bold = "\033[1m"; +constexpr auto cyan = "\033[36m"; +constexpr auto yellow = "\033[33m"; +constexpr auto magenta = "\033[35m"; +constexpr auto green = "\033[32m"; +constexpr auto blue = "\033[34m"; +} // namespace ansi + +static void append_field(std::string &msg, std::string_view label, std::string_view label_color, std::string_view value) { + if (value.empty()) + return; + msg += "\n"; + msg += ansi::bold; + msg += label_color; + msg += label; + msg += ansi::reset; + msg += ": "; + msg += value; +} + +static std::string_view address_kind_str(Function::AddressKind k) { + switch (k) { + case Function::AddressKind::None: + return "none"; + case Function::AddressKind::Line: + return "1 line"; + case Function::AddressKind::Range: + return "range"; + } + return ""; +} + +static std::string_view input_mode_str(Function::InputMode m) { + switch (m) { + case Function::InputMode::None: + return ""; + case Function::InputMode::Text: + return "text"; + case Function::InputMode::Interactive: + return "interactive"; + case Function::InputMode::CommandList: + return "command list"; + } + return ""; +} + +static std::string_view argument_kind_str(Function::ArgumentKind a) { + switch (a) { + case Function::ArgumentKind::None: + return ""; + case Function::ArgumentKind::Regex: + return "regex"; + case Function::ArgumentKind::Shell: + return "shell command"; + case Function::ArgumentKind::Any: + return "any"; + case Function::ArgumentKind::File: + return "file"; + case Function::ArgumentKind::Global: + return "ed global-style"; + case Function::ArgumentKind::Mark: + return "mark name"; + case Function::ArgumentKind::Number: + return "number"; + case Function::ArgumentKind::Line: + return "address"; + case Function::ArgumentKind::Range: + return "range"; + case Function::ArgumentKind::Ruby: + return "ruby code"; + } + return ""; +} + +static std::string describe_function(const Function &func) { + std::string msg = ansi::bold; + msg += func.desc; + msg += ansi::reset; + append_field(msg, "Default address", ansi::cyan, func.default_address); + append_field(msg, "Address", ansi::yellow, address_kind_str(func.address_kind)); + if (func.accept_zero) + append_field(msg, "Zero address", ansi::magenta, "allowed"); + append_field(msg, "Input", ansi::green, input_mode_str(func.input_mode)); + append_field(msg, "Argument", ansi::blue, argument_kind_str(func.argument_kind)); + return msg; +} + void Function::register_extented(BEd &ctx) { + ctx.functions.insert( + "*", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::Any, + .input_mode = Function::InputMode::None, + .desc = "Explain a command", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = []( + BEd &ctx, + const buffer::Address &, + vase::Shard *, + const Argument &arg_, + std::vector * + ) { + auto &str = std::get(arg_); + if (str.empty()) { + ctx.io.write_line(describe_function(ctx.no_op)); + return; + } + auto len = ctx.functions.longest_match(str); + if (len == str.size()) { + ctx.io.write_line(describe_function(*ctx.functions.get_ptr(str))); + return; + } + throw ed_error("Not a valid function name."); + }, + } + ); ctx.functions.insert( "#", Function{ .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::Any, .input_mode = Function::InputMode::None, - .desc = "Comment.", + .desc = "Write a comment", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -29,7 +149,7 @@ void Function::register_extented(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::Any, .input_mode = Function::InputMode::None, - .desc = "Echo given message.", + .desc = "Echo the given message (replacing $1-$4 with address information)", .default_address = "", .accept_zero = true, .pre_text_mode = nullptr, @@ -57,17 +177,30 @@ void Function::register_extented(BEd &ctx) { str[i - 1] = '\n'; continue; } - if (str[i] == '$' && i < str.size() && '1' <= str[i + 1] && str[i + 1] <= '2') { - bool one = str[i + 1] == '1'; + if (str[i] == '$' && i < str.size() && '1' <= str[i + 1] && str[i + 1] <= '4') { + char c = str[i + 1]; str.erase(i, 2); - if (one) { + switch (c) { + case '1': { auto start = std::to_string(addr.start); str.insert(i, start); i += start.size(); - } else { + } break; + case '2': { auto end = std::to_string(addr.end); str.insert(i, end); i += end.size(); + } break; + case '3': { + str.insert(i, addr.buffername); + i += addr.buffername.size(); + } break; + case '4': { + auto &buf = ctx.buffer(addr.buffername); + auto filename = buf.filename().string(); + str.insert(i, filename); + i += filename.size(); + } break; } continue; } @@ -83,7 +216,7 @@ void Function::register_extented(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::Any, .input_mode = Function::InputMode::None, - .desc = "Change directory.", + .desc = "Change directory", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -121,7 +254,7 @@ void Function::register_extented(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Print directory.", + .desc = "Print the current working directory", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -145,7 +278,7 @@ void Function::register_extented(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::Range, .input_mode = Function::InputMode::None, - .desc = "Exchange a range of lines for another.", + .desc = "Exchange a range of lines for another", .default_address = ".,.", .accept_zero = false, .pre_text_mode = nullptr, @@ -175,7 +308,7 @@ void Function::register_extented(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Redo last undo.", + .desc = "Redo the last undo modification", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -202,7 +335,7 @@ void Function::register_extented(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "List history versions.", + .desc = "List available history versions", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -222,5 +355,34 @@ void Function::register_extented(BEd &ctx) { } } ); + ctx.functions.insert( + "hp", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::Number, + .input_mode = Function::InputMode::None, + .desc = "Prune old history versions", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = []( + BEd &ctx, + const buffer::Address &addr_, + vase::Shard *, + const Argument &arg_, + std::vector * + ) { + auto &addr = std::get(addr_); + auto &arg = std::get(arg_); + auto &buf_ = ctx.buffer(addr); + if (buf_.kind != buffer::Buffer::Kind::Generic) + throw ed_error("Can't prune buffer."); + auto &buf = *(buffer::GenericBuffer *)&buf_; + auto versions = buf.prune(arg); + if (!ctx.suppress_mode) + ctx.io.write_line(std::format("{} undo versions left.", versions)); + } + } + ); } } // namespace bed::internal::functions diff --git a/src/internal/functions/posix.cc b/src/internal/functions/posix.cc index 02b917a..b1fca20 100644 --- a/src/internal/functions/posix.cc +++ b/src/internal/functions/posix.cc @@ -34,7 +34,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Line, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::Text, - .desc = "Append text to a line.", + .desc = "Append text to a line", .default_address = ".", .accept_zero = true, .pre_text_mode = nullptr, @@ -57,7 +57,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::Text, - .desc = "Change set of lines.", + .desc = "Change a range of lines", .default_address = ".,.", .accept_zero = false, .pre_text_mode = nullptr, @@ -80,7 +80,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Delete set of lines.", + .desc = "Delete a range of lines", .default_address = ".,.", .accept_zero = false, .pre_text_mode = nullptr, @@ -102,7 +102,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::File, .input_mode = Function::InputMode::None, - .desc = "Try load a file into the current buffer.", + .desc = "Load a file into the current buffer, warn once if unsaved changes exist", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -152,7 +152,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::File, .input_mode = Function::InputMode::None, - .desc = "Load a file into the current buffer.", + .desc = "Load a file into the current buffer, discarding unsaved changes", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -198,7 +198,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::File, .input_mode = Function::InputMode::None, - .desc = "Set a save path.", + .desc = "Set/print a save path", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -248,7 +248,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Toggle help mode.", + .desc = "Toggle help mode", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -271,7 +271,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Line, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::Text, - .desc = "Insert text before a line.", + .desc = "Insert text before a line", .default_address = ".", .accept_zero = true, .pre_text_mode = nullptr, @@ -296,7 +296,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Join a set of lines.", + .desc = "Join a range of lines", .default_address = ".,.+1", .accept_zero = false, .pre_text_mode = nullptr, @@ -318,7 +318,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Line, .argument_kind = Function::ArgumentKind::Mark, .input_mode = Function::InputMode::None, - .desc = "Mark a line.", + .desc = "Mark a line", .default_address = ".", .accept_zero = false, .pre_text_mode = nullptr, @@ -340,7 +340,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "List (print unambiguous) range", + .desc = "Print a range, showing nonprinting characters unambiguously", .default_address = ".,.", .accept_zero = false, .pre_text_mode = nullptr, @@ -363,7 +363,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::Line, .input_mode = Function::InputMode::None, - .desc = "Move a range of lines.", + .desc = "Move a range of lines", .default_address = ".,.", .accept_zero = false, .pre_text_mode = nullptr, @@ -407,7 +407,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Print range with line numbers", + .desc = "Print a range with line numbers", .default_address = ".,.", .accept_zero = false, .pre_text_mode = nullptr, @@ -430,7 +430,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Print range", + .desc = "Print a range", .default_address = ".,.", .accept_zero = false, .pre_text_mode = nullptr, @@ -453,7 +453,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::Any, .input_mode = Function::InputMode::None, - .desc = "Toggle/set prompt.", + .desc = "Toggle/set prompt string", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -482,7 +482,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Try quitting.", + .desc = "Quit, warn once if unsaved changes exist", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -513,7 +513,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Force quit.", + .desc = "Quit without saving, discarding unsaved changes", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -534,7 +534,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Line, .argument_kind = Function::ArgumentKind::File, .input_mode = Function::InputMode::None, - .desc = "Read from file into buffer.", + .desc = "Read a file's contents into the buffer after the given address", .default_address = "$", .accept_zero = true, .pre_text_mode = nullptr, @@ -581,7 +581,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::Regex, .input_mode = Function::InputMode::None, - .desc = "Substitute regex.", + .desc = "Substitute pattern in range for replacement", .default_address = ".,.", .accept_zero = false, .pre_text_mode = nullptr, @@ -620,7 +620,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::Line, .input_mode = Function::InputMode::None, - .desc = "Copy a range of lines.", + .desc = "Copy a range of lines", .default_address = ".,.", .accept_zero = false, .pre_text_mode = nullptr, @@ -650,7 +650,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Undo last modification to buffer.", + .desc = "Undo the last modification to the buffer", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -677,7 +677,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::File, .input_mode = Function::InputMode::None, - .desc = "Write buffer to disk.", + .desc = "Write a range of lines to disk", .default_address = "0,$", .accept_zero = true, .pre_text_mode = nullptr, @@ -729,7 +729,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Range, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Print line numbers", + .desc = "Print the line number of the given address", .default_address = "$", .accept_zero = true, .pre_text_mode = nullptr, @@ -758,7 +758,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::Shell, .input_mode = Function::InputMode::None, - .desc = "Run a shell command.", + .desc = "Run a shell command", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr, @@ -785,7 +785,7 @@ void Function::register_posix(BEd &ctx) { .address_kind = Function::AddressKind::Line, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Prints a line and jumps to it.", + .desc = "Print given line and move the cursor to it", .default_address = ".+1", .accept_zero = true, .pre_text_mode = nullptr, @@ -800,13 +800,16 @@ void Function::register_posix(BEd &ctx) { if (addr.number != 0) ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number); ctx.current() = addr; + ctx.prev().buffername = addr.buffername; + ctx.prev().start = addr.number; + ctx.prev().end = addr.number; } }; ctx.eof_op = Function{ .address_kind = Function::AddressKind::None, .argument_kind = Function::ArgumentKind::None, .input_mode = Function::InputMode::None, - .desc = "Try quitting.", + .desc = "Quit, warn once if unsaved changes exist", .default_address = "", .accept_zero = false, .pre_text_mode = nullptr,