diff --git a/include/commands/ed/ed.h b/include/commands/ed/ed.h index 03ba39f..d41b383 100644 --- a/include/commands/ed/ed.h +++ b/include/commands/ed/ed.h @@ -4,6 +4,84 @@ #include "pch.h" namespace crib::commands::ed { +struct ed_error : std::runtime_error { + ed_error(const char *msg) : std::runtime_error(msg) {} +}; + +struct Command { + enum struct Type : uint8_t { + Invalid, + Quit, + ForceQuit, + PromptToggle, + HelpToggle, + Help, + Print, + Number, + Append, + Change, + Delete, + Write, + Dump, + None + } type; + + struct Address { + enum struct Type : uint8_t { + None, + Current, + Last, + Number, + Mark, + SearchForward, + SearchBackward + } type; + int64_t number = 0; + std::string regex = ""; + int64_t offset = 0; + char mark = '\0'; + }; + + Address start{}; + Address end{}; + static constexpr uint8_t SEMICOLON = 0b01; + static constexpr uint8_t RANGE = 0b10; + uint8_t address_flags = 0; + char suffix = '\0'; + std::string argument; +}; + +struct Ed { + crib::internal::vase::Vase vase; + uint64_t line = 0; + bool modified = false; + bool quitting = false; + uint64_t marks[26]{0}; + std::string last_regex = ""; + bool help_mode = false; + bool suppress_mode = false; + bool prompt_mode = false; + std::string prompt = "*"; + std::string last_message = ""; + + Ed(std::filesystem::path file, bool suppress_mode, std::string prompt_) + : vase(file, "/tmp"), suppress_mode(suppress_mode), prompt(prompt_) { + if (prompt == "") + prompt = "*"; + else + prompt_mode = true; + line = vase.lines(); + std::cout << vase.length() << std::endl; + } + + void parse_address(std::string_view cmd, uint64_t &i, Command::Address &addr); + void resolve_address(Command::Address addr, uint64_t *out_line); + Command parse(std::string cmd, bool eof); + void append(std::string text, uint64_t line); + void remove(uint64_t start_line, uint64_t end_line); + bool handle(std::string cmd, bool eof); +}; + std::string summary(); void help(); void run(std::vector); diff --git a/include/internal/vase/iterators/line.h b/include/internal/vase/iterators/line.h index 4e703a4..72514ee 100644 --- a/include/internal/vase/iterators/line.h +++ b/include/internal/vase/iterators/line.h @@ -24,8 +24,8 @@ struct Iterator { Shard *root; std::string line; - Iterator(Shard *root, uint64_t line_num) - : root(root), it(root, line_num, Direction::Forward) { + Iterator(Shard *root, uint64_t line_num, Direction dir) + : root(root), it(root, line_num, dir) { Shard::retain(root); } @@ -63,9 +63,8 @@ struct Iterator { return *this; } - std::string &next() { - it.next(&line); - return line; + bool next() { + return it.next(&line); } private: diff --git a/include/internal/vase/shard.h b/include/internal/vase/shard.h index b1ea550..d0a5991 100644 --- a/include/internal/vase/shard.h +++ b/include/internal/vase/shard.h @@ -25,7 +25,7 @@ struct Shard { static void retain(Shard *n); static void release(Shard *n); - static Shard *from_file(std::filesystem::path path, OriginalBuffer *b); + static Shard *from_file(std::filesystem::path path, OriginalBuffer *b, bool posix_ending); static std::vector from_swap(std::filesystem::path path, OriginalBuffer *b); static std::pair split(Shard *n, uint64_t offset); @@ -34,6 +34,7 @@ struct Shard { static Shard *merge_leaves(Shard *a, Shard *b); static Shard *append(Shard *root, Shard *leaf); static Shard *build(Shard **pieces, uint64_t lo, uint64_t hi); + static void dump(Shard *node, int depth = 0); }; struct Branch : Shard { @@ -62,54 +63,4 @@ struct Petal : Shard { source(source), pos(pos) {}; }; -extern inline void dump_shard(Shard *node, int depth = 0) { - if (!node) { - std::cout << std::string(depth * 2, ' ') << "\n"; - return; - } - - std::string indent(depth * 2, ' '); - - std::cout << indent - << "Shard@" << node - << " kind="; - - switch (node->kind) { - case Shard::Kind::Branch: - std::cout << "Branch"; - break; - case Shard::Kind::Petal: - std::cout << "Petal"; - break; - } - - std::cout - << " height=" << unsigned(node->height) - << " length=" << node->length - << " lines=" << node->lines - << " refs=" << node->refs.load() - << "\n"; - - if (node->kind == Shard::Kind::Branch) { - auto *branch = static_cast(node); - - std::cout << indent << " left:\n"; - dump_shard(branch->left, depth + 2); - - std::cout << indent << " right:\n"; - dump_shard(branch->right, depth + 2); - } else { - auto *petal = static_cast(node); - - std::cout - << indent << " source=" << petal->source - << " pos=" << petal->pos - << " length=" << petal->length - << " lines=" << petal->lines - << "\n"; - } - - if (!depth) - std::cout << "\n\n"; -} } // namespace crib::internal::vase diff --git a/include/internal/vase/vase.h b/include/internal/vase/vase.h index 6a6f116..bbaf0d7 100644 --- a/include/internal/vase/vase.h +++ b/include/internal/vase/vase.h @@ -43,6 +43,15 @@ struct Vase { 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; @@ -50,15 +59,18 @@ struct Vase { ~Vase(); uint64_t length(); + uint64_t lines(); std::string to_string(); std::string to_string(Range range); - Iterator iterate(uint64_t line); + 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); diff --git a/src/commands/ed/ed.cc b/src/commands/ed/ed.cc index 2376dba..d9919b0 100644 --- a/src/commands/ed/ed.cc +++ b/src/commands/ed/ed.cc @@ -1,11 +1,40 @@ #include "commands/ed/ed.h" +#include "cli.h" namespace crib::commands::ed { std::string summary() { return "A POSIX-compliant line editor."; } + void help() {} + void run(std::vector args) { - return; + std::string prompt = ""; + std::filesystem::path filepath; + bool suppress = false; + for (size_t i = 1; i < args.size(); i++) { + if (args[i] == "-p") { + i++; + if (i >= args.size()) + throw crib::cli::cli_error("Prompt not specified!", 1); + prompt = args[i]; + } else if (args[i] == "-s") { + suppress = true; + } else { + if (filepath.string().size()) + throw crib::cli::cli_error("Invalid arguments given.", 1); + filepath = args[i]; + } + } + Ed ed(filepath, suppress, prompt); + std::string command; + while (true) { + std::string cmd; + if (ed.prompt_mode) + std::cout << ed.prompt; + bool eof = !std::getline(std::cin, cmd); + if (!ed.handle(cmd, eof)) + return; + } } } // namespace crib::commands::ed diff --git a/src/commands/ed/edit.cc b/src/commands/ed/edit.cc new file mode 100644 index 0000000..17ebde6 --- /dev/null +++ b/src/commands/ed/edit.cc @@ -0,0 +1,21 @@ +#include "commands/ed/ed.h" + +namespace crib::commands::ed { +void Ed::append(std::string text, uint64_t line) { + using namespace crib::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(); + } + vase.insert(&p, text); +} + +void Ed::remove(uint64_t start_line, uint64_t end_line) { + vase.erase({{start_line - 1, 0}, {end_line, 0}}); +} +} // namespace crib::commands::ed diff --git a/src/commands/ed/handle.cc b/src/commands/ed/handle.cc new file mode 100644 index 0000000..5e9c1a1 --- /dev/null +++ b/src/commands/ed/handle.cc @@ -0,0 +1,254 @@ +#include "commands/ed/ed.h" + +namespace crib::commands::ed { +bool Ed::handle(std::string cmd, bool eof) { + using namespace crib::internal::vase; + try { + if (line > vase.lines()) + line = vase.lines(); + Command command = parse(cmd, eof); + bool was_quitting = quitting; + quitting = false; + switch (command.type) { + case Command::Type::Quit: + if (modified && !was_quitting) { + quitting = true; + throw ed_error("Buffer modified."); + } else { + return false; + } + case Command::Type::ForceQuit: + return false; + case Command::Type::None: { + if (command.start.type != Command::Address::Type::None) { + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line); + else + resolve_address(command.start, &line); + } else { + line++; + } + if (line == 0) + throw ed_error("Line 0 is invalid."); + if (line > vase.lines()) + throw ed_error("Line position too high."); + Iterator it = vase.iterate(line - 1, Direction::Backward); + it.next(); + std::cout << it.line << std::endl; + } break; + case Command::Type::Invalid: + throw ed_error("Invalid command."); + case Command::Type::HelpToggle: + help_mode = !help_mode; + if (help_mode && last_message != "") + std::cout << last_message << std::endl; + break; + case Command::Type::Help: + if (last_message != "") + std::cout << last_message << std::endl; + break; + case Command::Type::PromptToggle: + prompt_mode = !prompt_mode; + break; + case Command::Type::Print: { + uint64_t line_start = line; + uint64_t line_end = line; + if (command.start.type != Command::Address::Type::None) { + resolve_address(command.start, &line_start); + line_end = line_start; + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line_end); + } + if (line_start == 0) + throw ed_error("Line 0 is invalid."); + if (line_end < line_start) + throw ed_error("Invalid address range."); + Iterator it = vase.iterate(line_start - 1, Direction::Forward); + while (it.next() && line_start++ <= line_end) + std::cout << it.line << std::endl; + line = line_end; + } break; + case Command::Type::Number: { + uint64_t line_start = line; + uint64_t line_end = line; + if (command.start.type != Command::Address::Type::None) { + resolve_address(command.start, &line_start); + line_end = line_start; + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line_end); + } + if (line_start == 0) + throw ed_error("Line 0 is invalid."); + if (line_end < line_start) + throw ed_error("Invalid address range."); + Iterator it = vase.iterate(line_start - 1, Direction::Forward); + while (it.next() && line_start <= line_end) + std::cout << line_start++ << "\t" << it.line << std::endl; + line = line_end; + } break; + case Command::Type::Append: { + std::string text; + std::string cline; + uint64_t line_count = 0; + while (std::getline(std::cin, cline)) { + if (cline == ".") + break; + line_count++; + text.append(cline); + text.push_back('\n'); + } + if (std::cin.eof()) + throw ed_error("EOF reached before '.' during text input."); + if (command.start.type != Command::Address::Type::None) { + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line); + else + resolve_address(command.start, &line); + } + if (text.empty()) { + if (line == 0) + line = 1; + break; + } + vase.snapshot(); + append(text, line); + modified = true; + line += line_count; + } break; + case Command::Type::Change: { + uint64_t line_start = line; + uint64_t line_end = line; + if (command.start.type != Command::Address::Type::None) { + resolve_address(command.start, &line_start); + if (line_start == 0) + line_start = 1; + line_end = line_start; + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line_end); + } + if (line_end < line_start) + throw ed_error("Invalid address range."); + std::string text; + std::string cline; + uint64_t line_count = 0; + while (std::getline(std::cin, cline)) { + if (cline == ".") + break; + line_count++; + text.append(cline); + text.push_back('\n'); + } + if (std::cin.eof()) + throw ed_error("EOF reached before '.' during text input."); + vase.snapshot(); + remove(line_start, line_end); + line = line_start; + if (text.empty()) + break; + append(text, line); + modified = true; + line += line_count; + } break; + case Command::Type::Delete: { + uint64_t line_start = line; + uint64_t line_end = line; + if (command.start.type != Command::Address::Type::None) { + resolve_address(command.start, &line_start); + if (line_start == 0) + line_start = 1; + line_end = line_start; + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line_end); + } + if (line_end < line_start) + throw ed_error("Invalid address range."); + vase.snapshot(); + vase.erase({{line_start - 1, 0}, {line_end, 0}}); + modified = true; + line = line_start; + if (line > vase.lines()) + line = vase.lines(); + } break; + case Command::Type::Write: { + std::string path = command.argument; + if (path.empty()) { + path = vase.path; + if (path.empty()) + throw ed_error("Need filename to write to."); + } + uint64_t line_start = 1; + uint64_t line_end = vase.lines(); + if (vase.lines() == 0) + line_start = line_end = 0; + if (command.start.type != Command::Address::Type::None) { + resolve_address(command.start, &line_start); + line_end = line_start; + if (command.address_flags & Command::RANGE) + resolve_address(command.end, &line_end); + } + if (vase.lines() && line_start == 0) + throw ed_error("Line 0 is invalid."); + if (line_end < line_start) + throw ed_error("Invalid address range."); + uint64_t bytes = 0; + if (path[0] == '!') { + FILE *pipe = popen(path.c_str() + 1, "w"); + if (!pipe) + throw ed_error("Error starting command."); + Iterator it = vase.iterate(line_start - 1, Direction::Forward); + while (it.next() && line_start++ <= line_end) { + it.line += '\n'; + bytes += it.line.size(); + if (fputs(it.line.c_str(), pipe) == EOF) { + pclose(pipe); + throw ed_error("Error writing to command."); + } + } + if (pclose(pipe) == -1) + throw ed_error("Error closing command."); + } else { + vase.path = path; + std::ofstream file(path, std::ios::out | std::ios::trunc); + if (!file) + throw ed_error("Error writing to file."); + Iterator it = vase.iterate(line_start - 1, Direction::Forward); + while (it.next() && line_start++ <= line_end) { + file << it.line << '\n'; + bytes += it.line.size() + 1; + } + if (!file) + throw ed_error("Error writing to file."); + modified = false; + } + if (!suppress_mode) + std::cout << bytes << std::endl; + } break; + case Command::Type::Dump: + Shard::dump(vase.root); + break; + } + if (command.suffix) { + Iterator it = vase.iterate(line - 1, Direction::Forward); + it.next(); + switch (command.suffix) { + case 'p': + std::cout << it.line << std::endl; + break; + case 'n': + std::cout << line << "\t" << it.line << std::endl; + break; + case 'l': + // TODO: escaping. + std::cout << it.line << std::endl; + break; + } + } + } catch (ed_error &e) { + last_message = e.what(); + std::cout << "?" << std::endl; + if (help_mode) + std::cout << e.what() << std::endl; + } + return true; +} +} // namespace crib::commands::ed diff --git a/src/commands/ed/parsers/address.cc b/src/commands/ed/parsers/address.cc new file mode 100644 index 0000000..ee68937 --- /dev/null +++ b/src/commands/ed/parsers/address.cc @@ -0,0 +1,178 @@ +#include "commands/ed/ed.h" + +namespace crib::commands::ed { +void Ed::parse_address(std::string_view cmd, uint64_t &i, Command::Address &addr) { + auto skip_space = [&] { + while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t')) + ++i; + }; + skip_space(); + if (i >= cmd.size()) { + addr.type = Command::Address::Type::None; + return; + } + switch (cmd[i]) { + case '.': + addr.type = Command::Address::Type::Current; + i++; + break; + case '$': + addr.type = Command::Address::Type::Last; + i++; + break; + case '\'': + addr.type = Command::Address::Type::Mark; + i++; + if (i < cmd.size() && 'a' <= cmd[i] && cmd[i] <= 'z') + i++; + else + throw ed_error("Invalid mark."); + addr.mark = cmd[i]; + break; + case '/': { + addr.type = Command::Address::Type::SearchForward; + 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++; + } + addr.regex = cmd.substr(start, i - start); + } break; + case '?': { + addr.type = Command::Address::Type::SearchBackward; + 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++; + } + addr.regex = cmd.substr(start, i - start); + } break; + case '+': { + 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; + addr.offset += num; + } break; + case '-': { + 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; + addr.offset -= num; + } break; + default: { + if ('0' <= cmd[i] && cmd[i] <= '9') { + int64_t num = 0; + addr.type = Command::Address::Type::Number; + while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') { + num = num * 10 + (cmd[i] - '0'); + i++; + } + addr.number = num; + } else { + addr.type = Command::Address::Type::None; + 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; + addr.offset += positive ? num : -num; + skip_space(); + } +} + +void Ed::resolve_address(Command::Address addr, uint64_t *out_line) { + switch (addr.type) { + case Command::Address::Type::None: + break; + case Command::Address::Type::Current: + *out_line = line; + if (*out_line > vase.lines()) + *out_line = vase.lines(); + break; + case Command::Address::Type::Number: + *out_line = addr.number; + break; + case Command::Address::Type::Last: + *out_line = vase.lines(); + break; + case Command::Address::Type::Mark: + *out_line = marks[addr.mark - 'a']; + if (!*out_line) + throw ed_error("Unset mark used."); + break; + case Command::Address::Type::SearchForward: + if (addr.regex == "") + addr.regex = last_regex; + last_regex = addr.regex; + // TODO: use vase.regex_search(regex, range, options); + break; + case Command::Address::Type::SearchBackward: + if (addr.regex == "") + addr.regex = last_regex; + last_regex = addr.regex; + // TODO + break; + } + if ((int64_t)*out_line + addr.offset < 0) + throw ed_error("Line position can't be negative."); + else + *out_line += addr.offset; + if (*out_line > vase.lines()) + throw ed_error("Line position too high."); +} +} // namespace crib::commands::ed diff --git a/src/commands/ed/parsers/command.cc b/src/commands/ed/parsers/command.cc new file mode 100644 index 0000000..4063ba4 --- /dev/null +++ b/src/commands/ed/parsers/command.cc @@ -0,0 +1,115 @@ +#include "commands/ed/ed.h" + +namespace crib::commands::ed { +Command Ed::parse(std::string cmd, bool eof) { + Command command = {}; + if (eof) + return (command.type = Command::Type::Quit, command); + uint64_t i = 0; + auto skip_space = [&] { + while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t')) + ++i; + }; + Command::Address first; + Command::Address second; + parse_address(cmd, i, first); + bool have_range = false; + while (i < cmd.size() && (cmd[i] == ',' || cmd[i] == ';')) { + char sep = cmd[i++]; + if (sep == ';') + resolve_address(first, &line); + command.address_flags = sep == ';' ? Command::SEMICOLON : 0; + if (have_range) + first = second; + parse_address(cmd, i, second); + have_range = true; + } + if (have_range) { + if (first.type != Command::Address::Type::None + && second.type == Command::Address::Type::None) { + second = first; + } else if (first.type == Command::Address::Type::None) { + if (command.address_flags & Command::SEMICOLON) { + first.type = Command::Address::Type::Current; + } else { + first.type = Command::Address::Type::Number; + first.number = 1; + } + if (second.type == Command::Address::Type::None) + second.type = Command::Address::Type::Last; + } + command.address_flags |= Command::RANGE; + command.start = first; + command.end = second; + } else { + command.start = first; + } + if (i >= cmd.size()) { + command.type = Command::Type::None; + return command; + } + switch (cmd[i]) { + case 'q': + command.type = Command::Type::Quit; + return command; + case 'Q': + command.type = Command::Type::ForceQuit; + return command; + case 'p': + command.type = Command::Type::Print; + i++; + break; + case 'n': + command.type = Command::Type::Number; + i++; + break; + case 'P': + command.type = Command::Type::PromptToggle; + i++; + break; + case 'H': + command.type = Command::Type::HelpToggle; + i++; + break; + case 'h': + command.type = Command::Type::Help; + i++; + break; + case 'a': + command.type = Command::Type::Append; + i++; + break; + case 'c': + command.type = Command::Type::Change; + i++; + break; + case 'd': + command.type = Command::Type::Delete; + i++; + break; + case 'w': + command.type = Command::Type::Write; + i++; + if (i >= cmd.size()) + return command; + if (cmd[i] == ' ' || cmd[i] == '\t') + skip_space(); + else + throw ed_error("Invalid command."); + command.argument = cmd.substr(i); + return command; + case '#': + command.type = Command::Type::Dump; + i++; + break; + } + if (i < cmd.size()) { + if (cmd[i] == 'l' || cmd[i] == 'n' || cmd[i] == 'p') + command.suffix = cmd[i++]; + skip_space(); + if (i != cmd.size()) + throw ed_error("Invalid command."); + } + return command; +} +} // namespace crib::commands::ed diff --git a/src/commands/ed/parsers/regex.cc b/src/commands/ed/parsers/regex.cc new file mode 100644 index 0000000..e69de29 diff --git a/src/internal/vase/iterators/petal.cc b/src/internal/vase/iterators/petal.cc index 2eb3eff..0049e44 100644 --- a/src/internal/vase/iterators/petal.cc +++ b/src/internal/vase/iterators/petal.cc @@ -88,16 +88,24 @@ void PetalIterator::seek_line(uint64_t line) { } else { auto *b = (Branch *)curr; uint64_t left_lines = b->left->lines; - if (line <= left_lines) { - if (dir == Direction::Forward) + if (dir == Direction::Forward) { + if (line < left_lines) { stack.push_back(b->right); - curr = b->left; + curr = b->left; + } else { + line -= left_lines; + global_offset += b->left->length; + curr = b->right; + } } else { - line -= left_lines; - if (dir == Direction::Backward) + if (line <= left_lines) { + curr = b->left; + } else { + line -= left_lines; stack.push_back(b->left); - global_offset += b->left->length; - curr = b->right; + global_offset += b->left->length; + curr = b->right; + } } } } diff --git a/src/internal/vase/shard.cc b/src/internal/vase/shard.cc index 4bebc8f..65abb2c 100644 --- a/src/internal/vase/shard.cc +++ b/src/internal/vase/shard.cc @@ -91,7 +91,7 @@ Shard *balance(Shard *node) { Shard *Shard::merge(Shard *a, Shard *b) { if (!a) - return b ? (Shard::retain(b), b) : nullptr; + return (Shard::retain(b), b); if (!b) return (Shard::retain(a), a); @@ -205,7 +205,7 @@ Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) { return node; } -Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o) { +Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o, bool posix_ending) { int dest_fd = o->fd; if (dest_fd == -1) return nullptr; @@ -214,6 +214,19 @@ Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o) { return nullptr; uint64_t total = std::filesystem::file_size(path); + if (posix_ending && total > 0) { + char last; + char s_last; + if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) + return nullptr; + if (pread(src_fd, &s_last, 1, (off_t)(total - 2)) != 1) + return nullptr; + if (last == '\n') { + total--; + if (s_last == '\r') + total--; + } + } std::vector pieces; uint64_t pos = 0; pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX); @@ -257,4 +270,54 @@ Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o) { return pieces[0]; return build(pieces.data(), 0, pieces.size()); } + +void Shard::dump(Shard *node, int depth) { + if (!node) { + std::cout << std::string(depth * 2, ' ') << "\n"; + return; + } + std::string indent(depth * 2, ' '); + std::cout << indent + << "Shard@" << node + << " kind="; + switch (node->kind) { + case Shard::Kind::Branch: + std::cout << "Branch"; + break; + case Shard::Kind::Petal: + std::cout << "Petal"; + break; + } + std::cout + << " height=" << unsigned(node->height) + << " length=" << node->length + << " lines=" << node->lines + << " refs=" << node->refs.load() + << "\n"; + if (node->kind == Shard::Kind::Branch) { + auto *branch = (Branch *)node; + std::cout << indent << " left:\n"; + dump(branch->left, depth + 2); + std::cout << indent << " right:\n"; + dump(branch->right, depth + 2); + } else { + auto *petal = static_cast(node); + constexpr auto clean = [](const std::string &text) { + std::string result = text; + size_t pos = 0; + while ((pos = result.find('\n', pos)) != std::string::npos) { + result.replace(pos, 1, "\\n"); + pos += 2; + } + return result; + }; + std::cout + << indent << " source=" << petal->source + << " pos=" << petal->pos + << " length=" << petal->length + << " lines=" << petal->lines + << " text=\"" << clean(std::string(petal->source->read(petal->pos), petal->length)) + << "\"\n"; + } +} } // namespace crib::internal::vase diff --git a/src/internal/vase/vase.cc b/src/internal/vase/vase.cc index 1eafa63..86d923a 100644 --- a/src/internal/vase/vase.cc +++ b/src/internal/vase/vase.cc @@ -8,12 +8,12 @@ Vase::Vase(std::filesystem::path path, std::filesystem::path swapdir) append = new AppendBuffer(swapdir); original = new OriginalBuffer(swapdir); if (std::filesystem::is_regular_file(path)) - root = Shard::from_file(path, original); + root = Shard::from_file(path, original, posix_ending); else root = nullptr; + history_top = 0; history.push_back(root); Shard::retain(root); - history_top = 0; } Vase::~Vase() { @@ -25,22 +25,38 @@ Vase::~Vase() { } uint64_t Vase::length() { + if (!root) + return 0; return root->length; } +uint64_t Vase::lines() { + if (!root) + return 0; + return root->lines + 1; +} + std::string Vase::to_string() { std::string out; + if (!root) + return out; PetalIterator it(root, Direction::Forward); it.seek_offset(0); const char *data; 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 out; + if (!root) + return out; PetalIterator it(root, Direction::Forward); uint64_t start = offset_of(range.start); it.seek_offset(start); @@ -55,8 +71,8 @@ std::string Vase::to_string(Range range) { return out; } -Iterator Vase::iterate(uint64_t line) { - return Iterator(root, line); +Iterator Vase::iterate(uint64_t line, Direction dir) { + return Iterator(root, line, dir); } bool Vase::undo() { @@ -100,6 +116,8 @@ void Vase::prune_history(uint64_t n) { } bool Vase::save() { + if (!root) + return true; std::ofstream file(path, std::ios::binary); if (!file) return false; @@ -112,6 +130,10 @@ bool Vase::save() { if (!file) return false; } + if (posix_ending) + file.write("\n", 1); + if (!file) + return false; return true; } @@ -137,6 +159,10 @@ void Vase::insert(Point *point, char key) { point->col++; } +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) { while (len) { uint64_t chunk_size = std::min(len, PETAL_SIZE_MAX); @@ -229,12 +255,17 @@ void Vase::erase(Range range) { root = new_root; } +void Vase::replace(Range range, std::string_view str) { + replace(range, str.data(), str.size()); +} + void Vase::replace(Range range, const char *data, uint64_t len) { erase(range); insert(&range.start, data, len); } uint64_t Vase::offset_of(Point point) { + clamp(&point); LineIterator it(root, point.row, Direction::Forward); std::string line; uint64_t offset = 0; @@ -296,6 +327,7 @@ Point Vase::point_of(uint64_t offset) { ptr += next_len; p.col++; } + clamp(&p); return p; } @@ -363,11 +395,19 @@ void Vase::move_clusters(Point *point, uint64_t amount, Direction dir) { } } } + clamp(point); } void Vase::clamp(Point *point) { - if (point->row > root->lines) + if (!root) { + point->row = 0; + point->col = 0; + return; + } + if (point->row > root->lines) { point->row = root->lines; + point->col = UINT64_MAX; + } LineIterator it(root, point->row, Direction::Forward); std::string line; uint64_t clusters = 0;