diff --git a/include/bed.h b/include/bed.h index a3ea53f..32321f0 100644 --- a/include/bed.h +++ b/include/bed.h @@ -18,7 +18,7 @@ struct BEd { internal::functions::Function eof_op; std::array, 26> suffixes; internal::theme::Theme theme; - std::unordered_map languages; + std::unordered_map languages; internal::io::IO &io; internal::vase::AppendStorage append{"/tmp"}; diff --git a/include/internal/buffer/buffer.h b/include/internal/buffer/buffer.h index 1bb96e6..908be8b 100644 --- a/include/internal/buffer/buffer.h +++ b/include/internal/buffer/buffer.h @@ -1,6 +1,5 @@ #pragma once -#include "clip.h" +#include "./types/clip.h" +#include "./types/generic.h" #include "decl.h" -#include "generic.h" -#include "pch.h" diff --git a/include/internal/buffer/decl.h b/include/internal/buffer/decl.h index 8ee6f39..361c278 100644 --- a/include/internal/buffer/decl.h +++ b/include/internal/buffer/decl.h @@ -10,7 +10,7 @@ namespace bed::internal::buffer { struct Buffer { enum struct Kind : uint8_t { Generic, - Null, + History, Clip, Cancel, Shell, @@ -26,7 +26,7 @@ struct Buffer { std::string name; explicit Buffer(std::string name, Kind kind) - : kind(kind), state(Unmodified), name(name) {}; + : kind(kind), state(Unmodified), name(std::move(name)) {}; virtual ~Buffer() = default; virtual bool waste() = 0; diff --git a/include/internal/buffer/clip.h b/include/internal/buffer/types/clip.h similarity index 97% rename from include/internal/buffer/clip.h rename to include/internal/buffer/types/clip.h index 88ed46e..cba4c3c 100644 --- a/include/internal/buffer/clip.h +++ b/include/internal/buffer/types/clip.h @@ -1,7 +1,6 @@ #pragma once -#include "decl.h" -#include "pch.h" +#include "../decl.h" namespace bed::internal::buffer { struct ClipBuffer : Buffer { diff --git a/include/internal/buffer/generic.h b/include/internal/buffer/types/generic.h similarity index 50% rename from include/internal/buffer/generic.h rename to include/internal/buffer/types/generic.h index 78e0c75..7a1a3f9 100644 --- a/include/internal/buffer/generic.h +++ b/include/internal/buffer/types/generic.h @@ -1,26 +1,41 @@ #pragma once -#include "decl.h" -#include "pch.h" +#include "../decl.h" +#include "history.h" +#include "shard.h" namespace bed::internal::buffer { -struct GenericBuffer : Buffer { - vase::Shard *root; +struct HistoryItem { + syntax::ParserSnapshot parse_state; + vase::Shard *text; + std::chrono::system_clock::time_point timestamp; + std::string summary; +}; + +struct GenericBuffer : ShardBuffer { + uint64_t base_version{0}; + std::chrono::system_clock::time_point timestamp{ + std::chrono::system_clock::now() + }; + std::string action{"created"}; std::filesystem::path save_path{}; - std::string language{}; - std::optional parser{}; + std::vector undo_stack; + std::vector redo_stack; GenericBuffer(std::string name) - : Buffer(name, Kind::Generic), root(nullptr) {}; + : ShardBuffer(name, nullptr, nullptr, Kind::Generic) {} ~GenericBuffer(); + void list_history(BEd &ctx); + HistoryBuffer *get_history(uint64_t version); + void snapshot(std::string action); + bool undo(BEd &ctx); + bool redo(BEd &ctx); + void prune(int = 0); bool waste() override; - uint64_t lines() override; - uint64_t bytes() override; void load(BEd &ctx, vase::Shard *text) override; void set_filename(std::filesystem::path path) override; std::filesystem::path filename() override; - vase::Shard *copy(uint64_t start_line, uint64_t end_line) override; void substitute( BEd &ctx, uint64_t start_line, uint64_t end_line, std::string ®ex, std::string &replacement, std::string &options @@ -29,12 +44,5 @@ struct GenericBuffer : Buffer { void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override; void append(BEd &ctx, vase::Shard *text, uint64_t line) override; void replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint64_t end_line) override; - void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override; - void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override; - void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override; - uint64_t next_closing(uint64_t start) override; - uint64_t prev_closing(uint64_t start) override; - uint64_t find_next(std::string_view pattern, uint64_t start) override; - uint64_t find_prev(std::string_view pattern, uint64_t start) override; }; } // namespace bed::internal::buffer diff --git a/include/internal/buffer/types/history.h b/include/internal/buffer/types/history.h new file mode 100644 index 0000000..dc0f103 --- /dev/null +++ b/include/internal/buffer/types/history.h @@ -0,0 +1,39 @@ +#pragma once + +#include "../decl.h" +#include "shard.h" + +namespace bed::internal::buffer { +struct HistoryBuffer : ShardBuffer { + HistoryBuffer( + std::string name, vase::Shard *root, + const syntax::ParserSnapshot &snapshot + ) : ShardBuffer(std::move(name), root, snapshot, Kind::History) {} + + bool waste() override { + return true; + } + void load(BEd &, vase::Shard *) override { + throw ed_error("History buffers are read-only."); + } + void set_filename(std::filesystem::path) override {} + std::filesystem::path filename() override { + return {}; + } + void substitute(BEd &, uint64_t, uint64_t, std::string &, std::string &, std::string &) override { + throw ed_error("History buffers are read-only."); + } + void join(BEd &, uint64_t, uint64_t) override { + throw ed_error("History buffers are read-only."); + } + void remove(BEd &, uint64_t, uint64_t) override { + throw ed_error("History buffers are read-only."); + } + void append(BEd &, vase::Shard *, uint64_t) override { + throw ed_error("History buffers are read-only."); + } + void replace(BEd &, vase::Shard *, uint64_t, uint64_t) override { + throw ed_error("History buffers are read-only."); + } +}; +} // namespace bed::internal::buffer diff --git a/include/internal/buffer/types/shard.h b/include/internal/buffer/types/shard.h new file mode 100644 index 0000000..55a9b89 --- /dev/null +++ b/include/internal/buffer/types/shard.h @@ -0,0 +1,42 @@ +#pragma once + +#include "../decl.h" + +namespace bed::internal::buffer { +struct ShardBuffer : Buffer { + vase::Shard *root; + syntax::ParserSnapshot parse{}; + + ShardBuffer(std::string name, vase::Shard *root, syntax::Language *language, Kind kind) + : Buffer(std::move(name), kind), root(root) { + vase::Shard::retain(root); + if (language) + parse = syntax::make_parser(root, lines(), language); + } + ShardBuffer( + std::string name, + vase::Shard *root, + const syntax::ParserSnapshot &snapshot, + Kind kind + ) : Buffer(std::move(name), kind), + root(root), + parse(syntax::retain(snapshot)) { + vase::Shard::retain(root); + } + ~ShardBuffer() { + vase::Shard::release(root); + syntax::release(parse); + } + + uint64_t lines() override; + uint64_t bytes() override; + vase::Shard *copy(uint64_t start_line, uint64_t end_line) override; + void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override; + void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override; + void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override; + uint64_t next_closing(uint64_t start) override; + uint64_t prev_closing(uint64_t start) override; + uint64_t find_next(std::string_view pattern, uint64_t start) override; + uint64_t find_prev(std::string_view pattern, uint64_t start) override; +}; +} // namespace bed::internal::buffer diff --git a/include/internal/syntax/decl.h b/include/internal/syntax/decl.h index 5e9b874..9169bce 100644 --- a/include/internal/syntax/decl.h +++ b/include/internal/syntax/decl.h @@ -1,6 +1,7 @@ #pragma once #include "internal/trie/trie.h" +#include "internal/vase/vase.h" #include "pch.h" namespace bed::internal::syntax { @@ -75,7 +76,11 @@ struct ParseEvent { struct Language { std::function none_state; - std::function *, std::vector *)> parse; + std::function *, std::vector * + )> + parse; std::function copy; std::function equal; std::function destroy; @@ -85,35 +90,125 @@ struct ParseState { static constexpr uint64_t BRANCH_BIT = 1ull << 63; static constexpr uint64_t LINES_MASK = ~BRANCH_BIT; uint64_t header; + uint16_t height; + std::atomic_uint16_t refs; bool is_branch() const { return header & BRANCH_BIT; } uint64_t lines() const { return header & LINES_MASK; } + explicit ParseState(bool branch, uint64_t lines, uint16_t height) + : height(height), refs(1) { + header = lines; + header |= branch * BRANCH_BIT; + }; + static void retain(ParseState *node); + static void release(Language &lang, ParseState *node); + static ParseState *build(Language &lang, ParseState **pieces, uint64_t lo, uint64_t hi); + static ParseState *splice( + Language &lang, ParseState *node, vase::Shard *vase, + uint64_t line, uint64_t original, uint64_t final + ); + static ParseState *concat(Language &lang, ParseState *a, ParseState *b); }; struct ParseStateBranch : ParseState { ParseState *left; ParseState *right; + ParseStateBranch(ParseState *l, ParseState *r) + : ParseState( + true, l->lines() + r->lines(), + 1 + std::max(l->height, r->height) + ), + left(l), right(r) { + retain(l); + retain(r); + } }; struct ParseStateLeaf : ParseState { - void *state; - uint32_t n; - uint32_t cap; - uint16_t *blocks; + static constexpr uint64_t MAX_CHUNK = 512; + uint32_t n{0}; static constexpr uint16_t IS_CLOSING = 0x8000; static constexpr uint16_t LINE_MASK = 0x7fff; + uint16_t *blocks{nullptr}; + void *state; + ParseStateLeaf(void *state, uint64_t lines, uint32_t n, uint16_t *blocks_) + : ParseState(false, lines, 1), n(n), state(state) { + blocks = (uint16_t *)malloc(sizeof(uint16_t) * n); + memcpy(blocks, blocks_, sizeof(uint16_t) * n); + }; +}; + +struct ParsePieceBuilder { + Language ⟨ + std::vector pieces; + std::vector blocks; + void *piece_state{nullptr}; + uint64_t chunk_start{0}; + uint64_t chunk_lines{0}; + ParsePieceBuilder(Language &lang, uint64_t first_line) + : lang(lang), chunk_start(first_line) {} + void add( + void *state, + uint64_t line, + const std::vector &events + ) { + if (chunk_lines == 0) { + chunk_start = line; + piece_state = lang.copy(state); + } + for (const auto &ev : events) { + blocks.push_back( + (ev.closing ? ParseStateLeaf::IS_CLOSING : 0) + | (line - chunk_start) + ); + } + ++chunk_lines; + if (chunk_lines == ParseStateLeaf::MAX_CHUNK) + flush(); + } + void flush() { + if (chunk_lines == 0) + return; + pieces.push_back(new ParseStateLeaf( + piece_state, + chunk_lines, + blocks.size(), + blocks.data() + )); + piece_state = nullptr; + blocks.clear(); + chunk_lines = 0; + } + ParseState *finish() { + flush(); + if (pieces.empty()) + return nullptr; + ParseState *root = ParseState::build(lang, pieces.data(), 0, pieces.size()); + pieces.clear(); + return root; + } }; struct TreeCursor { + Language ⟨ + ParseState *root; ParseStateLeaf *leaf = nullptr; ParseStateBranch *stack[64]; uint8_t depth = 0; bool went_left[64]; - TreeCursor(ParseState *root, uint64_t target_line, uint64_t *relative); + TreeCursor( + Language &lang, ParseState *root, + uint64_t target_line, uint64_t *relative + ); + ~TreeCursor(); + TreeCursor(const TreeCursor &) = delete; + TreeCursor &operator=(const TreeCursor &) = delete; void next(); void prev(); + ParseState *prefix(); + ParseState *suffix(); }; } // namespace bed::internal::syntax diff --git a/include/internal/syntax/parser.h b/include/internal/syntax/parser.h index 345e714..4d117e5 100644 --- a/include/internal/syntax/parser.h +++ b/include/internal/syntax/parser.h @@ -1,57 +1,54 @@ #pragma once #include "decl.h" -#include "internal/vase/vase.h" #include "pch.h" namespace bed::internal::syntax { -struct Parser { - static constexpr uint64_t MAX_CHUNK = 512; +struct ParserSnapshot { + ParseState *root = nullptr; + Language *lang = nullptr; +}; - ParseState *root; - Language lang; - bool in_edit = false; +ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang); +ParserSnapshot retain(const ParserSnapshot &snap); +void release(ParserSnapshot &snap); + +uint64_t next_closing(const ParserSnapshot &snap, uint64_t line); +uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line); + +struct Iterator { + ParserSnapshot snap; + std::optional it; + void *state; + uint64_t at; + std::vector tokens; + std::vector events; + Iterator(uint64_t, ParserSnapshot, vase::Shard *); + ~Iterator(); + Iterator(const Iterator &) = delete; + Iterator &operator=(const Iterator &) = delete; + Iterator(Iterator &&other); + Iterator &operator=(Iterator &&other); + void next(); +}; + +std::optional get_hl(const ParserSnapshot &snap, vase::Shard *vase, uint64_t target); + +void insert(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count); +void erase(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count); + +struct Edit { + ParserSnapshot *target; bool dirty = false; uint64_t dirty_start = 0; uint64_t dirty_end = 0; + int64_t edit_delta = 0; - Parser(vase::Shard *, uint64_t, Language); - ~Parser(); - Parser(const Parser &) = delete; - Parser &operator=(const Parser &) = delete; + explicit Edit(ParserSnapshot &snap) : target(&snap) {} - 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); - - void begin_edit(); - void erase(uint64_t start, uint64_t count); - void insert(uint64_t start, uint64_t count); - void end_edit(vase::Shard *vase); void mark_dirty(uint64_t start, uint64_t end); - - uint64_t next_closing(uint64_t line); - uint64_t prev_opening(uint64_t line); - - std::pair split_tree(ParseState *node, uint64_t line); - ParseState *join_tree(ParseState *a, ParseState *b); - - struct Iterator { - Parser *p; - std::optional it; - void *state; - uint64_t at; - std::vector tokens; - std::vector events; - Iterator(uint64_t, Parser *, vase::Shard *); - ~Iterator(); - Iterator(const Iterator &) = delete; - Iterator &operator=(const Iterator &) = delete; - Iterator(Iterator &&other); - Iterator &operator=(Iterator &&other); - void next(); - }; - std::optional get_hl(vase::Shard *, uint64_t); + void insert(uint64_t start, uint64_t count); + void erase(uint64_t start, uint64_t count); + void commit(vase::Shard *vase); }; } // namespace bed::internal::syntax diff --git a/include/internal/vase/shard.h b/include/internal/vase/shard.h index d55debd..9b4ec65 100644 --- a/include/internal/vase/shard.h +++ b/include/internal/vase/shard.h @@ -12,13 +12,13 @@ struct Shard { Petal } kind; - uint8_t height; + uint16_t height; std::atomic_uint32_t refs; uint64_t length; uint64_t lines; - Shard(Kind kind, uint64_t length, uint64_t lines, uint8_t height) + Shard(Kind kind, uint64_t length, uint64_t lines, uint16_t height) : kind(kind), height(height), refs(1), length(length), lines(lines) {}; static void retain(Shard *n); @@ -62,5 +62,4 @@ struct Petal : Shard { source->retain(); }; }; - } // namespace bed::internal::vase diff --git a/src/bed/run.cc b/src/bed/run.cc index 2907e52..eb7fac0 100644 --- a/src/bed/run.cc +++ b/src/bed/run.cc @@ -7,6 +7,7 @@ BEd::BEd(std::vector args, internal::io::IO &io) internal::functions::Function::register_posix(*this); internal::functions::Function::register_extented(*this); internal::functions::Suffix::register_suffixes(*this); + languages["ruby"] = new internal::syntax::Language(internal::syntax::ruby::lang_ruby()); std::string prompt_ = ""; std::string file = ""; bool suppress = false; @@ -47,6 +48,8 @@ BEd::BEd(std::vector args, internal::io::IO &io) BEd::~BEd() { for (auto &[_, buffer] : buffers) delete buffer; + for (auto &[_, lang] : languages) + delete lang; } void BEd::run() { @@ -162,9 +165,38 @@ void BEd::handle(std::string_view cmd, bool eof) { internal::buffer::Buffer &BEd::buffer(const std::string &name) { if (name.empty()) throw ed_error("can't have empty buffer name"); - auto it = buffers.find(name); - if (it != buffers.end()) - return *it->second; + { + auto it = buffers.find(name); + if (it != buffers.end()) + return *it->second; + } + constexpr std::string_view prefix = "history/"; + if (name.starts_with(prefix)) { + std::string_view path{name}; + path.remove_prefix(prefix.size()); + auto slash = path.rfind('/'); + if (slash == std::string_view::npos || slash == 0 || slash == path.size() - 1) + throw ed_error("invalid history"); + std::string bufname(path.substr(0, slash)); + auto version_str = path.substr(slash + 1); + std::size_t version; + try { + version = std::stoull(std::string(version_str)); + } catch (...) { + throw ed_error("invalid history version"); + } + auto it = buffers.find(bufname); + if (it != buffers.end()) { + auto &buf_ = *it->second; + if (buf_.kind != internal::buffer::Buffer::Kind::Generic) + throw ed_error("Only normal buffers can have history."); + auto &buf = *(internal::buffer::GenericBuffer *)&buf_; + auto *history_buf = buf.get_history(version); + buffers.emplace(name, history_buf); + return *history_buf; + } + throw ed_error("Buffer has no history."); + } auto *buf = new internal::buffer::GenericBuffer(name); buffers.emplace(name, buf); return *buf; diff --git a/src/internal/buffer/generic.cc b/src/internal/buffer/generic.cc index cc4390f..6899679 100644 --- a/src/internal/buffer/generic.cc +++ b/src/internal/buffer/generic.cc @@ -3,7 +3,192 @@ namespace bed::internal::buffer { GenericBuffer::~GenericBuffer() { + for (auto &item : undo_stack) { + syntax::release(item.parse_state); + vase::Shard::release(item.text); + } + for (auto &item : redo_stack) { + syntax::release(item.parse_state); + vase::Shard::release(item.text); + } +} + +void GenericBuffer::list_history(BEd &ctx) { + uint64_t current = base_version + undo_stack.size(); + for (size_t i = 0; i < undo_stack.size(); ++i) { + auto &item = undo_stack[i]; + uint64_t version = base_version + i; + auto time = std::chrono::system_clock::to_time_t(item.timestamp); + std::tm tm = *std::localtime(&time); + ctx.io.write_line( + std::format( + " {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}", + version, + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + item.summary + ) + ); + } + { + auto time = std::chrono::system_clock::to_time_t(timestamp); + std::tm tm = *std::localtime(&time); + ctx.io.write_line( + std::format( + "X {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}", + current, + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + action + ) + ); + } + for (size_t i = 0; i < redo_stack.size(); ++i) { + auto &item = redo_stack[redo_stack.size() - 1 - i]; + uint64_t version = current + i + 1; + auto time = std::chrono::system_clock::to_time_t(item.timestamp); + std::tm tm = *std::localtime(&time); + ctx.io.write_line( + std::format( + " {} {:04}-{:02}-{:02} {:02}:{:02}:{:02}", + version, + tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + item.summary + ) + ); + } +} + +HistoryBuffer *GenericBuffer::get_history(uint64_t version) { + uint64_t current = base_version + undo_stack.size(); + if (version < base_version) + throw ed_error("History version has been pruned."); + if (version < current) { + auto &item = undo_stack[version - base_version]; + return new HistoryBuffer(name, item.text, item.parse_state); + } + if (version == current) + return new HistoryBuffer(name, root, parse); + uint64_t redo_offset = version - current - 1; + if (redo_offset >= redo_stack.size()) + throw ed_error("No such history version."); + auto &item = redo_stack[redo_stack.size() - 1 - redo_offset]; + return new HistoryBuffer(name, item.text, item.parse_state); +} + +void GenericBuffer::snapshot(std::string action_) { + vase::Shard::retain(root); + undo_stack.push_back( + HistoryItem{ + syntax::retain(parse), + root, + timestamp, + action + } + ); + for (auto &item : redo_stack) { + syntax::release(item.parse_state); + vase::Shard::release(item.text); + } + redo_stack.clear(); + timestamp = std::chrono::system_clock::now(); + action = action_; +} + +bool GenericBuffer::undo(BEd &ctx) { + if (undo_stack.empty()) + return false; + HistoryItem prev = undo_stack.back(); + undo_stack.pop_back(); + vase::Shard::retain(root); + redo_stack.push_back( + HistoryItem{ + syntax::retain(parse), + root, + timestamp, + action + } + ); vase::Shard::release(root); + root = prev.text; + syntax::release(parse); + parse = prev.parse_state; + state = Modified; + if (!root) { + ctx.prev().buffername = name; + ctx.prev().start = 0; + ctx.prev().end = 0; + } else { + ctx.prev().buffername = name; + ctx.prev().start = 1; + ctx.prev().end = root->lines + 1; + } + return true; +} + +bool GenericBuffer::redo(BEd &ctx) { + if (redo_stack.empty()) + return false; + HistoryItem next = redo_stack.back(); + redo_stack.pop_back(); + vase::Shard::retain(root); + undo_stack.push_back( + HistoryItem{ + syntax::retain(parse), + root, + timestamp, + action + } + ); + vase::Shard::release(root); + root = next.text; + syntax::release(parse); + parse = next.parse_state; + state = Modified; + if (!root) { + ctx.prev().buffername = name; + ctx.prev().start = 0; + ctx.prev().end = 0; + } else { + ctx.prev().buffername = name; + ctx.prev().start = 1; + ctx.prev().end = root->lines + 1; + } + return true; +} + +void GenericBuffer::prune(int keep) { + size_t drop = + undo_stack.size() > (size_t)keep + ? undo_stack.size() - keep + : 0; + for (size_t i = 0; i < drop; ++i) { + syntax::release(undo_stack[i].parse_state); + vase::Shard::release(undo_stack[i].text); + } + undo_stack.erase( + undo_stack.begin(), + undo_stack.begin() + drop + ); + base_version += drop; + for (auto &item : redo_stack) { + syntax::release(item.parse_state); + vase::Shard::release(item.text); + } + redo_stack.clear(); } bool GenericBuffer::waste() { @@ -11,19 +196,8 @@ bool GenericBuffer::waste() { && root == nullptr; } -uint64_t GenericBuffer::lines() { - if (root) - return root->lines + 1; - return 0; -} - -uint64_t GenericBuffer::bytes() { - if (root) - return root->length + 1; - return 0; -} - void GenericBuffer::load(BEd &ctx, vase::Shard *text) { + snapshot("Load file."); if (lines()) ctx.marks.erase(name, 1, lines()); vase::Shard::release(root); @@ -39,7 +213,8 @@ void GenericBuffer::load(BEd &ctx, vase::Shard *text) { ctx.prev().start = 1; ctx.prev().end = text->lines + 1; } - parser.emplace(root, lines(), syntax::ruby::lang_ruby()); + syntax::release(parse); + parse = syntax::make_parser(root, lines(), ctx.languages["ruby"]); } void GenericBuffer::set_filename(std::filesystem::path path) { @@ -53,24 +228,26 @@ std::filesystem::path GenericBuffer::filename() { void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) { if (!text) return; + snapshot(std::format("Insert {} lines after line {}", text->lines + 1, line)); ctx.prev().buffername = name; ctx.prev().start = line + 1; ctx.prev().end = line + text->lines + 1; root = vase::insert(&ctx.append, root, text, line); ctx.marks.insert(name, line, text->lines + 1); - if (parser) - parser->insert(root, line, text->lines + 1); + if (parse.lang) + syntax::insert(parse, root, line, text->lines + 1); state = Modified; } void GenericBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) { + snapshot(std::format("Remove lines {} to {}", start_line, end_line)); root = vase::erase(root, start_line, end_line); ctx.prev().buffername = name; ctx.prev().start = std::min(start_line, lines()); ctx.prev().end = std::min(start_line, lines()); ctx.marks.erase(name, start_line, end_line - start_line + 1); - if (parser) - parser->erase(root, start_line, end_line - start_line + 1); + if (parse.lang) + syntax::erase(parse, root, start_line, end_line - start_line + 1); state = Modified; } @@ -79,17 +256,18 @@ void GenericBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, ui remove(ctx, start_line, end_line); return; } + snapshot(std::format("Replace lines {} to {} with {} lines", start_line, end_line, text->lines)); ctx.prev().buffername = name; ctx.prev().start = start_line; ctx.prev().end = start_line + text->lines; uint64_t new_count = text->lines + 1; uint64_t old_count = end_line - start_line + 1; root = vase::replace(root, text, start_line, end_line); - if (parser) { - parser->begin_edit(); - parser->erase(start_line, old_count); - parser->insert(start_line, new_count); - parser->end_edit(root); + if (parse.lang) { + syntax::Edit edit(parse); + edit.erase(start_line, old_count); + edit.insert(start_line, new_count); + edit.commit(root); } if (new_count > old_count) { uint64_t diff = new_count - old_count; @@ -102,13 +280,14 @@ void GenericBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, ui } void GenericBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) { + snapshot(std::format("Join lines {} to {}", start_line, end_line)); root = vase::join(root, start_line, end_line); 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); + if (parse.lang) + syntax::erase(parse, root, start_line, end_line - start_line); state = Modified; } @@ -116,11 +295,13 @@ void GenericBuffer::substitute( BEd &ctx, uint64_t start_line, uint64_t end_line, std::string ®ex, std::string &replacement, std::string &options ) { + snapshot(std::format("Substitute /{}/ with /{}/ in lines {} to {}", regex, replacement, start_line, end_line)); ctx.prev().buffername = name; ctx.prev().start = start_line; ctx.prev().end = end_line; - if (parser) - parser->begin_edit(); + std::optional edit; + if (parse.lang) + edit.emplace(parse); root = vase::substitute( &ctx.append, root, @@ -132,168 +313,19 @@ void GenericBuffer::substitute( [&](uint64_t line, uint64_t old_lines, uint64_t new_lines) { if (old_lines) { ctx.marks.erase(name, line, old_lines); - if (parser) - parser->erase(line, old_lines); + if (edit) + edit->erase(line, old_lines); } if (new_lines) { - ctx.marks.insert(name, line, line + new_lines); - if (parser) - parser->insert(line, new_lines); + ctx.marks.insert(name, line, new_lines); + if (edit) + edit->insert(line, new_lines); } } ); - if (parser) - parser->end_edit(root); + if (edit) + edit->commit(root); ctx.prev().buffername = name; state = Modified; } - -vase::Shard *GenericBuffer::copy(uint64_t start_line, uint64_t end_line) { - return vase::copy(root, start_line, end_line); -} - -uint64_t GenericBuffer::find_next(std::string_view pattern, uint64_t start) { - return vase::find_next(root, pattern, start); -} - -uint64_t GenericBuffer::find_prev(std::string_view pattern, uint64_t start) { - return vase::find_prev(root, pattern, start); -} - -uint64_t GenericBuffer::next_closing(uint64_t start) { - if (parser.has_value()) { - uint64_t closing = parser->next_closing(start - 1); - if (closing == UINT64_MAX) - return lines(); - return closing + 1; - } else { - start += 10; - if (start > lines()) - return lines(); - return start; - } -} - -uint64_t GenericBuffer::prev_closing(uint64_t start) { - if (parser.has_value()) { - return parser->prev_opening(start - 1) + 1; - } else { - if (start > 10) - return start - 10; - return 0; - } -} - -inline void apply(io::IO &io, const Highlight &hl) { - io.write("\x1b[0m"); - const uint8_t r = (hl.fg >> 16) & 0xff; - const uint8_t g = (hl.fg >> 8) & 0xff; - const uint8_t b = hl.fg & 0xff; - io.write(std::format("\x1b[38;2;{};{};{}m", r, g, b)); - if (hl.bg != 0) { - const uint8_t br = (hl.bg >> 16) & 0xff; - const uint8_t bg = (hl.bg >> 8) & 0xff; - const uint8_t bb = hl.bg & 0xff; - io.write(std::format("\x1b[48;2;{};{};{}m", br, bg, bb)); - } - if (hl.flags & Highlight::Bold) - io.write("\x1b[1m"); - if (hl.flags & Highlight::Italic) - io.write("\x1b[3m"); - if (hl.flags & Highlight::Underline) - io.write("\x1b[4m"); - if (hl.flags & Highlight::Strikethrough) - io.write("\x1b[9m"); -} - -inline void reset(io::IO &io) { - io.write("\x1b[0m"); -} - -void GenericBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) { - ctx.prev().buffername = name; - ctx.prev().start = start_line; - ctx.prev().end = end_line; - if (parser) { - auto it_o = parser->get_hl(root, start_line - 1); - auto &it = *it_o; - while (start_line <= end_line) { - it.next(); - const std::string &line = it.it->line; - const auto &tokens = it.tokens; - uint32_t cursor = 0; - for (const auto &token : tokens) { - const uint32_t start = token.start; - const uint32_t end = token.end; - if (start > line.size() || end > line.size()) - break; - if (cursor < start) - ctx.io.write(line.data() + cursor, start - cursor); - const auto highlight = ctx.theme.get(token); - apply(ctx.io, highlight); - ctx.io.write(line.data() + start, end - start); - reset(ctx.io); - cursor = end; - } - if (cursor < line.size()) - ctx.io.write(line.data() + cursor, line.size() - cursor); - ctx.io.write_line(""); - ++start_line; - } - } else { - vase::Iterator it(root, start_line - 1, Direction::Forward); - while (it.next() && start_line++ <= end_line) - ctx.io.write_line(it.line); - } -} - -void GenericBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t 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(root, start_line - 1); - auto &it = *it_o; - while (start_line <= end_line) { - it.next(); - ctx.io.write(std::format("{:>{}}\t", start_line, width)); - const std::string &line = it.it->line; - const auto &tokens = it.tokens; - uint32_t cursor = 0; - for (const auto &token : tokens) { - const uint32_t start = token.start; - const uint32_t end = token.end; - if (start > line.size() || end > line.size()) - break; - if (cursor < start) - ctx.io.write(line.data() + cursor, start - cursor); - const auto highlight = ctx.theme.get(token); - apply(ctx.io, highlight); - ctx.io.write(line.data() + start, end - start); - reset(ctx.io); - cursor = end; - } - if (cursor < line.size()) - ctx.io.write(line.data() + cursor, line.size() - cursor); - ctx.io.write_line(""); - ++start_line; - } - } else { - vase::Iterator it(root, start_line - 1, Direction::Forward); - while (it.next() && start_line <= end_line) - ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line)); - } -} - -void GenericBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) { - ctx.prev().buffername = name; - ctx.prev().start = start_line; - ctx.prev().end = end_line; - vase::Iterator it(root, start_line - 1, Direction::Forward); - while (it.next() && start_line++ <= end_line) - ctx.io.write_line(list_string(it.line)); -} } // namespace bed::internal::buffer diff --git a/src/internal/buffer/shard.cc b/src/internal/buffer/shard.cc new file mode 100644 index 0000000..0a2d0f8 --- /dev/null +++ b/src/internal/buffer/shard.cc @@ -0,0 +1,171 @@ +#include "bed.h" +#include "internal/buffer/buffer.h" + +namespace bed::internal::buffer { +uint64_t ShardBuffer::lines() { + if (root) + return root->lines + 1; + return 0; +} + +uint64_t ShardBuffer::bytes() { + if (root) + return root->length + 1; + return 0; +} + +vase::Shard *ShardBuffer::copy(uint64_t start_line, uint64_t end_line) { + return vase::copy(root, start_line, end_line); +} + +uint64_t ShardBuffer::find_next(std::string_view pattern, uint64_t start) { + return vase::find_next(root, pattern, start); +} + +uint64_t ShardBuffer::find_prev(std::string_view pattern, uint64_t start) { + return vase::find_prev(root, pattern, start); +} + +uint64_t ShardBuffer::next_closing(uint64_t start) { + if (parse.lang) { + uint64_t closing = syntax::next_closing(parse, start - 1); + if (closing == UINT64_MAX) + return lines(); + return closing + 1; + } else { + start += 10; + if (start > lines()) + return lines(); + return start; + } +} + +uint64_t ShardBuffer::prev_closing(uint64_t start) { + if (parse.lang) { + return syntax::prev_opening(parse, start - 1) + 1; + } else { + if (start > 10) + return start - 10; + return 0; + } +} + +inline void apply(io::IO &io, const Highlight &hl) { + io.write("\x1b[0m"); + const uint8_t r = (hl.fg >> 16) & 0xff; + const uint8_t g = (hl.fg >> 8) & 0xff; + const uint8_t b = hl.fg & 0xff; + io.write(std::format("\x1b[38;2;{};{};{}m", r, g, b)); + if (hl.bg != 0) { + const uint8_t br = (hl.bg >> 16) & 0xff; + const uint8_t bg = (hl.bg >> 8) & 0xff; + const uint8_t bb = hl.bg & 0xff; + io.write(std::format("\x1b[48;2;{};{};{}m", br, bg, bb)); + } + if (hl.flags & Highlight::Bold) + io.write("\x1b[1m"); + if (hl.flags & Highlight::Italic) + io.write("\x1b[3m"); + if (hl.flags & Highlight::Underline) + io.write("\x1b[4m"); + if (hl.flags & Highlight::Strikethrough) + io.write("\x1b[9m"); +} + +inline void reset(io::IO &io) { + io.write("\x1b[0m"); +} + +void ShardBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) { + ctx.prev().buffername = name; + ctx.prev().start = start_line; + ctx.prev().end = end_line; + if (parse.lang) { + auto it_o = syntax::get_hl(parse, root, start_line - 1); + if (!it_o) + goto h; + auto &it = *it_o; + while (start_line <= end_line) { + it.next(); + const std::string &line = it.it->line; + const auto &tokens = it.tokens; + uint32_t cursor = 0; + for (const auto &token : tokens) { + const uint32_t start = token.start; + const uint32_t end = token.end; + if (start > line.size() || end > line.size()) + break; + if (cursor < start) + ctx.io.write(line.data() + cursor, start - cursor); + const auto highlight = ctx.theme.get(token); + apply(ctx.io, highlight); + ctx.io.write(line.data() + start, end - start); + reset(ctx.io); + cursor = end; + } + if (cursor < line.size()) + ctx.io.write(line.data() + cursor, line.size() - cursor); + ctx.io.write_line(""); + ++start_line; + } + } else { + h: + vase::Iterator it(root, start_line - 1, Direction::Forward); + while (it.next() && start_line++ <= end_line) + ctx.io.write_line(it.line); + } +} + +void ShardBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t 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 (parse.lang) { + std::optional it_o = syntax::get_hl(parse, root, start_line - 1); + if (!it_o) + goto h; + auto &it = *it_o; + while (start_line <= end_line) { + it.next(); + ctx.io.write(std::format("{:>{}}\t", start_line, width)); + const std::string &line = it.it->line; + const auto &tokens = it.tokens; + uint32_t cursor = 0; + for (const auto &token : tokens) { + const uint32_t start = token.start; + const uint32_t end = token.end; + if (start > line.size() || end > line.size()) + break; + if (cursor < start) + ctx.io.write(line.data() + cursor, start - cursor); + const auto highlight = ctx.theme.get(token); + apply(ctx.io, highlight); + ctx.io.write(line.data() + start, end - start); + reset(ctx.io); + cursor = end; + } + if (cursor < line.size()) + ctx.io.write(line.data() + cursor, line.size() - cursor); + ctx.io.write_line(""); + ++start_line; + } + } else { + h: + vase::Iterator it(root, start_line - 1, Direction::Forward); + while (it.next() && start_line <= end_line) + ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line)); + } +} + +void ShardBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) { + ctx.prev().buffername = name; + ctx.prev().start = start_line; + ctx.prev().end = end_line; + vase::Iterator it(root, start_line - 1, Direction::Forward); + while (it.next() && start_line++ <= end_line) + ctx.io.write_line(list_string(it.line)); +} +} // namespace bed::internal::buffer diff --git a/src/internal/buffer/special/clip.cc b/src/internal/buffer/special/clip.cc index e3189a3..ea60171 100644 --- a/src/internal/buffer/special/clip.cc +++ b/src/internal/buffer/special/clip.cc @@ -128,7 +128,7 @@ void ClipBuffer::substitute( if (old_lines) ctx.marks.erase(name, line, old_lines); if (new_lines) - ctx.marks.insert(name, line, line + new_lines - 1); + ctx.marks.insert(name, line, new_lines - 1); } ); clip_write(s); diff --git a/src/internal/functions/extended.cc b/src/internal/functions/extended.cc index ec91b4a..6d1939b 100644 --- a/src/internal/functions/extended.cc +++ b/src/internal/functions/extended.cc @@ -97,5 +97,58 @@ void Function::register_extented(BEd &ctx) { }, } ); + ctx.functions.insert( + "U", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Redo last undo.", + .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_); + auto &buf_ = ctx.buffer(addr); + if (buf_.kind != buffer::Buffer::Kind::Generic) + throw ed_error("Can't redo buffer"); + auto &buf = *(buffer::GenericBuffer *)&buf_; + if (!buf.redo(ctx)) + throw ed_error("Can't redo buffer."); + } + } + ); + ctx.functions.insert( + "history-list", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "List history versions.", + .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_); + auto &buf_ = ctx.buffer(addr); + if (buf_.kind != buffer::Buffer::Kind::Generic) + throw ed_error("Can't redo buffer"); + auto &buf = *(buffer::GenericBuffer *)&buf_; + buf.list_history(ctx); + } + } + ); } } // namespace bed::internal::functions diff --git a/src/internal/functions/posix.cc b/src/internal/functions/posix.cc index 5411dc0..03cd760 100644 --- a/src/internal/functions/posix.cc +++ b/src/internal/functions/posix.cc @@ -213,10 +213,7 @@ void Function::register_posix(BEd &ctx) { std::vector * ) { auto &addr = std::get(addr_); - auto &buf_ = ctx.buffer(addr); - if (buf_.kind != buffer::Buffer::Kind::Generic) - return; - auto &buf = *(buffer::GenericBuffer *)&buf_; + auto &buf = ctx.buffer(addr); if (std::holds_alternative(arg)) buf.set_filename(std::get(arg)); else if (std::holds_alternative(arg)) @@ -649,6 +646,33 @@ void Function::register_posix(BEd &ctx) { }, } ); + ctx.functions.insert( + "u", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::None, + .input_mode = Function::InputMode::None, + .desc = "Undo last modification to buffer.", + .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_); + auto &buf_ = ctx.buffer(addr); + if (buf_.kind != buffer::Buffer::Kind::Generic) + throw ed_error("Can't undo buffer"); + auto &buf = *(buffer::GenericBuffer *)&buf_; + if (!buf.undo(ctx)) + throw ed_error("Can't undo buffer."); + } + } + ); ctx.functions.insert( "w", Function{ diff --git a/src/internal/syntax/iterator.cc b/src/internal/syntax/iterator.cc new file mode 100644 index 0000000..296e2aa --- /dev/null +++ b/src/internal/syntax/iterator.cc @@ -0,0 +1,54 @@ +#include "internal/syntax/parser.h" + +namespace bed::internal::syntax { +Iterator::Iterator(uint64_t target, ParserSnapshot p, vase::Shard *vase) + : snap(p) { + uint64_t offset; + TreeCursor c = TreeCursor(*p.lang, p.root, target, &offset); + at = target - offset; + state = p.lang->copy(c.leaf->state); + it = vase::Iterator(vase, at, Direction::Forward); + while (at < target) { + it->next(); + tokens.clear(); + events.clear(); + p.lang->parse(&state, it->line, at == 0, &tokens, &events); + at++; + } +} + +Iterator::~Iterator() { + if (state) + snap.lang->destroy(state); + release(snap); +} + +Iterator::Iterator(Iterator &&other) + : snap(other.snap), + it(std::move(other.it)), + state(other.state), + tokens(std::move(other.tokens)) { + other.snap = {}; + other.state = nullptr; +} + +Iterator &Iterator::operator=(Iterator &&other) { + if (this == &other) + return *this; + if (state) + snap.lang->destroy(state); + snap = other.snap; + it = std::move(other.it); + state = other.state; + tokens = std::move(other.tokens); + other.state = nullptr; + return *this; +} + +void Iterator::next() { + it->next(); + tokens.clear(); + events.clear(); + snap.lang->parse(&state, it->line, at++ == 0, &tokens, &events); +} +} // namespace bed::internal::syntax diff --git a/src/internal/syntax/parser.cc b/src/internal/syntax/parser.cc index 0e0e234..a5cb9ab 100644 --- a/src/internal/syntax/parser.cc +++ b/src/internal/syntax/parser.cc @@ -1,265 +1,32 @@ #include "internal/syntax/parser.h" namespace bed::internal::syntax { -static void destroy_tree(ParseState *node, Language &lang) { - if (!node) - return; - if (node->is_branch()) { - auto *branch = (ParseStateBranch *)node; - destroy_tree(branch->left, lang); - destroy_tree(branch->right, lang); - free(branch); - } else { - auto *leaf = (ParseStateLeaf *)node; - if (leaf->state) - lang.destroy(leaf->state); - if (leaf->blocks) - free(leaf->blocks); - free(leaf); - } -} - -static ParseState *make_branch(ParseState *left, ParseState *right) { - if (!left) - return right; - if (!right) - return left; - auto *branch = (ParseStateBranch *)malloc(sizeof(ParseStateBranch)); - branch->header = ParseState::BRANCH_BIT + left->lines() + right->lines(); - branch->left = left; - branch->right = right; - return branch; -} - -static ParseState *build_tree(std::vector &leaves, size_t begin, size_t end) { - const size_t count = end - begin; - if (count == 0) - return nullptr; - if (count == 1) - return leaves[begin]; - const size_t mid = begin + count / 2; - ParseState *left = build_tree(leaves, begin, mid); - ParseState *right = build_tree(leaves, mid, end); - return make_branch(left, right); -} - -Parser::Parser(vase::Shard *vase, uint64_t lines, Language lang) - : root(nullptr), lang(lang) { - reset(vase, lines, lang); -} - -Parser::~Parser() { - destroy_tree(root, lang); -} - -void Parser::reset(vase::Shard *vase, uint64_t lines, Language lang_) { - destroy_tree(root, lang); - root = nullptr; +ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang) { + ParserSnapshot snap{nullptr, lang}; if (lines == 0) - return; - lang = std::move(lang_); - std::vector leaves; - leaves.reserve((lines + MAX_CHUNK - 1) / MAX_CHUNK); - uint64_t consumed = 0; - while (consumed < lines) { - auto *leaf = (ParseStateLeaf *)malloc(sizeof(ParseStateLeaf)); - leaf->state = nullptr; - leaf->blocks = nullptr; - leaf->n = 0; - leaf->cap = 0; - uint64_t chunk = std::min(MAX_CHUNK, lines - consumed); - leaf->header = chunk; - consumed += chunk; - leaves.push_back(leaf); - } - root = build_tree(leaves, 0, leaves.size()); - modify(vase, 0, lines); + return snap; + if (lang) + snap.root = ParseState::splice(*lang, nullptr, vase, 0, 0, lines); + return snap; } -std::pair Parser::split_tree(ParseState *node, uint64_t line) { - if (!node) - return {nullptr, nullptr}; - if (line == 0) - return {nullptr, node}; - if (line >= node->lines()) - return {node, nullptr}; - if (node->is_branch()) { - auto *branch = (ParseStateBranch *)node; - uint64_t left_lines = branch->left->lines(); - if (line < left_lines) { - auto [a, b] = split_tree(branch->left, line); - ParseState *right = join_tree(b, branch->right); - free(branch); - return {a, right}; - } - if (line == left_lines) { - ParseState *left = branch->left; - ParseState *right = branch->right; - free(branch); - return {left, right}; - } - auto [a, b] = split_tree(branch->right, line - left_lines); - ParseState *left = join_tree(branch->left, a); - free(branch); - return {left, b}; - } else { - auto *leaf = (ParseStateLeaf *)node; - uint64_t lines = leaf->lines(); - auto *right = (ParseStateLeaf *)malloc(sizeof(ParseStateLeaf)); - right->header = lines - line; - right->state = nullptr; - right->blocks = nullptr; - right->n = 0; - right->cap = 0; - leaf->header = line; - leaf->n = 0; - return {leaf, right}; - } +ParserSnapshot retain(const ParserSnapshot &snap) { + if (snap.root) + ParseState::retain(snap.root); + return snap; } -ParseState *Parser::join_tree(ParseState *a, ParseState *b) { - // TODO: balance - return make_branch(a, b); +void release(ParserSnapshot &snap) { + if (snap.root && snap.lang) + ParseState::release(*snap.lang, snap.root); + snap.root = nullptr; } -void Parser::erase(vase::Shard *vase, uint64_t start, uint64_t count) { - begin_edit(); - erase(start, count); - end_edit(vase); -} - -void Parser::insert(vase::Shard *vase, uint64_t start, uint64_t count) { - begin_edit(); - insert(start, count); - end_edit(vase); -} - -void Parser::modify(vase::Shard *vase, uint64_t target, uint64_t count) { - if (count == 0 || !root) - return; - std::vector tokens; - std::vector events; - uint64_t offset; - TreeCursor c = TreeCursor(root, target, &offset); - uint64_t at = target - offset; - void *state = nullptr; - if (c.leaf->state) { - state = lang.copy(c.leaf->state); - } else { - while (!c.leaf->state) { - c.prev(); - if (!c.leaf) { - at = 0; - break; - } - at -= c.leaf->lines(); - } - if (c.leaf) { - state = lang.copy(c.leaf->state); - } else { - state = lang.none_state(); - c = TreeCursor(root, 0, &offset); - } - } - vase::Iterator it(vase, at, Direction::Forward); - uint64_t chunk_start = at; - uint64_t next_boundary = at + c.leaf->lines(); - c.leaf->n = 0; - while (true) { - it.next(); - if (at == next_boundary) { - c.next(); - if (!c.leaf) - break; - c.leaf->n = 0; - chunk_start = at; - next_boundary += c.leaf->lines(); - if (at >= target + count - && c.leaf->state != nullptr - && lang.equal(state, c.leaf->state)) - break; - if (c.leaf->state) - lang.destroy(c.leaf->state); - c.leaf->state = lang.copy(state); - } - tokens.clear(); - events.clear(); - lang.parse(&state, it.line, at == 0, &tokens, &events); - for (const auto &ev : events) { - if (c.leaf->n == c.leaf->cap) { - uint32_t cap = c.leaf->cap ? c.leaf->cap * 2 : 8; - c.leaf->blocks = (uint16_t *)realloc(c.leaf->blocks, cap * sizeof(uint16_t)); - c.leaf->cap = cap; - } - c.leaf->blocks[c.leaf->n++] = ev.closing << 15 | (at - chunk_start); - } - at++; - } - lang.destroy(state); -} - -void Parser::begin_edit() { - in_edit = true; -} - -void Parser::mark_dirty(uint64_t start, uint64_t end) { - if (!dirty) { - dirty_start = start; - dirty_end = end; - dirty = true; - } else { - dirty_start = std::min(dirty_start, start); - dirty_end = std::max(dirty_end, end); - } -} - -void Parser::erase(uint64_t start, uint64_t count) { - if (count == 0 || !root) - return; - auto [a, remaining] = split_tree(root, start); - auto [waste, b] = split_tree(remaining, count); - destroy_tree(waste, lang); - root = join_tree(a, b); - mark_dirty(start, start + 1); -} - -void Parser::insert(uint64_t start, uint64_t count) { - if (count == 0) - return; - std::vector leaves; - leaves.reserve((count + MAX_CHUNK - 1) / MAX_CHUNK); - uint64_t consumed = 0; - while (consumed < count) { - auto *leaf = (ParseStateLeaf *)malloc(sizeof(ParseStateLeaf)); - leaf->state = nullptr; - leaf->blocks = nullptr; - leaf->n = 0; - leaf->cap = 0; - uint64_t chunk = std::min(MAX_CHUNK, count - consumed); - leaf->header = chunk; - consumed += chunk; - leaves.push_back(leaf); - } - ParseState *subtree = build_tree(leaves, 0, leaves.size()); - auto [left, right] = split_tree(root, start); - root = join_tree(join_tree(left, subtree), right); - mark_dirty(start, start + count); -} - -void Parser::end_edit(vase::Shard *vase) { - in_edit = false; - if (!dirty) - return; - uint64_t count = dirty_end > dirty_start ? dirty_end - dirty_start : 1; - modify(vase, dirty_start, count); - dirty = false; -} - -uint64_t Parser::next_closing(uint64_t line) { - if (!root) - return UINT64_MAX; +uint64_t next_closing(const ParserSnapshot &snap, uint64_t line) { + if (!snap.root || !snap.lang) + return line + 10; uint64_t relative = 0; - TreeCursor c(root, line, &relative); + TreeCursor c(*snap.lang, snap.root, line, &relative); uint64_t line_offset = line - relative; int level = 0; bool first_leaf = true; @@ -283,14 +50,14 @@ uint64_t Parser::next_closing(uint64_t line) { c.next(); first_leaf = false; } - return UINT64_MAX; + return (snap.root->lines() - line > 10 ? line + 10 : snap.root->lines()); } -uint64_t Parser::prev_opening(uint64_t line) { - if (!root) +uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line) { + if (!snap.root || !snap.lang) return 0; uint64_t relative = 0; - TreeCursor c(root, line, &relative); + TreeCursor c(*snap.lang, snap.root, line, &relative); uint64_t line_offset = line - relative; int level = 0; bool first_leaf = true; @@ -315,77 +82,66 @@ uint64_t Parser::prev_opening(uint64_t line) { if (c.leaf) line_offset -= c.leaf->lines(); } - return 0; + return (line > 10 ? line - 10 : 0); } -std::optional Parser::get_hl(vase::Shard *vase, uint64_t target) { - if (!root) +std::optional get_hl(const ParserSnapshot &snap, vase::Shard *vase, uint64_t target) { + if (!snap.root || !snap.lang) return std::nullopt; - return Parser::Iterator(target, this, vase); + return Iterator(target, retain(snap), vase); } -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; - if (c.leaf->state) { - state = p->lang.copy(c.leaf->state); +void Edit::mark_dirty(uint64_t start, uint64_t end) { + if (!dirty) { + dirty_start = start; + dirty_end = end; + dirty = true; } else { - while (!c.leaf->state) { - c.prev(); - if (!c.leaf) { - at = 0; - break; - } - at -= c.leaf->lines(); - } - if (c.leaf) { - state = p->lang.copy(c.leaf->state); - } else { - state = p->lang.none_state(); - c = TreeCursor(p->root, 0, &offset); - } - } - it = vase::Iterator(vase, at, Direction::Forward); - while (at < target) { - it->next(); - tokens.clear(); - events.clear(); - p->lang.parse(&state, it->line, at == 0, &tokens, &events); - at++; + dirty_start = std::min(dirty_start, start); + dirty_end = std::max(dirty_end, end); } } -Parser::Iterator::~Iterator() { - if (state) - p->lang.destroy(state); +void Edit::insert(uint64_t start, uint64_t count) { + if (count == 0) + return; + uint64_t orig_pos = (uint64_t)((int64_t)start - edit_delta); + mark_dirty(orig_pos, orig_pos); + edit_delta += (int64_t)count; } -Parser::Iterator::Iterator(Iterator &&other) - : p(other.p), - it(std::move(other.it)), - state(other.state), - tokens(std::move(other.tokens)) { - other.state = nullptr; +void Edit::erase(uint64_t start, uint64_t count) { + if (count == 0 || !target->root) + return; + uint64_t orig_start = (uint64_t)((int64_t)start - edit_delta); + uint64_t orig_end = orig_start + count; + mark_dirty(orig_start, orig_end); + edit_delta -= (int64_t)count; } -Parser::Iterator &Parser::Iterator::operator=(Iterator &&other) { - if (this == &other) - return *this; - if (state) - p->lang.destroy(state); - p = other.p; - it = std::move(other.it); - state = other.state; - tokens = std::move(other.tokens); - other.state = nullptr; - return *this; +void Edit::commit(vase::Shard *vase) { + if (!dirty || !target->lang) + return; + uint64_t line = dirty_start; + uint64_t original = dirty_end - dirty_start; + uint64_t final = (uint64_t)((int64_t)original + edit_delta); + ParseState *new_root = + ParseState::splice(*target->lang, target->root, vase, line, original, final); + release(*target); + target->root = new_root; + dirty = false; + edit_delta = 0; } -void Parser::Iterator::next() { - it->next(); - tokens.clear(); - events.clear(); - p->lang.parse(&state, it->line, at++ == 0, &tokens, &events); +void insert(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count) { + Edit e(snap); + e.insert(start, count); + e.commit(vase); +} + +void erase(ParserSnapshot &snap, vase::Shard *vase, uint64_t start, uint64_t count) { + Edit e(snap); + e.erase(start, count); + e.commit(vase); } } // namespace bed::internal::syntax diff --git a/src/internal/syntax/parser_state.cc b/src/internal/syntax/parser_state.cc new file mode 100644 index 0000000..e045d44 --- /dev/null +++ b/src/internal/syntax/parser_state.cc @@ -0,0 +1,195 @@ +#include "internal/syntax/parser.h" + +namespace bed::internal::syntax { +void ParseState::retain(ParseState *n) { + if (n) + n->refs++; +}; + +void ParseState::release(Language &lang, ParseState *n) { + if (!n || --n->refs > 0) + return; + if (n->is_branch()) { + auto *branch = (ParseStateBranch *)n; + release(lang, branch->left); + release(lang, branch->right); + delete branch; + } else { + auto *leaf = (ParseStateLeaf *)n; + if (leaf->state) + lang.destroy(leaf->state); + if (leaf->blocks) + free(leaf->blocks); + delete leaf; + } +} + +int height(ParseState *n) { + return n ? n->height : 0; +} + +int balance_factor(ParseState *n) { + ParseStateBranch *b = (ParseStateBranch *)n; + return height(b->left) - height(b->right); +} + +ParseState *rotate_right(Language &lang, ParseStateBranch *z) { + ParseStateBranch *y = (ParseStateBranch *)z->left; + ParseState *middle = new ParseStateBranch(y->right, z->right); + ParseState *out = new ParseStateBranch(y->left, middle); + ParseState::release(lang, middle); + ParseState::release(lang, z); + return out; +} + +ParseState *rotate_left(Language &lang, ParseStateBranch *z) { + ParseStateBranch *y = (ParseStateBranch *)z->right; + ParseState *middle = new ParseStateBranch(z->left, y->left); + ParseState *out = new ParseStateBranch(middle, y->right); + ParseState::release(lang, middle); + ParseState::release(lang, z); + return out; +} + +ParseState *balance(Language &lang, ParseState *node) { + if (!node || !node->is_branch()) + return node; + ParseStateBranch *b = (ParseStateBranch *)node; + int bf = balance_factor(node); + if (bf > 1) { + ParseStateBranch *left = (ParseStateBranch *)b->left; + if (balance_factor(left) < 0) { + ParseState::retain(left); + auto new_left = rotate_left(lang, left); + auto rebuilt = new ParseStateBranch(new_left, b->right); + auto result = rotate_right(lang, (ParseStateBranch *)rebuilt); + ParseState::release(lang, new_left); + ParseState::release(lang, b); + return result; + } + return rotate_right(lang, b); + } + if (bf < -1) { + ParseStateBranch *right = (ParseStateBranch *)b->right; + if (balance_factor(right) > 0) { + ParseState::retain(right); + auto new_right = rotate_right(lang, right); + auto rebuilt = new ParseStateBranch(b->left, new_right); + auto result = rotate_left(lang, (ParseStateBranch *)rebuilt); + ParseState::release(lang, new_right); + ParseState::release(lang, b); + return result; + } + return rotate_left(lang, b); + } + return node; +} + +ParseState *ParseState::build(Language &lang, ParseState **pieces, uint64_t lo, uint64_t hi) { + if (hi - lo == 1) + return pieces[lo]; + uint64_t mid = lo + (hi - lo) / 2; + ParseState *left = build(lang, pieces, lo, mid); + ParseState *right = build(lang, pieces, mid, hi); + ParseState *node = concat(lang, left, right); + release(lang, left); + release(lang, right); + return node; +} + +ParseState *ParseState::splice( + Language &lang, ParseState *root, vase::Shard *vase, + uint64_t line, uint64_t original, uint64_t final +) { + if (!vase) + return nullptr; + if (!root || (line == 0 && root->lines() == original)) { + vase::Iterator it(vase, 0, Direction::Forward); + void *state = lang.none_state(); + std::vector tokens; + std::vector events; + ParsePieceBuilder builder(lang, 0); + for (uint64_t at = 0; at < final; ++at) { + it.next(); + tokens.clear(); + events.clear(); + lang.parse(&state, it.line, at == 0, &tokens, &events); + builder.add(state, at, events); + } + lang.destroy(state); + return builder.finish(); + } + uint64_t at; + void *state; + ParseState *prefix; + { + uint64_t offset; + TreeCursor c = TreeCursor(lang, root, line, &offset); + prefix = c.prefix(); + at = line - offset; + state = lang.copy(c.leaf->state); + } + vase::Iterator it(vase, at, Direction::Forward); + std::vector tokens; + std::vector events; + uint64_t end_in_tree = line + original; + uint64_t end_extra; + TreeCursor c = TreeCursor(lang, root, end_in_tree, &end_extra); + uint64_t end_in_vase = line + final; + ParsePieceBuilder builder(lang, at); + while (at < end_in_vase + end_extra) { + it.next(); + tokens.clear(); + events.clear(); + lang.parse(&state, it.line, at == 0, &tokens, &events); + builder.add(state, at, events); + ++at; + } + c.next(); + while (c.leaf) { + if (lang.equal(state, c.leaf->state)) + break; + for (uint64_t i = 0; i < c.leaf->lines(); ++i) { + it.next(); + tokens.clear(); + events.clear(); + lang.parse(&state, it.line, at == 0, &tokens, &events); + builder.add(state, at, events); + ++at; + } + c.next(); + } + lang.destroy(state); + ParseState *suffix = c.suffix(); + ParseState *new_stuff = builder.finish(); + auto a = concat(lang, prefix, new_stuff); + release(lang, prefix); + release(lang, new_stuff); + auto result = concat(lang, a, suffix); + release(lang, a); + release(lang, suffix); + return result; +} + +ParseState *ParseState::concat(Language &lang, ParseState *a, ParseState *b) { + if (!a) + return (retain(b), b); + if (!b) + return (retain(a), a); + if (a->height > b->height + 1) { + ParseStateBranch *ba = (ParseStateBranch *)a; + ParseState *r = concat(lang, ba->right, b); + ParseState *out = balance(lang, new ParseStateBranch(ba->left, r)); + release(lang, r); + return out; + } + if (b->height > a->height + 1) { + ParseStateBranch *bb = (ParseStateBranch *)b; + ParseState *l = concat(lang, a, bb->left); + ParseState *out = balance(lang, new ParseStateBranch(l, bb->right)); + release(lang, l); + return out; + } + return balance(lang, new ParseStateBranch(a, b)); +} +} // namespace bed::internal::syntax diff --git a/src/internal/syntax/tree_cursor.cc b/src/internal/syntax/tree_cursor.cc index bcd83d2..cd95af6 100644 --- a/src/internal/syntax/tree_cursor.cc +++ b/src/internal/syntax/tree_cursor.cc @@ -1,7 +1,19 @@ #include "internal/syntax/decl.h" namespace bed::internal::syntax { -TreeCursor::TreeCursor(ParseState *root, uint64_t target_line, uint64_t *relative) { +TreeCursor::~TreeCursor() { + ParseState::release(lang, root); +} + +TreeCursor::TreeCursor( + Language &lang, ParseState *root, + uint64_t target_line, uint64_t *relative +) : lang(lang), root(root) { + if (!root) { + *relative = 0; + return; + } + ParseState::retain(root); ParseState *node = root; while (node->is_branch()) { auto *branch = (ParseStateBranch *)node; @@ -63,4 +75,42 @@ void TreeCursor::prev() { } leaf = nullptr; } + +ParseState *TreeCursor::prefix() { + ParseState *result = nullptr; + for (uint8_t i = 0; i < depth; ++i) { + if (went_left[i]) + continue; + auto *branch = stack[i]; + ParseState *piece = branch->left; + if (!result) { + ParseState::retain(piece); + result = piece; + } else { + ParseState *next = ParseState::concat(lang, result, piece); + ParseState::release(lang, result); + result = next; + } + } + return result; +} + +ParseState *TreeCursor::suffix() { + ParseState *result = nullptr; + for (uint8_t i = depth; i-- > 0;) { + if (!went_left[i]) + continue; + auto *branch = stack[i]; + ParseState *piece = branch->right; + if (!result) { + ParseState::retain(piece); + result = piece; + } else { + ParseState *next = ParseState::concat(lang, result, piece); + ParseState::release(lang, result); + result = next; + } + } + return result; +} } // namespace bed::internal::syntax