diff --git a/README.md b/README.md
index eee5fdc..b8547c6 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ done.
Ed is an ed (the posix line editor) implementation using `Vase`.
-Not done yet.
+Mostly done.
#### `hl`
diff --git a/include/commands/ed/ed.h b/include/commands/ed/ed.h
index d41b383..190b4b6 100644
--- a/include/commands/ed/ed.h
+++ b/include/commands/ed/ed.h
@@ -19,9 +19,13 @@ struct Command {
Print,
Number,
Append,
+ Insert,
Change,
Delete,
Write,
+ Edit,
+ ForceEdit,
+ Filename,
Dump,
None
} type;
@@ -56,22 +60,24 @@ struct Ed {
uint64_t line = 0;
bool modified = false;
bool quitting = false;
+ bool editing = 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::filesystem::path save_path = "";
std::string last_message = "";
- Ed(std::filesystem::path file, bool suppress_mode, std::string prompt_)
- : vase(file, "/tmp"), suppress_mode(suppress_mode), prompt(prompt_) {
+ Ed(std::string file_path, bool suppress_mode, std::string prompt_)
+ : vase("/tmp"), suppress_mode(suppress_mode), prompt(prompt_) {
if (prompt == "")
prompt = "*";
else
prompt_mode = true;
- line = vase.lines();
- std::cout << vase.length() << std::endl;
+ if (file_path != "")
+ handle("E " + file_path, false);
}
void parse_address(std::string_view cmd, uint64_t &i, Command::Address &addr);
diff --git a/include/internal/vase/shard.h b/include/internal/vase/shard.h
index d0a5991..81e31c8 100644
--- a/include/internal/vase/shard.h
+++ b/include/internal/vase/shard.h
@@ -25,8 +25,9 @@ struct Shard {
static void retain(Shard *n);
static void release(Shard *n);
- static Shard *from_file(std::filesystem::path path, OriginalBuffer *b, bool posix_ending);
- static std::vector from_swap(std::filesystem::path path, OriginalBuffer *b);
+ static Shard *from_file(std::filesystem::path &path, OriginalBuffer *b, bool posix_ending);
+ static Shard *from_command(const char *cmd, OriginalBuffer *o, bool posix_ending);
+ static std::vector from_swap(std::filesystem::path &path, OriginalBuffer *b);
static std::pair split(Shard *n, uint64_t offset);
static Shard *concat(Shard *left, Shard *right);
diff --git a/include/internal/vase/vase.h b/include/internal/vase/vase.h
index bbaf0d7..0d673f8 100644
--- a/include/internal/vase/vase.h
+++ b/include/internal/vase/vase.h
@@ -56,7 +56,13 @@ struct Vase {
std::filesystem::path swapdir;
Vase(std::filesystem::path path, std::filesystem::path swapdir);
+ Vase(std::string cmd, std::filesystem::path swapdir);
+ Vase(std::filesystem::path swapdir);
~Vase();
+ Vase(Vase &&other) noexcept;
+ Vase &operator=(Vase &&other) noexcept;
+ Vase(const Vase &) = delete;
+ Vase &operator=(const Vase &) = delete;
uint64_t length();
uint64_t lines();
diff --git a/src/commands/ed/ed.cc b/src/commands/ed/ed.cc
index d9919b0..7f05e4b 100644
--- a/src/commands/ed/ed.cc
+++ b/src/commands/ed/ed.cc
@@ -10,7 +10,7 @@ void help() {}
void run(std::vector args) {
std::string prompt = "";
- std::filesystem::path filepath;
+ std::string file;
bool suppress = false;
for (size_t i = 1; i < args.size(); i++) {
if (args[i] == "-p") {
@@ -21,12 +21,12 @@ void run(std::vector args) {
} else if (args[i] == "-s") {
suppress = true;
} else {
- if (filepath.string().size())
+ if (file.size())
throw crib::cli::cli_error("Invalid arguments given.", 1);
- filepath = args[i];
+ file = args[i];
}
}
- Ed ed(filepath, suppress, prompt);
+ Ed ed(file, suppress, prompt);
std::string command;
while (true) {
std::string cmd;
diff --git a/src/commands/ed/handle.cc b/src/commands/ed/handle.cc
index 5e9c1a1..9e3c163 100644
--- a/src/commands/ed/handle.cc
+++ b/src/commands/ed/handle.cc
@@ -9,6 +9,8 @@ bool Ed::handle(std::string cmd, bool eof) {
Command command = parse(cmd, eof);
bool was_quitting = quitting;
quitting = false;
+ bool was_editing = editing;
+ editing = false;
switch (command.type) {
case Command::Type::Quit:
if (modified && !was_quitting) {
@@ -97,8 +99,6 @@ bool Ed::handle(std::string cmd, bool eof) {
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);
@@ -115,6 +115,32 @@ bool Ed::handle(std::string cmd, bool eof) {
modified = true;
line += line_count;
} break;
+ case Command::Type::Insert: {
+ 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 (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();
+ append(text, line - 1);
+ modified = true;
+ line += line_count - 1;
+ } break;
case Command::Type::Change: {
uint64_t line_start = line;
uint64_t line_end = line;
@@ -138,8 +164,6 @@ bool Ed::handle(std::string cmd, bool eof) {
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;
@@ -172,9 +196,9 @@ bool Ed::handle(std::string cmd, bool eof) {
case Command::Type::Write: {
std::string path = command.argument;
if (path.empty()) {
- path = vase.path;
+ path = save_path;
if (path.empty())
- throw ed_error("Need filename to write to.");
+ throw ed_error("Need file to write to.");
}
uint64_t line_start = 1;
uint64_t line_end = vase.lines();
@@ -207,7 +231,6 @@ bool Ed::handle(std::string cmd, bool eof) {
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.");
@@ -218,11 +241,68 @@ bool Ed::handle(std::string cmd, bool eof) {
}
if (!file)
throw ed_error("Error writing to file.");
+ save_path = path;
modified = false;
}
if (!suppress_mode)
std::cout << bytes << std::endl;
} break;
+ case Command::Type::Edit: {
+ if (modified && !was_editing) {
+ editing = true;
+ throw ed_error("Buffer modified.");
+ }
+ std::string path = command.argument;
+ if (path.empty()) {
+ path = save_path;
+ if (path.empty())
+ throw ed_error("Need file to read from.");
+ }
+ if (path[0] == '!') {
+ path.erase(1);
+ Vase new_vase(std::string(path), "/tmp");
+ vase = std::move(new_vase);
+ modified = true;
+ } else {
+ Vase new_vase(std::filesystem::path(path), "/tmp");
+ vase = std::move(new_vase);
+ save_path = path;
+ modified = false;
+ }
+ line = vase.lines();
+ if (!suppress_mode)
+ std::cout << vase.length() << std::endl;
+ } break;
+ case Command::Type::ForceEdit: {
+ std::string path = command.argument;
+ if (path.empty()) {
+ path = save_path;
+ if (path.empty())
+ throw ed_error("Need file to read from.");
+ }
+ if (path[0] == '!') {
+ path.erase(0, 1);
+ Vase new_vase(std::string(path), "/tmp");
+ vase = std::move(new_vase);
+ modified = true;
+ } else {
+ Vase new_vase(std::filesystem::path(path), "/tmp");
+ vase = std::move(new_vase);
+ save_path = path;
+ modified = false;
+ }
+ line = vase.lines();
+ std::fill(std::begin(marks), std::end(marks), '\0');
+ if (!suppress_mode)
+ std::cout << vase.length() << std::endl;
+ } break;
+ case Command::Type::Filename: {
+ std::string path = command.argument;
+ if (path.empty())
+ throw ed_error("No filename given.");
+ save_path = path;
+ std::cout << save_path.string() << std::endl;
+ } break;
case Command::Type::Dump:
Shard::dump(vase.root);
break;
diff --git a/src/commands/ed/parsers/command.cc b/src/commands/ed/parsers/command.cc
index 4063ba4..dd8d503 100644
--- a/src/commands/ed/parsers/command.cc
+++ b/src/commands/ed/parsers/command.cc
@@ -79,6 +79,10 @@ Command Ed::parse(std::string cmd, bool eof) {
command.type = Command::Type::Append;
i++;
break;
+ case 'i':
+ command.type = Command::Type::Insert;
+ i++;
+ break;
case 'c':
command.type = Command::Type::Change;
i++;
@@ -98,6 +102,39 @@ Command Ed::parse(std::string cmd, bool eof) {
throw ed_error("Invalid command.");
command.argument = cmd.substr(i);
return command;
+ case 'e':
+ command.type = Command::Type::Edit;
+ 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 'E':
+ command.type = Command::Type::ForceEdit;
+ 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 'f':
+ command.type = Command::Type::Filename;
+ 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++;
diff --git a/src/internal/vase/shard.cc b/src/internal/vase/shard.cc
index 65abb2c..0132774 100644
--- a/src/internal/vase/shard.cc
+++ b/src/internal/vase/shard.cc
@@ -205,7 +205,112 @@ Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
return node;
}
-Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o, bool posix_ending) {
+static bool write_all(int fd, const void *data, size_t len) {
+ const char *p = static_cast(data);
+ while (len > 0) {
+ ssize_t n = write(fd, p, len);
+ if (n > 0) {
+ p += n;
+ len -= (size_t)n;
+ continue;
+ }
+ if (n == -1 && errno == EINTR)
+ continue;
+ return false;
+ }
+ return true;
+}
+
+Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending) {
+ int dest_fd = o->fd;
+ if (dest_fd == -1)
+ return nullptr;
+
+ FILE *pipe = popen(cmd, "r");
+ if (!pipe)
+ return nullptr;
+
+ std::vector pieces;
+ pieces.reserve(16);
+
+ uint64_t pos = 0;
+ char buf[PETAL_SIZE_MAX];
+ uint64_t buf_cursor = 0;
+
+ char ending[2] = {'\0', '\0'};
+
+ while (true) {
+ size_t got = fread(buf + buf_cursor, 1, sizeof(buf) - buf_cursor, pipe);
+ buf_cursor += got;
+
+ if (buf_cursor == PETAL_SIZE_MAX || feof(pipe)) {
+ if (buf_cursor == 0)
+ break;
+ uint64_t lines = 0;
+ const char *p = buf;
+ const char *end = p + buf_cursor;
+ while (p < end) {
+ const void *nl = memchr(p, '\n', end - p);
+ if (!nl)
+ break;
+ lines++;
+ p = (const char *)nl + 1;
+ }
+ if (buf_cursor >= 2) {
+ ending[0] = buf[buf_cursor - 2];
+ ending[1] = buf[buf_cursor - 1];
+ } else if (buf_cursor == 1) {
+ ending[0] = ending[1];
+ ending[1] = buf[0];
+ }
+ if (!write_all(dest_fd, buf, buf_cursor)) {
+ pclose(pipe);
+ return nullptr;
+ }
+ pieces.push_back(new Petal(buf_cursor, lines, o, pos));
+ pos += buf_cursor;
+ }
+ if (feof(pipe))
+ break;
+ if (ferror(pipe))
+ return nullptr;
+ }
+
+ int status = pclose(pipe);
+ if (status == -1)
+ return nullptr;
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ return nullptr;
+
+ if (pieces.empty())
+ return nullptr;
+
+ if (posix_ending) {
+ if (ending[1] == '\n') {
+ Petal *last = (Petal *)pieces.back();
+ last->lines--;
+ last->length--;
+ if (last->length == 0) {
+ Shard::release(last);
+ pieces.pop_back();
+ last = nullptr;
+ if (!pieces.empty())
+ last = (Petal *)pieces.back();
+ }
+ if (last && ending[0] == '\r')
+ last->length--;
+ }
+ }
+
+ o->initialize();
+
+ if (pieces.size() == 1)
+ return pieces[0];
+
+ return build(pieces.data(), 0, pieces.size());
+}
+
+Shard *Shard::from_file(std::filesystem::path &path, OriginalBuffer *o, bool posix_ending) {
int dest_fd = o->fd;
if (dest_fd == -1)
return nullptr;
@@ -216,17 +321,22 @@ Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o, bool posi
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--;
+ if (total > 0) {
+ char s_last;
+ if (pread(src_fd, &s_last, 1, (off_t)(total - 1)) != 1)
+ return nullptr;
+ if (s_last == '\r')
+ total--;
+ }
}
}
+ if (total == 0)
+ return nullptr;
+
std::vector pieces;
uint64_t pos = 0;
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
@@ -250,8 +360,7 @@ Shard *Shard::from_file(std::filesystem::path path, OriginalBuffer *o, bool posi
lines++;
p = (const char *)nl + 1;
}
- ssize_t written = write(dest_fd, buf, take);
- if (written != (ssize_t)take) {
+ if (!write_all(dest_fd, buf, take)) {
close(src_fd);
return nullptr;
}
diff --git a/src/internal/vase/vase.cc b/src/internal/vase/vase.cc
index 86d923a..0a9ea3b 100644
--- a/src/internal/vase/vase.cc
+++ b/src/internal/vase/vase.cc
@@ -16,12 +16,78 @@ Vase::Vase(std::filesystem::path path, std::filesystem::path swapdir)
Shard::retain(root);
}
+Vase::Vase(std::string cmd, std::filesystem::path swapdir)
+ : path(""), swapdir(swapdir) {
+ if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir))
+ throw std::runtime_error("Swap directory does not exist or is not a directory.");
+ append = new AppendBuffer(swapdir);
+ original = new OriginalBuffer(swapdir);
+ root = Shard::from_command(cmd.c_str(), original, posix_ending);
+ history_top = 0;
+ history.push_back(root);
+ Shard::retain(root);
+}
+
+Vase::Vase(std::filesystem::path swapdir)
+ : path(""), swapdir(swapdir) {
+ if (!std::filesystem::exists(swapdir) || !std::filesystem::is_directory(swapdir))
+ throw std::runtime_error("Swap directory does not exist or is not a directory.");
+ append = new AppendBuffer(swapdir);
+ original = new OriginalBuffer(swapdir);
+ root = nullptr;
+ history_top = 0;
+ history.push_back(root);
+ Shard::retain(root);
+}
+
Vase::~Vase() {
Shard::release(root);
+ for (auto s : history)
+ Shard::release(s);
+ if (original)
+ delete original;
+ if (append)
+ delete append;
+}
+
+Vase::Vase(Vase &&other) noexcept
+ : original(other.original),
+ append(other.append),
+ root(other.root),
+ posix_ending(other.posix_ending),
+ using_crlf(other.using_crlf),
+ path(std::move(other.path)),
+ swapdir(std::move(other.swapdir)),
+ history(std::move(other.history)),
+ history_top(other.history_top) {
+ other.original = nullptr;
+ other.append = nullptr;
+ other.root = nullptr;
+ other.history_top = 0;
+}
+
+Vase &Vase::operator=(Vase &&other) noexcept {
+ if (this == &other)
+ return *this;
+ Shard::release(root);
for (auto s : history)
Shard::release(s);
delete original;
delete append;
+ original = other.original;
+ append = other.append;
+ root = other.root;
+ posix_ending = other.posix_ending;
+ using_crlf = other.using_crlf;
+ path = std::move(other.path);
+ swapdir = std::move(other.swapdir);
+ history = std::move(other.history);
+ history_top = other.history_top;
+ other.original = nullptr;
+ other.append = nullptr;
+ other.root = nullptr;
+ other.history_top = 0;
+ return *this;
}
uint64_t Vase::length() {
@@ -108,16 +174,21 @@ void Vase::snapshot() {
}
void Vase::prune_history(uint64_t n) {
- n = std::min(n, history_top);
- for (uint64_t i = 0; i < n; ++i)
+ uint64_t keep = std::min(history.size(), n + 1);
+ if (keep == history.size())
+ return;
+ uint64_t remove = history.size() - keep;
+ for (uint64_t i = 0; i < remove; ++i)
Shard::release(history[i]);
- history.erase(history.begin(), history.begin() + n);
- history_top -= n;
+ history.erase(history.begin(), history.begin() + remove);
+ history_top -= remove;
}
bool Vase::save() {
if (!root)
return true;
+ if (path == "")
+ return false;
std::ofstream file(path, std::ios::binary);
if (!file)
return false;