Major updates/cleanup.
This commit is contained in:
@@ -113,6 +113,7 @@
|
||||
export MRBC=${mruby}/bin/mrbc
|
||||
export MRUBY_CFLAGS=-I${mruby}/include
|
||||
export MRUBY_LIBS="-L${mruby}/lib -lmruby"
|
||||
export PATH="$PWD/result/bin:$PATH"
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ struct BEd {
|
||||
bool prompt_mode = true;
|
||||
std::function<std::string(BEd &)> prompt = nullptr;
|
||||
bool suppress_mode = false;
|
||||
bool color_mode = false;
|
||||
bool temporary_current = false;
|
||||
std::string last_help = "";
|
||||
std::string last_regex = "";
|
||||
|
||||
@@ -17,7 +17,6 @@ struct Token {
|
||||
RubyFunction,
|
||||
RubyArg,
|
||||
Function,
|
||||
Any,
|
||||
Shell,
|
||||
File,
|
||||
Suffix,
|
||||
|
||||
@@ -93,6 +93,9 @@ struct Parser {
|
||||
std::vector<io::Token> *tokens, CompletionContext *completion
|
||||
);
|
||||
|
||||
void token(io::Token::Kind);
|
||||
void end_token();
|
||||
|
||||
char peek(uint16_t = 0); // == \0 if at eof.
|
||||
std::string_view peek_str(uint16_t = UINT16_MAX);
|
||||
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
|
||||
|
||||
#include "definitions.h"
|
||||
#include "internal/syntax/miniparser.h"
|
||||
#include "pch.h"
|
||||
|
||||
namespace bed::internal::ui {
|
||||
namespace bed::internal::ui::text_mode {
|
||||
struct TextMode {
|
||||
std::string cmd;
|
||||
uint16_t cursor;
|
||||
struct Row {
|
||||
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 height;
|
||||
uint16_t term_height;
|
||||
uint16_t term_width;
|
||||
BEd &bed;
|
||||
|
||||
TextMode(BEd &);
|
||||
TextMode(BEd &, vase::Shard *, syntax::Language *, void *);
|
||||
std::pair<vase::Shard *, bool> run();
|
||||
std::pair<vase::Shard *, bool> run_pipe();
|
||||
std::pair<vase::Shard *, bool> run_terminal();
|
||||
void grow();
|
||||
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, 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, Range range);
|
||||
|
||||
+21
-2
@@ -7,6 +7,7 @@ BEd::BEd(std::vector<std::string> args)
|
||||
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++;
|
||||
@@ -15,6 +16,8 @@ BEd::BEd(std::vector<std::string> args)
|
||||
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") {
|
||||
@@ -31,6 +34,15 @@ BEd::BEd(std::vector<std::string> args)
|
||||
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);
|
||||
@@ -77,7 +89,7 @@ void BEd::run() {
|
||||
}
|
||||
|
||||
void BEd::handle(std::string_view cmd, bool eof) {
|
||||
if (eof) {
|
||||
if (eof && cmd.empty()) {
|
||||
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
|
||||
return;
|
||||
}
|
||||
@@ -149,10 +161,17 @@ void BEd::handle(std::string_view cmd, bool eof) {
|
||||
}
|
||||
internal::vase::Shard *text = nullptr;
|
||||
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();
|
||||
if (!b)
|
||||
text = a;
|
||||
else
|
||||
internal::vase::Shard::release(a);
|
||||
}
|
||||
if (c.function->handle)
|
||||
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;
|
||||
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) {
|
||||
|
||||
@@ -60,7 +60,21 @@ void Function::register_posix(BEd &ctx) {
|
||||
.desc = "Change a range of lines",
|
||||
.default_address = ".,.",
|
||||
.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 = [](
|
||||
BEd &ctx,
|
||||
const buffer::Address &addr_,
|
||||
@@ -68,10 +82,9 @@ void Function::register_posix(BEd &ctx) {
|
||||
const Argument &,
|
||||
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);
|
||||
vase::Shard::release(text);
|
||||
}
|
||||
vase::Shard::release(text); }
|
||||
}
|
||||
);
|
||||
ctx.functions.insert(
|
||||
|
||||
@@ -16,9 +16,11 @@ std::pair<std::string, bool> IO::read_pipe() {
|
||||
continue;
|
||||
}
|
||||
if (n == 0) {
|
||||
if (pipe_input.empty())
|
||||
return {"", true};
|
||||
std::string line = std::move(pipe_input);
|
||||
pipe_input.clear();
|
||||
return {line, true};
|
||||
return {line, false};
|
||||
}
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
@@ -35,7 +35,7 @@ IO::~IO() {
|
||||
}
|
||||
|
||||
void IO::apply(const io::Token::Kind &t) {
|
||||
if (bed.suppress_mode)
|
||||
if (!bed.color_mode)
|
||||
return;
|
||||
write("\x1b[0m");
|
||||
const auto &hl = bed.theme.get(t);
|
||||
@@ -60,7 +60,7 @@ void IO::apply(const io::Token::Kind &t) {
|
||||
}
|
||||
|
||||
void IO::reset() {
|
||||
if (bed.suppress_mode)
|
||||
if (!bed.color_mode)
|
||||
return;
|
||||
write("\x1b[0m");
|
||||
}
|
||||
|
||||
+53
-138
@@ -6,74 +6,49 @@ void Parser::locator(AddressPromise &addr) {
|
||||
addr.base = AddressPromise::None{};
|
||||
switch (peek()) {
|
||||
case '.':
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
addr.base = AddressPromise::Current{};
|
||||
break;
|
||||
case '$':
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
addr.base = AddressPromise::Last{};
|
||||
break;
|
||||
case '%':
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
addr.base = AddressPromise::LastRange{};
|
||||
break;
|
||||
case '[':
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
addr.base = AddressPromise::Block{Direction::Backward};
|
||||
break;
|
||||
case ']':
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
addr.base = AddressPromise::Block{Direction::Forward};
|
||||
break;
|
||||
case '^':
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
addr.base = AddressPromise::Diagnostic{Direction::Backward};
|
||||
break;
|
||||
case '~':
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
addr.base = AddressPromise::Diagnostic{Direction::Forward};
|
||||
break;
|
||||
case '\'':
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)2,
|
||||
.type = io::Token::Mark}
|
||||
);
|
||||
token(io::Token::Mark);
|
||||
advance();
|
||||
if (('a' <= peek() && peek() <= 'z')
|
||||
|| ('A' <= peek() && peek() <= 'Z'))
|
||||
@@ -81,17 +56,16 @@ void Parser::locator(AddressPromise &addr) {
|
||||
else
|
||||
throw ed_error("Valid mark needed after \'");
|
||||
advance();
|
||||
end_token();
|
||||
break;
|
||||
case '{': {
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
uint16_t j = 0;
|
||||
std::string func;
|
||||
std::string arg;
|
||||
token(io::Token::RubyFunction);
|
||||
while (peek(j) != '}') {
|
||||
if (peek(j) == '\0')
|
||||
throw ed_error("Scripted address not terminated");
|
||||
@@ -100,6 +74,8 @@ void Parser::locator(AddressPromise &addr) {
|
||||
if (peek(j) == ':') {
|
||||
func = peek_str(j);
|
||||
advance(j + 1);
|
||||
end_token();
|
||||
token(io::Token::RubyArg);
|
||||
j = 0;
|
||||
continue;
|
||||
}
|
||||
@@ -109,26 +85,15 @@ void Parser::locator(AddressPromise &addr) {
|
||||
arg = peek_str(j);
|
||||
else
|
||||
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)};
|
||||
advance(j + 1);
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i - (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
advance(j);
|
||||
end_token();
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
} break;
|
||||
case '/': {
|
||||
token(io::Token::Regexp);
|
||||
advance();
|
||||
uint64_t j = 0;
|
||||
while (true) {
|
||||
@@ -148,19 +113,11 @@ void Parser::locator(AddressPromise &addr) {
|
||||
Direction::Forward,
|
||||
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);
|
||||
end_token();
|
||||
} break;
|
||||
case '?': {
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::Regexp);
|
||||
advance();
|
||||
uint16_t j = 0;
|
||||
while (true) {
|
||||
@@ -180,14 +137,11 @@ void Parser::locator(AddressPromise &addr) {
|
||||
Direction::Backward,
|
||||
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);
|
||||
end_token();
|
||||
} break;
|
||||
case '<': {
|
||||
token(io::Token::Label);
|
||||
advance();
|
||||
uint16_t j = 0;
|
||||
while (peek(j) != '>'
|
||||
@@ -208,14 +162,11 @@ void Parser::locator(AddressPromise &addr) {
|
||||
};
|
||||
break;
|
||||
}
|
||||
tokens->push_back(
|
||||
{.start = (uint64_t)i - 1,
|
||||
.end = (uint64_t)i + j + 1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
advance(j + 1);
|
||||
end_token();
|
||||
} break;
|
||||
case '>': {
|
||||
token(io::Token::Label);
|
||||
advance();
|
||||
uint16_t j = 0;
|
||||
while (peek(j) != '>' && peek(j) != '\0')
|
||||
@@ -230,20 +181,13 @@ void Parser::locator(AddressPromise &addr) {
|
||||
};
|
||||
break;
|
||||
}
|
||||
tokens->push_back(
|
||||
{.start = (uint64_t)i - 1,
|
||||
.end = (uint64_t)i + j + 1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
advance(j + 1);
|
||||
end_token();
|
||||
} break;
|
||||
case '+': {
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
addr.base = AddressPromise::Current{};
|
||||
uint16_t j = 0;
|
||||
uint64_t num = 0;
|
||||
@@ -253,21 +197,15 @@ void Parser::locator(AddressPromise &addr) {
|
||||
}
|
||||
if (j == 0)
|
||||
num = 1;
|
||||
tokens->push_back(
|
||||
{.start = (uint64_t)i,
|
||||
.end = (uint64_t)i + j,
|
||||
.type = io::Token::Number}
|
||||
);
|
||||
advance(j);
|
||||
addr.offset += num;
|
||||
advance(j);
|
||||
end_token();
|
||||
} break;
|
||||
case '-': {
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
advance();
|
||||
end_token();
|
||||
token(io::Token::Number);
|
||||
addr.base = AddressPromise::Current{};
|
||||
uint16_t j = 0;
|
||||
uint64_t num = 0;
|
||||
@@ -277,28 +215,20 @@ void Parser::locator(AddressPromise &addr) {
|
||||
}
|
||||
if (j == 0)
|
||||
num = 1;
|
||||
tokens->push_back(
|
||||
{.start = (uint64_t)i,
|
||||
.end = (uint64_t)i + j,
|
||||
.type = io::Token::Number}
|
||||
);
|
||||
advance(j);
|
||||
addr.offset -= num;
|
||||
advance(j);
|
||||
end_token();
|
||||
} break;
|
||||
default:
|
||||
if ('0' <= peek() && peek() <= '9') {
|
||||
uint16_t start = i;
|
||||
token(io::Token::Number);
|
||||
uint64_t num = 0;
|
||||
while ('0' <= peek() && peek() <= '9') {
|
||||
num = num * 10 + (peek() - '0');
|
||||
advance();
|
||||
}
|
||||
addr.base = AddressPromise::Number{num};
|
||||
tokens->push_back(
|
||||
{.start = (uint64_t)start,
|
||||
.end = (uint64_t)i,
|
||||
.type = io::Token::Number}
|
||||
);
|
||||
end_token();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,26 +238,19 @@ int64_t Parser::offset() {
|
||||
while (peek() == '+' || peek() == '-'
|
||||
|| ('0' <= peek() && peek() <= '9')) {
|
||||
bool positive = peek() != '-';
|
||||
if (peek() == '+' || peek() == '-') {
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSymbol}
|
||||
);
|
||||
token(io::Token::AddressSymbol);
|
||||
if (peek() == '+' || peek() == '-')
|
||||
advance();
|
||||
}
|
||||
end_token();
|
||||
token(io::Token::Number);
|
||||
uint16_t j = 0;
|
||||
int64_t num = 0;
|
||||
while ('0' <= peek(j) && peek(j) <= '9')
|
||||
num = num * 10 + (peek(j++) - '0');
|
||||
if (j == 0)
|
||||
num = 1;
|
||||
tokens->push_back(
|
||||
{.start = (uint64_t)i,
|
||||
.end = (uint64_t)i + j,
|
||||
.type = io::Token::Number}
|
||||
);
|
||||
advance(j);
|
||||
end_token();
|
||||
offset += positive ? num : -num;
|
||||
skip_ws();
|
||||
}
|
||||
@@ -337,7 +260,7 @@ int64_t Parser::offset() {
|
||||
|
||||
void Parser::address(AddressPromise &addr) {
|
||||
if (peek() == ':') {
|
||||
uint16_t start = i;
|
||||
token(io::Token::BufferName);
|
||||
advance();
|
||||
uint16_t j = 0;
|
||||
while (peek(j) != ':' && peek(j) != '\0')
|
||||
@@ -346,11 +269,7 @@ void Parser::address(AddressPromise &addr) {
|
||||
advance(j);
|
||||
if (peek() == ':')
|
||||
advance();
|
||||
tokens->push_back(
|
||||
{.start = start,
|
||||
.end = i,
|
||||
.type = io::Token::BufferName}
|
||||
);
|
||||
end_token();
|
||||
}
|
||||
skip_ws();
|
||||
if (peek() == '\0')
|
||||
@@ -367,11 +286,7 @@ void Parser::addresses(std::vector<AddressPromise> &addresses) {
|
||||
address(*addr);
|
||||
while (peek() == ',' || peek() == ';') {
|
||||
addr->jumping = peek() == ';';
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::AddressSeperator}
|
||||
);
|
||||
token(io::Token::AddressSeperator);
|
||||
advance();
|
||||
skip_ws();
|
||||
addresses.push_back({});
|
||||
|
||||
@@ -81,16 +81,10 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
|
||||
AddressPromise prev;
|
||||
for (std::size_t idx = 0; idx < list.size(); idx++) {
|
||||
AddressPromise &curr = list[idx];
|
||||
if (curr.bufname.has_value()) {
|
||||
if (curr.bufname->empty()) {
|
||||
bufname = ctx.current().buffername;
|
||||
if (!curr.bufname.has_value())
|
||||
curr.bufname = bufname;
|
||||
} else {
|
||||
bufname = *curr.bufname;
|
||||
}
|
||||
} else {
|
||||
curr.bufname = bufname;
|
||||
}
|
||||
else if (curr.bufname->empty())
|
||||
curr.bufname = bufname = ctx.current().buffername;
|
||||
bool is_final = idx + 1 == list.size();
|
||||
if (!is_final) {
|
||||
if (std::holds_alternative<None>(curr.base)) {
|
||||
@@ -107,15 +101,15 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
|
||||
} else {
|
||||
prev_given = true;
|
||||
}
|
||||
if (curr.jumping) {
|
||||
buffer::Line resolved = curr.resolve(ctx);
|
||||
if (curr.jumping)
|
||||
ctx.current() = resolved;
|
||||
curr.bufname = resolved.buffername;
|
||||
curr.base = Number(resolved.number);
|
||||
curr.offset = 0;
|
||||
}
|
||||
prev = curr;
|
||||
prev_set = true;
|
||||
bufname = *curr.bufname;
|
||||
} else {
|
||||
if (std::holds_alternative<None>(curr.base)) {
|
||||
if (prev_set) {
|
||||
@@ -144,16 +138,10 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
|
||||
AddressPromise prev;
|
||||
for (std::size_t idx = 0; idx < list.size(); idx++) {
|
||||
AddressPromise &curr = list[idx];
|
||||
if (curr.bufname.has_value()) {
|
||||
if (curr.bufname->empty()) {
|
||||
bufname = ctx.current().buffername;
|
||||
if (!curr.bufname.has_value())
|
||||
curr.bufname = bufname;
|
||||
} else {
|
||||
bufname = *curr.bufname;
|
||||
}
|
||||
} else {
|
||||
curr.bufname = bufname;
|
||||
}
|
||||
else if (curr.bufname->empty())
|
||||
curr.bufname = bufname = ctx.current().buffername;
|
||||
bool is_final = idx + 1 == list.size();
|
||||
if (!is_final) {
|
||||
if (std::holds_alternative<None>(curr.base)) {
|
||||
@@ -170,15 +158,15 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
|
||||
} else {
|
||||
prev_given = true;
|
||||
}
|
||||
if (curr.jumping) {
|
||||
buffer::Line resolved = curr.resolve(ctx);
|
||||
ctx.current() = resolved;
|
||||
curr.bufname = resolved.buffername;
|
||||
curr.base = Number(resolved.number);
|
||||
curr.offset = 0;
|
||||
}
|
||||
if (curr.jumping)
|
||||
ctx.current() = resolved;
|
||||
prev = curr;
|
||||
prev_set = true;
|
||||
bufname = *curr.bufname;
|
||||
} else {
|
||||
if (std::holds_alternative<None>(curr.base)) {
|
||||
if (prev_set) {
|
||||
|
||||
@@ -11,12 +11,9 @@ void Parser::operation() {
|
||||
if (len == 0)
|
||||
throw ed_error("Function not found.");
|
||||
functions::Function *function = bed.functions.get_ptr(peek_str(len));
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + len,
|
||||
.type = io::Token::Function}
|
||||
);
|
||||
token(io::Token::Function);
|
||||
advance(len);
|
||||
end_token();
|
||||
command->function = function;
|
||||
char suffix = '\0';
|
||||
switch (command->function->argument_kind) {
|
||||
@@ -32,21 +29,15 @@ void Parser::operation() {
|
||||
command->argument = peek();
|
||||
else
|
||||
throw ed_error("Valid mark needed.");
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::Mark}
|
||||
);
|
||||
token(io::Token::Mark);
|
||||
advance();
|
||||
end_token();
|
||||
break;
|
||||
case functions::Function::ArgumentKind::Any:
|
||||
command->argument = std::string(peek_str());
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + peek_str().size(),
|
||||
.type = io::Token::Any}
|
||||
);
|
||||
token(io::Token::Data);
|
||||
advance(peek_str().size());
|
||||
end_token();
|
||||
break;
|
||||
case functions::Function::ArgumentKind::Global: {
|
||||
char delim;
|
||||
@@ -123,31 +114,22 @@ void Parser::operation() {
|
||||
skip_ws();
|
||||
switch (peek()) {
|
||||
case '!':
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = (uint64_t)i + 1,
|
||||
.type = io::Token::Error}
|
||||
);
|
||||
token(io::Token::Error);
|
||||
advance();
|
||||
end_token();
|
||||
token(io::Token::Shell);
|
||||
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());
|
||||
end_token();
|
||||
break;
|
||||
case '\0':
|
||||
command->argument = std::monostate();
|
||||
break;
|
||||
default:
|
||||
token(io::Token::File);
|
||||
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());
|
||||
end_token();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -163,7 +145,7 @@ void Parser::operation() {
|
||||
char delim = peek();
|
||||
if (delim == '\0')
|
||||
throw ed_error("regex expected");
|
||||
uint16_t start = i;
|
||||
token(io::Token::Regexp);
|
||||
advance();
|
||||
uint16_t j = 0;
|
||||
while (true) {
|
||||
@@ -203,20 +185,13 @@ void Parser::operation() {
|
||||
advance(j);
|
||||
options = "p";
|
||||
}
|
||||
tokens->push_back(
|
||||
{.start = start,
|
||||
.end = i,
|
||||
.type = io::Token::Regexp}
|
||||
);
|
||||
end_token();
|
||||
token(io::Token::Suffix);
|
||||
if (peek() != '\0') {
|
||||
options = std::string(peek_str());
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + peek_str().size(),
|
||||
.type = io::Token::Suffix}
|
||||
);
|
||||
advance(peek_str().size());
|
||||
}
|
||||
end_token();
|
||||
std::erase_if(options, [&](char c) {
|
||||
if (bed.suffixes[c - 'a'].has_value()) {
|
||||
suffix = c;
|
||||
@@ -243,22 +218,16 @@ void Parser::operation() {
|
||||
} break;
|
||||
case functions::Function::ArgumentKind::Shell:
|
||||
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + peek_str().size(),
|
||||
.type = io::Token::Shell}
|
||||
);
|
||||
token(io::Token::Shell);
|
||||
advance(peek_str().size());
|
||||
end_token();
|
||||
break;
|
||||
}
|
||||
if (!suffix) {
|
||||
suffix = peek();
|
||||
tokens->push_back(
|
||||
{.start = i,
|
||||
.end = i + (uint64_t)1,
|
||||
.type = io::Token::Suffix}
|
||||
);
|
||||
token(io::Token::Suffix);
|
||||
advance();
|
||||
end_token();
|
||||
}
|
||||
if (suffix) {
|
||||
auto &s = bed.suffixes[suffix - 'a'];
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
#include "bed.h"
|
||||
|
||||
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) {
|
||||
return i + o < cmd.size() ? cmd[i + o] : '\0';
|
||||
}
|
||||
@@ -15,18 +28,16 @@ void Parser::advance(uint16_t c) {
|
||||
}
|
||||
|
||||
void Parser::skip_ws() {
|
||||
end_token();
|
||||
while (peek() == ' ' || peek() == '\t')
|
||||
advance();
|
||||
}
|
||||
|
||||
void Parser::parse() {
|
||||
try {
|
||||
skip_ws();
|
||||
if (peek() == '@') {
|
||||
tokens->push_back(
|
||||
{.start = 0,
|
||||
.end = 1,
|
||||
.type = io::Token::TempCurrent}
|
||||
);
|
||||
token(io::Token::TempCurrent);
|
||||
advance();
|
||||
command->temp_address = true;
|
||||
} else {
|
||||
@@ -36,9 +47,18 @@ void Parser::parse() {
|
||||
addresses(command->addresses);
|
||||
operation();
|
||||
skip_ws();
|
||||
if (peek() != '\0')
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Parser::Parser(
|
||||
std::string_view cmd, BEd &bed, Command *command,
|
||||
|
||||
@@ -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) == '#'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -63,11 +63,6 @@ Theme Theme::default_theme() {
|
||||
.bg = 0x000000,
|
||||
.flags = io::Highlight::None,
|
||||
};
|
||||
theme.hl[io::Token::Any] = {
|
||||
.fg = GREY,
|
||||
.bg = 0x000000,
|
||||
.flags = io::Highlight::None,
|
||||
};
|
||||
theme.hl[io::Token::Shell] = {
|
||||
.fg = BRIGHT_GREEN,
|
||||
.bg = 0x000000,
|
||||
|
||||
@@ -3,66 +3,6 @@
|
||||
#include "internal/parser/parser.h"
|
||||
|
||||
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) {
|
||||
if (bed.prompt_mode)
|
||||
prompt = bed.prompt(bed);
|
||||
|
||||
@@ -1,9 +1,132 @@
|
||||
#include "internal/ui/text_mode.h"
|
||||
#include "bed.h"
|
||||
|
||||
namespace bed::internal::ui {
|
||||
TextMode::TextMode(BEd &bed) : bed(bed) {
|
||||
cursor = 0;
|
||||
namespace bed::internal::ui::text_mode {
|
||||
template <typename F>
|
||||
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() {
|
||||
@@ -13,31 +136,32 @@ std::pair<vase::Shard *, bool> TextMode::run() {
|
||||
}
|
||||
|
||||
std::pair<vase::Shard *, bool> TextMode::run_pipe() {
|
||||
cmd.clear();
|
||||
vase::Shard::release(vase);
|
||||
vase = nullptr;
|
||||
while (true) {
|
||||
auto [str, eof] = bed.io.read_pipe();
|
||||
if (str == "." || eof)
|
||||
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() {
|
||||
auto [row, col] = bed.io.cursor_position();
|
||||
auto [rows, cols] = bed.io.terminal_size();
|
||||
if (row > rows)
|
||||
throw fatal_error("Invalid cursor position.", 1);
|
||||
throw ed_error("Invalid cursor position.");
|
||||
start = row;
|
||||
height = rows - row + 1;
|
||||
term_width = cols;
|
||||
term_height = rows;
|
||||
cmd.clear();
|
||||
cursor = 0;
|
||||
redraw();
|
||||
bool running = true;
|
||||
|
||||
while (running) {
|
||||
redraw();
|
||||
io::KeyEvent res = bed.io.read_key();
|
||||
switch (res.type) {
|
||||
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::CTRL_ALT:
|
||||
case io::KeyEvent::Modifier::CTRL:
|
||||
if (res.text[0] == 'c')
|
||||
return {vase, true};
|
||||
break;
|
||||
case io::KeyEvent::Modifier::NONE:
|
||||
if (res.text[0] == '\b' || res.text[0] == 0x7f) {
|
||||
if (cursor > 0) {
|
||||
cmd.erase(--cursor, 1);
|
||||
if (!vase)
|
||||
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') {
|
||||
cmd.insert(cursor++, 1, '\n');
|
||||
grow();
|
||||
if (vase && cursor.row == vase->lines) {
|
||||
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 {
|
||||
cmd.insert(cursor, res.text);
|
||||
cursor += res.text.size();
|
||||
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
|
||||
parser->dirty(vase, cursor.row, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case io::KeyEvent::KeyType::PASTE:
|
||||
cmd.insert(cursor, res.text);
|
||||
cursor += res.text.size();
|
||||
grow();
|
||||
break;
|
||||
case io::KeyEvent::KeyType::PASTE: {
|
||||
auto lines = std::count(res.text.begin(), res.text.end(), '\n');
|
||||
vase = vase::insert(&bed.append, vase, &cursor, res.text.data(), res.text.size());
|
||||
if (lines) {
|
||||
parser->insert(vase, cursor.row - lines, lines);
|
||||
parser->dirty(vase, cursor.row - lines, 1);
|
||||
} else {
|
||||
parser->dirty(vase, cursor.row, 1);
|
||||
}
|
||||
} break;
|
||||
case io::KeyEvent::KeyType::SPECIAL:
|
||||
if (!vase)
|
||||
break;
|
||||
switch (res.special_key) {
|
||||
case io::KeyEvent::SpecialKey::UNKNOWN:
|
||||
break;
|
||||
case io::KeyEvent::SpecialKey::RIGHT:
|
||||
if (cursor < cmd.size())
|
||||
++cursor;
|
||||
break;
|
||||
case io::KeyEvent::SpecialKey::LEFT:
|
||||
if (cursor > 0)
|
||||
--cursor;
|
||||
break;
|
||||
case io::KeyEvent::SpecialKey::RIGHT: {
|
||||
vase::Iterator it(vase, cursor.row, Direction::Forward);
|
||||
if (!it.next())
|
||||
throw ed_error("Invalid cursor position.");
|
||||
cursor.col = next_cluster(it.line, cursor.col);
|
||||
} break;
|
||||
case io::KeyEvent::SpecialKey::LEFT: {
|
||||
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: {
|
||||
size_t line_start =
|
||||
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)
|
||||
if (!cursor.row)
|
||||
break;
|
||||
size_t prev_end = line_start - 1;
|
||||
size_t prev_start =
|
||||
cmd.rfind('\n', prev_end == 0 ? 0 : prev_end - 1);
|
||||
if (prev_start == std::string::npos)
|
||||
prev_start = 0;
|
||||
else
|
||||
++prev_start;
|
||||
size_t prev_len = prev_end - prev_start;
|
||||
cursor = prev_start + std::min(col, prev_len);
|
||||
break;
|
||||
}
|
||||
vase::Iterator current(vase, cursor.row, Direction::Forward);
|
||||
if (!current.next())
|
||||
throw ed_error("Invalid cursor position.");
|
||||
size_t wanted = visual_column(current.line, cursor.col);
|
||||
--cursor.row;
|
||||
vase::Iterator previous(vase, cursor.row, Direction::Forward);
|
||||
if (!previous.next())
|
||||
throw ed_error("Invalid cursor position.");
|
||||
cursor.col = byte_column(previous.line, wanted);
|
||||
} break;
|
||||
case io::KeyEvent::SpecialKey::DOWN: {
|
||||
size_t line_start =
|
||||
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())
|
||||
if (cursor.row + 1 >= vase->lines)
|
||||
break;
|
||||
size_t next_start = line_end + 1;
|
||||
size_t next_end = cmd.find('\n', next_start);
|
||||
if (next_end == std::string::npos)
|
||||
next_end = cmd.size();
|
||||
size_t next_len = next_end - next_start;
|
||||
cursor = next_start + std::min(col, next_len);
|
||||
break;
|
||||
}
|
||||
vase::Iterator current(vase, cursor.row, Direction::Forward);
|
||||
if (!current.next())
|
||||
throw ed_error("Invalid cursor position.");
|
||||
size_t wanted = visual_column(current.line, cursor.col);
|
||||
++cursor.row;
|
||||
vase::Iterator previous(vase, cursor.row, Direction::Forward);
|
||||
if (!previous.next())
|
||||
throw ed_error("Invalid cursor position.");
|
||||
cursor.col = byte_column(previous.line, wanted);
|
||||
} break;
|
||||
case io::KeyEvent::SpecialKey::DELETE:
|
||||
if (cursor < cmd.size())
|
||||
cmd.erase(cursor, 1);
|
||||
auto next = cursor;
|
||||
vase::Iterator it(vase, cursor.row, Direction::Forward);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
uint16_t last_row = start + total_lines;
|
||||
layout_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.write("\n", 1);
|
||||
return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false};
|
||||
bed.io.write("\n");
|
||||
return {vase, false};
|
||||
}
|
||||
|
||||
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();
|
||||
term_height = rows;
|
||||
term_width = cols;
|
||||
@@ -166,46 +331,85 @@ void TextMode::grow() {
|
||||
return;
|
||||
bed.io.move_cursor(rows, 1);
|
||||
for (long i = 0; i < overflow; ++i)
|
||||
bed.io.write("\n", 1);
|
||||
bed.io.write("\n");
|
||||
start -= overflow;
|
||||
if (start < 1)
|
||||
start = 1;
|
||||
}
|
||||
|
||||
void TextMode::redraw() {
|
||||
auto [rows, cols] = bed.io.terminal_size();
|
||||
term_height = rows;
|
||||
term_width = cols;
|
||||
layout_lines();
|
||||
grow();
|
||||
bed.io.write("\x1b[?25l", 6);
|
||||
for (uint16_t i = 0; i < height; ++i) {
|
||||
bed.io.move_cursor(start + i, 1);
|
||||
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;
|
||||
while (pos <= cmd.size()) {
|
||||
size_t end = cmd.find('\n', pos);
|
||||
if (end == std::string::npos)
|
||||
end = cmd.size();
|
||||
if (screen_line < start + height) {
|
||||
size_t len = std::min(end - pos, size_t(term_width));
|
||||
bed.io.move_cursor(screen_line, 1);
|
||||
bed.io.write(cmd.substr(pos, len));
|
||||
}
|
||||
if (end == cmd.size())
|
||||
uint64_t line = 0;
|
||||
size_t cursor_screen_row = start;
|
||||
size_t cursor_screen_col = 0;
|
||||
vase::Iterator it(vase, line, Direction::Forward);
|
||||
while (it.next()) {
|
||||
if (screen_line >= start + height)
|
||||
break;
|
||||
pos = end + 1;
|
||||
std::string_view l(it.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;
|
||||
}
|
||||
size_t vis_col = std::min(col, size_t(term_width - 1));
|
||||
bed.io.move_cursor(start + line, vis_col + 1);
|
||||
++line;
|
||||
}
|
||||
} // namespace bed::internal::ui
|
||||
cursor_screen_col = std::min(cursor_screen_col, size_t(term_width - 1));
|
||||
bed.io.move_cursor(cursor_screen_row, cursor_screen_col + 1);
|
||||
bed.io.write("\x1b[?25h", 6);
|
||||
}
|
||||
} // namespace bed::internal::ui::text_mode
|
||||
|
||||
@@ -74,14 +74,7 @@ void _insert(AppendStorage *ap, Shard **root, Point *point, const char *data, ui
|
||||
++lines;
|
||||
last_line = ++data;
|
||||
}
|
||||
uint64_t col = 0;
|
||||
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;
|
||||
}
|
||||
uint64_t col = end - last_line;
|
||||
if (lines) {
|
||||
point->row += lines;
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
return offset_of(root, point.row) + point.col;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user