Make substitutions work and other minor fixes.
This commit is contained in:
@@ -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 ®ex, 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
);
|
||||
++i;
|
||||
continue;
|
||||
} 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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user