Compare commits

...
11 Commits
Author SHA1 Message Date
syedm 8f67be87ee Add escape in strings control to ruby parser. 2026-09-13 14:07:19 +01:00
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
syedm 3346bc9f83 Fix a few bugs.
- Moving lines after themselves didnt work.
- moving empty lines deleted them.
- "w" didnt set state to unmodified in generic buffers.
- in "hl" aligned the numbers (by digits)
2026-09-11 22:35:06 +01:00
syedm a8d51dc0b6 Make ` command behave like irb with coloring
- And `` command to run selected lines, but removing ``` as
  that can be done by making a new buffer and writing in that.
2026-09-10 07:21:22 +01:00
syedm 118934a393 Make mruby raii cleanup, and fix cleanup bug.
- Functions were cleaned up after mruby, which was wrong.
- Becuase functions could have stored a reference to a Block which refers to mruby.
- And so it would crash on release builds at exit if custom functions were used.
2026-09-08 23:12:52 +01:00
syedm f5ec5516fa Cleanup flake.nix 2026-09-08 22:44:58 +01:00
syedm c325409dd0 Add basic runtime function operations. 2026-09-08 22:34:45 +01:00
syedm 0e4422b0c8 Fix issue with mruby linking. 2026-09-08 19:10:52 +01:00
syedm bb3ce7549a Add basic scripting support.
- Fix certain bugs in ruby parser
- And a lot more cleanup/minor fixes.
2026-09-08 18:07:39 +01:00
syedm a5794d9ab3 Cleanup. 2026-09-08 11:56:26 +01:00
34 changed files with 1054 additions and 380 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 -1
View File
@@ -36,7 +36,7 @@ CFLAGS_RELEASE :=\
-fomit-frame-pointer -DNDEBUG -s \ -fomit-frame-pointer -DNDEBUG -s \
-I./include -I./libs/unicode_width -I./include -I./libs/unicode_width
CFLAGS_DEBUG += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS) $(C_SANITIZER) CFLAGS_DEBUG += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS)
CFLAGS_RELEASE += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS) CFLAGS_RELEASE += $(PCRE_CFLAGS) $(LIBGRAPHEME_CFLAGS) $(MRUBY_CFLAGS)
UNICODE_SRC := $(wildcard libs/unicode_width/*.c) UNICODE_SRC := $(wildcard libs/unicode_width/*.c)
+3 -1
View File
@@ -56,5 +56,7 @@ It should support:
- Error handling. - Error handling.
- And more. - And more.
Not done yet. ### TODO:
- Make "g" command work.
- properly handle escapes for %q ' etc in ruby parser (rn everything is escapable.)
+2 -5
View File
@@ -70,9 +70,8 @@
buildPhase = '' buildPhase = ''
make \ make \
MRBC=${mruby}/bin/mrbc \ MRUBY_CFLAGS="-I${mruby}/include" \
MRUBY_CFLAGS=-I${mruby}/include \ MRUBY_LIBS="-L${mruby}/lib -lmruby"
MRUBY_LIBS=-L${mruby}/lib -lmruby
''; '';
installPhase = '' installPhase = ''
@@ -110,10 +109,8 @@
export CC="ccache $CC" export CC="ccache $CC"
export CXX="ccache $CXX" export CXX="ccache $CXX"
export MRBC=${mruby}/bin/mrbc
export MRUBY_CFLAGS=-I${mruby}/include export MRUBY_CFLAGS=-I${mruby}/include
export MRUBY_LIBS="-L${mruby}/lib -lmruby" export MRUBY_LIBS="-L${mruby}/lib -lmruby"
export PATH="$PWD/result/bin:$PATH"
''; '';
}; };
}; };
+3 -2
View File
@@ -6,6 +6,7 @@
#include "internal/functions/suffixes.h" #include "internal/functions/suffixes.h"
#include "internal/io/io.h" #include "internal/io/io.h"
#include "internal/marks/marks.h" #include "internal/marks/marks.h"
#include "internal/scripting/ruby.h"
#include "internal/theme/theme.h" #include "internal/theme/theme.h"
#include "internal/ui/command.h" #include "internal/ui/command.h"
#include "internal/ui/text_mode.h" #include "internal/ui/text_mode.h"
@@ -13,6 +14,7 @@
namespace bed { namespace bed {
struct BEd { struct BEd {
internal::scripting::RubyState mrb;
internal::trie::Trie<internal::functions::Function> functions; internal::trie::Trie<internal::functions::Function> functions;
internal::functions::Function no_op; internal::functions::Function no_op;
internal::functions::Function eof_op; internal::functions::Function eof_op;
@@ -21,6 +23,7 @@ struct BEd {
std::unordered_map<std::string, internal::syntax::Language *> languages; std::unordered_map<std::string, internal::syntax::Language *> languages;
internal::io::IO io; internal::io::IO io;
internal::vase::AppendStorage append{"/tmp"}; internal::vase::AppendStorage append{"/tmp"};
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
bool help_mode = false; bool help_mode = false;
bool prompt_mode = true; bool prompt_mode = true;
@@ -34,8 +37,6 @@ struct BEd {
std::string last_replacement = ""; std::string last_replacement = "";
std::string last_shell = ""; std::string last_shell = "";
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
internal::buffer::Range prev_1; internal::buffer::Range prev_1;
internal::buffer::Range prev_2; internal::buffer::Range prev_2;
internal::marks::MarksEngine marks; internal::marks::MarksEngine marks;
+1
View File
@@ -34,6 +34,7 @@ struct Buffer {
virtual uint64_t bytes() = 0; virtual uint64_t bytes() = 0;
virtual void set_filename(std::filesystem::path path) = 0; virtual void set_filename(std::filesystem::path path) = 0;
virtual std::filesystem::path filename() = 0; virtual std::filesystem::path filename() = 0;
virtual void saved_hook() = 0;
virtual void load(BEd &ctx, vase::Shard *text) = 0; virtual void load(BEd &ctx, vase::Shard *text) = 0;
virtual vase::Shard *copy(uint64_t start_line, uint64_t end_line) = 0; virtual vase::Shard *copy(uint64_t start_line, uint64_t end_line) = 0;
virtual void substitute( virtual void substitute(
+1
View File
@@ -10,6 +10,7 @@ struct ClipBuffer : Buffer {
void clip_write(vase::Shard *text); void clip_write(vase::Shard *text);
bool waste() override; bool waste() override;
void saved_hook() override;
uint64_t lines() override; uint64_t lines() override;
uint64_t bytes() override; uint64_t bytes() override;
void load(BEd &ctx, vase::Shard *text) override; void load(BEd &ctx, vase::Shard *text) override;
+4 -2
View File
@@ -14,8 +14,8 @@ struct HistoryItem {
struct GenericBuffer : ShardBuffer { struct GenericBuffer : ShardBuffer {
uint64_t base_version{0}; uint64_t base_version{0};
std::chrono::system_clock::time_point timestamp; std::chrono::system_clock::time_point timestamp{std::chrono::system_clock::now()};
std::string action; std::string action{"Created buffer."};
std::filesystem::path save_path{}; std::filesystem::path save_path{};
std::vector<HistoryItem> undo_stack; std::vector<HistoryItem> undo_stack;
std::vector<HistoryItem> redo_stack; std::vector<HistoryItem> redo_stack;
@@ -24,12 +24,14 @@ struct GenericBuffer : ShardBuffer {
: ShardBuffer(name, nullptr, nullptr, Kind::Generic) {} : ShardBuffer(name, nullptr, nullptr, Kind::Generic) {}
~GenericBuffer(); ~GenericBuffer();
void language(BEd &ctx, std::string name);
void list_history(BEd &ctx); void list_history(BEd &ctx);
ReadonlyBuffer *get_history(uint64_t version); ReadonlyBuffer *get_history(uint64_t version);
void snapshot(std::string action); void snapshot(std::string action);
bool undo(BEd &ctx); bool undo(BEd &ctx);
bool redo(BEd &ctx); bool redo(BEd &ctx);
uint64_t prune(int); uint64_t prune(int);
void saved_hook() override;
bool waste() override; bool waste() override;
void load(BEd &ctx, vase::Shard *text) override; void load(BEd &ctx, vase::Shard *text) override;
void set_filename(std::filesystem::path path) override; void set_filename(std::filesystem::path path) override;
+1
View File
@@ -12,6 +12,7 @@ struct ReadonlyBuffer : ShardBuffer {
const syntax::ParserSnapshot &snapshot const syntax::ParserSnapshot &snapshot
) : ShardBuffer(std::move(name), root, snapshot, Kind::History) {} ) : ShardBuffer(std::move(name), root, snapshot, Kind::History) {}
void saved_hook() override {}
bool waste() override { bool waste() override {
return useless; return useless;
} }
+124
View File
@@ -0,0 +1,124 @@
#pragma once
#include "definitions.h"
#include "pch.h"
namespace bed::internal::scripting {
struct RubyState {
mrb_state *state = nullptr;
int arena = 0;
explicit RubyState(BEd *ctx) : state(mrb_open()) {
if (!state)
throw fatal_error("Failed to initialize mruby.", 1);
state->ud = ctx;
arena = mrb_gc_arena_save(state);
}
~RubyState() {
state->ud = nullptr;
if (state) {
mrb_gc_arena_restore(state, arena);
mrb_full_gc(state);
mrb_close(state);
}
}
RubyState(const RubyState &) = delete;
RubyState &operator=(const RubyState &) = delete;
};
struct Block {
mrb_state *mrb = nullptr;
mrb_value proc = mrb_nil_value();
Block(mrb_state *mrb, mrb_value proc) noexcept
: mrb(mrb), proc(proc) {
mrb_gc_register(mrb, proc);
}
Block() noexcept = default;
~Block() noexcept {
if (!mrb_nil_p(proc) && mrb)
mrb_gc_unregister(mrb, proc);
}
Block(const Block &other) noexcept
: mrb(other.mrb), proc(other.proc) {
if (mrb && !mrb_nil_p(proc))
mrb_gc_register(mrb, proc);
}
Block &operator=(const Block &other) noexcept {
if (this != &other) {
if (mrb && !mrb_nil_p(proc))
mrb_gc_unregister(mrb, proc);
mrb = other.mrb;
proc = other.proc;
if (mrb && !mrb_nil_p(proc))
mrb_gc_register(mrb, proc);
}
return *this;
}
Block(Block &&other) noexcept
: mrb(other.mrb), proc(other.proc) {
other.mrb = nullptr;
other.proc = mrb_nil_value();
}
Block &operator=(Block &&other) noexcept {
if (this != &other) {
if (mrb && !mrb_nil_p(proc))
mrb_gc_unregister(mrb, proc);
mrb = other.mrb;
proc = other.proc;
other.mrb = nullptr;
other.proc = mrb_nil_value();
}
return *this;
}
void set_proc(mrb_state *mrb_, mrb_value new_proc) {
if (!mrb_nil_p(proc) && mrb)
mrb_gc_unregister(mrb, proc);
mrb = mrb_;
proc = new_proc;
mrb_gc_register(mrb, proc);
}
mrb_value call(int argc = 0, mrb_value *argv = nullptr) const {
if (mrb_nil_p(proc))
return mrb_nil_value();
mrb_value result = mrb_funcall_argv(mrb, proc, mrb_intern_cstr(mrb, "call"), argc, argv);
if (!mrb->exc)
return result;
mrb_value exc = mrb_obj_value(mrb->exc);
mrb_value msg = mrb_funcall(mrb, exc, "message", 0);
std::string error;
if (mrb_string_p(msg))
error.assign(RSTRING_PTR(msg), RSTRING_LEN(msg));
auto *fatal_class = mrb_class_get(mrb, "FatalError");
if (mrb_obj_is_kind_of(mrb, exc, fatal_class)) {
mrb_value code =
mrb_iv_get(mrb, exc, mrb_intern_lit(mrb, "@code"));
mrb->exc = nullptr;
int c = 1;
if (mrb_fixnum_p(code))
c = mrb_fixnum(code);
throw fatal_error(error, c);
}
auto *ed_class = mrb_class_get(mrb, "EdError");
if (mrb_obj_is_kind_of(mrb, exc, ed_class)) {
mrb->exc = nullptr;
throw ed_error(error);
}
mrb->exc = nullptr;
throw ed_error("Ruby Exception: " + error);
}
};
void register_basic(BEd &ctx);
std::string run(BEd &ctx, const std::string &str);
}; // namespace bed::internal::scripting
+19 -8
View File
@@ -48,6 +48,7 @@ struct ParseState {
uint64_t line, uint64_t original, uint64_t final uint64_t line, uint64_t original, uint64_t final
); );
static ParseState *concat(Language &lang, ParseState *a, ParseState *b); static ParseState *concat(Language &lang, ParseState *a, ParseState *b);
static void *state_before(Language &lang, ParseState *root, vase::Shard *vase, uint64_t line);
}; };
struct ParseStateBranch : ParseState { struct ParseStateBranch : ParseState {
@@ -83,18 +84,25 @@ struct ParsePieceBuilder {
std::vector<ParseState *> pieces; std::vector<ParseState *> pieces;
std::vector<uint16_t> blocks; std::vector<uint16_t> blocks;
void *piece_state{nullptr}; void *piece_state{nullptr};
void *prev_state{nullptr};
uint64_t chunk_start{0}; uint64_t chunk_start{0};
uint64_t chunk_lines{0}; uint64_t chunk_lines{0};
ParsePieceBuilder(Language &lang, uint64_t first_line) ParsePieceBuilder(Language &lang, uint64_t first_line, void *entry_state)
: lang(lang), chunk_start(first_line) {} : lang(lang), chunk_start(first_line) {
void add( prev_state = lang.copy(entry_state);
void *state, }
uint64_t line, ~ParsePieceBuilder() {
const std::vector<ParseEvent> &events if (prev_state)
) { lang.destroy(prev_state);
if (piece_state)
lang.destroy(piece_state);
}
ParsePieceBuilder(const ParsePieceBuilder &) = delete;
ParsePieceBuilder &operator=(const ParsePieceBuilder &) = delete;
void add(void *state, uint64_t line, const std::vector<ParseEvent> &events) {
if (chunk_lines == 0) { if (chunk_lines == 0) {
chunk_start = line; chunk_start = line;
piece_state = lang.copy(state); piece_state = lang.copy(prev_state);
} }
for (const auto &ev : events) { for (const auto &ev : events) {
blocks.push_back( blocks.push_back(
@@ -103,6 +111,9 @@ struct ParsePieceBuilder {
); );
} }
++chunk_lines; ++chunk_lines;
if (prev_state)
lang.destroy(prev_state);
prev_state = lang.copy(state);
if (chunk_lines == ParseStateLeaf::MAX_CHUNK) if (chunk_lines == ParseStateLeaf::MAX_CHUNK)
flush(); flush();
} }
+1
View File
@@ -13,6 +13,7 @@ ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang);
ParserSnapshot retain(const ParserSnapshot &snap); ParserSnapshot retain(const ParserSnapshot &snap);
void release(ParserSnapshot &snap); void release(ParserSnapshot &snap);
void *state_before(const ParserSnapshot &snap, vase::Shard *vase, uint64_t line);
uint64_t next_closing(const ParserSnapshot &snap, uint64_t line); uint64_t next_closing(const ParserSnapshot &snap, uint64_t line);
uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line); uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line);
+2 -1
View File
@@ -24,7 +24,8 @@ struct alignas(2) RubyState {
DEF_NAME = 0b10, DEF_NAME = 0b10,
MODULE_NAME = 0b11 MODULE_NAME = 0b11
}; };
static constexpr uint8_t NEWLINE = 1 << 5; static constexpr uint8_t NEWLINE = 1 << 4;
static constexpr uint8_t ALLOW_ESCAPE = 1 << 5;
static constexpr uint8_t ALLOW_INTERPOLATION = 1 << 6; static constexpr uint8_t ALLOW_INTERPOLATION = 1 << 6;
static constexpr uint8_t EXPECTING_EXPRESSION = 1 << 7; static constexpr uint8_t EXPECTING_EXPRESSION = 1 << 7;
uint8_t flags = 0; uint8_t flags = 0;
+5
View File
@@ -68,6 +68,11 @@ const static std::vector<std::string> builtins = {
}; };
const static std::vector<std::string> methods = { const static std::vector<std::string> methods = {
// BEd methods.
"handle",
"register",
"unregister",
// Normal:
"abort", "abort",
"at_exit", "at_exit",
"binding", "binding",
+3 -3
View File
@@ -24,9 +24,9 @@ struct Shard {
static void retain(Shard *n); static void retain(Shard *n);
static void release(Shard *n); static void release(Shard *n);
static Shard *from_file(const std::filesystem::path &path, bool posix_ending); static Shard *from_file(const std::filesystem::path &path);
static Shard *from_string(const char *data, uint64_t len, bool posix_ending); static Shard *from_string(const char *data, uint64_t len);
static Shard *from_command(const char *cmd, bool posix_ending); static Shard *from_command(const char *cmd);
static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset); static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset);
static Shard *concat(Shard *a, Shard *b); static Shard *concat(Shard *a, Shard *b);
+19
View File
@@ -4,10 +4,29 @@
#include <mruby.h> #include <mruby.h>
#include <mruby/array.h> #include <mruby/array.h>
#include <mruby/boxing_word.h>
#include <mruby/class.h>
#include <mruby/common.h>
#include <mruby/compile.h> #include <mruby/compile.h>
#include <mruby/data.h>
#include <mruby/dump.h>
#include <mruby/error.h>
#include <mruby/gc.h>
#include <mruby/hash.h> #include <mruby/hash.h>
#include <mruby/internal.h>
#include <mruby/irep.h> #include <mruby/irep.h>
#include <mruby/numeric.h>
#include <mruby/object.h>
#include <mruby/opcode.h>
#include <mruby/presym.h>
#include <mruby/proc.h>
#include <mruby/range.h>
#include <mruby/re.h>
#include <mruby/string.h> #include <mruby/string.h>
#include <mruby/throw.h>
#include <mruby/value.h>
#include <mruby/variable.h>
#include <mruby/version.h>
#include <pcre2.h> #include <pcre2.h>
extern "C" { extern "C" {
#include <grapheme.h> #include <grapheme.h>
+3
View File
@@ -0,0 +1,3 @@
register :happy, address: :none do
puts "be nice"
end
+73
View File
@@ -0,0 +1,73 @@
#include "bed.h"
namespace bed {
BEd::BEd(std::vector<std::string> args)
: mrb(this), theme(internal::theme::Theme::default_theme()), io(*this) {
std::string prompt_ = "";
std::string file = "";
bool suppress = false;
bool color = true;
for (size_t i = 1; i < args.size(); i++) {
if (args[i] == "-p") {
i++;
if (i >= args.size())
throw fatal_error("Prompt not specified!", 1);
prompt_ = args[i];
} else if (args[i] == "-s") {
suppress = true;
} else if (args[i] == "--no-color") {
color = false;
} else if (args[i] == "-v" || args[i] == "--verbose") {
help_mode = true;
} else if (args[i] == "-h" || args[i] == "--help") {
print_help();
throw fatal_error("", 0);
} else {
if (file.size())
throw fatal_error("Invalid arguments given.", 1);
file = args[i];
}
}
if (prompt_ != "")
prompt = [p = std::move(prompt_)](BEd &) { return p; };
else
prompt_mode = false;
suppress_mode = suppress;
const char *no_color = getenv("NO_COLOR");
if (no_color && *no_color != '\0')
color = false;
const char *colorterm = getenv("COLORTERM");
if (colorterm
&& strcmp(colorterm, "truecolor") != 0
&& strcmp(colorterm, "24bit") != 0)
color = false;
color_mode = color;
internal::functions::Function::register_posix(*this);
internal::functions::Function::register_extented(*this);
internal::functions::Suffix::register_suffixes(*this);
internal::scripting::register_basic(*this);
languages["ruby"] = new internal::syntax::Language(internal::syntax::ruby::lang_ruby());
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
current() = {"default", 0};
try {
if (file != "")
handle(":default:E " + file, false);
} catch (ed_error &e) {
io.write_line("?");
if (help_mode)
io.write_line(e.what());
last_help = e.what();
}
}
BEd::~BEd() {
for (auto &[_, buffer] : buffers)
delete buffer;
for (auto &[_, lang] : languages)
delete lang;
}
void BEd::print_help() {
io.write("BEd - A line editor.\n");
}
} // namespace bed
+115
View File
@@ -0,0 +1,115 @@
#include "bed.h"
#include "internal/parser/parser.h"
namespace bed {
void BEd::handle(std::string_view cmd, bool eof) {
if (eof && cmd.empty()) {
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
return;
}
internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this);
if (c.temp_address) {
marks.get(251) = marks.get(250);
prev_2 = prev_1;
temporary_current = true;
}
internal::buffer::Address address;
switch (c.function->address_kind) {
case internal::functions::Function::AddressKind::None: {
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_line(*this, vec);
if (!a.has_value())
a = current();
}
address = a->buffername;
} break;
case internal::functions::Function::AddressKind::Line: {
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_line(*this, vec);
if (!a.has_value())
a = current();
}
address = *a;
} break;
case internal::functions::Function::AddressKind::Range: {
auto a = internal::parser::AddressPromise::get_range(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_range(*this, vec);
if (!a.has_value())
a = internal::buffer::Range(current(), current());
}
address = *a;
} break;
}
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_line(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = current();
} else if (std::holds_alternative<internal::buffer::Range>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_range(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = internal::buffer::Range(current(), current());
}
if (!c.function->accept_zero) {
if (std::holds_alternative<internal::buffer::Line>(address)) {
if (std::get<internal::buffer::Line>(address).number == 0)
throw ed_error("Line number can't be zero.");
} else if (std::holds_alternative<internal::buffer::Range>(address)) {
auto r = std::get<internal::buffer::Range>(address);
if (r.start == 0 || r.end == 0)
throw ed_error("Line number can't be zero.");
}
}
internal::vase::Shard *text = nullptr;
if (c.function->input_mode == internal::functions::Function::InputMode::Text) {
internal::vase::Shard *vase = nullptr;
internal::syntax::Language *lang = nullptr;
void *state = nullptr;
if (c.function->pre_text_mode)
std::tie(vase, lang, state) = c.function->pre_text_mode(*this, address, c.argument);
internal::ui::text_mode::TextMode tm(*this, vase, lang, state);
auto [a, b] = tm.run();
if (!b) {
text = a;
} else {
if (a) {
auto p = internal::syntax::make_parser(a, a->lines + 1, lang);
auto cancel_buf = new internal::buffer::ReadonlyBuffer("cancel", a, p);
internal::syntax::release(p);
buffers["cancel"] = cancel_buf;
cancel_buf->useless = false;
internal::vase::Shard::release(a);
}
throw ed_error("Operation cancelled.");
}
}
if (c.function->handle)
c.function->handle(*this, address, text, c.argument, nullptr);
if (c.suffix)
c.suffix->handle(*this);
if (c.temp_address)
temporary_current = false;
for (auto it = buffers.begin(); it != buffers.end();) {
internal::buffer::Buffer *buf = it->second;
if (buf->waste()) {
delete buf;
it = buffers.erase(it);
} else {
++it;
}
}
}
} // namespace bed
+1 -181
View File
@@ -2,82 +2,13 @@
#include "internal/parser/parser.h" #include "internal/parser/parser.h"
namespace bed { namespace bed {
BEd::BEd(std::vector<std::string> args)
: theme(internal::theme::Theme::default_theme()), io(*this) {
std::string prompt_ = "";
std::string file = "";
bool suppress = false;
bool color = true;
for (size_t i = 1; i < args.size(); i++) {
if (args[i] == "-p") {
i++;
if (i >= args.size())
throw fatal_error("Prompt not specified!", 1);
prompt_ = args[i];
} else if (args[i] == "-s") {
suppress = true;
} else if (args[i] == "--no-color") {
color = false;
} else if (args[i] == "-v" || args[i] == "--verbose") {
help_mode = true;
} else if (args[i] == "-h" || args[i] == "--help") {
print_help();
throw fatal_error("", 0);
} else {
if (file.size())
throw fatal_error("Invalid arguments given.", 1);
file = args[i];
}
}
if (prompt_ != "")
prompt = [p = std::move(prompt_)](BEd &) { return p; };
else
prompt_mode = false;
suppress_mode = suppress;
const char *no_color = getenv("NO_COLOR");
if (no_color && *no_color != '\0')
color = false;
const char *colorterm = getenv("COLORTERM");
if (colorterm
&& strcmp(colorterm, "truecolor") != 0
&& strcmp(colorterm, "24bit") != 0)
color = false;
color_mode = color;
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());
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
current() = {"default", 0};
try {
if (file != "")
handle(":default:E " + file, false);
} catch (ed_error &e) {
io.write_line("?");
if (help_mode)
io.write_line(e.what());
last_help = e.what();
}
}
BEd::~BEd() {
for (auto &[_, buffer] : buffers)
delete buffer;
for (auto &[_, lang] : languages)
delete lang;
}
void BEd::print_help() {
io.write("BEd - A line editor.\n");
}
void BEd::run() { void BEd::run() {
while (true) { while (true) {
internal::ui::CommandIO command(*this); internal::ui::CommandIO command(*this);
auto [cmd, eof] = command.run(); auto [cmd, eof] = command.run();
try { try {
handle(cmd, eof); handle(cmd, eof);
} catch (ed_error &e) { } catch (const ed_error &e) {
io.apply(internal::io::Token::Warning); io.apply(internal::io::Token::Warning);
io.write_line("?"); io.write_line("?");
if (help_mode) if (help_mode)
@@ -88,117 +19,6 @@ void BEd::run() {
} }
} }
void BEd::handle(std::string_view cmd, bool eof) {
if (eof && cmd.empty()) {
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
return;
}
internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this);
if (c.temp_address) {
marks.get(251) = marks.get(250);
prev_2 = prev_1;
temporary_current = true;
}
internal::buffer::Address address;
switch (c.function->address_kind) {
case internal::functions::Function::AddressKind::None: {
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_line(*this, vec);
if (!a.has_value())
a = current();
}
address = a->buffername;
} break;
case internal::functions::Function::AddressKind::Line: {
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_line(*this, vec);
if (!a.has_value())
a = current();
}
address = *a;
} break;
case internal::functions::Function::AddressKind::Range: {
auto a = internal::parser::AddressPromise::get_range(*this, c.addresses);
if (!a.has_value()) {
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
a = internal::parser::AddressPromise::get_range(*this, vec);
if (!a.has_value())
a = internal::buffer::Range(current(), current());
}
address = *a;
} break;
}
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_line(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = current();
} else if (std::holds_alternative<internal::buffer::Range>(c.argument)) {
if (c.argument_addresses.empty())
throw ed_error("Function needs address argument.");
auto a = internal::parser::AddressPromise::get_range(*this, c.argument_addresses);
if (a.has_value())
c.argument = *a;
else
c.argument = internal::buffer::Range(current(), current());
}
if (!c.function->accept_zero) {
if (std::holds_alternative<internal::buffer::Line>(address)) {
if (std::get<internal::buffer::Line>(address).number == 0)
throw ed_error("Line number can't be zero.");
} else if (std::holds_alternative<internal::buffer::Range>(address)) {
auto r = std::get<internal::buffer::Range>(address);
if (r.start == 0 || r.end == 0)
throw ed_error("Line number can't be zero.");
}
}
internal::vase::Shard *text = nullptr;
if (c.function->input_mode == internal::functions::Function::InputMode::Text) {
internal::vase::Shard *vase = nullptr;
internal::syntax::Language *lang = nullptr;
void *state = nullptr;
if (c.function->pre_text_mode)
std::tie(vase, lang, state) = c.function->pre_text_mode(*this, address, c.argument);
internal::ui::text_mode::TextMode tm(*this, vase, lang, state);
auto [a, b] = tm.run();
if (!b) {
text = a;
} else {
if (a) {
auto p = internal::syntax::make_parser(a, a->lines + 1, lang);
auto cancel_buf = new internal::buffer::ReadonlyBuffer("cancel", a, p);
internal::syntax::release(p);
buffers["cancel"] = cancel_buf;
cancel_buf->useless = false;
internal::vase::Shard::release(a);
}
throw ed_error("Operation cancelled.");
}
}
if (c.function->handle)
c.function->handle(*this, address, text, c.argument, nullptr);
if (c.suffix)
c.suffix->handle(*this);
if (c.temp_address)
temporary_current = false;
for (auto it = buffers.begin(); it != buffers.end();) {
internal::buffer::Buffer *buf = it->second;
if (buf->waste()) {
delete buf;
it = buffers.erase(it);
} else {
++it;
}
}
}
internal::buffer::Buffer &BEd::buffer(const std::string &name) { internal::buffer::Buffer &BEd::buffer(const std::string &name) {
if (name.empty()) if (name.empty())
throw ed_error("Can't have empty buffer name"); throw ed_error("Can't have empty buffer name");
+35 -12
View File
@@ -15,6 +15,8 @@ GenericBuffer::~GenericBuffer() {
void GenericBuffer::list_history(BEd &ctx) { void GenericBuffer::list_history(BEd &ctx) {
uint64_t current = base_version + undo_stack.size(); uint64_t current = base_version + undo_stack.size();
uint64_t max_version = current + redo_stack.size();
size_t width = std::to_string(max_version).size();
for (size_t i = 0; i < undo_stack.size(); ++i) { for (size_t i = 0; i < undo_stack.size(); ++i) {
auto &item = undo_stack[i]; auto &item = undo_stack[i];
uint64_t version = base_version + i; uint64_t version = base_version + i;
@@ -22,8 +24,9 @@ void GenericBuffer::list_history(BEd &ctx) {
std::tm tm = *std::localtime(&time); std::tm tm = *std::localtime(&time);
ctx.io.write_line( ctx.io.write_line(
std::format( std::format(
" {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}", " {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
version, version,
width,
tm.tm_year + 1900, tm.tm_year + 1900,
tm.tm_mon + 1, tm.tm_mon + 1,
tm.tm_mday, tm.tm_mday,
@@ -39,8 +42,9 @@ void GenericBuffer::list_history(BEd &ctx) {
std::tm tm = *std::localtime(&time); std::tm tm = *std::localtime(&time);
ctx.io.write_line( ctx.io.write_line(
std::format( std::format(
"* {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}", "* {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
current, current,
width,
tm.tm_year + 1900, tm.tm_year + 1900,
tm.tm_mon + 1, tm.tm_mon + 1,
tm.tm_mday, tm.tm_mday,
@@ -58,8 +62,9 @@ void GenericBuffer::list_history(BEd &ctx) {
std::tm tm = *std::localtime(&time); std::tm tm = *std::localtime(&time);
ctx.io.write_line( ctx.io.write_line(
std::format( std::format(
" {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}", " {:>{}} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
version, version,
width,
tm.tm_year + 1900, tm.tm_year + 1900,
tm.tm_mon + 1, tm.tm_mon + 1,
tm.tm_mday, tm.tm_mday,
@@ -203,7 +208,27 @@ uint64_t GenericBuffer::prune(int keep) {
bool GenericBuffer::waste() { bool GenericBuffer::waste() {
return save_path.empty() return save_path.empty()
&& root == nullptr && root == nullptr
&& undo_stack.empty(); && undo_stack.empty()
&& parse.lang == nullptr;
}
void GenericBuffer::saved_hook() {
state = buffer::GenericBuffer::Unmodified;
}
void GenericBuffer::language(BEd &ctx, std::string name) {
syntax::Language *lang = nullptr;
if (name.size()) {
auto it = ctx.languages.find(name);
if (it == ctx.languages.end())
throw ed_error("Language not found.");
lang = it->second;
}
if (lang == parse.lang)
return;
snapshot("Change buffer language.");
syntax::release(parse);
parse = syntax::make_parser(root, lines(), lang);
} }
void GenericBuffer::load(BEd &ctx, vase::Shard *text) { void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
@@ -225,7 +250,7 @@ void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
} }
ctx.current() = {name, lines()}; ctx.current() = {name, lines()};
syntax::release(parse); syntax::release(parse);
parse = syntax::make_parser(root, lines(), ctx.languages["ruby"]); parse = syntax::make_parser(root, lines(), nullptr);
} }
void GenericBuffer::set_filename(std::filesystem::path path) { void GenericBuffer::set_filename(std::filesystem::path path) {
@@ -237,17 +262,15 @@ std::filesystem::path GenericBuffer::filename() {
}; };
void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) { void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
if (!text) snapshot(std::format("Insert {} lines after line {}", (text ? text->lines + 1 : 1), line));
return;
snapshot(std::format("Insert {} lines after line {}", text->lines + 1, line));
ctx.prev().buffername = name; ctx.prev().buffername = name;
ctx.prev().start = line + 1; ctx.prev().start = line + 1;
ctx.prev().end = line + text->lines + 1; ctx.prev().end = line + (text ? text->lines + 1 : 1);
ctx.current() = {name, line + text->lines + 1}; ctx.current() = {name, line + (text ? text->lines + 1 : 1)};
root = vase::insert(&ctx.append, root, text, line); root = vase::insert(&ctx.append, root, text, line);
ctx.marks.insert(name, line, text->lines + 1); ctx.marks.insert(name, line, (text ? text->lines + 1 : 1));
if (parse.lang) if (parse.lang)
syntax::insert(parse, root, line, text->lines + 1); syntax::insert(parse, root, line, (text ? text->lines + 1 : 1));
state = Modified; state = Modified;
} }
+15 -13
View File
@@ -8,15 +8,17 @@ bool ClipBuffer::waste() {
return false; return false;
} }
void ClipBuffer::saved_hook() {}
uint64_t ClipBuffer::lines() { 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; uint64_t lines = s ? s->lines + 1 : 0;
vase::Shard::release(s); vase::Shard::release(s);
return lines; return lines;
} }
uint64_t ClipBuffer::bytes() { 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; uint64_t length = s ? s->length + 1 : 0;
vase::Shard::release(s); vase::Shard::release(s);
return length; return length;
@@ -55,7 +57,7 @@ void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
ctx.prev().buffername = name; ctx.prev().buffername = name;
ctx.prev().start = line + 1; ctx.prev().start = line + 1;
ctx.prev().end = line + (text ? text->lines + 1 : 0); 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); s = vase::insert(&ctx.append, s, text, line);
clip_write(s); clip_write(s);
vase::Shard::release(s); vase::Shard::release(s);
@@ -63,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) { 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); s = vase::erase(s, start_line, end_line);
clip_write(s); clip_write(s);
ctx.prev().buffername = name; ctx.prev().buffername = name;
@@ -83,7 +85,7 @@ void ClipBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint6
ctx.prev().end = start_line + text->lines; ctx.prev().end = start_line + text->lines;
uint64_t new_count = text->lines + 1; uint64_t new_count = text->lines + 1;
uint64_t old_count = end_line - start_line + 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); s = vase::replace(s, text, start_line, end_line);
clip_write(s); clip_write(s);
vase::Shard::release(s); vase::Shard::release(s);
@@ -97,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) { 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); s = vase::join(s, start_line, end_line);
clip_write(s); clip_write(s);
vase::Shard::release(s); vase::Shard::release(s);
@@ -114,7 +116,7 @@ void ClipBuffer::substitute(
ctx.prev().buffername = name; ctx.prev().buffername = name;
ctx.prev().start = start_line; ctx.prev().start = start_line;
ctx.prev().end = end_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( s = vase::substitute(
&ctx.append, &ctx.append,
s, s,
@@ -136,21 +138,21 @@ void ClipBuffer::substitute(
} }
vase::Shard *ClipBuffer::copy(uint64_t start_line, uint64_t end_line) { 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 *o = vase::copy(s, start_line, end_line);
vase::Shard::release(s); vase::Shard::release(s);
return o; return o;
} }
uint64_t ClipBuffer::find_next(std::string_view pattern, uint64_t start) { 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); uint64_t line = vase::find_next(s, pattern, start);
vase::Shard::release(s); vase::Shard::release(s);
return line; return line;
} }
uint64_t ClipBuffer::find_prev(std::string_view pattern, uint64_t start) { 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); uint64_t line = vase::find_prev(s, pattern, start);
vase::Shard::release(s); vase::Shard::release(s);
return line; return line;
@@ -174,7 +176,7 @@ void ClipBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name; ctx.prev().buffername = name;
ctx.prev().start = start_line; ctx.prev().start = start_line;
ctx.prev().end = end_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); vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line) while (it.next() && start_line++ <= end_line)
ctx.io.write_line(it.line); ctx.io.write_line(it.line);
@@ -188,7 +190,7 @@ void ClipBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line)
uint8_t width = 1; uint8_t width = 1;
for (uint64_t n = end_line; n >= 10; n /= 10) for (uint64_t n = end_line; n >= 10; n /= 10)
++width; ++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); vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line <= end_line) while (it.next() && start_line <= end_line)
ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line)); ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line));
@@ -199,7 +201,7 @@ void ClipBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name; ctx.prev().buffername = name;
ctx.prev().start = start_line; ctx.prev().start = start_line;
ctx.prev().end = end_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); vase::Iterator it(s, start_line - 1, Direction::Forward);
while (it.next() && start_line++ <= end_line) while (it.next() && start_line++ <= end_line)
ctx.io.write_line(list_string(it.line)); ctx.io.write_line(list_string(it.line));
+102 -7
View File
@@ -173,21 +173,83 @@ void Function::register_extented(BEd &ctx) {
ctx.functions.insert( ctx.functions.insert(
"`", "`",
Function{ Function{
.address_kind = Function::AddressKind::None, .address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Ruby, .argument_kind = Function::ArgumentKind::Ruby,
.input_mode = Function::InputMode::None, .input_mode = Function::InputMode::None,
.desc = "Execute some ruby code", .desc = "Execute given ruby code.",
.default_address = "", .default_address = "0,0",
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto &addr = std::get<buffer::Range>(addr_);
auto &arg = std::get<RubyArg>(arg_);
auto pre_code = "$START=" + std::to_string(addr.start)
+ ";$END=" + std::to_string(addr.end)
+ ";$BUFNAME=\"" + addr.buffername + "\"";
scripting::run(ctx, pre_code);
auto line = scripting::run(ctx, arg.cmd);
ctx.io.write("=> ", 3);
auto parser = syntax::MiniParser(
*ctx.languages["ruby"],
vase::Shard::from_string(line.data(), line.size()),
nullptr
);
const auto &tokens = parser.lines[0].second;
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);
ctx.io.apply(token.type);
ctx.io.write(line.data() + start, end - start);
ctx.io.reset();
cursor = end;
}
if (cursor < line.size())
ctx.io.write(line.data() + cursor, line.size() - cursor);
ctx.io.write_line("");
},
}
);
ctx.functions.insert(
"``",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::None,
.desc = "Execute addressed lines as ruby code.",
.default_address = "1,$",
.accept_zero = false, .accept_zero = false,
.pre_text_mode = nullptr, .pre_text_mode = nullptr,
.handle = []( .handle = [](
BEd &, BEd &ctx,
const buffer::Address &, const buffer::Address &addr_,
vase::Shard *, vase::Shard *,
const Argument &, const Argument &,
std::vector<buffer::Line> * std::vector<buffer::Line> *
) { ) {
// TODO: connect up to mruby. auto &addr = std::get<buffer::Range>(addr_);
auto &buf = ctx.buffer(addr.buffername);
auto code = buf.copy(addr.start, addr.end);
ctx.prev().buffername = addr.buffername;
ctx.prev().start = addr.start;
ctx.prev().end = addr.end;
ctx.current() = {addr.buffername, addr.end};
auto pre_code = "$START=" + std::to_string(addr.start)
+ ";$END=" + std::to_string(addr.end)
+ ";$BUFNAME=\"" + addr.buffername + "\"";
scripting::run(ctx, pre_code);
scripting::run(ctx, vase::to_string(code));
vase::Shard::release(code);
}, },
} }
); );
@@ -225,7 +287,7 @@ void Function::register_extented(BEd &ctx) {
str[i - 1] = '\n'; str[i - 1] = '\n';
continue; continue;
} }
if (str[i] == '$' && i < str.size() && '1' <= str[i + 1] && str[i + 1] <= '4') { if (str[i] == '$' && i + 1 < str.size() && '1' <= str[i + 1] && str[i + 1] <= '4') {
char c = str[i + 1]; char c = str[i + 1];
str.erase(i, 2); str.erase(i, 2);
switch (c) { switch (c) {
@@ -432,5 +494,38 @@ void Function::register_extented(BEd &ctx) {
} }
} }
); );
ctx.functions.insert(
"lang",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Set buffer language",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &arg_,
std::vector<buffer::Line> *
) {
auto &addr = std::get<std::string>(addr_);
auto name = std::get<std::string>(arg_);
const auto first = name.find_first_not_of(" \t");
const auto last = name.find_last_not_of(" \t");
if (first == std::string::npos)
name.clear();
else
name = name.substr(first, last - first + 1);
auto &buf_ = ctx.buffer(addr);
if (buf_.kind != buffer::Buffer::Kind::Generic)
throw ed_error("Can't set language to buffer.");
auto &buf = *(buffer::GenericBuffer *)&buf_;
buf.language(ctx, name);
}
}
);
} }
} // namespace bed::internal::functions } // namespace bed::internal::functions
+42 -20
View File
@@ -37,7 +37,19 @@ void Function::register_posix(BEd &ctx) {
.desc = "Append text to a line", .desc = "Append text to a line",
.default_address = ".", .default_address = ".",
.accept_zero = true, .accept_zero = true,
.pre_text_mode = nullptr, .pre_text_mode = [](
BEd &ctx,
const buffer::Address &addr_,
const Argument &
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
const auto &addr = std::get<buffer::Line>(addr_);
auto &buf_ = ctx.buffer(addr.buffername);
if (buf_.kind != buffer::Buffer::Kind::Generic)
return {nullptr, nullptr, nullptr};
auto &buf = *(buffer::GenericBuffer *)&buf_;
void *state = syntax::state_before(buf.parse, buf.root, addr.number);
return {nullptr, buf.parse.lang, state};
},
.handle = []( .handle = [](
BEd &ctx, BEd &ctx,
const buffer::Address &addr_, const buffer::Address &addr_,
@@ -47,8 +59,7 @@ void Function::register_posix(BEd &ctx) {
) { ) {
auto addr = std::get<buffer::Line>(addr_); auto addr = std::get<buffer::Line>(addr_);
ctx.buffer(addr.buffername).append(ctx, text, addr.number); ctx.buffer(addr.buffername).append(ctx, text, addr.number);
vase::Shard::release(text); vase::Shard::release(text); }
}
} }
); );
ctx.functions.insert( ctx.functions.insert(
@@ -70,9 +81,7 @@ void Function::register_posix(BEd &ctx) {
if (buf_.kind != buffer::Buffer::Kind::Generic) if (buf_.kind != buffer::Buffer::Kind::Generic)
return {buf_.copy(addr.start, addr.end), nullptr, nullptr}; return {buf_.copy(addr.start, addr.end), nullptr, nullptr};
auto &buf = *(buffer::GenericBuffer *)&buf_; auto &buf = *(buffer::GenericBuffer *)&buf_;
if (!buf.parse.lang) void *state = syntax::state_before(buf.parse, buf.root, addr.start - 1);
return {buf.copy(addr.start, addr.end), nullptr, nullptr};
void *state = nullptr; // TODO: buf.parse.root.get_at(addr.start);
return {buf.copy(addr.start, addr.end), buf.parse.lang, state}; return {buf.copy(addr.start, addr.end), buf.parse.lang, state};
}, },
.handle = []( .handle = [](
@@ -135,17 +144,17 @@ void Function::register_posix(BEd &ctx) {
vase::Shard *s = nullptr; vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) { if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<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); buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) { } else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd; auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string()); 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 { } else {
auto path = buf.filename(); auto path = buf.filename();
if (path.empty()) if (path.empty())
throw ed_error("Need filename."); throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true); s = vase::Shard::from_file(path);
}; };
try { try {
buf.load(ctx, s); buf.load(ctx, s);
@@ -181,17 +190,17 @@ void Function::register_posix(BEd &ctx) {
vase::Shard *s = nullptr; vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) { if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<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); buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) { } else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd; auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string()); 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 { } else {
auto path = buf.filename(); auto path = buf.filename();
if (path.empty()) if (path.empty())
throw ed_error("Need filename."); throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true); s = vase::Shard::from_file(path);
}; };
try { try {
buf.load(ctx, s); buf.load(ctx, s);
@@ -291,7 +300,21 @@ void Function::register_posix(BEd &ctx) {
.desc = "Insert text before a line", .desc = "Insert text before a line",
.default_address = ".", .default_address = ".",
.accept_zero = true, .accept_zero = true,
.pre_text_mode = nullptr, .pre_text_mode = [](
BEd &ctx,
const buffer::Address &addr_,
const Argument &
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
auto addr = std::get<buffer::Line>(addr_);
if (addr.number)
addr.number--;
auto &buf_ = ctx.buffer(addr.buffername);
if (buf_.kind != buffer::Buffer::Kind::Generic)
return {nullptr, nullptr, nullptr};
auto &buf = *(buffer::GenericBuffer *)&buf_;
void *state = syntax::state_before(buf.parse, buf.root, addr.number);
return {nullptr, buf.parse.lang, state};
},
.handle = []( .handle = [](
BEd &ctx, BEd &ctx,
const buffer::Address &addr_, const buffer::Address &addr_,
@@ -303,8 +326,7 @@ void Function::register_posix(BEd &ctx) {
if (addr.number) if (addr.number)
addr.number--; addr.number--;
ctx.buffer(addr.buffername).append(ctx, text, addr.number); ctx.buffer(addr.buffername).append(ctx, text, addr.number);
vase::Shard::release(text); vase::Shard::release(text); }
}
} }
); );
ctx.functions.insert( ctx.functions.insert(
@@ -394,8 +416,7 @@ void Function::register_posix(BEd &ctx) {
auto addr = std::get<buffer::Range>(addr_); auto addr = std::get<buffer::Range>(addr_);
auto arg = std::get<buffer::Line>(arg_); auto arg = std::get<buffer::Line>(arg_);
if (arg.buffername == addr.buffername if (arg.buffername == addr.buffername
&& addr.start <= arg.number && arg.number >= addr.start && arg.number < addr.end)
&& addr.end < arg.number)
throw ed_error("Can't move lines within themselves."); throw ed_error("Can't move lines within themselves.");
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end); auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
ctx.mark(252, arg); ctx.mark(252, arg);
@@ -567,18 +588,18 @@ void Function::register_posix(BEd &ctx) {
vase::Shard *s = nullptr; vase::Shard *s = nullptr;
if (std::holds_alternative<std::filesystem::path>(arg)) { if (std::holds_alternative<std::filesystem::path>(arg)) {
auto path = std::get<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()) if (buf.filename().empty())
buf.set_filename(path); buf.set_filename(path);
} else if (std::holds_alternative<ShellArg>(arg)) { } else if (std::holds_alternative<ShellArg>(arg)) {
auto cmd = std::get<ShellArg>(arg).cmd; auto cmd = std::get<ShellArg>(arg).cmd;
ctx.escape_command(cmd, buf.filename().string()); 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 { } else {
auto path = buf.filename(); auto path = buf.filename();
if (path.empty()) if (path.empty())
throw ed_error("Need filename."); throw ed_error("Need filename.");
s = vase::Shard::from_file(path, true); s = vase::Shard::from_file(path);
}; };
try { try {
buf.append(ctx, s, addr.number); buf.append(ctx, s, addr.number);
@@ -735,6 +756,7 @@ void Function::register_posix(BEd &ctx) {
vase::Shard::release(text); vase::Shard::release(text);
throw; throw;
} }
buf.saved_hook();
if (!ctx.suppress_mode) if (!ctx.suppress_mode)
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0)); ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
} }
+149
View File
@@ -0,0 +1,149 @@
#include "internal/scripting/ruby.h"
#include "bed.h"
namespace bed::internal::scripting {
static mrb_value mrb_bed_exit(mrb_state *mrb, mrb_value) {
mrb_raise(mrb, E_RUNTIME_ERROR, "Use `handle(\"q\")` to quit.");
return mrb_nil_value();
}
static void raise_fatal(mrb_state *mrb, const fatal_error &e) {
struct RClass *klass = mrb_class_get(mrb, "FatalError");
mrb_value exc = mrb_exc_new_str(mrb, klass, mrb_str_new_cstr(mrb, e.what()));
mrb_iv_set(mrb, exc, mrb_intern_lit(mrb, "@code"), mrb_fixnum_value(e.code));
mrb_exc_raise(mrb, exc);
}
static mrb_value mrb_bed_handle(mrb_state *mrb, mrb_value) {
auto &ctx = *(BEd *)mrb->ud;
const char *command;
mrb_int len;
mrb_get_args(mrb, "s", &command, &len);
std::string_view cmd(command, len);
try {
ctx.handle(cmd, false);
} catch (const ed_error &e) {
mrb_raise(mrb, mrb_class_get(mrb, "EdError"), e.what());
} catch (const fatal_error &f) {
raise_fatal(mrb, f);
}
return mrb_nil_value();
}
static mrb_value hash_get(mrb_state *mrb, mrb_value hash, const char *name) {
if (mrb_nil_p(hash))
return mrb_nil_value();
return mrb_hash_get(mrb, hash, mrb_symbol_value(mrb_intern_cstr(mrb, name)));
}
static functions::Function::AddressKind parse_address_kind(mrb_state *mrb, mrb_value value) {
if (mrb_nil_p(value))
return functions::Function::AddressKind::None;
if (!mrb_symbol_p(value))
mrb_raise(mrb, E_TYPE_ERROR, "address must be a Symbol");
auto name = mrb_sym_name(mrb, mrb_symbol(value));
if (strcmp(name, "none") == 0)
return functions::Function::AddressKind::None;
if (strcmp(name, "line") == 0)
return functions::Function::AddressKind::Line;
if (strcmp(name, "range") == 0)
return functions::Function::AddressKind::Range;
mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid address type: :%s", name);
return functions::Function::AddressKind::None;
}
static mrb_value mrb_bed_register(mrb_state *mrb, mrb_value) {
auto &ctx = *(BEd *)mrb->ud;
mrb_sym r_cmd_name;
mrb_value proc;
mrb_value opts = mrb_nil_value();
mrb_get_args(mrb, "n&|H", &r_cmd_name, &proc, &opts);
mrb_int cmd_name_len;
const char *name = mrb_sym_name_len(mrb, r_cmd_name, &cmd_name_len);
std::string_view cmd_name(name, cmd_name_len);
std::string desc;
mrb_value r_desc = hash_get(mrb, opts, "desc");
if (mrb_string_p(r_desc))
desc.assign(RSTRING_PTR(r_desc), RSTRING_LEN(r_desc));
std::string default_address;
mrb_value r_default_address = hash_get(mrb, opts, "default");
if (mrb_string_p(r_default_address))
default_address.assign(RSTRING_PTR(r_default_address), RSTRING_LEN(r_default_address));
ctx.functions.insert(
cmd_name,
functions::Function{
.address_kind = parse_address_kind(mrb, hash_get(mrb, opts, "address")),
.argument_kind = functions::Function::ArgumentKind::None,
.input_mode = functions::Function::InputMode::None,
.desc = desc,
.default_address = default_address,
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [b = Block(mrb, proc)](
BEd &,
const buffer::Address &,
vase::Shard *,
const functions::Function::Argument &,
std::vector<buffer::Line> *
) {
b.call();
}
}
);
return mrb_nil_value();
}
static mrb_value mrb_bed_unregister(mrb_state *mrb, mrb_value) {
auto &ctx = *(BEd *)mrb->ud;
mrb_sym r_cmd_name;
mrb_get_args(mrb, "n", &r_cmd_name);
mrb_int cmd_name_len;
const char *name = mrb_sym_name_len(mrb, r_cmd_name, &cmd_name_len);
std::string_view cmd_name(name, cmd_name_len);
ctx.functions.remove(cmd_name);
return mrb_nil_value();
}
void register_basic(BEd &ctx) {
auto mrb = ctx.mrb.state;
auto *bed_error =
mrb_define_class(mrb, "EdError", mrb_exc_get_id(mrb, MRB_ERROR_SYM(RuntimeError)));
mrb_define_class(mrb, "FatalError", bed_error);
mrb_define_method(mrb, mrb->kernel_module, "exit", mrb_bed_exit, MRB_ARGS_NONE());
mrb_define_method(mrb, mrb->kernel_module, "handle", mrb_bed_handle, MRB_ARGS_REQ(1));
mrb_define_method(mrb, mrb->kernel_module, "register", mrb_bed_register, MRB_ARGS_REQ(1) | MRB_ARGS_OPT(1) | MRB_ARGS_BLOCK());
mrb_define_method(mrb, mrb->kernel_module, "unregister", mrb_bed_unregister, MRB_ARGS_REQ(1));
}
std::string run(BEd &ctx, const std::string &str) {
mrb_state *mrb = ctx.mrb.state;
mrb_value result = mrb_load_nstring(mrb, str.data(), str.size());
if (!mrb->exc) {
mrb_value inspected = mrb_funcall(mrb, result, "inspect", 0);
std::string output(RSTRING_PTR(inspected), RSTRING_LEN(inspected));
return output;
}
mrb_value exc = mrb_obj_value(mrb->exc);
mrb_value msg = mrb_funcall(mrb, exc, "message", 0);
std::string error;
if (mrb_string_p(msg))
error.assign(RSTRING_PTR(msg), RSTRING_LEN(msg));
auto *fatal_class = mrb_class_get(mrb, "FatalError");
if (mrb_obj_is_kind_of(mrb, exc, fatal_class)) {
mrb_value code =
mrb_iv_get(mrb, exc, mrb_intern_lit(mrb, "@code"));
mrb->exc = nullptr;
int c = 1;
if (mrb_fixnum_p(code))
c = mrb_fixnum(code);
throw fatal_error(error, c);
}
auto *ed_class = mrb_class_get(mrb, "EdError");
if (mrb_obj_is_kind_of(mrb, exc, ed_class)) {
mrb->exc = nullptr;
throw ed_error(error);
}
mrb->exc = nullptr;
throw ed_error("Ruby Exception: " + error);
}
} // namespace bed::internal::scripting
+6 -12
View File
@@ -2,19 +2,11 @@
namespace bed::internal::syntax { namespace bed::internal::syntax {
Iterator::Iterator(uint64_t target, ParserSnapshot p, vase::Shard *vase) Iterator::Iterator(uint64_t target, ParserSnapshot p, vase::Shard *vase)
: snap(p) { : snap(p), at(target) {
uint64_t offset; at = target;
TreeCursor c = TreeCursor(*p.lang, p.root, target, &offset); if (snap.lang)
at = target - offset; state = ParseState::state_before(*snap.lang, snap.root, vase, at);
state = p.lang->copy(c.leaf->state);
it = vase::Iterator(vase, at, Direction::Forward); 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() { Iterator::~Iterator() {
@@ -46,6 +38,8 @@ Iterator &Iterator::operator=(Iterator &&other) {
} }
void Iterator::next() { void Iterator::next() {
if (!state)
return;
it->next(); it->next();
tokens.clear(); tokens.clear();
events.clear(); events.clear();
+1 -1
View File
@@ -8,7 +8,7 @@ MiniParser::MiniParser(Language &lang, vase::Shard *vase, void *initial_state)
void *state = lang.copy(start_state); void *state = lang.copy(start_state);
std::vector<io::Token> tokens; std::vector<io::Token> tokens;
std::vector<ParseEvent> events; std::vector<ParseEvent> events;
const uint64_t count = vase ? vase->lines + 1 : 0; const uint64_t count = vase ? vase->lines + 1 : 1;
lines.reserve(count); lines.reserve(count);
for (uint64_t i = 0; i < count; ++i) { for (uint64_t i = 0; i < count; ++i) {
it.next(); it.next();
+6
View File
@@ -22,6 +22,12 @@ void release(ParserSnapshot &snap) {
snap.root = nullptr; snap.root = nullptr;
} }
void *state_before(const ParserSnapshot &snap, vase::Shard *vase, uint64_t line) {
if (!snap.lang)
return nullptr;
return ParseState::state_before(*snap.lang, snap.root, vase, line);
}
uint64_t next_closing(const ParserSnapshot &snap, uint64_t line) { uint64_t next_closing(const ParserSnapshot &snap, uint64_t line) {
if (!snap.root || !snap.lang) if (!snap.root || !snap.lang)
return line + 10; return line + 10;
+26 -2
View File
@@ -108,7 +108,7 @@ ParseState *ParseState::splice(
void *state = lang.none_state(); void *state = lang.none_state();
std::vector<io::Token> tokens; std::vector<io::Token> tokens;
std::vector<ParseEvent> events; std::vector<ParseEvent> events;
ParsePieceBuilder builder(lang, 0); ParsePieceBuilder builder(lang, 0, state);
for (uint64_t at = 0; at < final; ++at) { for (uint64_t at = 0; at < final; ++at) {
it.next(); it.next();
tokens.clear(); tokens.clear();
@@ -136,7 +136,7 @@ ParseState *ParseState::splice(
uint64_t end_extra; uint64_t end_extra;
TreeCursor c = TreeCursor(lang, root, end_in_tree, &end_extra); TreeCursor c = TreeCursor(lang, root, end_in_tree, &end_extra);
uint64_t end_in_vase = line + final; uint64_t end_in_vase = line + final;
ParsePieceBuilder builder(lang, at); ParsePieceBuilder builder(lang, at, state);
while (at < end_in_vase + end_extra) { while (at < end_in_vase + end_extra) {
it.next(); it.next();
tokens.clear(); tokens.clear();
@@ -192,4 +192,28 @@ ParseState *ParseState::concat(Language &lang, ParseState *a, ParseState *b) {
} }
return balance(lang, new ParseStateBranch(a, b)); return balance(lang, new ParseStateBranch(a, b));
} }
void *ParseState::state_before(Language &lang, ParseState *root, vase::Shard *vase, uint64_t line) {
uint64_t offset;
uint64_t at;
void *state;
if (!root) {
at = 0;
state = lang.none_state();
} else {
TreeCursor c = TreeCursor(lang, root, line, &offset);
at = line - offset;
state = lang.copy(c.leaf->state);
}
vase::Iterator it(vase, at, Direction::Forward);
std::vector<io::Token> tokens;
std::vector<ParseEvent> events;
for (; at < line; ++at) {
it.next();
tokens.clear();
events.clear();
lang.parse(&state, it.line, at == 0, &tokens, &events);
}
return state;
}
} // namespace bed::internal::syntax } // namespace bed::internal::syntax
+97 -70
View File
@@ -35,71 +35,83 @@ inline uint8_t utf8_codepoint_width(unsigned char c) {
} }
bool handle_escapes(RubyParser &p, std::vector<io::Token> *tokens, uint32_t &start, bool string = true) { bool handle_escapes(RubyParser &p, std::vector<io::Token> *tokens, uint32_t &start, bool string = true) {
if (p.peek() == '\\') { if (p.peek() != '\\')
if (string) return false;
tokens->push_back({start, p.i, io::Token::String}); if (!(p.current().flags & RubyState::RubyInternalState::ALLOW_ESCAPE)) {
else
tokens->push_back({start, p.i, io::Token::Regexp});
start = p.i;
p.advance(); p.advance();
if (p.peek() == 'x') { if (p.peek() != '\'' && p.peek() != '\\')
p.advance(); return false;
if (is_hex(p.peek())) if (string)
p.advance(); tokens->push_back({start, p.i - 1, io::Token::String});
if (is_hex(p.peek())) else
p.advance(); tokens->push_back({start, p.i - 1, io::Token::Regexp});
} else if (p.peek() == 'u') { p.advance();
p.advance(); tokens->push_back({p.i - 2, p.i, io::Token::Escape});
if (p.peek() == '{') {
p.advance();
while (p.peek() != '}' && p.peek() != '\0')
p.advance();
if (p.peek() == '}')
p.advance();
} else {
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
}
} else if ('0' <= p.peek() && p.peek() <= '7') {
p.advance();
if ('0' <= p.peek() && p.peek() <= '7')
p.advance();
if ('0' <= p.peek() && p.peek() <= '7')
p.advance();
} else if (p.peek() == 'c') {
p.advance();
if (p.peek() != '\\')
p.advance();
} else if (p.peek() == 'M' || p.peek() == 'C') {
p.advance();
if (p.peek() == '-') {
p.advance();
if (p.peek() != '\\')
p.advance();
}
} else if (p.peek() == 'N') {
p.advance();
if (p.peek() == '{') {
p.advance();
while (p.peek() != '}' && p.peek() != '\0')
p.advance();
if (p.peek() == '}')
p.advance();
}
} else {
p.advance();
}
tokens->push_back({start, p.i, io::Token::Escape});
start = p.i; start = p.i;
return true; return true;
} }
return false; if (string)
tokens->push_back({start, p.i, io::Token::String});
else
tokens->push_back({start, p.i, io::Token::Regexp});
start = p.i;
p.advance();
if (p.peek() == 'x') {
p.advance();
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
} else if (p.peek() == 'u') {
p.advance();
if (p.peek() == '{') {
p.advance();
while (p.peek() != '}' && p.peek() != '\0')
p.advance();
if (p.peek() == '}')
p.advance();
} else {
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
if (is_hex(p.peek()))
p.advance();
}
} else if ('0' <= p.peek() && p.peek() <= '7') {
p.advance();
if ('0' <= p.peek() && p.peek() <= '7')
p.advance();
if ('0' <= p.peek() && p.peek() <= '7')
p.advance();
} else if (p.peek() == 'c') {
p.advance();
if (p.peek() != '\\')
p.advance();
} else if (p.peek() == 'M' || p.peek() == 'C') {
p.advance();
if (p.peek() == '-') {
p.advance();
if (p.peek() != '\\')
p.advance();
}
} else if (p.peek() == 'N') {
p.advance();
if (p.peek() == '{') {
p.advance();
while (p.peek() != '}' && p.peek() != '\0')
p.advance();
if (p.peek() == '}')
p.advance();
}
} else {
p.advance();
}
tokens->push_back({start, p.i, io::Token::Escape});
start = p.i;
return true;
}; };
bool handle_heredoc(RubyParser &p, std::vector<io::Token> *tokens, std::vector<ParseEvent> *events) { bool handle_heredoc(RubyParser &p, std::vector<io::Token> *tokens, std::vector<ParseEvent> *events) {
@@ -668,6 +680,7 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
p.current().delim_start = '\''; p.current().delim_start = '\'';
p.current().delim_end = '\''; p.current().delim_end = '\'';
p.current().flags &= ~RubyState::RubyInternalState::ALLOW_INTERPOLATION; p.current().flags &= ~RubyState::RubyInternalState::ALLOW_INTERPOLATION;
p.current().flags &= ~RubyState::RubyInternalState::ALLOW_ESCAPE;
p.advance(); p.advance();
return false; return false;
} }
@@ -678,6 +691,7 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
p.current().delim_start = '"'; p.current().delim_start = '"';
p.current().delim_end = '"'; p.current().delim_end = '"';
p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION; p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION;
p.current().flags |= RubyState::RubyInternalState::ALLOW_ESCAPE;
p.advance(); p.advance();
return false; return false;
} }
@@ -703,6 +717,7 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
char delim_start = '\0'; char delim_start = '\0';
char delim_end = '\0'; char delim_end = '\0';
bool allow_interp = true; bool allow_interp = true;
bool allow_escape = true;
int prefix_len = 1; int prefix_len = 1;
bool is_regexp = false; bool is_regexp = false;
switch (type) { switch (type) {
@@ -716,6 +731,7 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
case 'I': case 'I':
case 'W': case 'W':
allow_interp = true; allow_interp = true;
allow_escape = true;
prefix_len = 2; prefix_len = 2;
break; break;
case 'w': case 'w':
@@ -723,10 +739,12 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
case 'i': case 'i':
case 's': case 's':
allow_interp = false; allow_interp = false;
allow_escape = false;
prefix_len = 2; prefix_len = 2;
break; break;
default: default:
allow_interp = true; allow_interp = true;
allow_escape = true;
prefix_len = 1; prefix_len = 1;
break; break;
} }
@@ -768,6 +786,8 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
p.current().delim_end = delim_end; p.current().delim_end = delim_end;
if (allow_interp) if (allow_interp)
p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION; p.current().flags |= RubyState::RubyInternalState::ALLOW_INTERPOLATION;
if (allow_escape)
p.current().flags |= RubyState::RubyInternalState::ALLOW_ESCAPE;
p.current().lit_brace_level = 1; p.current().lit_brace_level = 1;
p.advance(prefix_len + 1); p.advance(prefix_len + 1);
return false; return false;
@@ -782,11 +802,6 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
p.ending = false; p.ending = false;
p.advance(); p.advance();
return false; return false;
case '\0':
if (p.ending)
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
p.advance();
return false;
default: default:
if ('0' <= p.peek() && p.peek() <= '9') { if ('0' <= p.peek() && p.peek() <= '9') {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION; p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
@@ -981,6 +996,11 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
| RubyState::RubyInternalState::DEF_NAME; | RubyState::RubyInternalState::DEF_NAME;
return false; return false;
} }
if (j > 3 && p.peek_str(3) == "to_") {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, io::Token::Function});
p.advance(j);
}
uint32_t start = p.i; uint32_t start = p.i;
if (p.peek(j) == ':') { if (p.peek(j) == ':') {
p.advance(j); p.advance(j);
@@ -1008,8 +1028,9 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
return false; return false;
if (p.peek(j) == '&' if (p.peek(j) == '&'
|| p.peek(j) == '%' || p.peek(j) == '%'
|| p.peek(j) == ':') { || p.peek(j) == ':'
if (p.peek(j + 1) == ' ' || p.peek(j + 1) == '>') || p.peek(j) == '?') {
if (p.peek(j + 1) == ' ' || p.peek(j + 1) == '\0')
return false; return false;
} else if (p.peek(j) == '-') { } else if (p.peek(j) == '-') {
if (p.peek(j + 1) != '>') if (p.peek(j + 1) != '>')
@@ -1025,7 +1046,6 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
|| p.peek(j) == '*' || p.peek(j) == '*'
|| p.peek(j) == '/' || p.peek(j) == '/'
|| p.peek(j) == '=' || p.peek(j) == '='
|| p.peek(j) == '?'
|| p.peek(j) == '|' || p.peek(j) == '|'
|| p.peek(j) == '^' || p.peek(j) == '^'
|| p.peek(j) == '<' || p.peek(j) == '<'
@@ -1061,7 +1081,7 @@ void ruby_parse(
std::vector<ParseEvent> *events std::vector<ParseEvent> *events
) { ) {
RubyParser p(v_state, line); RubyParser p(v_state, line);
while (p.i <= p.len()) { while (p.i < p.len()) {
p.op_last = p.set_op_last; p.op_last = p.set_op_last;
p.set_op_last = false; p.set_op_last = false;
if (p.current().state == RubyState::RubyInternalState::END) if (p.current().state == RubyState::RubyInternalState::END)
@@ -1099,6 +1119,13 @@ void ruby_parse(
return; return;
if (!handle_syntax(p, tokens, events)) if (!handle_syntax(p, tokens, events))
p.current().flags &= ~RubyState::RubyInternalState::NEWLINE; p.current().flags &= ~RubyState::RubyInternalState::NEWLINE;
if (p.peek() == '\0') {
if (p.ending)
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
p.advance();
p.current().flags &= ~RubyState::RubyInternalState::NEWLINE;
break;
}
continue; continue;
} }
if (p.ending) if (p.ending)
+25 -15
View File
@@ -2,6 +2,8 @@
#include "bed.h" #include "bed.h"
namespace bed::internal::ui::text_mode { namespace bed::internal::ui::text_mode {
// TODO: make this into a proper layout engine, in order to avoid so many expensive calculations repeatedly.
template <typename F> template <typename F>
static void for_each_cluster(std::string_view s, F &&f) { static void for_each_cluster(std::string_view s, F &&f) {
size_t cluster_start = 0; size_t cluster_start = 0;
@@ -204,11 +206,13 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
} }
vase::Range r = {last, cursor}; vase::Range r = {last, cursor};
vase = vase::erase(vase, r); vase = vase::erase(vase, r);
if (cursor.row == last.row) { if (parser) {
parser->dirty(vase, cursor.row, 1); if (cursor.row == last.row) {
} else { parser->dirty(vase, cursor.row, 1);
parser->erase(vase, cursor.row, 1); } else {
parser->dirty(vase, last.row, 1); parser->erase(vase, cursor.row, 1);
parser->dirty(vase, last.row, 1);
}
} }
cursor = last; cursor = last;
} }
@@ -222,10 +226,12 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
} }
} }
vase = vase::insert(&bed.append, vase, &cursor, '\n'); vase = vase::insert(&bed.append, vase, &cursor, '\n');
parser->insert(vase, cursor.row - 1, 1); if (parser)
parser->insert(vase, cursor.row - 1, 1);
} else { } else {
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size()); vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
parser->dirty(vase, cursor.row, 1); if (parser)
parser->dirty(vase, cursor.row, 1);
} }
break; break;
} }
@@ -233,11 +239,13 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
case io::KeyEvent::KeyType::PASTE: { case io::KeyEvent::KeyType::PASTE: {
auto lines = std::count(res.text.begin(), res.text.end(), '\n'); auto lines = std::count(res.text.begin(), res.text.end(), '\n');
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size()); vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
if (lines) { if (parser) {
parser->insert(vase, cursor.row - lines, lines); if (lines) {
parser->dirty(vase, cursor.row - lines, 1); parser->insert(vase, cursor.row - lines, lines);
} else { parser->dirty(vase, cursor.row - lines, 1);
parser->dirty(vase, cursor.row, 1); } else {
parser->dirty(vase, cursor.row, 1);
}
} }
} break; } break;
case io::KeyEvent::KeyType::SPECIAL: case io::KeyEvent::KeyType::SPECIAL:
@@ -302,9 +310,11 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
} }
vase::Range r = {cursor, next}; vase::Range r = {cursor, next};
vase = vase::erase(vase, r); vase = vase::erase(vase, r);
if (cursor.row != next.row) if (parser) {
parser->erase(vase, cursor.row + 1, 1); if (cursor.row != next.row)
parser->dirty(vase, cursor.row, 1); parser->erase(vase, cursor.row + 1, 1);
parser->dirty(vase, cursor.row, 1);
}
} }
break; break;
} }
+20 -22
View File
@@ -202,7 +202,7 @@ Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
return node; return node;
} }
Shard *Shard::from_command(const char *cmd, bool posix_ending) { Shard *Shard::from_command(const char *cmd) {
auto o = new OriginalStorage("/tmp"); auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd; int dest_fd = o->fd;
if (dest_fd == -1) { if (dest_fd == -1) {
@@ -278,23 +278,21 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) {
io::IO::enable_raw(); io::IO::enable_raw();
return nullptr; return nullptr;
} }
if (posix_ending) { if (ending[1] == '\n') {
if (ending[1] == '\n') { Petal *last = (Petal *)pieces.back();
Petal *last = (Petal *)pieces.back(); last->lines--;
last->lines--; last->length--;
last->length--; if (last->length == 0) {
if (last->length == 0) { Shard::release(last);
Shard::release(last); pieces.pop_back();
pieces.pop_back(); last = nullptr;
last = nullptr; if (!pieces.empty())
if (!pieces.empty()) last = (Petal *)pieces.back();
last = (Petal *)pieces.back(); else
else return nullptr;
return nullptr;
}
if (last && ending[0] == '\r')
last->length--;
} }
if (last && ending[0] == '\r')
last->length--;
} }
o->initialize(); o->initialize();
io::IO::enable_raw(); 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()); 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"); auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd; int dest_fd = o->fd;
if (dest_fd == -1) { 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); int src_fd = open(path.c_str(), O_RDONLY);
if (src_fd == -1) { if (src_fd == -1) {
delete o; delete o;
return nullptr; throw ed_error("Couldn't open file.");
} }
uint64_t total = std::filesystem::file_size(path); uint64_t total = std::filesystem::file_size(path);
if (posix_ending && total > 0) { if (total > 0) {
char last; char last;
if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) { if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) {
delete o; 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()); 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"); auto o = new OriginalStorage("/tmp");
int dest_fd = o->fd; int dest_fd = o->fd;
if (dest_fd == -1 || data == nullptr) { 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; return nullptr;
} }
uint64_t total = len; uint64_t total = len;
if (posix_ending && total > 0) { if (total > 0) {
if (data[total - 1] == '\n') { if (data[total - 1] == '\n') {
total--; total--;
if (total > 0 && data[total - 1] == '\r') if (total > 0 && data[total - 1] == '\r')
-2
View File
@@ -182,8 +182,6 @@ Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line) {
Shard::retain(text); Shard::retain(text);
return text; return text;
} }
if (!text)
return root;
if (line > root->lines + 1) if (line > root->lines + 1)
throw ed_error("line out of range"); throw ed_error("line out of range");
Shard::retain(text); Shard::retain(text);