Add history support, and fix parsing system.
This commit is contained in:
+1
-1
@@ -18,7 +18,7 @@ struct BEd {
|
||||
internal::functions::Function eof_op;
|
||||
std::array<std::optional<internal::functions::Suffix>, 26> suffixes;
|
||||
internal::theme::Theme theme;
|
||||
std::unordered_map<std::string, internal::syntax::Language> languages;
|
||||
std::unordered_map<std::string, internal::syntax::Language *> languages;
|
||||
internal::io::IO &io;
|
||||
internal::vase::AppendStorage append{"/tmp"};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "decl.h"
|
||||
#include "pch.h"
|
||||
#include "../decl.h"
|
||||
|
||||
namespace bed::internal::buffer {
|
||||
struct ClipBuffer : Buffer {
|
||||
@@ -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<syntax::Parser> parser{};
|
||||
std::vector<HistoryItem> undo_stack;
|
||||
std::vector<HistoryItem> 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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<void *()> none_state;
|
||||
std::function<void(void **, std::string_view, bool, std::vector<Token> *, std::vector<ParseEvent> *)> parse;
|
||||
std::function<void(
|
||||
void **, std::string_view,
|
||||
bool, std::vector<Token> *, std::vector<ParseEvent> *
|
||||
)>
|
||||
parse;
|
||||
std::function<void *(void *)> copy;
|
||||
std::function<bool(void *, void *)> equal;
|
||||
std::function<void(void *)> 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<ParseState *> pieces;
|
||||
std::vector<uint16_t> 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<ParseEvent> &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
|
||||
|
||||
@@ -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<vase::Iterator> it;
|
||||
void *state;
|
||||
uint64_t at;
|
||||
std::vector<Token> tokens;
|
||||
std::vector<ParseEvent> 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<Iterator> 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<ParseState *, ParseState *> split_tree(ParseState *node, uint64_t line);
|
||||
ParseState *join_tree(ParseState *a, ParseState *b);
|
||||
|
||||
struct Iterator {
|
||||
Parser *p;
|
||||
std::optional<vase::Iterator> it;
|
||||
void *state;
|
||||
uint64_t at;
|
||||
std::vector<Token> tokens;
|
||||
std::vector<ParseEvent> 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<Iterator> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
+35
-3
@@ -7,6 +7,7 @@ BEd::BEd(std::vector<std::string> 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<std::string> 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;
|
||||
|
||||
+214
-182
@@ -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<syntax::Edit> 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<syntax::Parser::Iterator> 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
|
||||
|
||||
@@ -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<syntax::Iterator> 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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<buffer::Line> *
|
||||
) {
|
||||
auto &addr = std::get<std::string>(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<buffer::Line> *
|
||||
) {
|
||||
auto &addr = std::get<std::string>(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
|
||||
|
||||
@@ -213,10 +213,7 @@ void Function::register_posix(BEd &ctx) {
|
||||
std::vector<buffer::Line> *
|
||||
) {
|
||||
auto &addr = std::get<std::string>(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<std::filesystem::path>(arg))
|
||||
buf.set_filename(std::get<std::filesystem::path>(arg));
|
||||
else if (std::holds_alternative<ShellArg>(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<buffer::Line> *
|
||||
) {
|
||||
auto &addr = std::get<std::string>(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{
|
||||
|
||||
@@ -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
|
||||
+68
-312
@@ -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<ParseStateLeaf *> &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<ParseStateLeaf *> 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<ParseState *, ParseState *> 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<Token> tokens;
|
||||
std::vector<ParseEvent> 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<ParseStateLeaf *> 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::Iterator> Parser::get_hl(vase::Shard *vase, uint64_t target) {
|
||||
if (!root)
|
||||
std::optional<Iterator> 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
|
||||
|
||||
@@ -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<Token> tokens;
|
||||
std::vector<ParseEvent> 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<Token> tokens;
|
||||
std::vector<ParseEvent> 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user