diff --git a/include/bed.h b/include/bed.h index ab2b0ef..1e6e7e3 100644 --- a/include/bed.h +++ b/include/bed.h @@ -4,11 +4,11 @@ #include "internal/buffer/buffer.h" #include "internal/functions/functions.h" #include "internal/functions/suffixes.h" -#include "internal/io/command.h" #include "internal/io/io.h" #include "internal/marks/marks.h" #include "internal/theme/theme.h" #include "internal/ui/autocomp.h" +#include "internal/ui/command.h" #include "pch.h" namespace bed { @@ -27,6 +27,7 @@ struct BEd { bool prompt_mode = true; std::function prompt = nullptr; bool suppress_mode = false; + bool temporary_current = false; std::string last_regex = ""; std::string last_symbol = ""; std::string last_replacement = ""; diff --git a/include/internal/buffer/buffer.h b/include/internal/buffer/buffer.h index c06798b..9423430 100644 --- a/include/internal/buffer/buffer.h +++ b/include/internal/buffer/buffer.h @@ -25,6 +25,8 @@ struct Buffer { ~Buffer(); uint64_t lines(); + uint64_t bytes(); + void load(BEd &ctx, vase::Shard *text); vase::Shard *copy(uint64_t start_line, uint64_t end_line); void substitute( BEd &ctx, uint64_t start_line, uint64_t end_line, diff --git a/include/internal/io/io.h b/include/internal/io/io.h index 9ab91e6..296e07e 100644 --- a/include/internal/io/io.h +++ b/include/internal/io/io.h @@ -50,6 +50,14 @@ struct KeyEvent { }; struct IO { + static termios orig_termios; + static termios raw_termios; + static bool cleaned; + static void cleanup(); + static void enable_raw(); + static volatile std::atomic_bool resized; + static void handle_sigwinch(int); + IO(); ~IO(); @@ -70,13 +78,6 @@ struct IO { void write(const char *, uint64_t); void write(std::string_view); -private: - static termios orig_termios; - static bool cleaned; - static void cleanup(); - static volatile std::atomic_bool resized; - static void handle_sigwinch(int); - std::deque input_queue; KeyEvent::ReadResult get_next_byte(char &out); diff --git a/include/internal/marks/marks.h b/include/internal/marks/marks.h index 13fbf03..ddc5bd3 100644 --- a/include/internal/marks/marks.h +++ b/include/internal/marks/marks.h @@ -48,8 +48,8 @@ struct MarksEngine { continue; if (marks[i].number == UINT64_MAX) continue; - if (marks[i].number >= start) { - if (marks[i].number < start + count) + if (marks[i].number > start) { + if (marks[i].number <= start + count) marks[i].number = start; else marks[i].number -= count; diff --git a/include/internal/parser/parser.h b/include/internal/parser/parser.h index 2e76992..033187f 100644 --- a/include/internal/parser/parser.h +++ b/include/internal/parser/parser.h @@ -2,7 +2,7 @@ #include "internal/functions/functions.h" #include "internal/functions/suffixes.h" -#include "internal/io/command.h" +#include "internal/ui/command.h" #include "pch.h" namespace bed::internal::parser { @@ -85,12 +85,12 @@ struct Parser { std::string_view cmd; uint16_t i; Command *command; - std::vector *tokens; + std::vector *tokens; CompletionContext *completion; explicit Parser( std::string_view cmd, BEd &bed, Command *command, - std::vector *tokens, CompletionContext *completion + std::vector *tokens, CompletionContext *completion ); char peek(uint16_t = 0); // == \0 if at eof. diff --git a/include/internal/syntax/parser.h b/include/internal/syntax/parser.h index db6a6a6..a39e571 100644 --- a/include/internal/syntax/parser.h +++ b/include/internal/syntax/parser.h @@ -5,8 +5,6 @@ #include "pch.h" namespace bed::internal::syntax { -void dump_events(ParseState *node); - struct Parser { static constexpr uint64_t MAX_CHUNK = 512; diff --git a/include/internal/io/command.h b/include/internal/ui/command.h similarity index 88% rename from include/internal/io/command.h rename to include/internal/ui/command.h index a75a1ab..5e5742b 100644 --- a/include/internal/io/command.h +++ b/include/internal/ui/command.h @@ -4,7 +4,7 @@ #include "internal/ui/autocomp.h" #include "pch.h" -namespace bed::internal::io { +namespace bed::internal::ui { struct Token { enum struct Type : uint8_t { TempCurrent, // @ @@ -39,10 +39,10 @@ struct CommandIO { uint16_t term_height; uint16_t term_width; BEd &bed; - IO &io; + io::IO &io; - CommandIO(BEd &, IO &); + CommandIO(BEd &, io::IO &); std::pair run(); void redraw(); }; -} // namespace bed::internal::io +} // namespace bed::internal::ui diff --git a/include/internal/vase/shard.h b/include/internal/vase/shard.h index 9677812..d55debd 100644 --- a/include/internal/vase/shard.h +++ b/include/internal/vase/shard.h @@ -27,14 +27,12 @@ struct Shard { static Shard *from_file(const std::filesystem::path &path, bool posix_ending); static Shard *from_string(const char *data, uint64_t len, bool posix_ending); static Shard *from_command(const char *cmd, bool posix_ending); - // static std::vector from_swap(std::filesystem::path &path, OriginalStorage *b); static std::pair split(Shard *n, uint64_t offset); static Shard *concat(Shard *a, Shard *b); static Shard *merge_leaves(Shard *a, Shard *b); static Shard *append(Shard *root, Shard *leaf); static Shard *build(Shard **pieces, uint64_t lo, uint64_t hi); - static void dump(Shard *node, int depth = 0); }; struct Branch : Shard { diff --git a/include/internal/vase/vase.h b/include/internal/vase/vase.h index 3fecc90..8d5f12f 100644 --- a/include/internal/vase/vase.h +++ b/include/internal/vase/vase.h @@ -48,6 +48,7 @@ std::string to_string(Shard *root, Range range); 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); uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start); diff --git a/src/bed/run.cc b/src/bed/run.cc index cf22e33..6104575 100644 --- a/src/bed/run.cc +++ b/src/bed/run.cc @@ -64,37 +64,41 @@ void BEd::handle(std::string_view cmd, bool eof) { return; } internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this); + if (c.temp_address) { + marks.get(251) = marks.get(250); + temporary_current = true; + } internal::buffer::Address address; switch (c.function->address_kind) { case internal::functions::Function::AddressKind::None: { auto a = internal::parser::AddressPromise::get_line(*this, c.addresses); - if (a.has_value()) { - address = a->buffername; - } else { + if (!a.has_value()) { auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this); a = internal::parser::AddressPromise::get_line(*this, vec); - address = a->buffername; + if (!a.has_value()) + a = current(); } + address = a->buffername; } break; case internal::functions::Function::AddressKind::Line: { auto a = internal::parser::AddressPromise::get_line(*this, c.addresses); - if (a.has_value()) { - address = *a; - } else { + if (!a.has_value()) { auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this); a = internal::parser::AddressPromise::get_line(*this, vec); - address = *a; + if (!a.has_value()) + a = current(); } + address = *a; } break; case internal::functions::Function::AddressKind::Range: { auto a = internal::parser::AddressPromise::get_range(*this, c.addresses); - if (a.has_value()) { - address = *a; - } else { + if (!a.has_value()) { auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this); a = internal::parser::AddressPromise::get_range(*this, vec); - address = *a; + if (!a.has_value()) + a = internal::buffer::Range(current(), current()); } + address = *a; } break; } if (std::holds_alternative(c.argument)) { @@ -110,12 +114,36 @@ void BEd::handle(std::string_view cmd, bool eof) { else c.argument = internal::buffer::Range(current(), current()); } + if (!c.function->accept_zero) { + if (std::holds_alternative(address)) { + if (std::get(address).number == 0) + throw ed_error("Line number can't be zero."); + } else if (std::holds_alternative(address)) { + auto r = std::get(address); + if (r.start == 0 || r.end == 0) + throw ed_error("Line number can't be zero."); + } + } c.function->handle(*this, address, nullptr, c.argument, nullptr); if (c.suffix) c.suffix->handle(*this); + if (c.temp_address) + temporary_current = false; + for (auto it = buffers.begin(); it != buffers.end();) { + internal::buffer::Buffer *buf = it->second; + if (buf->save_path.empty() + && buf->root == nullptr) { + delete buf; + it = buffers.erase(it); + } else { + ++it; + } + } } internal::buffer::Buffer &BEd::buffer(const std::string &name) { + if (name.empty()) + throw ed_error("can't have empty buffer name"); auto it = buffers.find(name); if (it != buffers.end()) return *it->second; @@ -125,7 +153,7 @@ internal::buffer::Buffer &BEd::buffer(const std::string &name) { } internal::buffer::Line &BEd::current() { - return marks.get(250); + return marks.get(250 + temporary_current); } void BEd::mark(char m, internal::buffer::Line line) { diff --git a/src/internal/buffer/buffer.cc b/src/internal/buffer/buffer.cc index 853bdc0..9a56cd8 100644 --- a/src/internal/buffer/buffer.cc +++ b/src/internal/buffer/buffer.cc @@ -4,7 +4,6 @@ namespace bed::internal::buffer { Buffer::Buffer(std::string name) : state(Unmodified), root(nullptr), name(name) { - parser.emplace(root, lines(), syntax::ruby::lang_ruby()); // just for debug. } Buffer::~Buffer() { @@ -17,6 +16,24 @@ uint64_t Buffer::lines() { return 0; } +uint64_t Buffer::bytes() { + if (root) + return root->length + 1; + return 0; +} + +void Buffer::load(BEd &, vase::Shard *text) { + try { + vase::Shard::release(root); + root = text; + state = buffer::Buffer::Unmodified; + parser.emplace(root, lines(), syntax::ruby::lang_ruby()); + } catch (...) { + vase::Shard::release(text); + throw; + } +} + void Buffer::append(BEd &ctx, vase::Shard *text, uint64_t line) { ctx.prev.buffername = name; ctx.prev.start = line; @@ -31,8 +48,8 @@ void Buffer::append(BEd &ctx, vase::Shard *text, uint64_t line) { void Buffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) { root = vase::erase(root, start_line, end_line); ctx.prev.buffername = name; - ctx.prev.start = start_line; - ctx.prev.end = start_line; + ctx.prev.start = lines() ? 1 : 0; + ctx.prev.end = lines(); ctx.marks.erase(name, start_line, end_line - start_line + 1); if (parser) parser->erase(root, start_line, end_line - start_line + 1); @@ -40,7 +57,7 @@ void Buffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) { } void Buffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) { - root = vase::substitute(&ctx.append, root, R"(\n)", start_line, end_line, "", "g"); + root = vase::join(root, start_line, end_line); ctx.prev.buffername = name; ctx.prev.start = start_line; ctx.prev.end = start_line; @@ -54,7 +71,7 @@ void Buffer::substitute( BEd &ctx, uint64_t start_line, uint64_t end_line, std::string ®ex, std::string &replacement, std::string &options ) { - // make substitue return a list of modifications made. + // TODO: make substitue return a list of modifications made. root = vase::substitute(&ctx.append, root, regex, start_line, end_line, replacement, options); /*prev_range.start = start_line; prev_range.end = start_line; diff --git a/src/internal/functions/functions.cc b/src/internal/functions/functions.cc index df22d8d..7d2dd39 100644 --- a/src/internal/functions/functions.cc +++ b/src/internal/functions/functions.cc @@ -150,12 +150,13 @@ void Function::register_posix(BEd &ctx) { .default_address = "$", .accept_zero = true, .pre_text_mode = nullptr, - .handle = [](BEd &, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector *) { + .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"; else std::cout << ':' << addr.buffername << ':' << addr.start << "," << addr.end << "\n"; + ctx.current() = {addr.buffername, addr.end}; }, } ); @@ -204,17 +205,8 @@ void Function::register_posix(BEd &ctx) { else throw ed_error("Need filename to load."); } - try { - if (buf.lines()) - buf.remove(ctx, 1, buf.lines()); - buf.append(ctx, s, 0); - buf.state = buffer::Buffer::Unmodified; - } catch (...) { - vase::Shard::release(s); - throw; - } - std::cout << (s->length + 1) << std::endl; - vase::Shard::release(s); + buf.load(ctx, s); + std::cout << buf.bytes() << std::endl; ctx.current() = {addr, buf.lines()}; } } @@ -244,17 +236,8 @@ void Function::register_posix(BEd &ctx) { else throw ed_error("Need filename to load."); } - try { - if (buf.lines()) - buf.remove(ctx, 1, buf.lines()); - buf.append(ctx, s, 0); - buf.state = buffer::Buffer::Unmodified; - } catch (...) { - vase::Shard::release(s); - throw; - } - std::cout << (s->length + 1) << std::endl; - vase::Shard::release(s); + buf.load(ctx, s); + std::cout << buf.bytes() << std::endl; ctx.current() = {addr, buf.lines()}; } } @@ -291,5 +274,23 @@ void Function::register_posix(BEd &ctx) { } } ); + ctx.functions.insert( + "P", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Toggle prompt.", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector *) { + ctx.prompt_mode = !ctx.prompt_mode; + if (ctx.prompt_mode && !ctx.prompt) { + ctx.prompt = [](BEd &) { return "*"; }; + } + } + } + ); } } // namespace bed::internal::functions diff --git a/src/internal/io/_command.cc b/src/internal/io/_command.cc deleted file mode 100644 index 18d37c2..0000000 --- a/src/internal/io/_command.cc +++ /dev/null @@ -1,10 +0,0 @@ -#include "bed.h" -#include "internal/io/command.h" -#include "internal/io/io.h" - -namespace bed::internal::io { -std::pair IO::get_command(BEd &ctx) { - CommandIO cio(ctx, *this); - return cio.run(); -} -} // namespace bed::internal::io diff --git a/src/internal/io/command.cc b/src/internal/io/command.cc index b6e02eb..ab6fe3f 100644 --- a/src/internal/io/command.cc +++ b/src/internal/io/command.cc @@ -1,156 +1,10 @@ +#include "internal/ui/command.h" #include "bed.h" +#include "internal/io/io.h" namespace bed::internal::io { -/*template -static void for_each_cluster(std::string_view s, F &&f) { - unicode_width_state_t state; - unicode_width_init(&state); - size_t i = 0; - while (i < s.size()) { - unsigned char c = static_cast(s[i]); - size_t bytes = 1; - int width = 0; - if (c < 128) { - width = unicode_width_process(&state, c); - } else { - uint_least32_t cp; - size_t decoded = grapheme_decode_utf8(s.data() + i, s.size() - i, &cp); - bytes = decoded > 0 ? decoded : 1; - width = unicode_width_process(&state, cp); - } - if (width < 0) - width = 0; - f(i, bytes, width); - i += bytes; - } -} - -static int display_width(std::string_view s) { - int w = 0; - for_each_cluster(s, [&](size_t, size_t, int cw) { w += cw; }); - return w; -} - -static uint16_t count_clusters(std::string_view s) { - uint16_t n = 0; - for_each_cluster(s, [&](size_t, size_t, int) { ++n; }); - return n; -} - -static std::vector wrap_offsets(std::string_view line, uint16_t avail) { - std::vector offsets{0}; - int col = 0; - for_each_cluster(line, [&](uint16_t i, uint16_t, int w) { - if (col + w > avail && col > 0) { - offsets.push_back(i); - col = 0; - } - col += w; - }); - return offsets; -} - -// The word under/before `byte_pos`, split on plain ASCII spaces. Used to -// pick what prefix to hand the suggestion trie. -// TODO: change to use libgrapheme word break here. -static std::string current_word(const std::string &line, size_t byte_pos) { - size_t start = (byte_pos == 0) ? std::string::npos : line.rfind(' ', byte_pos - 1); - start = (start == std::string::npos) ? 0 : start + 1; - if (byte_pos < start) - byte_pos = start; - return line.substr(start, byte_pos - start); -}*/ - -CommandIO::CommandIO(BEd &bed, IO &io) : bed(bed), io(io) { - prompt = bed.prompt(bed); - cursor = 0; -} - -std::pair CommandIO::run() { - auto [row, col] = io.cursor_position(); - auto [rows, cols] = io.terminal_size(); - if (row > rows) - throw fatal_error("Invalid cursor position.", 1); - start = row; - height = rows - row + 1; - term_width = cols; - term_height = rows; - redraw(); - - // main loop. - KeyEvent res; - bool running = true; - while (running) { - res = io.read_key(); - switch (res.type) { - case KeyEvent::KeyType::EOF_: - running = false; - break; - case KeyEvent::KeyType::MOUSE: - case KeyEvent::KeyType::RESIZE: - break; - case KeyEvent::KeyType::CHAR: - switch (res.modifier) { - case KeyEvent::Modifier::SHIFT: - case KeyEvent::Modifier::ALT: - case KeyEvent::Modifier::CTRL_ALT: - case KeyEvent::Modifier::CTRL: - break; - case KeyEvent::Modifier::NONE: - if (res.text[0] == '\b' || res.text[0] == 0x7f) { - if (cursor > 0) - cmd.erase(--cursor, 1); - } else if (res.text[0] == '\n') { - running = false; - } else { - cmd.insert(cursor++, res.text); - } - break; - } - break; - case KeyEvent::KeyType::PASTE: - cmd.insert(cursor, res.text); - cursor += res.text.size(); - break; - case KeyEvent::KeyType::SPECIAL: - switch (res.special_key) { - case KeyEvent::SpecialKey::UNKNOWN: - case KeyEvent::SpecialKey::UP: - case KeyEvent::SpecialKey::DOWN: - break; - case KeyEvent::SpecialKey::RIGHT: - if (cursor < cmd.size()) - cursor++; - break; - case KeyEvent::SpecialKey::LEFT: - if (cursor > 0) - cursor--; - break; - case KeyEvent::SpecialKey::DELETE: - if (cursor < cmd.size()) - cmd.erase(cursor, 1); - break; - } - break; - } - redraw(); - } - - for (uint16_t i = 1; i < height; ++i) { - io.move_cursor(start + i, 1); - io.write("\x1b[2K", 4); - } - io.move_cursor(start, 1); - io.write("\n", 1); - return {cmd, false}; -} - -void CommandIO::redraw() { - io.move_cursor(start, 1); - io.write("\x1b[2K", 4); - io.move_cursor(start, 1); - io.write(prompt); - io.write(cmd); - io.move_cursor(start, prompt.size() + cursor + 1); +std::pair IO::get_command(BEd &ctx) { + ui::CommandIO cio(ctx, *this); + return cio.run(); } } // namespace bed::internal::io diff --git a/src/internal/io/io.cc b/src/internal/io/io.cc index a7d7d6f..72b9d4f 100644 --- a/src/internal/io/io.cc +++ b/src/internal/io/io.cc @@ -2,7 +2,8 @@ namespace bed::internal::io { termios IO::orig_termios{}; -bool IO::cleaned = false; +termios IO::raw_termios{}; +bool IO::cleaned = true; volatile std::atomic_bool IO::resized(false); IO::IO() { @@ -14,16 +15,13 @@ IO::IO() { sa.sa_flags = 0; if (sigaction(SIGWINCH, &sa, nullptr) == -1) throw fatal_error("Can't install SIGWINCH handler.", 1); - struct termios raw = orig_termios; - raw.c_iflag &= ~(BRKINT | ISTRIP | IXON); - raw.c_cflag |= (CS8); - raw.c_lflag &= ~(ECHO | ICANON | ISIG); - raw.c_cc[VMIN] = 1; - raw.c_cc[VTIME] = 0; - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) - throw fatal_error("Can't set terminal state.", 1); - std::string os = "\x1b[?2004h"; - write_all(STDOUT_FILENO, os.c_str(), os.size()); + raw_termios = orig_termios; + raw_termios.c_iflag &= ~(BRKINT | ISTRIP | IXON); + raw_termios.c_cflag |= (CS8); + raw_termios.c_lflag &= ~(ECHO | ICANON | ISIG); + raw_termios.c_cc[VMIN] = 1; + raw_termios.c_cc[VTIME] = 0; + enable_raw(); atexit(cleanup); } @@ -70,6 +68,16 @@ std::pair IO::cursor_position() { return {(uint16_t)row, (uint16_t)col}; } +void IO::enable_raw() { + if (!cleaned) + return; + std::string os = "\x1b[?2004h"; + write_all(STDOUT_FILENO, os.c_str(), os.size()); + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_termios) == -1) + throw fatal_error("Can't set raw terminal state.", 1); + cleaned = false; +} + void IO::cleanup() { if (cleaned) return; diff --git a/src/internal/parser/address.cc b/src/internal/parser/address.cc new file mode 100644 index 0000000..424b097 --- /dev/null +++ b/src/internal/parser/address.cc @@ -0,0 +1,252 @@ +#include "bed.h" +#include "internal/parser/parser.h" + +namespace bed::internal::parser { +void Parser::locator(AddressPromise &addr) { + addr.base = AddressPromise::None{}; + switch (peek()) { + case '.': + advance(); + addr.base = AddressPromise::Current{}; + break; + case '$': + advance(); + addr.base = AddressPromise::Last{}; + break; + case '%': + advance(); + addr.base = AddressPromise::LastRange{}; + break; + case '[': + advance(); + addr.base = AddressPromise::Block{Direction::Backward}; + break; + case ']': + advance(); + addr.base = AddressPromise::Block{Direction::Forward}; + break; + case '^': + advance(); + addr.base = AddressPromise::Diagnostic{Direction::Backward}; + break; + case '~': + advance(); + addr.base = AddressPromise::Diagnostic{Direction::Forward}; + break; + case '\'': + advance(); + if (('a' <= peek() && peek() <= 'z') + || ('A' <= peek() && peek() <= 'Z')) + addr.base = AddressPromise::Mark{peek()}; + else + throw ed_error("Valid mark needed after \'"); + advance(); + break; + case '{': { + advance(); + uint16_t j = 0; + std::string func; + std::string arg; + while (peek(j) != '}') { + if (peek(j) == '\0') + throw ed_error("Scripted address not terminated"); + if (peek(j) == '\\') + ++j; + if (peek(j) == ':') { + func = peek_str(j); + advance(j + 1); + j = 0; + continue; + } + ++j; + } + if (func.size()) + arg = peek_str(j); + else + func = peek_str(j); + addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)}; + advance(j + 1); + } break; + case '/': { + advance(); + uint64_t j = 0; + while (true) { + if (peek(j) == '\0') + break; + if (peek(j) == '/') + break; + else if (peek(j) == '\\') + j += 2; + else if (peek(j) == '[') + while (peek(j) != '\0' && cmd[i] != ']') + j++; + else + j++; + } + addr.base = AddressPromise::Regex( + Direction::Forward, + std::string(peek_str(j)) + ); + advance(j + 1); + } break; + case '?': { + advance(); + uint16_t j = 0; + while (true) { + if (peek(j) == '\0') + break; + if (peek(j) == '?') + break; + else if (peek(j) == '\\') + j += 2; + else if (peek(j) == '[') + while (peek(j) != '\0' && cmd[i] != ']') + j++; + else + j++; + } + addr.base = AddressPromise::Regex( + Direction::Backward, + std::string(peek_str(j)) + ); + advance(j + 1); + } break; + case '<': { + advance(); + uint16_t j = 0; + while (peek(j) != '>' + && peek(j) != '<' + && peek(j) != '\0') + j++; + switch (peek(j)) { + case '\0': + case '>': + addr.base = AddressPromise::SymbolDefinition{ + std::string(peek_str(j)) + }; + break; + case '<': + addr.base = AddressPromise::SymbolReference{ + Direction::Backward, + std::string(peek_str(j)) + }; + break; + } + advance(j + 1); + } break; + case '>': { + advance(); + uint16_t j = 0; + while (peek(j) != '>' && peek(j) != '\0') + j++; + switch (peek(j)) { + case '\0': + throw ed_error("Unterminated symbol reference addressing."); + case '>': + addr.base = AddressPromise::SymbolReference{ + Direction::Forward, + std::string(peek_str(j)) + }; + break; + } + advance(j + 1); + } break; + case '+': { + advance(); + addr.base = AddressPromise::Current{}; + uint16_t j = 0; + uint64_t num = 0; + while ('0' <= peek(j) && peek(j) <= '9') { + num = num * 10 + (peek(j) - '0'); + j++; + } + if (j == 0) + num = 1; + advance(j); + addr.offset += num; + } break; + case '-': { + advance(); + addr.base = AddressPromise::Current{}; + uint16_t j = 0; + uint64_t num = 0; + while ('0' <= peek(j) && peek(j) <= '9') { + num = num * 10 + (peek(j) - '0'); + j++; + } + if (j == 0) + num = 1; + advance(j); + addr.offset -= num; + } break; + default: + if ('0' <= peek() && peek() <= '9') { + uint64_t num = 0; + while ('0' <= peek() && peek() <= '9') { + num = num * 10 + (peek() - '0'); + advance(); + } + addr.base = AddressPromise::Number{num}; + } + } +} + +int64_t Parser::offset() { + int64_t offset = 0; + while (peek() == '+' || peek() == '-' + || ('0' <= peek() && peek() <= '9')) { + bool positive = peek() != '-'; + if (peek() == '+' || peek() == '-') + advance(); + uint16_t j = 0; + int64_t num = 0; + while ('0' <= peek(j) && peek(j) <= '9') + num = num * 10 + (peek(j++) - '0'); + if (j == 0) + num = 1; + advance(j); + offset += positive ? num : -num; + skip_ws(); + } + skip_ws(); + return offset; +} + +void Parser::address(AddressPromise &addr) { + if (peek() == ':') { + advance(); + uint16_t j = 0; + while (peek(j) != ':' && peek(j) != '\0') + j++; + addr.bufname = peek_str(j); + advance(j); + if (peek() == ':') + advance(); + } + skip_ws(); + if (peek() == '\0') + return; + locator(addr); + skip_ws(); + addr.offset += offset(); +} + +void Parser::addresses(std::vector &addresses) { + skip_ws(); + addresses.push_back({}); + auto *addr = &addresses.back(); + address(*addr); + while (peek() == ',' || peek() == ';') { + addr->jumping = peek() == ';'; + advance(); + skip_ws(); + addresses.push_back({}); + addr = &addresses.back(); + address(*addr); + } + if (addresses.size() == 1 + && addr->offset == 0 && !addr->bufname.has_value() + && std::holds_alternative(addr->base)) + addresses.pop_back(); +} +} // namespace bed::internal::parser diff --git a/src/internal/parser/address_promise.cc b/src/internal/parser/address_promise.cc index 96927bb..1d5942a 100644 --- a/src/internal/parser/address_promise.cc +++ b/src/internal/parser/address_promise.cc @@ -6,13 +6,16 @@ namespace bed::internal::parser { buffer::Line AddressPromise::resolve(BEd &ctx) { buffer::Line result = std::visit( [&](auto const &addr) -> buffer::Line { - if (!bufname.has_value()) + if (!bufname.has_value() || bufname->empty()) throw ed_error("Buffer name can't be empty."); buffer::Line line = {*bufname, 0}; auto &buf = ctx.buffer(line.buffername); using T = std::decay_t; if constexpr (std::is_same_v) { - throw ed_error("no address"); + if (line.buffername == ctx.current().buffername) + line.number = ctx.current().number; + else + line.number = buf.lines(); } else if constexpr (std::is_same_v) { if (line.buffername == ctx.current().buffername) line.number = ctx.current().number; @@ -133,15 +136,18 @@ std::optional AddressPromise::get_line(BEd &ctx, std::vector(curr.base)) { - if (!prev_set) - return std::nullopt; - if (prev_given) { - curr.base = prev.base; - curr.offset = prev.offset; - } else { - curr.base = Last(); - curr.offset = 0; + if (prev_set) { + if (prev_given) { + curr.base = prev.base; + curr.offset = prev.offset; + } else { + curr.base = Last(); + curr.offset = 0; + } } + } else if (std::holds_alternative(curr.base)) { + curr.bufname = ctx.prev.buffername; + curr.base = Number(ctx.prev.end); } return curr.resolve(ctx); } @@ -193,16 +199,18 @@ std::optional AddressPromise::get_range(BEd &ctx, std::vector(curr.base)) { - if (!prev_set) - return std::nullopt; - if (prev_given) { - curr.base = prev.base; - curr.offset = prev.offset; - } else { - curr.base = Last(); - curr.offset = 0; + if (prev_set) { + if (prev_given) { + curr.base = prev.base; + curr.offset = prev.offset; + } else { + curr.base = Last(); + curr.offset = 0; + } + return buffer::Range(prev.resolve(ctx), curr.resolve(ctx)); } - return buffer::Range(prev.resolve(ctx), curr.resolve(ctx)); + } else if (std::holds_alternative(curr.base)) { + return ctx.prev; } if (prev_given) return buffer::Range(prev.resolve(ctx), curr.resolve(ctx)); diff --git a/src/internal/parser/operation.cc b/src/internal/parser/operation.cc new file mode 100644 index 0000000..c90d65e --- /dev/null +++ b/src/internal/parser/operation.cc @@ -0,0 +1,205 @@ +#include "bed.h" +#include "internal/parser/parser.h" + +namespace bed::internal::parser { +void Parser::operation() { + if (peek() == '\0') { + command->function = &bed.no_op; + return; + } + uint64_t len = bed.functions.longest_match(peek_str()); + if (len == 0) + throw ed_error("Function not found."); + functions::Function *function = bed.functions.get_ptr(peek_str(len)); + advance(len); + command->function = function; + char suffix = '\0'; + switch (command->function->argument_kind) { + case functions::Function::ArgumentKind::None: + break; + case functions::Function::ArgumentKind::Number: + skip_ws(); + command->argument = offset(); + break; + case functions::Function::ArgumentKind::Mark: + if (('a' <= peek() && peek() <= 'z') + || ('A' <= peek() && peek() <= 'Z')) + command->argument = peek(); + else + throw ed_error("Valid mark needed."); + advance(); + break; + case functions::Function::ArgumentKind::Any: + command->argument = std::string(peek_str()); + advance(peek_str().size()); + break; + case functions::Function::ArgumentKind::Global: { + char delim; + std::string val; + switch (peek()) { + case '\0': + throw ed_error("Command needs a delimited value."); + case '{': { + advance(); + delim = '}'; + uint16_t j = 0; + while (peek(j) != '}' && peek(j) != '\0') { + if (peek(j) == '\\') + j++; + j++; + } + switch (peek(j)) { + case '\0': + throw ed_error("Unterminated {"); + case '}': + val = peek_str(j); + break; + } + advance(j + 1); + } break; + case '<': { + advance(); + delim = '<'; + uint16_t j = 0; + while (peek(j) != '>' || peek(j) != '\0') { + if (peek(j) == '\\') + j++; + j++; + } + switch (peek(j)) { + case '\0': + throw ed_error("Unterminated <"); + case '>': + val = peek_str(j); + break; + } + advance(j + 1); + } break; + case '^': + case '~': + advance(); + delim = '^'; + break; + default: + delim = peek(); + advance(); + uint64_t j = 0; + while (true) { + if (peek(j) == '\0') + break; + if (peek(j) == delim) + break; + else if (peek(j) == '\\') + j += 2; + else if (peek(j) == '[') + while (peek(j) != '\0' && cmd[i] != ']') + j++; + else + j++; + } + val = peek_str(j); + advance(j + 1); + } + command->argument = functions::Function::GlobalArg(delim, std::move(val)); + } break; + case functions::Function::ArgumentKind::File: + skip_ws(); + switch (peek()) { + case '!': + advance(); + command->argument = functions::Function::ShellArg(std::string(peek_str())); + advance(peek_str().size()); + break; + case '\0': + command->argument = std::monostate(); + break; + default: + command->argument = std::filesystem::path(peek_str()); + advance(peek_str().size()); + break; + } + break; + case functions::Function::ArgumentKind::Line: + command->argument = buffer::Line(); + addresses(command->argument_addresses); + break; + case functions::Function::ArgumentKind::Range: + command->argument = buffer::Range(); + addresses(command->argument_addresses); + break; + case functions::Function::ArgumentKind::Regex: { + char delim = peek(); + if (delim == '\0') + throw ed_error("regex expected"); + advance(); + uint64_t j = 0; + while (true) { + if (peek(j) == '\0') + throw ed_error("Unterminated regex"); + if (peek(j) == delim) + break; + else if (peek(j) == '\\') + j += 2; + else if (peek(j) == '[') + while (peek(j) != '\0' && cmd[i] != ']') + j++; + else + j++; + } + std::string expression(peek_str(j)); + advance(j + 1); + j = 0; + while (true) { + if (peek(j) == '\0') + break; + if (peek(j) == delim) + break; + else if (peek(j) == '\\') + j += 2; + else if (peek(j) == '[') + while (peek(j) != '\0' && cmd[i] != ']') + j++; + else + j++; + } + std::string replacement; + if (peek(j) != '\0') { + replacement = peek_str(j); + advance(j + 1); + } + std::string options; + if (peek() != '\0') { + options = std::string(peek_str()); + advance(peek_str().size()); + } + std::erase_if(options, [&](char c) { + if (bed.suffixes[c - 'a'].has_value()) { + suffix = c; + return true; + } + return false; + }); + command->argument = functions::Function::RegexArg(expression, replacement, options); + } break; + case functions::Function::ArgumentKind::Ruby: + command->argument = functions::Function::RubyArg(std::string(peek_str())); + advance(peek_str().size()); + break; + case functions::Function::ArgumentKind::Shell: + command->argument = functions::Function::ShellArg(std::string(peek_str())); + advance(peek_str().size()); + break; + } + if (!suffix) { + suffix = peek(); + advance(); + } + if (suffix) { + auto &s = bed.suffixes[suffix - 'a']; + if (s.has_value()) + command->suffix = &s.value(); + else + throw ed_error("Invalid suffix."); + } +} +} // namespace bed::internal::parser diff --git a/src/internal/parser/parser.cc b/src/internal/parser/parser.cc index 8284af3..3e6fee0 100644 --- a/src/internal/parser/parser.cc +++ b/src/internal/parser/parser.cc @@ -19,455 +19,6 @@ void Parser::skip_ws() { advance(); } -void Parser::locator(AddressPromise &addr) { - addr.base = AddressPromise::None{}; - switch (peek()) { - case '.': - advance(); - addr.base = AddressPromise::Current{}; - break; - case '$': - advance(); - addr.base = AddressPromise::Last{}; - break; - case '%': - advance(); - addr.base = AddressPromise::LastRange{}; - break; - case '[': - advance(); - addr.base = AddressPromise::Block{Direction::Backward}; - break; - case ']': - advance(); - addr.base = AddressPromise::Block{Direction::Forward}; - break; - case '^': - advance(); - addr.base = AddressPromise::Diagnostic{Direction::Backward}; - break; - case '~': - advance(); - addr.base = AddressPromise::Diagnostic{Direction::Forward}; - break; - case '\'': - advance(); - if (('a' <= peek() && peek() <= 'z') - || ('A' <= peek() && peek() <= 'Z')) - addr.base = AddressPromise::Mark{peek()}; - else - throw ed_error("Valid mark needed after \'"); - advance(); - break; - case '{': { - advance(); - uint16_t j = 0; - std::string func; - std::string arg; - while (peek(j) != '}') { - if (peek(j) == '\0') - throw ed_error("Scripted address not terminated"); - if (peek(j) == '\\') - ++j; - if (peek(j) == ':') { - func = peek_str(j); - advance(j + 1); - j = 0; - continue; - } - ++j; - } - if (func.size()) - arg = peek_str(j); - else - func = peek_str(j); - addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)}; - advance(j + 1); - } break; - case '/': { - advance(); - uint64_t j = 0; - while (true) { - if (peek(j) == '\0') - break; - if (peek(j) == '/') - break; - else if (peek(j) == '\\') - j += 2; - else if (peek(j) == '[') - while (peek(j) != '\0' && cmd[i] != ']') - j++; - else - j++; - } - addr.base = AddressPromise::Regex( - Direction::Forward, - std::string(peek_str(j)) - ); - advance(j + 1); - } break; - case '?': { - advance(); - uint16_t j = 0; - while (true) { - if (peek(j) == '\0') - break; - if (peek(j) == '?') - break; - else if (peek(j) == '\\') - j += 2; - else if (peek(j) == '[') - while (peek(j) != '\0' && cmd[i] != ']') - j++; - else - j++; - } - addr.base = AddressPromise::Regex( - Direction::Backward, - std::string(peek_str(j)) - ); - advance(j + 1); - } break; - case '<': { - advance(); - uint16_t j = 0; - while (peek(j) != '>' - && peek(j) != '<' - && peek(j) != '\0') - j++; - switch (peek(j)) { - case '\0': - case '>': - addr.base = AddressPromise::SymbolDefinition{ - std::string(peek_str(j)) - }; - break; - case '<': - addr.base = AddressPromise::SymbolReference{ - Direction::Backward, - std::string(peek_str(j)) - }; - break; - } - advance(j + 1); - } break; - case '>': { - advance(); - uint16_t j = 0; - while (peek(j) != '>' && peek(j) != '\0') - j++; - switch (peek(j)) { - case '\0': - throw ed_error("Unterminated symbol reference addressing."); - case '>': - addr.base = AddressPromise::SymbolReference{ - Direction::Forward, - std::string(peek_str(j)) - }; - break; - } - advance(j + 1); - } break; - case '+': { - advance(); - addr.base = AddressPromise::Current{}; - uint16_t j = 0; - uint64_t num = 0; - while ('0' <= peek(j) && peek(j) <= '9') { - num = num * 10 + (peek(j) - '0'); - j++; - } - if (j == 0) - num = 1; - advance(j); - addr.offset += num; - } break; - case '-': { - advance(); - addr.base = AddressPromise::Current{}; - uint16_t j = 0; - uint64_t num = 0; - while ('0' <= peek(j) && peek(j) <= '9') { - num = num * 10 + (peek(j) - '0'); - j++; - } - if (j == 0) - num = 1; - advance(j); - addr.offset -= num; - } break; - default: - if ('0' <= peek() && peek() <= '9') { - uint64_t num = 0; - while ('0' <= peek() && peek() <= '9') { - num = num * 10 + (peek() - '0'); - advance(); - } - addr.base = AddressPromise::Number{num}; - } - } -} - -int64_t Parser::offset() { - int64_t offset = 0; - while (peek() == '+' || peek() == '-' - || ('0' <= peek() && peek() <= '9')) { - bool positive = peek() != '-'; - if (peek() == '+' || peek() == '-') - advance(); - uint16_t j = 0; - int64_t num = 0; - while ('0' <= peek(j) && peek(j) <= '9') - num = num * 10 + (peek(j++) - '0'); - if (j == 0) - num = 1; - advance(j); - offset += positive ? num : -num; - skip_ws(); - } - skip_ws(); - return offset; -} - -void Parser::address(AddressPromise &addr) { - if (peek() == ':') { - advance(); - uint16_t j = 0; - while (peek(j) != ':' && peek(j) != '\0') - j++; - addr.bufname = peek_str(j); - advance(j); - if (peek() == ':') - advance(); - } - skip_ws(); - if (peek() == '\0') - return; - locator(addr); - skip_ws(); - addr.offset += offset(); -} - -void Parser::addresses(std::vector &addresses) { - skip_ws(); - addresses.push_back({}); - auto *addr = &addresses.back(); - address(*addr); - while (peek() == ',' || peek() == ';') { - addr->jumping = peek() == ';'; - advance(); - skip_ws(); - addresses.push_back({}); - addr = &addresses.back(); - address(*addr); - } - if (addresses.size() == 1 - && addr->offset == 0 && addr->bufname.has_value() - && std::holds_alternative(addr->base)) - addresses.pop_back(); -} - -void Parser::operation() { - if (peek() == '\0') { - command->function = &bed.no_op; - return; - } - uint64_t len = bed.functions.longest_match(peek_str()); - if (len == 0) - throw ed_error("Function not found."); - functions::Function *function = bed.functions.get_ptr(peek_str(len)); - advance(len); - command->function = function; - char suffix = '\0'; - switch (command->function->argument_kind) { - case functions::Function::ArgumentKind::None: - break; - case functions::Function::ArgumentKind::Number: - skip_ws(); - command->argument = offset(); - break; - case functions::Function::ArgumentKind::Mark: - if (('a' <= peek() && peek() <= 'z') - || ('A' <= peek() && peek() <= 'Z')) - command->argument = peek(); - else - throw ed_error("Valid mark needed."); - advance(); - break; - case functions::Function::ArgumentKind::Any: - command->argument = std::string(peek_str()); - advance(peek_str().size()); - break; - case functions::Function::ArgumentKind::Global: { - char delim; - std::string val; - switch (peek()) { - case '\0': - throw ed_error("Command needs a delimited value."); - case '{': { - advance(); - delim = '}'; - uint16_t j = 0; - while (peek(j) != '}' && peek(j) != '\0') { - if (peek(j) == '\\') - j++; - j++; - } - switch (peek(j)) { - case '\0': - throw ed_error("Unterminated {"); - case '}': - val = peek_str(j); - break; - } - advance(j + 1); - } break; - case '<': { - advance(); - delim = '<'; - uint16_t j = 0; - while (peek(j) != '>' || peek(j) != '\0') { - if (peek(j) == '\\') - j++; - j++; - } - switch (peek(j)) { - case '\0': - throw ed_error("Unterminated <"); - case '>': - val = peek_str(j); - break; - } - advance(j + 1); - } break; - case '^': - case '~': - advance(); - delim = '^'; - break; - default: - delim = peek(); - advance(); - uint64_t j = 0; - while (true) { - if (peek(j) == '\0') - break; - if (peek(j) == delim) - break; - else if (peek(j) == '\\') - j += 2; - else if (peek(j) == '[') - while (peek(j) != '\0' && cmd[i] != ']') - j++; - else - j++; - } - val = peek_str(j); - advance(j + 1); - } - command->argument = functions::Function::GlobalArg(delim, std::move(val)); - } break; - case functions::Function::ArgumentKind::File: - skip_ws(); - switch (peek()) { - case '!': - advance(); - command->argument = functions::Function::ShellArg(std::string(peek_str())); - advance(peek_str().size()); - break; - case '\0': - command->argument = std::monostate(); - break; - default: - command->argument = std::filesystem::path(peek_str()); - advance(peek_str().size()); - break; - } - break; - case functions::Function::ArgumentKind::Line: - command->argument = buffer::Line(); - addresses(command->argument_addresses); - break; - case functions::Function::ArgumentKind::Range: - command->argument = buffer::Range(); - addresses(command->argument_addresses); - break; - case functions::Function::ArgumentKind::Regex: { - char delim = peek(); - if (delim == '\0') - throw ed_error("regex expected"); - advance(); - uint64_t j = 0; - while (true) { - if (peek(j) == '\0') - throw ed_error("Unterminated regex"); - if (peek(j) == delim) - break; - else if (peek(j) == '\\') - j += 2; - else if (peek(j) == '[') - while (peek(j) != '\0' && cmd[i] != ']') - j++; - else - j++; - } - std::string expression(peek_str(j)); - advance(j + 1); - j = 0; - while (true) { - if (peek(j) == '\0') - break; - if (peek(j) == delim) - break; - else if (peek(j) == '\\') - j += 2; - else if (peek(j) == '[') - while (peek(j) != '\0' && cmd[i] != ']') - j++; - else - j++; - } - std::string replacement; - if (peek(j) != '\0') { - replacement = peek_str(j); - advance(j + 1); - } - std::string options; - if (peek() != '\0') { - options = std::string(peek_str()); - advance(peek_str().size()); - } - std::erase_if(options, [&](char c) { - if (bed.suffixes[c - 'a'].has_value()) { - suffix = c; - return true; - } - return false; - }); - command->argument = functions::Function::RegexArg(expression, replacement, options); - } break; - case functions::Function::ArgumentKind::Ruby: - command->argument = functions::Function::RubyArg(std::string(peek_str())); - advance(peek_str().size()); - break; - case functions::Function::ArgumentKind::Shell: - command->argument = functions::Function::ShellArg(std::string(peek_str())); - advance(peek_str().size()); - break; - } - if (!suffix) { - suffix = peek(); - advance(); - } - if (suffix) { - auto &s = bed.suffixes[suffix - 'a']; - if (s.has_value()) - command->suffix = &s.value(); - else - throw ed_error("Invalid suffix."); - } -} - void Parser::parse() { skip_ws(); if (peek() == '@') { @@ -486,14 +37,14 @@ void Parser::parse() { Parser::Parser( std::string_view cmd, BEd &bed, Command *command, - std::vector *tokens, CompletionContext *completion + std::vector *tokens, CompletionContext *completion ) : bed(bed), cmd(cmd), command(command), tokens(tokens), completion(completion) { i = 0; } Command Parser::get_command(std::string_view cmd, BEd &bed) { Command c; - std::vector tokens; + std::vector tokens; CompletionContext completion; Parser p(cmd, bed, &c, &tokens, &completion); p.parse(); @@ -502,7 +53,7 @@ Command Parser::get_command(std::string_view cmd, BEd &bed) { std::vector Parser::get_addresses(std::string_view cmd, BEd &bed) { std::vector result; - std::vector tokens; + std::vector tokens; CompletionContext completion; Parser p(cmd, bed, nullptr, &tokens, &completion); p.addresses(result); diff --git a/src/internal/syntax/parser.cc b/src/internal/syntax/parser.cc index 0e09672..18dbfa3 100644 --- a/src/internal/syntax/parser.cc +++ b/src/internal/syntax/parser.cc @@ -1,42 +1,6 @@ #include "internal/syntax/parser.h" namespace bed::internal::syntax { -void dump_events(ParseState *root) { - if (!root) - return; - uint64_t offset = 0; - TreeCursor c(root, 0, &offset); - uint64_t line_offset = 0; - int depth = 0; - while (c.leaf) { - auto *leaf = c.leaf; - for (uint32_t i = 0; i < leaf->n; ++i) { - uint32_t block = leaf->blocks[i]; - uint32_t pos = block & ParseStateLeaf::LINE_MASK; - bool closing = block & ParseStateLeaf::IS_CLOSING; - if (closing) { - if (depth > 0) - --depth; - else - std::cout << "!! UNMATCHED CLOSE !! "; - } - std::cout << std::string(static_cast(depth) * 2, ' ') - << (closing ? "}" : "{") - << " line " << (line_offset + pos) - << '\n'; - if (!closing) - ++depth; - } - line_offset += leaf->lines(); - c.next(); - } - if (depth != 0) { - std::cout << "!! UNBALANCED: depth = " - << depth - << " !!\n"; - } -} - static void destroy_tree(ParseState *node, Language &lang) { if (!node) return; diff --git a/src/internal/ui/command/command.cc b/src/internal/ui/command/command.cc new file mode 100644 index 0000000..1787020 --- /dev/null +++ b/src/internal/ui/command/command.cc @@ -0,0 +1,158 @@ +#include "internal/ui/command.h" +#include "bed.h" + +namespace bed::internal::ui { +/*template +static void for_each_cluster(std::string_view s, F &&f) { + unicode_width_state_t state; + unicode_width_init(&state); + size_t i = 0; + while (i < s.size()) { + unsigned char c = static_cast(s[i]); + size_t bytes = 1; + int width = 0; + if (c < 128) { + width = unicode_width_process(&state, c); + } else { + uint_least32_t cp; + size_t decoded = grapheme_decode_utf8(s.data() + i, s.size() - i, &cp); + bytes = decoded > 0 ? decoded : 1; + width = unicode_width_process(&state, cp); + } + if (width < 0) + width = 0; + f(i, bytes, width); + i += bytes; + } +} + +static int display_width(std::string_view s) { + int w = 0; + for_each_cluster(s, [&](size_t, size_t, int cw) { w += cw; }); + return w; +} + +static uint16_t count_clusters(std::string_view s) { + uint16_t n = 0; + for_each_cluster(s, [&](size_t, size_t, int) { ++n; }); + return n; +} + +static std::vector wrap_offsets(std::string_view line, uint16_t avail) { + std::vector offsets{0}; + int col = 0; + for_each_cluster(line, [&](uint16_t i, uint16_t, int w) { + if (col + w > avail && col > 0) { + offsets.push_back(i); + col = 0; + } + col += w; + }); + return offsets; +} + +// The word under/before `byte_pos`, split on plain ASCII spaces. Used to +// pick what prefix to hand the suggestion trie. +// TODO: change to use libgrapheme word break here. +static std::string current_word(const std::string &line, size_t byte_pos) { + size_t start = (byte_pos == 0) ? std::string::npos : line.rfind(' ', byte_pos - 1); + start = (start == std::string::npos) ? 0 : start + 1; + if (byte_pos < start) + byte_pos = start; + return line.substr(start, byte_pos - start); +}*/ + +CommandIO::CommandIO(BEd &bed, io::IO &io) : bed(bed), io(io) { + if (bed.prompt_mode) + prompt = bed.prompt(bed); + cursor = 0; +} + +std::pair CommandIO::run() { + auto [row, col] = io.cursor_position(); + auto [rows, cols] = io.terminal_size(); + if (row > rows) + throw fatal_error("Invalid cursor position.", 1); + start = row; + height = rows - row + 1; + term_width = cols; + term_height = rows; + redraw(); + + // main loop. + io::KeyEvent res; + bool running = true; + while (running) { + res = io.read_key(); + switch (res.type) { + case io::KeyEvent::KeyType::EOF_: + running = false; + break; + case io::KeyEvent::KeyType::MOUSE: + case io::KeyEvent::KeyType::RESIZE: + break; + case io::KeyEvent::KeyType::CHAR: + switch (res.modifier) { + case io::KeyEvent::Modifier::SHIFT: + case io::KeyEvent::Modifier::ALT: + case io::KeyEvent::Modifier::CTRL_ALT: + case io::KeyEvent::Modifier::CTRL: + break; + case io::KeyEvent::Modifier::NONE: + if (res.text[0] == '\b' || res.text[0] == 0x7f) { + if (cursor > 0) + cmd.erase(--cursor, 1); + } else if (res.text[0] == '\n') { + running = false; + } else { + cmd.insert(cursor++, res.text); + } + break; + } + break; + case io::KeyEvent::KeyType::PASTE: + cmd.insert(cursor, res.text); + cursor += res.text.size(); + break; + case io::KeyEvent::KeyType::SPECIAL: + switch (res.special_key) { + case io::KeyEvent::SpecialKey::UNKNOWN: + case io::KeyEvent::SpecialKey::UP: + case io::KeyEvent::SpecialKey::DOWN: + break; + case io::KeyEvent::SpecialKey::RIGHT: + if (cursor < cmd.size()) + cursor++; + break; + case io::KeyEvent::SpecialKey::LEFT: + if (cursor > 0) + cursor--; + break; + case io::KeyEvent::SpecialKey::DELETE: + if (cursor < cmd.size()) + cmd.erase(cursor, 1); + break; + } + break; + } + redraw(); + } + + for (uint16_t i = 1; i < height; ++i) { + io.move_cursor(start + i, 1); + io.write("\x1b[2K", 4); + } + io.move_cursor(start, 1); + io.write("\n", 1); + return {cmd, false}; +} + +void CommandIO::redraw() { + io.move_cursor(start, 1); + io.write("\x1b[2K", 4); + io.move_cursor(start, 1); + io.write(prompt); + io.write(cmd); + io.move_cursor(start, prompt.size() + cursor + 1); +} +} // namespace bed::internal::ui diff --git a/src/internal/vase/regex/api.cc b/src/internal/vase/regex/api.cc index db45280..c6a441e 100644 --- a/src/internal/vase/regex/api.cc +++ b/src/internal/vase/regex/api.cc @@ -80,20 +80,15 @@ Shard *substitute( (end + 1 == line_count) ? root->length : offset_of(root, end + 1); - std::vector matches = _regex_search(root, pattern, start_offset, end_offset, options); if (matches.empty()) return root; - std::vector replace_parts = parse_replace(ap, replace); - std::vector pieces; pieces.reserve(matches.size() * 2 + 1); - Shard *remaining = root; Shard::retain(remaining); uint64_t cursor = 0; - for (const RegexMatch &match : matches) { uint64_t gap = match.start - cursor; if (gap > 0) { @@ -102,11 +97,9 @@ Shard *substitute( pieces.push_back(keep); remaining = rest; } - auto [dropped, rest2] = Shard::split(remaining, match.end - match.start); Shard::release(remaining); remaining = rest2; - for (size_t i = 0; i < replace_parts.size(); ++i) { const ReplacePart &part = replace_parts[i]; switch (part.type) { @@ -141,11 +134,9 @@ Shard *substitute( cursor = match.end; } pieces.push_back(remaining); - for (auto &part : replace_parts) if (part.type == ReplacePart::PartType::Constant) Shard::release(std::get(part.value)); - std::vector compact; compact.reserve(pieces.size()); for (Shard *p : pieces) { @@ -154,7 +145,6 @@ Shard *substitute( else if (p) Shard::release(p); } - Shard *new_root = compact.empty() ? nullptr : Shard::build(compact.data(), 0, compact.size()); Shard::release(root); return new_root; diff --git a/src/internal/vase/shard.cc b/src/internal/vase/shard.cc index 3d15bb9..e0c95d0 100644 --- a/src/internal/vase/shard.cc +++ b/src/internal/vase/shard.cc @@ -1,3 +1,4 @@ +#include "internal/io/io.h" #include "internal/vase/vase.h" namespace bed::internal::vase { @@ -192,10 +193,10 @@ Shard *Shard::append(Shard *root, Shard *leaf) { Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) { if (hi - lo == 1) return pieces[lo]; - size_t mid = lo + (hi - lo) / 2; + uint64_t mid = lo + (hi - lo) / 2; Shard *left = build(pieces, lo, mid); Shard *right = build(pieces, mid, hi); - Shard *node = new Branch(left, right); + Shard *node = Shard::concat(left, right); Shard::release(left); Shard::release(right); return node; @@ -208,9 +209,11 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) { delete o; return nullptr; } + io::IO::cleanup(); FILE *pipe = popen(cmd, "r"); if (!pipe) { delete o; + io::IO::enable_raw(); return nullptr; } std::vector pieces; @@ -245,6 +248,7 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) { if (!write_all(dest_fd, buf, buf_cursor)) { pclose(pipe); delete o; + io::IO::enable_raw(); return nullptr; } pieces.push_back(new Petal(buf_cursor, lines, o, pos)); @@ -254,20 +258,24 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) { break; if (ferror(pipe)) { delete o; + io::IO::enable_raw(); return nullptr; } } int status = pclose(pipe); if (status == -1) { delete o; + io::IO::enable_raw(); return nullptr; } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { delete o; + io::IO::enable_raw(); return nullptr; } if (pieces.empty()) { delete o; + io::IO::enable_raw(); return nullptr; } if (posix_ending) { @@ -287,6 +295,7 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) { } } o->initialize(); + io::IO::enable_raw(); if (pieces.size() == 1) return pieces[0]; return build(pieces.data(), 0, pieces.size()); @@ -421,54 +430,4 @@ Shard *Shard::from_string(const char *data, uint64_t len, bool posix_ending) { return pieces[0]; return build(pieces.data(), 0, pieces.size()); } - -void Shard::dump(Shard *node, int depth) { - if (!node) { - std::cout << std::string(depth * 2, ' ') << "\n"; - return; - } - std::string indent(depth * 2, ' '); - std::cout << indent - << "Shard@" << node - << " kind="; - switch (node->kind) { - case Shard::Kind::Branch: - std::cout << "Branch"; - break; - case Shard::Kind::Petal: - std::cout << "Petal"; - break; - } - std::cout - << " height=" << unsigned(node->height) - << " length=" << node->length - << " lines=" << node->lines - << " refs=" << node->refs.load() - << "\n"; - if (node->kind == Shard::Kind::Branch) { - auto *branch = (Branch *)node; - std::cout << indent << " left:\n"; - dump(branch->left, depth + 2); - std::cout << indent << " right:\n"; - dump(branch->right, depth + 2); - } else { - auto *petal = static_cast(node); - constexpr auto clean = [](const std::string &text) { - std::string result = text; - size_t pos = 0; - while ((pos = result.find('\n', pos)) != std::string::npos) { - result.replace(pos, 1, "\\n"); - pos += 2; - } - return result; - }; - std::cout - << indent << " source=" << petal->source - << " pos=" << petal->pos - << " length=" << petal->length - << " lines=" << petal->lines - << " text=\"" << clean(std::string(petal->source->read(petal->pos), petal->length)) - << "\"\n"; - } -} } // namespace bed::internal::vase diff --git a/src/internal/vase/vase.cc b/src/internal/vase/vase.cc index 8f1e2c1..4ed9272 100644 --- a/src/internal/vase/vase.cc +++ b/src/internal/vase/vase.cc @@ -222,6 +222,38 @@ Shard *erase(Shard *root, uint64_t start, uint64_t end) { return new_root; } +Shard *join(Shard *root, uint64_t start, uint64_t end) { + if (!start || !end) + throw ed_error("Invalid range."); + start--; + end--; + if (!root) + throw ed_error("line range out of bounds"); + uint64_t line_count = root->lines + 1; + if (start >= end || end >= line_count) + throw ed_error("line range out of bounds"); + + uint64_t offset = offset_of(root, start); + std::vector pieces; + pieces.reserve(end - start + 3); + auto [a, b] = Shard::split(root, offset); + pieces.push_back(a); + for (uint64_t line = start; line < end; line++) { + uint64_t nl_pos = offset_of(b, 1) - 1; + auto [content, rest] = Shard::split(b, nl_pos); + Shard::release(b); + auto [nl, next_b] = Shard::split(rest, 1); + Shard::release(rest); + Shard::release(nl); + pieces.push_back(content); + b = next_b; + } + pieces.push_back(b); + Shard *joined = Shard::build(pieces.data(), 0, pieces.size()); + Shard::release(root); + return joined; +} + Shard *copy(Shard *root, uint64_t start, uint64_t end) { if (!start || !end) throw ed_error("Invalid range.");