Major updates/cleanup.

This commit is contained in:
2026-09-08 07:00:13 +01:00
parent 0df03f418a
commit 0760209417
23 changed files with 668 additions and 441 deletions
+1
View File
@@ -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"
''; '';
}; };
}; };
+1
View File
@@ -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 = "";
-1
View File
@@ -17,7 +17,6 @@ struct Token {
RubyFunction, RubyFunction,
RubyArg, RubyArg,
Function, Function,
Any,
Shell, Shell,
File, File,
Suffix, Suffix,
+3
View File
@@ -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);
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "decl.h"
namespace bed::internal::syntax {
struct MiniParser {
Language &lang;
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
+14 -6
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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);
-1
View File
@@ -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) {
+17 -4
View File
@@ -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(
+3 -1
View File
@@ -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;
+2 -2
View File
@@ -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
View File
@@ -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({});
+11 -23
View File
@@ -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; curr.bufname = bufname;
} else { else if (curr.bufname->empty())
bufname = *curr.bufname; curr.bufname = bufname = ctx.current().buffername;
}
} else {
curr.bufname = bufname;
}
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; curr.bufname = bufname;
} else { else if (curr.bufname->empty())
bufname = *curr.bufname; curr.bufname = bufname = ctx.current().buffername;
}
} else {
curr.bufname = bufname;
}
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);
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;
} if (curr.jumping)
ctx.current() = resolved;
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) {
+20 -51
View File
@@ -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'];
+26 -6
View File
@@ -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,18 +28,16 @@ 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() {
try {
skip_ws(); skip_ws();
if (peek() == '@') { if (peek() == '@') {
tokens->push_back( token(io::Token::TempCurrent);
{.start = 0,
.end = 1,
.type = io::Token::TempCurrent}
);
advance(); advance();
command->temp_address = true; command->temp_address = true;
} else { } else {
@@ -36,9 +47,18 @@ void Parser::parse() {
addresses(command->addresses); addresses(command->addresses);
operation(); operation();
skip_ws(); skip_ws();
if (peek() != '\0') if (peek() != '\0') {
token(io::Token::Error);
throw ed_error("Malformed command"); 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( Parser::Parser(
std::string_view cmd, BEd &bed, Command *command, std::string_view cmd, BEd &bed, Command *command,
+120
View File
@@ -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
+1
View File
@@ -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;
} }
-5
View File
@@ -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,
-60
View File
@@ -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);
View File
+316 -112
View File
@@ -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);
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; 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);
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; ++screen_line;
} }
size_t vis_col = std::min(col, size_t(term_width - 1)); ++line;
bed.io.move_cursor(start + line, vis_col + 1);
} }
} // 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
+17 -8
View File
@@ -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;
} }