Setup ed the posix line editor.
This commit is contained in:
@@ -4,6 +4,84 @@
|
||||
#include "pch.h"
|
||||
|
||||
namespace crib::commands::ed {
|
||||
struct ed_error : std::runtime_error {
|
||||
ed_error(const char *msg) : std::runtime_error(msg) {}
|
||||
};
|
||||
|
||||
struct Command {
|
||||
enum struct Type : uint8_t {
|
||||
Invalid,
|
||||
Quit,
|
||||
ForceQuit,
|
||||
PromptToggle,
|
||||
HelpToggle,
|
||||
Help,
|
||||
Print,
|
||||
Number,
|
||||
Append,
|
||||
Change,
|
||||
Delete,
|
||||
Write,
|
||||
Dump,
|
||||
None
|
||||
} type;
|
||||
|
||||
struct Address {
|
||||
enum struct Type : uint8_t {
|
||||
None,
|
||||
Current,
|
||||
Last,
|
||||
Number,
|
||||
Mark,
|
||||
SearchForward,
|
||||
SearchBackward
|
||||
} type;
|
||||
int64_t number = 0;
|
||||
std::string regex = "";
|
||||
int64_t offset = 0;
|
||||
char mark = '\0';
|
||||
};
|
||||
|
||||
Address start{};
|
||||
Address end{};
|
||||
static constexpr uint8_t SEMICOLON = 0b01;
|
||||
static constexpr uint8_t RANGE = 0b10;
|
||||
uint8_t address_flags = 0;
|
||||
char suffix = '\0';
|
||||
std::string argument;
|
||||
};
|
||||
|
||||
struct Ed {
|
||||
crib::internal::vase::Vase vase;
|
||||
uint64_t line = 0;
|
||||
bool modified = false;
|
||||
bool quitting = false;
|
||||
uint64_t marks[26]{0};
|
||||
std::string last_regex = "";
|
||||
bool help_mode = false;
|
||||
bool suppress_mode = false;
|
||||
bool prompt_mode = false;
|
||||
std::string prompt = "*";
|
||||
std::string last_message = "";
|
||||
|
||||
Ed(std::filesystem::path file, bool suppress_mode, std::string prompt_)
|
||||
: vase(file, "/tmp"), suppress_mode(suppress_mode), prompt(prompt_) {
|
||||
if (prompt == "")
|
||||
prompt = "*";
|
||||
else
|
||||
prompt_mode = true;
|
||||
line = vase.lines();
|
||||
std::cout << vase.length() << std::endl;
|
||||
}
|
||||
|
||||
void parse_address(std::string_view cmd, uint64_t &i, Command::Address &addr);
|
||||
void resolve_address(Command::Address addr, uint64_t *out_line);
|
||||
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);
|
||||
bool handle(std::string cmd, bool eof);
|
||||
};
|
||||
|
||||
std::string summary();
|
||||
void help();
|
||||
void run(std::vector<std::string>);
|
||||
|
||||
@@ -24,8 +24,8 @@ struct Iterator {
|
||||
Shard *root;
|
||||
std::string line;
|
||||
|
||||
Iterator(Shard *root, uint64_t line_num)
|
||||
: root(root), it(root, line_num, Direction::Forward) {
|
||||
Iterator(Shard *root, uint64_t line_num, Direction dir)
|
||||
: root(root), it(root, line_num, dir) {
|
||||
Shard::retain(root);
|
||||
}
|
||||
|
||||
@@ -63,9 +63,8 @@ struct Iterator {
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string &next() {
|
||||
it.next(&line);
|
||||
return line;
|
||||
bool next() {
|
||||
return it.next(&line);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -25,7 +25,7 @@ struct Shard {
|
||||
static void retain(Shard *n);
|
||||
static void release(Shard *n);
|
||||
|
||||
static Shard *from_file(std::filesystem::path path, OriginalBuffer *b);
|
||||
static Shard *from_file(std::filesystem::path path, OriginalBuffer *b, bool posix_ending);
|
||||
static std::vector<Shard *> from_swap(std::filesystem::path path, OriginalBuffer *b);
|
||||
|
||||
static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset);
|
||||
@@ -34,6 +34,7 @@ struct Shard {
|
||||
static Shard *merge_leaves(Shard *a, Shard *b);
|
||||
static Shard *append(Shard *root, Shard *leaf);
|
||||
static Shard *build(Shard **pieces, uint64_t lo, uint64_t hi);
|
||||
static void dump(Shard *node, int depth = 0);
|
||||
};
|
||||
|
||||
struct Branch : Shard {
|
||||
@@ -62,54 +63,4 @@ struct Petal : Shard {
|
||||
source(source), pos(pos) {};
|
||||
};
|
||||
|
||||
extern inline void dump_shard(Shard *node, int depth = 0) {
|
||||
if (!node) {
|
||||
std::cout << std::string(depth * 2, ' ') << "<null>\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string indent(depth * 2, ' ');
|
||||
|
||||
std::cout << indent
|
||||
<< "Shard@" << node
|
||||
<< " kind=";
|
||||
|
||||
switch (node->kind) {
|
||||
case Shard::Kind::Branch:
|
||||
std::cout << "Branch";
|
||||
break;
|
||||
case Shard::Kind::Petal:
|
||||
std::cout << "Petal";
|
||||
break;
|
||||
}
|
||||
|
||||
std::cout
|
||||
<< " height=" << unsigned(node->height)
|
||||
<< " length=" << node->length
|
||||
<< " lines=" << node->lines
|
||||
<< " refs=" << node->refs.load()
|
||||
<< "\n";
|
||||
|
||||
if (node->kind == Shard::Kind::Branch) {
|
||||
auto *branch = static_cast<Branch *>(node);
|
||||
|
||||
std::cout << indent << " left:\n";
|
||||
dump_shard(branch->left, depth + 2);
|
||||
|
||||
std::cout << indent << " right:\n";
|
||||
dump_shard(branch->right, depth + 2);
|
||||
} else {
|
||||
auto *petal = static_cast<Petal *>(node);
|
||||
|
||||
std::cout
|
||||
<< indent << " source=" << petal->source
|
||||
<< " pos=" << petal->pos
|
||||
<< " length=" << petal->length
|
||||
<< " lines=" << petal->lines
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (!depth)
|
||||
std::cout << "\n\n";
|
||||
}
|
||||
} // namespace crib::internal::vase
|
||||
|
||||
@@ -43,6 +43,15 @@ struct Vase {
|
||||
OriginalBuffer *original;
|
||||
AppendBuffer *append;
|
||||
Shard *root;
|
||||
|
||||
#ifdef _WIN32
|
||||
bool posix_ending = false;
|
||||
bool using_crlf = true;
|
||||
#else
|
||||
bool posix_ending = true;
|
||||
bool using_crlf = false;
|
||||
#endif
|
||||
|
||||
std::filesystem::path path;
|
||||
std::filesystem::path swapdir;
|
||||
|
||||
@@ -50,15 +59,18 @@ struct Vase {
|
||||
~Vase();
|
||||
|
||||
uint64_t length();
|
||||
uint64_t lines();
|
||||
std::string to_string();
|
||||
std::string to_string(Range range);
|
||||
|
||||
Iterator iterate(uint64_t line);
|
||||
Iterator iterate(uint64_t line, Direction dir);
|
||||
|
||||
void insert(Point *point, char key);
|
||||
void insert(Point *point, std::string_view str);
|
||||
void insert(Point *point, const char *data, uint64_t len);
|
||||
void erase(Point *point, uint64_t amount, Direction dir);
|
||||
void erase(Range range);
|
||||
void replace(Range range, std::string_view str);
|
||||
void replace(Range range, const char *data, uint64_t len);
|
||||
|
||||
void move_clusters(Point *point, uint64_t amount, Direction dir);
|
||||
|
||||
+30
-1
@@ -1,11 +1,40 @@
|
||||
#include "commands/ed/ed.h"
|
||||
#include "cli.h"
|
||||
|
||||
namespace crib::commands::ed {
|
||||
std::string summary() {
|
||||
return "A POSIX-compliant line editor.";
|
||||
}
|
||||
|
||||
void help() {}
|
||||
|
||||
void run(std::vector<std::string> args) {
|
||||
return;
|
||||
std::string prompt = "";
|
||||
std::filesystem::path filepath;
|
||||
bool suppress = false;
|
||||
for (size_t i = 1; i < args.size(); i++) {
|
||||
if (args[i] == "-p") {
|
||||
i++;
|
||||
if (i >= args.size())
|
||||
throw crib::cli::cli_error("Prompt not specified!", 1);
|
||||
prompt = args[i];
|
||||
} else if (args[i] == "-s") {
|
||||
suppress = true;
|
||||
} else {
|
||||
if (filepath.string().size())
|
||||
throw crib::cli::cli_error("Invalid arguments given.", 1);
|
||||
filepath = args[i];
|
||||
}
|
||||
}
|
||||
Ed ed(filepath, suppress, prompt);
|
||||
std::string command;
|
||||
while (true) {
|
||||
std::string cmd;
|
||||
if (ed.prompt_mode)
|
||||
std::cout << ed.prompt;
|
||||
bool eof = !std::getline(std::cin, cmd);
|
||||
if (!ed.handle(cmd, eof))
|
||||
return;
|
||||
}
|
||||
}
|
||||
} // namespace crib::commands::ed
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#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
|
||||
@@ -0,0 +1,254 @@
|
||||
#include "commands/ed/ed.h"
|
||||
|
||||
namespace crib::commands::ed {
|
||||
bool Ed::handle(std::string cmd, bool eof) {
|
||||
using namespace crib::internal::vase;
|
||||
try {
|
||||
if (line > vase.lines())
|
||||
line = vase.lines();
|
||||
Command command = parse(cmd, eof);
|
||||
bool was_quitting = quitting;
|
||||
quitting = false;
|
||||
switch (command.type) {
|
||||
case Command::Type::Quit:
|
||||
if (modified && !was_quitting) {
|
||||
quitting = true;
|
||||
throw ed_error("Buffer modified.");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case Command::Type::ForceQuit:
|
||||
return false;
|
||||
case Command::Type::None: {
|
||||
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);
|
||||
} else {
|
||||
line++;
|
||||
}
|
||||
if (line == 0)
|
||||
throw ed_error("Line 0 is invalid.");
|
||||
if (line > vase.lines())
|
||||
throw ed_error("Line position too high.");
|
||||
Iterator it = vase.iterate(line - 1, Direction::Backward);
|
||||
it.next();
|
||||
std::cout << it.line << std::endl;
|
||||
} break;
|
||||
case Command::Type::Invalid:
|
||||
throw ed_error("Invalid command.");
|
||||
case Command::Type::HelpToggle:
|
||||
help_mode = !help_mode;
|
||||
if (help_mode && last_message != "")
|
||||
std::cout << last_message << std::endl;
|
||||
break;
|
||||
case Command::Type::Help:
|
||||
if (last_message != "")
|
||||
std::cout << last_message << std::endl;
|
||||
break;
|
||||
case Command::Type::PromptToggle:
|
||||
prompt_mode = !prompt_mode;
|
||||
break;
|
||||
case Command::Type::Print: {
|
||||
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 << it.line << std::endl;
|
||||
line = line_end;
|
||||
} break;
|
||||
case Command::Type::Number: {
|
||||
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 << line_start++ << "\t" << it.line << std::endl;
|
||||
line = line_end;
|
||||
} break;
|
||||
case Command::Type::Append: {
|
||||
std::string text;
|
||||
std::string cline;
|
||||
uint64_t line_count = 0;
|
||||
while (std::getline(std::cin, cline)) {
|
||||
if (cline == ".")
|
||||
break;
|
||||
line_count++;
|
||||
text.append(cline);
|
||||
text.push_back('\n');
|
||||
}
|
||||
if (std::cin.eof())
|
||||
throw ed_error("EOF reached before '.' during text input.");
|
||||
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;
|
||||
break;
|
||||
}
|
||||
vase.snapshot();
|
||||
append(text, line);
|
||||
modified = true;
|
||||
line += line_count;
|
||||
} break;
|
||||
case Command::Type::Change: {
|
||||
uint64_t line_start = line;
|
||||
uint64_t line_end = line;
|
||||
if (command.start.type != Command::Address::Type::None) {
|
||||
resolve_address(command.start, &line_start);
|
||||
if (line_start == 0)
|
||||
line_start = 1;
|
||||
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.");
|
||||
std::string text;
|
||||
std::string cline;
|
||||
uint64_t line_count = 0;
|
||||
while (std::getline(std::cin, cline)) {
|
||||
if (cline == ".")
|
||||
break;
|
||||
line_count++;
|
||||
text.append(cline);
|
||||
text.push_back('\n');
|
||||
}
|
||||
if (std::cin.eof())
|
||||
throw ed_error("EOF reached before '.' during text input.");
|
||||
vase.snapshot();
|
||||
remove(line_start, line_end);
|
||||
line = line_start;
|
||||
if (text.empty())
|
||||
break;
|
||||
append(text, line);
|
||||
modified = true;
|
||||
line += line_count;
|
||||
} break;
|
||||
case Command::Type::Delete: {
|
||||
uint64_t line_start = line;
|
||||
uint64_t line_end = line;
|
||||
if (command.start.type != Command::Address::Type::None) {
|
||||
resolve_address(command.start, &line_start);
|
||||
if (line_start == 0)
|
||||
line_start = 1;
|
||||
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.");
|
||||
vase.snapshot();
|
||||
vase.erase({{line_start - 1, 0}, {line_end, 0}});
|
||||
modified = true;
|
||||
line = line_start;
|
||||
if (line > vase.lines())
|
||||
line = vase.lines();
|
||||
} break;
|
||||
case Command::Type::Write: {
|
||||
std::string path = command.argument;
|
||||
if (path.empty()) {
|
||||
path = vase.path;
|
||||
if (path.empty())
|
||||
throw ed_error("Need filename to write to.");
|
||||
}
|
||||
uint64_t line_start = 1;
|
||||
uint64_t line_end = vase.lines();
|
||||
if (vase.lines() == 0)
|
||||
line_start = line_end = 0;
|
||||
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 (vase.lines() && line_start == 0)
|
||||
throw ed_error("Line 0 is invalid.");
|
||||
if (line_end < line_start)
|
||||
throw ed_error("Invalid address range.");
|
||||
uint64_t bytes = 0;
|
||||
if (path[0] == '!') {
|
||||
FILE *pipe = popen(path.c_str() + 1, "w");
|
||||
if (!pipe)
|
||||
throw ed_error("Error starting command.");
|
||||
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
|
||||
while (it.next() && line_start++ <= line_end) {
|
||||
it.line += '\n';
|
||||
bytes += it.line.size();
|
||||
if (fputs(it.line.c_str(), pipe) == EOF) {
|
||||
pclose(pipe);
|
||||
throw ed_error("Error writing to command.");
|
||||
}
|
||||
}
|
||||
if (pclose(pipe) == -1)
|
||||
throw ed_error("Error closing command.");
|
||||
} else {
|
||||
vase.path = path;
|
||||
std::ofstream file(path, std::ios::out | std::ios::trunc);
|
||||
if (!file)
|
||||
throw ed_error("Error writing to file.");
|
||||
Iterator it = vase.iterate(line_start - 1, Direction::Forward);
|
||||
while (it.next() && line_start++ <= line_end) {
|
||||
file << it.line << '\n';
|
||||
bytes += it.line.size() + 1;
|
||||
}
|
||||
if (!file)
|
||||
throw ed_error("Error writing to file.");
|
||||
modified = false;
|
||||
}
|
||||
if (!suppress_mode)
|
||||
std::cout << bytes << std::endl;
|
||||
} break;
|
||||
case Command::Type::Dump:
|
||||
Shard::dump(vase.root);
|
||||
break;
|
||||
}
|
||||
if (command.suffix) {
|
||||
Iterator it = vase.iterate(line - 1, Direction::Forward);
|
||||
it.next();
|
||||
switch (command.suffix) {
|
||||
case 'p':
|
||||
std::cout << it.line << std::endl;
|
||||
break;
|
||||
case 'n':
|
||||
std::cout << line << "\t" << it.line << std::endl;
|
||||
break;
|
||||
case 'l':
|
||||
// TODO: escaping.
|
||||
std::cout << it.line << std::endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (ed_error &e) {
|
||||
last_message = e.what();
|
||||
std::cout << "?" << std::endl;
|
||||
if (help_mode)
|
||||
std::cout << e.what() << std::endl;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace crib::commands::ed
|
||||
@@ -0,0 +1,178 @@
|
||||
#include "commands/ed/ed.h"
|
||||
|
||||
namespace crib::commands::ed {
|
||||
void Ed::parse_address(std::string_view cmd, uint64_t &i, Command::Address &addr) {
|
||||
auto skip_space = [&] {
|
||||
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
|
||||
++i;
|
||||
};
|
||||
skip_space();
|
||||
if (i >= cmd.size()) {
|
||||
addr.type = Command::Address::Type::None;
|
||||
return;
|
||||
}
|
||||
switch (cmd[i]) {
|
||||
case '.':
|
||||
addr.type = Command::Address::Type::Current;
|
||||
i++;
|
||||
break;
|
||||
case '$':
|
||||
addr.type = Command::Address::Type::Last;
|
||||
i++;
|
||||
break;
|
||||
case '\'':
|
||||
addr.type = Command::Address::Type::Mark;
|
||||
i++;
|
||||
if (i < cmd.size() && 'a' <= cmd[i] && cmd[i] <= 'z')
|
||||
i++;
|
||||
else
|
||||
throw ed_error("Invalid mark.");
|
||||
addr.mark = cmd[i];
|
||||
break;
|
||||
case '/': {
|
||||
addr.type = Command::Address::Type::SearchForward;
|
||||
i++;
|
||||
uint64_t start = i;
|
||||
while (true) {
|
||||
if (i >= cmd.size())
|
||||
break;
|
||||
if (cmd[i] == '/')
|
||||
break;
|
||||
else if (cmd[i] == '\\')
|
||||
i += 2;
|
||||
else if (cmd[i] == '[' && i + 1 < cmd.size() && cmd[i + 1] == '[')
|
||||
while (i < cmd.size() && !(cmd[i - 1] == ']' && cmd[i] == ']'))
|
||||
i++;
|
||||
else if (cmd[i] == '[')
|
||||
while (i < cmd.size() && cmd[i] != ']')
|
||||
i++;
|
||||
else
|
||||
i++;
|
||||
}
|
||||
addr.regex = cmd.substr(start, i - start);
|
||||
} break;
|
||||
case '?': {
|
||||
addr.type = Command::Address::Type::SearchBackward;
|
||||
i++;
|
||||
uint64_t start = i;
|
||||
while (true) {
|
||||
if (i >= cmd.size())
|
||||
break;
|
||||
if (cmd[i] == '?')
|
||||
break;
|
||||
else if (cmd[i] == '\\')
|
||||
i += 2;
|
||||
else if (cmd[i] == '[' && i + 1 < cmd.size() && cmd[i + 1] == '[')
|
||||
while (i < cmd.size() && !(cmd[i - 1] == ']' && cmd[i] == ']'))
|
||||
i++;
|
||||
else if (cmd[i] == '[')
|
||||
while (i < cmd.size() && cmd[i] != ']')
|
||||
i++;
|
||||
else
|
||||
i++;
|
||||
}
|
||||
addr.regex = cmd.substr(start, i - start);
|
||||
} break;
|
||||
case '+': {
|
||||
i++;
|
||||
skip_space();
|
||||
uint64_t start = i;
|
||||
int64_t num = 0;
|
||||
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
|
||||
num = num * 10 + (cmd[i] - '0');
|
||||
i++;
|
||||
}
|
||||
if (start == i)
|
||||
num = 1;
|
||||
addr.offset += num;
|
||||
} break;
|
||||
case '-': {
|
||||
i++;
|
||||
skip_space();
|
||||
uint64_t start = i;
|
||||
int64_t num = 0;
|
||||
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
|
||||
num = num * 10 + (cmd[i] - '0');
|
||||
i++;
|
||||
}
|
||||
if (start == i)
|
||||
num = 1;
|
||||
addr.offset -= num;
|
||||
} break;
|
||||
default: {
|
||||
if ('0' <= cmd[i] && cmd[i] <= '9') {
|
||||
int64_t num = 0;
|
||||
addr.type = Command::Address::Type::Number;
|
||||
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
|
||||
num = num * 10 + (cmd[i] - '0');
|
||||
i++;
|
||||
}
|
||||
addr.number = num;
|
||||
} else {
|
||||
addr.type = Command::Address::Type::None;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
skip_space();
|
||||
while (i < cmd.size() && (cmd[i] == '+' || cmd[i] == '-' || ('0' <= cmd[i] && cmd[i] <= '9'))) {
|
||||
bool positive = cmd[i] != '-';
|
||||
if (cmd[i] == '+' || cmd[i] == '-') {
|
||||
i++;
|
||||
skip_space();
|
||||
}
|
||||
uint64_t start = i;
|
||||
int64_t num = 0;
|
||||
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
|
||||
num = num * 10 + (cmd[i] - '0');
|
||||
i++;
|
||||
}
|
||||
if (start == i)
|
||||
num = 1;
|
||||
addr.offset += positive ? num : -num;
|
||||
skip_space();
|
||||
}
|
||||
}
|
||||
|
||||
void Ed::resolve_address(Command::Address addr, uint64_t *out_line) {
|
||||
switch (addr.type) {
|
||||
case Command::Address::Type::None:
|
||||
break;
|
||||
case Command::Address::Type::Current:
|
||||
*out_line = line;
|
||||
if (*out_line > vase.lines())
|
||||
*out_line = vase.lines();
|
||||
break;
|
||||
case Command::Address::Type::Number:
|
||||
*out_line = addr.number;
|
||||
break;
|
||||
case Command::Address::Type::Last:
|
||||
*out_line = vase.lines();
|
||||
break;
|
||||
case Command::Address::Type::Mark:
|
||||
*out_line = marks[addr.mark - 'a'];
|
||||
if (!*out_line)
|
||||
throw ed_error("Unset mark used.");
|
||||
break;
|
||||
case Command::Address::Type::SearchForward:
|
||||
if (addr.regex == "")
|
||||
addr.regex = last_regex;
|
||||
last_regex = addr.regex;
|
||||
// TODO: use vase.regex_search(regex, range, options);
|
||||
break;
|
||||
case Command::Address::Type::SearchBackward:
|
||||
if (addr.regex == "")
|
||||
addr.regex = last_regex;
|
||||
last_regex = addr.regex;
|
||||
// TODO
|
||||
break;
|
||||
}
|
||||
if ((int64_t)*out_line + addr.offset < 0)
|
||||
throw ed_error("Line position can't be negative.");
|
||||
else
|
||||
*out_line += addr.offset;
|
||||
if (*out_line > vase.lines())
|
||||
throw ed_error("Line position too high.");
|
||||
}
|
||||
} // namespace crib::commands::ed
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "commands/ed/ed.h"
|
||||
|
||||
namespace crib::commands::ed {
|
||||
Command Ed::parse(std::string cmd, bool eof) {
|
||||
Command command = {};
|
||||
if (eof)
|
||||
return (command.type = Command::Type::Quit, command);
|
||||
uint64_t i = 0;
|
||||
auto skip_space = [&] {
|
||||
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
|
||||
++i;
|
||||
};
|
||||
Command::Address first;
|
||||
Command::Address second;
|
||||
parse_address(cmd, i, first);
|
||||
bool have_range = false;
|
||||
while (i < cmd.size() && (cmd[i] == ',' || cmd[i] == ';')) {
|
||||
char sep = cmd[i++];
|
||||
if (sep == ';')
|
||||
resolve_address(first, &line);
|
||||
command.address_flags = sep == ';' ? Command::SEMICOLON : 0;
|
||||
if (have_range)
|
||||
first = second;
|
||||
parse_address(cmd, i, second);
|
||||
have_range = true;
|
||||
}
|
||||
if (have_range) {
|
||||
if (first.type != Command::Address::Type::None
|
||||
&& second.type == Command::Address::Type::None) {
|
||||
second = first;
|
||||
} else if (first.type == Command::Address::Type::None) {
|
||||
if (command.address_flags & Command::SEMICOLON) {
|
||||
first.type = Command::Address::Type::Current;
|
||||
} else {
|
||||
first.type = Command::Address::Type::Number;
|
||||
first.number = 1;
|
||||
}
|
||||
if (second.type == Command::Address::Type::None)
|
||||
second.type = Command::Address::Type::Last;
|
||||
}
|
||||
command.address_flags |= Command::RANGE;
|
||||
command.start = first;
|
||||
command.end = second;
|
||||
} else {
|
||||
command.start = first;
|
||||
}
|
||||
if (i >= cmd.size()) {
|
||||
command.type = Command::Type::None;
|
||||
return command;
|
||||
}
|
||||
switch (cmd[i]) {
|
||||
case 'q':
|
||||
command.type = Command::Type::Quit;
|
||||
return command;
|
||||
case 'Q':
|
||||
command.type = Command::Type::ForceQuit;
|
||||
return command;
|
||||
case 'p':
|
||||
command.type = Command::Type::Print;
|
||||
i++;
|
||||
break;
|
||||
case 'n':
|
||||
command.type = Command::Type::Number;
|
||||
i++;
|
||||
break;
|
||||
case 'P':
|
||||
command.type = Command::Type::PromptToggle;
|
||||
i++;
|
||||
break;
|
||||
case 'H':
|
||||
command.type = Command::Type::HelpToggle;
|
||||
i++;
|
||||
break;
|
||||
case 'h':
|
||||
command.type = Command::Type::Help;
|
||||
i++;
|
||||
break;
|
||||
case 'a':
|
||||
command.type = Command::Type::Append;
|
||||
i++;
|
||||
break;
|
||||
case 'c':
|
||||
command.type = Command::Type::Change;
|
||||
i++;
|
||||
break;
|
||||
case 'd':
|
||||
command.type = Command::Type::Delete;
|
||||
i++;
|
||||
break;
|
||||
case 'w':
|
||||
command.type = Command::Type::Write;
|
||||
i++;
|
||||
if (i >= cmd.size())
|
||||
return command;
|
||||
if (cmd[i] == ' ' || cmd[i] == '\t')
|
||||
skip_space();
|
||||
else
|
||||
throw ed_error("Invalid command.");
|
||||
command.argument = cmd.substr(i);
|
||||
return command;
|
||||
case '#':
|
||||
command.type = Command::Type::Dump;
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
if (i < cmd.size()) {
|
||||
if (cmd[i] == 'l' || cmd[i] == 'n' || cmd[i] == 'p')
|
||||
command.suffix = cmd[i++];
|
||||
skip_space();
|
||||
if (i != cmd.size())
|
||||
throw ed_error("Invalid command.");
|
||||
}
|
||||
return command;
|
||||
}
|
||||
} // namespace crib::commands::ed
|
||||
@@ -88,16 +88,24 @@ void PetalIterator::seek_line(uint64_t line) {
|
||||
} else {
|
||||
auto *b = (Branch *)curr;
|
||||
uint64_t left_lines = b->left->lines;
|
||||
if (line <= left_lines) {
|
||||
if (dir == Direction::Forward)
|
||||
if (dir == Direction::Forward) {
|
||||
if (line < left_lines) {
|
||||
stack.push_back(b->right);
|
||||
curr = b->left;
|
||||
curr = b->left;
|
||||
} else {
|
||||
line -= left_lines;
|
||||
global_offset += b->left->length;
|
||||
curr = b->right;
|
||||
}
|
||||
} else {
|
||||
line -= left_lines;
|
||||
if (dir == Direction::Backward)
|
||||
if (line <= left_lines) {
|
||||
curr = b->left;
|
||||
} else {
|
||||
line -= left_lines;
|
||||
stack.push_back(b->left);
|
||||
global_offset += b->left->length;
|
||||
curr = b->right;
|
||||
global_offset += b->left->length;
|
||||
curr = b->right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ Shard *balance(Shard *node) {
|
||||
|
||||
Shard *Shard::merge(Shard *a, Shard *b) {
|
||||
if (!a)
|
||||
return b ? (Shard::retain(b), b) : nullptr;
|
||||
return (Shard::retain(b), b);
|
||||
if (!b)
|
||||
return (Shard::retain(a), a);
|
||||
|
||||
@@ -205,7 +205,7 @@ Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
|
||||
return node;
|
||||
}
|
||||
|
||||
Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o) {
|
||||
Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o, bool posix_ending) {
|
||||
int dest_fd = o->fd;
|
||||
if (dest_fd == -1)
|
||||
return nullptr;
|
||||
@@ -214,6 +214,19 @@ Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o) {
|
||||
return nullptr;
|
||||
|
||||
uint64_t total = std::filesystem::file_size(path);
|
||||
if (posix_ending && total > 0) {
|
||||
char last;
|
||||
char s_last;
|
||||
if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1)
|
||||
return nullptr;
|
||||
if (pread(src_fd, &s_last, 1, (off_t)(total - 2)) != 1)
|
||||
return nullptr;
|
||||
if (last == '\n') {
|
||||
total--;
|
||||
if (s_last == '\r')
|
||||
total--;
|
||||
}
|
||||
}
|
||||
std::vector<Shard *> pieces;
|
||||
uint64_t pos = 0;
|
||||
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
|
||||
@@ -257,4 +270,54 @@ Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o) {
|
||||
return pieces[0];
|
||||
return build(pieces.data(), 0, pieces.size());
|
||||
}
|
||||
|
||||
void Shard::dump(Shard *node, int depth) {
|
||||
if (!node) {
|
||||
std::cout << std::string(depth * 2, ' ') << "<null>\n";
|
||||
return;
|
||||
}
|
||||
std::string indent(depth * 2, ' ');
|
||||
std::cout << indent
|
||||
<< "Shard@" << node
|
||||
<< " kind=";
|
||||
switch (node->kind) {
|
||||
case Shard::Kind::Branch:
|
||||
std::cout << "Branch";
|
||||
break;
|
||||
case Shard::Kind::Petal:
|
||||
std::cout << "Petal";
|
||||
break;
|
||||
}
|
||||
std::cout
|
||||
<< " height=" << unsigned(node->height)
|
||||
<< " length=" << node->length
|
||||
<< " lines=" << node->lines
|
||||
<< " refs=" << node->refs.load()
|
||||
<< "\n";
|
||||
if (node->kind == Shard::Kind::Branch) {
|
||||
auto *branch = (Branch *)node;
|
||||
std::cout << indent << " left:\n";
|
||||
dump(branch->left, depth + 2);
|
||||
std::cout << indent << " right:\n";
|
||||
dump(branch->right, depth + 2);
|
||||
} else {
|
||||
auto *petal = static_cast<Petal *>(node);
|
||||
constexpr auto clean = [](const std::string &text) {
|
||||
std::string result = text;
|
||||
size_t pos = 0;
|
||||
while ((pos = result.find('\n', pos)) != std::string::npos) {
|
||||
result.replace(pos, 1, "\\n");
|
||||
pos += 2;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
std::cout
|
||||
<< indent << " source=" << petal->source
|
||||
<< " pos=" << petal->pos
|
||||
<< " length=" << petal->length
|
||||
<< " lines=" << petal->lines
|
||||
<< " text=\"" << clean(std::string(petal->source->read(petal->pos), petal->length))
|
||||
<< "\"\n";
|
||||
}
|
||||
}
|
||||
} // namespace crib::internal::vase
|
||||
|
||||
@@ -8,12 +8,12 @@ Vase::Vase(std::filesystem::path path, std::filesystem::path swapdir)
|
||||
append = new AppendBuffer(swapdir);
|
||||
original = new OriginalBuffer(swapdir);
|
||||
if (std::filesystem::is_regular_file(path))
|
||||
root = Shard::from_file(path, original);
|
||||
root = Shard::from_file(path, original, posix_ending);
|
||||
else
|
||||
root = nullptr;
|
||||
history_top = 0;
|
||||
history.push_back(root);
|
||||
Shard::retain(root);
|
||||
history_top = 0;
|
||||
}
|
||||
|
||||
Vase::~Vase() {
|
||||
@@ -25,22 +25,38 @@ Vase::~Vase() {
|
||||
}
|
||||
|
||||
uint64_t Vase::length() {
|
||||
if (!root)
|
||||
return 0;
|
||||
return root->length;
|
||||
}
|
||||
|
||||
uint64_t Vase::lines() {
|
||||
if (!root)
|
||||
return 0;
|
||||
return root->lines + 1;
|
||||
}
|
||||
|
||||
std::string Vase::to_string() {
|
||||
std::string out;
|
||||
if (!root)
|
||||
return out;
|
||||
PetalIterator it(root, Direction::Forward);
|
||||
it.seek_offset(0);
|
||||
const char *data;
|
||||
uint64_t len;
|
||||
while (it.next(&data, &len))
|
||||
out.append(data, len);
|
||||
if (posix_ending)
|
||||
out.append("\n");
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string Vase::to_string(Range range) {
|
||||
clamp(&range.start);
|
||||
clamp(&range.end);
|
||||
std::string out;
|
||||
if (!root)
|
||||
return out;
|
||||
PetalIterator it(root, Direction::Forward);
|
||||
uint64_t start = offset_of(range.start);
|
||||
it.seek_offset(start);
|
||||
@@ -55,8 +71,8 @@ std::string Vase::to_string(Range range) {
|
||||
return out;
|
||||
}
|
||||
|
||||
Iterator Vase::iterate(uint64_t line) {
|
||||
return Iterator(root, line);
|
||||
Iterator Vase::iterate(uint64_t line, Direction dir) {
|
||||
return Iterator(root, line, dir);
|
||||
}
|
||||
|
||||
bool Vase::undo() {
|
||||
@@ -100,6 +116,8 @@ void Vase::prune_history(uint64_t n) {
|
||||
}
|
||||
|
||||
bool Vase::save() {
|
||||
if (!root)
|
||||
return true;
|
||||
std::ofstream file(path, std::ios::binary);
|
||||
if (!file)
|
||||
return false;
|
||||
@@ -112,6 +130,10 @@ bool Vase::save() {
|
||||
if (!file)
|
||||
return false;
|
||||
}
|
||||
if (posix_ending)
|
||||
file.write("\n", 1);
|
||||
if (!file)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -137,6 +159,10 @@ void Vase::insert(Point *point, char key) {
|
||||
point->col++;
|
||||
}
|
||||
|
||||
void Vase::insert(Point *point, std::string_view str) {
|
||||
insert(point, str.data(), str.size());
|
||||
}
|
||||
|
||||
void Vase::insert(Point *point, const char *data, uint64_t len) {
|
||||
while (len) {
|
||||
uint64_t chunk_size = std::min<uint64_t>(len, PETAL_SIZE_MAX);
|
||||
@@ -229,12 +255,17 @@ void Vase::erase(Range range) {
|
||||
root = new_root;
|
||||
}
|
||||
|
||||
void Vase::replace(Range range, std::string_view str) {
|
||||
replace(range, str.data(), str.size());
|
||||
}
|
||||
|
||||
void Vase::replace(Range range, const char *data, uint64_t len) {
|
||||
erase(range);
|
||||
insert(&range.start, data, len);
|
||||
}
|
||||
|
||||
uint64_t Vase::offset_of(Point point) {
|
||||
clamp(&point);
|
||||
LineIterator it(root, point.row, Direction::Forward);
|
||||
std::string line;
|
||||
uint64_t offset = 0;
|
||||
@@ -296,6 +327,7 @@ Point Vase::point_of(uint64_t offset) {
|
||||
ptr += next_len;
|
||||
p.col++;
|
||||
}
|
||||
clamp(&p);
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -363,11 +395,19 @@ void Vase::move_clusters(Point *point, uint64_t amount, Direction dir) {
|
||||
}
|
||||
}
|
||||
}
|
||||
clamp(point);
|
||||
}
|
||||
|
||||
void Vase::clamp(Point *point) {
|
||||
if (point->row > root->lines)
|
||||
if (!root) {
|
||||
point->row = 0;
|
||||
point->col = 0;
|
||||
return;
|
||||
}
|
||||
if (point->row > root->lines) {
|
||||
point->row = root->lines;
|
||||
point->col = UINT64_MAX;
|
||||
}
|
||||
LineIterator it(root, point->row, Direction::Forward);
|
||||
std::string line;
|
||||
uint64_t clusters = 0;
|
||||
|
||||
Reference in New Issue
Block a user