Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb3ce7549a
|
||
|
|
a5794d9ab3
|
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -21,6 +22,8 @@ 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"};
|
||||||
|
mrb_state *mrb = nullptr;
|
||||||
|
int arena;
|
||||||
|
|
||||||
bool help_mode = false;
|
bool help_mode = false;
|
||||||
bool prompt_mode = true;
|
bool prompt_mode = true;
|
||||||
|
|||||||
@@ -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,6 +24,7 @@ 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);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -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");
|
||||||
|
|||||||
@@ -203,7 +203,23 @@ 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::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 +241,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) {
|
||||||
|
|||||||
@@ -173,24 +173,95 @@ 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 some ruby code",
|
||||||
.default_address = "",
|
.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,
|
.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);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
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(
|
ctx.functions.insert(
|
||||||
"echo",
|
"echo",
|
||||||
Function{
|
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
|
} // namespace bed::internal::functions
|
||||||
|
|||||||
@@ -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 = [](
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -782,11 +782,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 +976,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 +1008,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 +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) == '?'
|
|
||||||
|| 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
|
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 +1099,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)
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user