Add some more ed commands.
This commit is contained in:
@@ -17,12 +17,15 @@ struct Command {
|
||||
HelpToggle,
|
||||
Help,
|
||||
Print,
|
||||
List,
|
||||
Number,
|
||||
Append,
|
||||
Insert,
|
||||
Change,
|
||||
Delete,
|
||||
Write,
|
||||
Join,
|
||||
Mark,
|
||||
Edit,
|
||||
ForceEdit,
|
||||
Filename,
|
||||
@@ -85,6 +88,8 @@ struct Ed {
|
||||
Command parse(std::string cmd, bool eof);
|
||||
void append(std::string text, uint64_t line);
|
||||
void remove(uint64_t start_line, uint64_t end_line);
|
||||
std::string list_string(std::string_view s);
|
||||
void join(uint64_t start_line, uint64_t end_line);
|
||||
bool handle(std::string cmd, bool eof);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#include "commands/ed/ed.h"
|
||||
|
||||
namespace crib::commands::ed {
|
||||
void Ed::append(std::string text, uint64_t line) {
|
||||
using namespace crib::internal::vase;
|
||||
Point p = {line, 0};
|
||||
if (!vase.lines()) {
|
||||
text.pop_back();
|
||||
} else if (line == vase.lines()) {
|
||||
p.row--;
|
||||
p.col = UINT64_MAX;
|
||||
text = "\n" + text;
|
||||
text.pop_back();
|
||||
}
|
||||
vase.insert(&p, text);
|
||||
}
|
||||
|
||||
void Ed::remove(uint64_t start_line, uint64_t end_line) {
|
||||
vase.erase({{start_line - 1, 0}, {end_line, 0}});
|
||||
}
|
||||
} // namespace crib::commands::ed
|
||||
+66
-16
@@ -70,6 +70,24 @@ bool Ed::handle(std::string cmd, bool eof) {
|
||||
std::cout << it.line << std::endl;
|
||||
line = line_end;
|
||||
} break;
|
||||
case Command::Type::List: {
|
||||
uint64_t line_start = line;
|
||||
uint64_t line_end = line;
|
||||
if (command.start.type != Command::Address::Type::None) {
|
||||
resolve_address(command.start, &line_start);
|
||||
line_end = line_start;
|
||||
if (command.address_flags & Command::RANGE)
|
||||
resolve_address(command.end, &line_end);
|
||||
}
|
||||
if (line_start == 0)
|
||||
throw ed_error("Line 0 is invalid.");
|
||||
if (line_end < line_start)
|
||||
throw ed_error("Invalid address range.");
|
||||
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
|
||||
while (it.next() && line_start++ <= line_end)
|
||||
std::cout << list_string(it.line) << std::endl;
|
||||
line = line_end;
|
||||
} break;
|
||||
case Command::Type::Number: {
|
||||
uint64_t line_start = line;
|
||||
uint64_t line_end = line;
|
||||
@@ -89,6 +107,12 @@ bool Ed::handle(std::string cmd, bool eof) {
|
||||
line = line_end;
|
||||
} break;
|
||||
case Command::Type::Append: {
|
||||
if (command.start.type != Command::Address::Type::None) {
|
||||
if (command.address_flags & Command::RANGE)
|
||||
resolve_address(command.end, &line);
|
||||
else
|
||||
resolve_address(command.start, &line);
|
||||
}
|
||||
std::string text;
|
||||
std::string cline;
|
||||
uint64_t line_count = 0;
|
||||
@@ -99,12 +123,6 @@ bool Ed::handle(std::string cmd, bool eof) {
|
||||
text.append(cline);
|
||||
text.push_back('\n');
|
||||
}
|
||||
if (command.start.type != Command::Address::Type::None) {
|
||||
if (command.address_flags & Command::RANGE)
|
||||
resolve_address(command.end, &line);
|
||||
else
|
||||
resolve_address(command.start, &line);
|
||||
}
|
||||
if (text.empty()) {
|
||||
if (line == 0)
|
||||
line = 1;
|
||||
@@ -116,6 +134,14 @@ bool Ed::handle(std::string cmd, bool eof) {
|
||||
line += line_count;
|
||||
} break;
|
||||
case Command::Type::Insert: {
|
||||
if (command.start.type != Command::Address::Type::None) {
|
||||
if (command.address_flags & Command::RANGE)
|
||||
resolve_address(command.end, &line);
|
||||
else
|
||||
resolve_address(command.start, &line);
|
||||
}
|
||||
if (line == 0)
|
||||
line = 1;
|
||||
std::string text;
|
||||
std::string cline;
|
||||
uint64_t line_count = 0;
|
||||
@@ -126,14 +152,6 @@ bool Ed::handle(std::string cmd, bool eof) {
|
||||
text.append(cline);
|
||||
text.push_back('\n');
|
||||
}
|
||||
if (command.start.type != Command::Address::Type::None) {
|
||||
if (command.address_flags & Command::RANGE)
|
||||
resolve_address(command.end, &line);
|
||||
else
|
||||
resolve_address(command.start, &line);
|
||||
}
|
||||
if (line == 0)
|
||||
line = 1;
|
||||
if (text.empty())
|
||||
break;
|
||||
vase.snapshot();
|
||||
@@ -247,6 +265,39 @@ bool Ed::handle(std::string cmd, bool eof) {
|
||||
if (!suppress_mode)
|
||||
std::cout << bytes << std::endl;
|
||||
} break;
|
||||
case Command::Type::Join: {
|
||||
uint64_t line_start = line;
|
||||
uint64_t line_end = line + 1;
|
||||
if (command.start.type != Command::Address::Type::None) {
|
||||
resolve_address(command.start, &line_start);
|
||||
line_end = line_start;
|
||||
if (command.address_flags & Command::RANGE)
|
||||
resolve_address(command.end, &line_end);
|
||||
}
|
||||
if (line_end < line_start)
|
||||
throw ed_error("Invalid address range.");
|
||||
if (line_start == 0)
|
||||
throw ed_error("Invalid line number given.");
|
||||
if (line_end == line_start)
|
||||
break;
|
||||
vase.snapshot();
|
||||
join(line_start, line_end);
|
||||
line = line_start;
|
||||
modified = true;
|
||||
} break;
|
||||
case Command::Type::Mark: {
|
||||
uint64_t mark_line = line;
|
||||
if (command.start.type != Command::Address::Type::None) {
|
||||
if (command.address_flags & Command::RANGE)
|
||||
resolve_address(command.end, &mark_line);
|
||||
else
|
||||
resolve_address(command.start, &mark_line);
|
||||
}
|
||||
if (mark_line == 0)
|
||||
throw ed_error("Invalid line number given.");
|
||||
char mark = command.argument[0];
|
||||
marks[mark - 'a'] = mark_line;
|
||||
} break;
|
||||
case Command::Type::Edit: {
|
||||
if (modified && !was_editing) {
|
||||
editing = true;
|
||||
@@ -318,8 +369,7 @@ bool Ed::handle(std::string cmd, bool eof) {
|
||||
std::cout << line << "\t" << it.line << std::endl;
|
||||
break;
|
||||
case 'l':
|
||||
// TODO: escaping.
|
||||
std::cout << it.line << std::endl;
|
||||
std::cout << list_string(it.line) << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#include "commands/ed/ed.h"
|
||||
|
||||
namespace crib::commands::ed {
|
||||
void Ed::append(std::string text, uint64_t line) {
|
||||
using namespace crib::internal::vase;
|
||||
Point p = {line, 0};
|
||||
if (!vase.lines()) {
|
||||
text.pop_back();
|
||||
} else if (line == vase.lines()) {
|
||||
p.row--;
|
||||
p.col = UINT64_MAX;
|
||||
text = "\n" + text;
|
||||
text.pop_back();
|
||||
}
|
||||
vase.insert(&p, text);
|
||||
}
|
||||
|
||||
void Ed::remove(uint64_t start_line, uint64_t end_line) {
|
||||
vase.erase({{start_line - 1, 0}, {end_line, 0}});
|
||||
}
|
||||
|
||||
void Ed::join(uint64_t start_line, uint64_t end_line) {
|
||||
vase.regex_search_replace("\\n", {{start_line - 1, 0}, {end_line, 0}}, "", "");
|
||||
}
|
||||
|
||||
std::string Ed::list_string(std::string_view s) {
|
||||
uint32_t width = 80;
|
||||
winsize ws{};
|
||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0)
|
||||
width = ws.ws_col;
|
||||
std::string out;
|
||||
out.reserve(s.size());
|
||||
const uint32_t max_width = width > 1 ? width - 1 : 1;
|
||||
uint32_t column = 0;
|
||||
auto append = [&](std::string_view text) {
|
||||
if (column + text.size() > max_width) {
|
||||
out += "\\\n";
|
||||
column = 0;
|
||||
}
|
||||
out += text;
|
||||
column += text.size();
|
||||
};
|
||||
for (unsigned char c : s) {
|
||||
switch (c) {
|
||||
case '\\':
|
||||
append("\\\\");
|
||||
break;
|
||||
case '$':
|
||||
append("\\$");
|
||||
break;
|
||||
case '\a':
|
||||
append("\\a");
|
||||
break;
|
||||
case '\b':
|
||||
append("\\b");
|
||||
break;
|
||||
case '\f':
|
||||
append("\\f");
|
||||
break;
|
||||
case '\r':
|
||||
append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
append("\\t");
|
||||
break;
|
||||
case '\v':
|
||||
append("\\v");
|
||||
break;
|
||||
default:
|
||||
if (!std::isprint(c)) {
|
||||
char buf[5];
|
||||
std::snprintf(buf, sizeof(buf), "\\%03o", c);
|
||||
append(buf);
|
||||
} else {
|
||||
append(std::string_view((const char *)&c, 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
out += '$';
|
||||
return out;
|
||||
}
|
||||
} // namespace crib::commands::ed
|
||||
@@ -59,6 +59,10 @@ Command Ed::parse(std::string cmd, bool eof) {
|
||||
command.type = Command::Type::Print;
|
||||
i++;
|
||||
break;
|
||||
case 'l':
|
||||
command.type = Command::Type::List;
|
||||
i++;
|
||||
break;
|
||||
case 'n':
|
||||
command.type = Command::Type::Number;
|
||||
i++;
|
||||
@@ -91,6 +95,10 @@ Command Ed::parse(std::string cmd, bool eof) {
|
||||
command.type = Command::Type::Delete;
|
||||
i++;
|
||||
break;
|
||||
case 'j':
|
||||
command.type = Command::Type::Join;
|
||||
i++;
|
||||
break;
|
||||
case 'w':
|
||||
command.type = Command::Type::Write;
|
||||
i++;
|
||||
@@ -135,6 +143,16 @@ Command Ed::parse(std::string cmd, bool eof) {
|
||||
throw ed_error("Invalid command.");
|
||||
command.argument = cmd.substr(i);
|
||||
return command;
|
||||
case 'k':
|
||||
command.type = Command::Type::Mark;
|
||||
i++;
|
||||
if (i >= cmd.size())
|
||||
throw ed_error("Invalid command.");
|
||||
if ('a' <= cmd[i] && cmd[i] <= 'z')
|
||||
command.argument = cmd[i];
|
||||
else
|
||||
throw ed_error("Invalid mark used.");
|
||||
break;
|
||||
case '#':
|
||||
command.type = Command::Type::Dump;
|
||||
i++;
|
||||
|
||||
@@ -7,6 +7,12 @@ std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
|
||||
auto flush_constant = [&]() {
|
||||
if (!constant.empty()) {
|
||||
uint64_t lines = 0;
|
||||
const char *p = constant.data();
|
||||
const char *end = p + constant.size();
|
||||
while ((p = (const char *)memchr(p, '\n', end - p))) {
|
||||
++lines;
|
||||
++p;
|
||||
}
|
||||
uint64_t pos = append->append(constant.data(), (uint64_t)constant.size());
|
||||
parts.push_back(
|
||||
ReplacePart{
|
||||
|
||||
@@ -7,8 +7,6 @@ std::vector<Vase::RegexMatch> Vase::_regex_search(
|
||||
bool global = false;
|
||||
uint64_t flags = PCRE2_MULTILINE | PCRE2_UTF;
|
||||
|
||||
const char *dot = "(?:(?!\\n)\\X)";
|
||||
|
||||
for (char c : options) {
|
||||
switch (c) {
|
||||
case 'g':
|
||||
@@ -18,7 +16,7 @@ std::vector<Vase::RegexMatch> Vase::_regex_search(
|
||||
flags |= PCRE2_CASELESS;
|
||||
break;
|
||||
case 's':
|
||||
dot = "(?:\\X)";
|
||||
flags |= PCRE2_DOTALL;
|
||||
break;
|
||||
case 'x':
|
||||
flags |= PCRE2_EXTENDED;
|
||||
@@ -40,57 +38,9 @@ std::vector<Vase::RegexMatch> Vase::_regex_search(
|
||||
int errornumber;
|
||||
PCRE2_SIZE erroroffset;
|
||||
|
||||
std::string out;
|
||||
out.reserve(pattern.size() * 2);
|
||||
bool escaped = false;
|
||||
bool in_class = false;
|
||||
bool in_quote = false;
|
||||
for (size_t i = 0; i < pattern.size(); i++) {
|
||||
char c = pattern[i];
|
||||
if (escaped) {
|
||||
out += c;
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (c == '\\') {
|
||||
out += c;
|
||||
if (i + 1 < pattern.size()) {
|
||||
char next = pattern[i + 1];
|
||||
if (next == 'Q')
|
||||
in_quote = true;
|
||||
else if (next == 'E')
|
||||
in_quote = false;
|
||||
out += next;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (in_quote) {
|
||||
out += c;
|
||||
continue;
|
||||
}
|
||||
if (c == '[') {
|
||||
in_class = true;
|
||||
out += c;
|
||||
continue;
|
||||
}
|
||||
if (c == ']' && in_class) {
|
||||
in_class = false;
|
||||
out += c;
|
||||
continue;
|
||||
}
|
||||
if (c == '.' && !in_class) {
|
||||
out += dot;
|
||||
continue;
|
||||
}
|
||||
out += c;
|
||||
}
|
||||
|
||||
pcre2_code *re = pcre2_compile(
|
||||
(PCRE2_SPTR)out.data(),
|
||||
out.size(),
|
||||
(PCRE2_SPTR)pattern.data(),
|
||||
pattern.size(),
|
||||
flags,
|
||||
&errornumber,
|
||||
&erroroffset,
|
||||
|
||||
Reference in New Issue
Block a user