Make substitutions work and other minor fixes.

This commit is contained in:
2026-08-30 17:02:34 +01:00
parent 41049b1ab6
commit 49bf5a2e2d
9 changed files with 233 additions and 73 deletions
+10
View File
@@ -10,6 +10,10 @@ struct Parser {
ParseState *root;
Language lang;
bool in_edit = false;
bool dirty = false;
uint64_t dirty_start = 0;
uint64_t dirty_end = 0;
Parser(vase::Shard *, uint64_t, Language);
~Parser();
@@ -21,6 +25,12 @@ struct Parser {
void insert(vase::Shard *, uint64_t, uint64_t);
void modify(vase::Shard *, uint64_t, uint64_t);
void begin_edit();
void erase(uint64_t start, uint64_t count);
void insert(uint64_t start, uint64_t count);
void end_edit(vase::Shard *vase);
void mark_dirty(uint64_t start, uint64_t end);
uint64_t next_closing(uint64_t line);
uint64_t prev_opening(uint64_t line);
+6 -3
View File
@@ -20,8 +20,8 @@ struct Range {
};
struct RegexGroup {
uint64_t start{0};
uint64_t end{0};
uint64_t start{UINT64_MAX};
uint64_t end{UINT64_MAX};
};
struct RegexMatch {
@@ -57,7 +57,10 @@ uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start);
Shard *substitute(
AppendStorage *ap, Shard *root,
std::string_view pattern, uint64_t start, uint64_t end,
std::string_view replace, std::string_view options
std::string_view replace, std::string_view options,
const std::function<void(
uint64_t line, uint64_t old_lines, uint64_t new_lines
)> &on_edit = nullptr
);
// internal
+38 -7
View File
@@ -22,11 +22,20 @@ uint64_t Buffer::bytes() {
return 0;
}
void Buffer::load(BEd &, vase::Shard *text) {
void Buffer::load(BEd &ctx, vase::Shard *text) {
try {
vase::Shard::release(root);
root = text;
state = buffer::Buffer::Unmodified;
if (!text) {
ctx.prev().buffername = name;
ctx.prev().start = 0;
ctx.prev().end = 0;
} else {
ctx.prev().buffername = name;
ctx.prev().start = 1;
ctx.prev().end = text->lines + 1;
}
parser.emplace(root, lines(), syntax::ruby::lang_ruby());
} catch (...) {
vase::Shard::release(text);
@@ -71,13 +80,35 @@ void Buffer::substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) {
// TODO: make substitue return a list of modifications made.
root = vase::substitute(&ctx.append, root, regex, start_line, end_line, replacement, options);
/*prev_range.start = start_line;
prev_range.end = start_line;
marks.collapse(start_line, end_line - start_line);
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = end_line;
if (parser)
parser->erase(vase, start_line, end_line - start_line);*/
parser->begin_edit();
root = vase::substitute(
&ctx.append,
root,
regex,
start_line,
end_line,
replacement,
options,
[&](uint64_t line, uint64_t old_lines, uint64_t new_lines) {
if (old_lines) {
ctx.marks.erase(name, line, old_lines);
if (parser)
parser->erase(line, old_lines);
}
if (new_lines) {
ctx.marks.insert(name, line, line + new_lines - 1);
if (parser)
parser->insert(line, line + new_lines - 1);
}
}
);
if (parser)
parser->end_edit(root);
ctx.prev().buffername = name;
state = Modified;
}
+55 -3
View File
@@ -79,7 +79,7 @@ void Function::register_posix(BEd &ctx) {
.input_mode = Function::InputMode::None,
.desc = "Delete set of lines.",
.default_address = ".,.",
.accept_zero = true,
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
@@ -88,6 +88,25 @@ void Function::register_posix(BEd &ctx) {
}
}
);
ctx.functions.insert(
"c",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::None,
.input_mode = Function::InputMode::Text,
.desc = "Change set of lines.",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *text, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
ctx.buffer(addr.buffername).append(ctx, text, addr.start - 1);
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
vase::Shard::release(text);
}
}
);
ctx.functions.insert(
"j",
Function{
@@ -186,7 +205,7 @@ void Function::register_posix(BEd &ctx) {
.accept_zero = true,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Range>(addr_);
auto &addr = std::get<buffer::Range>(addr_);
if (addr.start == addr.end)
std::cout << ':' << addr.buffername << ':' << addr.start << "\n";
else
@@ -206,11 +225,44 @@ void Function::register_posix(BEd &ctx) {
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg, std::vector<buffer::Line> *) {
auto addr = std::get<buffer::Line>(addr_);
auto &addr = std::get<buffer::Line>(addr_);
ctx.mark(std::get<char>(arg), addr);
}
}
);
ctx.functions.insert(
"s",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Regex,
.input_mode = Function::InputMode::None,
.desc = "Substitute regex.",
.default_address = ".,.",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](BEd &ctx, const buffer::Address &addr_, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
auto &addr = std::get<buffer::Range>(addr_);
auto arg = std::get<RegexArg>(arg_);
if (arg.expression == "")
arg.expression = ctx.last_regex;
else
ctx.last_regex = arg.expression;
if (arg.replacement == "%")
arg.replacement = ctx.last_replacement;
else
ctx.last_replacement = arg.replacement;
ctx.buffer(addr.buffername)
.substitute(
ctx,
addr.start,
addr.end,
arg.expression,
arg.replacement,
arg.options
);
}
}
);
ctx.functions.insert(
"e",
Function{
+6 -5
View File
@@ -132,7 +132,7 @@ void Parser::operation() {
if (delim == '\0')
throw ed_error("regex expected");
advance();
uint64_t j = 0;
uint16_t j = 0;
while (true) {
if (peek(j) == '\0')
throw ed_error("Unterminated regex");
@@ -141,7 +141,7 @@ void Parser::operation() {
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']')
while (peek(j) != '\0' && peek(j) != ']')
j++;
else
j++;
@@ -157,15 +157,16 @@ void Parser::operation() {
else if (peek(j) == '\\')
j += 2;
else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']')
while (peek(j) != '\0' && peek(j) != ']')
j++;
else
j++;
}
std::string replacement;
std::string replacement(peek_str(j));
if (peek(j) != '\0') {
replacement = peek_str(j);
advance(j + 1);
} else {
advance(j);
}
std::string options;
if (peek() != '\0') {
+63 -27
View File
@@ -123,36 +123,15 @@ ParseState *Parser::join_tree(ParseState *a, ParseState *b) {
}
void Parser::erase(vase::Shard *vase, uint64_t start, uint64_t count) {
if (count == 0 || !root)
return;
auto [a, remaining] = split_tree(root, start);
auto [waste, b] = split_tree(remaining, count);
destroy_tree(waste, lang);
root = join_tree(a, b);
modify(vase, start, 1);
begin_edit();
erase(start, count);
end_edit(vase);
}
void Parser::insert(vase::Shard *vase, uint64_t start, uint64_t count) {
if (count == 0)
return;
std::vector<ParseStateLeaf *> leaves;
leaves.reserve((count + MAX_CHUNK - 1) / MAX_CHUNK);
uint64_t consumed = 0;
while (consumed < count) {
auto *leaf = (ParseStateLeaf *)malloc(sizeof(ParseStateLeaf));
leaf->state = nullptr;
leaf->blocks = nullptr;
leaf->n = 0;
leaf->cap = 0;
uint64_t chunk = std::min(MAX_CHUNK, count - consumed);
leaf->header = chunk;
consumed += chunk;
leaves.push_back(leaf);
}
ParseState *subtree = build_tree(leaves, 0, leaves.size());
auto [left, right] = split_tree(root, start);
root = join_tree(join_tree(left, subtree), right);
modify(vase, start, count);
begin_edit();
insert(start, count);
end_edit(vase);
}
void Parser::modify(vase::Shard *vase, uint64_t target, uint64_t count) {
@@ -219,6 +198,63 @@ void Parser::modify(vase::Shard *vase, uint64_t target, uint64_t count) {
lang.destroy(state);
}
void Parser::begin_edit() {
in_edit = true;
}
void Parser::mark_dirty(uint64_t start, uint64_t end) {
if (!dirty) {
dirty_start = start;
dirty_end = end;
dirty = true;
} else {
dirty_start = std::min(dirty_start, start);
dirty_end = std::max(dirty_end, end);
}
}
void Parser::erase(uint64_t start, uint64_t count) {
if (count == 0 || !root)
return;
auto [a, remaining] = split_tree(root, start);
auto [waste, b] = split_tree(remaining, count);
destroy_tree(waste, lang);
root = join_tree(a, b);
mark_dirty(start, start + 1);
}
void Parser::insert(uint64_t start, uint64_t count) {
if (count == 0)
return;
std::vector<ParseStateLeaf *> leaves;
leaves.reserve((count + MAX_CHUNK - 1) / MAX_CHUNK);
uint64_t consumed = 0;
while (consumed < count) {
auto *leaf = (ParseStateLeaf *)malloc(sizeof(ParseStateLeaf));
leaf->state = nullptr;
leaf->blocks = nullptr;
leaf->n = 0;
leaf->cap = 0;
uint64_t chunk = std::min(MAX_CHUNK, count - consumed);
leaf->header = chunk;
consumed += chunk;
leaves.push_back(leaf);
}
ParseState *subtree = build_tree(leaves, 0, leaves.size());
auto [left, right] = split_tree(root, start);
root = join_tree(join_tree(left, subtree), right);
mark_dirty(start, start + count);
}
void Parser::end_edit(vase::Shard *vase) {
in_edit = false;
if (!dirty)
return;
uint64_t count = dirty_end > dirty_start ? dirty_end - dirty_start : 1;
modify(vase, dirty_start, count);
dirty = false;
}
uint64_t Parser::next_closing(uint64_t line) {
if (!root)
return UINT64_MAX;
+50 -22
View File
@@ -25,12 +25,7 @@ std::vector<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s) {
};
for (size_t i = 0; i < s.size(); ++i) {
char c = s[i];
if (c == '\\' && i + 1 < s.size() && s[i + 1] == '$') {
constant.push_back('$');
++i;
continue;
}
if (c == '$' && i + 1 < s.size()) {
if (c == '\\' && i + 1 < s.size()) {
char next = s[i + 1];
if (next == '0') {
flush_constant();
@@ -40,10 +35,7 @@ std::vector<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s) {
.value = (uint8_t)0
}
);
++i;
continue;
}
if (next >= '1' && next <= '9') {
} else if (next >= '1' && next <= '9') {
flush_constant();
parts.push_back(
ReplacePart{
@@ -51,9 +43,22 @@ std::vector<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s) {
.value = (uint8_t)(next - '0')
}
);
} else if (next == 'n') {
constant.push_back('\n');
} else {
constant.push_back(next);
}
++i;
continue;
}
if (c == '&') {
flush_constant();
parts.push_back(
ReplacePart{
.type = ReplacePart::PartType::FullMatch,
.value = (uint8_t)0
}
);
}
constant.push_back(c);
}
@@ -64,7 +69,8 @@ std::vector<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s) {
Shard *substitute(
AppendStorage *ap, Shard *root,
std::string_view pattern, uint64_t start, uint64_t end,
std::string_view replace, std::string_view options
std::string_view replace, std::string_view options,
const std::function<void(uint64_t line, uint64_t old_lines, uint64_t new_lines)> &on_edit
) {
if (!start || !end)
throw ed_error("Invalid range.");
@@ -79,11 +85,18 @@ Shard *substitute(
uint64_t end_offset =
(end + 1 == line_count)
? root->length
: offset_of(root, end + 1);
: offset_of(root, end + 1) - 1;
std::vector<RegexMatch> matches = _regex_search(root, pattern, start_offset, end_offset, options);
if (matches.empty())
return root;
std::vector<ReplacePart> replace_parts = parse_replace(ap, replace);
struct Edit {
uint64_t line;
uint64_t old_lines;
uint64_t new_lines;
};
uint64_t orig_line = start;
int64_t line_delta = 0;
std::vector<Shard *> pieces;
pieces.reserve(matches.size() * 2 + 1);
Shard *remaining = root;
@@ -96,26 +109,33 @@ Shard *substitute(
Shard::release(remaining);
pieces.push_back(keep);
remaining = rest;
orig_line += keep ? keep->lines : 0;
}
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
Shard::release(remaining);
remaining = rest2;
uint64_t old_lines = dropped ? dropped->lines : 0;
uint64_t new_lines = 0;
for (size_t i = 0; i < replace_parts.size(); ++i) {
const ReplacePart &part = replace_parts[i];
switch (part.type) {
case ReplacePart::PartType::Constant:
Shard::retain(std::get<Shard *>(part.value));
pieces.push_back(std::get<Shard *>(part.value));
case ReplacePart::PartType::Constant: {
Shard *c = std::get<Shard *>(part.value);
Shard::retain(c);
pieces.push_back(c);
new_lines += c ? c->lines : 0;
break;
}
case ReplacePart::PartType::FullMatch:
Shard::retain(dropped);
pieces.push_back(dropped);
new_lines += dropped ? dropped->lines : 0;
break;
case ReplacePart::PartType::CaptureGroup: {
uint8_t idx = std::get<uint8_t>(part.value);
if (idx <= 9) {
const RegexGroup &group = match.groups[idx - 1];
if (group.start > group.end) {
if (group.start != UINT64_MAX) {
uint64_t ls = group.start - match.start;
uint64_t le = group.end - match.start;
auto [a, b] = Shard::split(dropped, ls);
@@ -124,6 +144,7 @@ Shard *substitute(
Shard::release(b);
Shard::release(c);
pieces.push_back(g);
new_lines += g ? g->lines : 0;
}
}
break;
@@ -131,6 +152,13 @@ Shard *substitute(
}
}
Shard::release(dropped);
if (old_lines || new_lines) {
uint64_t report_line = (uint64_t)((int64_t)orig_line + line_delta);
if (on_edit)
on_edit(report_line, old_lines, new_lines);
line_delta += (int64_t)new_lines - (int64_t)old_lines;
}
orig_line += old_lines;
cursor = match.end;
}
pieces.push_back(remaining);
@@ -151,7 +179,7 @@ Shard *substitute(
}
uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) {
if (start == 0 || start > root->lines)
if (start == 0 || start > root->lines + 1)
throw ed_error("Invalid line number.");
start--;
std::vector<RegexMatch> results;
@@ -180,7 +208,7 @@ uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) {
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at;
return at + 1;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
@@ -198,7 +226,7 @@ uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) {
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at;
return at + 1;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
@@ -213,7 +241,7 @@ uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) {
}
uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start) {
if (start == 0 || start > root->lines)
if (start == 0 || start > root->lines + 1)
throw ed_error("Invalid line number.");
start--;
std::vector<RegexMatch> results;
@@ -242,7 +270,7 @@ uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start) {
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at;
return at + 1;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
@@ -260,7 +288,7 @@ uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start) {
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at;
return at + 1;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
+3 -3
View File
@@ -64,7 +64,7 @@ std::vector<RegexMatch> _regex_search(
uint64_t offset = UINT64_MAX;
auto record_match = [&](int rc, PCRE2_SIZE *ovector) {
if (global_offset + (uint64_t)ovector[1] > end_offset || ovector[0] == ovector[1])
if (global_offset + (uint64_t)ovector[1] > end_offset)
return;
RegexMatch match{
.start = global_offset + (uint64_t)ovector[0],
@@ -74,8 +74,8 @@ std::vector<RegexMatch> _regex_search(
PCRE2_SIZE s = ovector[2 * i];
PCRE2_SIZE e = ovector[2 * i + 1];
if (s != PCRE2_UNSET) {
match.groups[i].start = global_offset + (uint64_t)s;
match.groups[i].end = global_offset + (uint64_t)e;
match.groups[i - 1].start = global_offset + (uint64_t)s;
match.groups[i - 1].end = global_offset + (uint64_t)e;
}
}
results.push_back(std::move(match));
-1
View File
@@ -232,7 +232,6 @@ Shard *join(Shard *root, uint64_t start, uint64_t end) {
uint64_t line_count = root->lines + 1;
if (start >= end || end >= line_count)
throw ed_error("line range out of bounds");
uint64_t offset = offset_of(root, start);
std::vector<Shard *> pieces;
pieces.reserve(end - start + 3);