Compare commits

...
2 Commits
Author SHA1 Message Date
syedm 88785436cf Add ARCHITECTURE.md. 2026-09-13 13:15:57 +01:00
syedm d246559292 Make posix line mode the default always. 2026-09-13 13:15:07 +01:00
7 changed files with 196 additions and 51 deletions
+149
View File
@@ -0,0 +1,149 @@
# Base.
### `PCH`
Brings in all the external and standard headers. <br/>
Apart from c++std stuff we have:
- `mruby` and almost all it's subheaders.
- `pcre2` (For regex.)
- `grapheme.h` - The suckless libgrapheme (to find utf8 boundaries)
- `unicode_width.h` - To find terminal width of unicode char's.
*Maybe try removing some of them ive probably stopped using.*
### `definitions.h`
- Defines the `bed` namespace
- Forward declaration of `BEd` type needed by all.
- `fatal_error` definition, a msg, code (quits application on being thrown)
- `ed_error`. just a message (stops the current cycle of the ed repl.)
### `bed.h`
**As this is based heavily on the internals, and so ill look into it at the end.**
# `Internal`
## `Vase`
- This module doesn't depend on any other.
- It's namespace is `bed::internal::vase`
- Vase is the module to do with the actual storage and handling of a disk backed immutable and reference counted avl piece tree.
- It is a O(log n) per edit text datatype.
### `constants.h`
Defines `PETAL_SIZE_MAX` as `32 * 1024`.
- `PETAL_SIZE_MAX` limits teh size of a petal which makes multiple O(n) in petal searches O(1) as it is now bounded.
### `storage/`
A storage is something that stores the actual text, the text is actually stored in storages which are referenced by the trees.
#### `storage.h`
Defines the virtual `Storage` class.
It has the methods:
- `const char *read(uint64_t pos)`: reads text from position `pos` and returns a pointer to it.
- `uint64_t length()`: returns the length of the storage.
- `void retain() / release()`: Methods to help with refcounted storages.
#### `original.h`
Defines the `Original` type of storage.
This is a refcounted storage.
It has the fields:
- `atomic_uint64_t refs{0}`: refcount
- `const char *buf`: pointer to the actual text.
- `uint64_t len`
- `int fd = -1`: the file descriptor of teh undelying disk storage.
It adds the method `initialize` over the base class.
`src/vase/storage/original.cc`.
The constructor uses `mkstemp` to make a temp file, then unlinks it to hide it from the fs but keeps the fd alive.
`initialize` then `mmap`s the fd into a non modifyable pointer managed by the kernel then closes the fd itself.
- This is what gives us a pointer to the file contents that the kernel can load and evict from memory as needed, and therefore if we have say a 500MB file loaded, but the cursor is arount 200MB and the user is only editing a couple lines around that, the rest of the file will not be using your memory, it might be loaded but if needed the kernel can evict it.
The call site fills the fd before calling initialze, it cannot be modified after that.
#### `append.h`
Defines the `Append` type of storage.
This is not actually refcounted, it is to be owned by the application just once and reused all throughout.
It contains the actively typed stuff.
It has the fields:
- `const char *buf`: pointer to the actual text.
- `uint64_t allocated_capacity`: the amount of bytees that can be written to it without needing to increase the size.
- `uint64_t current_size`: Stores the size of actual text stored in it. (the cursor)
- `int fd = -1`: the file descriptor of the underlying disk storage.
It has the methods `append` (with 2 overloads) and private `grow` over the base class.
`retain` and `release` do nothing.
`src/vase/storage/append.cc`.
The constructor uses `mkstemp` an `unlink` similar to the original storage.
It then `ftruncate`s the file to `2^30` bytes or 1GiB this makes writing 1gb to the file possible but does'nt neccasarily use up 1gb on the disk.
But when `mmap`ing we set `PROT_READ | PROT_WRITE` and `MAP_SHARED` to be able to write edits to disk.
`grow` works by doubling the capacity `ftruncat`ing the fd to the new capacity and then on `linux` it uses `mremap` to remap to the new file size, but otherwise we `munmap` and `mmap` into the new size.
The append methods append text into the buffer (either a single char or char * + len), they may grow the storage size. (the char*+len version uses memcpy).
### `shard.h`
Defines the actual peice tree.
The tree is a polymorhic set of classes.
The base class `Shard` has:
- `enum Kind : uint8_t kind`: Branch or Petal.
- `uint16_t height`: the tree height (for avl balancing.)
- `atomic_uint32_t refs`: the refcount.
- `uint64_t length`: the length in bytes of this (sub)tree.
- `uint64_t lines`: the number of `\n` in this subtree.
- It starts refcount at 1.
The `Branch` class adds:
- `Shard *left/right` the subtrees.
- its constructor takes l, r and sets length/lines `(l->length + r->length)`
- and height `1 + std::max(l->height, r->height)`
The `Petal` (leaf) class adds:
- `Storage *` a pointer to the storage used.
- `uint64_t pos` the position in the storage its text starts at.
- it takes `(uint64_t length, uint64_t lines, Storage *source, uint64_t pos)`
- it retains `source`. (usefull for refcounted sotrages.)
A `nullptr` `Shard*` is an empty peice of text (i.e. valid).
`src/internal/vase/shard.cc`: defines a set of static function on the Shard class. (All the applications of it.)
the operations methods are:
- `retain/release` retain/releases them, if freeing a leaf it also releases the storage and for branches it releases both subtrees. nullptr's are ignored.
- `balance/rotate_(right/left) and height/balance factor` are for doing simple avl balancing operations.
- `concat` borrows 2 pointers to `Shard` and returns an owned Shard*.
- `split` borrows a `Shard` and returns a `pair<Shard*, Shard*>` of 2 owned shards.
- `merge_leaves` works similar to concat but it tries to merge the middle bit if they have their physical representation in order. for example if i have a shard `hell` and i type the letter `o` and because i'd been typing in order the Append storage will have text `hello` we can merge them into a single petal.
- `append` similar to concat, but uses merge_leaves, useful for small appends like when typing.
- `build` takes a pointer to an array of `Shard*` and start and end then makes a single avl balanced `Shard*` out of it it is generally faster than calling concat on each peice when we have many.
Then for building a Shard* in the first place we have:
- `from_command(const char *cmd)` runs the \0 terminated command string and reads its STDOUT and returns an owned Shard*.
- `from_file(const std::filesystem::path &path)` reads the file at path.
- `from_string(const char *data, uint64_t len)` loads the string given into a new storage, (normally inserting text can work by inserting into the append storage but this function loads it into a `OriginalStorage` object.).
These methods all clip the final newline if it exists.
And they also create a new `OriginalStorage` object.
### `vase.h`
### `iterators/`
#### `line.h`
#### `petal.h`
+1 -3
View File
@@ -56,9 +56,7 @@ It should support:
- Error handling.
- And more.
Not done yet.
### TODO immediately:
### TODO:
- Make "g" command work.
- properly handle escapes for %q ' etc in ruby parser (rn everything is escapable.)
+3 -3
View File
@@ -24,9 +24,9 @@ struct Shard {
static void retain(Shard *n);
static void release(Shard *n);
static Shard *from_file(const std::filesystem::path &path, bool posix_ending);
static Shard *from_string(const char *data, uint64_t len, bool posix_ending);
static Shard *from_command(const char *cmd, bool posix_ending);
static Shard *from_file(const std::filesystem::path &path);
static Shard *from_string(const char *data, uint64_t len);
static Shard *from_command(const char *cmd);
static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset);
static Shard *concat(Shard *a, Shard *b);
+13 -13
View File
@@ -11,14 +11,14 @@ bool ClipBuffer::waste() {
void ClipBuffer::saved_hook() {}
uint64_t ClipBuffer::lines() {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
uint64_t lines = s ? s->lines + 1 : 0;
vase::Shard::release(s);
return lines;
}
uint64_t ClipBuffer::bytes() {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
uint64_t length = s ? s->length + 1 : 0;
vase::Shard::release(s);
return length;
@@ -57,7 +57,7 @@ void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
ctx.prev().buffername = name;
ctx.prev().start = line + 1;
ctx.prev().end = line + (text ? text->lines + 1 : 0);
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
s = vase::insert(&ctx.append, s, text, line);
clip_write(s);
vase::Shard::release(s);
@@ -65,7 +65,7 @@ void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
}
void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
s = vase::erase(s, start_line, end_line);
clip_write(s);
ctx.prev().buffername = name;
@@ -85,7 +85,7 @@ void ClipBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint6
ctx.prev().end = start_line + text->lines;
uint64_t new_count = text->lines + 1;
uint64_t old_count = end_line - start_line + 1;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
s = vase::replace(s, text, start_line, end_line);
clip_write(s);
vase::Shard::release(s);
@@ -99,7 +99,7 @@ void ClipBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint6
}
void ClipBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
s = vase::join(s, start_line, end_line);
clip_write(s);
vase::Shard::release(s);
@@ -116,7 +116,7 @@ void ClipBuffer::substitute(
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
s = vase::substitute(
&ctx.append,
s,
@@ -138,21 +138,21 @@ void ClipBuffer::substitute(
}
vase::Shard *ClipBuffer::copy(uint64_t start_line, uint64_t end_line) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
vase::Shard *o = vase::copy(s, start_line, end_line);
vase::Shard::release(s);
return o;
}
uint64_t ClipBuffer::find_next(std::string_view pattern, uint64_t start) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
uint64_t line = vase::find_next(s, pattern, start);
vase::Shard::release(s);
return line;
}
uint64_t ClipBuffer::find_prev(std::string_view pattern, uint64_t start) {
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
uint64_t line = vase::find_prev(s, pattern, start);
vase::Shard::release(s);
return line;
@@ -176,7 +176,7 @@ void ClipBuffer::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;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
ctx.io.write_line(it.line);
@@ -190,7 +190,7 @@ void ClipBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line)
uint8_t width = 1;
for (uint64_t n = end_line; n >= 10; n /= 10)
++width;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line <= end_line)
ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line));
@@ -201,7 +201,7 @@ void ClipBuffer::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;
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
auto s = vase::Shard::from_command("xclip -selection clipboard -o");
vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line)
ctx.io.write_line(list_string(it.line));
+1 -1
View File
@@ -197,7 +197,7 @@ void Function::register_extented(BEd &ctx) {
ctx.io.write("=> ", 3);
auto parser = syntax::MiniParser(
*ctx.languages["ruby"],
vase::Shard::from_string(line.data(), line.size(), true),
vase::Shard::from_string(line.data(), line.size()),
nullptr
);
const auto &tokens = parser.lines[0].second;
+9 -9
View File
@@ -144,17 +144,17 @@ void Function::register_posix(BEd &ctx) {
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true);
s = vase::Shard::from_file(path);
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true);
s = vase::Shard::from_command(cmd.c_str());
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true);
s = vase::Shard::from_file(path);
};
try {
buf.load(ctx, s);
@@ -190,17 +190,17 @@ void Function::register_posix(BEd &ctx) {
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true);
s = vase::Shard::from_file(path);
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true);
s = vase::Shard::from_command(cmd.c_str());
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true);
s = vase::Shard::from_file(path);
};
try {
buf.load(ctx, s);
@@ -588,18 +588,18 @@ void Function::register_posix(BEd &ctx) {
vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<std::filesystem::path>(arg);
s = vase::Shard::from_file(path, true);
s = vase::Shard::from_file(path);
if (buf.filename().empty())
buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string());
s = vase::Shard::from_command(cmd.c_str(), true);
s = vase::Shard::from_command(cmd.c_str());
} else {
auto path = buf.filename();
if (path.empty())
throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true);
s = vase::Shard::from_file(path);
};
try {
buf.append(ctx, s, addr.number);
+20 -22
View File
@@ -202,7 +202,7 @@ Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
return node;
}
Shard *Shard::from_command(const char *cmd, bool posix_ending) {
Shard *Shard::from_command(const char *cmd) {
auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd;
if (dest_fd == -1) {
@@ -278,23 +278,21 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
io::IO::enable_raw();
return nullptr;
}
if (posix_ending) {
if (ending[1] == '\n') {
Petal *last = (Petal *)pieces.back();
last->lines--;
last->length--;
if (last->length == 0) {
Shard::release(last);
pieces.pop_back();
last = nullptr;
if (!pieces.empty())
last = (Petal *)pieces.back();
else
return nullptr;
}
if (last && ending[0] == '\r')
last->length--;
if (ending[1] == '\n') {
Petal *last = (Petal *)pieces.back();
last->lines--;
last->length--;
if (last->length == 0) {
Shard::release(last);
pieces.pop_back();
last = nullptr;
if (!pieces.empty())
last = (Petal *)pieces.back();
else
return nullptr;
}
if (last && ending[0] == '\r')
last->length--;
}
o->initialize();
io::IO::enable_raw();
@@ -303,7 +301,7 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
return build(pieces.data(), 0, pieces.size());
}
Shard *Shard::from_file(const std::filesystem::path &path, bool posix_ending) {
Shard *Shard::from_file(const std::filesystem::path &path) {
auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd;
if (dest_fd == -1) {
@@ -313,10 +311,10 @@ Shard *Shard::from_file(const std::filesystem::path &path, bool posix_ending) {
int src_fd = open(path.c_str(), O_RDONLY);
if (src_fd == -1) {
delete o;
return nullptr;
throw ed_error("Couldn't open file.");
}
uint64_t total = std::filesystem::file_size(path);
if (posix_ending && total > 0) {
if (total > 0) {
char last;
if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) {
delete o;
@@ -381,7 +379,7 @@ Shard *Shard::from_file(const std::filesystem::path &path, bool posix_ending) {
return build(pieces.data(), 0, pieces.size());
}
Shard *Shard::from_string(const char *data, uint64_t len, bool posix_ending) {
Shard *Shard::from_string(const char *data, uint64_t len) {
auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd;
if (dest_fd == -1 || data == nullptr) {
@@ -389,7 +387,7 @@ Shard *Shard::from_string(const char *data, uint64_t len, bool posix_ending) {
return nullptr;
}
uint64_t total = len;
if (posix_ending && total > 0) {
if (total > 0) {
if (data[total - 1] == '\n') {
total--;
if (total > 0 && data[total - 1] == '\r')