Major updates/cleanup.
This commit is contained in:
@@ -113,6 +113,7 @@
|
|||||||
export MRBC=${mruby}/bin/mrbc
|
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"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ struct BEd {
|
|||||||
bool prompt_mode = true;
|
bool prompt_mode = true;
|
||||||
std::function<std::string(BEd &)> prompt = nullptr;
|
std::function<std::string(BEd &)> prompt = nullptr;
|
||||||
bool suppress_mode = false;
|
bool suppress_mode = false;
|
||||||
|
bool color_mode = false;
|
||||||
bool temporary_current = false;
|
bool temporary_current = false;
|
||||||
std::string last_help = "";
|
std::string last_help = "";
|
||||||
std::string last_regex = "";
|
std::string last_regex = "";
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ struct Token {
|
|||||||
RubyFunction,
|
RubyFunction,
|
||||||
RubyArg,
|
RubyArg,
|
||||||
Function,
|
Function,
|
||||||
Any,
|
|
||||||
Shell,
|
Shell,
|
||||||
File,
|
File,
|
||||||
Suffix,
|
Suffix,
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ struct Parser {
|
|||||||
std::vector<io::Token> *tokens, CompletionContext *completion
|
std::vector<io::Token> *tokens, CompletionContext *completion
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void token(io::Token::Kind);
|
||||||
|
void end_token();
|
||||||
|
|
||||||
char peek(uint16_t = 0); // == \0 if at eof.
|
char peek(uint16_t = 0); // == \0 if at eof.
|
||||||
std::string_view peek_str(uint16_t = UINT16_MAX);
|
std::string_view peek_str(uint16_t = UINT16_MAX);
|
||||||
void advance(uint16_t = 1);
|
void advance(uint16_t = 1);
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "decl.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
struct MiniParser {
|
||||||
|
Language ⟨
|
||||||
|
void *start_state;
|
||||||
|
std::vector<std::pair<void *, std::vector<io::Token>>> lines;
|
||||||
|
|
||||||
|
explicit MiniParser(Language &lang, vase::Shard *vase, void *initial_state);
|
||||||
|
~MiniParser();
|
||||||
|
|
||||||
|
MiniParser(const MiniParser &) = delete;
|
||||||
|
MiniParser &operator=(const MiniParser &) = delete;
|
||||||
|
|
||||||
|
void dirty(vase::Shard *vase, uint64_t start, uint64_t count);
|
||||||
|
void insert(vase::Shard *vase, uint64_t start, uint64_t count);
|
||||||
|
void erase(vase::Shard *vase, uint64_t start, uint64_t count);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
@@ -1,23 +1,31 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "definitions.h"
|
#include "internal/syntax/miniparser.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::ui {
|
namespace bed::internal::ui::text_mode {
|
||||||
struct TextMode {
|
struct TextMode {
|
||||||
std::string cmd;
|
struct Row {
|
||||||
uint16_t cursor;
|
uint64_t start;
|
||||||
|
uint64_t length;
|
||||||
|
};
|
||||||
|
using Lines = std::vector<Row>;
|
||||||
|
vase::Shard *vase;
|
||||||
|
vase::Point cursor;
|
||||||
|
std::optional<syntax::MiniParser> parser;
|
||||||
|
std::vector<Lines> lines;
|
||||||
uint16_t start;
|
uint16_t start;
|
||||||
uint16_t height;
|
uint16_t height;
|
||||||
uint16_t term_height;
|
uint16_t term_height;
|
||||||
uint16_t term_width;
|
uint16_t term_width;
|
||||||
BEd &bed;
|
BEd &bed;
|
||||||
|
|
||||||
TextMode(BEd &);
|
TextMode(BEd &, vase::Shard *, syntax::Language *, void *);
|
||||||
std::pair<vase::Shard *, bool> run();
|
std::pair<vase::Shard *, bool> run();
|
||||||
std::pair<vase::Shard *, bool> run_pipe();
|
std::pair<vase::Shard *, bool> run_pipe();
|
||||||
std::pair<vase::Shard *, bool> run_terminal();
|
std::pair<vase::Shard *, bool> run_terminal();
|
||||||
void grow();
|
void grow();
|
||||||
void redraw();
|
void redraw();
|
||||||
|
void layout_lines();
|
||||||
};
|
};
|
||||||
} // namespace bed::internal::ui
|
} // namespace bed::internal::ui::text_mode
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ struct ReplacePart {
|
|||||||
|
|
||||||
uint64_t offset_of(Shard *root, uint64_t line);
|
uint64_t offset_of(Shard *root, uint64_t line);
|
||||||
uint64_t offset_of(Shard *root, Point point);
|
uint64_t offset_of(Shard *root, Point point);
|
||||||
Point point_of(Shard *root, uint64_t offset);
|
Point eof_point(Shard *root);
|
||||||
|
|
||||||
std::string to_string(Shard *root);
|
std::string to_string(Shard *root);
|
||||||
std::string to_string(Shard *root, Range range);
|
std::string to_string(Shard *root, Range range);
|
||||||
|
|||||||
+21
-2
@@ -7,6 +7,7 @@ BEd::BEd(std::vector<std::string> args)
|
|||||||
std::string prompt_ = "";
|
std::string prompt_ = "";
|
||||||
std::string file = "";
|
std::string file = "";
|
||||||
bool suppress = false;
|
bool suppress = false;
|
||||||
|
bool color = true;
|
||||||
for (size_t i = 1; i < args.size(); i++) {
|
for (size_t i = 1; i < args.size(); i++) {
|
||||||
if (args[i] == "-p") {
|
if (args[i] == "-p") {
|
||||||
i++;
|
i++;
|
||||||
@@ -15,6 +16,8 @@ BEd::BEd(std::vector<std::string> args)
|
|||||||
prompt_ = args[i];
|
prompt_ = args[i];
|
||||||
} else if (args[i] == "-s") {
|
} else if (args[i] == "-s") {
|
||||||
suppress = true;
|
suppress = true;
|
||||||
|
} else if (args[i] == "--no-color") {
|
||||||
|
color = false;
|
||||||
} else if (args[i] == "-v" || args[i] == "--verbose") {
|
} else if (args[i] == "-v" || args[i] == "--verbose") {
|
||||||
help_mode = true;
|
help_mode = true;
|
||||||
} else if (args[i] == "-h" || args[i] == "--help") {
|
} else if (args[i] == "-h" || args[i] == "--help") {
|
||||||
@@ -31,6 +34,15 @@ BEd::BEd(std::vector<std::string> args)
|
|||||||
else
|
else
|
||||||
prompt_mode = false;
|
prompt_mode = false;
|
||||||
suppress_mode = suppress;
|
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_posix(*this);
|
||||||
internal::functions::Function::register_extented(*this);
|
internal::functions::Function::register_extented(*this);
|
||||||
internal::functions::Suffix::register_suffixes(*this);
|
internal::functions::Suffix::register_suffixes(*this);
|
||||||
@@ -77,7 +89,7 @@ void BEd::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BEd::handle(std::string_view cmd, bool eof) {
|
void BEd::handle(std::string_view cmd, bool eof) {
|
||||||
if (eof) {
|
if (eof && cmd.empty()) {
|
||||||
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
|
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -149,10 +161,17 @@ void BEd::handle(std::string_view cmd, bool eof) {
|
|||||||
}
|
}
|
||||||
internal::vase::Shard *text = nullptr;
|
internal::vase::Shard *text = nullptr;
|
||||||
if (c.function->input_mode == internal::functions::Function::InputMode::Text) {
|
if (c.function->input_mode == internal::functions::Function::InputMode::Text) {
|
||||||
internal::ui::TextMode tm(*this);
|
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();
|
auto [a, b] = tm.run();
|
||||||
if (!b)
|
if (!b)
|
||||||
text = a;
|
text = a;
|
||||||
|
else
|
||||||
|
internal::vase::Shard::release(a);
|
||||||
}
|
}
|
||||||
if (c.function->handle)
|
if (c.function->handle)
|
||||||
c.function->handle(*this, address, text, c.argument, nullptr);
|
c.function->handle(*this, address, text, c.argument, nullptr);
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ void ClipBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, uint6
|
|||||||
uint64_t diff = old_count - new_count;
|
uint64_t diff = old_count - new_count;
|
||||||
ctx.marks.collapse(name, start_line + new_count - 1, diff);
|
ctx.marks.collapse(name, start_line + new_count - 1, diff);
|
||||||
}
|
}
|
||||||
state = Modified;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -60,7 +60,21 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
.desc = "Change a range of lines",
|
.desc = "Change a range of lines",
|
||||||
.default_address = ".,.",
|
.default_address = ".,.",
|
||||||
.accept_zero = false,
|
.accept_zero = false,
|
||||||
.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::Range>(addr_);
|
||||||
|
auto &buf_ = ctx.buffer(addr.buffername);
|
||||||
|
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);
|
||||||
|
return {buf.copy(addr.start, addr.end), buf.parse.lang, state};
|
||||||
|
},
|
||||||
.handle = [](
|
.handle = [](
|
||||||
BEd &ctx,
|
BEd &ctx,
|
||||||
const buffer::Address &addr_,
|
const buffer::Address &addr_,
|
||||||
@@ -68,10 +82,9 @@ void Function::register_posix(BEd &ctx) {
|
|||||||
const Argument &,
|
const Argument &,
|
||||||
std::vector<buffer::Line> *
|
std::vector<buffer::Line> *
|
||||||
) {
|
) {
|
||||||
auto addr = std::get<buffer::Range>(addr_);
|
const auto &addr = std::get<buffer::Range>(addr_);
|
||||||
ctx.buffer(addr.buffername).replace(ctx, text, addr.start, addr.end);
|
ctx.buffer(addr.buffername).replace(ctx, text, addr.start, addr.end);
|
||||||
vase::Shard::release(text);
|
vase::Shard::release(text); }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
ctx.functions.insert(
|
ctx.functions.insert(
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ std::pair<std::string, bool> IO::read_pipe() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
|
if (pipe_input.empty())
|
||||||
|
return {"", true};
|
||||||
std::string line = std::move(pipe_input);
|
std::string line = std::move(pipe_input);
|
||||||
pipe_input.clear();
|
pipe_input.clear();
|
||||||
return {line, true};
|
return {line, false};
|
||||||
}
|
}
|
||||||
if (errno == EINTR)
|
if (errno == EINTR)
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ IO::~IO() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IO::apply(const io::Token::Kind &t) {
|
void IO::apply(const io::Token::Kind &t) {
|
||||||
if (bed.suppress_mode)
|
if (!bed.color_mode)
|
||||||
return;
|
return;
|
||||||
write("\x1b[0m");
|
write("\x1b[0m");
|
||||||
const auto &hl = bed.theme.get(t);
|
const auto &hl = bed.theme.get(t);
|
||||||
@@ -60,7 +60,7 @@ void IO::apply(const io::Token::Kind &t) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IO::reset() {
|
void IO::reset() {
|
||||||
if (bed.suppress_mode)
|
if (!bed.color_mode)
|
||||||
return;
|
return;
|
||||||
write("\x1b[0m");
|
write("\x1b[0m");
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-138
@@ -6,74 +6,49 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
addr.base = AddressPromise::None{};
|
addr.base = AddressPromise::None{};
|
||||||
switch (peek()) {
|
switch (peek()) {
|
||||||
case '.':
|
case '.':
|
||||||
tokens->push_back(
|
token(io::Token::AddressSymbol);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
addr.base = AddressPromise::Current{};
|
addr.base = AddressPromise::Current{};
|
||||||
break;
|
break;
|
||||||
case '$':
|
case '$':
|
||||||
tokens->push_back(
|
token(io::Token::AddressSymbol);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
addr.base = AddressPromise::Last{};
|
addr.base = AddressPromise::Last{};
|
||||||
break;
|
break;
|
||||||
case '%':
|
case '%':
|
||||||
tokens->push_back(
|
token(io::Token::AddressSymbol);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
addr.base = AddressPromise::LastRange{};
|
addr.base = AddressPromise::LastRange{};
|
||||||
break;
|
break;
|
||||||
case '[':
|
case '[':
|
||||||
tokens->push_back(
|
token(io::Token::AddressSymbol);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
addr.base = AddressPromise::Block{Direction::Backward};
|
addr.base = AddressPromise::Block{Direction::Backward};
|
||||||
break;
|
break;
|
||||||
case ']':
|
case ']':
|
||||||
tokens->push_back(
|
token(io::Token::AddressSymbol);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
addr.base = AddressPromise::Block{Direction::Forward};
|
addr.base = AddressPromise::Block{Direction::Forward};
|
||||||
break;
|
break;
|
||||||
case '^':
|
case '^':
|
||||||
tokens->push_back(
|
token(io::Token::AddressSymbol);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
addr.base = AddressPromise::Diagnostic{Direction::Backward};
|
addr.base = AddressPromise::Diagnostic{Direction::Backward};
|
||||||
break;
|
break;
|
||||||
case '~':
|
case '~':
|
||||||
tokens->push_back(
|
token(io::Token::AddressSymbol);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
addr.base = AddressPromise::Diagnostic{Direction::Forward};
|
addr.base = AddressPromise::Diagnostic{Direction::Forward};
|
||||||
break;
|
break;
|
||||||
case '\'':
|
case '\'':
|
||||||
tokens->push_back(
|
token(io::Token::Mark);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)2,
|
|
||||||
.type = io::Token::Mark}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
if (('a' <= peek() && peek() <= 'z')
|
if (('a' <= peek() && peek() <= 'z')
|
||||||
|| ('A' <= peek() && peek() <= 'Z'))
|
|| ('A' <= peek() && peek() <= 'Z'))
|
||||||
@@ -81,17 +56,16 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
else
|
else
|
||||||
throw ed_error("Valid mark needed after \'");
|
throw ed_error("Valid mark needed after \'");
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
break;
|
break;
|
||||||
case '{': {
|
case '{': {
|
||||||
tokens->push_back(
|
token(io::Token::AddressSymbol);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
std::string func;
|
std::string func;
|
||||||
std::string arg;
|
std::string arg;
|
||||||
|
token(io::Token::RubyFunction);
|
||||||
while (peek(j) != '}') {
|
while (peek(j) != '}') {
|
||||||
if (peek(j) == '\0')
|
if (peek(j) == '\0')
|
||||||
throw ed_error("Scripted address not terminated");
|
throw ed_error("Scripted address not terminated");
|
||||||
@@ -100,6 +74,8 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
if (peek(j) == ':') {
|
if (peek(j) == ':') {
|
||||||
func = peek_str(j);
|
func = peek_str(j);
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
|
end_token();
|
||||||
|
token(io::Token::RubyArg);
|
||||||
j = 0;
|
j = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -109,26 +85,15 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
arg = peek_str(j);
|
arg = peek_str(j);
|
||||||
else
|
else
|
||||||
func = peek_str(j);
|
func = peek_str(j);
|
||||||
tokens->push_back(
|
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)func.size(),
|
|
||||||
.type = io::Token::RubyFunction}
|
|
||||||
);
|
|
||||||
if (arg.size())
|
|
||||||
tokens->push_back(
|
|
||||||
{.start = i + (uint64_t)func.size() + 1,
|
|
||||||
.end = i + (uint64_t)func.size() + 1 + (uint64_t)arg.size(),
|
|
||||||
.type = io::Token::RubyFunction}
|
|
||||||
);
|
|
||||||
addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)};
|
addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)};
|
||||||
advance(j + 1);
|
advance(j);
|
||||||
tokens->push_back(
|
end_token();
|
||||||
{.start = i,
|
token(io::Token::AddressSymbol);
|
||||||
.end = i - (uint64_t)1,
|
advance();
|
||||||
.type = io::Token::AddressSymbol}
|
end_token();
|
||||||
);
|
|
||||||
} break;
|
} break;
|
||||||
case '/': {
|
case '/': {
|
||||||
|
token(io::Token::Regexp);
|
||||||
advance();
|
advance();
|
||||||
uint64_t j = 0;
|
uint64_t j = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -148,19 +113,11 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
Direction::Forward,
|
Direction::Forward,
|
||||||
std::string(peek_str(j))
|
std::string(peek_str(j))
|
||||||
);
|
);
|
||||||
tokens->push_back(
|
|
||||||
{.start = (uint64_t)i - 1,
|
|
||||||
.end = (uint64_t)i + j + 1,
|
|
||||||
.type = io::Token::Regexp}
|
|
||||||
);
|
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
|
end_token();
|
||||||
} break;
|
} break;
|
||||||
case '?': {
|
case '?': {
|
||||||
tokens->push_back(
|
token(io::Token::Regexp);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -180,14 +137,11 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
Direction::Backward,
|
Direction::Backward,
|
||||||
std::string(peek_str(j))
|
std::string(peek_str(j))
|
||||||
);
|
);
|
||||||
tokens->push_back(
|
|
||||||
{.start = (uint64_t)i - 1,
|
|
||||||
.end = (uint64_t)i + j + 1,
|
|
||||||
.type = io::Token::Regexp}
|
|
||||||
);
|
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
|
end_token();
|
||||||
} break;
|
} break;
|
||||||
case '<': {
|
case '<': {
|
||||||
|
token(io::Token::Label);
|
||||||
advance();
|
advance();
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
while (peek(j) != '>'
|
while (peek(j) != '>'
|
||||||
@@ -208,14 +162,11 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
tokens->push_back(
|
|
||||||
{.start = (uint64_t)i - 1,
|
|
||||||
.end = (uint64_t)i + j + 1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
|
end_token();
|
||||||
} break;
|
} break;
|
||||||
case '>': {
|
case '>': {
|
||||||
|
token(io::Token::Label);
|
||||||
advance();
|
advance();
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
while (peek(j) != '>' && peek(j) != '\0')
|
while (peek(j) != '>' && peek(j) != '\0')
|
||||||
@@ -230,20 +181,13 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
tokens->push_back(
|
|
||||||
{.start = (uint64_t)i - 1,
|
|
||||||
.end = (uint64_t)i + j + 1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance(j + 1);
|
advance(j + 1);
|
||||||
|
end_token();
|
||||||
} break;
|
} break;
|
||||||
case '+': {
|
case '+': {
|
||||||
tokens->push_back(
|
token(io::Token::AddressSymbol);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
addr.base = AddressPromise::Current{};
|
addr.base = AddressPromise::Current{};
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
uint64_t num = 0;
|
uint64_t num = 0;
|
||||||
@@ -253,21 +197,15 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
}
|
}
|
||||||
if (j == 0)
|
if (j == 0)
|
||||||
num = 1;
|
num = 1;
|
||||||
tokens->push_back(
|
|
||||||
{.start = (uint64_t)i,
|
|
||||||
.end = (uint64_t)i + j,
|
|
||||||
.type = io::Token::Number}
|
|
||||||
);
|
|
||||||
advance(j);
|
|
||||||
addr.offset += num;
|
addr.offset += num;
|
||||||
|
advance(j);
|
||||||
|
end_token();
|
||||||
} break;
|
} break;
|
||||||
case '-': {
|
case '-': {
|
||||||
tokens->push_back(
|
token(io::Token::AddressSymbol);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
|
token(io::Token::Number);
|
||||||
addr.base = AddressPromise::Current{};
|
addr.base = AddressPromise::Current{};
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
uint64_t num = 0;
|
uint64_t num = 0;
|
||||||
@@ -277,28 +215,20 @@ void Parser::locator(AddressPromise &addr) {
|
|||||||
}
|
}
|
||||||
if (j == 0)
|
if (j == 0)
|
||||||
num = 1;
|
num = 1;
|
||||||
tokens->push_back(
|
|
||||||
{.start = (uint64_t)i,
|
|
||||||
.end = (uint64_t)i + j,
|
|
||||||
.type = io::Token::Number}
|
|
||||||
);
|
|
||||||
advance(j);
|
|
||||||
addr.offset -= num;
|
addr.offset -= num;
|
||||||
|
advance(j);
|
||||||
|
end_token();
|
||||||
} break;
|
} break;
|
||||||
default:
|
default:
|
||||||
if ('0' <= peek() && peek() <= '9') {
|
if ('0' <= peek() && peek() <= '9') {
|
||||||
uint16_t start = i;
|
token(io::Token::Number);
|
||||||
uint64_t num = 0;
|
uint64_t num = 0;
|
||||||
while ('0' <= peek() && peek() <= '9') {
|
while ('0' <= peek() && peek() <= '9') {
|
||||||
num = num * 10 + (peek() - '0');
|
num = num * 10 + (peek() - '0');
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
addr.base = AddressPromise::Number{num};
|
addr.base = AddressPromise::Number{num};
|
||||||
tokens->push_back(
|
end_token();
|
||||||
{.start = (uint64_t)start,
|
|
||||||
.end = (uint64_t)i,
|
|
||||||
.type = io::Token::Number}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,26 +238,19 @@ int64_t Parser::offset() {
|
|||||||
while (peek() == '+' || peek() == '-'
|
while (peek() == '+' || peek() == '-'
|
||||||
|| ('0' <= peek() && peek() <= '9')) {
|
|| ('0' <= peek() && peek() <= '9')) {
|
||||||
bool positive = peek() != '-';
|
bool positive = peek() != '-';
|
||||||
if (peek() == '+' || peek() == '-') {
|
token(io::Token::AddressSymbol);
|
||||||
tokens->push_back(
|
if (peek() == '+' || peek() == '-')
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSymbol}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
}
|
end_token();
|
||||||
|
token(io::Token::Number);
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
int64_t num = 0;
|
int64_t num = 0;
|
||||||
while ('0' <= peek(j) && peek(j) <= '9')
|
while ('0' <= peek(j) && peek(j) <= '9')
|
||||||
num = num * 10 + (peek(j++) - '0');
|
num = num * 10 + (peek(j++) - '0');
|
||||||
if (j == 0)
|
if (j == 0)
|
||||||
num = 1;
|
num = 1;
|
||||||
tokens->push_back(
|
|
||||||
{.start = (uint64_t)i,
|
|
||||||
.end = (uint64_t)i + j,
|
|
||||||
.type = io::Token::Number}
|
|
||||||
);
|
|
||||||
advance(j);
|
advance(j);
|
||||||
|
end_token();
|
||||||
offset += positive ? num : -num;
|
offset += positive ? num : -num;
|
||||||
skip_ws();
|
skip_ws();
|
||||||
}
|
}
|
||||||
@@ -337,7 +260,7 @@ int64_t Parser::offset() {
|
|||||||
|
|
||||||
void Parser::address(AddressPromise &addr) {
|
void Parser::address(AddressPromise &addr) {
|
||||||
if (peek() == ':') {
|
if (peek() == ':') {
|
||||||
uint16_t start = i;
|
token(io::Token::BufferName);
|
||||||
advance();
|
advance();
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
while (peek(j) != ':' && peek(j) != '\0')
|
while (peek(j) != ':' && peek(j) != '\0')
|
||||||
@@ -346,11 +269,7 @@ void Parser::address(AddressPromise &addr) {
|
|||||||
advance(j);
|
advance(j);
|
||||||
if (peek() == ':')
|
if (peek() == ':')
|
||||||
advance();
|
advance();
|
||||||
tokens->push_back(
|
end_token();
|
||||||
{.start = start,
|
|
||||||
.end = i,
|
|
||||||
.type = io::Token::BufferName}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
skip_ws();
|
skip_ws();
|
||||||
if (peek() == '\0')
|
if (peek() == '\0')
|
||||||
@@ -367,11 +286,7 @@ void Parser::addresses(std::vector<AddressPromise> &addresses) {
|
|||||||
address(*addr);
|
address(*addr);
|
||||||
while (peek() == ',' || peek() == ';') {
|
while (peek() == ',' || peek() == ';') {
|
||||||
addr->jumping = peek() == ';';
|
addr->jumping = peek() == ';';
|
||||||
tokens->push_back(
|
token(io::Token::AddressSeperator);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::AddressSeperator}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
skip_ws();
|
skip_ws();
|
||||||
addresses.push_back({});
|
addresses.push_back({});
|
||||||
|
|||||||
@@ -81,16 +81,10 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
|
|||||||
AddressPromise prev;
|
AddressPromise prev;
|
||||||
for (std::size_t idx = 0; idx < list.size(); idx++) {
|
for (std::size_t idx = 0; idx < list.size(); idx++) {
|
||||||
AddressPromise &curr = list[idx];
|
AddressPromise &curr = list[idx];
|
||||||
if (curr.bufname.has_value()) {
|
if (!curr.bufname.has_value())
|
||||||
if (curr.bufname->empty()) {
|
|
||||||
bufname = ctx.current().buffername;
|
|
||||||
curr.bufname = bufname;
|
|
||||||
} else {
|
|
||||||
bufname = *curr.bufname;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
curr.bufname = bufname;
|
curr.bufname = bufname;
|
||||||
}
|
else if (curr.bufname->empty())
|
||||||
|
curr.bufname = bufname = ctx.current().buffername;
|
||||||
bool is_final = idx + 1 == list.size();
|
bool is_final = idx + 1 == list.size();
|
||||||
if (!is_final) {
|
if (!is_final) {
|
||||||
if (std::holds_alternative<None>(curr.base)) {
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
@@ -107,15 +101,15 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
|
|||||||
} else {
|
} else {
|
||||||
prev_given = true;
|
prev_given = true;
|
||||||
}
|
}
|
||||||
if (curr.jumping) {
|
buffer::Line resolved = curr.resolve(ctx);
|
||||||
buffer::Line resolved = curr.resolve(ctx);
|
if (curr.jumping)
|
||||||
ctx.current() = resolved;
|
ctx.current() = resolved;
|
||||||
curr.bufname = resolved.buffername;
|
curr.bufname = resolved.buffername;
|
||||||
curr.base = Number(resolved.number);
|
curr.base = Number(resolved.number);
|
||||||
curr.offset = 0;
|
curr.offset = 0;
|
||||||
}
|
|
||||||
prev = curr;
|
prev = curr;
|
||||||
prev_set = true;
|
prev_set = true;
|
||||||
|
bufname = *curr.bufname;
|
||||||
} else {
|
} else {
|
||||||
if (std::holds_alternative<None>(curr.base)) {
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
if (prev_set) {
|
if (prev_set) {
|
||||||
@@ -144,16 +138,10 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
|
|||||||
AddressPromise prev;
|
AddressPromise prev;
|
||||||
for (std::size_t idx = 0; idx < list.size(); idx++) {
|
for (std::size_t idx = 0; idx < list.size(); idx++) {
|
||||||
AddressPromise &curr = list[idx];
|
AddressPromise &curr = list[idx];
|
||||||
if (curr.bufname.has_value()) {
|
if (!curr.bufname.has_value())
|
||||||
if (curr.bufname->empty()) {
|
|
||||||
bufname = ctx.current().buffername;
|
|
||||||
curr.bufname = bufname;
|
|
||||||
} else {
|
|
||||||
bufname = *curr.bufname;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
curr.bufname = bufname;
|
curr.bufname = bufname;
|
||||||
}
|
else if (curr.bufname->empty())
|
||||||
|
curr.bufname = bufname = ctx.current().buffername;
|
||||||
bool is_final = idx + 1 == list.size();
|
bool is_final = idx + 1 == list.size();
|
||||||
if (!is_final) {
|
if (!is_final) {
|
||||||
if (std::holds_alternative<None>(curr.base)) {
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
@@ -170,15 +158,15 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
|
|||||||
} else {
|
} else {
|
||||||
prev_given = true;
|
prev_given = true;
|
||||||
}
|
}
|
||||||
if (curr.jumping) {
|
buffer::Line resolved = curr.resolve(ctx);
|
||||||
buffer::Line resolved = curr.resolve(ctx);
|
curr.bufname = resolved.buffername;
|
||||||
|
curr.base = Number(resolved.number);
|
||||||
|
curr.offset = 0;
|
||||||
|
if (curr.jumping)
|
||||||
ctx.current() = resolved;
|
ctx.current() = resolved;
|
||||||
curr.bufname = resolved.buffername;
|
|
||||||
curr.base = Number(resolved.number);
|
|
||||||
curr.offset = 0;
|
|
||||||
}
|
|
||||||
prev = curr;
|
prev = curr;
|
||||||
prev_set = true;
|
prev_set = true;
|
||||||
|
bufname = *curr.bufname;
|
||||||
} else {
|
} else {
|
||||||
if (std::holds_alternative<None>(curr.base)) {
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
if (prev_set) {
|
if (prev_set) {
|
||||||
|
|||||||
@@ -11,12 +11,9 @@ void Parser::operation() {
|
|||||||
if (len == 0)
|
if (len == 0)
|
||||||
throw ed_error("Function not found.");
|
throw ed_error("Function not found.");
|
||||||
functions::Function *function = bed.functions.get_ptr(peek_str(len));
|
functions::Function *function = bed.functions.get_ptr(peek_str(len));
|
||||||
tokens->push_back(
|
token(io::Token::Function);
|
||||||
{.start = i,
|
|
||||||
.end = i + len,
|
|
||||||
.type = io::Token::Function}
|
|
||||||
);
|
|
||||||
advance(len);
|
advance(len);
|
||||||
|
end_token();
|
||||||
command->function = function;
|
command->function = function;
|
||||||
char suffix = '\0';
|
char suffix = '\0';
|
||||||
switch (command->function->argument_kind) {
|
switch (command->function->argument_kind) {
|
||||||
@@ -32,21 +29,15 @@ void Parser::operation() {
|
|||||||
command->argument = peek();
|
command->argument = peek();
|
||||||
else
|
else
|
||||||
throw ed_error("Valid mark needed.");
|
throw ed_error("Valid mark needed.");
|
||||||
tokens->push_back(
|
token(io::Token::Mark);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::Mark}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
break;
|
break;
|
||||||
case functions::Function::ArgumentKind::Any:
|
case functions::Function::ArgumentKind::Any:
|
||||||
command->argument = std::string(peek_str());
|
command->argument = std::string(peek_str());
|
||||||
tokens->push_back(
|
token(io::Token::Data);
|
||||||
{.start = i,
|
|
||||||
.end = i + peek_str().size(),
|
|
||||||
.type = io::Token::Any}
|
|
||||||
);
|
|
||||||
advance(peek_str().size());
|
advance(peek_str().size());
|
||||||
|
end_token();
|
||||||
break;
|
break;
|
||||||
case functions::Function::ArgumentKind::Global: {
|
case functions::Function::ArgumentKind::Global: {
|
||||||
char delim;
|
char delim;
|
||||||
@@ -123,31 +114,22 @@ void Parser::operation() {
|
|||||||
skip_ws();
|
skip_ws();
|
||||||
switch (peek()) {
|
switch (peek()) {
|
||||||
case '!':
|
case '!':
|
||||||
tokens->push_back(
|
token(io::Token::Error);
|
||||||
{.start = i,
|
|
||||||
.end = (uint64_t)i + 1,
|
|
||||||
.type = io::Token::Error}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
|
token(io::Token::Shell);
|
||||||
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
||||||
tokens->push_back(
|
|
||||||
{.start = i,
|
|
||||||
.end = i + peek_str().size(),
|
|
||||||
.type = io::Token::Shell}
|
|
||||||
);
|
|
||||||
advance(peek_str().size());
|
advance(peek_str().size());
|
||||||
|
end_token();
|
||||||
break;
|
break;
|
||||||
case '\0':
|
case '\0':
|
||||||
command->argument = std::monostate();
|
command->argument = std::monostate();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
token(io::Token::File);
|
||||||
command->argument = std::filesystem::path(peek_str());
|
command->argument = std::filesystem::path(peek_str());
|
||||||
tokens->push_back(
|
|
||||||
{.start = i,
|
|
||||||
.end = i + peek_str().size(),
|
|
||||||
.type = io::Token::File}
|
|
||||||
);
|
|
||||||
advance(peek_str().size());
|
advance(peek_str().size());
|
||||||
|
end_token();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -163,7 +145,7 @@ void Parser::operation() {
|
|||||||
char delim = peek();
|
char delim = peek();
|
||||||
if (delim == '\0')
|
if (delim == '\0')
|
||||||
throw ed_error("regex expected");
|
throw ed_error("regex expected");
|
||||||
uint16_t start = i;
|
token(io::Token::Regexp);
|
||||||
advance();
|
advance();
|
||||||
uint16_t j = 0;
|
uint16_t j = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -203,20 +185,13 @@ void Parser::operation() {
|
|||||||
advance(j);
|
advance(j);
|
||||||
options = "p";
|
options = "p";
|
||||||
}
|
}
|
||||||
tokens->push_back(
|
end_token();
|
||||||
{.start = start,
|
token(io::Token::Suffix);
|
||||||
.end = i,
|
|
||||||
.type = io::Token::Regexp}
|
|
||||||
);
|
|
||||||
if (peek() != '\0') {
|
if (peek() != '\0') {
|
||||||
options = std::string(peek_str());
|
options = std::string(peek_str());
|
||||||
tokens->push_back(
|
|
||||||
{.start = i,
|
|
||||||
.end = i + peek_str().size(),
|
|
||||||
.type = io::Token::Suffix}
|
|
||||||
);
|
|
||||||
advance(peek_str().size());
|
advance(peek_str().size());
|
||||||
}
|
}
|
||||||
|
end_token();
|
||||||
std::erase_if(options, [&](char c) {
|
std::erase_if(options, [&](char c) {
|
||||||
if (bed.suffixes[c - 'a'].has_value()) {
|
if (bed.suffixes[c - 'a'].has_value()) {
|
||||||
suffix = c;
|
suffix = c;
|
||||||
@@ -243,22 +218,16 @@ void Parser::operation() {
|
|||||||
} break;
|
} break;
|
||||||
case functions::Function::ArgumentKind::Shell:
|
case functions::Function::ArgumentKind::Shell:
|
||||||
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
||||||
tokens->push_back(
|
token(io::Token::Shell);
|
||||||
{.start = i,
|
|
||||||
.end = i + peek_str().size(),
|
|
||||||
.type = io::Token::Shell}
|
|
||||||
);
|
|
||||||
advance(peek_str().size());
|
advance(peek_str().size());
|
||||||
|
end_token();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!suffix) {
|
if (!suffix) {
|
||||||
suffix = peek();
|
suffix = peek();
|
||||||
tokens->push_back(
|
token(io::Token::Suffix);
|
||||||
{.start = i,
|
|
||||||
.end = i + (uint64_t)1,
|
|
||||||
.type = io::Token::Suffix}
|
|
||||||
);
|
|
||||||
advance();
|
advance();
|
||||||
|
end_token();
|
||||||
}
|
}
|
||||||
if (suffix) {
|
if (suffix) {
|
||||||
auto &s = bed.suffixes[suffix - 'a'];
|
auto &s = bed.suffixes[suffix - 'a'];
|
||||||
|
|||||||
@@ -2,6 +2,19 @@
|
|||||||
#include "bed.h"
|
#include "bed.h"
|
||||||
|
|
||||||
namespace bed::internal::parser {
|
namespace bed::internal::parser {
|
||||||
|
void Parser::token(io::Token::Kind k) {
|
||||||
|
end_token();
|
||||||
|
tokens->push_back({.start = i, .end = 0, .type = k});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::end_token() {
|
||||||
|
if (tokens->empty())
|
||||||
|
return;
|
||||||
|
auto &b = tokens->back();
|
||||||
|
if (!b.end)
|
||||||
|
b.end = i;
|
||||||
|
}
|
||||||
|
|
||||||
char Parser::peek(uint16_t o) {
|
char Parser::peek(uint16_t o) {
|
||||||
return i + o < cmd.size() ? cmd[i + o] : '\0';
|
return i + o < cmd.size() ? cmd[i + o] : '\0';
|
||||||
}
|
}
|
||||||
@@ -15,29 +28,36 @@ void Parser::advance(uint16_t c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Parser::skip_ws() {
|
void Parser::skip_ws() {
|
||||||
|
end_token();
|
||||||
while (peek() == ' ' || peek() == '\t')
|
while (peek() == ' ' || peek() == '\t')
|
||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::parse() {
|
void Parser::parse() {
|
||||||
skip_ws();
|
try {
|
||||||
if (peek() == '@') {
|
skip_ws();
|
||||||
tokens->push_back(
|
if (peek() == '@') {
|
||||||
{.start = 0,
|
token(io::Token::TempCurrent);
|
||||||
.end = 1,
|
advance();
|
||||||
.type = io::Token::TempCurrent}
|
command->temp_address = true;
|
||||||
);
|
} else {
|
||||||
advance();
|
command->temp_address = false;
|
||||||
command->temp_address = true;
|
}
|
||||||
} else {
|
skip_ws();
|
||||||
command->temp_address = false;
|
addresses(command->addresses);
|
||||||
|
operation();
|
||||||
|
skip_ws();
|
||||||
|
if (peek() != '\0') {
|
||||||
|
token(io::Token::Error);
|
||||||
|
throw ed_error("Malformed command");
|
||||||
|
}
|
||||||
|
advance(cmd.size() - i);
|
||||||
|
end_token();
|
||||||
|
} catch (const ed_error &e) {
|
||||||
|
advance(cmd.size() - i);
|
||||||
|
end_token();
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
skip_ws();
|
|
||||||
addresses(command->addresses);
|
|
||||||
operation();
|
|
||||||
skip_ws();
|
|
||||||
if (peek() != '\0')
|
|
||||||
throw ed_error("Malformed command");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::Parser(
|
Parser::Parser(
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
#include "internal/syntax/miniparser.h"
|
||||||
|
|
||||||
|
namespace bed::internal::syntax {
|
||||||
|
MiniParser::MiniParser(Language &lang, vase::Shard *vase, void *initial_state)
|
||||||
|
: lang(lang),
|
||||||
|
start_state(initial_state ? lang.copy(initial_state) : lang.none_state()) {
|
||||||
|
vase::Iterator it(vase, 0, Direction::Forward);
|
||||||
|
void *state = lang.copy(start_state);
|
||||||
|
std::vector<io::Token> tokens;
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
const uint64_t count = vase ? vase->lines + 1 : 0;
|
||||||
|
lines.reserve(count);
|
||||||
|
for (uint64_t i = 0; i < count; ++i) {
|
||||||
|
it.next();
|
||||||
|
tokens.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, i == 0, &tokens, &events);
|
||||||
|
lines.emplace_back(lang.copy(state), std::move(tokens));
|
||||||
|
}
|
||||||
|
lang.destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
MiniParser::~MiniParser() {
|
||||||
|
if (start_state)
|
||||||
|
lang.destroy(start_state);
|
||||||
|
for (auto &[state, tokens] : lines)
|
||||||
|
if (state)
|
||||||
|
lang.destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniParser::dirty(vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
|
vase::Iterator it(vase, start, Direction::Forward);
|
||||||
|
void *state = start ? lang.copy(lines[start - 1].first)
|
||||||
|
: lang.copy(start_state);
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
uint64_t i = start;
|
||||||
|
for (; i < start + count; i++) {
|
||||||
|
it.next();
|
||||||
|
auto &line = lines[i];
|
||||||
|
line.second.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, i == 0, &line.second, &events);
|
||||||
|
lang.destroy(line.first);
|
||||||
|
line.first = lang.copy(state);
|
||||||
|
}
|
||||||
|
while (it.next()) {
|
||||||
|
auto &line = lines[i++];
|
||||||
|
line.second.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, false, &line.second, &events);
|
||||||
|
if (lang.equal(line.first, state))
|
||||||
|
break;
|
||||||
|
lang.destroy(line.first);
|
||||||
|
line.first = lang.copy(state);
|
||||||
|
}
|
||||||
|
lang.destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniParser::insert(vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
|
vase::Iterator it(vase, start, Direction::Forward);
|
||||||
|
void *state = start ? lang.copy(lines[start - 1].first)
|
||||||
|
: lang.copy(start_state);
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
std::vector<std::pair<void *, std::vector<io::Token>>> lines_new;
|
||||||
|
std::vector<io::Token> tokens;
|
||||||
|
lines_new.reserve(count);
|
||||||
|
for (uint64_t i = 0; i < count; ++i) {
|
||||||
|
it.next();
|
||||||
|
tokens.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, start + i == 0, &tokens, &events);
|
||||||
|
lines_new.emplace_back(lang.copy(state), std::move(tokens));
|
||||||
|
}
|
||||||
|
uint64_t i = start;
|
||||||
|
while (it.next()) {
|
||||||
|
auto &line = lines[i++];
|
||||||
|
line.second.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, i == 0, &line.second, &events);
|
||||||
|
if (lang.equal(line.first, state))
|
||||||
|
break;
|
||||||
|
lang.destroy(line.first);
|
||||||
|
line.first = lang.copy(state);
|
||||||
|
}
|
||||||
|
lines.insert(
|
||||||
|
lines.begin() + start,
|
||||||
|
std::make_move_iterator(lines_new.begin()),
|
||||||
|
std::make_move_iterator(lines_new.end())
|
||||||
|
);
|
||||||
|
lang.destroy(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MiniParser::erase(vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
|
if (count == 0)
|
||||||
|
return;
|
||||||
|
void *state = start ? lang.copy(lines[start - 1].first)
|
||||||
|
: lang.copy(start_state);
|
||||||
|
std::vector<ParseEvent> events;
|
||||||
|
for (uint64_t i = start; i < start + count; ++i)
|
||||||
|
if (lines[i].first)
|
||||||
|
lang.destroy(lines[i].first);
|
||||||
|
lines.erase(
|
||||||
|
lines.begin() + start,
|
||||||
|
lines.begin() + start + count
|
||||||
|
);
|
||||||
|
vase::Iterator it(vase, start, Direction::Forward);
|
||||||
|
uint64_t i = start;
|
||||||
|
while (i < lines.size() && it.next()) {
|
||||||
|
auto &line = lines[i];
|
||||||
|
line.second.clear();
|
||||||
|
events.clear();
|
||||||
|
lang.parse(&state, it.line, i++ == 0, &line.second, &events);
|
||||||
|
if (lang.equal(line.first, state))
|
||||||
|
break;
|
||||||
|
lang.destroy(line.first);
|
||||||
|
line.first = lang.copy(state);
|
||||||
|
}
|
||||||
|
lang.destroy(state);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::syntax
|
||||||
@@ -1030,6 +1030,7 @@ 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) == '#'
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,11 +63,6 @@ Theme Theme::default_theme() {
|
|||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
.flags = io::Highlight::None,
|
.flags = io::Highlight::None,
|
||||||
};
|
};
|
||||||
theme.hl[io::Token::Any] = {
|
|
||||||
.fg = GREY,
|
|
||||||
.bg = 0x000000,
|
|
||||||
.flags = io::Highlight::None,
|
|
||||||
};
|
|
||||||
theme.hl[io::Token::Shell] = {
|
theme.hl[io::Token::Shell] = {
|
||||||
.fg = BRIGHT_GREEN,
|
.fg = BRIGHT_GREEN,
|
||||||
.bg = 0x000000,
|
.bg = 0x000000,
|
||||||
|
|||||||
@@ -3,66 +3,6 @@
|
|||||||
#include "internal/parser/parser.h"
|
#include "internal/parser/parser.h"
|
||||||
|
|
||||||
namespace bed::internal::ui {
|
namespace bed::internal::ui {
|
||||||
/*template <typename F>
|
|
||||||
static void for_each_cluster(std::string_view s, F &&f) {
|
|
||||||
unicode_width_state_t state;
|
|
||||||
unicode_width_init(&state);
|
|
||||||
size_t i = 0;
|
|
||||||
while (i < s.size()) {
|
|
||||||
unsigned char c = static_cast<unsigned char>(s[i]);
|
|
||||||
size_t bytes = 1;
|
|
||||||
int width = 0;
|
|
||||||
if (c < 128) {
|
|
||||||
width = unicode_width_process(&state, c);
|
|
||||||
} else {
|
|
||||||
uint_least32_t cp;
|
|
||||||
size_t decoded = grapheme_decode_utf8(s.data() + i, s.size() - i, &cp);
|
|
||||||
bytes = decoded > 0 ? decoded : 1;
|
|
||||||
width = unicode_width_process(&state, cp);
|
|
||||||
}
|
|
||||||
if (width < 0)
|
|
||||||
width = 0;
|
|
||||||
f(i, bytes, width);
|
|
||||||
i += bytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int display_width(std::string_view s) {
|
|
||||||
int w = 0;
|
|
||||||
for_each_cluster(s, [&](size_t, size_t, int cw) { w += cw; });
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint16_t count_clusters(std::string_view s) {
|
|
||||||
uint16_t n = 0;
|
|
||||||
for_each_cluster(s, [&](size_t, size_t, int) { ++n; });
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::vector<uint16_t> wrap_offsets(std::string_view line, uint16_t avail) {
|
|
||||||
std::vector<uint16_t> offsets{0};
|
|
||||||
int col = 0;
|
|
||||||
for_each_cluster(line, [&](uint16_t i, uint16_t, int w) {
|
|
||||||
if (col + w > avail && col > 0) {
|
|
||||||
offsets.push_back(i);
|
|
||||||
col = 0;
|
|
||||||
}
|
|
||||||
col += w;
|
|
||||||
});
|
|
||||||
return offsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The word under/before `byte_pos`, split on plain ASCII spaces. Used to
|
|
||||||
// pick what prefix to hand the suggestion trie.
|
|
||||||
// TODO: change to use libgrapheme word break here.
|
|
||||||
static std::string current_word(const std::string &line, size_t byte_pos) {
|
|
||||||
size_t start = (byte_pos == 0) ? std::string::npos : line.rfind(' ', byte_pos - 1);
|
|
||||||
start = (start == std::string::npos) ? 0 : start + 1;
|
|
||||||
if (byte_pos < start)
|
|
||||||
byte_pos = start;
|
|
||||||
return line.substr(start, byte_pos - start);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
CommandIO::CommandIO(BEd &bed) : bed(bed) {
|
CommandIO::CommandIO(BEd &bed) : bed(bed) {
|
||||||
if (bed.prompt_mode)
|
if (bed.prompt_mode)
|
||||||
prompt = bed.prompt(bed);
|
prompt = bed.prompt(bed);
|
||||||
|
|||||||
@@ -1,9 +1,132 @@
|
|||||||
#include "internal/ui/text_mode.h"
|
#include "internal/ui/text_mode.h"
|
||||||
#include "bed.h"
|
#include "bed.h"
|
||||||
|
|
||||||
namespace bed::internal::ui {
|
namespace bed::internal::ui::text_mode {
|
||||||
TextMode::TextMode(BEd &bed) : bed(bed) {
|
template <typename F>
|
||||||
cursor = 0;
|
static void for_each_cluster(std::string_view s, F &&f) {
|
||||||
|
size_t cluster_start = 0;
|
||||||
|
while (cluster_start < s.size()) {
|
||||||
|
size_t cluster_len =
|
||||||
|
grapheme_next_character_break_utf8(
|
||||||
|
s.data() + cluster_start,
|
||||||
|
s.size() - cluster_start
|
||||||
|
);
|
||||||
|
if (cluster_len == 0)
|
||||||
|
break;
|
||||||
|
size_t cluster_end = cluster_start + cluster_len;
|
||||||
|
unicode_width_state_t state;
|
||||||
|
unicode_width_init(&state);
|
||||||
|
size_t i = cluster_start;
|
||||||
|
int width = 0;
|
||||||
|
while (i < cluster_end) {
|
||||||
|
uint_least32_t cp;
|
||||||
|
size_t decoded =
|
||||||
|
grapheme_decode_utf8(s.data() + i, cluster_end - i, &cp);
|
||||||
|
if (decoded == 0)
|
||||||
|
decoded = 1;
|
||||||
|
int w = unicode_width_process(&state, cp);
|
||||||
|
if (w > width)
|
||||||
|
width = w;
|
||||||
|
i += decoded;
|
||||||
|
}
|
||||||
|
if (width < 0)
|
||||||
|
width = 0;
|
||||||
|
if (!f(cluster_start, cluster_len, width))
|
||||||
|
return;
|
||||||
|
cluster_start = cluster_end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t previous_cluster(std::string_view line, size_t col) {
|
||||||
|
size_t previous = 0;
|
||||||
|
for_each_cluster(line, [&](size_t start, size_t len, int) {
|
||||||
|
if (start >= col)
|
||||||
|
return false;
|
||||||
|
previous = start;
|
||||||
|
return start + len < col;
|
||||||
|
});
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t next_cluster(std::string_view line, size_t col) {
|
||||||
|
size_t next = line.size();
|
||||||
|
for_each_cluster(line, [&](size_t start, size_t len, int) {
|
||||||
|
if (start >= col) {
|
||||||
|
next = start + len;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t visual_column(std::string_view line, size_t col) {
|
||||||
|
size_t width = 0;
|
||||||
|
for_each_cluster(line, [&](size_t start, size_t, int cluster_width) {
|
||||||
|
if (start >= col)
|
||||||
|
return false;
|
||||||
|
width += cluster_width;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t byte_column(std::string_view line, size_t target_width) {
|
||||||
|
size_t width = 0;
|
||||||
|
size_t col = 0;
|
||||||
|
for_each_cluster(line, [&](size_t start, size_t len, int cluster_width) {
|
||||||
|
if (width + cluster_width > target_width)
|
||||||
|
return false;
|
||||||
|
width += cluster_width;
|
||||||
|
col = start + len;
|
||||||
|
if (width == target_width)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::pair<size_t, size_t>> wrap_line(std::string_view line, size_t avail) {
|
||||||
|
std::vector<std::pair<size_t, size_t>> result;
|
||||||
|
if (avail == 0) {
|
||||||
|
result.push_back({0, line.size()});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
size_t begin = 0;
|
||||||
|
size_t end = 0;
|
||||||
|
int col = 0;
|
||||||
|
for_each_cluster(line, [&](size_t i, size_t len, int width) {
|
||||||
|
if (col > 0 && col + width > int(avail)) {
|
||||||
|
result.push_back({begin, i});
|
||||||
|
begin = i;
|
||||||
|
col = 0;
|
||||||
|
}
|
||||||
|
col += width;
|
||||||
|
end = i + len;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (begin < line.size() || result.empty())
|
||||||
|
result.push_back({begin, end});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextMode::layout_lines() {
|
||||||
|
vase::Iterator it(vase, 0, Direction::Forward);
|
||||||
|
lines.clear();
|
||||||
|
while (it.next()) {
|
||||||
|
auto wrapped = wrap_line(it.line, term_width);
|
||||||
|
lines.push_back({});
|
||||||
|
for (auto [begin, end] : wrapped)
|
||||||
|
lines.back().push_back({begin, end - begin});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextMode::TextMode(
|
||||||
|
BEd &bed, vase::Shard *vase, syntax::Language *lang, void *state
|
||||||
|
) : vase(vase), bed(bed) {
|
||||||
|
if (lang)
|
||||||
|
parser.emplace(*lang, vase, state);
|
||||||
|
cursor = vase::eof_point(vase);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<vase::Shard *, bool> TextMode::run() {
|
std::pair<vase::Shard *, bool> TextMode::run() {
|
||||||
@@ -13,31 +136,32 @@ std::pair<vase::Shard *, bool> TextMode::run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<vase::Shard *, bool> TextMode::run_pipe() {
|
std::pair<vase::Shard *, bool> TextMode::run_pipe() {
|
||||||
cmd.clear();
|
vase::Shard::release(vase);
|
||||||
|
vase = nullptr;
|
||||||
while (true) {
|
while (true) {
|
||||||
auto [str, eof] = bed.io.read_pipe();
|
auto [str, eof] = bed.io.read_pipe();
|
||||||
if (str == "." || eof)
|
if (str == "." || eof)
|
||||||
break;
|
break;
|
||||||
cmd += str + "\n";
|
vase = vase::insert(&bed.append, vase, &cursor, str.data(), str.length());
|
||||||
|
vase = vase::insert(&bed.append, vase, &cursor, '\n');
|
||||||
}
|
}
|
||||||
return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false};
|
return {vase, false};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<vase::Shard *, bool> TextMode::run_terminal() {
|
std::pair<vase::Shard *, bool> TextMode::run_terminal() {
|
||||||
auto [row, col] = bed.io.cursor_position();
|
auto [row, col] = bed.io.cursor_position();
|
||||||
auto [rows, cols] = bed.io.terminal_size();
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
if (row > rows)
|
if (row > rows)
|
||||||
throw fatal_error("Invalid cursor position.", 1);
|
throw ed_error("Invalid cursor position.");
|
||||||
start = row;
|
start = row;
|
||||||
height = rows - row + 1;
|
height = rows - row + 1;
|
||||||
term_width = cols;
|
term_width = cols;
|
||||||
term_height = rows;
|
term_height = rows;
|
||||||
cmd.clear();
|
|
||||||
cursor = 0;
|
|
||||||
redraw();
|
redraw();
|
||||||
bool running = true;
|
bool running = true;
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
|
redraw();
|
||||||
io::KeyEvent res = bed.io.read_key();
|
io::KeyEvent res = bed.io.read_key();
|
||||||
switch (res.type) {
|
switch (res.type) {
|
||||||
case io::KeyEvent::KeyType::EOF_:
|
case io::KeyEvent::KeyType::EOF_:
|
||||||
@@ -52,110 +176,151 @@ std::pair<vase::Shard *, bool> TextMode::run_terminal() {
|
|||||||
case io::KeyEvent::Modifier::ALT:
|
case io::KeyEvent::Modifier::ALT:
|
||||||
case io::KeyEvent::Modifier::CTRL_ALT:
|
case io::KeyEvent::Modifier::CTRL_ALT:
|
||||||
case io::KeyEvent::Modifier::CTRL:
|
case io::KeyEvent::Modifier::CTRL:
|
||||||
|
if (res.text[0] == 'c')
|
||||||
|
return {vase, true};
|
||||||
break;
|
break;
|
||||||
case io::KeyEvent::Modifier::NONE:
|
case io::KeyEvent::Modifier::NONE:
|
||||||
if (res.text[0] == '\b' || res.text[0] == 0x7f) {
|
if (res.text[0] == '\b' || res.text[0] == 0x7f) {
|
||||||
if (cursor > 0) {
|
if (!vase)
|
||||||
cmd.erase(--cursor, 1);
|
break;
|
||||||
|
if (cursor.row || cursor.col) {
|
||||||
|
auto last = cursor;
|
||||||
|
if (last.col) {
|
||||||
|
vase::Iterator it(vase, last.row, Direction::Forward);
|
||||||
|
if (!it.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
last.col = previous_cluster(it.line, last.col);
|
||||||
|
} else if (last.row) {
|
||||||
|
--last.row;
|
||||||
|
vase::Iterator it(vase, last.row, Direction::Forward);
|
||||||
|
if (!it.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
last.col = it.line.size();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
cursor = last;
|
||||||
}
|
}
|
||||||
} else if (res.text[0] == '\n') {
|
} else if (res.text[0] == '\n') {
|
||||||
cmd.insert(cursor++, 1, '\n');
|
if (vase && cursor.row == vase->lines) {
|
||||||
grow();
|
vase::Iterator it(vase, vase->lines, Direction::Backward);
|
||||||
|
if (it.next() && it.line == ".") {
|
||||||
|
vase = vase::erase(vase, vase->lines + 1, vase->lines + 1);
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vase = vase::insert(&bed.append, vase, &cursor, '\n');
|
||||||
|
parser->insert(vase, cursor.row - 1, 1);
|
||||||
} else {
|
} else {
|
||||||
cmd.insert(cursor, res.text);
|
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
|
||||||
cursor += res.text.size();
|
parser->dirty(vase, cursor.row, 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case io::KeyEvent::KeyType::PASTE:
|
case io::KeyEvent::KeyType::PASTE: {
|
||||||
cmd.insert(cursor, res.text);
|
auto lines = std::count(res.text.begin(), res.text.end(), '\n');
|
||||||
cursor += res.text.size();
|
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
|
||||||
grow();
|
if (lines) {
|
||||||
break;
|
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:
|
case io::KeyEvent::KeyType::SPECIAL:
|
||||||
|
if (!vase)
|
||||||
|
break;
|
||||||
switch (res.special_key) {
|
switch (res.special_key) {
|
||||||
case io::KeyEvent::SpecialKey::UNKNOWN:
|
case io::KeyEvent::SpecialKey::UNKNOWN:
|
||||||
break;
|
break;
|
||||||
case io::KeyEvent::SpecialKey::RIGHT:
|
case io::KeyEvent::SpecialKey::RIGHT: {
|
||||||
if (cursor < cmd.size())
|
vase::Iterator it(vase, cursor.row, Direction::Forward);
|
||||||
++cursor;
|
if (!it.next())
|
||||||
break;
|
throw ed_error("Invalid cursor position.");
|
||||||
case io::KeyEvent::SpecialKey::LEFT:
|
cursor.col = next_cluster(it.line, cursor.col);
|
||||||
if (cursor > 0)
|
} break;
|
||||||
--cursor;
|
case io::KeyEvent::SpecialKey::LEFT: {
|
||||||
break;
|
vase::Iterator it(vase, cursor.row, Direction::Forward);
|
||||||
|
if (!it.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
cursor.col = previous_cluster(it.line, cursor.col);
|
||||||
|
} break;
|
||||||
case io::KeyEvent::SpecialKey::UP: {
|
case io::KeyEvent::SpecialKey::UP: {
|
||||||
size_t line_start =
|
if (!cursor.row)
|
||||||
cmd.rfind('\n', cursor == 0 ? 0 : cursor - 1);
|
|
||||||
if (line_start == std::string::npos)
|
|
||||||
line_start = 0;
|
|
||||||
else
|
|
||||||
++line_start;
|
|
||||||
size_t col = cursor - line_start;
|
|
||||||
if (line_start == 0)
|
|
||||||
break;
|
break;
|
||||||
size_t prev_end = line_start - 1;
|
vase::Iterator current(vase, cursor.row, Direction::Forward);
|
||||||
size_t prev_start =
|
if (!current.next())
|
||||||
cmd.rfind('\n', prev_end == 0 ? 0 : prev_end - 1);
|
throw ed_error("Invalid cursor position.");
|
||||||
if (prev_start == std::string::npos)
|
size_t wanted = visual_column(current.line, cursor.col);
|
||||||
prev_start = 0;
|
--cursor.row;
|
||||||
else
|
vase::Iterator previous(vase, cursor.row, Direction::Forward);
|
||||||
++prev_start;
|
if (!previous.next())
|
||||||
size_t prev_len = prev_end - prev_start;
|
throw ed_error("Invalid cursor position.");
|
||||||
cursor = prev_start + std::min(col, prev_len);
|
cursor.col = byte_column(previous.line, wanted);
|
||||||
break;
|
} break;
|
||||||
}
|
|
||||||
case io::KeyEvent::SpecialKey::DOWN: {
|
case io::KeyEvent::SpecialKey::DOWN: {
|
||||||
size_t line_start =
|
if (cursor.row + 1 >= vase->lines)
|
||||||
cmd.rfind('\n', cursor == 0 ? 0 : cursor - 1);
|
|
||||||
if (line_start == std::string::npos)
|
|
||||||
line_start = 0;
|
|
||||||
else
|
|
||||||
++line_start;
|
|
||||||
size_t col = cursor - line_start;
|
|
||||||
size_t line_end = cmd.find('\n', cursor);
|
|
||||||
if (line_end == std::string::npos)
|
|
||||||
line_end = cmd.size();
|
|
||||||
if (line_end == cmd.size())
|
|
||||||
break;
|
break;
|
||||||
size_t next_start = line_end + 1;
|
vase::Iterator current(vase, cursor.row, Direction::Forward);
|
||||||
size_t next_end = cmd.find('\n', next_start);
|
if (!current.next())
|
||||||
if (next_end == std::string::npos)
|
throw ed_error("Invalid cursor position.");
|
||||||
next_end = cmd.size();
|
size_t wanted = visual_column(current.line, cursor.col);
|
||||||
size_t next_len = next_end - next_start;
|
++cursor.row;
|
||||||
cursor = next_start + std::min(col, next_len);
|
vase::Iterator previous(vase, cursor.row, Direction::Forward);
|
||||||
break;
|
if (!previous.next())
|
||||||
}
|
throw ed_error("Invalid cursor position.");
|
||||||
|
cursor.col = byte_column(previous.line, wanted);
|
||||||
|
} break;
|
||||||
case io::KeyEvent::SpecialKey::DELETE:
|
case io::KeyEvent::SpecialKey::DELETE:
|
||||||
if (cursor < cmd.size())
|
auto next = cursor;
|
||||||
cmd.erase(cursor, 1);
|
vase::Iterator it(vase, cursor.row, Direction::Forward);
|
||||||
break;
|
if (!it.next())
|
||||||
|
break;
|
||||||
|
if (cursor.col < it.line.size()) {
|
||||||
|
vase::Iterator it(vase, next.row, Direction::Forward);
|
||||||
|
if (!it.next())
|
||||||
|
throw ed_error("Invalid cursor position.");
|
||||||
|
next.col = next_cluster(it.line, cursor.col);
|
||||||
|
} else if (cursor.row + 1 < vase->lines) {
|
||||||
|
++next.row;
|
||||||
|
next.col = 0;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
redraw();
|
|
||||||
if (cmd.size() >= 3 && cmd.compare(cmd.size() - 3, 3, "\n.\n") == 0) {
|
|
||||||
cmd.erase(cmd.size() - 3);
|
|
||||||
cursor = cmd.size();
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
if (cmd.size() == 2 && cmd.compare(0, 2, ".\n") == 0) {
|
|
||||||
cmd.clear();
|
|
||||||
cursor = 0;
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t total_lines = cmd.size() ? 1 + std::count(cmd.begin(), cmd.end(), '\n') : 0;
|
layout_lines();
|
||||||
uint16_t last_row = start + total_lines;
|
size_t required_height = 0;
|
||||||
|
for (const auto &elem : lines)
|
||||||
|
required_height += elem.size();
|
||||||
|
uint16_t last_row = start + required_height;
|
||||||
bed.io.move_cursor(last_row, 1);
|
bed.io.move_cursor(last_row, 1);
|
||||||
bed.io.write("\n", 1);
|
bed.io.write("\n");
|
||||||
return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false};
|
return {vase, false};
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextMode::grow() {
|
void TextMode::grow() {
|
||||||
size_t required_height = 1 + std::count(cmd.begin(), cmd.end(), '\n');
|
size_t required_height = 0;
|
||||||
|
for (const auto &elem : lines)
|
||||||
|
required_height += elem.size();
|
||||||
auto [rows, cols] = bed.io.terminal_size();
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
term_height = rows;
|
term_height = rows;
|
||||||
term_width = cols;
|
term_width = cols;
|
||||||
@@ -166,46 +331,85 @@ void TextMode::grow() {
|
|||||||
return;
|
return;
|
||||||
bed.io.move_cursor(rows, 1);
|
bed.io.move_cursor(rows, 1);
|
||||||
for (long i = 0; i < overflow; ++i)
|
for (long i = 0; i < overflow; ++i)
|
||||||
bed.io.write("\n", 1);
|
bed.io.write("\n");
|
||||||
start -= overflow;
|
start -= overflow;
|
||||||
if (start < 1)
|
if (start < 1)
|
||||||
start = 1;
|
start = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextMode::redraw() {
|
void TextMode::redraw() {
|
||||||
auto [rows, cols] = bed.io.terminal_size();
|
layout_lines();
|
||||||
term_height = rows;
|
grow();
|
||||||
term_width = cols;
|
bed.io.write("\x1b[?25l", 6);
|
||||||
for (uint16_t i = 0; i < height; ++i) {
|
for (uint16_t i = 0; i < height; ++i) {
|
||||||
bed.io.move_cursor(start + i, 1);
|
bed.io.move_cursor(start + i, 1);
|
||||||
bed.io.write("\x1b[2K", 4);
|
bed.io.write("\x1b[2K", 4);
|
||||||
}
|
}
|
||||||
size_t line = 0;
|
|
||||||
size_t line_start = 0;
|
|
||||||
for (size_t i = 0; i < cursor; ++i) {
|
|
||||||
if (cmd[i] == '\n') {
|
|
||||||
++line;
|
|
||||||
line_start = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
size_t col = cursor - line_start;
|
|
||||||
size_t pos = 0;
|
|
||||||
uint16_t screen_line = start;
|
uint16_t screen_line = start;
|
||||||
while (pos <= cmd.size()) {
|
uint64_t line = 0;
|
||||||
size_t end = cmd.find('\n', pos);
|
size_t cursor_screen_row = start;
|
||||||
if (end == std::string::npos)
|
size_t cursor_screen_col = 0;
|
||||||
end = cmd.size();
|
vase::Iterator it(vase, line, Direction::Forward);
|
||||||
if (screen_line < start + height) {
|
while (it.next()) {
|
||||||
size_t len = std::min(end - pos, size_t(term_width));
|
if (screen_line >= start + height)
|
||||||
bed.io.move_cursor(screen_line, 1);
|
|
||||||
bed.io.write(cmd.substr(pos, len));
|
|
||||||
}
|
|
||||||
if (end == cmd.size())
|
|
||||||
break;
|
break;
|
||||||
pos = end + 1;
|
std::string_view l(it.line);
|
||||||
++screen_line;
|
const std::vector<io::Token> *tokens = nullptr;
|
||||||
|
if (parser && parser->lines.size() > line)
|
||||||
|
tokens = &parser->lines[line].second;
|
||||||
|
size_t token_index = 0;
|
||||||
|
for (auto &row : lines[line]) {
|
||||||
|
const size_t row_start = row.start;
|
||||||
|
const size_t row_end = row.start + row.length;
|
||||||
|
bed.io.move_cursor(screen_line, 1);
|
||||||
|
if (tokens) {
|
||||||
|
while (token_index < tokens->size() && (*tokens)[token_index].end <= row_start)
|
||||||
|
++token_index;
|
||||||
|
size_t pos = row_start;
|
||||||
|
for (size_t i = token_index; i < tokens->size(); ++i) {
|
||||||
|
const auto &token = (*tokens)[i];
|
||||||
|
if (token.start >= row_end)
|
||||||
|
break;
|
||||||
|
const size_t begin = std::max<size_t>(
|
||||||
|
token.start,
|
||||||
|
row_start
|
||||||
|
);
|
||||||
|
const size_t end = std::min<size_t>(
|
||||||
|
token.end,
|
||||||
|
row_end
|
||||||
|
);
|
||||||
|
if (begin > pos)
|
||||||
|
bed.io.write(l.data() + pos, begin - pos);
|
||||||
|
if (begin < end) {
|
||||||
|
bed.io.apply(token.type);
|
||||||
|
bed.io.write(l.data() + begin, end - begin);
|
||||||
|
bed.io.reset();
|
||||||
|
pos = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pos < row_end)
|
||||||
|
bed.io.write(l.data() + pos, row_end - pos);
|
||||||
|
bed.io.reset();
|
||||||
|
} else {
|
||||||
|
bed.io.write(l.substr(row_start, row.length));
|
||||||
|
}
|
||||||
|
if (line == cursor.row && cursor.col >= row_start && cursor.col <= row_end) {
|
||||||
|
cursor_screen_row = screen_line;
|
||||||
|
cursor_screen_col = 0;
|
||||||
|
for_each_cluster(
|
||||||
|
l.substr(row_start, cursor.col - row_start),
|
||||||
|
[&](size_t, size_t, int width) {
|
||||||
|
cursor_screen_col += width;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
++screen_line;
|
||||||
|
}
|
||||||
|
++line;
|
||||||
}
|
}
|
||||||
size_t vis_col = std::min(col, size_t(term_width - 1));
|
cursor_screen_col = std::min(cursor_screen_col, size_t(term_width - 1));
|
||||||
bed.io.move_cursor(start + line, vis_col + 1);
|
bed.io.move_cursor(cursor_screen_row, cursor_screen_col + 1);
|
||||||
|
bed.io.write("\x1b[?25h", 6);
|
||||||
}
|
}
|
||||||
} // namespace bed::internal::ui
|
} // namespace bed::internal::ui::text_mode
|
||||||
|
|||||||
@@ -74,14 +74,7 @@ void _insert(AppendStorage *ap, Shard **root, Point *point, const char *data, ui
|
|||||||
++lines;
|
++lines;
|
||||||
last_line = ++data;
|
last_line = ++data;
|
||||||
}
|
}
|
||||||
uint64_t col = 0;
|
uint64_t col = end - last_line;
|
||||||
uint64_t remaining = end - last_line;
|
|
||||||
while (remaining) {
|
|
||||||
uint64_t n = grapheme_next_character_break_utf8(last_line, remaining);
|
|
||||||
last_line += n;
|
|
||||||
remaining -= n;
|
|
||||||
++col;
|
|
||||||
}
|
|
||||||
if (lines) {
|
if (lines) {
|
||||||
point->row += lines;
|
point->row += lines;
|
||||||
point->col = col;
|
point->col = col;
|
||||||
@@ -122,6 +115,22 @@ Shard *replace(AppendStorage *ap, Shard *root, Range range, const char *data, ui
|
|||||||
return insert(ap, root, &range.start, data, len);
|
return insert(ap, root, &range.start, data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Point eof_point(Shard *root) {
|
||||||
|
if (!root)
|
||||||
|
return {0, 0};
|
||||||
|
Point result{
|
||||||
|
.row = root->lines,
|
||||||
|
.col = 0
|
||||||
|
};
|
||||||
|
PetalIterator it(root, Direction::Forward);
|
||||||
|
it.seek_line(root->lines);
|
||||||
|
const char *data;
|
||||||
|
uint64_t len;
|
||||||
|
while (it.next(&data, &len))
|
||||||
|
result.col += len;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t offset_of(Shard *root, Point point) {
|
uint64_t offset_of(Shard *root, Point point) {
|
||||||
return offset_of(root, point.row) + point.col;
|
return offset_of(root, point.row) + point.col;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user