Add marks and regex addressing systems

- Hook up parser to buffer edits.
- Other minor fixes.
This commit is contained in:
2026-08-22 15:01:48 +01:00
parent 2a903f761a
commit bea8ff18c0
15 changed files with 357 additions and 73 deletions
+9 -4
View File
@@ -29,11 +29,12 @@ Address::Address(std::string &cmd, uint64_t &i) {
break;
case '\'': {
i++;
if (i < cmd.size() && 'a' <= cmd[i] && cmd[i] <= 'z')
if (i < cmd.size()
&& (('a' <= cmd[i] && cmd[i] <= 'z') || ('A' <= cmd[i] && cmd[i] <= 'Z')))
i++;
else
throw address_error("Invalid mark.");
base = Mark(cmd[i]);
base = Mark(cmd[i - 1]);
} break;
case '/': {
i++;
@@ -54,7 +55,9 @@ Address::Address(std::string &cmd, uint64_t &i) {
else
i++;
}
base = RegexLine(Direction::Forward, cmd.substr(start, i - start));
base = Regex(Direction::Forward, cmd.substr(start, i - start));
if (i < cmd.size())
i++;
} break;
case '?': {
i++;
@@ -75,7 +78,9 @@ Address::Address(std::string &cmd, uint64_t &i) {
else
i++;
}
base = RegexLine(Direction::Backward, cmd.substr(start, i - start));
base = Regex(Direction::Backward, cmd.substr(start, i - start));
if (i < cmd.size())
i++;
} break;
case '+': {
base = Current();
+15
View File
@@ -17,6 +17,21 @@ uint64_t Address::resolve(BEd &ctx) {
line = ctx.active->vase.lines();
} else if constexpr (std::is_same_v<T, Number>) {
line = addr.i;
} else if constexpr (std::is_same_v<T, Mark>) {
line = ctx.active->marks.get(addr.m);
if (line == UINT64_MAX)
throw address_error("Mark not set.");
} else if constexpr (std::is_same_v<T, Regex>) {
std::string_view re = addr.re;
if (re.size() == 0)
re = ctx.last_regex;
if (re.size() == 0)
throw address_error("No regex given.");
if (addr.dir == Direction::Forward)
line = ctx.active->vase.find_next(re, ctx.active->line - 1) + 1;
else
line = ctx.active->vase.find_prev(re, ctx.active->line - 1) + 1;
ctx.last_regex = re;
} else if constexpr (std::is_same_v<T, Block>) {
uint64_t current_line = ctx.active->line;
if (ctx.active->vase.lines() > 0 && current_line == 0)
+55 -2
View File
@@ -64,8 +64,6 @@ void Buffer::jump(uint64_t n_line) {
if (n_line > vase.lines())
throw ed_error("Line number too high.");
line = n_line;
prev_range.start = line;
prev_range.end = line;
}
void Buffer::append(std::string text, uint64_t line) {
@@ -82,6 +80,9 @@ void Buffer::append(std::string text, uint64_t line) {
prev_range.start = p.row + 1;
vase.insert(&p, text);
prev_range.end = p.row + 1;
marks.insert(prev_range.start, prev_range.end);
if (parser)
parser->insert(vase, prev_range.start, prev_range.end);
modified = true;
}
@@ -89,6 +90,9 @@ void Buffer::remove(uint64_t start_line, uint64_t end_line) {
vase.erase({{start_line - 1, 0}, {end_line, 0}});
prev_range.start = start_line;
prev_range.end = start_line;
marks.erase(start_line, end_line - start_line + 1);
if (parser)
parser->erase(vase, start_line, end_line - start_line + 1);
modified = true;
}
@@ -96,6 +100,9 @@ void Buffer::join(uint64_t start_line, uint64_t end_line) {
vase.regex_search_replace(R"(\n)", {{start_line - 1, 0}, {end_line, 0}}, "", "g");
prev_range.start = start_line;
prev_range.end = start_line;
marks.collapse(start_line, end_line - start_line);
if (parser)
parser->erase(vase, start_line, end_line - start_line);
modified = true;
}
@@ -132,6 +139,8 @@ inline void reset(std::ostream &out) {
}
void Buffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
prev_range.start = start_line;
prev_range.end = end_line;
if (parser) {
std::optional<syntax::Parser::Iterator> it_o = parser->get_hl(vase, start_line - 1);
auto &it = *it_o;
@@ -169,8 +178,52 @@ void Buffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
while (it.next() && start_line++ <= end_line)
std::cout << it.line << std::endl;
}
}
void Buffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
prev_range.start = start_line;
prev_range.end = end_line;
uint8_t width = 1;
for (uint64_t n = end_line; n >= 10; n /= 10)
++width;
if (parser) {
std::optional<syntax::Parser::Iterator> it_o = parser->get_hl(vase, start_line - 1);
auto &it = *it_o;
while (start_line <= end_line) {
it.next();
std::cout << std::setw(width) << it.at << "\t";
const std::string &line = it.it->line;
const auto &tokens = it.tokens;
uint32_t cursor = 0;
for (const auto &token : tokens) {
const uint32_t start = token.start;
const uint32_t end = token.end;
if (start > line.size())
break;
if (end > line.size())
break;
if (cursor < start) {
std::cout.write(
line.data() + cursor,
start - cursor
);
}
const auto highlight = ctx.theme.get(token);
apply(std::cout, highlight);
std::cout.write(line.data() + start, end - start);
reset(std::cout);
cursor = end;
}
if (cursor < line.size())
std::cout.write(line.data() + cursor, line.size() - cursor);
std::cout << std::endl;
++start_line;
}
} else {
vase::Iterator it = vase.iterate(start_line - 1, Direction::Forward);
while (it.next() && start_line <= end_line)
std::cout << std::setw(width) << start_line++ << "\t" << it.line << std::endl;
}
}
std::string Buffer::list_string(std::string_view s) {
+78 -9
View File
@@ -9,6 +9,15 @@ void Suffix::register_suffixes(BEd &ctx) {
.handle = [](BEd &ctx) {
auto line = ctx.active->line;
ctx.active->print(ctx, line, line);
ctx.active->jump(line);
}
};
ctx.suffixes['n' - 'a'] = Suffix{
.desc = "Prints current line with line number.",
.handle = [](BEd &ctx) {
auto line = ctx.active->line;
ctx.active->number_print(ctx, line, line);
ctx.active->jump(line);
}
};
}
@@ -55,6 +64,26 @@ void Command::register_posix(BEd &ctx) {
}
}
);
ctx.commands.insert(
"j",
Command{
.address_mode = Command::AddressMode::Range,
.suffix = Command::SuffixKind::Suffix,
.desc = "Join a set of lines (default: .,.+1)",
.accept_zero = true,
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
std::cout << addresses.size() << "\n";
uint64_t start_line = ctx.active->line;
uint64_t end_line = ctx.active->line + 1;
if (addresses.size() == 1)
return;
else if (addresses.size() == 2)
start_line = addresses[0], end_line = addresses[1];
ctx.active->join(start_line, end_line);
ctx.active->jump(start_line);
}
}
);
ctx.commands.insert(
"q",
Command{
@@ -92,27 +121,67 @@ void Command::register_posix(BEd &ctx) {
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
uint64_t start_line = ctx.active->line;
uint64_t end_line = ctx.active->line;
if (!addresses.size())
ctx.active->print(ctx, start_line, end_line);
else if (addresses.size() == 1)
ctx.active->print(ctx, addresses[0], addresses[0]);
else
ctx.active->print(ctx, addresses[0], addresses[1]);
if (addresses.size() == 1)
start_line = addresses[0], end_line = addresses[0];
else if (addresses.size() == 2)
start_line = addresses[0], end_line = addresses[1];
ctx.active->print(ctx, start_line, end_line);
ctx.active->jump(end_line);
}
}
);
ctx.commands.insert(
"n",
Command{
.address_mode = Command::AddressMode::Range,
.suffix = Command::SuffixKind::Suffix,
.desc = "Print range with line numbers (default .,.)",
.accept_zero = false,
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
uint64_t start_line = ctx.active->line;
uint64_t end_line = ctx.active->line;
if (addresses.size() == 1)
start_line = addresses[0], end_line = addresses[0];
else if (addresses.size() == 2)
start_line = addresses[0], end_line = addresses[1];
ctx.active->number_print(ctx, start_line, end_line);
ctx.active->jump(end_line);
}
}
);
ctx.commands.insert(
"=",
Command{
.address_mode = Command::AddressMode::Single,
.address_mode = Command::AddressMode::Range,
.suffix = Command::SuffixKind::Suffix,
.desc = "Print line number",
.desc = "Print line number(s)",
.accept_zero = true,
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
if (!addresses.size())
std::cout << ctx.active->vase.lines() << std::endl;
else
else if (addresses.size() == 1)
std::cout << addresses[0] << std::endl;
else
std::cout << addresses[0] << "," << addresses[1] << std::endl;
}
}
);
ctx.commands.insert(
"k",
Command{
.address_mode = Command::AddressMode::Single,
.suffix = Command::SuffixKind::Continuation,
.desc = "Mark a line.",
.accept_zero = true,
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view args) {
if (args.size() < 1 || args.size() > 2)
throw ed_error("Malformed mark command");
if (addresses.size())
ctx.active->marks.set(args[0], addresses[0]);
else
ctx.active->marks.set(args[0], ctx.active->line);
if (args.size() > 1)
ctx.suffix_handle(args[1]);
}
}
);
+1 -4
View File
@@ -243,15 +243,12 @@ void Parser::modify(vase::Vase &vase, uint64_t target, uint64_t count) {
events.clear();
lang.parse(&state, it.line, at == 0, &tokens, &events);
for (const auto &ev : events) {
const auto t = uint8_t(ev.ev_type);
if (t > ParseEvent::Closing)
continue;
if (c.leaf->n == c.leaf->cap) {
uint32_t cap = c.leaf->cap ? c.leaf->cap * 2 : 8;
c.leaf->blocks = (uint16_t *)realloc(c.leaf->blocks, cap * sizeof(uint16_t));
c.leaf->cap = cap;
}
c.leaf->blocks[c.leaf->n++] = t << 15 | (at - chunk_start);
c.leaf->blocks[c.leaf->n++] = ev.closing << 15 | (at - chunk_start);
}
at++;
}
+16 -15
View File
@@ -225,7 +225,7 @@ bool handle_line_markers(RubyParser &p, std::vector<Token> *tokens, std::vector<
if (p.len() == 6 && p.peek_str(6) == "=begin") {
p.current().state = RubyState::RubyInternalState::COMMENT;
p.current().flags &= ~RubyState::RubyInternalState::EXPECTING_EXPRESSION;
events->push_back(ParseEvent::Opening);
events->push_back(false);
tokens->push_back({0, p.len(), Token::Comment});
return true;
}
@@ -266,7 +266,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
case RubyState::RubyInternalState::CLASS_NAME: {
if (p.peek() == '<' && p.peek(1) == '<') {
p.advance(2);
events->push_back(ParseEvent::Opening);
events->push_back(false);
p.current().flags = (p.current().flags & ~RubyState::RubyInternalState::NAME_MASK);
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
return false;
@@ -285,7 +285,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
}
}
events->push_back(ParseEvent::Opening);
events->push_back(false);
p.current().flags = (p.current().flags & ~RubyState::RubyInternalState::NAME_MASK);
return false;
}
@@ -311,7 +311,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
}
}
events->push_back(ParseEvent::Opening);
events->push_back(false);
p.current().flags = (p.current().flags & ~RubyState::RubyInternalState::NAME_MASK);
return false;
}
@@ -330,7 +330,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
}
}
events->push_back(ParseEvent::Opening);
events->push_back(false);
p.current().flags = (p.current().flags & ~RubyState::RubyInternalState::NAME_MASK);
return false;
}
@@ -899,21 +899,21 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
p.advance(j);
return false;
} else if (j == tries.expecting_end_keywords_trie.longest_match(p.peek_str(j))) {
events->push_back(ParseEvent::Opening);
events->push_back(false);
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::Keyword});
p.advance(j);
return false;
} else if (j == tries.conditional_keywords_trie.longest_match(p.peek_str(j))) {
if (p.current().flags & RubyState::RubyInternalState::NEWLINE || p.op_last)
events->push_back(ParseEvent::Opening);
events->push_back(false);
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
tokens->push_back({p.i, p.i + j, Token::Keyword});
p.advance(j);
return false;
} else if (j == tries.end_keywords_trie.longest_match(p.peek_str(j))) {
p.current().flags |= RubyState::RubyInternalState::EXPECTING_EXPRESSION;
events->push_back(ParseEvent::Opening);
events->push_back(false);
tokens->push_back({p.i, p.i + j, Token::Keyword});
p.advance(j);
return false;
@@ -950,7 +950,7 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
}
if (j == 3 && p.peek_str(3) == "end") {
events->push_back(ParseEvent::Closing);
events->push_back(true);
tokens->push_back({p.i, p.i + j, Token::Keyword});
p.advance(3);
return false;
@@ -980,17 +980,18 @@ bool handle_syntax(RubyParser &p, std::vector<Token> *tokens, std::vector<ParseE
return false;
}
uint32_t start = p.i;
p.advance(j);
if (p.peek() == ':') {
if (p.peek(j) == ':') {
p.advance(j);
p.advance();
tokens->push_back({start, p.i, Token::Label});
return false;
} else if (p.peek(-1) == '!' || p.peek(-1) == '?') {
p.advance();
} else if (p.peek(j - 1) == '!' || p.peek(j - 1) == '?') {
p.advance(j);
tokens->push_back({start, p.i, Token::Function});
return false;
} else {
uint32_t j = 0;
p.advance(j);
j = 0;
if (p.peek(j) == '(' || p.peek(j) == '{') {
tokens->push_back({start, p.i, Token::Function});
return false;
@@ -1066,7 +1067,7 @@ void ruby_parse(
tokens->push_back({p.i, p.len(), Token::Comment});
if (p.i == 0 && p.peek_str(4) == "=end") {
p.current().state = RubyState::RubyInternalState::NONE;
events->push_back(ParseEvent::Closing);
events->push_back(true);
}
return;
}
+118
View File
@@ -156,4 +156,122 @@ std::vector<Range> Vase::regex_search(
result.push_back({point_of(match.start), point_of(match.end)});
return result;
}
uint64_t Vase::find_next(std::string_view pattern, uint64_t start) {
std::vector<RegexMatch> results;
int errornumber;
PCRE2_SIZE erroroffset;
pcre2_code *re = pcre2_compile(
(PCRE2_SPTR)pattern.data(),
pattern.size(),
PCRE2_UTF | PCRE2_EXTENDED,
&errornumber,
&erroroffset,
NULL
);
if (re == NULL)
throw ed_error("Can't compile regex.");
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr);
if (!match_data) {
pcre2_code_free(re);
throw ed_error("Can't create regex match data.");
}
uint64_t at = (start + 1) % lines();
LineIterator it(root, at, Direction::Forward);
std::string line;
while (it.next(&line)) {
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("Regex matching failed.");
}
at++;
}
at = 0;
LineIterator it2(root, at, Direction::Forward);
while (it2.next(&line)) {
if (at >= start)
break;
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("Regex matching failed.");
}
at++;
}
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("No line matched.");
}
uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) {
std::vector<RegexMatch> results;
int errornumber;
PCRE2_SIZE erroroffset;
pcre2_code *re = pcre2_compile(
(PCRE2_SPTR)pattern.data(),
pattern.size(),
PCRE2_UTF | PCRE2_EXTENDED,
&errornumber,
&erroroffset,
NULL
);
if (re == NULL)
throw ed_error("Can't compile regex.");
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, nullptr);
if (!match_data) {
pcre2_code_free(re);
throw ed_error("Can't create regex match data.");
}
uint64_t at = (start == 0 ? lines() : start) - 1;
LineIterator it(root, at, Direction::Backward);
std::string line;
while (it.next(&line)) {
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("Regex matching failed.");
}
at--;
}
at = lines();
LineIterator it2(root, at, Direction::Backward);
while (it2.next(&line)) {
if (at <= start)
break;
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
if (rc >= 0) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return at;
}
if (rc != PCRE2_ERROR_NOMATCH) {
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("Regex matching failed.");
}
at--;
}
pcre2_match_data_free(match_data);
pcre2_code_free(re);
throw ed_error("No line matched.");
}
} // namespace bed::internal::vase