From b8eac7b2da33a9c5b885a63a7481debeb017e4c3 Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Sat, 29 Aug 2026 22:30:01 +0100 Subject: [PATCH] Major updates: - A lotta cleanup - BEd command parser rewrite - Vase class removed - Proper IO system. - A lot more. --- include/bed.h | 39 +- include/definitions.h | 4 +- include/internal/address/address.h | 108 ---- include/internal/buffer/buffer.h | 78 ++- include/internal/commands/commands.h | 30 - include/internal/commands/suffixes.h | 13 - include/internal/functions/functions.h | 85 +++ include/internal/functions/suffixes.h | 14 + include/internal/io/command.h | 48 ++ include/internal/io/io.h | 6 +- include/internal/marks/marks.h | 49 +- include/internal/parser/parser.h | 114 ++++ include/internal/syntax/parser.h | 14 +- include/internal/trie/trie.h | 40 +- include/internal/vase/buffer/original.h | 19 - include/internal/vase/shard.h | 19 +- .../vase/{buffer => storage}/append.h | 11 +- include/internal/vase/storage/original.h | 30 + .../{buffer/buffer.h => storage/storage.h} | 6 +- include/internal/vase/vase.h | 154 ++--- src/bed/handle.cc | 101 ---- src/bed/run.cc | 97 +++- src/internal/address/address.cc | 146 ----- src/internal/address/handle.cc | 58 -- src/internal/address/resolve.cc | 76 --- src/internal/buffer/buffer.cc | 156 ++--- src/internal/commands/commands.cc | 226 -------- src/internal/functions/functions.cc | 295 ++++++++++ src/internal/io/_command.cc | 10 + src/internal/io/command.cc | 302 +++++----- src/internal/io/input.cc | 6 +- src/internal/io/io.cc | 8 + src/internal/io/text_mode.cc | 18 +- src/internal/parser/address_promise.cc | 215 +++++++ src/internal/parser/parser.cc | 514 +++++++++++++++++ src/internal/syntax/parser.cc | 18 +- src/internal/vase/regex/api.cc | 69 ++- src/internal/vase/regex/core.cc | 7 +- src/internal/vase/shard.cc | 127 +++-- .../vase/{buffer => storage}/append.cc | 14 +- .../vase/{buffer => storage}/original.cc | 10 +- src/internal/vase/vase.cc | 532 +++++------------- 42 files changed, 2153 insertions(+), 1733 deletions(-) delete mode 100644 include/internal/address/address.h delete mode 100644 include/internal/commands/commands.h delete mode 100644 include/internal/commands/suffixes.h create mode 100644 include/internal/functions/functions.h create mode 100644 include/internal/functions/suffixes.h create mode 100644 include/internal/io/command.h create mode 100644 include/internal/parser/parser.h delete mode 100644 include/internal/vase/buffer/original.h rename include/internal/vase/{buffer => storage}/append.h (70%) create mode 100644 include/internal/vase/storage/original.h rename include/internal/vase/{buffer/buffer.h => storage/storage.h} (62%) delete mode 100644 src/bed/handle.cc delete mode 100644 src/internal/address/address.cc delete mode 100644 src/internal/address/handle.cc delete mode 100644 src/internal/address/resolve.cc delete mode 100644 src/internal/commands/commands.cc create mode 100644 src/internal/functions/functions.cc create mode 100644 src/internal/io/_command.cc create mode 100644 src/internal/parser/address_promise.cc create mode 100644 src/internal/parser/parser.cc rename src/internal/vase/{buffer => storage}/append.cc (83%) rename src/internal/vase/{buffer => storage}/original.cc (80%) diff --git a/include/bed.h b/include/bed.h index ade7696..ab2b0ef 100644 --- a/include/bed.h +++ b/include/bed.h @@ -2,35 +2,25 @@ #include "definitions.h" #include "internal/buffer/buffer.h" -#include "internal/commands/commands.h" -#include "internal/commands/suffixes.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 "pch.h" namespace bed { -struct Suggestion { - enum : uint8_t { - Command, - Address, - Suffix, - History, - Regex - } type; - std::string_view name; - std::string_view desc; - std::string completion; -}; - struct BEd { - internal::trie::Trie commands; - internal::commands::Command no_op; - internal::commands::Command eof_op; - std::array, 26> suffixes; + internal::trie::Trie functions; + internal::functions::Function no_op; + internal::functions::Function eof_op; + std::array, 26> suffixes; internal::theme::Theme theme; std::unordered_map languages; internal::io::IO &io; + internal::vase::AppendStorage append{"/tmp"}; bool help_mode = false; std::string last_help = ""; @@ -38,12 +28,21 @@ struct BEd { std::function prompt = nullptr; bool suppress_mode = false; std::string last_regex = ""; + std::string last_symbol = ""; + std::string last_replacement = ""; std::unordered_map buffers; - internal::buffer::Buffer *active; + + internal::buffer::Range prev; + internal::marks::MarksEngine marks; BEd(std::vector args, internal::io::IO &io); ~BEd(); + + internal::buffer::Buffer &buffer(const std::string &); + internal::buffer::Line ¤t(); + void mark(char, internal::buffer::Line); + void handle(std::string_view cmd, bool eof); void run(); void suffix_handle(char s); diff --git a/include/definitions.h b/include/definitions.h index 97cb050..46e2c8f 100644 --- a/include/definitions.h +++ b/include/definitions.h @@ -4,6 +4,8 @@ #include "pch.h" namespace bed { +struct BEd; + struct fatal_error : std::runtime_error { uint8_t code; fatal_error(std::string msg, uint8_t code) @@ -26,6 +28,4 @@ struct Highlight { uint32_t bg; uint8_t flags; }; - -struct BEd; } // namespace bed diff --git a/include/internal/address/address.h b/include/internal/address/address.h deleted file mode 100644 index 0fa0c7a..0000000 --- a/include/internal/address/address.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include "definitions.h" -#include "internal/generic.h" -#include "pch.h" - -namespace bed::internal::address { -struct address_error : ed_error { - address_error(const char *msg) : ed_error(msg) {} -}; - -struct Address { - // % refers to range of whatever was resulted from the previous modification. - // it expands in theory to a Num,Num and so can be followed by chaining more adresses the ed way. - // this is handled and resolved by the handle function, - // the constuctor and resolve etc. are also only called in the handle function, - // - // in case of any issue an instance of address_error(const char *) is thrown. - - struct Result { - std::array data{}; - uint8_t size{0}; - std::span span(uint8_t max = UINT8_MAX) const { - return {data.data(), std::min(size, max)}; - } - }; - - struct None {}; - - // Posix ed types of addressing. - struct Current {}; // . - - struct Last {}; // $ - - struct Number { // n - uint64_t i; - }; - - struct Mark { // 'm - char m; - }; - - struct Regex { // /re/ or ?re? - internal::Direction dir; - std::string re; - }; - - // extended types - struct Diagnostic { // #n# for diagnostic number n. (From lsp.) - uint16_t n; - }; - - struct DiagnosticNext { // ^ or % for previous/next diagnostic - internal::Direction dir; - }; - - struct SymbolDefinition { // goto symbol definition. (From lsp or using internal language parsers) - std::string sym; - }; - - struct SymbolReference { // goto nth symbol reference - uint16_t n; - std::string sym; - }; - - struct SymbolReferenceNext { // >sym> or - base{}; - - // they can then be followed by any number of +n or -n etc accumulating in. - int64_t offset = 0; - - Address(std::string &cmd, uint64_t &i); - Address() = default; - - uint64_t resolve(BEd &ctx); - - static Result handle(BEd &ctx, std::string &cmd, uint64_t &i); -}; -} // namespace bed::internal::address diff --git a/include/internal/buffer/buffer.h b/include/internal/buffer/buffer.h index 870224e..c06798b 100644 --- a/include/internal/buffer/buffer.h +++ b/include/internal/buffer/buffer.h @@ -1,7 +1,6 @@ #pragma once #include "definitions.h" -#include "internal/marks/marks.h" #include "internal/syntax/parser.h" #include "internal/syntax/ruby/parser.h" #include "internal/theme/theme.h" @@ -9,36 +8,67 @@ namespace bed::internal::buffer { struct Buffer { - vase::Vase vase; - marks::MarksEngine marks; - std::string language; - std::optional parser; - uint64_t line = 0; - bool modified; + enum { + Unmodified, + Modified, + Warned + } state; std::filesystem::path save_path = ""; - struct { - uint64_t start{0}; - uint64_t end{0}; - std::span values() const { - return {&start, 2}; - } - } prev_range; + vase::Shard *root; - Buffer(); - Buffer(std::string command); - Buffer(std::filesystem::path path); + std::string name; + std::string language; + std::optional parser; - ~Buffer() = default; + explicit Buffer(std::string name); + ~Buffer(); - void load(std::string command); - void load(std::filesystem::path path); - void jump(uint64_t n_line); - void join(uint64_t start_line, uint64_t end_line); - void remove(uint64_t start_line, uint64_t end_line); - void append(std::string text, uint64_t line); + uint64_t lines(); + vase::Shard *copy(uint64_t start_line, uint64_t end_line); + void substitute( + BEd &ctx, uint64_t start_line, uint64_t end_line, + std::string ®ex, std::string &replacement, std::string &options + ); + void join(BEd &ctx, uint64_t start_line, uint64_t end_line); + void remove(BEd &ctx, uint64_t start_line, uint64_t end_line); + void append(BEd &ctx, vase::Shard *text, uint64_t line); void print(BEd &ctx, uint64_t start_line, uint64_t end_line); void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line); std::string list_string(std::string_view s); }; + +struct Line { + std::string buffername; + uint64_t number; +}; + +struct Range { + std::string buffername; + uint64_t start; + uint64_t end; + + explicit Range(const Line &a, const Line &b) { + if (a.buffername != b.buffername) + throw ed_error("Invalid range."); + if (a.number > b.number) + throw ed_error("Invalid range."); + buffername = a.buffername; + start = a.number; + end = b.number; + }; + + explicit Range(std::string buffername, uint64_t start, uint64_t end) + : buffername(buffername), start(start), end(end) { + if (start > end) + throw ed_error("Invalid range."); + }; + + explicit Range() : buffername("default"), start(0), end(0) {}; +}; + +using Address = std::variant< + std::string, + buffer::Range, + buffer::Line>; } // namespace bed::internal::buffer diff --git a/include/internal/commands/commands.h b/include/internal/commands/commands.h deleted file mode 100644 index e6aec59..0000000 --- a/include/internal/commands/commands.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "definitions.h" -#include "internal/trie/trie.h" -#include "pch.h" - -namespace bed::internal::commands { -struct Command { - enum struct AddressMode : uint8_t { - None, - Single, - Range - } address_mode; - - enum struct SuffixKind : uint8_t { - None, - Suffix, - Argument, - Continuation - } suffix; - - std::string desc; - - bool accept_zero; - - void (*handle)(BEd &, std::span, std::string_view); - - static void register_posix(BEd &ctx); -}; -} // namespace bed::internal::commands diff --git a/include/internal/commands/suffixes.h b/include/internal/commands/suffixes.h deleted file mode 100644 index 82a4bbc..0000000 --- a/include/internal/commands/suffixes.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "definitions.h" -#include "pch.h" - -namespace bed::internal::commands { -struct Suffix { - std::string desc; - void (*handle)(BEd &, std::span); - - static void register_suffixes(BEd &ctx); -}; -} // namespace bed::internal::commands diff --git a/include/internal/functions/functions.h b/include/internal/functions/functions.h new file mode 100644 index 0000000..96b9c16 --- /dev/null +++ b/include/internal/functions/functions.h @@ -0,0 +1,85 @@ +#pragma once + +#include "definitions.h" +#include "internal/buffer/buffer.h" +#include "internal/trie/trie.h" +#include "pch.h" + +namespace bed::internal::functions { +struct Function { + struct GlobalArg { + char delim; + std::string str; + }; + struct RegexArg { + std::string expression; + std::string replacement; + std::string options; + }; + struct ShellArg { + std::string cmd; + }; + struct RubyArg { + std::string cmd; + }; + using Argument = std::variant< + std::monostate, + GlobalArg, + RegexArg, + ShellArg, + RubyArg, + std::string, + std::filesystem::path, + int64_t, + char, + buffer::Range, + buffer::Line>; + + enum struct AddressKind { + None, + Line, + Range + } address_kind; + + enum struct ArgumentKind { + None, + Regex, + Shell, + Ruby, + Any, + File, + Number, + Mark, + Range, + Line, + Global + } argument_kind; + + enum struct InputMode { + None, + Text, + Interactive, + CommandList + } input_mode; + + std::string desc; + std::string default_address; + bool accept_zero; + + std::function< + std::tuple( + BEd &ctx, const buffer::Address &addr, const Argument &arg + )> + pre_text_mode; + + std::function< + void( + BEd &ctx, const buffer::Address &addr, + vase::Shard *text, const Argument &arg, + std::vector *marked + )> + handle; + + static void register_posix(BEd &ctx); +}; +} // namespace bed::internal::functions diff --git a/include/internal/functions/suffixes.h b/include/internal/functions/suffixes.h new file mode 100644 index 0000000..53c9e1a --- /dev/null +++ b/include/internal/functions/suffixes.h @@ -0,0 +1,14 @@ +#pragma once + +#include "definitions.h" +#include "internal/buffer/buffer.h" +#include "pch.h" + +namespace bed::internal::functions { +struct Suffix { + std::string desc; + std::function handle; + + static void register_suffixes(BEd &ctx); +}; +} // namespace bed::internal::functions diff --git a/include/internal/io/command.h b/include/internal/io/command.h new file mode 100644 index 0000000..a75a1ab --- /dev/null +++ b/include/internal/io/command.h @@ -0,0 +1,48 @@ +#pragma once + +#include "internal/io/io.h" +#include "internal/ui/autocomp.h" +#include "pch.h" + +namespace bed::internal::io { +struct Token { + enum struct Type : uint8_t { + TempCurrent, // @ + AddressSeperator, // ; , + Address, // . $ % [ ] ^ ~ + Offset, // +N -N + - + AddressRegex, // /re/ ?re? + AddressSymbol, // >s> + Number, // 10 + Mark, // 'm + RubyFunction, // (func:arg) + RubyArg, + Function, + Any, + Shell, + Ruby, + File, + Regex, + Replacement, + Suffix + } type; + uint16_t start; + uint16_t end; +}; + +struct CommandIO { + std::string cmd; + uint16_t cursor; + std::string prompt; + uint16_t start; + uint16_t height; + uint16_t term_height; + uint16_t term_width; + BEd &bed; + IO &io; + + CommandIO(BEd &, IO &); + std::pair run(); + void redraw(); +}; +} // namespace bed::internal::io diff --git a/include/internal/io/io.h b/include/internal/io/io.h index 2ceffc4..9ab91e6 100644 --- a/include/internal/io/io.h +++ b/include/internal/io/io.h @@ -11,7 +11,7 @@ struct KeyEvent { RESIZE }; enum struct KeyType { - NONE, + EOF_, CHAR, SPECIAL, MOUSE, @@ -38,7 +38,7 @@ struct KeyEvent { RELEASE }; - KeyType type = KeyType::NONE; + KeyType type = KeyType::EOF_; std::string text; SpecialKey special_key = SpecialKey::UNKNOWN; @@ -67,6 +67,8 @@ struct IO { std::pair get_text(BEd &); KeyEvent read_key(); + void write(const char *, uint64_t); + void write(std::string_view); private: static termios orig_termios; diff --git a/include/internal/marks/marks.h b/include/internal/marks/marks.h index c8a5f9c..13fbf03 100644 --- a/include/internal/marks/marks.h +++ b/include/internal/marks/marks.h @@ -1,55 +1,58 @@ #pragma once +#include "internal/buffer/buffer.h" #include "pch.h" namespace bed::internal::marks { struct MarksEngine { - uint64_t marks[256]; + buffer::Line marks[256]; MarksEngine() { for (auto &m : marks) - m = UINT64_MAX; + m = {"", UINT64_MAX}; } - uint64_t get(uint8_t m) { + buffer::Line &get(uint8_t m) { return marks[m]; } - void set(uint8_t m, uint64_t line) { - marks[m] = line; - } - - void insert(uint64_t start, uint64_t count) { + void insert(std::string bufname, uint64_t start, uint64_t count) { for (uint16_t i = 0; i <= UINT8_MAX; ++i) { - if (marks[i] == UINT64_MAX) + if (marks[i].buffername != bufname) continue; - if (marks[i] >= start) - marks[i] += count; + if (marks[i].number == UINT64_MAX) + continue; + if (marks[i].number >= start) + marks[i].number += count; } } - void erase(uint64_t start, uint64_t count) { + void erase(std::string bufname, uint64_t start, uint64_t count) { for (uint16_t i = 0; i <= UINT8_MAX; ++i) { - if (marks[i] == UINT64_MAX) + if (marks[i].buffername != bufname) continue; - if (marks[i] >= start) { - if (marks[i] < start + count) - marks[i] = UINT64_MAX; + if (marks[i].number == UINT64_MAX) + continue; + if (marks[i].number >= start) { + if (marks[i].number < start + count) + marks[i].number = UINT64_MAX; else - marks[i] -= count; + marks[i].number -= count; } } } - void collapse(uint64_t start, uint64_t count) { + void collapse(std::string bufname, uint64_t start, uint64_t count) { for (uint16_t i = 0; i <= UINT8_MAX; ++i) { - if (marks[i] == UINT64_MAX) + if (marks[i].buffername != bufname) continue; - if (marks[i] >= start) { - if (marks[i] < start + count) - marks[i] = start; + if (marks[i].number == UINT64_MAX) + continue; + if (marks[i].number >= start) { + if (marks[i].number < start + count) + marks[i].number = start; else - marks[i] -= count; + marks[i].number -= count; } } } diff --git a/include/internal/parser/parser.h b/include/internal/parser/parser.h new file mode 100644 index 0000000..2e76992 --- /dev/null +++ b/include/internal/parser/parser.h @@ -0,0 +1,114 @@ +#pragma once + +#include "internal/functions/functions.h" +#include "internal/functions/suffixes.h" +#include "internal/io/command.h" +#include "pch.h" + +namespace bed::internal::parser { +struct AddressPromise { + struct None {}; // + struct Current {}; // . + struct Last {}; // $ + struct Number { // n + uint64_t i; + }; + struct Mark { // 'm + char m; + }; + struct Regex { // /re/ or ?re? + internal::Direction dir; + std::string re; + }; + struct Diagnostic { // ^ or ~ for previous/next diagnostic + internal::Direction dir; + }; + struct SymbolDefinition { // goto symbol definition. (From lsp or using internal language parsers) + std::string sym; + }; + struct SymbolReference { // >sym> or bufname; + std::variant< + None, + Current, + Last, + Number, + Mark, + Regex, + Diagnostic, + SymbolDefinition, + SymbolReference, + Block, + Scripted, + LastRange> + base{}; + int64_t offset = 0; + bool jumping{false}; // ; == jumping and , == non jumping + buffer::Line resolve(BEd &ctx); + static std::optional get_line(BEd &ctx, std::vector &); + static std::optional get_range(BEd &ctx, std::vector &); +}; + +struct Command { + bool temp_address{false}; + std::vector addresses{}; + functions::Function *function; + functions::Function::Argument argument; + std::vector argument_addresses{}; + functions::Suffix *suffix; +}; + +struct CompletionContext { + enum { + Error, + Valid, + Incomplete + } error; + // TODO: store a state of typing context to think about possible completions. +}; + +struct Parser { + BEd &bed; + std::string_view cmd; + uint16_t i; + Command *command; + std::vector *tokens; + CompletionContext *completion; + + explicit Parser( + std::string_view cmd, BEd &bed, Command *command, + std::vector *tokens, CompletionContext *completion + ); + + char peek(uint16_t = 0); // == \0 if at eof. + std::string_view peek_str(uint16_t = UINT16_MAX); + void advance(uint16_t = 1); + + void skip_ws(); + + void locator(AddressPromise &); + int64_t offset(); + void address(AddressPromise &addr); + void addresses(std::vector &addresses); + + void operation(); + + void parse(); + + static Command get_command(std::string_view, BEd &); + static std::vector get_addresses(std::string_view cmd, BEd &bed); +}; +} // namespace bed::internal::parser diff --git a/include/internal/syntax/parser.h b/include/internal/syntax/parser.h index 246900f..db6a6a6 100644 --- a/include/internal/syntax/parser.h +++ b/include/internal/syntax/parser.h @@ -13,15 +13,15 @@ struct Parser { ParseState *root; Language lang; - Parser(vase::Vase &, uint64_t, Language); + Parser(vase::Shard *, uint64_t, Language); ~Parser(); Parser(const Parser &) = delete; Parser &operator=(const Parser &) = delete; - void reset(vase::Vase &, uint64_t, Language); - void erase(vase::Vase &, uint64_t, uint64_t); - void insert(vase::Vase &, uint64_t, uint64_t); - void modify(vase::Vase &, uint64_t, uint64_t); + void reset(vase::Shard *, uint64_t, Language); + void erase(vase::Shard *, uint64_t, uint64_t); + void insert(vase::Shard *, uint64_t, uint64_t); + void modify(vase::Shard *, uint64_t, uint64_t); uint64_t next_closing(uint64_t line); uint64_t prev_opening(uint64_t line); @@ -36,7 +36,7 @@ struct Parser { uint64_t at; std::vector tokens; std::vector events; - Iterator(uint64_t, Parser *, vase::Vase &); + Iterator(uint64_t, Parser *, vase::Shard *); ~Iterator(); Iterator(const Iterator &) = delete; Iterator &operator=(const Iterator &) = delete; @@ -44,6 +44,6 @@ struct Parser { Iterator &operator=(Iterator &&other); void next(); }; - std::optional get_hl(vase::Vase &, uint64_t); + std::optional get_hl(vase::Shard *, uint64_t); }; } // namespace bed::internal::syntax diff --git a/include/internal/trie/trie.h b/include/internal/trie/trie.h index 5ded2f2..ba4a2e1 100644 --- a/include/internal/trie/trie.h +++ b/include/internal/trie/trie.h @@ -147,7 +147,7 @@ struct Trie { } static void collect( - const Node &node, + Node &node, std::string &key, std::vector &result ) { @@ -228,6 +228,44 @@ struct Trie { return *current->value; } + V *get_ptr(std::string_view key) { + Node *current = &root; + uint64_t pos = 0; + while (pos < key.size()) { + Node *child = find_child(*current, key[pos]); + if (!child) + return nullptr; + const auto remaining = key.substr(pos); + const auto common = common_prefix(child->edge, remaining); + if (common != child->edge.size()) + return nullptr; + pos += common; + current = child; + } + if (!current->value) + return nullptr; + return &*current->value; + } + + const V *get_ptr(std::string_view key) const { + const Node *current = &root; + uint64_t pos = 0; + while (pos < key.size()) { + const Node *child = find_child(*current, key[pos]); + if (!child) + return nullptr; + const auto remaining = key.substr(pos); + const auto common = common_prefix(child->edge, remaining); + if (common != child->edge.size()) + return nullptr; + pos += common; + current = child; + } + if (!current->value) + return nullptr; + return &*current->value; + } + bool equal_char(char a, char b) const { if (case_sensitive) return a == b; diff --git a/include/internal/vase/buffer/original.h b/include/internal/vase/buffer/original.h deleted file mode 100644 index f2e9d41..0000000 --- a/include/internal/vase/buffer/original.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include "buffer.h" -#include "pch.h" - -namespace bed::internal::vase { -struct OriginalBuffer : Buffer { - const char *buf; - uint64_t len; - int fd = -1; - - OriginalBuffer(std::filesystem::path base_dir); - ~OriginalBuffer(); - - void initialize(); - const char *read(uint64_t pos) override; - uint64_t length() override; -}; -} // namespace bed::internal::vase diff --git a/include/internal/vase/shard.h b/include/internal/vase/shard.h index bfc0fba..9677812 100644 --- a/include/internal/vase/shard.h +++ b/include/internal/vase/shard.h @@ -1,9 +1,9 @@ #pragma once -#include "buffer/buffer.h" -#include "buffer/original.h" #include "constants.h" #include "pch.h" +#include "storage/original.h" +#include "storage/storage.h" namespace bed::internal::vase { struct Shard { @@ -24,9 +24,10 @@ struct Shard { static void retain(Shard *n); static void release(Shard *n); - static Shard *from_file(std::filesystem::path &path, OriginalBuffer *b, bool posix_ending); - static Shard *from_command(const char *cmd, OriginalBuffer *o, bool posix_ending); - static std::vector from_swap(std::filesystem::path &path, OriginalBuffer *b); + 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); @@ -54,12 +55,14 @@ struct Branch : Shard { }; struct Petal : Shard { - Buffer *source; + Storage *source; uint64_t pos; - Petal(uint64_t length, uint64_t lines, Buffer *source, uint64_t pos) + Petal(uint64_t length, uint64_t lines, Storage *source, uint64_t pos) : Shard(Kind::Petal, length, lines, 1), - source(source), pos(pos) {}; + source(source), pos(pos) { + source->retain(); + }; }; } // namespace bed::internal::vase diff --git a/include/internal/vase/buffer/append.h b/include/internal/vase/storage/append.h similarity index 70% rename from include/internal/vase/buffer/append.h rename to include/internal/vase/storage/append.h index e258e6b..ee96f13 100644 --- a/include/internal/vase/buffer/append.h +++ b/include/internal/vase/storage/append.h @@ -1,24 +1,27 @@ #pragma once #include "../constants.h" -#include "buffer.h" #include "pch.h" +#include "storage.h" namespace bed::internal::vase { -struct AppendBuffer : Buffer { +struct AppendStorage : Storage { char *buf = nullptr; uint64_t allocated_capacity = 0; uint64_t current_size = 0; int fd = -1; - AppendBuffer(std::filesystem::path base_dir); - ~AppendBuffer(); + AppendStorage(std::filesystem::path base_dir); + ~AppendStorage(); uint64_t append(const char c); uint64_t append(const char *text, uint64_t len); const char *read(uint64_t pos) override; uint64_t length() override; + void retain() override {} + void release() override {} + private: void grow(uint64_t len); }; diff --git a/include/internal/vase/storage/original.h b/include/internal/vase/storage/original.h new file mode 100644 index 0000000..0b621ad --- /dev/null +++ b/include/internal/vase/storage/original.h @@ -0,0 +1,30 @@ +#pragma once + +#include "pch.h" +#include "storage.h" + +namespace bed::internal::vase { +struct OriginalStorage : Storage { + std::atomic_uint64_t refs{0}; + const char *buf = nullptr; + uint64_t len = 0; + int fd = -1; + + OriginalStorage(std::filesystem::path base_dir); + ~OriginalStorage(); + + void initialize(); + const char *read(uint64_t pos) override; + uint64_t length() override; + + void retain() override { + refs++; + } + + void release() override { + if (--refs > 0) + return; + delete this; + } +}; +} // namespace bed::internal::vase diff --git a/include/internal/vase/buffer/buffer.h b/include/internal/vase/storage/storage.h similarity index 62% rename from include/internal/vase/buffer/buffer.h rename to include/internal/vase/storage/storage.h index 2f1812a..a6f728c 100644 --- a/include/internal/vase/buffer/buffer.h +++ b/include/internal/vase/storage/storage.h @@ -3,9 +3,11 @@ #include "pch.h" namespace bed::internal::vase { -struct Buffer { +struct Storage { virtual const char *read(uint64_t pos) = 0; virtual uint64_t length() = 0; - virtual ~Buffer() = default; + virtual ~Storage() = default; + virtual void retain() = 0; + virtual void release() = 0; }; } // namespace bed::internal::vase diff --git a/include/internal/vase/vase.h b/include/internal/vase/vase.h index 16aa2fd..3fecc90 100644 --- a/include/internal/vase/vase.h +++ b/include/internal/vase/vase.h @@ -1,12 +1,12 @@ #pragma once -#include "buffer/append.h" -#include "buffer/original.h" #include "constants.h" #include "definitions.h" #include "iterators/line.h" #include "pch.h" #include "shard.h" +#include "storage/append.h" +#include "storage/original.h" namespace bed::internal::vase { struct Point { @@ -19,103 +19,57 @@ struct Range { Point end; }; -struct Vase { - struct RegexGroup { - uint64_t start{0}; - uint64_t end{0}; - }; - - struct RegexMatch { - uint64_t start; - uint64_t end; - RegexGroup groups[9]{}; - }; - - struct ReplacePart { - enum struct PartType { - FullMatch, - CaptureGroup, - Constant - } type; - - std::variant value; - }; - - OriginalBuffer *original; - AppendBuffer *append; - Shard *root; - -#ifdef _WIN32 - bool posix_ending = false; - bool using_crlf = true; -#else - bool posix_ending = true; - bool using_crlf = false; -#endif - - std::filesystem::path path; - std::filesystem::path swapdir; - - Vase(std::filesystem::path path, std::filesystem::path swapdir); - Vase(std::string cmd, std::filesystem::path swapdir); - Vase(std::filesystem::path swapdir); - ~Vase(); - Vase(Vase &&other) noexcept; - Vase &operator=(Vase &&other) noexcept; - Vase(const Vase &) = delete; - Vase &operator=(const Vase &) = delete; - - uint64_t length(); - uint64_t lines(); - std::string to_string(); - std::string to_string(Range range); - - Iterator iterate(uint64_t line, Direction dir); - - void insert(Point *point, char key); - void insert(Point *point, std::string_view str); - void insert(Point *point, const char *data, uint64_t len); - void erase(Point *point, uint64_t amount, Direction dir); - void erase(Range range); - void replace(Range range, std::string_view str); - void replace(Range range, const char *data, uint64_t len); - - void move_clusters(Point *point, uint64_t amount, Direction dir); - void move_lines(Point *point, uint64_t amount, Direction dir); - void clamp(Point *point); - - void regex_search_replace( - std::string_view pattern, Range range, - std::string_view replace, std::string_view options - ); - - std::vector regex_search( - std::string_view pattern, Range range, std::string_view options - ); - - uint64_t find_next(std::string_view pattern, uint64_t start); - uint64_t find_prev(std::string_view pattern, uint64_t start); - - bool undo(); - bool redo(); - void snapshot(); - void prune_history(uint64_t n); - - bool save(); - bool save_swap(); - - uint64_t offset_of(Point point); - Point point_of(uint64_t offset); - -private: - std::vector history; - uint64_t history_top; - - void _insert(Point *point, const char *data, uint64_t len); - - std::vector parse_replace(std::string_view s); - std::vector _regex_search( - std::string_view pattern, Range range, std::string_view options - ); +struct RegexGroup { + uint64_t start{0}; + uint64_t end{0}; }; + +struct RegexMatch { + uint64_t start; + uint64_t end; + RegexGroup groups[9]{}; +}; + +struct ReplacePart { + enum struct PartType { + FullMatch, + CaptureGroup, + Constant + } type; + std::variant value; +}; + +uint64_t offset_of(Shard *root, uint64_t line); +uint64_t offset_of(Shard *root, Point point); +Point point_of(Shard *root, uint64_t offset); + +std::string to_string(Shard *root); +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 *copy(Shard *root, uint64_t start, uint64_t end); + +uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start); +uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start); + +Shard *substitute( + AppendStorage *ap, Shard *root, + std::string_view pattern, uint64_t start, uint64_t end, + std::string_view replace, std::string_view options +); + +// internal + +void _insert(AppendStorage *ap, Shard **root, Point *point, const char *data, uint64_t len); +Shard *insert(AppendStorage *ap, Shard *root, Point *point, char key); +Shard *insert(AppendStorage *ap, Shard *root, Point *point, const char *data, uint64_t len); + +Shard *erase(Shard *root, Range range); +Shard *replace(AppendStorage *ap, Shard *root, Range range, const char *data, uint64_t len); + +std::vector parse_replace(AppendStorage *ap, std::string_view s); +std::vector _regex_search( + Shard *root, std::string_view pattern, uint64_t start_offset, uint64_t end_offset, std::string_view options +); } // namespace bed::internal::vase diff --git a/src/bed/handle.cc b/src/bed/handle.cc deleted file mode 100644 index 426296d..0000000 --- a/src/bed/handle.cc +++ /dev/null @@ -1,101 +0,0 @@ -#include "bed.h" -#include "internal/address/address.h" -#include "internal/commands/commands.h" - -namespace bed { -void BEd::handle(std::string_view cmd_, bool eof) { - using namespace internal::commands; - std::string cmd(cmd_); - if (eof) { - eof_op.handle(*this, {}, ""); - return; - } - uint64_t i = 0; - auto skip_space = [&] { - while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t')) - ++i; - }; - skip_space(); - auto addresses = internal::address::Address::handle(*this, cmd, i); - if (i >= cmd.size()) { - if (!no_op.accept_zero) - for (auto l : addresses.span()) - if (l == 0) - throw ed_error("Invalid address given."); - switch (no_op.address_mode) { - case Command::AddressMode::None: - no_op.handle(*this, addresses.span(0), ""); - break; - case Command::AddressMode::Single: - no_op.handle(*this, addresses.span(1), ""); - break; - case Command::AddressMode::Range: - no_op.handle(*this, addresses.span(2), ""); - break; - } - return; - } - uint64_t len = commands.longest_match(cmd.substr(i)); - if (len == 0) - throw ed_error("Command not found."); - std::optional command_opt = commands.get(cmd.substr(i, len)); - i += len; - if (!command_opt) - throw ed_error("Command error."); - Command command = command_opt.value(); - std::string argument; - Suffix *suffix = nullptr; - switch (command.suffix) { - case Command::SuffixKind::None: - skip_space(); - if (i < cmd.size()) - throw ed_error("Command error."); - break; - case Command::SuffixKind::Suffix: - skip_space(); - if (i < cmd.size()) { - if (suffixes[cmd[i] - 'a'].has_value()) - suffix = &(suffixes[cmd[i++] - 'a'].value()); - skip_space(); - if (i < cmd.size()) - throw ed_error("Command error."); - } - break; - case Command::SuffixKind::Argument: - if (i < cmd.size()) { - if (cmd[i] == ' ' || cmd[i] == '\t') - skip_space(); - else - throw ed_error("Command Error."); - argument = cmd.substr(i); - } - break; - case Command::SuffixKind::Continuation: - argument = cmd.substr(i); - break; - } - if (!command.accept_zero) - for (auto l : addresses.span()) - if (l == 0) - throw ed_error("Invalid address given."); - switch (command.address_mode) { - case Command::AddressMode::None: - command.handle(*this, addresses.span(0), argument); - break; - case Command::AddressMode::Single: - command.handle(*this, addresses.span(1), argument); - break; - case Command::AddressMode::Range: - command.handle(*this, addresses.span(2), argument); - break; - } - if (suffix) - suffix->handle(*this, active->prev_range.values()); - return; -} - -void BEd::suffix_handle(char s) { - if (suffixes[s - 'a'].has_value()) - suffixes[s - 'a'].value().handle(*this, active->prev_range.values()); -} -} // namespace bed diff --git a/src/bed/run.cc b/src/bed/run.cc index baa96f4..cf22e33 100644 --- a/src/bed/run.cc +++ b/src/bed/run.cc @@ -1,10 +1,11 @@ #include "bed.h" +#include "internal/parser/parser.h" namespace bed { BEd::BEd(std::vector args, internal::io::IO &io) : theme(internal::theme::Theme::default_theme()), io(io) { - internal::commands::Command::register_posix(*this); - internal::commands::Suffix::register_suffixes(*this); + internal::functions::Function::register_posix(*this); + internal::functions::Suffix::register_suffixes(*this); std::string prompt_ = ""; std::string file = ""; bool suppress = false; @@ -27,10 +28,15 @@ BEd::BEd(std::vector args, internal::io::IO &io) else prompt_mode = false; suppress_mode = suppress; - active = new internal::buffer::Buffer(); - buffers.emplace("0", active); - if (file != "") - handle("E " + file, false); + try { + if (file != "") + handle(":default:E " + file, false); + } catch (ed_error &e) { + std::cout << "?" << std::endl; + if (help_mode) + std::cout << e.what() << std::endl; + last_help = e.what(); + } } BEd::~BEd() { @@ -44,8 +50,85 @@ void BEd::run() { try { handle(cmd, eof); } catch (ed_error &e) { - std::cout << e.what() << std::endl; + std::cout << "?" << std::endl; + if (help_mode) + std::cout << e.what() << std::endl; + last_help = e.what(); } } } + +void BEd::handle(std::string_view cmd, bool eof) { + if (eof) { + eof_op.handle(*this, "", nullptr, std::monostate(), nullptr); + return; + } + internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this); + 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 { + auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this); + a = internal::parser::AddressPromise::get_line(*this, vec); + 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 { + auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this); + a = internal::parser::AddressPromise::get_line(*this, vec); + 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 { + auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this); + a = internal::parser::AddressPromise::get_range(*this, vec); + address = *a; + } + } break; + } + if (std::holds_alternative(c.argument)) { + auto a = internal::parser::AddressPromise::get_line(*this, c.argument_addresses); + if (a.has_value()) + c.argument = *a; + else + c.argument = current(); + } else if (std::holds_alternative(c.argument)) { + auto a = internal::parser::AddressPromise::get_range(*this, c.argument_addresses); + if (a.has_value()) + c.argument = *a; + else + c.argument = internal::buffer::Range(current(), current()); + } + c.function->handle(*this, address, nullptr, c.argument, nullptr); + if (c.suffix) + c.suffix->handle(*this); +} + +internal::buffer::Buffer &BEd::buffer(const std::string &name) { + auto it = buffers.find(name); + if (it != buffers.end()) + return *it->second; + auto *buf = new internal::buffer::Buffer(name); + buffers.emplace(name, buf); + return *buf; +} + +internal::buffer::Line &BEd::current() { + return marks.get(250); +} + +void BEd::mark(char m, internal::buffer::Line line) { + marks.get(m) = line; +} } // namespace bed diff --git a/src/internal/address/address.cc b/src/internal/address/address.cc deleted file mode 100644 index aeef700..0000000 --- a/src/internal/address/address.cc +++ /dev/null @@ -1,146 +0,0 @@ -#include "internal/address/address.h" - -namespace bed::internal::address { -Address::Address(std::string &cmd, uint64_t &i) { - base = None{}; - auto skip_space = [&] { - while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t')) - ++i; - }; - skip_space(); - if (i >= cmd.size()) - return; - switch (cmd[i]) { - case '.': - base = Current(); - i++; - break; - case '$': - base = Last(); - i++; - break; - case ']': - base = Block(Direction::Forward); - i++; - break; - case '[': - base = Block(Direction::Backward); - i++; - break; - case '\'': { - i++; - if (i < cmd.size() - && (('a' <= cmd[i] && cmd[i] <= 'z') || ('A' <= cmd[i] && cmd[i] <= 'Z'))) - i++; - else - throw address_error("Invalid mark."); - base = Mark(cmd[i - 1]); - } break; - case '/': { - i++; - uint64_t start = i; - while (true) { - if (i >= cmd.size()) - break; - if (cmd[i] == '/') - break; - else if (cmd[i] == '\\') - i += 2; - else if (cmd[i] == '[' && i + 1 < cmd.size() && cmd[i + 1] == '[') - while (i < cmd.size() && !(cmd[i - 1] == ']' && cmd[i] == ']')) - i++; - else if (cmd[i] == '[') - while (i < cmd.size() && cmd[i] != ']') - i++; - else - i++; - } - base = Regex(Direction::Forward, cmd.substr(start, i - start)); - if (i < cmd.size()) - i++; - } break; - case '?': { - i++; - uint64_t start = i; - while (true) { - if (i >= cmd.size()) - break; - if (cmd[i] == '?') - break; - else if (cmd[i] == '\\') - i += 2; - else if (cmd[i] == '[' && i + 1 < cmd.size() && cmd[i + 1] == '[') - while (i < cmd.size() && !(cmd[i - 1] == ']' && cmd[i] == ']')) - i++; - else if (cmd[i] == '[') - while (i < cmd.size() && cmd[i] != ']') - i++; - else - i++; - } - base = Regex(Direction::Backward, cmd.substr(start, i - start)); - if (i < cmd.size()) - i++; - } break; - case '+': { - base = Current(); - i++; - skip_space(); - uint64_t start = i; - int64_t num = 0; - while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') { - num = num * 10 + (cmd[i] - '0'); - i++; - } - if (start == i) - num = 1; - offset += num; - } break; - case '-': { - base = Current(); - i++; - skip_space(); - uint64_t start = i; - int64_t num = 0; - while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') { - num = num * 10 + (cmd[i] - '0'); - i++; - } - if (start == i) - num = 1; - offset -= num; - } break; - default: { - if ('0' <= cmd[i] && cmd[i] <= '9') { - uint64_t num = 0; - while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') { - num = num * 10 + (cmd[i] - '0'); - i++; - } - base = Number(num); - } else { - return; - } - break; - } - } - skip_space(); - while (i < cmd.size() && (cmd[i] == '+' || cmd[i] == '-' || ('0' <= cmd[i] && cmd[i] <= '9'))) { - bool positive = cmd[i] != '-'; - if (cmd[i] == '+' || cmd[i] == '-') { - i++; - skip_space(); - } - uint64_t start = i; - int64_t num = 0; - while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') { - num = num * 10 + (cmd[i] - '0'); - i++; - } - if (start == i) - num = 1; - offset += positive ? num : -num; - skip_space(); - } -} -} // namespace bed::internal::address diff --git a/src/internal/address/handle.cc b/src/internal/address/handle.cc deleted file mode 100644 index a7b28f1..0000000 --- a/src/internal/address/handle.cc +++ /dev/null @@ -1,58 +0,0 @@ -#include "bed.h" -#include "internal/address/address.h" - -namespace bed::internal::address { -Address::Result Address::handle(BEd &ctx, std::string &cmd, uint64_t &i) { - bool prev_given = false; - Address prev; - Address curr; - while (i < cmd.size()) { - if (cmd[i] == '%') { - prev_given = true; - prev.base = Number(ctx.active->prev_range.start); - prev.offset = 0; - curr.base = Number(ctx.active->prev_range.end); - curr.offset = 0; - i++; - } else { - curr = Address(cmd, i); - } - if (i < cmd.size() && (cmd[i] == ',' || cmd[i] == ';')) { - if (std::holds_alternative(curr.base)) { - if (cmd[i] == ',') - curr.base = Number(1); - else - curr.base = Current(); - curr.offset = 0; - prev_given = false; - } else { - if (cmd[i] == ';') { - uint64_t resolved = curr.resolve(ctx); - ctx.active->jump(resolved); - curr.base = Number(resolved); - curr.offset = 0; - } - prev_given = true; - } - i++; - prev = std::move(curr); - } else { - if (std::holds_alternative(curr.base)) { - if (std::holds_alternative(prev.base)) - return {{}, 0}; - if (prev_given) { - curr = prev; - } else { - curr.base = Last(); - curr.offset = 0; - } - return {{prev.resolve(ctx), curr.resolve(ctx)}, 2}; - } - if (prev_given) - return {{prev.resolve(ctx), curr.resolve(ctx)}, 2}; - return {{curr.resolve(ctx)}, 1}; - } - } - return {}; -} -}; // namespace bed::internal::address diff --git a/src/internal/address/resolve.cc b/src/internal/address/resolve.cc deleted file mode 100644 index 9b75d80..0000000 --- a/src/internal/address/resolve.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "bed.h" -#include "internal/address/address.h" - -namespace bed::internal::address { -uint64_t Address::resolve(BEd &ctx) { - uint64_t result = std::visit( - [&](auto const &addr) -> uint64_t { - uint64_t line = 0; - using T = std::decay_t; - if constexpr (std::is_same_v) { - throw address_error("empty address"); - } else if constexpr (std::is_same_v) { - throw address_error("no address"); - } else if constexpr (std::is_same_v) { - line = ctx.active->line; - } else if constexpr (std::is_same_v) { - line = ctx.active->vase.lines(); - } else if constexpr (std::is_same_v) { - line = addr.i; - } else if constexpr (std::is_same_v) { - line = ctx.active->marks.get(addr.m); - if (line == UINT64_MAX) - throw address_error("Mark not set."); - } else if constexpr (std::is_same_v) { - std::string_view re = addr.re; - if (re.size() == 0) - re = ctx.last_regex; - if (re.size() == 0) - throw address_error("No regex given."); - if (addr.dir == Direction::Forward) - line = ctx.active->vase.find_next(re, ctx.active->line - 1) + 1; - else - line = ctx.active->vase.find_prev(re, ctx.active->line - 1) + 1; - ctx.last_regex = re; - } else if constexpr (std::is_same_v) { - uint64_t current_line = ctx.active->line; - if (ctx.active->vase.lines() > 0 && current_line == 0) - current_line = 1; - if (addr.dir == Direction::Forward) { - if (ctx.active->parser) { - uint64_t closing = ctx.active->parser->next_closing(current_line - 1); - if (closing == UINT64_MAX) - line = ctx.active->vase.lines(); - else - line = closing + 1; - } else { - line = current_line + 10; - if (line > ctx.active->vase.lines()) - line = ctx.active->vase.lines(); - } - } else { - if (ctx.active->parser) { - line = ctx.active->parser->prev_opening(current_line - 1) + 1; - } else { - if (current_line > 10) - line = current_line - 10; - else - line = 0; - } - } - } else { - throw ed_error("Unhandled address given."); - } - if (offset < 0 && line < (uint64_t)-offset) - throw address_error("Can't have negative addresses"); - line += offset; - if (line > ctx.active->vase.lines()) - throw address_error("Line number too high."); - return line; - }, - base - ); - - return result; -} -}; // namespace bed::internal::address diff --git a/src/internal/buffer/buffer.cc b/src/internal/buffer/buffer.cc index f7861a7..853bdc0 100644 --- a/src/internal/buffer/buffer.cc +++ b/src/internal/buffer/buffer.cc @@ -2,108 +2,70 @@ #include "bed.h" namespace bed::internal::buffer { -Buffer::Buffer() : vase("/tmp") { - parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby()); - line = vase.lines(); - modified = false; +Buffer::Buffer(std::string name) + : state(Unmodified), root(nullptr), name(name) { + parser.emplace(root, lines(), syntax::ruby::lang_ruby()); // just for debug. } -Buffer::Buffer(std::string command) : vase(command, "/tmp") { - parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby()); - line = vase.lines(); - if (!line) - return; - prev_range.start = 1; - prev_range.end = line; - modified = false; +Buffer::~Buffer() { + vase::Shard::release(root); } -Buffer::Buffer(std::filesystem::path path) : vase(path, "/tmp") { - parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby()); - line = vase.lines(); - if (!line) - return; - prev_range.start = 1; - prev_range.end = line; - save_path = path; - modified = false; +uint64_t Buffer::lines() { + if (root) + return root->lines + 1; + return 0; } -void Buffer::load(std::string command) { - vase::Vase new_vase = vase::Vase(command, "/tmp"); - vase = std::move(new_vase); - parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby()); - line = vase.lines(); - if (!line) { - prev_range.start = 0; - prev_range.end = 0; - } else { - prev_range.start = 1; - prev_range.end = line; - } - modified = false; -} - -void Buffer::load(std::filesystem::path path) { - vase::Vase new_vase = vase::Vase(path, "/tmp"); - vase = std::move(new_vase); - parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby()); - line = vase.lines(); - if (!line) { - prev_range.start = 0; - prev_range.end = 0; - } else { - prev_range.start = 1; - prev_range.end = line; - } - save_path = path; - modified = false; -} - -void Buffer::jump(uint64_t n_line) { - if (n_line > vase.lines()) - throw ed_error("Line number too high."); - line = n_line; -} - -void Buffer::append(std::string text, uint64_t line) { - using namespace bed::internal::vase; - Point p = {line, 0}; - if (!vase.lines()) { - text.pop_back(); - } else if (line == vase.lines()) { - p.row--; - p.col = UINT64_MAX; - text = "\n" + text; - text.pop_back(); - } - prev_range.start = p.row + 1; - vase.insert(&p, text); - prev_range.end = p.row + 1; - marks.insert(prev_range.start, prev_range.end); +void Buffer::append(BEd &ctx, vase::Shard *text, uint64_t line) { + ctx.prev.buffername = name; + ctx.prev.start = line; + ctx.prev.end = line + text->lines; + root = vase::insert(&ctx.append, root, text, line); + ctx.marks.insert(name, ctx.prev.start, ctx.prev.end); if (parser) - parser->insert(vase, prev_range.start, prev_range.end); - modified = true; + parser->insert(root, ctx.prev.start, ctx.prev.end); + state = Modified; } -void Buffer::remove(uint64_t start_line, uint64_t end_line) { - vase.erase({{start_line - 1, 0}, {end_line, 0}}); - prev_range.start = start_line; - prev_range.end = start_line; - marks.erase(start_line, end_line - start_line + 1); +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.marks.erase(name, start_line, end_line - start_line + 1); if (parser) - parser->erase(vase, start_line, end_line - start_line + 1); - modified = true; + parser->erase(root, start_line, end_line - start_line + 1); + state = Modified; } -void Buffer::join(uint64_t start_line, uint64_t end_line) { - vase.regex_search_replace(R"(\n)", {{start_line - 1, 0}, {end_line, 0}}, "", "g"); - prev_range.start = start_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"); + ctx.prev.buffername = name; + ctx.prev.start = start_line; + ctx.prev.end = start_line; + ctx.marks.collapse(name, start_line, end_line - start_line); + if (parser) + parser->erase(root, start_line, end_line - start_line); + state = Modified; +} + +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. + root = vase::substitute(&ctx.append, root, regex, start_line, end_line, replacement, options); + /*prev_range.start = start_line; prev_range.end = start_line; marks.collapse(start_line, end_line - start_line); if (parser) - parser->erase(vase, start_line, end_line - start_line); - modified = true; + parser->erase(vase, start_line, end_line - start_line);*/ + state = Modified; +} + +vase::Shard *Buffer::copy(uint64_t start_line, uint64_t end_line) { + return vase::copy(root, start_line, end_line); } inline void apply(std::ostream &out, const Highlight &hl) { @@ -139,10 +101,11 @@ inline void reset(std::ostream &out) { } void Buffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) { - prev_range.start = start_line; - prev_range.end = end_line; + ctx.prev.buffername = name; + ctx.prev.start = start_line; + ctx.prev.end = end_line; if (parser) { - std::optional it_o = parser->get_hl(vase, start_line - 1); + std::optional it_o = parser->get_hl(root, start_line - 1); auto &it = *it_o; while (start_line <= end_line) { it.next(); @@ -174,24 +137,25 @@ void Buffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) { ++start_line; } } else { - vase::Iterator it = vase.iterate(start_line - 1, Direction::Forward); + vase::Iterator it(root, start_line - 1, Direction::Forward); while (it.next() && start_line++ <= end_line) std::cout << it.line << std::endl; } } void Buffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) { - prev_range.start = start_line; - prev_range.end = end_line; + ctx.prev.buffername = name; + ctx.prev.start = start_line; + ctx.prev.end = end_line; uint8_t width = 1; for (uint64_t n = end_line; n >= 10; n /= 10) ++width; if (parser) { - std::optional it_o = parser->get_hl(vase, start_line - 1); + std::optional it_o = parser->get_hl(root, start_line - 1); auto &it = *it_o; while (start_line <= end_line) { it.next(); - std::cout << std::setw(width) << it.at << "\t"; + std::cout << std::setw(width) << start_line << "\t"; const std::string &line = it.it->line; const auto &tokens = it.tokens; uint32_t cursor = 0; @@ -220,7 +184,7 @@ void Buffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) { ++start_line; } } else { - vase::Iterator it = vase.iterate(start_line - 1, Direction::Forward); + 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; } diff --git a/src/internal/commands/commands.cc b/src/internal/commands/commands.cc deleted file mode 100644 index fbfc6a1..0000000 --- a/src/internal/commands/commands.cc +++ /dev/null @@ -1,226 +0,0 @@ -#include "internal/commands/commands.h" -#include "bed.h" -#include "internal/commands/suffixes.h" - -namespace bed::internal::commands { -void Suffix::register_suffixes(BEd &ctx) { - ctx.suffixes['p' - 'a'] = Suffix{ - .desc = "Prints current line.", - .handle = [](BEd &ctx, std::span addresses) { - ctx.active->print(ctx, addresses[0], addresses[1]); - } - }; - ctx.suffixes['n' - 'a'] = Suffix{ - .desc = "Prints current line with line number.", - .handle = [](BEd &ctx, std::span addresses) { - ctx.active->number_print(ctx, addresses[0], addresses[1]); - } - }; -} - -void Command::register_posix(BEd &ctx) { - ctx.no_op = Command{ - .address_mode = Command::AddressMode::Single, - .suffix = Command::SuffixKind::None, - .desc = "Prints a line and jumps to it (default: .+1)", - .accept_zero = false, - .handle = [](BEd &ctx, std::span addresses, std::string_view) { - uint64_t line; - if (addresses.size()) - line = addresses[0]; - else - line = ctx.active->line + 1; - if (line == 0) - throw ed_error("Line 0 is invalid."); - ctx.active->jump(line); - ctx.active->print(ctx, line, line); - } - }; - ctx.eof_op = Command{ - .address_mode = Command::AddressMode::None, - .suffix = Command::SuffixKind::None, - .desc = "Try quitting.", - .accept_zero = false, - .handle = [](BEd &ctx, std::span, std::string_view) { - for (auto &[name, buffer] : ctx.buffers) - if (buffer->modified) - throw ed_error("Buffer " + name + " modified."); - throw fatal_error("Quitting", 0); - } - }; - ctx.commands.insert( - "a", - Command{ - .address_mode = Command::AddressMode::Single, - .suffix = Command::SuffixKind::Suffix, - .desc = "Append lines at address (default: .)", - .accept_zero = true, - .handle = [](BEd &, std::span, std::string_view) { - // TODO: start a text editing session, then append its stuff. - } - } - ); - ctx.commands.insert( - "j", - Command{ - .address_mode = Command::AddressMode::Range, - .suffix = Command::SuffixKind::Suffix, - .desc = "Join a set of lines (default: .,.+1)", - .accept_zero = true, - .handle = [](BEd &ctx, std::span addresses, std::string_view) { - uint64_t start_line = ctx.active->line; - uint64_t end_line = ctx.active->line + 1; - if (addresses.size() == 1) - return; - else if (addresses.size() == 2) - start_line = addresses[0], end_line = addresses[1]; - ctx.active->join(start_line, end_line); - ctx.active->jump(start_line); - } - } - ); - ctx.commands.insert( - "q", - Command{ - .address_mode = Command::AddressMode::None, - .suffix = Command::SuffixKind::None, - .desc = "Try quitting.", - .accept_zero = false, - .handle = [](BEd &ctx, std::span, std::string_view) { - for (auto &[name, buffer] : ctx.buffers) - if (buffer->modified) - throw ed_error("Buffer " + name + " modified."); - throw fatal_error("Quitting", 0); - } - } - ); - ctx.commands.insert( - "Q", - Command{ - .address_mode = Command::AddressMode::None, - .suffix = Command::SuffixKind::None, - .desc = "Force quitting.", - .accept_zero = false, - .handle = [](BEd &, std::span, std::string_view) { - throw fatal_error("Force Quitting", 0); - } - } - ); - ctx.commands.insert( - "p", - Command{ - .address_mode = Command::AddressMode::Range, - .suffix = Command::SuffixKind::Suffix, - .desc = "Print range (default .,.)", - .accept_zero = false, - .handle = [](BEd &ctx, std::span addresses, std::string_view) { - uint64_t start_line = ctx.active->line; - uint64_t end_line = ctx.active->line; - if (addresses.size() == 1) - start_line = addresses[0], end_line = addresses[0]; - else if (addresses.size() == 2) - start_line = addresses[0], end_line = addresses[1]; - ctx.active->print(ctx, start_line, end_line); - ctx.active->jump(end_line); - } - } - ); - ctx.commands.insert( - "n", - Command{ - .address_mode = Command::AddressMode::Range, - .suffix = Command::SuffixKind::Suffix, - .desc = "Print range with line numbers (default .,.)", - .accept_zero = false, - .handle = [](BEd &ctx, std::span addresses, std::string_view) { - uint64_t start_line = ctx.active->line; - uint64_t end_line = ctx.active->line; - if (addresses.size() == 1) - start_line = addresses[0], end_line = addresses[0]; - else if (addresses.size() == 2) - start_line = addresses[0], end_line = addresses[1]; - ctx.active->number_print(ctx, start_line, end_line); - ctx.active->jump(end_line); - } - } - ); - ctx.commands.insert( - "=", - Command{ - .address_mode = Command::AddressMode::Range, - .suffix = Command::SuffixKind::Suffix, - .desc = "Print line number(s)", - .accept_zero = true, - .handle = [](BEd &ctx, std::span addresses, std::string_view) { - if (!addresses.size()) - std::cout << ctx.active->vase.lines() << std::endl; - else if (addresses.size() == 1) - std::cout << addresses[0] << std::endl; - else - std::cout << addresses[0] << "," << addresses[1] << std::endl; - } - } - ); - ctx.commands.insert( - "k", - Command{ - .address_mode = Command::AddressMode::Single, - .suffix = Command::SuffixKind::Continuation, - .desc = "Mark a line.", - .accept_zero = true, - .handle = [](BEd &ctx, std::span addresses, std::string_view args) { - if (args.size() < 1 || args.size() > 2) - throw ed_error("Malformed mark command"); - if (addresses.size()) - ctx.active->marks.set(args[0], addresses[0]); - else - ctx.active->marks.set(args[0], ctx.active->line); - if (args.size() > 1) - ctx.suffix_handle(args[1]); - } - } - ); - ctx.commands.insert( - "debug", - Command{ - .address_mode = Command::AddressMode::None, - .suffix = Command::SuffixKind::None, - .desc = "", - .accept_zero = false, - .handle = [](BEd &ctx, std::span, std::string_view) { - syntax::dump_events(ctx.active->parser->root); - } - } - ); - ctx.commands.insert( - "E", - Command{ - .address_mode = Command::AddressMode::None, - .suffix = Command::SuffixKind::Argument, - .desc = "Load a file into the current buffer.", - .accept_zero = true, - .handle = [](BEd &ctx, std::span, std::string_view file) { - bool empty = false; - if (file.empty()) - empty = true; - uint64_t i = 0; - while (i < file.length() && (file[i] == ' ' || file[i] == '\t')) - i++; - if (i >= file.size()) - empty = true; - if (empty) { - if (ctx.active->save_path == "") - throw ed_error("Need filename!"); - ctx.active->load(ctx.active->save_path); - } else if (file[i] == '!') { - file = file.substr(1); - ctx.active->load(file); - } else { - ctx.active->load(std::filesystem::path(file)); - } - std::cout << ctx.active->vase.length() << std::endl; - } - } - ); -} -} // namespace bed::internal::commands diff --git a/src/internal/functions/functions.cc b/src/internal/functions/functions.cc new file mode 100644 index 0000000..df22d8d --- /dev/null +++ b/src/internal/functions/functions.cc @@ -0,0 +1,295 @@ +#include "internal/functions/functions.h" +#include "bed.h" +#include "internal/functions/suffixes.h" + +namespace bed::internal::functions { +void Suffix::register_suffixes(BEd &ctx) { + ctx.suffixes['p' - 'a'] = Suffix{ + .desc = "Prints current line.", + .handle = [](BEd &ctx) { + auto &addr = ctx.current(); + ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number); + } + }; + ctx.suffixes['n' - 'a'] = Suffix{ + .desc = "Prints current line with line number.", + .handle = [](BEd &ctx) { + auto &addr = ctx.current(); + ctx.buffer(addr.buffername).number_print(ctx, addr.number, addr.number); + } + }; +} + +void Function::register_posix(BEd &ctx) { + ctx.no_op = Function{ + .address_kind = Function::AddressKind::Line, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Prints a line and jumps to it.", + .default_address = ".+1", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector *) { + auto addr = std::get(addr_); + ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number); + ctx.current() = addr; + } + }; + ctx.eof_op = Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::None, + .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( + "j", + Function{ + .address_kind = Function::AddressKind::Range, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Join a set of lines.", + .default_address = ".,.+1", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector *) { + auto addr = std::get(addr_); + ctx.buffer(addr.buffername).join(ctx, addr.start, addr.end); + ctx.current() = {addr.buffername, addr.start}; + } + } + ); + ctx.functions.insert( + "q", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Try quitting.", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector *) { + 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( + "Q", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Force quit.", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &, const buffer::Address &, vase::Shard *, const Argument &, std::vector *) { + throw fatal_error("Force Quitting", 0); + } + } + ); + ctx.functions.insert( + "p", + Function{ + .address_kind = Function::AddressKind::Range, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Print range", + .default_address = ".,.", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector *) { + auto addr = std::get(addr_); + ctx.buffer(addr.buffername).print(ctx, addr.start, addr.end); + ctx.current() = {addr.buffername, addr.end}; + }, + } + ); + ctx.functions.insert( + "n", + Function{ + .address_kind = Function::AddressKind::Range, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Print range with line numbers", + .default_address = ".,.", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector *) { + auto addr = std::get(addr_); + ctx.buffer(addr.buffername).number_print(ctx, addr.start, addr.end); + ctx.current() = {addr.buffername, addr.end}; + }, + } + ); + ctx.functions.insert( + "=", + Function{ + .address_kind = Function::AddressKind::Range, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Print line numbers", + .default_address = "$", + .accept_zero = true, + .pre_text_mode = nullptr, + .handle = [](BEd &, 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.functions.insert( + "k", + Function{ + .address_kind = Function::AddressKind::Line, + .argument_kind = Function::ArgumentKind::Mark, + .input_mode = Function::InputMode::None, + .desc = "Mark a line.", + .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_); + ctx.mark(std::get(arg), addr); + } + } + ); + ctx.functions.insert( + "e", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::File, + .input_mode = Function::InputMode::None, + .desc = "Try load a file into the current buffer.", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector *) { + auto &addr = std::get(addr_); + auto &buf = ctx.buffer(addr); + if (buf.state == buffer::Buffer::Modified) { + buf.state = buffer::Buffer::Warned; + throw ed_error("Buffer modified."); + } + vase::Shard *s = nullptr; + if (std::holds_alternative(arg)) { + s = vase::Shard::from_file(std::get(arg), true); + buf.save_path = std::get(arg); + } else if (std::holds_alternative(arg)) { + s = vase::Shard::from_command(std::get(arg).cmd.c_str(), true); + } else if (std::holds_alternative(arg)) { + if (buf.save_path != "") + s = vase::Shard::from_file(buf.save_path, true); + 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); + ctx.current() = {addr, buf.lines()}; + } + } + ); + ctx.functions.insert( + "E", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::File, + .input_mode = Function::InputMode::None, + .desc = "Load a file into the current buffer.", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector *) { + auto &addr = std::get(addr_); + auto &buf = ctx.buffer(addr); + vase::Shard *s = nullptr; + if (std::holds_alternative(arg)) { + s = vase::Shard::from_file(std::get(arg), true); + buf.save_path = std::get(arg); + } else if (std::holds_alternative(arg)) { + s = vase::Shard::from_command(std::get(arg).cmd.c_str(), true); + } else if (std::holds_alternative(arg)) { + if (buf.save_path != "") + s = vase::Shard::from_file(buf.save_path, true); + 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); + ctx.current() = {addr, buf.lines()}; + } + } + ); + ctx.functions.insert( + "H", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Toggle help mode.", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector *) { + ctx.help_mode = !ctx.help_mode; + if (ctx.help_mode) + std::cout << ctx.last_help << std::endl; + } + } + ); + ctx.functions.insert( + "h", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Print last help message", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector *) { + std::cout << ctx.last_help << std::endl; + } + } + ); +} +} // namespace bed::internal::functions diff --git a/src/internal/io/_command.cc b/src/internal/io/_command.cc new file mode 100644 index 0000000..18d37c2 --- /dev/null +++ b/src/internal/io/_command.cc @@ -0,0 +1,10 @@ +#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 23ffeb6..b6e02eb 100644 --- a/src/internal/io/command.cc +++ b/src/internal/io/command.cc @@ -1,182 +1,156 @@ #include "bed.h" -#include "internal/io/io.h" namespace bed::internal::io { -std::pair IO::get_command(BEd &ctx) { - uint16_t start; - uint16_t height; - { - auto [row, col] = cursor_position(); - auto [rows, cols] = terminal_size(); - if (row > rows) - throw fatal_error("Invalid cursor position.", 1); - start = row; - height = rows - row + 1; +/*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; } - // uint16_t width = cols; - // TODO: support multiline wrapped commands. - // also handle ctrl+l to try and clear screen. +} - std::string cmd; - size_t cursor = 0; +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; +} - std::string prompt; - if (ctx.prompt_mode) - prompt = ctx.prompt(ctx); +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; +} - auto redraw = [&] { - move_cursor(start, 1); - write_all(STDOUT_FILENO, "\x1b[2K", 4); - write_all(STDOUT_FILENO, prompt.c_str(), prompt.size()); - write_all(STDOUT_FILENO, cmd.c_str(), cmd.size()); - move_cursor(start, cursor + prompt.size() + 1); - }; +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(); - bool eof = false; - - while (true) { - KeyEvent ev = read_key(); - if (ev.type == KeyEvent::KeyType::RESIZE) { - { - auto [row, col] = cursor_position(); - auto [rows, cols] = terminal_size(); - if (row > rows) - throw fatal_error("Invalid cursor position.", 1); - start = row; - height = rows - row + 1; + // 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; } - redraw(); - continue; - } - if ( - (ev.type == KeyEvent::KeyType::CHAR - && ev.modifier == KeyEvent::Modifier::CTRL - && ev.text[0] == 'd') - || ev.type == KeyEvent::KeyType::NONE - ) { - eof = true; break; } - if (ev.type == KeyEvent::KeyType::CHAR - && ev.modifier == KeyEvent::Modifier::CTRL - && ev.text[0] == 'w') { - // TODO: delete previous word here. - if (cursor == 0) - continue; - cmd.erase(cursor -= 1, 1); - redraw(); - continue; - } - if (ev.type == KeyEvent::KeyType::CHAR - && ev.modifier == KeyEvent::Modifier::NONE) { - if (ev.text == "\r" || ev.text == "\n") - break; - if (ev.text.size() == 1 - && (ev.text[0] == '\x7f' || ev.text[0] == '\x08')) { - if (cursor == 0) - continue; - cmd.erase(cursor -= 1, 1); - redraw(); - continue; - } - cmd.insert(cursor, ev.text); - cursor += ev.text.size(); - redraw(); - continue; - } - if (ev.type == KeyEvent::KeyType::SPECIAL) { - if (ev.special_key == KeyEvent::SpecialKey::LEFT) { - if (cursor == 0) - continue; - cursor -= 1; - } else if (ev.special_key == KeyEvent::SpecialKey::RIGHT) { - if (cursor >= cmd.size()) - continue; - cursor += 1; - } else if (ev.special_key == KeyEvent::SpecialKey::DELETE) { - if (cursor >= cmd.size()) - continue; - cmd.erase(cursor, 1); - } - redraw(); - continue; - } - if (ev.type == KeyEvent::KeyType::PASTE) { - std::string text = ev.text; - if (!text.empty() && text.front() == '\n') - text.erase(text.begin()); - if (!text.empty() && text.back() == '\n') - text.pop_back(); - bool multiline = text.find('\n') != std::string::npos; - if (multiline) { - size_t line_count = 1 + std::count(text.begin(), text.end(), '\n'); - if (height == 1) { - write_all(STDOUT_FILENO, "\n", 1); - --start; - ++height; - } - bool go_ahead; - std::string msg = - "* paste " - + std::to_string(line_count) - + " lines into a single-line command? (y/n) "; - move_cursor(start + 1, 1); - write_all(STDOUT_FILENO, "\x1b[2K", 4); - write_all(STDOUT_FILENO, msg.c_str(), msg.size()); - while (true) { - KeyEvent ev = read_key(); - if (ev.type == KeyEvent::KeyType::CHAR && ev.text.size() == 1) { - if (ev.text[0] == 'y' || ev.text[0] == 'Y') { - go_ahead = true; - break; - } - if (ev.text[0] == 'n' || ev.text[0] == 'N') { - go_ahead = false; - break; - } - } - if (ev.type == KeyEvent::KeyType::NONE) { - go_ahead = false; - break; - } - if (ev.type == KeyEvent::KeyType::RESIZE) { - { - auto [row, col] = cursor_position(); - auto [rows, cols] = terminal_size(); - if (row - 1 > rows) - throw fatal_error("Invalid cursor position.", 1); - start = row - 1; - height = rows - row; - } - redraw(); - move_cursor(start + 1, 1); - write_all(STDOUT_FILENO, "\x1b[2K", 4); - write_all(STDOUT_FILENO, msg.c_str(), msg.size()); - continue; - } - } - move_cursor(start + 1, 1); - write_all(STDOUT_FILENO, "\x1b[2K", 4); - if (!go_ahead) { - redraw(); - continue; - } - std::replace(text.begin(), text.end(), '\n', ' '); - } - cmd.insert(cursor, text); - cursor += text.size(); - redraw(); - continue; - } + redraw(); } for (uint16_t i = 1; i < height; ++i) { - move_cursor(start + i, 1); - write_all(STDOUT_FILENO, "\x1b[2K", 4); + io.move_cursor(start + i, 1); + io.write("\x1b[2K", 4); } - move_cursor(start, 1); - write_all(STDOUT_FILENO, "\n", 1); - return {cmd, eof}; + 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::io diff --git a/src/internal/io/input.cc b/src/internal/io/input.cc index 2ba3e9a..e83e3a4 100644 --- a/src/internal/io/input.cc +++ b/src/internal/io/input.cc @@ -216,7 +216,7 @@ KeyEvent IO::read_key() { KeyEvent ev; switch (res) { case KeyEvent::ReadResult::EOF_: - ev.type = KeyEvent::KeyType::NONE; + ev.type = KeyEvent::KeyType::EOF_; return ev; case KeyEvent::ReadResult::RESIZE: resized.store(false); @@ -233,7 +233,7 @@ KeyEvent IO::read_key() { ev.text = std::move(pasted); break; case KeyEvent::ReadResult::EOF_: - ev.type = KeyEvent::KeyType::NONE; + ev.type = KeyEvent::KeyType::EOF_; break; case KeyEvent::ReadResult::RESIZE: resized.store(false); @@ -244,7 +244,7 @@ KeyEvent IO::read_key() { } if (buf.size() >= 3 && buf[0] == '\x1b' && buf[1] == '[' && buf[2] == 'M') { ev = parse_mouse(buf); - if (ev.type == KeyEvent::KeyType::NONE) + if (ev.type == KeyEvent::KeyType::EOF_) continue; return ev; } diff --git a/src/internal/io/io.cc b/src/internal/io/io.cc index c10faa6..a7d7d6f 100644 --- a/src/internal/io/io.cc +++ b/src/internal/io/io.cc @@ -89,4 +89,12 @@ void IO::move_cursor(uint16_t row, uint16_t col) { int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col); write_all(STDOUT_FILENO, buf, n); } + +void IO::write(const char *buf, uint64_t n) { + write_all(STDOUT_FILENO, buf, n); +} + +void IO::write(std::string_view s) { + write_all(STDOUT_FILENO, s.data(), s.size()); +} } // namespace bed::internal::io diff --git a/src/internal/io/text_mode.cc b/src/internal/io/text_mode.cc index be89d42..8996b59 100644 --- a/src/internal/io/text_mode.cc +++ b/src/internal/io/text_mode.cc @@ -3,16 +3,18 @@ namespace bed::internal::io { std::pair IO::get_text(BEd &) { - auto [row, col] = cursor_position(); - auto [rows, cols] = terminal_size(); - if (row > rows) - throw fatal_error("Invalid cursor position.", 1); - uint16_t start = row; - uint16_t height = rows - row + 1; + uint16_t start; + uint16_t height; + { + auto [row, col] = cursor_position(); + auto [rows, cols] = terminal_size(); + if (row > rows) + throw fatal_error("Invalid cursor position.", 1); + start = row; + height = rows - row + 1; + } enable_mouse(); // uint16_t width = cols; - // TODO: support multiline wrapped commands. - // also handle ctrl+l to try and clear screen. disable_mouse(); for (uint16_t i = 1; i < height; ++i) { diff --git a/src/internal/parser/address_promise.cc b/src/internal/parser/address_promise.cc new file mode 100644 index 0000000..96927bb --- /dev/null +++ b/src/internal/parser/address_promise.cc @@ -0,0 +1,215 @@ +#include "bed.h" +#include "internal/parser/parser.h" +#include "internal/vase/vase.h" + +namespace bed::internal::parser { +buffer::Line AddressPromise::resolve(BEd &ctx) { + buffer::Line result = std::visit( + [&](auto const &addr) -> buffer::Line { + if (!bufname.has_value()) + 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"); + } else if constexpr (std::is_same_v) { + if (line.buffername == ctx.current().buffername) + line.number = ctx.current().number; + else + line.number = buf.lines(); + } else if constexpr (std::is_same_v) { + line.number = buf.lines(); + } else if constexpr (std::is_same_v) { + line.number = addr.i; + } else if constexpr (std::is_same_v) { + line = ctx.marks.get(addr.m); + if (line.number == UINT64_MAX) + throw ed_error("Mark not set."); + } else if constexpr (std::is_same_v) { + if (line.buffername == ctx.current().buffername) + line.number = ctx.current().number; + else + line.number = buf.lines(); + if (buf.lines() > 0 && line.number == 0) + line.number = 1; + if (line.number == 0) + throw ed_error("Can't search empty buffer."); + std::string_view re = addr.re; + if (re.size() == 0) + re = ctx.last_regex; + if (re.size() == 0) + throw ed_error("No regex given."); + if (addr.dir == Direction::Forward) + line.number = vase::find_next(buf.root, re, line.number); + else + line.number = vase::find_prev(buf.root, re, line.number); + ctx.last_regex = re; + } else if constexpr (std::is_same_v) { + if (line.buffername == ctx.current().buffername) + line.number = ctx.current().number; + else + line.number = buf.lines(); + if (buf.lines() > 0 && line.number == 0) + line.number = 1; + if (addr.dir == Direction::Forward) { + if (buf.parser.has_value()) { + uint64_t closing = buf.parser->next_closing(line.number - 1); + if (closing == UINT64_MAX) + line.number = buf.lines(); + else + line.number = closing + 1; + } else { + line.number += 10; + if (line.number > buf.lines()) + line.number = buf.lines(); + } + } else { + if (buf.parser.has_value()) { + line.number = buf.parser->prev_opening(line.number - 1) + 1; + } else { + if (line.number > 10) + line.number -= 10; + else + line.number = 0; + } + } + } else { + throw ed_error("Unhandled address given."); + } + if (offset < 0 && line.number < (uint64_t)-offset) + throw ed_error("Can't have negative addresses"); + line.number += offset; + if (line.number > ctx.buffer(line.buffername).lines()) + throw ed_error("Line number too high."); + return line; + }, + base + ); + return result; +} + +std::optional AddressPromise::get_line(BEd &ctx, std::vector &list) { + std::string bufname = ctx.current().buffername; + bool prev_given = false; + bool prev_set = false; + AddressPromise prev; + for (std::size_t idx = 0; idx < list.size(); idx++) { + AddressPromise &curr = list[idx]; + if (curr.bufname.has_value()) { + if (curr.bufname->empty()) { + bufname = ctx.current().buffername; + curr.bufname = bufname; + } else { + bufname = *curr.bufname; + } + } else { + curr.bufname = bufname; + } + bool is_final = idx + 1 == list.size(); + if (!is_final) { + if (std::holds_alternative(curr.base)) { + if (!curr.jumping) + curr.base = Number(1); + else + curr.base = Current(); + curr.offset = 0; + prev_given = false; + } else if (std::holds_alternative(curr.base)) { + curr.bufname = ctx.prev.buffername; + curr.base = Number(ctx.prev.end); + prev_given = true; + } else { + prev_given = true; + } + if (curr.jumping) { + buffer::Line resolved = curr.resolve(ctx); + ctx.current() = resolved; + curr.bufname = resolved.buffername; + curr.base = Number(resolved.number); + curr.offset = 0; + } + prev = curr; + prev_set = true; + } else { + if (std::holds_alternative(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; + } + } + return curr.resolve(ctx); + } + } + return std::nullopt; +} + +std::optional AddressPromise::get_range(BEd &ctx, std::vector &list) { + std::string bufname = ctx.current().buffername; + bool prev_given = false; + bool prev_set = false; + AddressPromise prev; + for (std::size_t idx = 0; idx < list.size(); idx++) { + AddressPromise &curr = list[idx]; + if (curr.bufname.has_value()) { + if (curr.bufname->empty()) { + bufname = ctx.current().buffername; + curr.bufname = bufname; + } else { + bufname = *curr.bufname; + } + } else { + curr.bufname = bufname; + } + bool is_final = idx + 1 == list.size(); + if (!is_final) { + if (std::holds_alternative(curr.base)) { + if (!curr.jumping) + curr.base = Number(1); + else + curr.base = Current(); + curr.offset = 0; + prev_given = false; + } else if (std::holds_alternative(curr.base)) { + curr.bufname = ctx.prev.buffername; + curr.base = Number(ctx.prev.end); + prev_given = true; + } else { + prev_given = true; + } + if (curr.jumping) { + buffer::Line resolved = curr.resolve(ctx); + ctx.current() = resolved; + curr.bufname = resolved.buffername; + curr.base = Number(resolved.number); + curr.offset = 0; + } + prev = curr; + prev_set = true; + } else { + if (std::holds_alternative(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; + } + return buffer::Range(prev.resolve(ctx), curr.resolve(ctx)); + } + if (prev_given) + return buffer::Range(prev.resolve(ctx), curr.resolve(ctx)); + buffer::Line only = curr.resolve(ctx); + return buffer::Range(only, only); + } + } + return std::nullopt; +} +} // namespace bed::internal::parser diff --git a/src/internal/parser/parser.cc b/src/internal/parser/parser.cc new file mode 100644 index 0000000..8284af3 --- /dev/null +++ b/src/internal/parser/parser.cc @@ -0,0 +1,514 @@ +#include "internal/parser/parser.h" +#include "bed.h" + +namespace bed::internal::parser { +char Parser::peek(uint16_t o) { + return i + o < cmd.size() ? cmd[i + o] : '\0'; +} + +std::string_view Parser::peek_str(uint16_t len) { + return cmd.substr(i, len); +} + +void Parser::advance(uint16_t c) { + i += c; +} + +void Parser::skip_ws() { + while (peek() == ' ' || peek() == '\t') + 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() == '@') { + advance(); + command->temp_address = true; + } else { + command->temp_address = false; + } + skip_ws(); + addresses(command->addresses); + operation(); + skip_ws(); + if (peek() != '\0') + throw ed_error("Malformed command"); +} + +Parser::Parser( + std::string_view cmd, BEd &bed, Command *command, + 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; + CompletionContext completion; + Parser p(cmd, bed, &c, &tokens, &completion); + p.parse(); + return c; +} + +std::vector Parser::get_addresses(std::string_view cmd, BEd &bed) { + std::vector result; + std::vector tokens; + CompletionContext completion; + Parser p(cmd, bed, nullptr, &tokens, &completion); + p.addresses(result); + p.skip_ws(); + if (p.peek() != '\0') + throw ed_error("Malformed address"); + return result; +} +} // namespace bed::internal::parser diff --git a/src/internal/syntax/parser.cc b/src/internal/syntax/parser.cc index ee989a2..0e09672 100644 --- a/src/internal/syntax/parser.cc +++ b/src/internal/syntax/parser.cc @@ -79,7 +79,7 @@ static ParseState *build_tree(std::vector &leaves, size_t begi return make_branch(left, right); } -Parser::Parser(vase::Vase &vase, uint64_t lines, Language lang) +Parser::Parser(vase::Shard *vase, uint64_t lines, Language lang) : root(nullptr), lang(lang) { reset(vase, lines, lang); } @@ -88,7 +88,7 @@ Parser::~Parser() { destroy_tree(root, lang); } -void Parser::reset(vase::Vase &vase, uint64_t lines, Language lang_) { +void Parser::reset(vase::Shard *vase, uint64_t lines, Language lang_) { destroy_tree(root, lang); root = nullptr; if (lines == 0) @@ -158,7 +158,7 @@ ParseState *Parser::join_tree(ParseState *a, ParseState *b) { return make_branch(a, b); } -void Parser::erase(vase::Vase &vase, uint64_t start, uint64_t count) { +void Parser::erase(vase::Shard *vase, uint64_t start, uint64_t count) { if (count == 0 || !root) return; auto [a, remaining] = split_tree(root, start); @@ -168,7 +168,7 @@ void Parser::erase(vase::Vase &vase, uint64_t start, uint64_t count) { modify(vase, start, 1); } -void Parser::insert(vase::Vase &vase, uint64_t start, uint64_t count) { +void Parser::insert(vase::Shard *vase, uint64_t start, uint64_t count) { if (count == 0) return; std::vector leaves; @@ -191,7 +191,7 @@ void Parser::insert(vase::Vase &vase, uint64_t start, uint64_t count) { modify(vase, start, count); } -void Parser::modify(vase::Vase &vase, uint64_t target, uint64_t count) { +void Parser::modify(vase::Shard *vase, uint64_t target, uint64_t count) { if (count == 0 || !root) return; std::vector tokens; @@ -218,7 +218,7 @@ void Parser::modify(vase::Vase &vase, uint64_t target, uint64_t count) { c = TreeCursor(root, 0, &offset); } } - vase::Iterator it = vase.iterate(at, Direction::Forward); + vase::Iterator it(vase, at, Direction::Forward); uint64_t chunk_start = at; uint64_t next_boundary = at + c.leaf->lines(); c.leaf->n = 0; @@ -318,13 +318,13 @@ uint64_t Parser::prev_opening(uint64_t line) { return 0; } -std::optional Parser::get_hl(vase::Vase &vase, uint64_t target) { +std::optional Parser::get_hl(vase::Shard *vase, uint64_t target) { if (!root) return std::nullopt; return Parser::Iterator(target, this, vase); } -Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Vase &vase) : p(p) { +Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Shard *vase) : p(p) { uint64_t offset; TreeCursor c = TreeCursor(p->root, target, &offset); at = target - offset; @@ -346,7 +346,7 @@ Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Vase &vase) : p(p) c = TreeCursor(p->root, 0, &offset); } } - it = vase.iterate(at, Direction::Forward); + it = vase::Iterator(vase, at, Direction::Forward); while (at < target) { it->next(); tokens.clear(); diff --git a/src/internal/vase/regex/api.cc b/src/internal/vase/regex/api.cc index 8dd9582..db45280 100644 --- a/src/internal/vase/regex/api.cc +++ b/src/internal/vase/regex/api.cc @@ -1,7 +1,7 @@ #include "internal/vase/vase.h" namespace bed::internal::vase { -std::vector Vase::parse_replace(std::string_view s) { +std::vector parse_replace(AppendStorage *ap, std::string_view s) { std::vector parts; std::string constant; auto flush_constant = [&]() { @@ -13,11 +13,11 @@ std::vector Vase::parse_replace(std::string_view s) { ++lines; ++p; } - uint64_t pos = append->append(constant.data(), (uint64_t)constant.size()); + uint64_t pos = ap->append(constant.data(), (uint64_t)constant.size()); parts.push_back( ReplacePart{ .type = ReplacePart::PartType::Constant, - .value = new Petal((uint64_t)constant.size(), lines, append, pos) + .value = new Petal((uint64_t)constant.size(), lines, ap, pos) } ); constant.clear(); @@ -61,15 +61,31 @@ std::vector Vase::parse_replace(std::string_view s) { return parts; } -void Vase::regex_search_replace( - std::string_view pattern, Range range, +Shard *substitute( + AppendStorage *ap, Shard *root, + std::string_view pattern, uint64_t start, uint64_t end, std::string_view replace, std::string_view options ) { - std::vector matches = _regex_search(pattern, range, options); - if (matches.empty()) - return; + 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 start_offset = offset_of(root, start); + uint64_t end_offset = + (end + 1 == line_count) + ? root->length + : offset_of(root, end + 1); - std::vector replace_parts = parse_replace(replace); + 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); @@ -141,23 +157,13 @@ void Vase::regex_search_replace( Shard *new_root = compact.empty() ? nullptr : Shard::build(compact.data(), 0, compact.size()); Shard::release(root); - root = new_root; + return new_root; } -std::vector Vase::regex_search( - std::string_view pattern, Range range, std::string_view options -) { - std::vector matches = _regex_search(pattern, range, options); - if (matches.empty()) - return {}; - std::vector result; - result.reserve(matches.size()); - for (auto match : matches) - result.push_back({point_of(match.start), point_of(match.end)}); - return result; -} - -uint64_t Vase::find_next(std::string_view pattern, uint64_t start) { +uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) { + if (start == 0 || start > root->lines) + throw ed_error("Invalid line number."); + start--; std::vector results; int errornumber; PCRE2_SIZE erroroffset; @@ -176,7 +182,7 @@ uint64_t Vase::find_next(std::string_view pattern, uint64_t start) { pcre2_code_free(re); throw ed_error("Can't create regex match data."); } - uint64_t at = (start + 1) % lines(); + uint64_t at = (start + 1) % (root->lines + 1); LineIterator it(root, at, Direction::Forward); std::string line; while (it.next(&line)) { @@ -196,7 +202,7 @@ uint64_t Vase::find_next(std::string_view pattern, uint64_t start) { at = 0; LineIterator it2(root, at, Direction::Forward); while (it2.next(&line)) { - if (at >= start) + if (at > start) break; int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr); if (rc >= 0) { @@ -216,7 +222,10 @@ uint64_t Vase::find_next(std::string_view pattern, uint64_t start) { throw ed_error("No line matched."); } -uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) { +uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start) { + if (start == 0 || start > root->lines) + throw ed_error("Invalid line number."); + start--; std::vector results; int errornumber; PCRE2_SIZE erroroffset; @@ -235,7 +244,7 @@ uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) { pcre2_code_free(re); throw ed_error("Can't create regex match data."); } - uint64_t at = (start == 0 ? lines() : start) - 1; + uint64_t at = (start == 0 ? root->lines : start - 1); LineIterator it(root, at, Direction::Backward); std::string line; while (it.next(&line)) { @@ -252,10 +261,10 @@ uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) { } at--; } - at = lines() - 1; + at = root->lines; LineIterator it2(root, at, Direction::Backward); while (it2.next(&line)) { - if (at <= start) + if (at < start) break; int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr); if (rc >= 0) { diff --git a/src/internal/vase/regex/core.cc b/src/internal/vase/regex/core.cc index d0a2fd8..94247a0 100644 --- a/src/internal/vase/regex/core.cc +++ b/src/internal/vase/regex/core.cc @@ -1,8 +1,8 @@ #include "internal/vase/vase.h" namespace bed::internal::vase { -std::vector Vase::_regex_search( - std::string_view pattern, Range range, std::string_view options +std::vector _regex_search( + Shard *root, std::string_view pattern, uint64_t start_offset, uint64_t end_offset, std::string_view options ) { bool global = false; uint64_t flags = PCRE2_MULTILINE | PCRE2_UTF; @@ -52,9 +52,6 @@ std::vector Vase::_regex_search( pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL); - uint64_t start_offset = offset_of(range.start); - uint64_t end_offset = offset_of(range.end); - PetalIterator it(root, Direction::Forward); it.seek_offset(start_offset); diff --git a/src/internal/vase/shard.cc b/src/internal/vase/shard.cc index 4b311a0..3d15bb9 100644 --- a/src/internal/vase/shard.cc +++ b/src/internal/vase/shard.cc @@ -14,6 +14,7 @@ void Shard::release(Shard *n) { release(((Branch *)n)->right); delete (Branch *)n; } else { + ((Petal *)n)->source->release(); delete (Petal *)n; } } @@ -200,28 +201,27 @@ Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) { return node; } -Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending) { +Shard *Shard::from_command(const char *cmd, bool posix_ending) { + auto o = new OriginalStorage("/tmp"); int dest_fd = o->fd; - if (dest_fd == -1) + if (dest_fd == -1) { + delete o; return nullptr; - + } FILE *pipe = popen(cmd, "r"); - if (!pipe) + if (!pipe) { + delete o; return nullptr; - + } std::vector pieces; pieces.reserve(16); - uint64_t pos = 0; char buf[PETAL_SIZE_MAX]; uint64_t buf_cursor = 0; - char ending[2] = {'\0', '\0'}; - while (true) { size_t got = fread(buf + buf_cursor, 1, sizeof(buf) - buf_cursor, pipe); buf_cursor += got; - if (buf_cursor == PETAL_SIZE_MAX || feof(pipe)) { if (buf_cursor == 0) break; @@ -244,6 +244,7 @@ Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending } if (!write_all(dest_fd, buf, buf_cursor)) { pclose(pipe); + delete o; return nullptr; } pieces.push_back(new Petal(buf_cursor, lines, o, pos)); @@ -251,19 +252,24 @@ Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending } if (feof(pipe)) break; - if (ferror(pipe)) + if (ferror(pipe)) { + delete o; return nullptr; + } } - int status = pclose(pipe); - if (status == -1) + if (status == -1) { + delete o; return nullptr; - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + delete o; return nullptr; - - if (pieces.empty()) + } + if (pieces.empty()) { + delete o; return nullptr; - + } if (posix_ending) { if (ending[1] == '\n') { Petal *last = (Petal *)pieces.back(); @@ -280,52 +286,58 @@ Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending last->length--; } } - o->initialize(); - if (pieces.size() == 1) return pieces[0]; - return build(pieces.data(), 0, pieces.size()); } -Shard *Shard::from_file(std::filesystem::path &path, OriginalBuffer *o, bool posix_ending) { +Shard *Shard::from_file(const std::filesystem::path &path, bool posix_ending) { + auto o = new OriginalStorage("/tmp"); int dest_fd = o->fd; - if (dest_fd == -1) + if (dest_fd == -1) { + delete o; return nullptr; + } int src_fd = open(path.c_str(), O_RDONLY); - if (src_fd == -1) + if (src_fd == -1) { + delete o; return nullptr; - + } uint64_t total = std::filesystem::file_size(path); if (posix_ending && total > 0) { char last; - if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) + if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) { + delete o; return nullptr; + } if (last == '\n') { total--; if (total > 0) { char s_last; - if (pread(src_fd, &s_last, 1, (off_t)(total - 1)) != 1) + if (pread(src_fd, &s_last, 1, (off_t)(total - 1)) != 1) { + delete o; return nullptr; + } if (s_last == '\r') total--; } } } - if (total == 0) + if (total == 0) { + delete o; return nullptr; - + } std::vector pieces; uint64_t pos = 0; pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX); char buf[PETAL_SIZE_MAX]; - while (pos < total) { uint64_t want = std::min(PETAL_SIZE_MAX, total - pos); ssize_t got = pread(src_fd, buf, want, pos); if (got <= 0) { close(src_fd); + delete o; return nullptr; } uint64_t take = (uint64_t)got; @@ -341,19 +353,70 @@ Shard *Shard::from_file(std::filesystem::path &path, OriginalBuffer *o, bool pos } if (!write_all(dest_fd, buf, take)) { close(src_fd); + delete o; return nullptr; } pieces.push_back(new Petal(take, lines, o, pos)); pos += take; } - close(src_fd); - - if (pieces.empty()) + if (pieces.empty()) { + delete o; return nullptr; - + } o->initialize(); + if (pieces.size() == 1) + return pieces[0]; + return build(pieces.data(), 0, pieces.size()); +} +Shard *Shard::from_string(const char *data, uint64_t len, bool posix_ending) { + auto o = new OriginalStorage("/tmp"); + int dest_fd = o->fd; + if (dest_fd == -1 || data == nullptr) { + delete o; + return nullptr; + } + uint64_t total = len; + if (posix_ending && total > 0) { + if (data[total - 1] == '\n') { + total--; + if (total > 0 && data[total - 1] == '\r') + total--; + } + } + if (total == 0) { + delete o; + return nullptr; + } + std::vector pieces; + uint64_t pos = 0; + pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX); + while (pos < total) { + uint64_t take = std::min(PETAL_SIZE_MAX, total - pos); + const char *buf = data + pos; + uint64_t lines = 0; + const char *p = buf; + const char *end = buf + take; + while (p < end) { + const void *nl = memchr(p, '\n', end - p); + if (!nl) + break; + lines++; + p = (const char *)nl + 1; + } + if (!write_all(dest_fd, buf, take)) { + delete o; + return nullptr; + } + pieces.push_back(new Petal(take, lines, o, pos)); + pos += take; + } + if (pieces.empty()) { + delete o; + return nullptr; + } + o->initialize(); if (pieces.size() == 1) return pieces[0]; return build(pieces.data(), 0, pieces.size()); diff --git a/src/internal/vase/buffer/append.cc b/src/internal/vase/storage/append.cc similarity index 83% rename from src/internal/vase/buffer/append.cc rename to src/internal/vase/storage/append.cc index f0000cd..bbf5e98 100644 --- a/src/internal/vase/buffer/append.cc +++ b/src/internal/vase/storage/append.cc @@ -1,7 +1,7 @@ #include "internal/vase/vase.h" namespace bed::internal::vase { -AppendBuffer::AppendBuffer(std::filesystem::path base_dir) { +AppendStorage::AppendStorage(std::filesystem::path base_dir) { base_dir /= "tapp.XXXXXX"; char *s = strdup(base_dir.c_str()); fd = mkstemp(s); @@ -17,14 +17,14 @@ AppendBuffer::AppendBuffer(std::filesystem::path base_dir) { throw std::runtime_error("mmap failed"); } -AppendBuffer::~AppendBuffer() { +AppendStorage::~AppendStorage() { if (buf && buf != MAP_FAILED) munmap(buf, allocated_capacity); if (fd != -1) close(fd); } -void AppendBuffer::grow(uint64_t len) { +void AppendStorage::grow(uint64_t len) { if (current_size + len > allocated_capacity) { uint64_t new_capacity = allocated_capacity * 2; if (new_capacity < current_size + len) @@ -46,13 +46,13 @@ void AppendBuffer::grow(uint64_t len) { } } -uint64_t AppendBuffer::append(const char c) { +uint64_t AppendStorage::append(const char c) { grow(1); buf[current_size++] = c; return current_size - 1; } -uint64_t AppendBuffer::append(const char *text, uint64_t len) { +uint64_t AppendStorage::append(const char *text, uint64_t len) { grow(len); memcpy(buf + current_size, text, len); uint64_t old_pos = current_size; @@ -60,13 +60,13 @@ uint64_t AppendBuffer::append(const char *text, uint64_t len) { return old_pos; } -const char *AppendBuffer::read(uint64_t pos) { +const char *AppendStorage::read(uint64_t pos) { if (pos >= current_size) return nullptr; return buf + pos; } -uint64_t AppendBuffer::length() { +uint64_t AppendStorage::length() { return current_size; } } // namespace bed::internal::vase diff --git a/src/internal/vase/buffer/original.cc b/src/internal/vase/storage/original.cc similarity index 80% rename from src/internal/vase/buffer/original.cc rename to src/internal/vase/storage/original.cc index 9935b08..77087d3 100644 --- a/src/internal/vase/buffer/original.cc +++ b/src/internal/vase/storage/original.cc @@ -1,7 +1,7 @@ #include "internal/vase/vase.h" namespace bed::internal::vase { -OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) { +OriginalStorage::OriginalStorage(std::filesystem::path base_dir) { if (!std::filesystem::exists(base_dir) || !std::filesystem::is_directory(base_dir)) throw std::runtime_error("Swap directory does not exist or is not a directory."); base_dir /= "tbuf.XXXXXX"; @@ -13,14 +13,14 @@ OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) { free(s); } -OriginalBuffer::~OriginalBuffer() { +OriginalStorage::~OriginalStorage() { if (buf) munmap((char *)buf, len); if (fd != -1) close(fd); } -void OriginalBuffer::initialize() { +void OriginalStorage::initialize() { struct stat st; if (fstat(fd, &st) == -1) throw std::runtime_error("fstat failed"); @@ -35,13 +35,13 @@ void OriginalBuffer::initialize() { fd = -1; } -const char *OriginalBuffer::read(uint64_t pos) { +const char *OriginalStorage::read(uint64_t pos) { if (pos >= len) return nullptr; return buf + pos; } -uint64_t OriginalBuffer::length() { +uint64_t OriginalStorage::length() { return len; } } // namespace bed::internal::vase diff --git a/src/internal/vase/vase.cc b/src/internal/vase/vase.cc index be4ca58..8f1e2c1 100644 --- a/src/internal/vase/vase.cc +++ b/src/internal/vase/vase.cc @@ -1,108 +1,7 @@ #include "internal/vase/vase.h" namespace bed::internal::vase { -Vase::Vase(std::filesystem::path path, std::filesystem::path swapdir) - : path(path), swapdir(swapdir) { - if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir)) - throw std::runtime_error("Swap directory does not exist or is not a directory."); - append = new AppendBuffer(swapdir); - original = new OriginalBuffer(swapdir); - if (std::filesystem::is_regular_file(path)) - root = Shard::from_file(path, original, posix_ending); - else - root = nullptr; - history_top = 0; - history.push_back(root); - Shard::retain(root); -} - -Vase::Vase(std::string cmd, std::filesystem::path swapdir) - : path(""), swapdir(swapdir) { - if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir)) - throw std::runtime_error("Swap directory does not exist or is not a directory."); - append = new AppendBuffer(swapdir); - original = new OriginalBuffer(swapdir); - root = Shard::from_command(cmd.c_str(), original, posix_ending); - history_top = 0; - history.push_back(root); - Shard::retain(root); -} - -Vase::Vase(std::filesystem::path swapdir) - : path(""), swapdir(swapdir) { - if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir)) - throw std::runtime_error("Swap directory does not exist or is not a directory."); - append = new AppendBuffer(swapdir); - original = new OriginalBuffer(swapdir); - root = nullptr; - history_top = 0; - history.push_back(root); - Shard::retain(root); -} - -Vase::~Vase() { - Shard::release(root); - for (auto s : history) - Shard::release(s); - if (original) - delete original; - if (append) - delete append; -} - -Vase::Vase(Vase &&other) noexcept - : original(other.original), - append(other.append), - root(other.root), - posix_ending(other.posix_ending), - using_crlf(other.using_crlf), - path(std::move(other.path)), - swapdir(std::move(other.swapdir)), - history(std::move(other.history)), - history_top(other.history_top) { - other.original = nullptr; - other.append = nullptr; - other.root = nullptr; - other.history_top = 0; -} - -Vase &Vase::operator=(Vase &&other) noexcept { - if (this == &other) - return *this; - Shard::release(root); - for (auto s : history) - Shard::release(s); - delete original; - delete append; - original = other.original; - append = other.append; - root = other.root; - posix_ending = other.posix_ending; - using_crlf = other.using_crlf; - path = std::move(other.path); - swapdir = std::move(other.swapdir); - history = std::move(other.history); - history_top = other.history_top; - other.original = nullptr; - other.append = nullptr; - other.root = nullptr; - other.history_top = 0; - return *this; -} - -uint64_t Vase::length() { - if (!root) - return 0; - return root->length + posix_ending; -} - -uint64_t Vase::lines() { - if (!root) - return 0; - return root->lines + 1; -} - -std::string Vase::to_string() { +std::string to_string(Shard *root) { std::string out; if (!root) return out; @@ -112,23 +11,19 @@ std::string Vase::to_string() { uint64_t len; while (it.next(&data, &len)) out.append(data, len); - if (posix_ending) - out.append("\n"); return out; } -std::string Vase::to_string(Range range) { - clamp(&range.start); - clamp(&range.end); +std::string to_string(Shard *root, Range range) { std::string out; if (!root) return out; PetalIterator it(root, Direction::Forward); - uint64_t start = offset_of(range.start); + uint64_t start = offset_of(root, range.start); it.seek_offset(start); const char *data; uint64_t len; - uint64_t remaining = offset_of(range.end) - start; + uint64_t remaining = offset_of(root, range.end) - start; while (remaining && it.next(&data, &len)) { uint64_t n = std::min(len, remaining); out.append(data, n); @@ -137,85 +32,10 @@ std::string Vase::to_string(Range range) { return out; } -Iterator Vase::iterate(uint64_t line, Direction dir) { - return Iterator(root, line, dir); -} - -bool Vase::undo() { - if (history_top == 0) - return false; - Shard::release(root); - history_top--; - root = history[history_top]; - Shard::retain(root); - return true; -} - -bool Vase::redo() { - if (history_top + 1 >= history.size()) - return false; - Shard::release(root); - history_top++; - root = history[history_top]; - Shard::retain(root); - return true; -} - -void Vase::snapshot() { - if (history[history_top] == root) - return; - while (history.size() > history_top + 1) { - Shard::release(history.back()); - history.pop_back(); - } - Shard::retain(root); - history.push_back(root); - history_top++; -} - -void Vase::prune_history(uint64_t n) { - uint64_t keep = std::min(history.size(), n + 1); - if (keep == history.size()) - return; - uint64_t remove = history.size() - keep; - for (uint64_t i = 0; i < remove; ++i) - Shard::release(history[i]); - history.erase(history.begin(), history.begin() + remove); - history_top -= remove; -} - -bool Vase::save() { - if (!root) - return true; - if (path == "") - return false; - std::ofstream file(path, std::ios::binary); - if (!file) - return false; - PetalIterator it(root, Direction::Forward); - it.seek_offset(0); - const char *data; - uint64_t len; - while (it.next(&data, &len)) { - file.write(data, len); - if (!file) - return false; - } - if (posix_ending) - file.write("\n", 1); - if (!file) - return false; - return true; -} - -bool Vase::save_swap() { - return false; -} - -void Vase::insert(Point *point, char key) { - uint64_t pos = append->append(key); - Shard *inserted = new Petal(1, key == '\n', append, pos); - auto [left, right] = Shard::split(root, offset_of(*point)); +Shard *insert(AppendStorage *ap, Shard *root, Point *point, char key) { + uint64_t pos = ap->append(key); + Shard *inserted = new Petal(1, key == '\n', ap, pos); + auto [left, right] = Shard::split(root, offset_of(root, *point)); Shard *left2 = Shard::append(left, inserted); Shard::release(left); Shard::release(inserted); @@ -223,31 +43,28 @@ void Vase::insert(Point *point, char key) { Shard::release(left2); Shard::release(right); Shard::release(root); - root = new_root; if (key == '\n') *point = {point->row + 1, 0}; else point->col++; + return new_root; } -void Vase::insert(Point *point, std::string_view str) { - insert(point, str.data(), str.size()); -} - -void Vase::insert(Point *point, const char *data, uint64_t len) { +Shard *insert(AppendStorage *ap, Shard *root, Point *point, const char *data, uint64_t len) { while (len) { uint64_t chunk_size = std::min(len, PETAL_SIZE_MAX); - _insert(point, data, chunk_size); + _insert(ap, &root, point, data, chunk_size); len -= chunk_size; data += chunk_size; } + return root; } -void Vase::_insert(Point *point, const char *data, uint64_t len) { +void _insert(AppendStorage *ap, Shard **root, Point *point, const char *data, uint64_t len) { if (len == 0) return; - uint64_t offset = offset_of(*point); - uint64_t pos = append->append(data, len); + uint64_t offset = offset_of(*root, *point); + uint64_t pos = ap->append(data, len); uint64_t lines = 0; const char *start = data; const char *last_line = start; @@ -270,50 +87,23 @@ void Vase::_insert(Point *point, const char *data, uint64_t len) { } else { point->col += col; } - Shard *inserted = new Petal(len, lines, append, pos); - auto [left, right] = Shard::split(root, offset); + Shard *inserted = new Petal(len, lines, ap, pos); + auto [left, right] = Shard::split(*root, offset); Shard *left2 = Shard::append(left, inserted); Shard::release(left); Shard::release(inserted); Shard *new_root = Shard::concat(left2, right); Shard::release(left2); Shard::release(right); - Shard::release(root); - root = new_root; + Shard::release(*root); + *root = new_root; } -void Vase::erase(Point *point, uint64_t amount, Direction dir) { - if (amount == 0) - return; - Point start = *point; - Point end = *point; - if (dir == Direction::Forward) - move_clusters(&end, amount, Direction::Forward); - else - move_clusters(&start, amount, Direction::Backward); - uint64_t start_offset = offset_of(start); - uint64_t end_offset = offset_of(end); - if (start_offset > end_offset) - std::swap(start_offset, end_offset); - uint64_t count = end_offset - start_offset; - auto [a, b] = Shard::split(root, start_offset); - auto [d, c] = Shard::split(b, count); - Shard *new_root = Shard::concat(a, c); - Shard::release(a); - Shard::release(b); - Shard::release(c); - Shard::release(d); - Shard::release(root); - root = new_root; - if (dir == Direction::Backward) - *point = start; -} - -void Vase::erase(Range range) { +Shard *erase(Shard *root, Range range) { Point start = range.start; Point end = range.end; - uint64_t start_offset = offset_of(start); - uint64_t end_offset = offset_of(end); + uint64_t start_offset = offset_of(root, start); + uint64_t end_offset = offset_of(root, end); uint64_t count = end_offset - start_offset; auto [a, b] = Shard::split(root, start_offset); auto [d, c] = Shard::split(b, count); @@ -323,188 +113,136 @@ void Vase::erase(Range range) { Shard::release(c); Shard::release(d); Shard::release(root); - root = new_root; + return new_root; } -void Vase::replace(Range range, std::string_view str) { - replace(range, str.data(), str.size()); +Shard *replace(AppendStorage *ap, Shard *root, Range range, const char *data, uint64_t len) { + root = erase(root, range); + return insert(ap, root, &range.start, data, len); } -void Vase::replace(Range range, const char *data, uint64_t len) { - erase(range); - insert(&range.start, data, len); +uint64_t offset_of(Shard *root, Point point) { + return offset_of(root, point.row) + point.col; } -uint64_t Vase::offset_of(Point point) { - clamp(&point); - LineIterator it(root, point.row, Direction::Forward); - std::string line; +uint64_t offset_of(Shard *root, uint64_t line) { + if (!root) + return 0; + if (line > root->lines) + throw ed_error("line out of range"); uint64_t offset = 0; - if (it.next(&line)) { - const char *ptr = line.data(); - uint64_t remaining = line.length(); - while (point.col && remaining) { - uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining); - remaining -= next_len; - ptr += next_len; - offset += next_len; - point.col--; + Shard *curr = root; + while (curr->kind == Shard::Kind::Branch) { + auto *b = (Branch *)curr; + if (line <= b->left->lines) { + curr = b->left; + } else { + line -= b->left->lines; + offset += b->left->length; + curr = b->right; } } - return it.byte_offset() + offset; + auto *petal = (Petal *)curr; + if (line == 0) + return offset; + const char *text = petal->source->read(petal->pos); + uint64_t local = 0; + while (line--) { + const char *nl = (const char *)memchr(text + local, '\n', petal->length - local); + if (!nl) + throw std::runtime_error("leaf line count is wrong."); + local = (nl - text) + 1; + } + return offset + local; } -Point Vase::point_of(uint64_t offset) { - PetalIterator it(root, Direction::Backward); - it.seek_offset(offset); - Point p; - p.row = it.global_line; - std::string line; - const char *chunk; - uint64_t len = 0; - if (!it.next(&chunk, &len)) - return p; - while (true) { -#if defined(__GLIBC__) || defined(__APPLE__) - const char *nl = (const char *)memrchr(chunk, '\n', len); -#else - const char *nl = nullptr; - const char *p = chunk + len; - while (!nl && p != chunk) - if (*(--p) == '\n') - nl = p; -#endif - if (!nl) { - if (len && chunk[len - 1] == '\r') - --len; - line.insert(0, chunk, len); - if (!it.next(&chunk, &len)) - break; - continue; - } - const char *end = chunk + len; - const char *start = nl + 1; - const char *line_end = end; - if (line_end > start && *(line_end - 1) == '\r') - --line_end; - line.insert(0, start, line_end - start); - break; - } - const char *ptr = line.data(); - uint64_t remaining = line.length(); - while (remaining) { - uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining); - remaining -= next_len; - ptr += next_len; - p.col++; - } - clamp(&p); - return p; +static Shard *newline(AppendStorage *ap) { + uint64_t pos = ap->append('\n'); + return new Petal(1, true, ap, pos); } -void Vase::move_clusters(Point *point, uint64_t amount, Direction dir) { - if (amount == 0) - return; - if (dir == Direction::Backward) { - LineIterator it(root, point->row, Direction::Backward); - while (amount) { - std::string line; - if (!it.next(&line)) - return; - std::vector clusters; - const char *ptr = line.data(); - uint64_t remaining = line.size(); - uint64_t byte = 0; - while (remaining) { - clusters.push_back(byte); - uint64_t len = - grapheme_next_character_break_utf8(ptr, remaining); - ptr += len; - remaining -= len; - byte += len; - } - while (amount && point->col) { - point->col--; - amount--; - } - if (amount == 0) - return; - if (point->row == 0) - return; - point->row--; - point->col = clusters.size(); - amount--; - } - } else { - LineIterator it(root, point->row, Direction::Forward); - while (amount) { - std::string line; - if (!it.next(&line)) - return; - if (point->col || amount) { - const char *ptr = line.data(); - uint64_t remaining = line.size(); - uint64_t col = 0; - while (col < point->col && remaining) { - uint64_t len = grapheme_next_character_break_utf8(ptr, remaining); - ptr += len; - remaining -= len; - col++; - } - while (amount && remaining) { - uint64_t len = grapheme_next_character_break_utf8(ptr, remaining); - ptr += len; - remaining -= len; - point->col++; - amount--; - } - } - if (amount) { - point->row++; - point->col = 0; - amount--; - } - } - } - clamp(point); -} - -void Vase::clamp(Point *point) { +Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line) { if (!root) { - point->row = 0; - point->col = 0; - return; + if (line != 0) + throw ed_error("line out of range"); + Shard::retain(text); + return text; } - if (point->row > root->lines) { - point->row = root->lines; - point->col = UINT64_MAX; + if (line > root->lines + 1) + throw ed_error("line out of range"); + Shard::retain(text); + Shard *nl = newline(ap); + if (line <= root->lines) { + uint64_t offset = offset_of(root, line); + auto [left, right] = Shard::split(root, offset); + Shard *middle = Shard::concat(text, nl); + Shard::release(text); + Shard::release(nl); + Shard *new_root = Shard::concat(left, middle); + Shard::release(left); + Shard::release(middle); + middle = Shard::concat(new_root, right); + Shard::release(new_root); + Shard::release(right); + Shard::release(root); + return middle; } - LineIterator it(root, point->row, Direction::Forward); - std::string line; - uint64_t clusters = 0; - if (it.next(&line)) { - const char *ptr = line.data(); - uint64_t remaining = line.length(); - while (remaining) { - uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining); - remaining -= next_len; - ptr += next_len; - clusters++; - } - } - if (point->col > clusters) - point->col = clusters; + Shard *new_root = Shard::concat(root, nl); + Shard::release(root); + Shard::release(nl); + Shard *result = Shard::concat(new_root, text); + Shard::release(new_root); + Shard::release(text); + return result; } -void Vase::move_lines(Point *point, uint64_t amount, Direction dir) { - if (dir == Direction::Forward) { - point->row += amount; - } else { - if (amount > point->row) - point->row = 0; - else - point->row -= amount; - } - clamp(point); +Shard *erase(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 start_offset = offset_of(root, start); + uint64_t end_offset = + (end + 1 == line_count) + ? root->length + : offset_of(root, end + 1); + auto [left, rest] = Shard::split(root, start_offset); + auto [middle, right] = Shard::split(rest, end_offset - start_offset); + Shard *new_root = Shard::concat(left, right); + Shard::release(left); + Shard::release(rest); + Shard::release(middle); + Shard::release(right); + Shard::release(root); + return new_root; +} + +Shard *copy(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 start_offset = offset_of(root, start); + uint64_t end_offset = + (end + 1 == line_count) + ? root->length + : offset_of(root, end + 1); + auto [left, rest] = Shard::split(root, start_offset); + auto [middle, right] = Shard::split(rest, end_offset - start_offset); + Shard::release(left); + Shard::release(rest); + Shard::release(right); + Shard::release(root); + return middle; } } // namespace bed::internal::vase