Compare commits

..
2 Commits
Author SHA1 Message Date
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
20 changed files with 599 additions and 246 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ CFLAGS_RELEASE :=\
-fomit-frame-pointer -DNDEBUG -s \
-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)
UNICODE_SRC := $(wildcard libs/unicode_width/*.c)
+3
View File
@@ -6,6 +6,7 @@
#include "internal/functions/suffixes.h"
#include "internal/io/io.h"
#include "internal/marks/marks.h"
#include "internal/scripting/ruby.h"
#include "internal/theme/theme.h"
#include "internal/ui/command.h"
#include "internal/ui/text_mode.h"
@@ -21,6 +22,8 @@ struct BEd {
std::unordered_map<std::string, internal::syntax::Language *> languages;
internal::io::IO io;
internal::vase::AppendStorage append{"/tmp"};
mrb_state *mrb = nullptr;
int arena;
bool help_mode = false;
bool prompt_mode = true;
+3 -2
View File
@@ -14,8 +14,8 @@ struct HistoryItem {
struct GenericBuffer : ShardBuffer {
uint64_t base_version{0};
std::chrono::system_clock::time_point timestamp;
std::string action;
std::chrono::system_clock::time_point timestamp{std::chrono::system_clock::now()};
std::string action{"Created buffer."};
std::filesystem::path save_path{};
std::vector<HistoryItem> undo_stack;
std::vector<HistoryItem> redo_stack;
@@ -24,6 +24,7 @@ struct GenericBuffer : ShardBuffer {
: ShardBuffer(name, nullptr, nullptr, Kind::Generic) {}
~GenericBuffer();
void language(BEd &ctx, std::string name);
void list_history(BEd &ctx);
ReadonlyBuffer *get_history(uint64_t version);
void snapshot(std::string action);
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include "definitions.h"
#include "pch.h"
namespace bed::internal::scripting {
struct Block {
mrb_value proc;
mrb_state *mrb;
Block(mrb_state *mrb, mrb_value proc)
: proc(proc), mrb(mrb) {
mrb_gc_register(mrb, proc);
}
Block() : proc(mrb_nil_value()) {}
~Block() {
if (!mrb_nil_p(proc))
mrb_gc_unregister(mrb, proc);
}
void set_proc(mrb_value new_proc) {
if (!mrb_nil_p(proc))
mrb_gc_unregister(mrb, proc);
proc = new_proc;
mrb_gc_register(mrb, proc);
}
Block(const Block &) = delete;
Block &operator=(const Block &) = delete;
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);
return result;
}
};
void register_basic(BEd &ctx);
void 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
);
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 {
@@ -83,18 +84,25 @@ struct ParsePieceBuilder {
std::vector<ParseState *> pieces;
std::vector<uint16_t> blocks;
void *piece_state{nullptr};
void *prev_state{nullptr};
uint64_t chunk_start{0};
uint64_t chunk_lines{0};
ParsePieceBuilder(Language &lang, uint64_t first_line)
: lang(lang), chunk_start(first_line) {}
void add(
void *state,
uint64_t line,
const std::vector<ParseEvent> &events
) {
ParsePieceBuilder(Language &lang, uint64_t first_line, void *entry_state)
: lang(lang), chunk_start(first_line) {
prev_state = lang.copy(entry_state);
}
~ParsePieceBuilder() {
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) {
chunk_start = line;
piece_state = lang.copy(state);
piece_state = lang.copy(prev_state);
}
for (const auto &ev : events) {
blocks.push_back(
@@ -103,6 +111,9 @@ struct ParsePieceBuilder {
);
}
++chunk_lines;
if (prev_state)
lang.destroy(prev_state);
prev_state = lang.copy(state);
if (chunk_lines == ParseStateLeaf::MAX_CHUNK)
flush();
}
+1
View File
@@ -13,6 +13,7 @@ ParserSnapshot make_parser(vase::Shard *vase, uint64_t lines, Language *lang);
ParserSnapshot retain(const 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 prev_opening(const ParserSnapshot &snap, uint64_t line);
+19
View File
@@ -4,10 +4,29 @@
#include <mruby.h>
#include <mruby/array.h>
#include <mruby/boxing_word.h>
#include <mruby/class.h>
#include <mruby/common.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/internal.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/throw.h>
#include <mruby/value.h>
#include <mruby/variable.h>
#include <mruby/version.h>
#include <pcre2.h>
extern "C" {
#include <grapheme.h>
+84
View File
@@ -0,0 +1,84 @@
#include "bed.h"
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;
mrb = mrb_open();
if (!mrb)
throw fatal_error("Failed to initialize mruby.", 1);
mrb->ud = this;
arena = mrb_gc_arena_save(mrb);
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;
mrb->ud = nullptr;
if (mrb) {
mrb_gc_arena_restore(mrb, arena);
mrb_full_gc(mrb);
mrb_close(mrb);
}
}
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"
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() {
while (true) {
internal::ui::CommandIO command(*this);
auto [cmd, eof] = command.run();
try {
handle(cmd, eof);
} catch (ed_error &e) {
} catch (const ed_error &e) {
io.apply(internal::io::Token::Warning);
io.write_line("?");
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) {
if (name.empty())
throw ed_error("Can't have empty buffer name");
+18 -2
View File
@@ -203,7 +203,23 @@ uint64_t GenericBuffer::prune(int keep) {
bool GenericBuffer::waste() {
return save_path.empty()
&& root == nullptr
&& undo_stack.empty();
&& undo_stack.empty()
&& parse.lang == nullptr;
}
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) {
@@ -225,7 +241,7 @@ void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
}
ctx.current() = {name, lines()};
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) {
+108 -4
View File
@@ -173,24 +173,95 @@ void Function::register_extented(BEd &ctx) {
ctx.functions.insert(
"`",
Function{
.address_kind = Function::AddressKind::None,
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Ruby,
.input_mode = Function::InputMode::None,
.desc = "Execute some ruby code",
.default_address = "",
.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);
scripting::run(ctx, arg.cmd);
},
}
);
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 = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &,
const buffer::Address &,
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *,
const Argument &,
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);
},
}
);
ctx.functions.insert(
"```",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Execute some ruby code (taken from text mode)",
.default_address = "",
.accept_zero = true,
.pre_text_mode = [](
BEd &ctx,
const buffer::Address &,
const Argument &
) -> std::tuple<vase::Shard *, syntax::Language *, void *> {
return {nullptr, ctx.languages["ruby"], nullptr};
},
.handle = [](
BEd &ctx,
const buffer::Address &addr_,
vase::Shard *code,
const Argument &,
std::vector<buffer::Line> *
) {
auto &addr = std::get<buffer::Range>(addr_);
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); },
}
);
ctx.functions.insert(
"echo",
Function{
@@ -432,5 +503,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
+31 -9
View File
@@ -37,7 +37,19 @@ void Function::register_posix(BEd &ctx) {
.desc = "Append text to a line",
.default_address = ".",
.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 = [](
BEd &ctx,
const buffer::Address &addr_,
@@ -47,8 +59,7 @@ void Function::register_posix(BEd &ctx) {
) {
auto addr = std::get<buffer::Line>(addr_);
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
vase::Shard::release(text);
}
vase::Shard::release(text); }
}
);
ctx.functions.insert(
@@ -70,9 +81,7 @@ void Function::register_posix(BEd &ctx) {
if (buf_.kind != buffer::Buffer::Kind::Generic)
return {buf_.copy(addr.start, addr.end), nullptr, nullptr};
auto &buf = *(buffer::GenericBuffer *)&buf_;
if (!buf.parse.lang)
return {buf.copy(addr.start, addr.end), nullptr, nullptr};
void *state = nullptr; // TODO: buf.parse.root.get_at(addr.start);
void *state = syntax::state_before(buf.parse, buf.root, addr.start - 1);
return {buf.copy(addr.start, addr.end), buf.parse.lang, state};
},
.handle = [](
@@ -291,7 +300,21 @@ void Function::register_posix(BEd &ctx) {
.desc = "Insert text before a line",
.default_address = ".",
.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 = [](
BEd &ctx,
const buffer::Address &addr_,
@@ -303,8 +326,7 @@ void Function::register_posix(BEd &ctx) {
if (addr.number)
addr.number--;
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
vase::Shard::release(text);
}
vase::Shard::release(text); }
}
);
ctx.functions.insert(
+73
View File
@@ -0,0 +1,73 @@
#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);
} catch (...) {
mrb_raise(mrb, E_RUNTIME_ERROR, "Unexpected error.");
}
return mrb_nil_value();
}
void register_basic(BEd &ctx) {
auto *bed_error =
mrb_define_class(ctx.mrb, "EdError", mrb_exc_get_id(ctx.mrb, MRB_ERROR_SYM(RuntimeError)));
mrb_define_class(ctx.mrb, "FatalError", bed_error);
mrb_define_method(ctx.mrb, ctx.mrb->kernel_module, "exit", mrb_bed_exit, MRB_ARGS_NONE());
mrb_define_method(ctx.mrb, ctx.mrb->kernel_module, "handle", mrb_bed_handle, MRB_ARGS_REQ(1));
}
void run(BEd &ctx, const std::string &str) {
mrb_state *mrb = ctx.mrb;
mrb_load_nstring(mrb, str.data(), str.size());
if (!mrb->exc)
return;
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 {
Iterator::Iterator(uint64_t target, ParserSnapshot p, vase::Shard *vase)
: snap(p) {
uint64_t offset;
TreeCursor c = TreeCursor(*p.lang, p.root, target, &offset);
at = target - offset;
state = p.lang->copy(c.leaf->state);
: snap(p), at(target) {
at = target;
if (snap.lang)
state = ParseState::state_before(*snap.lang, snap.root, vase, at);
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() {
@@ -46,6 +38,8 @@ Iterator &Iterator::operator=(Iterator &&other) {
}
void Iterator::next() {
if (!state)
return;
it->next();
tokens.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);
std::vector<io::Token> tokens;
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);
for (uint64_t i = 0; i < count; ++i) {
it.next();
+6
View File
@@ -22,6 +22,12 @@ void release(ParserSnapshot &snap) {
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) {
if (!snap.root || !snap.lang)
return line + 10;
+26 -2
View File
@@ -108,7 +108,7 @@ ParseState *ParseState::splice(
void *state = lang.none_state();
std::vector<io::Token> tokens;
std::vector<ParseEvent> events;
ParsePieceBuilder builder(lang, 0);
ParsePieceBuilder builder(lang, 0, state);
for (uint64_t at = 0; at < final; ++at) {
it.next();
tokens.clear();
@@ -136,7 +136,7 @@ ParseState *ParseState::splice(
uint64_t end_extra;
TreeCursor c = TreeCursor(lang, root, end_in_tree, &end_extra);
uint64_t end_in_vase = line + final;
ParsePieceBuilder builder(lang, at);
ParsePieceBuilder builder(lang, at, state);
while (at < end_in_vase + end_extra) {
it.next();
tokens.clear();
@@ -192,4 +192,28 @@ ParseState *ParseState::concat(Language &lang, ParseState *a, ParseState *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
+16 -9
View File
@@ -782,11 +782,6 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
p.ending = false;
p.advance();
return false;
case '\0':
if (p.ending)
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
p.advance();
return false;
default:
if ('0' <= p.peek() && p.peek() <= '9') {
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
@@ -981,6 +976,11 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
| RubyState::RubyInternalState::DEF_NAME;
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;
if (p.peek(j) == ':') {
p.advance(j);
@@ -1008,8 +1008,9 @@ bool handle_syntax(RubyParser &p, std::vector<io::Token> *tokens, std::vector<Pa
return false;
if (p.peek(j) == '&'
|| p.peek(j) == '%'
|| p.peek(j) == ':') {
if (p.peek(j + 1) == ' ' || p.peek(j + 1) == '>')
|| p.peek(j) == ':'
|| p.peek(j) == '?') {
if (p.peek(j + 1) == ' ' || p.peek(j + 1) == '\0')
return false;
} else if (p.peek(j) == '-') {
if (p.peek(j + 1) != '>')
@@ -1025,7 +1026,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) == '<'
@@ -1061,7 +1061,7 @@ void ruby_parse(
std::vector<ParseEvent> *events
) {
RubyParser p(v_state, line);
while (p.i <= p.len()) {
while (p.i < p.len()) {
p.op_last = p.set_op_last;
p.set_op_last = false;
if (p.current().state == RubyState::RubyInternalState::END)
@@ -1099,6 +1099,13 @@ void ruby_parse(
return;
if (!handle_syntax(p, tokens, events))
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;
}
if (p.ending)
+25 -15
View File
@@ -2,6 +2,8 @@
#include "bed.h"
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>
static void for_each_cluster(std::string_view s, F &&f) {
size_t cluster_start = 0;
@@ -204,11 +206,13 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
}
vase::Range r = {last, cursor};
vase = vase::erase(vase, r);
if (cursor.row == last.row) {
parser->dirty(vase, cursor.row, 1);
} else {
parser->erase(vase, cursor.row, 1);
parser->dirty(vase, last.row, 1);
if (parser) {
if (cursor.row == last.row) {
parser->dirty(vase, cursor.row, 1);
} else {
parser->erase(vase, cursor.row, 1);
parser->dirty(vase, last.row, 1);
}
}
cursor = last;
}
@@ -222,10 +226,12 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
}
}
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 {
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;
}
@@ -233,11 +239,13 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
case io::KeyEvent::KeyType::PASTE: {
auto lines = std::count(res.text.begin(), res.text.end(), '\n');
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
if (lines) {
parser->insert(vase, cursor.row - lines, lines);
parser->dirty(vase, cursor.row - lines, 1);
} else {
parser->dirty(vase, cursor.row, 1);
if (parser) {
if (lines) {
parser->insert(vase, cursor.row - lines, lines);
parser->dirty(vase, cursor.row - lines, 1);
} else {
parser->dirty(vase, cursor.row, 1);
}
}
} break;
case io::KeyEvent::KeyType::SPECIAL:
@@ -302,9 +310,11 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
}
vase::Range r = {cursor, next};
vase = vase::erase(vase, r);
if (cursor.row != next.row)
parser->erase(vase, cursor.row + 1, 1);
parser->dirty(vase, cursor.row, 1);
if (parser) {
if (cursor.row != next.row)
parser->erase(vase, cursor.row + 1, 1);
parser->dirty(vase, cursor.row, 1);
}
}
break;
}