Compare commits
24
Commits
1f3b53753a
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0f9c6a234
|
||
|
|
4b0f3b8fb7
|
||
|
|
24914028c0
|
||
|
|
0d126021d0
|
||
|
|
e44267a6fe
|
||
|
|
a996beb4aa
|
||
|
|
010f916d35
|
||
|
|
81bb65b7cf
|
||
|
|
49bf5a2e2d
|
||
|
|
41049b1ab6
|
||
|
|
4a87a1a834
|
||
|
|
44e58b3041
|
||
|
|
7a18aa0db2
|
||
|
|
87d9148b82
|
||
|
|
e19a8e6bba
|
||
|
|
afa60dd12f
|
||
|
|
b8eac7b2da
|
||
|
|
d7278251ec
|
||
|
|
c8a270378a
|
||
|
|
6f1c87400a
|
||
|
|
f4e3a190b8
|
||
|
|
dba279b036
|
||
|
|
2ac71085db
|
||
|
|
3563324a1c
|
+28
-9
@@ -2,35 +2,54 @@
|
|||||||
|
|
||||||
#include "definitions.h"
|
#include "definitions.h"
|
||||||
#include "internal/buffer/buffer.h"
|
#include "internal/buffer/buffer.h"
|
||||||
#include "internal/commands/commands.h"
|
#include "internal/functions/functions.h"
|
||||||
#include "internal/commands/suffixes.h"
|
#include "internal/functions/suffixes.h"
|
||||||
|
#include "internal/io/io.h"
|
||||||
#include "internal/marks/marks.h"
|
#include "internal/marks/marks.h"
|
||||||
#include "internal/theme/theme.h"
|
#include "internal/theme/theme.h"
|
||||||
|
#include "internal/ui/command.h"
|
||||||
|
#include "internal/ui/text_mode.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed {
|
namespace bed {
|
||||||
struct BEd {
|
struct BEd {
|
||||||
internal::trie::Trie<internal::commands::Command> commands;
|
internal::trie::Trie<internal::functions::Function> functions;
|
||||||
internal::commands::Command no_op;
|
internal::functions::Function no_op;
|
||||||
internal::commands::Command eof_op;
|
internal::functions::Function eof_op;
|
||||||
std::array<std::optional<internal::commands::Suffix>, 26> suffixes;
|
std::array<std::optional<internal::functions::Suffix>, 26> suffixes;
|
||||||
internal::theme::Theme theme;
|
internal::theme::Theme theme;
|
||||||
std::unordered_map<std::string, internal::syntax::Language> languages;
|
std::unordered_map<std::string, internal::syntax::Language> languages;
|
||||||
|
internal::io::IO &io;
|
||||||
|
internal::vase::AppendStorage append{"/tmp"};
|
||||||
|
|
||||||
bool help_mode = false;
|
bool help_mode = false;
|
||||||
std::string last_help = "";
|
|
||||||
bool prompt_mode = true;
|
bool prompt_mode = true;
|
||||||
std::function<std::string(BEd &)> prompt = nullptr;
|
std::function<std::string(BEd &)> prompt = nullptr;
|
||||||
bool suppress_mode = false;
|
bool suppress_mode = false;
|
||||||
|
bool temporary_current = false;
|
||||||
|
std::string last_help = "";
|
||||||
std::string last_regex = "";
|
std::string last_regex = "";
|
||||||
|
std::string last_symbol = "";
|
||||||
|
std::string last_replacement = "";
|
||||||
|
std::string last_shell = "";
|
||||||
|
|
||||||
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
|
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
|
||||||
internal::buffer::Buffer *active;
|
|
||||||
|
|
||||||
BEd(std::vector<std::string> args);
|
internal::buffer::Range prev_1;
|
||||||
|
internal::buffer::Range prev_2;
|
||||||
|
internal::marks::MarksEngine marks;
|
||||||
|
|
||||||
|
BEd(std::vector<std::string> args, internal::io::IO &io);
|
||||||
~BEd();
|
~BEd();
|
||||||
|
|
||||||
|
internal::buffer::Buffer &buffer(const std::string &);
|
||||||
|
internal::buffer::Line ¤t();
|
||||||
|
internal::buffer::Range &prev();
|
||||||
|
void mark(uint8_t, internal::buffer::Line);
|
||||||
|
|
||||||
void handle(std::string_view cmd, bool eof);
|
void handle(std::string_view cmd, bool eof);
|
||||||
void run();
|
void run();
|
||||||
void suffix_handle(char s);
|
void suffix_handle(char s);
|
||||||
|
bool escape_command(std::string &cmd, std::string_view filename);
|
||||||
};
|
};
|
||||||
} // namespace bed
|
} // namespace bed
|
||||||
|
|||||||
+14
-1
@@ -4,6 +4,8 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed {
|
namespace bed {
|
||||||
|
struct BEd;
|
||||||
|
|
||||||
struct fatal_error : std::runtime_error {
|
struct fatal_error : std::runtime_error {
|
||||||
uint8_t code;
|
uint8_t code;
|
||||||
fatal_error(std::string msg, uint8_t code)
|
fatal_error(std::string msg, uint8_t code)
|
||||||
@@ -14,5 +16,16 @@ struct ed_error : std::runtime_error {
|
|||||||
ed_error(std::string msg) : std::runtime_error(msg) {}
|
ed_error(std::string msg) : std::runtime_error(msg) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BEd;
|
struct Highlight {
|
||||||
|
enum : uint8_t {
|
||||||
|
None = 0,
|
||||||
|
Bold = 1 << 0,
|
||||||
|
Italic = 1 << 1,
|
||||||
|
Strikethrough = 1 << 2,
|
||||||
|
Underline = 1 << 3,
|
||||||
|
};
|
||||||
|
uint32_t fg;
|
||||||
|
uint32_t bg;
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
} // namespace bed
|
} // namespace bed
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "definitions.h"
|
|
||||||
#include "internal/generic.h"
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
namespace bed::internal::address {
|
|
||||||
struct address_error : ed_error {
|
|
||||||
address_error(const char *msg) : ed_error(msg) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Address {
|
|
||||||
// % refers to range of whatever was resulted from the previous modification.
|
|
||||||
// it expands in theory to a Num,Num and so can be followed by chaining more adresses the ed way.
|
|
||||||
// this is handled and resolved by the handle function,
|
|
||||||
// the constuctor and resolve etc. are also only called in the handle function,
|
|
||||||
//
|
|
||||||
// in case of any issue an instance of address_error(const char *) is thrown.
|
|
||||||
|
|
||||||
struct Result {
|
|
||||||
std::array<uint64_t, 2> data{};
|
|
||||||
uint8_t size{0};
|
|
||||||
std::span<const uint64_t> span(uint8_t max = UINT8_MAX) const {
|
|
||||||
return {data.data(), std::min(size, max)};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct None {};
|
|
||||||
|
|
||||||
// Posix ed types of addressing.
|
|
||||||
struct Current {}; // .
|
|
||||||
|
|
||||||
struct Last {}; // $
|
|
||||||
|
|
||||||
struct Number { // n
|
|
||||||
uint64_t i;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Mark { // 'm
|
|
||||||
char m;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Regex { // /re/ or ?re?
|
|
||||||
internal::Direction dir;
|
|
||||||
std::string re;
|
|
||||||
};
|
|
||||||
|
|
||||||
// extended types
|
|
||||||
struct Diagnostic { // #n# for diagnostic number n. (From lsp.)
|
|
||||||
uint16_t n;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DiagnosticNext { // ^ or % for previous/next diagnostic
|
|
||||||
internal::Direction dir;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SymbolDefinition { // <sym> goto symbol definition. (From lsp or using internal language parsers)
|
|
||||||
std::string sym;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SymbolReference { // <sym:n> goto nth symbol reference
|
|
||||||
uint16_t n;
|
|
||||||
std::string sym;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SymbolReferenceNext { // >sym> or <sym<
|
|
||||||
internal::Direction dir; // goto next or previous symbol reference
|
|
||||||
std::string sym;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Block { // [ or ] goto start/end of containing block.
|
|
||||||
internal::Direction dir;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Scripted { // (function_name:arguments)
|
|
||||||
std::string func; // Calls ruby mapping with name giving the argument as string.
|
|
||||||
std::string arg; // resolves to line number returned by function (or throws error).
|
|
||||||
// The mruby runtime also has the full context of the file and extentions etc.
|
|
||||||
};
|
|
||||||
|
|
||||||
std::variant<
|
|
||||||
std::monostate,
|
|
||||||
None,
|
|
||||||
Current,
|
|
||||||
Last,
|
|
||||||
Number,
|
|
||||||
Mark,
|
|
||||||
Regex,
|
|
||||||
Diagnostic,
|
|
||||||
DiagnosticNext,
|
|
||||||
SymbolDefinition,
|
|
||||||
SymbolReference,
|
|
||||||
SymbolReferenceNext,
|
|
||||||
Block,
|
|
||||||
Scripted>
|
|
||||||
base{};
|
|
||||||
|
|
||||||
// they can then be followed by any number of +n or -n etc accumulating in.
|
|
||||||
int64_t offset = 0;
|
|
||||||
|
|
||||||
Address(std::string &cmd, uint64_t &i);
|
|
||||||
Address() = default;
|
|
||||||
|
|
||||||
uint64_t resolve(BEd &ctx);
|
|
||||||
|
|
||||||
static Result handle(BEd &ctx, std::string &cmd, uint64_t &i);
|
|
||||||
};
|
|
||||||
} // namespace bed::internal::address
|
|
||||||
@@ -1,40 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "definitions.h"
|
#include "clip.h"
|
||||||
#include "internal/marks/marks.h"
|
#include "decl.h"
|
||||||
#include "internal/syntax/parser.h"
|
#include "generic.h"
|
||||||
#include "internal/syntax/ruby/parser.h"
|
|
||||||
#include "internal/theme/theme.h"
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::buffer {
|
|
||||||
struct Buffer {
|
|
||||||
vase::Vase vase;
|
|
||||||
marks::MarksEngine marks;
|
|
||||||
std::string language;
|
|
||||||
std::optional<syntax::Parser> parser;
|
|
||||||
uint64_t line = 0;
|
|
||||||
bool modified;
|
|
||||||
std::filesystem::path save_path = "";
|
|
||||||
struct {
|
|
||||||
uint64_t start{0};
|
|
||||||
uint64_t end{0};
|
|
||||||
} prev_range;
|
|
||||||
|
|
||||||
Buffer();
|
|
||||||
Buffer(std::string command);
|
|
||||||
Buffer(std::filesystem::path path);
|
|
||||||
|
|
||||||
~Buffer() = default;
|
|
||||||
|
|
||||||
void load(std::string command);
|
|
||||||
void load(std::filesystem::path path);
|
|
||||||
void jump(uint64_t n_line);
|
|
||||||
void join(uint64_t start_line, uint64_t end_line);
|
|
||||||
void remove(uint64_t start_line, uint64_t end_line);
|
|
||||||
void append(std::string text, uint64_t line);
|
|
||||||
void print(BEd &ctx, uint64_t start_line, uint64_t end_line);
|
|
||||||
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line);
|
|
||||||
std::string list_string(std::string_view s);
|
|
||||||
};
|
|
||||||
} // namespace bed::internal::buffer
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "decl.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
struct ClipBuffer : Buffer {
|
||||||
|
ClipBuffer(std::string name)
|
||||||
|
: Buffer(name, Kind::Clip) {};
|
||||||
|
~ClipBuffer();
|
||||||
|
|
||||||
|
void clip_write(vase::Shard *text);
|
||||||
|
bool waste() override;
|
||||||
|
uint64_t lines() override;
|
||||||
|
uint64_t bytes() override;
|
||||||
|
void load(BEd &ctx, vase::Shard *text) override;
|
||||||
|
void set_filename(std::filesystem::path path) override;
|
||||||
|
std::filesystem::path filename() override;
|
||||||
|
vase::Shard *copy(uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void substitute(
|
||||||
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
|
std::string ®ex, std::string &replacement, std::string &options
|
||||||
|
) override;
|
||||||
|
void join(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
|
||||||
|
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
uint64_t next_closing(uint64_t start) override;
|
||||||
|
uint64_t prev_closing(uint64_t start) override;
|
||||||
|
uint64_t find_next(std::string_view pattern, uint64_t start) override;
|
||||||
|
uint64_t find_prev(std::string_view pattern, uint64_t start) override;
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "internal/syntax/parser.h"
|
||||||
|
#include "internal/syntax/ruby/parser.h"
|
||||||
|
#include "internal/theme/theme.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
struct Buffer {
|
||||||
|
enum struct Kind : uint8_t {
|
||||||
|
Generic,
|
||||||
|
Null,
|
||||||
|
Clip,
|
||||||
|
Cancel,
|
||||||
|
Shell,
|
||||||
|
} kind;
|
||||||
|
|
||||||
|
enum : uint8_t {
|
||||||
|
Special,
|
||||||
|
Unmodified,
|
||||||
|
Modified,
|
||||||
|
Warned
|
||||||
|
} state;
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
explicit Buffer(std::string name, Kind kind)
|
||||||
|
: kind(kind), state(Unmodified), name(name) {};
|
||||||
|
virtual ~Buffer() = default;
|
||||||
|
|
||||||
|
virtual bool waste() = 0;
|
||||||
|
virtual uint64_t lines() = 0;
|
||||||
|
virtual uint64_t bytes() = 0;
|
||||||
|
virtual void set_filename(std::filesystem::path path) = 0;
|
||||||
|
virtual std::filesystem::path filename() = 0;
|
||||||
|
virtual void load(BEd &ctx, vase::Shard *text) = 0;
|
||||||
|
virtual vase::Shard *copy(uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
virtual void substitute(
|
||||||
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
|
std::string ®ex, std::string &replacement, std::string &options
|
||||||
|
) = 0;
|
||||||
|
virtual void join(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
virtual void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
virtual void append(BEd &ctx, vase::Shard *text, uint64_t line) = 0;
|
||||||
|
virtual void print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
virtual void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
virtual void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) = 0;
|
||||||
|
virtual uint64_t find_next(std::string_view pattern, uint64_t start) = 0;
|
||||||
|
virtual uint64_t find_prev(std::string_view pattern, uint64_t start) = 0;
|
||||||
|
virtual uint64_t next_closing(uint64_t start) = 0;
|
||||||
|
virtual uint64_t prev_closing(uint64_t start) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Line {
|
||||||
|
std::string buffername;
|
||||||
|
uint64_t number;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Range {
|
||||||
|
std::string buffername;
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t end;
|
||||||
|
|
||||||
|
explicit Range(const Line &a, const Line &b) {
|
||||||
|
if (a.buffername != b.buffername)
|
||||||
|
throw ed_error("Invalid range.");
|
||||||
|
if (a.number > b.number)
|
||||||
|
throw ed_error("Invalid range.");
|
||||||
|
buffername = a.buffername;
|
||||||
|
start = a.number;
|
||||||
|
end = b.number;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit Range(std::string buffername, uint64_t start, uint64_t end)
|
||||||
|
: buffername(buffername), start(start), end(end) {
|
||||||
|
if (start > end)
|
||||||
|
throw ed_error("Invalid range.");
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit Range() : buffername("default"), start(0), end(0) {};
|
||||||
|
};
|
||||||
|
|
||||||
|
using Address = std::variant<
|
||||||
|
std::string,
|
||||||
|
buffer::Range,
|
||||||
|
buffer::Line>;
|
||||||
|
|
||||||
|
inline static std::string 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 bed::internal::buffer
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "decl.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
struct GenericBuffer : Buffer {
|
||||||
|
vase::Shard *root;
|
||||||
|
std::filesystem::path save_path{};
|
||||||
|
std::string language{};
|
||||||
|
std::optional<syntax::Parser> parser{};
|
||||||
|
|
||||||
|
GenericBuffer(std::string name)
|
||||||
|
: Buffer(name, Kind::Generic), root(nullptr) {};
|
||||||
|
~GenericBuffer();
|
||||||
|
|
||||||
|
bool waste() override;
|
||||||
|
uint64_t lines() override;
|
||||||
|
uint64_t bytes() override;
|
||||||
|
void load(BEd &ctx, vase::Shard *text) override;
|
||||||
|
void set_filename(std::filesystem::path path) override;
|
||||||
|
std::filesystem::path filename() override;
|
||||||
|
vase::Shard *copy(uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void substitute(
|
||||||
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
|
std::string ®ex, std::string &replacement, std::string &options
|
||||||
|
) override;
|
||||||
|
void join(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void remove(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void append(BEd &ctx, vase::Shard *text, uint64_t line) override;
|
||||||
|
void print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
void list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) override;
|
||||||
|
uint64_t next_closing(uint64_t start) override;
|
||||||
|
uint64_t prev_closing(uint64_t start) override;
|
||||||
|
uint64_t find_next(std::string_view pattern, uint64_t start) override;
|
||||||
|
uint64_t find_prev(std::string_view pattern, uint64_t start) override;
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "definitions.h"
|
|
||||||
#include "internal/trie/trie.h"
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
namespace bed::internal::commands {
|
|
||||||
struct Command {
|
|
||||||
enum struct AddressMode : uint8_t {
|
|
||||||
None,
|
|
||||||
Single,
|
|
||||||
Range
|
|
||||||
} address_mode;
|
|
||||||
|
|
||||||
enum struct SuffixKind : uint8_t {
|
|
||||||
None,
|
|
||||||
Suffix,
|
|
||||||
Argument,
|
|
||||||
Continuation
|
|
||||||
} suffix;
|
|
||||||
|
|
||||||
std::string desc;
|
|
||||||
|
|
||||||
bool accept_zero;
|
|
||||||
|
|
||||||
void (*handle)(BEd &, std::span<const uint64_t>, std::string_view);
|
|
||||||
|
|
||||||
static void register_posix(BEd &ctx);
|
|
||||||
};
|
|
||||||
} // namespace bed::internal::commands
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "definitions.h"
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
namespace bed::internal::commands {
|
|
||||||
struct Suffix {
|
|
||||||
std::string desc;
|
|
||||||
void (*handle)(BEd &);
|
|
||||||
|
|
||||||
static void register_suffixes(BEd &ctx);
|
|
||||||
};
|
|
||||||
} // namespace bed::internal::commands
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "internal/buffer/buffer.h"
|
||||||
|
#include "internal/trie/trie.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::functions {
|
||||||
|
struct Function {
|
||||||
|
struct GlobalArg {
|
||||||
|
char delim;
|
||||||
|
std::string str;
|
||||||
|
};
|
||||||
|
struct RegexArg {
|
||||||
|
std::string expression;
|
||||||
|
std::string replacement;
|
||||||
|
std::string options;
|
||||||
|
};
|
||||||
|
struct ShellArg {
|
||||||
|
std::string cmd;
|
||||||
|
};
|
||||||
|
struct RubyArg {
|
||||||
|
std::string cmd;
|
||||||
|
};
|
||||||
|
using Argument = std::variant<
|
||||||
|
std::monostate,
|
||||||
|
GlobalArg,
|
||||||
|
RegexArg,
|
||||||
|
ShellArg,
|
||||||
|
RubyArg,
|
||||||
|
std::string,
|
||||||
|
std::filesystem::path,
|
||||||
|
int64_t,
|
||||||
|
char,
|
||||||
|
buffer::Range,
|
||||||
|
buffer::Line>;
|
||||||
|
|
||||||
|
enum struct AddressKind {
|
||||||
|
None,
|
||||||
|
Line,
|
||||||
|
Range
|
||||||
|
} address_kind;
|
||||||
|
|
||||||
|
enum struct ArgumentKind {
|
||||||
|
None,
|
||||||
|
Regex,
|
||||||
|
Shell,
|
||||||
|
Ruby,
|
||||||
|
Any,
|
||||||
|
File,
|
||||||
|
Number,
|
||||||
|
Mark,
|
||||||
|
Range,
|
||||||
|
Line,
|
||||||
|
Global
|
||||||
|
} argument_kind;
|
||||||
|
|
||||||
|
enum struct InputMode {
|
||||||
|
None,
|
||||||
|
Text,
|
||||||
|
Interactive,
|
||||||
|
CommandList
|
||||||
|
} input_mode;
|
||||||
|
|
||||||
|
std::string desc;
|
||||||
|
std::string default_address;
|
||||||
|
bool accept_zero;
|
||||||
|
|
||||||
|
std::function<
|
||||||
|
std::tuple<vase::Shard *, syntax::Language *, void *>(
|
||||||
|
BEd &ctx, const buffer::Address &addr, const Argument &arg
|
||||||
|
)>
|
||||||
|
pre_text_mode;
|
||||||
|
|
||||||
|
std::function<
|
||||||
|
void(
|
||||||
|
BEd &ctx, const buffer::Address &addr,
|
||||||
|
vase::Shard *text, const Argument &arg,
|
||||||
|
std::vector<buffer::Line> *marked
|
||||||
|
)>
|
||||||
|
handle;
|
||||||
|
|
||||||
|
static void register_posix(BEd &ctx);
|
||||||
|
static void register_extented(BEd &ctx);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::functions
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "internal/buffer/buffer.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::functions {
|
||||||
|
struct Suffix {
|
||||||
|
std::string desc;
|
||||||
|
std::function<void(BEd &)> handle;
|
||||||
|
|
||||||
|
static void register_suffixes(BEd &ctx);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::functions
|
||||||
@@ -7,4 +7,20 @@ enum struct Direction : uint8_t {
|
|||||||
Forward,
|
Forward,
|
||||||
Backward
|
Backward
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline static bool write_all(int fd, const void *data, size_t len) {
|
||||||
|
const char *p = (const char *)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;
|
||||||
}
|
}
|
||||||
|
} // namespace bed::internal
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::io {
|
||||||
|
struct KeyEvent {
|
||||||
|
enum struct ReadResult {
|
||||||
|
SUCCESS,
|
||||||
|
EOF_,
|
||||||
|
RESIZE
|
||||||
|
};
|
||||||
|
enum struct KeyType {
|
||||||
|
EOF_,
|
||||||
|
CHAR,
|
||||||
|
SPECIAL,
|
||||||
|
MOUSE,
|
||||||
|
PASTE,
|
||||||
|
RESIZE
|
||||||
|
};
|
||||||
|
enum struct SpecialKey {
|
||||||
|
UP,
|
||||||
|
DOWN,
|
||||||
|
LEFT,
|
||||||
|
RIGHT,
|
||||||
|
DELETE,
|
||||||
|
UNKNOWN
|
||||||
|
};
|
||||||
|
enum struct Modifier {
|
||||||
|
NONE,
|
||||||
|
SHIFT,
|
||||||
|
ALT,
|
||||||
|
CTRL,
|
||||||
|
CTRL_ALT
|
||||||
|
};
|
||||||
|
enum struct MouseState {
|
||||||
|
PRESS,
|
||||||
|
RELEASE
|
||||||
|
};
|
||||||
|
|
||||||
|
KeyType type = KeyType::EOF_;
|
||||||
|
std::string text;
|
||||||
|
|
||||||
|
SpecialKey special_key = SpecialKey::UNKNOWN;
|
||||||
|
Modifier modifier = Modifier::NONE;
|
||||||
|
|
||||||
|
MouseState mouse_state = MouseState::PRESS;
|
||||||
|
uint16_t mouse_x = 0;
|
||||||
|
uint16_t mouse_y = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IO {
|
||||||
|
static termios orig_termios;
|
||||||
|
static termios raw_termios;
|
||||||
|
static bool cleaned;
|
||||||
|
static void cleanup();
|
||||||
|
static void enable_raw();
|
||||||
|
static volatile std::atomic_bool resized;
|
||||||
|
static void handle_sigwinch(int);
|
||||||
|
|
||||||
|
IO();
|
||||||
|
~IO();
|
||||||
|
|
||||||
|
IO(const IO &) = delete;
|
||||||
|
IO &operator=(const IO &) = delete;
|
||||||
|
|
||||||
|
void enable_mouse();
|
||||||
|
void disable_mouse();
|
||||||
|
|
||||||
|
std::pair<uint16_t, uint16_t> terminal_size();
|
||||||
|
std::pair<uint16_t, uint16_t> cursor_position();
|
||||||
|
void move_cursor(uint16_t row, uint16_t col);
|
||||||
|
|
||||||
|
KeyEvent read_key();
|
||||||
|
void write(const char *, uint64_t);
|
||||||
|
void write(std::string_view);
|
||||||
|
void write_line(std::string_view);
|
||||||
|
void run_pty(const std::string &);
|
||||||
|
|
||||||
|
std::deque<char> input_queue;
|
||||||
|
|
||||||
|
KeyEvent::ReadResult get_next_byte(char &out);
|
||||||
|
void enqueue_bytes(const std::string &bytes);
|
||||||
|
static int utf8_seq_len(uint8_t byte);
|
||||||
|
|
||||||
|
KeyEvent::ReadResult read_next_unit(std::string &out);
|
||||||
|
KeyEvent::ReadResult read_bracketed_paste(std::string &out);
|
||||||
|
|
||||||
|
KeyEvent parse_mouse(const std::string &buf);
|
||||||
|
KeyEvent parse_escape(const std::string &buf);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::io
|
||||||
@@ -1,55 +1,58 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "internal/buffer/buffer.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::marks {
|
namespace bed::internal::marks {
|
||||||
struct MarksEngine {
|
struct MarksEngine {
|
||||||
uint64_t marks[1 << UINT8_WIDTH]{UINT64_MAX};
|
buffer::Line marks[256];
|
||||||
|
|
||||||
MarksEngine() {
|
MarksEngine() {
|
||||||
for (auto &m : marks)
|
for (auto &m : marks)
|
||||||
m = UINT64_MAX;
|
m = {"", UINT64_MAX};
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t get(uint8_t m) {
|
buffer::Line &get(uint8_t m) {
|
||||||
return marks[m];
|
return marks[m];
|
||||||
}
|
}
|
||||||
|
|
||||||
void set(uint8_t m, uint64_t line) {
|
void insert(std::string bufname, uint64_t start, uint64_t count) {
|
||||||
marks[m] = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
void insert(uint64_t start, uint64_t count) {
|
|
||||||
for (uint16_t i = 0; i <= UINT8_MAX; ++i) {
|
for (uint16_t i = 0; i <= UINT8_MAX; ++i) {
|
||||||
if (marks[i] == UINT64_MAX)
|
if (marks[i].buffername != bufname)
|
||||||
continue;
|
continue;
|
||||||
if (marks[i] >= start)
|
if (marks[i].number == UINT64_MAX)
|
||||||
marks[i] += count;
|
continue;
|
||||||
|
if (marks[i].number >= start)
|
||||||
|
marks[i].number += count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void erase(uint64_t start, uint64_t count) {
|
void erase(std::string bufname, uint64_t start, uint64_t count) {
|
||||||
for (uint16_t i = 0; i <= UINT8_MAX; ++i) {
|
for (uint16_t i = 0; i <= UINT8_MAX; ++i) {
|
||||||
if (marks[i] == UINT64_MAX)
|
if (marks[i].buffername != bufname)
|
||||||
continue;
|
continue;
|
||||||
if (marks[i] >= start) {
|
if (marks[i].number == UINT64_MAX)
|
||||||
if (marks[i] < start + count)
|
continue;
|
||||||
marks[i] = UINT64_MAX;
|
if (marks[i].number >= start) {
|
||||||
|
if (marks[i].number < start + count)
|
||||||
|
marks[i].number = UINT64_MAX;
|
||||||
else
|
else
|
||||||
marks[i] -= count;
|
marks[i].number -= count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void collapse(uint64_t start, uint64_t count) {
|
void collapse(std::string bufname, uint64_t start, uint64_t count) {
|
||||||
for (uint16_t i = 0; i <= UINT8_MAX; ++i) {
|
for (uint16_t i = 0; i <= UINT8_MAX; ++i) {
|
||||||
if (marks[i] == UINT64_MAX)
|
if (marks[i].buffername != bufname)
|
||||||
continue;
|
continue;
|
||||||
if (marks[i] >= start) {
|
if (marks[i].number == UINT64_MAX)
|
||||||
if (marks[i] < start + count)
|
continue;
|
||||||
marks[i] = start;
|
if (marks[i].number > start) {
|
||||||
|
if (marks[i].number <= start + count)
|
||||||
|
marks[i].number = start;
|
||||||
else
|
else
|
||||||
marks[i] -= count;
|
marks[i].number -= count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "internal/functions/functions.h"
|
||||||
|
#include "internal/functions/suffixes.h"
|
||||||
|
#include "internal/ui/command.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::parser {
|
||||||
|
struct AddressPromise {
|
||||||
|
struct None {}; //
|
||||||
|
struct Current {}; // .
|
||||||
|
struct Last {}; // $
|
||||||
|
struct Number { // n
|
||||||
|
uint64_t i;
|
||||||
|
};
|
||||||
|
struct Mark { // 'm
|
||||||
|
char m;
|
||||||
|
};
|
||||||
|
struct Regex { // /re/ or ?re?
|
||||||
|
internal::Direction dir;
|
||||||
|
std::string re;
|
||||||
|
};
|
||||||
|
struct Diagnostic { // ^ or ~ for previous/next diagnostic
|
||||||
|
internal::Direction dir;
|
||||||
|
};
|
||||||
|
struct SymbolDefinition { // <sym> goto symbol definition. (From lsp or using internal language parsers)
|
||||||
|
std::string sym;
|
||||||
|
};
|
||||||
|
struct SymbolReference { // >sym> or <sym<
|
||||||
|
internal::Direction dir; // goto next or previous symbol reference
|
||||||
|
std::string sym;
|
||||||
|
};
|
||||||
|
struct Block { // [ or ] goto start/end of containing block.
|
||||||
|
internal::Direction dir;
|
||||||
|
};
|
||||||
|
struct Scripted { // {function_name:arguments}
|
||||||
|
std::string func; // Calls ruby mapping with name giving the argument as string.
|
||||||
|
std::string arg; // resolves to line number returned by function (or throws error).
|
||||||
|
// The mruby runtime also has the full context of the file and extentions etc.
|
||||||
|
};
|
||||||
|
struct LastRange {}; // % , refers to last used range
|
||||||
|
|
||||||
|
std::optional<std::string> bufname;
|
||||||
|
std::variant<
|
||||||
|
None,
|
||||||
|
Current,
|
||||||
|
Last,
|
||||||
|
Number,
|
||||||
|
Mark,
|
||||||
|
Regex,
|
||||||
|
Diagnostic,
|
||||||
|
SymbolDefinition,
|
||||||
|
SymbolReference,
|
||||||
|
Block,
|
||||||
|
Scripted,
|
||||||
|
LastRange>
|
||||||
|
base{};
|
||||||
|
int64_t offset = 0;
|
||||||
|
bool jumping{false}; // ; == jumping and , == non jumping
|
||||||
|
buffer::Line resolve(BEd &ctx);
|
||||||
|
static std::optional<buffer::Line> get_line(BEd &ctx, std::vector<AddressPromise> &);
|
||||||
|
static std::optional<buffer::Range> get_range(BEd &ctx, std::vector<AddressPromise> &);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Command {
|
||||||
|
bool temp_address{false};
|
||||||
|
std::vector<AddressPromise> addresses{};
|
||||||
|
functions::Function *function{nullptr};
|
||||||
|
functions::Function::Argument argument{std::monostate()};
|
||||||
|
std::vector<AddressPromise> argument_addresses{};
|
||||||
|
functions::Suffix *suffix{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CompletionContext {
|
||||||
|
enum {
|
||||||
|
Error,
|
||||||
|
Valid,
|
||||||
|
Incomplete
|
||||||
|
} error;
|
||||||
|
// TODO: store a state of typing context to think about possible completions.
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Parser {
|
||||||
|
BEd &bed;
|
||||||
|
std::string_view cmd;
|
||||||
|
uint16_t i;
|
||||||
|
Command *command;
|
||||||
|
std::vector<ui::Token> *tokens;
|
||||||
|
CompletionContext *completion;
|
||||||
|
|
||||||
|
explicit Parser(
|
||||||
|
std::string_view cmd, BEd &bed, Command *command,
|
||||||
|
std::vector<ui::Token> *tokens, CompletionContext *completion
|
||||||
|
);
|
||||||
|
|
||||||
|
char peek(uint16_t = 0); // == \0 if at eof.
|
||||||
|
std::string_view peek_str(uint16_t = UINT16_MAX);
|
||||||
|
void advance(uint16_t = 1);
|
||||||
|
|
||||||
|
void skip_ws();
|
||||||
|
|
||||||
|
void locator(AddressPromise &);
|
||||||
|
int64_t offset();
|
||||||
|
void address(AddressPromise &addr);
|
||||||
|
void addresses(std::vector<AddressPromise> &addresses);
|
||||||
|
|
||||||
|
void operation();
|
||||||
|
|
||||||
|
void parse();
|
||||||
|
|
||||||
|
static Command get_command(std::string_view, BEd &);
|
||||||
|
static std::vector<AddressPromise> get_addresses(std::string_view cmd, BEd &bed);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::parser
|
||||||
@@ -5,23 +5,31 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::syntax {
|
namespace bed::internal::syntax {
|
||||||
void dump_events(ParseState *node);
|
|
||||||
|
|
||||||
struct Parser {
|
struct Parser {
|
||||||
static constexpr uint64_t MAX_CHUNK = 512;
|
static constexpr uint64_t MAX_CHUNK = 512;
|
||||||
|
|
||||||
ParseState *root;
|
ParseState *root;
|
||||||
Language lang;
|
Language lang;
|
||||||
|
bool in_edit = false;
|
||||||
|
bool dirty = false;
|
||||||
|
uint64_t dirty_start = 0;
|
||||||
|
uint64_t dirty_end = 0;
|
||||||
|
|
||||||
Parser(vase::Vase &, uint64_t, Language);
|
Parser(vase::Shard *, uint64_t, Language);
|
||||||
~Parser();
|
~Parser();
|
||||||
Parser(const Parser &) = delete;
|
Parser(const Parser &) = delete;
|
||||||
Parser &operator=(const Parser &) = delete;
|
Parser &operator=(const Parser &) = delete;
|
||||||
|
|
||||||
void reset(vase::Vase &, uint64_t, Language);
|
void reset(vase::Shard *, uint64_t, Language);
|
||||||
void erase(vase::Vase &, uint64_t, uint64_t);
|
void erase(vase::Shard *, uint64_t, uint64_t);
|
||||||
void insert(vase::Vase &, uint64_t, uint64_t);
|
void insert(vase::Shard *, uint64_t, uint64_t);
|
||||||
void modify(vase::Vase &, uint64_t, uint64_t);
|
void modify(vase::Shard *, uint64_t, uint64_t);
|
||||||
|
|
||||||
|
void begin_edit();
|
||||||
|
void erase(uint64_t start, uint64_t count);
|
||||||
|
void insert(uint64_t start, uint64_t count);
|
||||||
|
void end_edit(vase::Shard *vase);
|
||||||
|
void mark_dirty(uint64_t start, uint64_t end);
|
||||||
|
|
||||||
uint64_t next_closing(uint64_t line);
|
uint64_t next_closing(uint64_t line);
|
||||||
uint64_t prev_opening(uint64_t line);
|
uint64_t prev_opening(uint64_t line);
|
||||||
@@ -36,7 +44,7 @@ struct Parser {
|
|||||||
uint64_t at;
|
uint64_t at;
|
||||||
std::vector<Token> tokens;
|
std::vector<Token> tokens;
|
||||||
std::vector<ParseEvent> events;
|
std::vector<ParseEvent> events;
|
||||||
Iterator(uint64_t, Parser *, vase::Vase &);
|
Iterator(uint64_t, Parser *, vase::Shard *);
|
||||||
~Iterator();
|
~Iterator();
|
||||||
Iterator(const Iterator &) = delete;
|
Iterator(const Iterator &) = delete;
|
||||||
Iterator &operator=(const Iterator &) = delete;
|
Iterator &operator=(const Iterator &) = delete;
|
||||||
@@ -44,6 +52,6 @@ struct Parser {
|
|||||||
Iterator &operator=(Iterator &&other);
|
Iterator &operator=(Iterator &&other);
|
||||||
void next();
|
void next();
|
||||||
};
|
};
|
||||||
std::optional<Iterator> get_hl(vase::Vase &, uint64_t);
|
std::optional<Iterator> get_hl(vase::Shard *, uint64_t);
|
||||||
};
|
};
|
||||||
} // namespace bed::internal::syntax
|
} // namespace bed::internal::syntax
|
||||||
|
|||||||
@@ -5,19 +5,6 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::theme {
|
namespace bed::internal::theme {
|
||||||
struct Highlight {
|
|
||||||
enum : uint8_t {
|
|
||||||
None = 0,
|
|
||||||
Bold = 1 << 0,
|
|
||||||
Italic = 1 << 1,
|
|
||||||
Strikethrough = 1 << 2,
|
|
||||||
Underline = 1 << 3,
|
|
||||||
};
|
|
||||||
uint32_t fg;
|
|
||||||
uint32_t bg;
|
|
||||||
uint8_t flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Theme {
|
struct Theme {
|
||||||
std::array<Highlight, internal::syntax::Token::Count> hl;
|
std::array<Highlight, internal::syntax::Token::Count> hl;
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ namespace bed::internal::trie {
|
|||||||
template <typename T = void>
|
template <typename T = void>
|
||||||
struct Trie {
|
struct Trie {
|
||||||
using V = std::conditional_t<std::is_void_v<T>, std::monostate, T>;
|
using V = std::conditional_t<std::is_void_v<T>, std::monostate, T>;
|
||||||
|
using SearchResult = std::conditional_t<
|
||||||
|
std::is_void_v<T>,
|
||||||
|
std::string,
|
||||||
|
std::pair<std::string, V &>>;
|
||||||
|
|
||||||
struct Node {
|
struct Node {
|
||||||
std::string edge;
|
std::string edge;
|
||||||
@@ -113,8 +117,8 @@ struct Trie {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> search(std::string_view prefix) {
|
std::vector<SearchResult> search(std::string_view prefix) {
|
||||||
std::vector<std::string> result;
|
std::vector<SearchResult> result;
|
||||||
Node *current = &root;
|
Node *current = &root;
|
||||||
std::string key;
|
std::string key;
|
||||||
uint64_t pos = 0;
|
uint64_t pos = 0;
|
||||||
@@ -143,12 +147,17 @@ struct Trie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void collect(
|
static void collect(
|
||||||
const Node &node,
|
Node &node,
|
||||||
std::string &key,
|
std::string &key,
|
||||||
std::vector<std::string> &result
|
std::vector<SearchResult> &result
|
||||||
) {
|
) {
|
||||||
if (node.value)
|
if (node.value) {
|
||||||
result.push_back(key);
|
if constexpr (std::is_void_v<T>) {
|
||||||
|
result.push_back(key);
|
||||||
|
} else {
|
||||||
|
result.emplace_back(key, *node.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (auto *child : node.children) {
|
for (auto *child : node.children) {
|
||||||
const auto old_size = key.size();
|
const auto old_size = key.size();
|
||||||
key += child->edge;
|
key += child->edge;
|
||||||
@@ -219,6 +228,44 @@ struct Trie {
|
|||||||
return *current->value;
|
return *current->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
V *get_ptr(std::string_view key) {
|
||||||
|
Node *current = &root;
|
||||||
|
uint64_t pos = 0;
|
||||||
|
while (pos < key.size()) {
|
||||||
|
Node *child = find_child(*current, key[pos]);
|
||||||
|
if (!child)
|
||||||
|
return nullptr;
|
||||||
|
const auto remaining = key.substr(pos);
|
||||||
|
const auto common = common_prefix(child->edge, remaining);
|
||||||
|
if (common != child->edge.size())
|
||||||
|
return nullptr;
|
||||||
|
pos += common;
|
||||||
|
current = child;
|
||||||
|
}
|
||||||
|
if (!current->value)
|
||||||
|
return nullptr;
|
||||||
|
return &*current->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const V *get_ptr(std::string_view key) const {
|
||||||
|
const Node *current = &root;
|
||||||
|
uint64_t pos = 0;
|
||||||
|
while (pos < key.size()) {
|
||||||
|
const Node *child = find_child(*current, key[pos]);
|
||||||
|
if (!child)
|
||||||
|
return nullptr;
|
||||||
|
const auto remaining = key.substr(pos);
|
||||||
|
const auto common = common_prefix(child->edge, remaining);
|
||||||
|
if (common != child->edge.size())
|
||||||
|
return nullptr;
|
||||||
|
pos += common;
|
||||||
|
current = child;
|
||||||
|
}
|
||||||
|
if (!current->value)
|
||||||
|
return nullptr;
|
||||||
|
return &*current->value;
|
||||||
|
}
|
||||||
|
|
||||||
bool equal_char(char a, char b) const {
|
bool equal_char(char a, char b) const {
|
||||||
if (case_sensitive)
|
if (case_sensitive)
|
||||||
return a == b;
|
return a == b;
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::ui {
|
||||||
|
struct Token {
|
||||||
|
enum struct Type : uint8_t {
|
||||||
|
TempCurrent, // @
|
||||||
|
AddressSeperator, // ; ,
|
||||||
|
Address, // . $ % [ ] ^ ~
|
||||||
|
Offset, // +N -N + -
|
||||||
|
AddressRegex, // /re/ ?re?
|
||||||
|
AddressSymbol, // >s> <s< <s>
|
||||||
|
Number, // 10
|
||||||
|
Mark, // 'm
|
||||||
|
RubyFunction, // (func:arg)
|
||||||
|
RubyArg,
|
||||||
|
Function,
|
||||||
|
Any,
|
||||||
|
Shell,
|
||||||
|
Ruby,
|
||||||
|
File,
|
||||||
|
Regex,
|
||||||
|
Replacement,
|
||||||
|
Suffix
|
||||||
|
} type;
|
||||||
|
uint16_t start;
|
||||||
|
uint16_t end;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CommandIO {
|
||||||
|
std::string cmd;
|
||||||
|
uint16_t cursor;
|
||||||
|
std::string prompt;
|
||||||
|
uint16_t start;
|
||||||
|
uint16_t height;
|
||||||
|
uint16_t term_height;
|
||||||
|
uint16_t term_width;
|
||||||
|
BEd &bed;
|
||||||
|
|
||||||
|
CommandIO(BEd &);
|
||||||
|
std::pair<std::string, bool> run();
|
||||||
|
void redraw();
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::ui
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::ui {
|
||||||
|
struct TextMode {
|
||||||
|
std::string cmd;
|
||||||
|
uint16_t cursor;
|
||||||
|
uint16_t start;
|
||||||
|
uint16_t height;
|
||||||
|
uint16_t term_height;
|
||||||
|
uint16_t term_width;
|
||||||
|
BEd &bed;
|
||||||
|
|
||||||
|
TextMode(BEd &);
|
||||||
|
std::pair<vase::Shard *, bool> run();
|
||||||
|
void grow(size_t required_height);
|
||||||
|
void redraw();
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::ui
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "buffer.h"
|
|
||||||
#include "pch.h"
|
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
|
||||||
struct OriginalBuffer : Buffer {
|
|
||||||
const char *buf;
|
|
||||||
uint64_t len;
|
|
||||||
int fd = -1;
|
|
||||||
|
|
||||||
OriginalBuffer(std::filesystem::path base_dir);
|
|
||||||
~OriginalBuffer();
|
|
||||||
|
|
||||||
void initialize();
|
|
||||||
const char *read(uint64_t pos) override;
|
|
||||||
uint64_t length() override;
|
|
||||||
};
|
|
||||||
} // namespace bed::internal::vase
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "buffer/buffer.h"
|
|
||||||
#include "buffer/original.h"
|
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
#include "storage/original.h"
|
||||||
|
#include "storage/storage.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct Shard {
|
struct Shard {
|
||||||
@@ -24,16 +24,15 @@ struct Shard {
|
|||||||
static void retain(Shard *n);
|
static void retain(Shard *n);
|
||||||
static void release(Shard *n);
|
static void release(Shard *n);
|
||||||
|
|
||||||
static Shard *from_file(std::filesystem::path &path, OriginalBuffer *b, bool posix_ending);
|
static Shard *from_file(const std::filesystem::path &path, bool posix_ending);
|
||||||
static Shard *from_command(const char *cmd, OriginalBuffer *o, bool posix_ending);
|
static Shard *from_string(const char *data, uint64_t len, bool posix_ending);
|
||||||
static std::vector<Shard *> from_swap(std::filesystem::path &path, OriginalBuffer *b);
|
static Shard *from_command(const char *cmd, bool posix_ending);
|
||||||
|
|
||||||
static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset);
|
static std::pair<Shard *, Shard *> split(Shard *n, uint64_t offset);
|
||||||
static Shard *concat(Shard *a, Shard *b);
|
static Shard *concat(Shard *a, Shard *b);
|
||||||
static Shard *merge_leaves(Shard *a, Shard *b);
|
static Shard *merge_leaves(Shard *a, Shard *b);
|
||||||
static Shard *append(Shard *root, Shard *leaf);
|
static Shard *append(Shard *root, Shard *leaf);
|
||||||
static Shard *build(Shard **pieces, uint64_t lo, uint64_t hi);
|
static Shard *build(Shard **pieces, uint64_t lo, uint64_t hi);
|
||||||
static void dump(Shard *node, int depth = 0);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Branch : Shard {
|
struct Branch : Shard {
|
||||||
@@ -54,12 +53,14 @@ struct Branch : Shard {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Petal : Shard {
|
struct Petal : Shard {
|
||||||
Buffer *source;
|
Storage *source;
|
||||||
uint64_t pos;
|
uint64_t pos;
|
||||||
|
|
||||||
Petal(uint64_t length, uint64_t lines, Buffer *source, uint64_t pos)
|
Petal(uint64_t length, uint64_t lines, Storage *source, uint64_t pos)
|
||||||
: Shard(Kind::Petal, length, lines, 1),
|
: Shard(Kind::Petal, length, lines, 1),
|
||||||
source(source), pos(pos) {};
|
source(source), pos(pos) {
|
||||||
|
source->retain();
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace bed::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../constants.h"
|
#include "../constants.h"
|
||||||
#include "buffer.h"
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
#include "storage.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct AppendBuffer : Buffer {
|
struct AppendStorage : Storage {
|
||||||
char *buf = nullptr;
|
char *buf = nullptr;
|
||||||
uint64_t allocated_capacity = 0;
|
uint64_t allocated_capacity = 0;
|
||||||
uint64_t current_size = 0;
|
uint64_t current_size = 0;
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
|
|
||||||
AppendBuffer(std::filesystem::path base_dir);
|
AppendStorage(std::filesystem::path base_dir);
|
||||||
~AppendBuffer();
|
~AppendStorage();
|
||||||
|
|
||||||
uint64_t append(const char c);
|
uint64_t append(const char c);
|
||||||
uint64_t append(const char *text, uint64_t len);
|
uint64_t append(const char *text, uint64_t len);
|
||||||
const char *read(uint64_t pos) override;
|
const char *read(uint64_t pos) override;
|
||||||
uint64_t length() override;
|
uint64_t length() override;
|
||||||
|
|
||||||
|
void retain() override {}
|
||||||
|
void release() override {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void grow(uint64_t len);
|
void grow(uint64_t len);
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "pch.h"
|
||||||
|
#include "storage.h"
|
||||||
|
|
||||||
|
namespace bed::internal::vase {
|
||||||
|
struct OriginalStorage : Storage {
|
||||||
|
std::atomic_uint64_t refs{0};
|
||||||
|
const char *buf = nullptr;
|
||||||
|
uint64_t len = 0;
|
||||||
|
int fd = -1;
|
||||||
|
|
||||||
|
OriginalStorage(std::filesystem::path base_dir);
|
||||||
|
~OriginalStorage();
|
||||||
|
|
||||||
|
void initialize();
|
||||||
|
const char *read(uint64_t pos) override;
|
||||||
|
uint64_t length() override;
|
||||||
|
|
||||||
|
void retain() override {
|
||||||
|
refs++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void release() override {
|
||||||
|
if (--refs > 0)
|
||||||
|
return;
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::vase
|
||||||
@@ -3,9 +3,11 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct Buffer {
|
struct Storage {
|
||||||
virtual const char *read(uint64_t pos) = 0;
|
virtual const char *read(uint64_t pos) = 0;
|
||||||
virtual uint64_t length() = 0;
|
virtual uint64_t length() = 0;
|
||||||
virtual ~Buffer() = default;
|
virtual ~Storage() = default;
|
||||||
|
virtual void retain() = 0;
|
||||||
|
virtual void release() = 0;
|
||||||
};
|
};
|
||||||
} // namespace bed::internal::vase
|
} // namespace bed::internal::vase
|
||||||
+60
-100
@@ -1,12 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "buffer/append.h"
|
|
||||||
#include "buffer/original.h"
|
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
#include "definitions.h"
|
#include "definitions.h"
|
||||||
#include "iterators/line.h"
|
#include "iterators/line.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
#include "shard.h"
|
#include "shard.h"
|
||||||
|
#include "storage/append.h"
|
||||||
|
#include "storage/original.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
namespace bed::internal::vase {
|
||||||
struct Point {
|
struct Point {
|
||||||
@@ -19,103 +19,63 @@ struct Range {
|
|||||||
Point end;
|
Point end;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Vase {
|
struct RegexGroup {
|
||||||
struct RegexGroup {
|
uint64_t start{UINT64_MAX};
|
||||||
uint64_t start{0};
|
uint64_t end{UINT64_MAX};
|
||||||
uint64_t end{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RegexMatch {
|
|
||||||
uint64_t start;
|
|
||||||
uint64_t end;
|
|
||||||
RegexGroup groups[9]{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ReplacePart {
|
|
||||||
enum struct PartType {
|
|
||||||
FullMatch,
|
|
||||||
CaptureGroup,
|
|
||||||
Constant
|
|
||||||
} type;
|
|
||||||
|
|
||||||
std::variant<uint8_t, Shard *> value;
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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();
|
|
||||||
std::string to_string();
|
|
||||||
std::string to_string(Range range);
|
|
||||||
|
|
||||||
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);
|
|
||||||
void move_lines(Point *point, uint64_t amount, Direction dir);
|
|
||||||
void clamp(Point *point);
|
|
||||||
|
|
||||||
void regex_search_replace(
|
|
||||||
std::string_view pattern, Range range,
|
|
||||||
std::string_view replace, std::string_view options
|
|
||||||
);
|
|
||||||
|
|
||||||
std::vector<Range> regex_search(
|
|
||||||
std::string_view pattern, Range range, std::string_view options
|
|
||||||
);
|
|
||||||
|
|
||||||
uint64_t find_next(std::string_view pattern, uint64_t start);
|
|
||||||
uint64_t find_prev(std::string_view pattern, uint64_t start);
|
|
||||||
|
|
||||||
bool undo();
|
|
||||||
bool redo();
|
|
||||||
void snapshot();
|
|
||||||
void prune_history(uint64_t n);
|
|
||||||
|
|
||||||
bool save();
|
|
||||||
bool save_swap();
|
|
||||||
|
|
||||||
uint64_t offset_of(Point point);
|
|
||||||
Point point_of(uint64_t offset);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<Shard *> history;
|
|
||||||
uint64_t history_top;
|
|
||||||
|
|
||||||
void _insert(Point *point, const char *data, uint64_t len);
|
|
||||||
|
|
||||||
std::vector<ReplacePart> parse_replace(std::string_view s);
|
|
||||||
std::vector<RegexMatch> _regex_search(
|
|
||||||
std::string_view pattern, Range range, std::string_view options
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RegexMatch {
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t end;
|
||||||
|
RegexGroup groups[9]{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReplacePart {
|
||||||
|
enum struct PartType {
|
||||||
|
FullMatch,
|
||||||
|
CaptureGroup,
|
||||||
|
Constant
|
||||||
|
} type;
|
||||||
|
std::variant<uint8_t, Shard *> value;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t offset_of(Shard *root, uint64_t line);
|
||||||
|
uint64_t offset_of(Shard *root, Point point);
|
||||||
|
Point point_of(Shard *root, uint64_t offset);
|
||||||
|
|
||||||
|
std::string to_string(Shard *root);
|
||||||
|
std::string to_string(Shard *root, Range range);
|
||||||
|
|
||||||
|
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line);
|
||||||
|
Shard *erase(Shard *root, uint64_t start, uint64_t end);
|
||||||
|
Shard *join(Shard *root, uint64_t start, uint64_t end);
|
||||||
|
Shard *copy(Shard *root, uint64_t start, uint64_t end);
|
||||||
|
void write_file(std::filesystem::path path, Shard *text);
|
||||||
|
void write_command(const char *cmd, Shard *text);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Shard *substitute(
|
||||||
|
AppendStorage *ap, Shard *root,
|
||||||
|
std::string_view pattern, uint64_t start, uint64_t end,
|
||||||
|
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 = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
// internal
|
||||||
|
|
||||||
|
void _insert(AppendStorage *ap, Shard **root, Point *point, const char *data, uint64_t len);
|
||||||
|
Shard *insert(AppendStorage *ap, Shard *root, Point *point, char key);
|
||||||
|
Shard *insert(AppendStorage *ap, Shard *root, Point *point, const char *data, uint64_t len);
|
||||||
|
|
||||||
|
Shard *erase(Shard *root, Range range);
|
||||||
|
Shard *replace(AppendStorage *ap, Shard *root, Range range, const char *data, uint64_t len);
|
||||||
|
|
||||||
|
std::vector<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s);
|
||||||
|
std::vector<RegexMatch> _regex_search(
|
||||||
|
Shard *root, std::string_view pattern, uint64_t start_offset, uint64_t end_offset, std::string_view options
|
||||||
|
);
|
||||||
} // namespace bed::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
+1
-1
@@ -29,12 +29,12 @@ extern "C" {
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
#include <pty.h>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
#include "bed.h"
|
|
||||||
#include "internal/address/address.h"
|
|
||||||
#include "internal/commands/commands.h"
|
|
||||||
|
|
||||||
namespace bed {
|
|
||||||
void BEd::handle(std::string_view cmd_, bool eof) {
|
|
||||||
using namespace internal::commands;
|
|
||||||
std::string cmd(cmd_);
|
|
||||||
if (eof) {
|
|
||||||
eof_op.handle(*this, {}, "");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint64_t i = 0;
|
|
||||||
auto skip_space = [&] {
|
|
||||||
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
|
|
||||||
++i;
|
|
||||||
};
|
|
||||||
skip_space();
|
|
||||||
auto addresses = internal::address::Address::handle(*this, cmd, i);
|
|
||||||
if (i >= cmd.size()) {
|
|
||||||
if (!no_op.accept_zero)
|
|
||||||
for (auto l : addresses.span())
|
|
||||||
if (l == 0)
|
|
||||||
throw ed_error("Invalid address given.");
|
|
||||||
switch (no_op.address_mode) {
|
|
||||||
case Command::AddressMode::None:
|
|
||||||
no_op.handle(*this, addresses.span(0), "");
|
|
||||||
break;
|
|
||||||
case Command::AddressMode::Single:
|
|
||||||
no_op.handle(*this, addresses.span(1), "");
|
|
||||||
break;
|
|
||||||
case Command::AddressMode::Range:
|
|
||||||
no_op.handle(*this, addresses.span(2), "");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint64_t len = commands.longest_match(cmd.substr(i));
|
|
||||||
if (len == 0)
|
|
||||||
throw ed_error("Command not found.");
|
|
||||||
std::optional<Command> command_opt = commands.get(cmd.substr(i, len));
|
|
||||||
i += len;
|
|
||||||
if (!command_opt)
|
|
||||||
throw ed_error("Command error.");
|
|
||||||
Command command = command_opt.value();
|
|
||||||
std::string argument;
|
|
||||||
Suffix *suffix = nullptr;
|
|
||||||
switch (command.suffix) {
|
|
||||||
case Command::SuffixKind::None:
|
|
||||||
skip_space();
|
|
||||||
if (i < cmd.size())
|
|
||||||
throw ed_error("Command error.");
|
|
||||||
break;
|
|
||||||
case Command::SuffixKind::Suffix:
|
|
||||||
skip_space();
|
|
||||||
if (i < cmd.size()) {
|
|
||||||
if (suffixes[cmd[i] - 'a'].has_value())
|
|
||||||
suffix = &(suffixes[cmd[i++] - 'a'].value());
|
|
||||||
skip_space();
|
|
||||||
if (i < cmd.size())
|
|
||||||
throw ed_error("Command error.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Command::SuffixKind::Argument:
|
|
||||||
if (i < cmd.size()) {
|
|
||||||
if (cmd[i] == ' ' || cmd[i] == '\t')
|
|
||||||
skip_space();
|
|
||||||
else
|
|
||||||
throw ed_error("Command Error.");
|
|
||||||
argument = cmd.substr(i);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Command::SuffixKind::Continuation:
|
|
||||||
argument = cmd.substr(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!command.accept_zero)
|
|
||||||
for (auto l : addresses.span())
|
|
||||||
if (l == 0)
|
|
||||||
throw ed_error("Invalid address given.");
|
|
||||||
switch (command.address_mode) {
|
|
||||||
case Command::AddressMode::None:
|
|
||||||
command.handle(*this, addresses.span(0), argument);
|
|
||||||
break;
|
|
||||||
case Command::AddressMode::Single:
|
|
||||||
command.handle(*this, addresses.span(1), argument);
|
|
||||||
break;
|
|
||||||
case Command::AddressMode::Range:
|
|
||||||
command.handle(*this, addresses.span(2), argument);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (suffix)
|
|
||||||
suffix->handle(*this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void BEd::suffix_handle(char s) {
|
|
||||||
if (suffixes[s - 'a'].has_value())
|
|
||||||
suffixes[s - 'a'].value().handle(*this);
|
|
||||||
}
|
|
||||||
} // namespace bed
|
|
||||||
+172
-13
@@ -1,10 +1,12 @@
|
|||||||
#include "bed.h"
|
#include "bed.h"
|
||||||
|
#include "internal/parser/parser.h"
|
||||||
|
|
||||||
namespace bed {
|
namespace bed {
|
||||||
BEd::BEd(std::vector<std::string> args)
|
BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
|
||||||
: theme(internal::theme::Theme::default_theme()) {
|
: theme(internal::theme::Theme::default_theme()), io(io) {
|
||||||
internal::commands::Command::register_posix(*this);
|
internal::functions::Function::register_posix(*this);
|
||||||
internal::commands::Suffix::register_suffixes(*this);
|
internal::functions::Function::register_extented(*this);
|
||||||
|
internal::functions::Suffix::register_suffixes(*this);
|
||||||
std::string prompt_ = "";
|
std::string prompt_ = "";
|
||||||
std::string file = "";
|
std::string file = "";
|
||||||
bool suppress = false;
|
bool suppress = false;
|
||||||
@@ -16,6 +18,8 @@ BEd::BEd(std::vector<std::string> args)
|
|||||||
prompt_ = args[i];
|
prompt_ = args[i];
|
||||||
} else if (args[i] == "-s") {
|
} else if (args[i] == "-s") {
|
||||||
suppress = true;
|
suppress = true;
|
||||||
|
} else if (args[i] == "-v" || args[i] == "--verbose") {
|
||||||
|
help_mode = true;
|
||||||
} else {
|
} else {
|
||||||
if (file.size())
|
if (file.size())
|
||||||
throw fatal_error("Invalid arguments given.", 1);
|
throw fatal_error("Invalid arguments given.", 1);
|
||||||
@@ -27,10 +31,17 @@ BEd::BEd(std::vector<std::string> args)
|
|||||||
else
|
else
|
||||||
prompt_mode = false;
|
prompt_mode = false;
|
||||||
suppress_mode = suppress;
|
suppress_mode = suppress;
|
||||||
active = new internal::buffer::Buffer();
|
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
|
||||||
buffers.emplace("0", active);
|
current() = {"default", 0};
|
||||||
if (file != "")
|
try {
|
||||||
handle("E " + file, false);
|
if (file != "")
|
||||||
|
handle(":default:E " + file, false);
|
||||||
|
} catch (ed_error &e) {
|
||||||
|
io.write_line("?");
|
||||||
|
if (help_mode)
|
||||||
|
io.write_line(e.what());
|
||||||
|
last_help = e.what();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BEd::~BEd() {
|
BEd::~BEd() {
|
||||||
@@ -40,15 +51,163 @@ BEd::~BEd() {
|
|||||||
|
|
||||||
void BEd::run() {
|
void BEd::run() {
|
||||||
while (true) {
|
while (true) {
|
||||||
std::string cmd;
|
internal::ui::CommandIO command(*this);
|
||||||
if (prompt_mode)
|
auto [cmd, eof] = command.run();
|
||||||
std::cout << prompt(*this);
|
|
||||||
bool eof = !std::getline(std::cin, cmd);
|
|
||||||
try {
|
try {
|
||||||
handle(cmd, eof);
|
handle(cmd, eof);
|
||||||
} catch (ed_error &e) {
|
} catch (ed_error &e) {
|
||||||
std::cout << e.what() << std::endl;
|
io.write_line("?");
|
||||||
|
if (help_mode)
|
||||||
|
io.write_line(e.what());
|
||||||
|
last_help = e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BEd::handle(std::string_view cmd, bool eof) {
|
||||||
|
if (eof) {
|
||||||
|
eof_op.handle(*this, "", nullptr, std::monostate(), nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this);
|
||||||
|
if (c.temp_address) {
|
||||||
|
marks.get(251) = marks.get(250);
|
||||||
|
prev_2 = prev_1;
|
||||||
|
temporary_current = true;
|
||||||
|
}
|
||||||
|
internal::buffer::Address address;
|
||||||
|
switch (c.function->address_kind) {
|
||||||
|
case internal::functions::Function::AddressKind::None: {
|
||||||
|
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
|
||||||
|
if (!a.has_value()) {
|
||||||
|
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||||
|
a = internal::parser::AddressPromise::get_line(*this, vec);
|
||||||
|
if (!a.has_value())
|
||||||
|
a = current();
|
||||||
|
}
|
||||||
|
address = a->buffername;
|
||||||
|
} break;
|
||||||
|
case internal::functions::Function::AddressKind::Line: {
|
||||||
|
auto a = internal::parser::AddressPromise::get_line(*this, c.addresses);
|
||||||
|
if (!a.has_value()) {
|
||||||
|
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||||
|
a = internal::parser::AddressPromise::get_line(*this, vec);
|
||||||
|
if (!a.has_value())
|
||||||
|
a = current();
|
||||||
|
}
|
||||||
|
address = *a;
|
||||||
|
} break;
|
||||||
|
case internal::functions::Function::AddressKind::Range: {
|
||||||
|
auto a = internal::parser::AddressPromise::get_range(*this, c.addresses);
|
||||||
|
if (!a.has_value()) {
|
||||||
|
auto vec = internal::parser::Parser::get_addresses(c.function->default_address, *this);
|
||||||
|
a = internal::parser::AddressPromise::get_range(*this, vec);
|
||||||
|
if (!a.has_value())
|
||||||
|
a = internal::buffer::Range(current(), current());
|
||||||
|
}
|
||||||
|
address = *a;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
if (std::holds_alternative<internal::buffer::Line>(c.argument)) {
|
||||||
|
if (c.argument_addresses.empty())
|
||||||
|
throw ed_error("Function needs address argument.");
|
||||||
|
auto a = internal::parser::AddressPromise::get_line(*this, c.argument_addresses);
|
||||||
|
if (a.has_value())
|
||||||
|
c.argument = *a;
|
||||||
|
else
|
||||||
|
c.argument = current();
|
||||||
|
} else if (std::holds_alternative<internal::buffer::Range>(c.argument)) {
|
||||||
|
if (c.argument_addresses.empty())
|
||||||
|
throw ed_error("Function needs address argument.");
|
||||||
|
auto a = internal::parser::AddressPromise::get_range(*this, c.argument_addresses);
|
||||||
|
if (a.has_value())
|
||||||
|
c.argument = *a;
|
||||||
|
else
|
||||||
|
c.argument = internal::buffer::Range(current(), current());
|
||||||
|
}
|
||||||
|
if (!c.function->accept_zero) {
|
||||||
|
if (std::holds_alternative<internal::buffer::Line>(address)) {
|
||||||
|
if (std::get<internal::buffer::Line>(address).number == 0)
|
||||||
|
throw ed_error("Line number can't be zero.");
|
||||||
|
} else if (std::holds_alternative<internal::buffer::Range>(address)) {
|
||||||
|
auto r = std::get<internal::buffer::Range>(address);
|
||||||
|
if (r.start == 0 || r.end == 0)
|
||||||
|
throw ed_error("Line number can't be zero.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal::vase::Shard *text = nullptr;
|
||||||
|
if (c.function->input_mode == internal::functions::Function::InputMode::Text) {
|
||||||
|
internal::ui::TextMode tm(*this);
|
||||||
|
auto [a, b] = tm.run();
|
||||||
|
if (!b)
|
||||||
|
text = a;
|
||||||
|
}
|
||||||
|
if (c.function->handle)
|
||||||
|
c.function->handle(*this, address, text, c.argument, nullptr);
|
||||||
|
if (c.suffix)
|
||||||
|
c.suffix->handle(*this);
|
||||||
|
if (c.temp_address)
|
||||||
|
temporary_current = false;
|
||||||
|
for (auto it = buffers.begin(); it != buffers.end();) {
|
||||||
|
internal::buffer::Buffer *buf = it->second;
|
||||||
|
if (buf->waste()) {
|
||||||
|
delete buf;
|
||||||
|
it = buffers.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal::buffer::Buffer &BEd::buffer(const std::string &name) {
|
||||||
|
if (name.empty())
|
||||||
|
throw ed_error("can't have empty buffer name");
|
||||||
|
auto it = buffers.find(name);
|
||||||
|
if (it != buffers.end())
|
||||||
|
return *it->second;
|
||||||
|
auto *buf = new internal::buffer::GenericBuffer(name);
|
||||||
|
buffers.emplace(name, buf);
|
||||||
|
return *buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal::buffer::Line &BEd::current() {
|
||||||
|
return marks.get(250 + temporary_current);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal::buffer::Range &BEd::prev() {
|
||||||
|
if (temporary_current)
|
||||||
|
return prev_2;
|
||||||
|
else
|
||||||
|
return prev_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BEd::mark(uint8_t m, internal::buffer::Line line) {
|
||||||
|
marks.get(m) = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BEd::escape_command(std::string &cmd, std::string_view filename) {
|
||||||
|
bool modified = false;
|
||||||
|
if (cmd == "!") {
|
||||||
|
cmd = last_shell;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
last_shell = cmd;
|
||||||
|
for (size_t i = 0; i < cmd.size();) {
|
||||||
|
if (cmd[i] == '\\') {
|
||||||
|
if (i + 1 >= cmd.size())
|
||||||
|
break;
|
||||||
|
cmd.erase(i++, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cmd[i] == '%') {
|
||||||
|
cmd.erase(i, 1);
|
||||||
|
cmd.insert(i, filename);
|
||||||
|
i += filename.size();
|
||||||
|
modified = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
} // namespace bed
|
} // namespace bed
|
||||||
|
|||||||
@@ -1,146 +0,0 @@
|
|||||||
#include "internal/address/address.h"
|
|
||||||
|
|
||||||
namespace bed::internal::address {
|
|
||||||
Address::Address(std::string &cmd, uint64_t &i) {
|
|
||||||
base = None{};
|
|
||||||
auto skip_space = [&] {
|
|
||||||
while (i < cmd.size() && (cmd[i] == ' ' || cmd[i] == '\t'))
|
|
||||||
++i;
|
|
||||||
};
|
|
||||||
skip_space();
|
|
||||||
if (i >= cmd.size())
|
|
||||||
return;
|
|
||||||
switch (cmd[i]) {
|
|
||||||
case '.':
|
|
||||||
base = Current();
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case '$':
|
|
||||||
base = Last();
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case ']':
|
|
||||||
base = Block(Direction::Forward);
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case '[':
|
|
||||||
base = Block(Direction::Backward);
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
case '\'': {
|
|
||||||
i++;
|
|
||||||
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 - 1]);
|
|
||||||
} break;
|
|
||||||
case '/': {
|
|
||||||
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++;
|
|
||||||
}
|
|
||||||
base = Regex(Direction::Forward, cmd.substr(start, i - start));
|
|
||||||
if (i < cmd.size())
|
|
||||||
i++;
|
|
||||||
} break;
|
|
||||||
case '?': {
|
|
||||||
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++;
|
|
||||||
}
|
|
||||||
base = Regex(Direction::Backward, cmd.substr(start, i - start));
|
|
||||||
if (i < cmd.size())
|
|
||||||
i++;
|
|
||||||
} break;
|
|
||||||
case '+': {
|
|
||||||
base = Current();
|
|
||||||
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;
|
|
||||||
offset += num;
|
|
||||||
} break;
|
|
||||||
case '-': {
|
|
||||||
base = Current();
|
|
||||||
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;
|
|
||||||
offset -= num;
|
|
||||||
} break;
|
|
||||||
default: {
|
|
||||||
if ('0' <= cmd[i] && cmd[i] <= '9') {
|
|
||||||
uint64_t num = 0;
|
|
||||||
while (i < cmd.size() && '0' <= cmd[i] && cmd[i] <= '9') {
|
|
||||||
num = num * 10 + (cmd[i] - '0');
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
base = Number(num);
|
|
||||||
} else {
|
|
||||||
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;
|
|
||||||
offset += positive ? num : -num;
|
|
||||||
skip_space();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace bed::internal::address
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#include "bed.h"
|
|
||||||
#include "internal/address/address.h"
|
|
||||||
|
|
||||||
namespace bed::internal::address {
|
|
||||||
Address::Result Address::handle(BEd &ctx, std::string &cmd, uint64_t &i) {
|
|
||||||
bool prev_given = false;
|
|
||||||
Address prev;
|
|
||||||
Address curr;
|
|
||||||
while (i < cmd.size()) {
|
|
||||||
if (cmd[i] == '%') {
|
|
||||||
prev_given = true;
|
|
||||||
prev.base = Number(ctx.active->prev_range.start);
|
|
||||||
prev.offset = 0;
|
|
||||||
curr.base = Number(ctx.active->prev_range.end);
|
|
||||||
curr.offset = 0;
|
|
||||||
i++;
|
|
||||||
} else {
|
|
||||||
curr = Address(cmd, i);
|
|
||||||
}
|
|
||||||
if (i < cmd.size() && (cmd[i] == ',' || cmd[i] == ';')) {
|
|
||||||
if (std::holds_alternative<None>(curr.base)) {
|
|
||||||
if (cmd[i] == ',')
|
|
||||||
curr.base = Number(1);
|
|
||||||
else
|
|
||||||
curr.base = Current();
|
|
||||||
curr.offset = 0;
|
|
||||||
prev_given = false;
|
|
||||||
} else {
|
|
||||||
if (cmd[i] == ';') {
|
|
||||||
uint64_t resolved = curr.resolve(ctx);
|
|
||||||
ctx.active->jump(resolved);
|
|
||||||
curr.base = Number(resolved);
|
|
||||||
curr.offset = 0;
|
|
||||||
}
|
|
||||||
prev_given = true;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
prev = std::move(curr);
|
|
||||||
} else {
|
|
||||||
if (std::holds_alternative<None>(curr.base)) {
|
|
||||||
if (std::holds_alternative<std::monostate>(prev.base))
|
|
||||||
return {{}, 0};
|
|
||||||
if (prev_given) {
|
|
||||||
curr = prev;
|
|
||||||
} else {
|
|
||||||
curr.base = Last();
|
|
||||||
curr.offset = 0;
|
|
||||||
}
|
|
||||||
return {{prev.resolve(ctx), curr.resolve(ctx)}, 2};
|
|
||||||
}
|
|
||||||
if (prev_given)
|
|
||||||
return {{prev.resolve(ctx), curr.resolve(ctx)}, 2};
|
|
||||||
return {{curr.resolve(ctx)}, 1};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}; // namespace bed::internal::address
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#include "bed.h"
|
|
||||||
#include "internal/address/address.h"
|
|
||||||
|
|
||||||
namespace bed::internal::address {
|
|
||||||
uint64_t Address::resolve(BEd &ctx) {
|
|
||||||
uint64_t result = std::visit(
|
|
||||||
[&](auto const &addr) -> uint64_t {
|
|
||||||
uint64_t line = 0;
|
|
||||||
using T = std::decay_t<decltype(addr)>;
|
|
||||||
if constexpr (std::is_same_v<T, std::monostate>) {
|
|
||||||
throw address_error("empty address");
|
|
||||||
} else if constexpr (std::is_same_v<T, None>) {
|
|
||||||
throw address_error("no address");
|
|
||||||
} else if constexpr (std::is_same_v<T, Current>) {
|
|
||||||
line = ctx.active->line;
|
|
||||||
} else if constexpr (std::is_same_v<T, Last>) {
|
|
||||||
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)
|
|
||||||
current_line = 1;
|
|
||||||
if (addr.dir == Direction::Forward) {
|
|
||||||
if (ctx.active->parser) {
|
|
||||||
uint64_t closing = ctx.active->parser->next_closing(current_line - 1);
|
|
||||||
if (closing == UINT64_MAX)
|
|
||||||
line = ctx.active->vase.lines();
|
|
||||||
else
|
|
||||||
line = closing + 1;
|
|
||||||
} else {
|
|
||||||
line = current_line + 10;
|
|
||||||
if (line > ctx.active->vase.lines())
|
|
||||||
line = ctx.active->vase.lines();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ctx.active->parser) {
|
|
||||||
line = ctx.active->parser->prev_opening(current_line - 1) + 1;
|
|
||||||
} else {
|
|
||||||
if (current_line > 10)
|
|
||||||
line = current_line - 10;
|
|
||||||
else
|
|
||||||
line = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw ed_error("Unhandled address given.");
|
|
||||||
}
|
|
||||||
if (offset < 0 && line < (uint64_t)-offset)
|
|
||||||
throw address_error("Can't have negative addresses");
|
|
||||||
line += offset;
|
|
||||||
if (line > ctx.active->vase.lines())
|
|
||||||
throw address_error("Line number too high.");
|
|
||||||
return line;
|
|
||||||
},
|
|
||||||
base
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}; // namespace bed::internal::address
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
#include "internal/buffer/buffer.h"
|
|
||||||
#include "bed.h"
|
|
||||||
|
|
||||||
namespace bed::internal::buffer {
|
|
||||||
Buffer::Buffer() : vase("/tmp") {
|
|
||||||
parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby());
|
|
||||||
line = vase.lines();
|
|
||||||
modified = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Buffer::Buffer(std::string command) : vase(command, "/tmp") {
|
|
||||||
parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby());
|
|
||||||
line = vase.lines();
|
|
||||||
if (!line)
|
|
||||||
return;
|
|
||||||
prev_range.start = 1;
|
|
||||||
prev_range.end = line;
|
|
||||||
modified = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Buffer::Buffer(std::filesystem::path path) : vase(path, "/tmp") {
|
|
||||||
parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby());
|
|
||||||
line = vase.lines();
|
|
||||||
if (!line)
|
|
||||||
return;
|
|
||||||
prev_range.start = 1;
|
|
||||||
prev_range.end = line;
|
|
||||||
save_path = path;
|
|
||||||
modified = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Buffer::load(std::string command) {
|
|
||||||
vase::Vase new_vase = vase::Vase(command, "/tmp");
|
|
||||||
vase = std::move(new_vase);
|
|
||||||
parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby());
|
|
||||||
line = vase.lines();
|
|
||||||
if (!line) {
|
|
||||||
prev_range.start = 0;
|
|
||||||
prev_range.end = 0;
|
|
||||||
} else {
|
|
||||||
prev_range.start = 1;
|
|
||||||
prev_range.end = line;
|
|
||||||
}
|
|
||||||
modified = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Buffer::load(std::filesystem::path path) {
|
|
||||||
vase::Vase new_vase = vase::Vase(path, "/tmp");
|
|
||||||
vase = std::move(new_vase);
|
|
||||||
parser.emplace(vase, vase.lines(), syntax::ruby::lang_ruby());
|
|
||||||
line = vase.lines();
|
|
||||||
if (!line) {
|
|
||||||
prev_range.start = 0;
|
|
||||||
prev_range.end = 0;
|
|
||||||
} else {
|
|
||||||
prev_range.start = 1;
|
|
||||||
prev_range.end = line;
|
|
||||||
}
|
|
||||||
save_path = path;
|
|
||||||
modified = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Buffer::jump(uint64_t n_line) {
|
|
||||||
if (n_line > vase.lines())
|
|
||||||
throw ed_error("Line number too high.");
|
|
||||||
line = n_line;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Buffer::append(std::string text, uint64_t line) {
|
|
||||||
using namespace bed::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();
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void apply(std::ostream &out, const theme::Highlight &hl) {
|
|
||||||
out << "\x1b[0m";
|
|
||||||
const uint8_t r = (hl.fg >> 16) & 0xff;
|
|
||||||
const uint8_t g = (hl.fg >> 8) & 0xff;
|
|
||||||
const uint8_t b = hl.fg & 0xff;
|
|
||||||
out << "\x1b[38;2;"
|
|
||||||
<< (unsigned)r << ';'
|
|
||||||
<< (unsigned)g << ';'
|
|
||||||
<< (unsigned)b << 'm';
|
|
||||||
if (hl.bg != 0) {
|
|
||||||
const uint8_t br = (hl.bg >> 16) & 0xff;
|
|
||||||
const uint8_t bg = (hl.bg >> 8) & 0xff;
|
|
||||||
const uint8_t bb = hl.bg & 0xff;
|
|
||||||
out << "\x1b[48;2;"
|
|
||||||
<< (unsigned)br << ';'
|
|
||||||
<< (unsigned)bg << ';'
|
|
||||||
<< (unsigned)bb << 'm';
|
|
||||||
}
|
|
||||||
if (hl.flags & theme::Highlight::Bold)
|
|
||||||
out << "\x1b[1m";
|
|
||||||
if (hl.flags & theme::Highlight::Italic)
|
|
||||||
out << "\x1b[3m";
|
|
||||||
if (hl.flags & theme::Highlight::Underline)
|
|
||||||
out << "\x1b[4m";
|
|
||||||
if (hl.flags & theme::Highlight::Strikethrough)
|
|
||||||
out << "\x1b[9m";
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void reset(std::ostream &out) {
|
|
||||||
out << "\x1b[0m";
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
while (start_line <= end_line) {
|
|
||||||
it.next();
|
|
||||||
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 << 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) {
|
|
||||||
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 bed::internal::buffer
|
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/buffer/buffer.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
GenericBuffer::~GenericBuffer() {
|
||||||
|
vase::Shard::release(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GenericBuffer::waste() {
|
||||||
|
return save_path.empty()
|
||||||
|
&& root == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GenericBuffer::lines() {
|
||||||
|
if (root)
|
||||||
|
return root->lines + 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GenericBuffer::bytes() {
|
||||||
|
if (root)
|
||||||
|
return root->length + 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
|
||||||
|
if (lines())
|
||||||
|
ctx.marks.erase(name, 1, lines());
|
||||||
|
vase::Shard::release(root);
|
||||||
|
vase::Shard::retain(text);
|
||||||
|
root = text;
|
||||||
|
state = buffer::GenericBuffer::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());
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::set_filename(std::filesystem::path path) {
|
||||||
|
save_path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path GenericBuffer::filename() {
|
||||||
|
return save_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
void GenericBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = line + 1;
|
||||||
|
ctx.prev().end = line + text->lines + 1;
|
||||||
|
root = vase::insert(&ctx.append, root, text, line);
|
||||||
|
ctx.marks.insert(name, ctx.prev().start, ctx.prev().end);
|
||||||
|
if (parser)
|
||||||
|
parser->insert(root, ctx.prev().start, ctx.prev().end);
|
||||||
|
state = Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
root = vase::erase(root, start_line, end_line);
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = std::min(start_line, lines());
|
||||||
|
ctx.prev().end = std::min(start_line, lines());
|
||||||
|
ctx.marks.erase(name, start_line, end_line - start_line + 1);
|
||||||
|
if (parser)
|
||||||
|
parser->erase(root, start_line, end_line - start_line + 1);
|
||||||
|
state = Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
root = vase::join(root, start_line, end_line);
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = start_line;
|
||||||
|
ctx.marks.collapse(name, start_line, end_line - start_line);
|
||||||
|
if (parser)
|
||||||
|
parser->erase(root, start_line, end_line - start_line);
|
||||||
|
state = Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::substitute(
|
||||||
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
|
std::string ®ex, std::string &replacement, std::string &options
|
||||||
|
) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
if (parser)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
vase::Shard *GenericBuffer::copy(uint64_t start_line, uint64_t end_line) {
|
||||||
|
return vase::copy(root, start_line, end_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GenericBuffer::find_next(std::string_view pattern, uint64_t start) {
|
||||||
|
return vase::find_next(root, pattern, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GenericBuffer::find_prev(std::string_view pattern, uint64_t start) {
|
||||||
|
return vase::find_prev(root, pattern, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GenericBuffer::next_closing(uint64_t start) {
|
||||||
|
if (parser.has_value()) {
|
||||||
|
uint64_t closing = parser->next_closing(start - 1);
|
||||||
|
if (closing == UINT64_MAX)
|
||||||
|
return lines();
|
||||||
|
return closing + 1;
|
||||||
|
} else {
|
||||||
|
start += 10;
|
||||||
|
if (start > lines())
|
||||||
|
return lines();
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t GenericBuffer::prev_closing(uint64_t start) {
|
||||||
|
if (parser.has_value()) {
|
||||||
|
return parser->prev_opening(start - 1) + 1;
|
||||||
|
} else {
|
||||||
|
if (start > 10)
|
||||||
|
return start - 10;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void apply(io::IO &io, const Highlight &hl) {
|
||||||
|
io.write("\x1b[0m");
|
||||||
|
const uint8_t r = (hl.fg >> 16) & 0xff;
|
||||||
|
const uint8_t g = (hl.fg >> 8) & 0xff;
|
||||||
|
const uint8_t b = hl.fg & 0xff;
|
||||||
|
io.write(std::format("\x1b[38;2;{};{};{}m", r, g, b));
|
||||||
|
if (hl.bg != 0) {
|
||||||
|
const uint8_t br = (hl.bg >> 16) & 0xff;
|
||||||
|
const uint8_t bg = (hl.bg >> 8) & 0xff;
|
||||||
|
const uint8_t bb = hl.bg & 0xff;
|
||||||
|
io.write(std::format("\x1b[48;2;{};{};{}m", br, bg, bb));
|
||||||
|
}
|
||||||
|
if (hl.flags & Highlight::Bold)
|
||||||
|
io.write("\x1b[1m");
|
||||||
|
if (hl.flags & Highlight::Italic)
|
||||||
|
io.write("\x1b[3m");
|
||||||
|
if (hl.flags & Highlight::Underline)
|
||||||
|
io.write("\x1b[4m");
|
||||||
|
if (hl.flags & Highlight::Strikethrough)
|
||||||
|
io.write("\x1b[9m");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void reset(io::IO &io) {
|
||||||
|
io.write("\x1b[0m");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
if (parser) {
|
||||||
|
auto it_o = parser->get_hl(root, start_line - 1);
|
||||||
|
auto &it = *it_o;
|
||||||
|
while (start_line <= end_line) {
|
||||||
|
it.next();
|
||||||
|
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() || end > line.size())
|
||||||
|
break;
|
||||||
|
if (cursor < start)
|
||||||
|
ctx.io.write(line.data() + cursor, start - cursor);
|
||||||
|
const auto highlight = ctx.theme.get(token);
|
||||||
|
apply(ctx.io, highlight);
|
||||||
|
ctx.io.write(line.data() + start, end - start);
|
||||||
|
reset(ctx.io);
|
||||||
|
cursor = end;
|
||||||
|
}
|
||||||
|
if (cursor < line.size())
|
||||||
|
ctx.io.write(line.data() + cursor, line.size() - cursor);
|
||||||
|
ctx.io.write_line("");
|
||||||
|
++start_line;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vase::Iterator it(root, start_line - 1, Direction::Forward);
|
||||||
|
while (it.next() && start_line++ <= end_line)
|
||||||
|
ctx.io.write_line(it.line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().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(root, start_line - 1);
|
||||||
|
auto &it = *it_o;
|
||||||
|
while (start_line <= end_line) {
|
||||||
|
it.next();
|
||||||
|
ctx.io.write(std::format("{:>{}}\t", start_line, width));
|
||||||
|
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() || end > line.size())
|
||||||
|
break;
|
||||||
|
if (cursor < start)
|
||||||
|
ctx.io.write(line.data() + cursor, start - cursor);
|
||||||
|
const auto highlight = ctx.theme.get(token);
|
||||||
|
apply(ctx.io, highlight);
|
||||||
|
ctx.io.write(line.data() + start, end - start);
|
||||||
|
reset(ctx.io);
|
||||||
|
cursor = end;
|
||||||
|
}
|
||||||
|
if (cursor < line.size())
|
||||||
|
ctx.io.write(line.data() + cursor, line.size() - cursor);
|
||||||
|
ctx.io.write_line("");
|
||||||
|
++start_line;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vase::Iterator it(root, start_line - 1, Direction::Forward);
|
||||||
|
while (it.next() && start_line <= end_line)
|
||||||
|
ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenericBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
vase::Iterator it(root, start_line - 1, Direction::Forward);
|
||||||
|
while (it.next() && start_line++ <= end_line)
|
||||||
|
ctx.io.write_line(list_string(it.line));
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/buffer/buffer.h"
|
||||||
|
|
||||||
|
namespace bed::internal::buffer {
|
||||||
|
ClipBuffer::~ClipBuffer() {}
|
||||||
|
|
||||||
|
bool ClipBuffer::waste() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ClipBuffer::lines() {
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
uint64_t lines = s ? s->lines + 1 : 0;
|
||||||
|
vase::Shard::release(s);
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ClipBuffer::bytes() {
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
uint64_t length = s ? s->length + 1 : 0;
|
||||||
|
vase::Shard::release(s);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::clip_write(vase::Shard *text) {
|
||||||
|
FILE *pipe = popen("xclip -selection clipboard -i", "w");
|
||||||
|
if (!pipe)
|
||||||
|
throw ed_error("can't access clipboard");
|
||||||
|
auto s = vase::to_string(text);
|
||||||
|
fwrite(s.data(), 1, s.length(), pipe);
|
||||||
|
if (pclose(pipe) != 0)
|
||||||
|
throw ed_error("can't write clipboard");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::load(BEd &ctx, vase::Shard *text) {
|
||||||
|
clip_write(text);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::set_filename(std::filesystem::path) {}
|
||||||
|
|
||||||
|
std::filesystem::path ClipBuffer::filename() {
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
void ClipBuffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = line + 1;
|
||||||
|
ctx.prev().end = line + text->lines + 1;
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
s = vase::insert(&ctx.append, s, text, line);
|
||||||
|
clip_write(s);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
ctx.marks.insert(name, ctx.prev().start, ctx.prev().end);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
s = vase::erase(s, start_line, end_line);
|
||||||
|
clip_write(s);
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = std::min(start_line, s ? s->lines + 1 : 0);
|
||||||
|
ctx.prev().end = std::min(start_line, s ? s->lines + 1 : 0);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
ctx.marks.erase(name, start_line, end_line - start_line + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
s = vase::join(s, start_line, end_line);
|
||||||
|
clip_write(s);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = start_line;
|
||||||
|
ctx.marks.collapse(name, start_line, end_line - start_line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::substitute(
|
||||||
|
BEd &ctx, uint64_t start_line, uint64_t end_line,
|
||||||
|
std::string ®ex, std::string &replacement, std::string &options
|
||||||
|
) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
s = vase::substitute(
|
||||||
|
&ctx.append,
|
||||||
|
s,
|
||||||
|
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 (new_lines)
|
||||||
|
ctx.marks.insert(name, line, line + new_lines - 1);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
clip_write(s);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
vase::Shard *ClipBuffer::copy(uint64_t start_line, uint64_t end_line) {
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
vase::Shard *o = vase::copy(s, start_line, end_line);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ClipBuffer::find_next(std::string_view pattern, uint64_t start) {
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
uint64_t line = vase::find_next(s, pattern, start);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ClipBuffer::find_prev(std::string_view pattern, uint64_t start) {
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
uint64_t line = vase::find_prev(s, pattern, start);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ClipBuffer::next_closing(uint64_t start) {
|
||||||
|
start += 10;
|
||||||
|
uint64_t line = lines();
|
||||||
|
if (start > line)
|
||||||
|
return line;
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ClipBuffer::prev_closing(uint64_t start) {
|
||||||
|
if (start > 10)
|
||||||
|
return start - 10;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
vase::Iterator it(s, start_line - 1, Direction::Forward);
|
||||||
|
while (it.next() && start_line++ <= end_line)
|
||||||
|
ctx.io.write_line(it.line);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::number_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
uint8_t width = 1;
|
||||||
|
for (uint64_t n = end_line; n >= 10; n /= 10)
|
||||||
|
++width;
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
vase::Iterator it(s, start_line - 1, Direction::Forward);
|
||||||
|
while (it.next() && start_line <= end_line)
|
||||||
|
ctx.io.write_line(std::format("{:>{}}\t{}", start_line++, width, it.line));
|
||||||
|
vase::Shard::release(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipBuffer::list_print(BEd &ctx, uint64_t start_line, uint64_t end_line) {
|
||||||
|
ctx.prev().buffername = name;
|
||||||
|
ctx.prev().start = start_line;
|
||||||
|
ctx.prev().end = end_line;
|
||||||
|
auto s = vase::Shard::from_command("xclip -selection clipboard -o", true);
|
||||||
|
vase::Iterator it(s, start_line - 1, Direction::Forward);
|
||||||
|
while (it.next() && start_line++ <= end_line)
|
||||||
|
ctx.io.write_line(list_string(it.line));
|
||||||
|
vase::Shard::release(s);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::buffer
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
#include "internal/commands/commands.h"
|
|
||||||
#include "bed.h"
|
|
||||||
#include "internal/commands/suffixes.h"
|
|
||||||
|
|
||||||
namespace bed::internal::commands {
|
|
||||||
void Suffix::register_suffixes(BEd &ctx) {
|
|
||||||
ctx.suffixes['p' - 'a'] = Suffix{
|
|
||||||
.desc = "Prints current line.",
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void Command::register_posix(BEd &ctx) {
|
|
||||||
ctx.no_op = Command{
|
|
||||||
.address_mode = Command::AddressMode::Single,
|
|
||||||
.suffix = Command::SuffixKind::None,
|
|
||||||
.desc = "Prints a line and jumps to it (default: .+1)",
|
|
||||||
.accept_zero = false,
|
|
||||||
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
|
|
||||||
uint64_t line;
|
|
||||||
if (addresses.size())
|
|
||||||
line = addresses[0];
|
|
||||||
else
|
|
||||||
line = ctx.active->line + 1;
|
|
||||||
if (line == 0)
|
|
||||||
throw ed_error("Line 0 is invalid.");
|
|
||||||
ctx.active->jump(line);
|
|
||||||
ctx.active->print(ctx, line, line);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ctx.eof_op = Command{
|
|
||||||
.address_mode = Command::AddressMode::None,
|
|
||||||
.suffix = Command::SuffixKind::None,
|
|
||||||
.desc = "Try quitting.",
|
|
||||||
.accept_zero = false,
|
|
||||||
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view) {
|
|
||||||
for (auto &[name, buffer] : ctx.buffers)
|
|
||||||
if (buffer->modified)
|
|
||||||
throw ed_error("Buffer " + name + " modified.");
|
|
||||||
throw fatal_error("Quitting", 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ctx.commands.insert(
|
|
||||||
"a",
|
|
||||||
Command{
|
|
||||||
.address_mode = Command::AddressMode::Single,
|
|
||||||
.suffix = Command::SuffixKind::Suffix,
|
|
||||||
.desc = "Append lines at address (default: .)",
|
|
||||||
.accept_zero = true,
|
|
||||||
.handle = [](BEd &, std::span<const uint64_t>, std::string_view) {
|
|
||||||
// TODO: start a text editing session, then append its stuff.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
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{
|
|
||||||
.address_mode = Command::AddressMode::None,
|
|
||||||
.suffix = Command::SuffixKind::None,
|
|
||||||
.desc = "Try quitting.",
|
|
||||||
.accept_zero = false,
|
|
||||||
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view) {
|
|
||||||
for (auto &[name, buffer] : ctx.buffers)
|
|
||||||
if (buffer->modified)
|
|
||||||
throw ed_error("Buffer " + name + " modified.");
|
|
||||||
throw fatal_error("Quitting", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ctx.commands.insert(
|
|
||||||
"Q",
|
|
||||||
Command{
|
|
||||||
.address_mode = Command::AddressMode::None,
|
|
||||||
.suffix = Command::SuffixKind::None,
|
|
||||||
.desc = "Force quitting.",
|
|
||||||
.accept_zero = false,
|
|
||||||
.handle = [](BEd &, std::span<const uint64_t>, std::string_view) {
|
|
||||||
throw fatal_error("Force Quitting", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ctx.commands.insert(
|
|
||||||
"p",
|
|
||||||
Command{
|
|
||||||
.address_mode = Command::AddressMode::Range,
|
|
||||||
.suffix = Command::SuffixKind::Suffix,
|
|
||||||
.desc = "Print range (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->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::Range,
|
|
||||||
.suffix = Command::SuffixKind::Suffix,
|
|
||||||
.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 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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ctx.commands.insert(
|
|
||||||
"debug",
|
|
||||||
Command{
|
|
||||||
.address_mode = Command::AddressMode::None,
|
|
||||||
.suffix = Command::SuffixKind::None,
|
|
||||||
.desc = "",
|
|
||||||
.accept_zero = false,
|
|
||||||
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view) {
|
|
||||||
syntax::dump_events(ctx.active->parser->root);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
ctx.commands.insert(
|
|
||||||
"E",
|
|
||||||
Command{
|
|
||||||
.address_mode = Command::AddressMode::None,
|
|
||||||
.suffix = Command::SuffixKind::Argument,
|
|
||||||
.desc = "Load a file into the current buffer.",
|
|
||||||
.accept_zero = true,
|
|
||||||
.handle = [](BEd &ctx, std::span<const uint64_t>, std::string_view file) {
|
|
||||||
bool empty = false;
|
|
||||||
if (file.empty())
|
|
||||||
empty = true;
|
|
||||||
uint64_t i = 0;
|
|
||||||
while (i < file.length() && (file[i] == ' ' || file[i] == '\t'))
|
|
||||||
i++;
|
|
||||||
if (i >= file.size())
|
|
||||||
empty = true;
|
|
||||||
if (empty) {
|
|
||||||
if (ctx.active->save_path == "")
|
|
||||||
throw ed_error("Need filename!");
|
|
||||||
ctx.active->load(ctx.active->save_path);
|
|
||||||
} else if (file[i] == '!') {
|
|
||||||
file = file.substr(1);
|
|
||||||
ctx.active->load(file);
|
|
||||||
} else {
|
|
||||||
ctx.active->load(std::filesystem::path(file));
|
|
||||||
}
|
|
||||||
std::cout << ctx.active->vase.length() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} // namespace bed::internal::commands
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/functions/functions.h"
|
||||||
|
#include "internal/functions/suffixes.h"
|
||||||
|
|
||||||
|
namespace bed::internal::functions {
|
||||||
|
void Function::register_extented(BEd &ctx) {
|
||||||
|
ctx.functions.insert(
|
||||||
|
"cd",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::Any,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Change directory.",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](BEd &, const buffer::Address &, vase::Shard *, const Argument &arg_, std::vector<buffer::Line> *) {
|
||||||
|
auto path = std::get<std::string>(arg_);
|
||||||
|
const auto first = path.find_first_not_of(" \t");
|
||||||
|
const auto last = path.find_last_not_of(" \t");
|
||||||
|
if (first == std::string::npos)
|
||||||
|
path.clear();
|
||||||
|
else
|
||||||
|
path = path.substr(first, last - first + 1);
|
||||||
|
if (path.empty())
|
||||||
|
path = getenv("HOME");
|
||||||
|
if (path == "~")
|
||||||
|
path = getenv("HOME");
|
||||||
|
if (path.starts_with("~/")) {
|
||||||
|
const char *home = getenv("HOME");
|
||||||
|
if (home)
|
||||||
|
path = std::string(home) + path.substr(1);
|
||||||
|
}
|
||||||
|
if (chdir(path.c_str()) == -1)
|
||||||
|
throw ed_error("Can't change directory.");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"pwd",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Print directory.",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
|
||||||
|
char cwd[PATH_MAX];
|
||||||
|
if (!getcwd(cwd, sizeof(cwd)))
|
||||||
|
throw ed_error("Can't determine current directory.");
|
||||||
|
ctx.io.write_line(cwd);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::functions
|
||||||
@@ -0,0 +1,657 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/functions/functions.h"
|
||||||
|
#include "internal/functions/suffixes.h"
|
||||||
|
|
||||||
|
namespace bed::internal::functions {
|
||||||
|
void Suffix::register_suffixes(BEd &ctx) {
|
||||||
|
ctx.suffixes['p' - 'a'] = Suffix{
|
||||||
|
.desc = "Prints current line.",
|
||||||
|
.handle = [](BEd &ctx) {
|
||||||
|
auto &addr = ctx.current();
|
||||||
|
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ctx.suffixes['n' - 'a'] = Suffix{
|
||||||
|
.desc = "Prints current line with line number.",
|
||||||
|
.handle = [](BEd &ctx) {
|
||||||
|
auto &addr = ctx.current();
|
||||||
|
ctx.buffer(addr.buffername).number_print(ctx, addr.number, addr.number);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ctx.suffixes['l' - 'a'] = Suffix{
|
||||||
|
.desc = "Prints current line unambiguously.",
|
||||||
|
.handle = [](BEd &ctx) {
|
||||||
|
auto &addr = ctx.current();
|
||||||
|
ctx.buffer(addr.buffername).list_print(ctx, addr.number, addr.number);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Function::register_posix(BEd &ctx) {
|
||||||
|
ctx.functions.insert(
|
||||||
|
"a",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Line,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::Text,
|
||||||
|
.desc = "Append text to a line.",
|
||||||
|
.default_address = ".",
|
||||||
|
.accept_zero = true,
|
||||||
|
.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::Line>(addr_);
|
||||||
|
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
|
||||||
|
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
|
||||||
|
vase::Shard::release(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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(
|
||||||
|
"d",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Delete set of lines.",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.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_);
|
||||||
|
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
|
||||||
|
ctx.current() = {addr.buffername, addr.start};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"e",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Try load a file into the current buffer.",
|
||||||
|
.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<std::string>(addr_);
|
||||||
|
auto &buf = ctx.buffer(addr);
|
||||||
|
if (buf.state == buffer::Buffer::Modified) {
|
||||||
|
buf.state = buffer::Buffer::Warned;
|
||||||
|
throw ed_error("Buffer modified.");
|
||||||
|
}
|
||||||
|
vase::Shard *s = nullptr;
|
||||||
|
if (std::holds_alternative<std::filesystem::path>(arg)) {
|
||||||
|
auto path = std::get<std::filesystem::path>(arg);
|
||||||
|
s = vase::Shard::from_file(path, true);
|
||||||
|
buf.set_filename(path);
|
||||||
|
} else if (std::holds_alternative<ShellArg>(arg)) {
|
||||||
|
auto cmd = std::get<ShellArg>(arg).cmd;
|
||||||
|
ctx.escape_command(cmd, buf.filename().string());
|
||||||
|
s = vase::Shard::from_command(cmd.c_str(), true);
|
||||||
|
} else {
|
||||||
|
auto path = buf.filename();
|
||||||
|
if (path.empty())
|
||||||
|
throw ed_error("Need filename.");
|
||||||
|
s = vase::Shard::from_file(path, true);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
buf.load(ctx, s);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
} catch (...) {
|
||||||
|
vase::Shard::release(s);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
ctx.io.write_line(std::format("{}", buf.bytes()));
|
||||||
|
ctx.current() = {addr, buf.lines()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"E",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Load a file into the current buffer.",
|
||||||
|
.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<std::string>(addr_);
|
||||||
|
auto &buf = ctx.buffer(addr);
|
||||||
|
vase::Shard *s = nullptr;
|
||||||
|
if (std::holds_alternative<std::filesystem::path>(arg)) {
|
||||||
|
auto path = std::get<std::filesystem::path>(arg);
|
||||||
|
s = vase::Shard::from_file(path, true);
|
||||||
|
buf.set_filename(path);
|
||||||
|
} else if (std::holds_alternative<ShellArg>(arg)) {
|
||||||
|
auto cmd = std::get<ShellArg>(arg).cmd;
|
||||||
|
ctx.escape_command(cmd, buf.filename().string());
|
||||||
|
s = vase::Shard::from_command(cmd.c_str(), true);
|
||||||
|
} else {
|
||||||
|
auto path = buf.filename();
|
||||||
|
if (path.empty())
|
||||||
|
throw ed_error("Need filename.");
|
||||||
|
s = vase::Shard::from_file(path, true);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
buf.load(ctx, s);
|
||||||
|
vase::Shard::release(s);
|
||||||
|
} catch (...) {
|
||||||
|
vase::Shard::release(s);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
ctx.io.write_line(std::format("{}", buf.bytes()));
|
||||||
|
ctx.current() = {addr, buf.lines()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"f",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Set a save path.",
|
||||||
|
.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<std::string>(addr_);
|
||||||
|
auto &buf_ = ctx.buffer(addr);
|
||||||
|
if (buf_.kind != buffer::Buffer::Kind::Generic)
|
||||||
|
return;
|
||||||
|
auto &buf = *(buffer::GenericBuffer *)&buf_;
|
||||||
|
if (std::holds_alternative<std::filesystem::path>(arg))
|
||||||
|
buf.set_filename(std::get<std::filesystem::path>(arg));
|
||||||
|
else if (std::holds_alternative<ShellArg>(arg))
|
||||||
|
throw ed_error("Can't save shell command as save path.");
|
||||||
|
if (buf.filename().empty())
|
||||||
|
throw ed_error("Filename needed.");
|
||||||
|
ctx.io.write_line(buf.filename().string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"h",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Print last help message",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
|
||||||
|
ctx.io.write_line(ctx.last_help);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"H",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Toggle help mode.",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
|
||||||
|
ctx.help_mode = !ctx.help_mode;
|
||||||
|
if (ctx.help_mode)
|
||||||
|
ctx.io.write_line(ctx.last_help);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"i",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Line,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::Text,
|
||||||
|
.desc = "Insert text before a line.",
|
||||||
|
.default_address = ".",
|
||||||
|
.accept_zero = true,
|
||||||
|
.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::Line>(addr_);
|
||||||
|
if (addr.number)
|
||||||
|
addr.number--;
|
||||||
|
ctx.buffer(addr.buffername).append(ctx, text, addr.number);
|
||||||
|
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
|
||||||
|
vase::Shard::release(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"j",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Join a set of lines.",
|
||||||
|
.default_address = ".,.+1",
|
||||||
|
.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_);
|
||||||
|
ctx.buffer(addr.buffername).join(ctx, addr.start, addr.end);
|
||||||
|
ctx.current() = {addr.buffername, addr.start};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"k",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Line,
|
||||||
|
.argument_kind = Function::ArgumentKind::Mark,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Mark a line.",
|
||||||
|
.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::Line>(addr_);
|
||||||
|
ctx.mark(std::get<char>(arg), addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"l",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "List (print unambiguous) range",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.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_);
|
||||||
|
ctx.buffer(addr.buffername).list_print(ctx, addr.start, addr.end);
|
||||||
|
ctx.current() = {addr.buffername, addr.end};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"m",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::Line,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Move a range of lines.",
|
||||||
|
.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<buffer::Line>(arg_);
|
||||||
|
if (arg.buffername == addr.buffername
|
||||||
|
&& addr.start <= arg.number
|
||||||
|
&& addr.end < arg.number)
|
||||||
|
throw ed_error("Can't move lines within themselves.");
|
||||||
|
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
|
||||||
|
ctx.mark(252, arg);
|
||||||
|
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
|
||||||
|
arg = ctx.marks.get(252);
|
||||||
|
try {
|
||||||
|
if (arg.number == UINT64_MAX)
|
||||||
|
throw ed_error("Unexpected error when moving lines.");
|
||||||
|
ctx.buffer(arg.buffername).append(ctx, text, arg.number);
|
||||||
|
vase::Shard::release(text);
|
||||||
|
} catch (...) {
|
||||||
|
if (!addr.start) {
|
||||||
|
vase::Shard::release(text);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
ctx.buffer(addr.buffername).append(ctx, text, --addr.start);
|
||||||
|
vase::Shard::release(text);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"n",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Print range with line numbers",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.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_);
|
||||||
|
ctx.buffer(addr.buffername).number_print(ctx, addr.start, addr.end);
|
||||||
|
ctx.current() = {addr.buffername, addr.end};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"p",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Print range",
|
||||||
|
.default_address = ".,.",
|
||||||
|
.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_);
|
||||||
|
ctx.buffer(addr.buffername).print(ctx, addr.start, addr.end);
|
||||||
|
ctx.current() = {addr.buffername, addr.end};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"P",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Toggle prompt.",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
|
||||||
|
ctx.prompt_mode = !ctx.prompt_mode;
|
||||||
|
if (ctx.prompt_mode && !ctx.prompt) {
|
||||||
|
ctx.prompt = [](BEd &) { return "*"; };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"q",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Try quitting.",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
|
||||||
|
std::string modified_buffers;
|
||||||
|
for (auto &[name, buffer] : ctx.buffers) {
|
||||||
|
if (buffer->state == buffer::Buffer::Modified) {
|
||||||
|
buffer->state = buffer::Buffer::Warned;
|
||||||
|
modified_buffers.append(name + ", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!modified_buffers.size())
|
||||||
|
throw fatal_error("Quitting", 0);
|
||||||
|
modified_buffers.erase(modified_buffers.size() - 2);
|
||||||
|
throw ed_error("Buffer(s) " + modified_buffers + " modified.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"Q",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Force quit.",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](BEd &, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
|
||||||
|
throw fatal_error("Force Quitting", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"r",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Line,
|
||||||
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Read from file into buffer.",
|
||||||
|
.default_address = "$",
|
||||||
|
.accept_zero = true,
|
||||||
|
.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 &buf = ctx.buffer(addr.buffername);
|
||||||
|
vase::Shard *s = nullptr;
|
||||||
|
if (std::holds_alternative<std::filesystem::path>(arg)) {
|
||||||
|
auto path = std::get<std::filesystem::path>(arg);
|
||||||
|
s = vase::Shard::from_file(path, true);
|
||||||
|
if (buf.filename().empty())
|
||||||
|
buf.set_filename(path);
|
||||||
|
} else if (std::holds_alternative<ShellArg>(arg)) {
|
||||||
|
auto cmd = std::get<ShellArg>(arg).cmd;
|
||||||
|
ctx.escape_command(cmd, buf.filename().string());
|
||||||
|
s = vase::Shard::from_command(cmd.c_str(), true);
|
||||||
|
} else {
|
||||||
|
auto path = buf.filename();
|
||||||
|
if (path.empty())
|
||||||
|
throw ed_error("Need filename.");
|
||||||
|
s = vase::Shard::from_file(path, true);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
buf.append(ctx, s, addr.number);
|
||||||
|
ctx.io.write_line(std::format("{}", s ? s->length + 1 : 0));
|
||||||
|
ctx.current() = {addr.buffername, addr.number + (s ? s->lines + 1 : 0)};
|
||||||
|
vase::Shard::release(s);
|
||||||
|
} catch (...) {
|
||||||
|
vase::Shard::release(s);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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(
|
||||||
|
"t",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::Line,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Copy a range of lines.",
|
||||||
|
.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<buffer::Line>(arg_);
|
||||||
|
auto text = ctx.buffer(addr.buffername).copy(addr.start, addr.end);
|
||||||
|
try {
|
||||||
|
ctx.buffer(arg.buffername).append(ctx, text, arg.number);
|
||||||
|
vase::Shard::release(text);
|
||||||
|
} catch (...) {
|
||||||
|
if (!addr.start) {
|
||||||
|
vase::Shard::release(text);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
ctx.buffer(addr.buffername).append(ctx, text, --addr.start);
|
||||||
|
vase::Shard::release(text);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"w",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::File,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Write buffer to disk.",
|
||||||
|
.default_address = "0,$",
|
||||||
|
.accept_zero = true,
|
||||||
|
.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 &buf = ctx.buffer(addr.buffername);
|
||||||
|
vase::Shard *text;
|
||||||
|
if (!addr.start && !addr.end) {
|
||||||
|
text = nullptr;
|
||||||
|
} else {
|
||||||
|
if (!addr.start && addr.end)
|
||||||
|
addr.start = 1;
|
||||||
|
text = buf.copy(addr.start, addr.end);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (std::holds_alternative<std::filesystem::path>(arg)) {
|
||||||
|
auto path = std::get<std::filesystem::path>(arg);
|
||||||
|
vase::write_file(path, text);
|
||||||
|
buf.set_filename(path);
|
||||||
|
} else if (std::holds_alternative<ShellArg>(arg)) {
|
||||||
|
auto cmd = std::get<ShellArg>(arg).cmd;
|
||||||
|
ctx.escape_command(cmd, buf.filename().string());
|
||||||
|
vase::write_command(cmd.c_str(), text);
|
||||||
|
} else {
|
||||||
|
auto path = buf.filename();
|
||||||
|
if (path.empty())
|
||||||
|
throw ed_error("Need filename.");
|
||||||
|
vase::write_file(path, text);
|
||||||
|
};
|
||||||
|
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
|
||||||
|
vase::Shard::release(text);
|
||||||
|
} catch (...) {
|
||||||
|
vase::Shard::release(text);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"=",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::Range,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Print line numbers",
|
||||||
|
.default_address = "$",
|
||||||
|
.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_);
|
||||||
|
if (addr.start == addr.end)
|
||||||
|
ctx.io.write_line(std::format(":{}:{}", addr.buffername, addr.start));
|
||||||
|
else
|
||||||
|
ctx.io.write_line(std::format(":{}:{},{}", addr.buffername, addr.start, addr.end));
|
||||||
|
ctx.current() = {addr.buffername, addr.end};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.functions.insert(
|
||||||
|
"!",
|
||||||
|
Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::Shell,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Run a shell command.",
|
||||||
|
.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<std::string>(addr_);
|
||||||
|
auto filename = ctx.buffer(addr).filename();
|
||||||
|
auto &arg = std::get<ShellArg>(arg_);
|
||||||
|
auto cmd = arg.cmd;
|
||||||
|
if (ctx.escape_command(cmd, filename.string()))
|
||||||
|
ctx.io.write(cmd + "\n");
|
||||||
|
ctx.io.run_pty(cmd);
|
||||||
|
ctx.io.write("!\n");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ctx.no_op = Function{
|
||||||
|
.address_kind = Function::AddressKind::Line,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Prints a line and jumps to it.",
|
||||||
|
.default_address = ".+1",
|
||||||
|
.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::Line>(addr_);
|
||||||
|
if (addr.number != 0)
|
||||||
|
ctx.buffer(addr.buffername).print(ctx, addr.number, addr.number);
|
||||||
|
ctx.current() = addr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ctx.eof_op = Function{
|
||||||
|
.address_kind = Function::AddressKind::None,
|
||||||
|
.argument_kind = Function::ArgumentKind::None,
|
||||||
|
.input_mode = Function::InputMode::None,
|
||||||
|
.desc = "Try quitting.",
|
||||||
|
.default_address = "",
|
||||||
|
.accept_zero = false,
|
||||||
|
.pre_text_mode = nullptr,
|
||||||
|
.handle = [](BEd &ctx, const buffer::Address &, vase::Shard *, const Argument &, std::vector<buffer::Line> *) {
|
||||||
|
std::string modified_buffers;
|
||||||
|
for (auto &[name, buffer] : ctx.buffers) {
|
||||||
|
if (buffer->state == buffer::Buffer::Modified) {
|
||||||
|
buffer->state = buffer::Buffer::Warned;
|
||||||
|
modified_buffers.append(name + ", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!modified_buffers.size())
|
||||||
|
throw fatal_error("Quitting", 0);
|
||||||
|
modified_buffers.erase(modified_buffers.size() - 2);
|
||||||
|
throw ed_error("Buffer(s) " + modified_buffers + " modified.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::functions
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
#include "internal/io/io.h"
|
||||||
|
|
||||||
|
namespace bed::internal::io {
|
||||||
|
KeyEvent::ReadResult IO::get_next_byte(char &out) {
|
||||||
|
if (!input_queue.empty()) {
|
||||||
|
out = input_queue.front();
|
||||||
|
input_queue.pop_front();
|
||||||
|
return KeyEvent::ReadResult::SUCCESS;
|
||||||
|
}
|
||||||
|
if (resized.load())
|
||||||
|
return KeyEvent::ReadResult::RESIZE;
|
||||||
|
ssize_t n = read(STDIN_FILENO, &out, 1);
|
||||||
|
if (n == 1)
|
||||||
|
return KeyEvent::ReadResult::SUCCESS;
|
||||||
|
if (n == -1 && errno == EINTR && resized.load())
|
||||||
|
return KeyEvent::ReadResult::RESIZE;
|
||||||
|
if (n == -1 && errno == EINTR)
|
||||||
|
return get_next_byte(out);
|
||||||
|
return KeyEvent::ReadResult::EOF_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::enqueue_bytes(const std::string &bytes) {
|
||||||
|
input_queue.insert(input_queue.begin(), bytes.begin(), bytes.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
int IO::utf8_seq_len(uint8_t byte) {
|
||||||
|
if ((byte & 0x80) == 0x00)
|
||||||
|
return 1;
|
||||||
|
if ((byte & 0xE0) == 0xC0)
|
||||||
|
return 2;
|
||||||
|
if ((byte & 0xF0) == 0xE0)
|
||||||
|
return 3;
|
||||||
|
if ((byte & 0xF8) == 0xF0)
|
||||||
|
return 4;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent::ReadResult IO::read_next_unit(std::string &out) {
|
||||||
|
out.clear();
|
||||||
|
char header;
|
||||||
|
KeyEvent::ReadResult res = get_next_byte(header);
|
||||||
|
if (res != KeyEvent::ReadResult::SUCCESS)
|
||||||
|
return res;
|
||||||
|
if (header == '\x1b') {
|
||||||
|
out.push_back(header);
|
||||||
|
char c;
|
||||||
|
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS)
|
||||||
|
return res;
|
||||||
|
out.push_back(c);
|
||||||
|
if (c != '[')
|
||||||
|
return KeyEvent::ReadResult::SUCCESS;
|
||||||
|
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS)
|
||||||
|
return res;
|
||||||
|
out.push_back(c);
|
||||||
|
if (c == 'M') {
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS)
|
||||||
|
return res;
|
||||||
|
out.push_back(c);
|
||||||
|
}
|
||||||
|
return KeyEvent::ReadResult::SUCCESS;
|
||||||
|
}
|
||||||
|
while ((uint8_t)c < 0x40 || (uint8_t)c > 0x7E) {
|
||||||
|
if (out.size() >= 32)
|
||||||
|
break;
|
||||||
|
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS)
|
||||||
|
return res;
|
||||||
|
out.push_back(c);
|
||||||
|
}
|
||||||
|
return KeyEvent::ReadResult::SUCCESS;
|
||||||
|
}
|
||||||
|
int seq_len = utf8_seq_len((uint8_t)header);
|
||||||
|
out.push_back(header);
|
||||||
|
if (seq_len == 1)
|
||||||
|
return KeyEvent::ReadResult::SUCCESS;
|
||||||
|
for (int i = 1; i < seq_len; i++) {
|
||||||
|
char c;
|
||||||
|
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) {
|
||||||
|
enqueue_bytes(out);
|
||||||
|
out.clear();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
out.push_back(c);
|
||||||
|
}
|
||||||
|
uint32_t prev_cp, cur_cp;
|
||||||
|
grapheme_decode_utf8(out.data(), out.size(), &prev_cp);
|
||||||
|
uint16_t state = 0;
|
||||||
|
while (true) {
|
||||||
|
char next_header;
|
||||||
|
if ((res = get_next_byte(next_header)) != KeyEvent::ReadResult::SUCCESS)
|
||||||
|
break;
|
||||||
|
int next_len = utf8_seq_len((uint8_t)next_header);
|
||||||
|
std::string next_seq(1, next_header);
|
||||||
|
bool complete = true;
|
||||||
|
for (int i = 1; i < next_len; i++) {
|
||||||
|
char c;
|
||||||
|
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) {
|
||||||
|
complete = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
next_seq.push_back(c);
|
||||||
|
}
|
||||||
|
if (!complete) {
|
||||||
|
enqueue_bytes(next_seq);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
grapheme_decode_utf8(next_seq.data(), next_seq.size(), &cur_cp);
|
||||||
|
if (grapheme_is_character_break(prev_cp, cur_cp, &state)) {
|
||||||
|
enqueue_bytes(next_seq);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out += next_seq;
|
||||||
|
prev_cp = cur_cp;
|
||||||
|
}
|
||||||
|
return KeyEvent::ReadResult::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent::ReadResult IO::read_bracketed_paste(std::string &out) {
|
||||||
|
KeyEvent::ReadResult res = KeyEvent::ReadResult::SUCCESS;
|
||||||
|
out.clear();
|
||||||
|
std::string window;
|
||||||
|
while (true) {
|
||||||
|
char c;
|
||||||
|
if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS)
|
||||||
|
return res;
|
||||||
|
window.push_back(c);
|
||||||
|
if (window.size() == 5 && window == "\x1b[201") {
|
||||||
|
char tilde;
|
||||||
|
if ((res = get_next_byte(tilde)) != KeyEvent::ReadResult::SUCCESS)
|
||||||
|
return res;
|
||||||
|
if (tilde == '~')
|
||||||
|
return res;
|
||||||
|
out += window;
|
||||||
|
out.push_back(tilde);
|
||||||
|
window.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (window.size() == 5) {
|
||||||
|
out.push_back(window.front());
|
||||||
|
window.erase(window.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent IO::parse_mouse(const std::string &buf) {
|
||||||
|
KeyEvent ev;
|
||||||
|
if (buf.size() < 6)
|
||||||
|
return ev;
|
||||||
|
uint8_t code = (uint8_t)buf[3] - 32;
|
||||||
|
uint8_t button = code & 0x03;
|
||||||
|
if (button != 0 && button != 3)
|
||||||
|
return ev;
|
||||||
|
ev.type = KeyEvent::KeyType::MOUSE;
|
||||||
|
ev.mouse_state = (button == 3) ? KeyEvent::MouseState::RELEASE
|
||||||
|
: KeyEvent::MouseState::PRESS;
|
||||||
|
ev.mouse_x = (uint8_t)buf[4] - 33;
|
||||||
|
ev.mouse_y = (uint8_t)buf[5] - 33;
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent IO::parse_escape(const std::string &buf) {
|
||||||
|
KeyEvent ev;
|
||||||
|
ev.type = KeyEvent::KeyType::SPECIAL;
|
||||||
|
bool has_modifier = buf.size() > 3 && buf[3] == ';';
|
||||||
|
size_t pos;
|
||||||
|
if (!has_modifier) {
|
||||||
|
pos = 2;
|
||||||
|
} else {
|
||||||
|
pos = 5;
|
||||||
|
switch (buf.size() > 4 ? buf[4] : 0) {
|
||||||
|
case '2':
|
||||||
|
ev.modifier = KeyEvent::Modifier::SHIFT;
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
ev.modifier = KeyEvent::Modifier::ALT;
|
||||||
|
break;
|
||||||
|
case '5':
|
||||||
|
ev.modifier = KeyEvent::Modifier::CTRL;
|
||||||
|
break;
|
||||||
|
case '7':
|
||||||
|
ev.modifier = KeyEvent::Modifier::CTRL_ALT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ev.modifier = KeyEvent::Modifier::NONE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char key = pos < buf.size() ? buf[pos] : 0;
|
||||||
|
switch (key) {
|
||||||
|
case 'A':
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::UP;
|
||||||
|
break;
|
||||||
|
case 'B':
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::DOWN;
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::RIGHT;
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::LEFT;
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::DELETE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::UNKNOWN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent IO::read_key() {
|
||||||
|
while (true) {
|
||||||
|
std::string buf;
|
||||||
|
auto res = read_next_unit(buf);
|
||||||
|
KeyEvent ev;
|
||||||
|
switch (res) {
|
||||||
|
case KeyEvent::ReadResult::EOF_:
|
||||||
|
ev.type = KeyEvent::KeyType::EOF_;
|
||||||
|
return ev;
|
||||||
|
case KeyEvent::ReadResult::RESIZE:
|
||||||
|
resized.store(false);
|
||||||
|
ev.type = KeyEvent::KeyType::RESIZE;
|
||||||
|
return ev;
|
||||||
|
case KeyEvent::ReadResult::SUCCESS:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (buf.size() >= 6 && buf[0] == '\x1b' && buf[1] == '[' && buf.compare(2, 4, "200~") == 0) {
|
||||||
|
std::string pasted;
|
||||||
|
switch (read_bracketed_paste(pasted)) {
|
||||||
|
case KeyEvent::ReadResult::SUCCESS:
|
||||||
|
ev.type = KeyEvent::KeyType::PASTE;
|
||||||
|
ev.text = std::move(pasted);
|
||||||
|
break;
|
||||||
|
case KeyEvent::ReadResult::EOF_:
|
||||||
|
ev.type = KeyEvent::KeyType::EOF_;
|
||||||
|
break;
|
||||||
|
case KeyEvent::ReadResult::RESIZE:
|
||||||
|
resized.store(false);
|
||||||
|
ev.type = KeyEvent::KeyType::RESIZE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
if (buf.size() >= 3 && buf[0] == '\x1b' && buf[1] == '[' && buf[2] == 'M') {
|
||||||
|
ev = parse_mouse(buf);
|
||||||
|
if (ev.type == KeyEvent::KeyType::EOF_)
|
||||||
|
continue;
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
if (buf.size() >= 2 && buf[0] == '\x1b' && buf[1] == '[')
|
||||||
|
return parse_escape(buf);
|
||||||
|
ev.type = KeyEvent::KeyType::CHAR;
|
||||||
|
ev.modifier = KeyEvent::Modifier::NONE;
|
||||||
|
if (buf.size() == 1) {
|
||||||
|
uint8_t c = (uint8_t)buf[0];
|
||||||
|
if (c >= 1 && c <= 26 && c != '\t' && c != '\n' && c != '\r' && c != '\x08') {
|
||||||
|
ev.modifier = KeyEvent::Modifier::CTRL;
|
||||||
|
ev.text = 'a' + c - 1;
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buf.size() == 2 && (uint8_t)buf[0] == 0x1B) {
|
||||||
|
uint8_t c = (uint8_t)buf[1];
|
||||||
|
if (c >= 1 && c <= 26 && c != '\t' && c != '\n' && c != '\r' && c != '\x08') {
|
||||||
|
ev.modifier = KeyEvent::Modifier::CTRL_ALT;
|
||||||
|
ev.text = 'a' + c - 1;
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
ev.modifier = KeyEvent::Modifier::ALT;
|
||||||
|
ev.text = buf.substr(1);
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
ev.text = std::move(buf);
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::io
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
#include "internal/io/io.h"
|
||||||
|
|
||||||
|
namespace bed::internal::io {
|
||||||
|
termios IO::orig_termios{};
|
||||||
|
termios IO::raw_termios{};
|
||||||
|
bool IO::cleaned = true;
|
||||||
|
volatile std::atomic_bool IO::resized(false);
|
||||||
|
|
||||||
|
IO::IO() {
|
||||||
|
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
|
||||||
|
throw fatal_error("Can't get terminal state.", 1);
|
||||||
|
struct sigaction sa{};
|
||||||
|
sa.sa_handler = handle_sigwinch;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
if (sigaction(SIGWINCH, &sa, nullptr) == -1)
|
||||||
|
throw fatal_error("Can't install SIGWINCH handler.", 1);
|
||||||
|
raw_termios = orig_termios;
|
||||||
|
raw_termios.c_iflag &= ~(BRKINT | ISTRIP | IXON);
|
||||||
|
raw_termios.c_cflag |= (CS8);
|
||||||
|
raw_termios.c_lflag &= ~(ECHO | ICANON | ISIG);
|
||||||
|
raw_termios.c_cc[VMIN] = 1;
|
||||||
|
raw_termios.c_cc[VTIME] = 0;
|
||||||
|
enable_raw();
|
||||||
|
atexit(cleanup);
|
||||||
|
}
|
||||||
|
|
||||||
|
IO::~IO() {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::enable_mouse() {
|
||||||
|
const char *seq = "\x1b[?1000h";
|
||||||
|
write_all(STDOUT_FILENO, seq, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::disable_mouse() {
|
||||||
|
const char *seq = "\x1b[?1000l";
|
||||||
|
write_all(STDOUT_FILENO, seq, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint16_t, uint16_t> IO::terminal_size() {
|
||||||
|
struct winsize ws{};
|
||||||
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
|
||||||
|
throw fatal_error("Can't get terminal size.", 1);
|
||||||
|
return {ws.ws_row, ws.ws_col};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint16_t, uint16_t> IO::cursor_position() {
|
||||||
|
write_all(STDOUT_FILENO, "\x1b[6n", 4);
|
||||||
|
std::string response;
|
||||||
|
char c;
|
||||||
|
if (read(STDIN_FILENO, &c, 1) != 1 || c != '\x1b')
|
||||||
|
throw fatal_error("Invalid cursor position response.", 1);
|
||||||
|
if (read(STDIN_FILENO, &c, 1) != 1 || c != '[')
|
||||||
|
throw fatal_error("Invalid cursor position response.", 1);
|
||||||
|
while (true) {
|
||||||
|
if (read(STDIN_FILENO, &c, 1) != 1)
|
||||||
|
throw fatal_error("Invalid cursor position response.", 1);
|
||||||
|
if (c == 'R')
|
||||||
|
break;
|
||||||
|
response += c;
|
||||||
|
}
|
||||||
|
unsigned row;
|
||||||
|
unsigned col;
|
||||||
|
if (sscanf(response.c_str(), "%u;%u", &row, &col) != 2)
|
||||||
|
throw fatal_error("Invalid cursor position response.", 1);
|
||||||
|
return {(uint16_t)row, (uint16_t)col};
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::enable_raw() {
|
||||||
|
if (!cleaned)
|
||||||
|
return;
|
||||||
|
std::string os = "\x1b[?2004h";
|
||||||
|
write_all(STDOUT_FILENO, os.c_str(), os.size());
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_termios) == -1)
|
||||||
|
throw fatal_error("Can't set raw terminal state.", 1);
|
||||||
|
cleaned = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::cleanup() {
|
||||||
|
if (cleaned)
|
||||||
|
return;
|
||||||
|
std::string os = "\x1b[?1000l\x1b[?2004l";
|
||||||
|
write_all(STDOUT_FILENO, os.c_str(), os.size());
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1)
|
||||||
|
perror("Can't clean up terminal.");
|
||||||
|
cleaned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::handle_sigwinch(int) {
|
||||||
|
resized.store(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::move_cursor(uint16_t row, uint16_t col) {
|
||||||
|
char buf[32];
|
||||||
|
int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col);
|
||||||
|
write_all(STDOUT_FILENO, buf, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::write(const char *buf, uint64_t n) {
|
||||||
|
write_all(STDOUT_FILENO, buf, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::write(std::string_view s) {
|
||||||
|
write_all(STDOUT_FILENO, s.data(), s.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::write_line(std::string_view s) {
|
||||||
|
write(s);
|
||||||
|
write("\n", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::run_pty(const std::string &cmd) {
|
||||||
|
int master_fd = -1;
|
||||||
|
struct winsize ws{};
|
||||||
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
|
||||||
|
throw fatal_error("Can't get terminal size.", 1);
|
||||||
|
pid_t pid = forkpty(
|
||||||
|
&master_fd,
|
||||||
|
nullptr,
|
||||||
|
&orig_termios,
|
||||||
|
&ws
|
||||||
|
);
|
||||||
|
if (pid == -1)
|
||||||
|
throw fatal_error("Can't create PTY.", 1);
|
||||||
|
if (pid == 0) {
|
||||||
|
const char *shell = getenv("BED_SHELL");
|
||||||
|
if (!shell || !*shell)
|
||||||
|
shell = getenv("SHELL");
|
||||||
|
if (!shell || !*shell)
|
||||||
|
shell = "/bin/sh";
|
||||||
|
execl(shell, shell, "-i", "-c", cmd.c_str(), (char *)nullptr);
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
struct pollfd fds[2];
|
||||||
|
while (true) {
|
||||||
|
fds[0].fd = STDIN_FILENO;
|
||||||
|
fds[0].events = POLLIN;
|
||||||
|
fds[1].fd = master_fd;
|
||||||
|
fds[1].events = POLLIN;
|
||||||
|
int rc = poll(fds, 2, -1);
|
||||||
|
if (rc == -1) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (resized.exchange(false)) {
|
||||||
|
struct winsize new_ws{};
|
||||||
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &new_ws) == 0)
|
||||||
|
ioctl(master_fd, TIOCSWINSZ, &new_ws);
|
||||||
|
}
|
||||||
|
if (fds[0].revents & POLLIN) {
|
||||||
|
char buf[4096];
|
||||||
|
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
|
||||||
|
if (n > 0)
|
||||||
|
write_all(master_fd, buf, n);
|
||||||
|
else if (n == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (fds[1].revents & POLLIN) {
|
||||||
|
char buf[8192];
|
||||||
|
ssize_t n = read(master_fd, buf, sizeof(buf));
|
||||||
|
if (n > 0)
|
||||||
|
write_all(STDOUT_FILENO, buf, n);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
|
||||||
|
break;
|
||||||
|
if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
close(master_fd);
|
||||||
|
int status;
|
||||||
|
while (waitpid(pid, &status, 0) == -1)
|
||||||
|
if (errno != EINTR)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::io
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/parser/parser.h"
|
||||||
|
|
||||||
|
namespace bed::internal::parser {
|
||||||
|
void Parser::locator(AddressPromise &addr) {
|
||||||
|
addr.base = AddressPromise::None{};
|
||||||
|
switch (peek()) {
|
||||||
|
case '.':
|
||||||
|
advance();
|
||||||
|
addr.base = AddressPromise::Current{};
|
||||||
|
break;
|
||||||
|
case '$':
|
||||||
|
advance();
|
||||||
|
addr.base = AddressPromise::Last{};
|
||||||
|
break;
|
||||||
|
case '%':
|
||||||
|
advance();
|
||||||
|
addr.base = AddressPromise::LastRange{};
|
||||||
|
break;
|
||||||
|
case '[':
|
||||||
|
advance();
|
||||||
|
addr.base = AddressPromise::Block{Direction::Backward};
|
||||||
|
break;
|
||||||
|
case ']':
|
||||||
|
advance();
|
||||||
|
addr.base = AddressPromise::Block{Direction::Forward};
|
||||||
|
break;
|
||||||
|
case '^':
|
||||||
|
advance();
|
||||||
|
addr.base = AddressPromise::Diagnostic{Direction::Backward};
|
||||||
|
break;
|
||||||
|
case '~':
|
||||||
|
advance();
|
||||||
|
addr.base = AddressPromise::Diagnostic{Direction::Forward};
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
advance();
|
||||||
|
if (('a' <= peek() && peek() <= 'z')
|
||||||
|
|| ('A' <= peek() && peek() <= 'Z'))
|
||||||
|
addr.base = AddressPromise::Mark{peek()};
|
||||||
|
else
|
||||||
|
throw ed_error("Valid mark needed after \'");
|
||||||
|
advance();
|
||||||
|
break;
|
||||||
|
case '{': {
|
||||||
|
advance();
|
||||||
|
uint16_t j = 0;
|
||||||
|
std::string func;
|
||||||
|
std::string arg;
|
||||||
|
while (peek(j) != '}') {
|
||||||
|
if (peek(j) == '\0')
|
||||||
|
throw ed_error("Scripted address not terminated");
|
||||||
|
if (peek(j) == '\\')
|
||||||
|
++j;
|
||||||
|
if (peek(j) == ':') {
|
||||||
|
func = peek_str(j);
|
||||||
|
advance(j + 1);
|
||||||
|
j = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
if (func.size())
|
||||||
|
arg = peek_str(j);
|
||||||
|
else
|
||||||
|
func = peek_str(j);
|
||||||
|
addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)};
|
||||||
|
advance(j + 1);
|
||||||
|
} break;
|
||||||
|
case '/': {
|
||||||
|
advance();
|
||||||
|
uint64_t j = 0;
|
||||||
|
while (true) {
|
||||||
|
if (peek(j) == '\0')
|
||||||
|
break;
|
||||||
|
if (peek(j) == '/')
|
||||||
|
break;
|
||||||
|
else if (peek(j) == '\\')
|
||||||
|
j += 2;
|
||||||
|
else if (peek(j) == '[')
|
||||||
|
while (peek(j) != '\0' && cmd[i] != ']')
|
||||||
|
j++;
|
||||||
|
else
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
addr.base = AddressPromise::Regex(
|
||||||
|
Direction::Forward,
|
||||||
|
std::string(peek_str(j))
|
||||||
|
);
|
||||||
|
advance(j + 1);
|
||||||
|
} break;
|
||||||
|
case '?': {
|
||||||
|
advance();
|
||||||
|
uint16_t j = 0;
|
||||||
|
while (true) {
|
||||||
|
if (peek(j) == '\0')
|
||||||
|
break;
|
||||||
|
if (peek(j) == '?')
|
||||||
|
break;
|
||||||
|
else if (peek(j) == '\\')
|
||||||
|
j += 2;
|
||||||
|
else if (peek(j) == '[')
|
||||||
|
while (peek(j) != '\0' && cmd[i] != ']')
|
||||||
|
j++;
|
||||||
|
else
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
addr.base = AddressPromise::Regex(
|
||||||
|
Direction::Backward,
|
||||||
|
std::string(peek_str(j))
|
||||||
|
);
|
||||||
|
advance(j + 1);
|
||||||
|
} break;
|
||||||
|
case '<': {
|
||||||
|
advance();
|
||||||
|
uint16_t j = 0;
|
||||||
|
while (peek(j) != '>'
|
||||||
|
&& peek(j) != '<'
|
||||||
|
&& peek(j) != '\0')
|
||||||
|
j++;
|
||||||
|
switch (peek(j)) {
|
||||||
|
case '\0':
|
||||||
|
case '>':
|
||||||
|
addr.base = AddressPromise::SymbolDefinition{
|
||||||
|
std::string(peek_str(j))
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
addr.base = AddressPromise::SymbolReference{
|
||||||
|
Direction::Backward,
|
||||||
|
std::string(peek_str(j))
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
advance(j + 1);
|
||||||
|
} break;
|
||||||
|
case '>': {
|
||||||
|
advance();
|
||||||
|
uint16_t j = 0;
|
||||||
|
while (peek(j) != '>' && peek(j) != '\0')
|
||||||
|
j++;
|
||||||
|
switch (peek(j)) {
|
||||||
|
case '\0':
|
||||||
|
throw ed_error("Unterminated symbol reference addressing.");
|
||||||
|
case '>':
|
||||||
|
addr.base = AddressPromise::SymbolReference{
|
||||||
|
Direction::Forward,
|
||||||
|
std::string(peek_str(j))
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
advance(j + 1);
|
||||||
|
} break;
|
||||||
|
case '+': {
|
||||||
|
advance();
|
||||||
|
addr.base = AddressPromise::Current{};
|
||||||
|
uint16_t j = 0;
|
||||||
|
uint64_t num = 0;
|
||||||
|
while ('0' <= peek(j) && peek(j) <= '9') {
|
||||||
|
num = num * 10 + (peek(j) - '0');
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
if (j == 0)
|
||||||
|
num = 1;
|
||||||
|
advance(j);
|
||||||
|
addr.offset += num;
|
||||||
|
} break;
|
||||||
|
case '-': {
|
||||||
|
advance();
|
||||||
|
addr.base = AddressPromise::Current{};
|
||||||
|
uint16_t j = 0;
|
||||||
|
uint64_t num = 0;
|
||||||
|
while ('0' <= peek(j) && peek(j) <= '9') {
|
||||||
|
num = num * 10 + (peek(j) - '0');
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
if (j == 0)
|
||||||
|
num = 1;
|
||||||
|
advance(j);
|
||||||
|
addr.offset -= num;
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
if ('0' <= peek() && peek() <= '9') {
|
||||||
|
uint64_t num = 0;
|
||||||
|
while ('0' <= peek() && peek() <= '9') {
|
||||||
|
num = num * 10 + (peek() - '0');
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
addr.base = AddressPromise::Number{num};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t Parser::offset() {
|
||||||
|
int64_t offset = 0;
|
||||||
|
while (peek() == '+' || peek() == '-'
|
||||||
|
|| ('0' <= peek() && peek() <= '9')) {
|
||||||
|
bool positive = peek() != '-';
|
||||||
|
if (peek() == '+' || peek() == '-')
|
||||||
|
advance();
|
||||||
|
uint16_t j = 0;
|
||||||
|
int64_t num = 0;
|
||||||
|
while ('0' <= peek(j) && peek(j) <= '9')
|
||||||
|
num = num * 10 + (peek(j++) - '0');
|
||||||
|
if (j == 0)
|
||||||
|
num = 1;
|
||||||
|
advance(j);
|
||||||
|
offset += positive ? num : -num;
|
||||||
|
skip_ws();
|
||||||
|
}
|
||||||
|
skip_ws();
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::address(AddressPromise &addr) {
|
||||||
|
if (peek() == ':') {
|
||||||
|
advance();
|
||||||
|
uint16_t j = 0;
|
||||||
|
while (peek(j) != ':' && peek(j) != '\0')
|
||||||
|
j++;
|
||||||
|
addr.bufname = peek_str(j);
|
||||||
|
advance(j);
|
||||||
|
if (peek() == ':')
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
skip_ws();
|
||||||
|
if (peek() == '\0')
|
||||||
|
return;
|
||||||
|
locator(addr);
|
||||||
|
skip_ws();
|
||||||
|
addr.offset += offset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::addresses(std::vector<AddressPromise> &addresses) {
|
||||||
|
skip_ws();
|
||||||
|
addresses.push_back({});
|
||||||
|
auto *addr = &addresses.back();
|
||||||
|
address(*addr);
|
||||||
|
while (peek() == ',' || peek() == ';') {
|
||||||
|
addr->jumping = peek() == ';';
|
||||||
|
advance();
|
||||||
|
skip_ws();
|
||||||
|
addresses.push_back({});
|
||||||
|
addr = &addresses.back();
|
||||||
|
address(*addr);
|
||||||
|
}
|
||||||
|
if (addresses.size() == 1
|
||||||
|
&& addr->offset == 0 && !addr->bufname.has_value()
|
||||||
|
&& std::holds_alternative<AddressPromise::None>(addr->base))
|
||||||
|
addresses.pop_back();
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::parser
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/parser/parser.h"
|
||||||
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
|
namespace bed::internal::parser {
|
||||||
|
buffer::Line AddressPromise::resolve(BEd &ctx) {
|
||||||
|
buffer::Line result = std::visit(
|
||||||
|
[&](auto const &addr) -> buffer::Line {
|
||||||
|
if (!bufname.has_value() || bufname->empty())
|
||||||
|
throw ed_error("Buffer name can't be empty.");
|
||||||
|
buffer::Line line = {*bufname, 0};
|
||||||
|
auto &buf = ctx.buffer(line.buffername);
|
||||||
|
using T = std::decay_t<decltype(addr)>;
|
||||||
|
if constexpr (std::is_same_v<T, None>) {
|
||||||
|
if (line.buffername == ctx.current().buffername)
|
||||||
|
line.number = ctx.current().number;
|
||||||
|
else
|
||||||
|
line.number = buf.lines();
|
||||||
|
} else if constexpr (std::is_same_v<T, Current>) {
|
||||||
|
if (line.buffername == ctx.current().buffername)
|
||||||
|
line.number = ctx.current().number;
|
||||||
|
else
|
||||||
|
line.number = buf.lines();
|
||||||
|
} else if constexpr (std::is_same_v<T, Last>) {
|
||||||
|
line.number = buf.lines();
|
||||||
|
} else if constexpr (std::is_same_v<T, Number>) {
|
||||||
|
line.number = addr.i;
|
||||||
|
} else if constexpr (std::is_same_v<T, Mark>) {
|
||||||
|
line = ctx.marks.get(addr.m);
|
||||||
|
if (line.number == UINT64_MAX)
|
||||||
|
throw ed_error("Mark not set.");
|
||||||
|
} else if constexpr (std::is_same_v<T, Regex>) {
|
||||||
|
if (line.buffername == ctx.current().buffername)
|
||||||
|
line.number = ctx.current().number;
|
||||||
|
else
|
||||||
|
line.number = buf.lines();
|
||||||
|
if (buf.lines() > 0 && line.number == 0)
|
||||||
|
line.number = 1;
|
||||||
|
if (line.number == 0)
|
||||||
|
throw ed_error("Can't search empty buffer.");
|
||||||
|
std::string_view re = addr.re;
|
||||||
|
if (re.size() == 0)
|
||||||
|
re = ctx.last_regex;
|
||||||
|
if (re.size() == 0)
|
||||||
|
throw ed_error("No regex given.");
|
||||||
|
if (addr.dir == Direction::Forward)
|
||||||
|
line.number = buf.find_next(re, line.number);
|
||||||
|
else
|
||||||
|
line.number = buf.find_prev(re, line.number);
|
||||||
|
ctx.last_regex = re;
|
||||||
|
} else if constexpr (std::is_same_v<T, Block>) {
|
||||||
|
if (line.buffername == ctx.current().buffername)
|
||||||
|
line.number = ctx.current().number;
|
||||||
|
else
|
||||||
|
line.number = buf.lines();
|
||||||
|
if (buf.lines() > 0 && line.number == 0)
|
||||||
|
line.number = 1;
|
||||||
|
if (addr.dir == Direction::Forward)
|
||||||
|
line.number = buf.next_closing(line.number);
|
||||||
|
else
|
||||||
|
line.number = buf.prev_closing(line.number);
|
||||||
|
} else {
|
||||||
|
throw ed_error("Unhandled address given.");
|
||||||
|
}
|
||||||
|
if (offset < 0 && line.number < (uint64_t)-offset)
|
||||||
|
throw ed_error("Can't have negative addresses");
|
||||||
|
line.number += offset;
|
||||||
|
if (line.number > ctx.buffer(line.buffername).lines())
|
||||||
|
throw ed_error("Line number too high.");
|
||||||
|
return line;
|
||||||
|
},
|
||||||
|
base
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<AddressPromise> &list) {
|
||||||
|
std::string bufname = ctx.current().buffername;
|
||||||
|
bool prev_given = false;
|
||||||
|
bool prev_set = false;
|
||||||
|
AddressPromise prev;
|
||||||
|
for (std::size_t idx = 0; idx < list.size(); idx++) {
|
||||||
|
AddressPromise &curr = list[idx];
|
||||||
|
if (curr.bufname.has_value()) {
|
||||||
|
if (curr.bufname->empty()) {
|
||||||
|
bufname = ctx.current().buffername;
|
||||||
|
curr.bufname = bufname;
|
||||||
|
} else {
|
||||||
|
bufname = *curr.bufname;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
curr.bufname = bufname;
|
||||||
|
}
|
||||||
|
bool is_final = idx + 1 == list.size();
|
||||||
|
if (!is_final) {
|
||||||
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
|
if (!curr.jumping)
|
||||||
|
curr.base = Number(1);
|
||||||
|
else
|
||||||
|
curr.base = Current();
|
||||||
|
curr.offset = 0;
|
||||||
|
prev_given = false;
|
||||||
|
} else if (std::holds_alternative<LastRange>(curr.base)) {
|
||||||
|
curr.bufname = ctx.prev().buffername;
|
||||||
|
curr.base = Number(ctx.prev().end);
|
||||||
|
prev_given = true;
|
||||||
|
} else {
|
||||||
|
prev_given = true;
|
||||||
|
}
|
||||||
|
if (curr.jumping) {
|
||||||
|
buffer::Line resolved = curr.resolve(ctx);
|
||||||
|
ctx.current() = resolved;
|
||||||
|
curr.bufname = resolved.buffername;
|
||||||
|
curr.base = Number(resolved.number);
|
||||||
|
curr.offset = 0;
|
||||||
|
}
|
||||||
|
prev = curr;
|
||||||
|
prev_set = true;
|
||||||
|
} else {
|
||||||
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
|
if (prev_set) {
|
||||||
|
if (prev_given) {
|
||||||
|
curr.base = prev.base;
|
||||||
|
curr.offset = prev.offset;
|
||||||
|
} else {
|
||||||
|
curr.base = Last();
|
||||||
|
curr.offset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (std::holds_alternative<LastRange>(curr.base)) {
|
||||||
|
curr.bufname = ctx.prev().buffername;
|
||||||
|
curr.base = Number(ctx.prev().end);
|
||||||
|
}
|
||||||
|
return curr.resolve(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<AddressPromise> &list) {
|
||||||
|
std::string bufname = ctx.current().buffername;
|
||||||
|
bool prev_given = false;
|
||||||
|
bool prev_set = false;
|
||||||
|
AddressPromise prev;
|
||||||
|
for (std::size_t idx = 0; idx < list.size(); idx++) {
|
||||||
|
AddressPromise &curr = list[idx];
|
||||||
|
if (curr.bufname.has_value()) {
|
||||||
|
if (curr.bufname->empty()) {
|
||||||
|
bufname = ctx.current().buffername;
|
||||||
|
curr.bufname = bufname;
|
||||||
|
} else {
|
||||||
|
bufname = *curr.bufname;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
curr.bufname = bufname;
|
||||||
|
}
|
||||||
|
bool is_final = idx + 1 == list.size();
|
||||||
|
if (!is_final) {
|
||||||
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
|
if (!curr.jumping)
|
||||||
|
curr.base = Number(1);
|
||||||
|
else
|
||||||
|
curr.base = Current();
|
||||||
|
curr.offset = 0;
|
||||||
|
prev_given = false;
|
||||||
|
} else if (std::holds_alternative<LastRange>(curr.base)) {
|
||||||
|
curr.bufname = ctx.prev().buffername;
|
||||||
|
curr.base = Number(ctx.prev().end);
|
||||||
|
prev_given = true;
|
||||||
|
} else {
|
||||||
|
prev_given = true;
|
||||||
|
}
|
||||||
|
if (curr.jumping) {
|
||||||
|
buffer::Line resolved = curr.resolve(ctx);
|
||||||
|
ctx.current() = resolved;
|
||||||
|
curr.bufname = resolved.buffername;
|
||||||
|
curr.base = Number(resolved.number);
|
||||||
|
curr.offset = 0;
|
||||||
|
}
|
||||||
|
prev = curr;
|
||||||
|
prev_set = true;
|
||||||
|
} else {
|
||||||
|
if (std::holds_alternative<None>(curr.base)) {
|
||||||
|
if (prev_set) {
|
||||||
|
if (prev_given) {
|
||||||
|
curr.base = prev.base;
|
||||||
|
curr.offset = prev.offset;
|
||||||
|
} else {
|
||||||
|
curr.base = Last();
|
||||||
|
curr.offset = 0;
|
||||||
|
}
|
||||||
|
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
|
||||||
|
}
|
||||||
|
} else if (std::holds_alternative<LastRange>(curr.base)) {
|
||||||
|
auto previous = ctx.prev();
|
||||||
|
auto &buf = ctx.buffer(previous.buffername);
|
||||||
|
if (curr.offset < 0 && previous.start < (uint64_t)-curr.offset)
|
||||||
|
throw ed_error("Can't have negative addresses");
|
||||||
|
previous.start += curr.offset;
|
||||||
|
if (previous.start > buf.lines())
|
||||||
|
throw ed_error("Line number too high.");
|
||||||
|
if (curr.offset < 0 && previous.end < (uint64_t)-curr.offset)
|
||||||
|
throw ed_error("Can't have negative addresses");
|
||||||
|
previous.end += curr.offset;
|
||||||
|
if (previous.end > buf.lines())
|
||||||
|
throw ed_error("Line number too high.");
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
if (prev_given)
|
||||||
|
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
|
||||||
|
buffer::Line only = curr.resolve(ctx);
|
||||||
|
return buffer::Range(only, only);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::parser
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/parser/parser.h"
|
||||||
|
|
||||||
|
namespace bed::internal::parser {
|
||||||
|
void Parser::operation() {
|
||||||
|
if (peek() == '\0') {
|
||||||
|
command->function = &bed.no_op;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t len = bed.functions.longest_match(peek_str());
|
||||||
|
if (len == 0)
|
||||||
|
throw ed_error("Function not found.");
|
||||||
|
functions::Function *function = bed.functions.get_ptr(peek_str(len));
|
||||||
|
advance(len);
|
||||||
|
command->function = function;
|
||||||
|
char suffix = '\0';
|
||||||
|
switch (command->function->argument_kind) {
|
||||||
|
case functions::Function::ArgumentKind::None:
|
||||||
|
break;
|
||||||
|
case functions::Function::ArgumentKind::Number:
|
||||||
|
skip_ws();
|
||||||
|
command->argument = offset();
|
||||||
|
break;
|
||||||
|
case functions::Function::ArgumentKind::Mark:
|
||||||
|
if (('a' <= peek() && peek() <= 'z')
|
||||||
|
|| ('A' <= peek() && peek() <= 'Z'))
|
||||||
|
command->argument = peek();
|
||||||
|
else
|
||||||
|
throw ed_error("Valid mark needed.");
|
||||||
|
advance();
|
||||||
|
break;
|
||||||
|
case functions::Function::ArgumentKind::Any:
|
||||||
|
command->argument = std::string(peek_str());
|
||||||
|
advance(peek_str().size());
|
||||||
|
break;
|
||||||
|
case functions::Function::ArgumentKind::Global: {
|
||||||
|
char delim;
|
||||||
|
std::string val;
|
||||||
|
switch (peek()) {
|
||||||
|
case '\0':
|
||||||
|
throw ed_error("Command needs a delimited value.");
|
||||||
|
case '{': {
|
||||||
|
advance();
|
||||||
|
delim = '}';
|
||||||
|
uint16_t j = 0;
|
||||||
|
while (peek(j) != '}' && peek(j) != '\0') {
|
||||||
|
if (peek(j) == '\\')
|
||||||
|
j++;
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
switch (peek(j)) {
|
||||||
|
case '\0':
|
||||||
|
throw ed_error("Unterminated {");
|
||||||
|
case '}':
|
||||||
|
val = peek_str(j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
advance(j + 1);
|
||||||
|
} break;
|
||||||
|
case '<': {
|
||||||
|
advance();
|
||||||
|
delim = '<';
|
||||||
|
uint16_t j = 0;
|
||||||
|
while (peek(j) != '>' || peek(j) != '\0') {
|
||||||
|
if (peek(j) == '\\')
|
||||||
|
j++;
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
switch (peek(j)) {
|
||||||
|
case '\0':
|
||||||
|
throw ed_error("Unterminated <");
|
||||||
|
case '>':
|
||||||
|
val = peek_str(j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
advance(j + 1);
|
||||||
|
} break;
|
||||||
|
case '^':
|
||||||
|
case '~':
|
||||||
|
advance();
|
||||||
|
delim = '^';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
delim = peek();
|
||||||
|
advance();
|
||||||
|
uint64_t j = 0;
|
||||||
|
while (true) {
|
||||||
|
if (peek(j) == '\0')
|
||||||
|
break;
|
||||||
|
if (peek(j) == delim)
|
||||||
|
break;
|
||||||
|
else if (peek(j) == '\\')
|
||||||
|
j += 2;
|
||||||
|
else if (peek(j) == '[')
|
||||||
|
while (peek(j) != '\0' && cmd[i] != ']')
|
||||||
|
j++;
|
||||||
|
else
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
val = peek_str(j);
|
||||||
|
advance(j + 1);
|
||||||
|
}
|
||||||
|
command->argument = functions::Function::GlobalArg(delim, std::move(val));
|
||||||
|
} break;
|
||||||
|
case functions::Function::ArgumentKind::File:
|
||||||
|
if (!(peek() == '\t' || peek() == ' ' || peek() == '\0'))
|
||||||
|
throw ed_error("Incorrect file input usage.");
|
||||||
|
skip_ws();
|
||||||
|
switch (peek()) {
|
||||||
|
case '!':
|
||||||
|
advance();
|
||||||
|
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
||||||
|
advance(peek_str().size());
|
||||||
|
break;
|
||||||
|
case '\0':
|
||||||
|
command->argument = std::monostate();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
command->argument = std::filesystem::path(peek_str());
|
||||||
|
advance(peek_str().size());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case functions::Function::ArgumentKind::Line:
|
||||||
|
command->argument = buffer::Line();
|
||||||
|
addresses(command->argument_addresses);
|
||||||
|
break;
|
||||||
|
case functions::Function::ArgumentKind::Range:
|
||||||
|
command->argument = buffer::Range();
|
||||||
|
addresses(command->argument_addresses);
|
||||||
|
break;
|
||||||
|
case functions::Function::ArgumentKind::Regex: {
|
||||||
|
char delim = peek();
|
||||||
|
if (delim == '\0')
|
||||||
|
throw ed_error("regex expected");
|
||||||
|
advance();
|
||||||
|
uint16_t j = 0;
|
||||||
|
while (true) {
|
||||||
|
if (peek(j) == '\0')
|
||||||
|
throw ed_error("Unterminated regex");
|
||||||
|
if (peek(j) == delim)
|
||||||
|
break;
|
||||||
|
else if (peek(j) == '\\')
|
||||||
|
j += 2;
|
||||||
|
else if (peek(j) == '[')
|
||||||
|
while (peek(j) != '\0' && peek(j) != ']')
|
||||||
|
j++;
|
||||||
|
else
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
std::string expression(peek_str(j));
|
||||||
|
advance(j + 1);
|
||||||
|
j = 0;
|
||||||
|
while (true) {
|
||||||
|
if (peek(j) == '\0')
|
||||||
|
break;
|
||||||
|
if (peek(j) == delim)
|
||||||
|
break;
|
||||||
|
else if (peek(j) == '\\')
|
||||||
|
j += 2;
|
||||||
|
else if (peek(j) == '[')
|
||||||
|
while (peek(j) != '\0' && peek(j) != ']')
|
||||||
|
j++;
|
||||||
|
else
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
std::string replacement(peek_str(j));
|
||||||
|
if (peek(j) != '\0') {
|
||||||
|
advance(j + 1);
|
||||||
|
} else {
|
||||||
|
advance(j);
|
||||||
|
}
|
||||||
|
std::string options;
|
||||||
|
if (peek() != '\0') {
|
||||||
|
options = std::string(peek_str());
|
||||||
|
advance(peek_str().size());
|
||||||
|
}
|
||||||
|
std::erase_if(options, [&](char c) {
|
||||||
|
if (bed.suffixes[c - 'a'].has_value()) {
|
||||||
|
suffix = c;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
command->argument = functions::Function::RegexArg(expression, replacement, options);
|
||||||
|
} break;
|
||||||
|
case functions::Function::ArgumentKind::Ruby:
|
||||||
|
command->argument = functions::Function::RubyArg(std::string(peek_str()));
|
||||||
|
advance(peek_str().size());
|
||||||
|
break;
|
||||||
|
case functions::Function::ArgumentKind::Shell:
|
||||||
|
command->argument = functions::Function::ShellArg(std::string(peek_str()));
|
||||||
|
advance(peek_str().size());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!suffix) {
|
||||||
|
suffix = peek();
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
if (suffix) {
|
||||||
|
auto &s = bed.suffixes[suffix - 'a'];
|
||||||
|
if (s.has_value())
|
||||||
|
command->suffix = &s.value();
|
||||||
|
else
|
||||||
|
throw ed_error("Invalid suffix.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::parser
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#include "internal/parser/parser.h"
|
||||||
|
#include "bed.h"
|
||||||
|
|
||||||
|
namespace bed::internal::parser {
|
||||||
|
char Parser::peek(uint16_t o) {
|
||||||
|
return i + o < cmd.size() ? cmd[i + o] : '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view Parser::peek_str(uint16_t len) {
|
||||||
|
return cmd.substr(i, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::advance(uint16_t c) {
|
||||||
|
i += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::skip_ws() {
|
||||||
|
while (peek() == ' ' || peek() == '\t')
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::parse() {
|
||||||
|
skip_ws();
|
||||||
|
if (peek() == '@') {
|
||||||
|
advance();
|
||||||
|
command->temp_address = true;
|
||||||
|
} else {
|
||||||
|
command->temp_address = false;
|
||||||
|
}
|
||||||
|
skip_ws();
|
||||||
|
addresses(command->addresses);
|
||||||
|
operation();
|
||||||
|
skip_ws();
|
||||||
|
if (peek() != '\0')
|
||||||
|
throw ed_error("Malformed command");
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser::Parser(
|
||||||
|
std::string_view cmd, BEd &bed, Command *command,
|
||||||
|
std::vector<ui::Token> *tokens, CompletionContext *completion
|
||||||
|
) : bed(bed), cmd(cmd), command(command), tokens(tokens), completion(completion) {
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Command Parser::get_command(std::string_view cmd, BEd &bed) {
|
||||||
|
Command c;
|
||||||
|
std::vector<ui::Token> tokens;
|
||||||
|
CompletionContext completion;
|
||||||
|
Parser p(cmd, bed, &c, &tokens, &completion);
|
||||||
|
p.parse();
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AddressPromise> Parser::get_addresses(std::string_view cmd, BEd &bed) {
|
||||||
|
std::vector<AddressPromise> result;
|
||||||
|
std::vector<ui::Token> tokens;
|
||||||
|
CompletionContext completion;
|
||||||
|
Parser p(cmd, bed, nullptr, &tokens, &completion);
|
||||||
|
p.addresses(result);
|
||||||
|
p.skip_ws();
|
||||||
|
if (p.peek() != '\0')
|
||||||
|
throw ed_error("Malformed address");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::parser
|
||||||
@@ -1,42 +1,6 @@
|
|||||||
#include "internal/syntax/parser.h"
|
#include "internal/syntax/parser.h"
|
||||||
|
|
||||||
namespace bed::internal::syntax {
|
namespace bed::internal::syntax {
|
||||||
void dump_events(ParseState *root) {
|
|
||||||
if (!root)
|
|
||||||
return;
|
|
||||||
uint64_t offset = 0;
|
|
||||||
TreeCursor c(root, 0, &offset);
|
|
||||||
uint64_t line_offset = 0;
|
|
||||||
int depth = 0;
|
|
||||||
while (c.leaf) {
|
|
||||||
auto *leaf = c.leaf;
|
|
||||||
for (uint32_t i = 0; i < leaf->n; ++i) {
|
|
||||||
uint32_t block = leaf->blocks[i];
|
|
||||||
uint32_t pos = block & ParseStateLeaf::LINE_MASK;
|
|
||||||
bool closing = block & ParseStateLeaf::IS_CLOSING;
|
|
||||||
if (closing) {
|
|
||||||
if (depth > 0)
|
|
||||||
--depth;
|
|
||||||
else
|
|
||||||
std::cout << "!! UNMATCHED CLOSE !! ";
|
|
||||||
}
|
|
||||||
std::cout << std::string(static_cast<size_t>(depth) * 2, ' ')
|
|
||||||
<< (closing ? "}" : "{")
|
|
||||||
<< " line " << (line_offset + pos)
|
|
||||||
<< '\n';
|
|
||||||
if (!closing)
|
|
||||||
++depth;
|
|
||||||
}
|
|
||||||
line_offset += leaf->lines();
|
|
||||||
c.next();
|
|
||||||
}
|
|
||||||
if (depth != 0) {
|
|
||||||
std::cout << "!! UNBALANCED: depth = "
|
|
||||||
<< depth
|
|
||||||
<< " !!\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void destroy_tree(ParseState *node, Language &lang) {
|
static void destroy_tree(ParseState *node, Language &lang) {
|
||||||
if (!node)
|
if (!node)
|
||||||
return;
|
return;
|
||||||
@@ -79,7 +43,7 @@ static ParseState *build_tree(std::vector<ParseStateLeaf *> &leaves, size_t begi
|
|||||||
return make_branch(left, right);
|
return make_branch(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::Parser(vase::Vase &vase, uint64_t lines, Language lang)
|
Parser::Parser(vase::Shard *vase, uint64_t lines, Language lang)
|
||||||
: root(nullptr), lang(lang) {
|
: root(nullptr), lang(lang) {
|
||||||
reset(vase, lines, lang);
|
reset(vase, lines, lang);
|
||||||
}
|
}
|
||||||
@@ -88,7 +52,7 @@ Parser::~Parser() {
|
|||||||
destroy_tree(root, lang);
|
destroy_tree(root, lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::reset(vase::Vase &vase, uint64_t lines, Language lang_) {
|
void Parser::reset(vase::Shard *vase, uint64_t lines, Language lang_) {
|
||||||
destroy_tree(root, lang);
|
destroy_tree(root, lang);
|
||||||
root = nullptr;
|
root = nullptr;
|
||||||
if (lines == 0)
|
if (lines == 0)
|
||||||
@@ -158,40 +122,19 @@ ParseState *Parser::join_tree(ParseState *a, ParseState *b) {
|
|||||||
return make_branch(a, b);
|
return make_branch(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::erase(vase::Vase &vase, uint64_t start, uint64_t count) {
|
void Parser::erase(vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
if (count == 0 || !root)
|
begin_edit();
|
||||||
return;
|
erase(start, count);
|
||||||
auto [a, remaining] = split_tree(root, start);
|
end_edit(vase);
|
||||||
auto [waste, b] = split_tree(remaining, count);
|
|
||||||
destroy_tree(waste, lang);
|
|
||||||
root = join_tree(a, b);
|
|
||||||
modify(vase, start, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::insert(vase::Vase &vase, uint64_t start, uint64_t count) {
|
void Parser::insert(vase::Shard *vase, uint64_t start, uint64_t count) {
|
||||||
if (count == 0)
|
begin_edit();
|
||||||
return;
|
insert(start, count);
|
||||||
std::vector<ParseStateLeaf *> leaves;
|
end_edit(vase);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Parser::modify(vase::Vase &vase, uint64_t target, uint64_t count) {
|
void Parser::modify(vase::Shard *vase, uint64_t target, uint64_t count) {
|
||||||
if (count == 0 || !root)
|
if (count == 0 || !root)
|
||||||
return;
|
return;
|
||||||
std::vector<Token> tokens;
|
std::vector<Token> tokens;
|
||||||
@@ -218,7 +161,7 @@ void Parser::modify(vase::Vase &vase, uint64_t target, uint64_t count) {
|
|||||||
c = TreeCursor(root, 0, &offset);
|
c = TreeCursor(root, 0, &offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vase::Iterator it = vase.iterate(at, Direction::Forward);
|
vase::Iterator it(vase, at, Direction::Forward);
|
||||||
uint64_t chunk_start = at;
|
uint64_t chunk_start = at;
|
||||||
uint64_t next_boundary = at + c.leaf->lines();
|
uint64_t next_boundary = at + c.leaf->lines();
|
||||||
c.leaf->n = 0;
|
c.leaf->n = 0;
|
||||||
@@ -255,6 +198,63 @@ void Parser::modify(vase::Vase &vase, uint64_t target, uint64_t count) {
|
|||||||
lang.destroy(state);
|
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) {
|
uint64_t Parser::next_closing(uint64_t line) {
|
||||||
if (!root)
|
if (!root)
|
||||||
return UINT64_MAX;
|
return UINT64_MAX;
|
||||||
@@ -318,13 +318,13 @@ uint64_t Parser::prev_opening(uint64_t line) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Parser::Iterator> Parser::get_hl(vase::Vase &vase, uint64_t target) {
|
std::optional<Parser::Iterator> Parser::get_hl(vase::Shard *vase, uint64_t target) {
|
||||||
if (!root)
|
if (!root)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
return Parser::Iterator(target, this, vase);
|
return Parser::Iterator(target, this, vase);
|
||||||
}
|
}
|
||||||
|
|
||||||
Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Vase &vase) : p(p) {
|
Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Shard *vase) : p(p) {
|
||||||
uint64_t offset;
|
uint64_t offset;
|
||||||
TreeCursor c = TreeCursor(p->root, target, &offset);
|
TreeCursor c = TreeCursor(p->root, target, &offset);
|
||||||
at = target - offset;
|
at = target - offset;
|
||||||
@@ -346,7 +346,7 @@ Parser::Iterator::Iterator(uint64_t target, Parser *p, vase::Vase &vase) : p(p)
|
|||||||
c = TreeCursor(p->root, 0, &offset);
|
c = TreeCursor(p->root, 0, &offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it = vase.iterate(at, Direction::Forward);
|
it = vase::Iterator(vase, at, Direction::Forward);
|
||||||
while (at < target) {
|
while (at < target) {
|
||||||
it->next();
|
it->next();
|
||||||
tokens.clear();
|
tokens.clear();
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
#include "internal/ui/command.h"
|
||||||
|
#include "bed.h"
|
||||||
|
|
||||||
|
namespace bed::internal::ui {
|
||||||
|
/*template <typename F>
|
||||||
|
static void for_each_cluster(std::string_view s, F &&f) {
|
||||||
|
unicode_width_state_t state;
|
||||||
|
unicode_width_init(&state);
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < s.size()) {
|
||||||
|
unsigned char c = static_cast<unsigned char>(s[i]);
|
||||||
|
size_t bytes = 1;
|
||||||
|
int width = 0;
|
||||||
|
if (c < 128) {
|
||||||
|
width = unicode_width_process(&state, c);
|
||||||
|
} else {
|
||||||
|
uint_least32_t cp;
|
||||||
|
size_t decoded = grapheme_decode_utf8(s.data() + i, s.size() - i, &cp);
|
||||||
|
bytes = decoded > 0 ? decoded : 1;
|
||||||
|
width = unicode_width_process(&state, cp);
|
||||||
|
}
|
||||||
|
if (width < 0)
|
||||||
|
width = 0;
|
||||||
|
f(i, bytes, width);
|
||||||
|
i += bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int display_width(std::string_view s) {
|
||||||
|
int w = 0;
|
||||||
|
for_each_cluster(s, [&](size_t, size_t, int cw) { w += cw; });
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t count_clusters(std::string_view s) {
|
||||||
|
uint16_t n = 0;
|
||||||
|
for_each_cluster(s, [&](size_t, size_t, int) { ++n; });
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<uint16_t> wrap_offsets(std::string_view line, uint16_t avail) {
|
||||||
|
std::vector<uint16_t> offsets{0};
|
||||||
|
int col = 0;
|
||||||
|
for_each_cluster(line, [&](uint16_t i, uint16_t, int w) {
|
||||||
|
if (col + w > avail && col > 0) {
|
||||||
|
offsets.push_back(i);
|
||||||
|
col = 0;
|
||||||
|
}
|
||||||
|
col += w;
|
||||||
|
});
|
||||||
|
return offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The word under/before `byte_pos`, split on plain ASCII spaces. Used to
|
||||||
|
// pick what prefix to hand the suggestion trie.
|
||||||
|
// TODO: change to use libgrapheme word break here.
|
||||||
|
static std::string current_word(const std::string &line, size_t byte_pos) {
|
||||||
|
size_t start = (byte_pos == 0) ? std::string::npos : line.rfind(' ', byte_pos - 1);
|
||||||
|
start = (start == std::string::npos) ? 0 : start + 1;
|
||||||
|
if (byte_pos < start)
|
||||||
|
byte_pos = start;
|
||||||
|
return line.substr(start, byte_pos - start);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
CommandIO::CommandIO(BEd &bed) : bed(bed) {
|
||||||
|
if (bed.prompt_mode)
|
||||||
|
prompt = bed.prompt(bed);
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::string, bool> CommandIO::run() {
|
||||||
|
auto [row, col] = bed.io.cursor_position();
|
||||||
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
|
if (row > rows)
|
||||||
|
throw fatal_error("Invalid cursor position.", 1);
|
||||||
|
start = row;
|
||||||
|
height = rows - row + 1;
|
||||||
|
term_width = cols;
|
||||||
|
term_height = rows;
|
||||||
|
redraw();
|
||||||
|
|
||||||
|
io::KeyEvent res;
|
||||||
|
bool running = true;
|
||||||
|
while (running) {
|
||||||
|
res = bed.io.read_key();
|
||||||
|
switch (res.type) {
|
||||||
|
case io::KeyEvent::KeyType::EOF_:
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::KeyType::MOUSE:
|
||||||
|
case io::KeyEvent::KeyType::RESIZE:
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::KeyType::CHAR:
|
||||||
|
switch (res.modifier) {
|
||||||
|
case io::KeyEvent::Modifier::SHIFT:
|
||||||
|
case io::KeyEvent::Modifier::ALT:
|
||||||
|
case io::KeyEvent::Modifier::CTRL_ALT:
|
||||||
|
case io::KeyEvent::Modifier::CTRL:
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::Modifier::NONE:
|
||||||
|
if (res.text[0] == '\b' || res.text[0] == 0x7f) {
|
||||||
|
if (cursor > 0)
|
||||||
|
cmd.erase(--cursor, 1);
|
||||||
|
} else if (res.text[0] == '\n') {
|
||||||
|
running = false;
|
||||||
|
} else {
|
||||||
|
cmd.insert(cursor++, res.text);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::KeyType::PASTE:
|
||||||
|
cmd.insert(cursor, res.text);
|
||||||
|
cursor += res.text.size();
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::KeyType::SPECIAL:
|
||||||
|
switch (res.special_key) {
|
||||||
|
case io::KeyEvent::SpecialKey::UNKNOWN:
|
||||||
|
case io::KeyEvent::SpecialKey::UP:
|
||||||
|
case io::KeyEvent::SpecialKey::DOWN:
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::SpecialKey::RIGHT:
|
||||||
|
if (cursor < cmd.size())
|
||||||
|
cursor++;
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::SpecialKey::LEFT:
|
||||||
|
if (cursor > 0)
|
||||||
|
cursor--;
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::SpecialKey::DELETE:
|
||||||
|
if (cursor < cmd.size())
|
||||||
|
cmd.erase(cursor, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t i = 1; i < height; ++i) {
|
||||||
|
bed.io.move_cursor(start + i, 1);
|
||||||
|
bed.io.write("\x1b[2K", 4);
|
||||||
|
}
|
||||||
|
bed.io.move_cursor(start, 1);
|
||||||
|
bed.io.write("\n", 1);
|
||||||
|
return {cmd, false};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandIO::redraw() {
|
||||||
|
bed.io.move_cursor(start, 1);
|
||||||
|
bed.io.write("\x1b[2K", 4);
|
||||||
|
bed.io.move_cursor(start, 1);
|
||||||
|
bed.io.write(prompt);
|
||||||
|
bed.io.write(cmd);
|
||||||
|
bed.io.move_cursor(start, prompt.size() + cursor + 1);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::ui
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
#include "internal/ui/text_mode.h"
|
||||||
|
#include "bed.h"
|
||||||
|
|
||||||
|
namespace bed::internal::ui {
|
||||||
|
TextMode::TextMode(BEd &bed) : bed(bed) {
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<vase::Shard *, bool> TextMode::run() {
|
||||||
|
auto [row, col] = bed.io.cursor_position();
|
||||||
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
|
if (row > rows)
|
||||||
|
throw fatal_error("Invalid cursor position.", 1);
|
||||||
|
start = row;
|
||||||
|
height = rows - row + 1;
|
||||||
|
term_width = cols;
|
||||||
|
term_height = rows;
|
||||||
|
cmd.clear();
|
||||||
|
cursor = 0;
|
||||||
|
redraw();
|
||||||
|
bool running = true;
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
io::KeyEvent res = bed.io.read_key();
|
||||||
|
switch (res.type) {
|
||||||
|
case io::KeyEvent::KeyType::EOF_:
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::KeyType::MOUSE:
|
||||||
|
case io::KeyEvent::KeyType::RESIZE:
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::KeyType::CHAR:
|
||||||
|
switch (res.modifier) {
|
||||||
|
case io::KeyEvent::Modifier::SHIFT:
|
||||||
|
case io::KeyEvent::Modifier::ALT:
|
||||||
|
case io::KeyEvent::Modifier::CTRL_ALT:
|
||||||
|
case io::KeyEvent::Modifier::CTRL:
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::Modifier::NONE:
|
||||||
|
if (res.text[0] == '\b' || res.text[0] == 0x7f) {
|
||||||
|
if (cursor > 0) {
|
||||||
|
cmd.erase(--cursor, 1);
|
||||||
|
}
|
||||||
|
} else if (res.text[0] == '\n') {
|
||||||
|
size_t lines = 1 + std::count(cmd.begin(), cmd.end(), '\n');
|
||||||
|
grow(lines + 1);
|
||||||
|
cmd.insert(cursor++, 1, '\n');
|
||||||
|
} else {
|
||||||
|
cmd.insert(cursor, res.text);
|
||||||
|
cursor += res.text.size();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::KeyType::PASTE:
|
||||||
|
cmd.insert(cursor, res.text);
|
||||||
|
cursor += res.text.size();
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::KeyType::SPECIAL:
|
||||||
|
switch (res.special_key) {
|
||||||
|
case io::KeyEvent::SpecialKey::UNKNOWN:
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::SpecialKey::RIGHT:
|
||||||
|
if (cursor < cmd.size())
|
||||||
|
++cursor;
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::SpecialKey::LEFT:
|
||||||
|
if (cursor > 0)
|
||||||
|
--cursor;
|
||||||
|
break;
|
||||||
|
case io::KeyEvent::SpecialKey::UP: {
|
||||||
|
size_t line_start =
|
||||||
|
cmd.rfind('\n', cursor == 0 ? 0 : cursor - 1);
|
||||||
|
if (line_start == std::string::npos)
|
||||||
|
line_start = 0;
|
||||||
|
else
|
||||||
|
++line_start;
|
||||||
|
size_t col = cursor - line_start;
|
||||||
|
if (line_start == 0)
|
||||||
|
break;
|
||||||
|
size_t prev_end = line_start - 1;
|
||||||
|
size_t prev_start =
|
||||||
|
cmd.rfind('\n', prev_end == 0 ? 0 : prev_end - 1);
|
||||||
|
if (prev_start == std::string::npos)
|
||||||
|
prev_start = 0;
|
||||||
|
else
|
||||||
|
++prev_start;
|
||||||
|
size_t prev_len = prev_end - prev_start;
|
||||||
|
cursor = prev_start + std::min(col, prev_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case io::KeyEvent::SpecialKey::DOWN: {
|
||||||
|
size_t line_start =
|
||||||
|
cmd.rfind('\n', cursor == 0 ? 0 : cursor - 1);
|
||||||
|
if (line_start == std::string::npos)
|
||||||
|
line_start = 0;
|
||||||
|
else
|
||||||
|
++line_start;
|
||||||
|
size_t col = cursor - line_start;
|
||||||
|
size_t line_end = cmd.find('\n', cursor);
|
||||||
|
if (line_end == std::string::npos)
|
||||||
|
line_end = cmd.size();
|
||||||
|
if (line_end == cmd.size())
|
||||||
|
break;
|
||||||
|
size_t next_start = line_end + 1;
|
||||||
|
size_t next_end = cmd.find('\n', next_start);
|
||||||
|
if (next_end == std::string::npos)
|
||||||
|
next_end = cmd.size();
|
||||||
|
size_t next_len = next_end - next_start;
|
||||||
|
cursor = next_start + std::min(col, next_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case io::KeyEvent::SpecialKey::DELETE:
|
||||||
|
if (cursor < cmd.size())
|
||||||
|
cmd.erase(cursor, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
redraw();
|
||||||
|
if (cmd.size() >= 3 && cmd.compare(cmd.size() - 3, 3, "\n.\n") == 0) {
|
||||||
|
cmd.erase(cmd.size() - 3);
|
||||||
|
cursor = cmd.size();
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t total_lines = 1 + std::count(cmd.begin(), cmd.end(), '\n');
|
||||||
|
uint16_t last_row = start + total_lines;
|
||||||
|
bed.io.move_cursor(last_row, 1);
|
||||||
|
bed.io.write("\n", 1);
|
||||||
|
return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextMode::grow(size_t required_height) {
|
||||||
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
|
term_height = rows;
|
||||||
|
term_width = cols;
|
||||||
|
if (required_height > height)
|
||||||
|
height = required_height;
|
||||||
|
long overflow = long(start) + long(height) - 1 - long(rows);
|
||||||
|
if (overflow <= 0)
|
||||||
|
return;
|
||||||
|
bed.io.move_cursor(rows, 1);
|
||||||
|
for (long i = 0; i < overflow; ++i)
|
||||||
|
bed.io.write("\n", 1);
|
||||||
|
start -= overflow;
|
||||||
|
if (start < 1)
|
||||||
|
start = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextMode::redraw() {
|
||||||
|
auto [rows, cols] = bed.io.terminal_size();
|
||||||
|
term_height = rows;
|
||||||
|
term_width = cols;
|
||||||
|
for (uint16_t i = 0; i < height; ++i) {
|
||||||
|
bed.io.move_cursor(start + i, 1);
|
||||||
|
bed.io.write("\x1b[2K", 4);
|
||||||
|
}
|
||||||
|
size_t line = 0;
|
||||||
|
size_t line_start = 0;
|
||||||
|
for (size_t i = 0; i < cursor; ++i) {
|
||||||
|
if (cmd[i] == '\n') {
|
||||||
|
++line;
|
||||||
|
line_start = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t col = cursor - line_start;
|
||||||
|
size_t pos = 0;
|
||||||
|
uint16_t screen_line = start;
|
||||||
|
while (pos <= cmd.size()) {
|
||||||
|
size_t end = cmd.find('\n', pos);
|
||||||
|
if (end == std::string::npos)
|
||||||
|
end = cmd.size();
|
||||||
|
if (screen_line < start + height) {
|
||||||
|
size_t len = std::min(end - pos, size_t(term_width));
|
||||||
|
bed.io.move_cursor(screen_line, 1);
|
||||||
|
bed.io.write(cmd.substr(pos, len));
|
||||||
|
}
|
||||||
|
if (end == cmd.size())
|
||||||
|
break;
|
||||||
|
pos = end + 1;
|
||||||
|
++screen_line;
|
||||||
|
}
|
||||||
|
size_t vis_col = std::min(col, size_t(term_width - 1));
|
||||||
|
bed.io.move_cursor(start + line, vis_col + 1);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::ui
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
namespace bed::internal::vase {
|
||||||
std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
|
std::vector<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s) {
|
||||||
std::vector<ReplacePart> parts;
|
std::vector<ReplacePart> parts;
|
||||||
std::string constant;
|
std::string constant;
|
||||||
auto flush_constant = [&]() {
|
auto flush_constant = [&]() {
|
||||||
@@ -13,11 +13,11 @@ std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
|
|||||||
++lines;
|
++lines;
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
uint64_t pos = append->append(constant.data(), (uint64_t)constant.size());
|
uint64_t pos = ap->append(constant.data(), (uint64_t)constant.size());
|
||||||
parts.push_back(
|
parts.push_back(
|
||||||
ReplacePart{
|
ReplacePart{
|
||||||
.type = ReplacePart::PartType::Constant,
|
.type = ReplacePart::PartType::Constant,
|
||||||
.value = new Petal((uint64_t)constant.size(), lines, append, pos)
|
.value = new Petal((uint64_t)constant.size(), lines, ap, pos)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
constant.clear();
|
constant.clear();
|
||||||
@@ -25,12 +25,7 @@ std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
|
|||||||
};
|
};
|
||||||
for (size_t i = 0; i < s.size(); ++i) {
|
for (size_t i = 0; i < s.size(); ++i) {
|
||||||
char c = s[i];
|
char c = s[i];
|
||||||
if (c == '\\' && i + 1 < s.size() && s[i + 1] == '$') {
|
if (c == '\\' && i + 1 < s.size()) {
|
||||||
constant.push_back('$');
|
|
||||||
++i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (c == '$' && i + 1 < s.size()) {
|
|
||||||
char next = s[i + 1];
|
char next = s[i + 1];
|
||||||
if (next == '0') {
|
if (next == '0') {
|
||||||
flush_constant();
|
flush_constant();
|
||||||
@@ -40,10 +35,7 @@ std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
|
|||||||
.value = (uint8_t)0
|
.value = (uint8_t)0
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
++i;
|
} else if (next >= '1' && next <= '9') {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (next >= '1' && next <= '9') {
|
|
||||||
flush_constant();
|
flush_constant();
|
||||||
parts.push_back(
|
parts.push_back(
|
||||||
ReplacePart{
|
ReplacePart{
|
||||||
@@ -51,9 +43,22 @@ std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
|
|||||||
.value = (uint8_t)(next - '0')
|
.value = (uint8_t)(next - '0')
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
++i;
|
} else if (next == 'n') {
|
||||||
continue;
|
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);
|
constant.push_back(c);
|
||||||
}
|
}
|
||||||
@@ -61,23 +66,42 @@ std::vector<Vase::ReplacePart> Vase::parse_replace(std::string_view s) {
|
|||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vase::regex_search_replace(
|
Shard *substitute(
|
||||||
std::string_view pattern, Range range,
|
AppendStorage *ap, Shard *root,
|
||||||
std::string_view replace, std::string_view options
|
std::string_view pattern, uint64_t start, uint64_t end,
|
||||||
|
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
|
||||||
) {
|
) {
|
||||||
std::vector<RegexMatch> matches = _regex_search(pattern, range, options);
|
if (!start || !end)
|
||||||
|
throw ed_error("Invalid range.");
|
||||||
|
start--;
|
||||||
|
end--;
|
||||||
|
if (!root)
|
||||||
|
throw ed_error("line range out of bounds");
|
||||||
|
uint64_t line_count = root->lines + 1;
|
||||||
|
if (start > end || end >= line_count)
|
||||||
|
throw ed_error("line range out of bounds");
|
||||||
|
uint64_t start_offset = offset_of(root, start);
|
||||||
|
uint64_t end_offset =
|
||||||
|
(end + 1 == line_count)
|
||||||
|
? root->length
|
||||||
|
: offset_of(root, end + 1) - 1;
|
||||||
|
std::vector<RegexMatch> matches = _regex_search(root, pattern, start_offset, end_offset, options);
|
||||||
if (matches.empty())
|
if (matches.empty())
|
||||||
return;
|
return root;
|
||||||
|
std::vector<ReplacePart> replace_parts = parse_replace(ap, replace);
|
||||||
std::vector<ReplacePart> replace_parts = parse_replace(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;
|
std::vector<Shard *> pieces;
|
||||||
pieces.reserve(matches.size() * 2 + 1);
|
pieces.reserve(matches.size() * 2 + 1);
|
||||||
|
|
||||||
Shard *remaining = root;
|
Shard *remaining = root;
|
||||||
Shard::retain(remaining);
|
Shard::retain(remaining);
|
||||||
uint64_t cursor = 0;
|
uint64_t cursor = 0;
|
||||||
|
|
||||||
for (const RegexMatch &match : matches) {
|
for (const RegexMatch &match : matches) {
|
||||||
uint64_t gap = match.start - cursor;
|
uint64_t gap = match.start - cursor;
|
||||||
if (gap > 0) {
|
if (gap > 0) {
|
||||||
@@ -85,28 +109,33 @@ void Vase::regex_search_replace(
|
|||||||
Shard::release(remaining);
|
Shard::release(remaining);
|
||||||
pieces.push_back(keep);
|
pieces.push_back(keep);
|
||||||
remaining = rest;
|
remaining = rest;
|
||||||
|
orig_line += keep ? keep->lines : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
|
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
|
||||||
Shard::release(remaining);
|
Shard::release(remaining);
|
||||||
remaining = rest2;
|
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) {
|
for (size_t i = 0; i < replace_parts.size(); ++i) {
|
||||||
const ReplacePart &part = replace_parts[i];
|
const ReplacePart &part = replace_parts[i];
|
||||||
switch (part.type) {
|
switch (part.type) {
|
||||||
case ReplacePart::PartType::Constant:
|
case ReplacePart::PartType::Constant: {
|
||||||
Shard::retain(std::get<Shard *>(part.value));
|
Shard *c = std::get<Shard *>(part.value);
|
||||||
pieces.push_back(std::get<Shard *>(part.value));
|
Shard::retain(c);
|
||||||
|
pieces.push_back(c);
|
||||||
|
new_lines += c ? c->lines : 0;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case ReplacePart::PartType::FullMatch:
|
case ReplacePart::PartType::FullMatch:
|
||||||
Shard::retain(dropped);
|
Shard::retain(dropped);
|
||||||
pieces.push_back(dropped);
|
pieces.push_back(dropped);
|
||||||
|
new_lines += dropped ? dropped->lines : 0;
|
||||||
break;
|
break;
|
||||||
case ReplacePart::PartType::CaptureGroup: {
|
case ReplacePart::PartType::CaptureGroup: {
|
||||||
uint8_t idx = std::get<uint8_t>(part.value);
|
uint8_t idx = std::get<uint8_t>(part.value);
|
||||||
if (idx <= 9) {
|
if (idx <= 9) {
|
||||||
const RegexGroup &group = match.groups[idx - 1];
|
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 ls = group.start - match.start;
|
||||||
uint64_t le = group.end - match.start;
|
uint64_t le = group.end - match.start;
|
||||||
auto [a, b] = Shard::split(dropped, ls);
|
auto [a, b] = Shard::split(dropped, ls);
|
||||||
@@ -115,6 +144,7 @@ void Vase::regex_search_replace(
|
|||||||
Shard::release(b);
|
Shard::release(b);
|
||||||
Shard::release(c);
|
Shard::release(c);
|
||||||
pieces.push_back(g);
|
pieces.push_back(g);
|
||||||
|
new_lines += g ? g->lines : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -122,14 +152,19 @@ void Vase::regex_search_replace(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Shard::release(dropped);
|
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;
|
cursor = match.end;
|
||||||
}
|
}
|
||||||
pieces.push_back(remaining);
|
pieces.push_back(remaining);
|
||||||
|
|
||||||
for (auto &part : replace_parts)
|
for (auto &part : replace_parts)
|
||||||
if (part.type == ReplacePart::PartType::Constant)
|
if (part.type == ReplacePart::PartType::Constant)
|
||||||
Shard::release(std::get<Shard *>(part.value));
|
Shard::release(std::get<Shard *>(part.value));
|
||||||
|
|
||||||
std::vector<Shard *> compact;
|
std::vector<Shard *> compact;
|
||||||
compact.reserve(pieces.size());
|
compact.reserve(pieces.size());
|
||||||
for (Shard *p : pieces) {
|
for (Shard *p : pieces) {
|
||||||
@@ -138,26 +173,17 @@ void Vase::regex_search_replace(
|
|||||||
else if (p)
|
else if (p)
|
||||||
Shard::release(p);
|
Shard::release(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
Shard *new_root = compact.empty() ? nullptr : Shard::build(compact.data(), 0, compact.size());
|
Shard *new_root = compact.empty() ? nullptr : Shard::build(compact.data(), 0, compact.size());
|
||||||
Shard::release(root);
|
Shard::release(root);
|
||||||
root = new_root;
|
return new_root;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Range> Vase::regex_search(
|
uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) {
|
||||||
std::string_view pattern, Range range, std::string_view options
|
if (!root)
|
||||||
) {
|
throw ed_error("Invalid line number.");
|
||||||
std::vector<RegexMatch> matches = _regex_search(pattern, range, options);
|
if (start == 0 || start > root->lines + 1)
|
||||||
if (matches.empty())
|
throw ed_error("Invalid line number.");
|
||||||
return {};
|
start--;
|
||||||
std::vector<Range> result;
|
|
||||||
result.reserve(matches.size());
|
|
||||||
for (auto match : matches)
|
|
||||||
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;
|
std::vector<RegexMatch> results;
|
||||||
int errornumber;
|
int errornumber;
|
||||||
PCRE2_SIZE erroroffset;
|
PCRE2_SIZE erroroffset;
|
||||||
@@ -176,7 +202,7 @@ uint64_t Vase::find_next(std::string_view pattern, uint64_t start) {
|
|||||||
pcre2_code_free(re);
|
pcre2_code_free(re);
|
||||||
throw ed_error("Can't create regex match data.");
|
throw ed_error("Can't create regex match data.");
|
||||||
}
|
}
|
||||||
uint64_t at = (start + 1) % lines();
|
uint64_t at = (start + 1) % (root->lines + 1);
|
||||||
LineIterator it(root, at, Direction::Forward);
|
LineIterator it(root, at, Direction::Forward);
|
||||||
std::string line;
|
std::string line;
|
||||||
while (it.next(&line)) {
|
while (it.next(&line)) {
|
||||||
@@ -184,7 +210,7 @@ uint64_t Vase::find_next(std::string_view pattern, uint64_t start) {
|
|||||||
if (rc >= 0) {
|
if (rc >= 0) {
|
||||||
pcre2_match_data_free(match_data);
|
pcre2_match_data_free(match_data);
|
||||||
pcre2_code_free(re);
|
pcre2_code_free(re);
|
||||||
return at;
|
return at + 1;
|
||||||
}
|
}
|
||||||
if (rc != PCRE2_ERROR_NOMATCH) {
|
if (rc != PCRE2_ERROR_NOMATCH) {
|
||||||
pcre2_match_data_free(match_data);
|
pcre2_match_data_free(match_data);
|
||||||
@@ -196,13 +222,13 @@ uint64_t Vase::find_next(std::string_view pattern, uint64_t start) {
|
|||||||
at = 0;
|
at = 0;
|
||||||
LineIterator it2(root, at, Direction::Forward);
|
LineIterator it2(root, at, Direction::Forward);
|
||||||
while (it2.next(&line)) {
|
while (it2.next(&line)) {
|
||||||
if (at >= start)
|
if (at > start)
|
||||||
break;
|
break;
|
||||||
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
|
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
|
||||||
if (rc >= 0) {
|
if (rc >= 0) {
|
||||||
pcre2_match_data_free(match_data);
|
pcre2_match_data_free(match_data);
|
||||||
pcre2_code_free(re);
|
pcre2_code_free(re);
|
||||||
return at;
|
return at + 1;
|
||||||
}
|
}
|
||||||
if (rc != PCRE2_ERROR_NOMATCH) {
|
if (rc != PCRE2_ERROR_NOMATCH) {
|
||||||
pcre2_match_data_free(match_data);
|
pcre2_match_data_free(match_data);
|
||||||
@@ -216,7 +242,12 @@ uint64_t Vase::find_next(std::string_view pattern, uint64_t start) {
|
|||||||
throw ed_error("No line matched.");
|
throw ed_error("No line matched.");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) {
|
uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start) {
|
||||||
|
if (!root)
|
||||||
|
throw ed_error("Invalid line number.");
|
||||||
|
if (start == 0 || start > root->lines + 1)
|
||||||
|
throw ed_error("Invalid line number.");
|
||||||
|
start--;
|
||||||
std::vector<RegexMatch> results;
|
std::vector<RegexMatch> results;
|
||||||
int errornumber;
|
int errornumber;
|
||||||
PCRE2_SIZE erroroffset;
|
PCRE2_SIZE erroroffset;
|
||||||
@@ -235,7 +266,7 @@ uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) {
|
|||||||
pcre2_code_free(re);
|
pcre2_code_free(re);
|
||||||
throw ed_error("Can't create regex match data.");
|
throw ed_error("Can't create regex match data.");
|
||||||
}
|
}
|
||||||
uint64_t at = (start == 0 ? lines() : start) - 1;
|
uint64_t at = (start == 0 ? root->lines : start - 1);
|
||||||
LineIterator it(root, at, Direction::Backward);
|
LineIterator it(root, at, Direction::Backward);
|
||||||
std::string line;
|
std::string line;
|
||||||
while (it.next(&line)) {
|
while (it.next(&line)) {
|
||||||
@@ -243,7 +274,7 @@ uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) {
|
|||||||
if (rc >= 0) {
|
if (rc >= 0) {
|
||||||
pcre2_match_data_free(match_data);
|
pcre2_match_data_free(match_data);
|
||||||
pcre2_code_free(re);
|
pcre2_code_free(re);
|
||||||
return at;
|
return at + 1;
|
||||||
}
|
}
|
||||||
if (rc != PCRE2_ERROR_NOMATCH) {
|
if (rc != PCRE2_ERROR_NOMATCH) {
|
||||||
pcre2_match_data_free(match_data);
|
pcre2_match_data_free(match_data);
|
||||||
@@ -252,16 +283,16 @@ uint64_t Vase::find_prev(std::string_view pattern, uint64_t start) {
|
|||||||
}
|
}
|
||||||
at--;
|
at--;
|
||||||
}
|
}
|
||||||
at = lines();
|
at = root->lines;
|
||||||
LineIterator it2(root, at, Direction::Backward);
|
LineIterator it2(root, at, Direction::Backward);
|
||||||
while (it2.next(&line)) {
|
while (it2.next(&line)) {
|
||||||
if (at <= start)
|
if (at < start)
|
||||||
break;
|
break;
|
||||||
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
|
int rc = pcre2_match(re, (PCRE2_SPTR)line.data(), line.size(), 0, 0, match_data, nullptr);
|
||||||
if (rc >= 0) {
|
if (rc >= 0) {
|
||||||
pcre2_match_data_free(match_data);
|
pcre2_match_data_free(match_data);
|
||||||
pcre2_code_free(re);
|
pcre2_code_free(re);
|
||||||
return at;
|
return at + 1;
|
||||||
}
|
}
|
||||||
if (rc != PCRE2_ERROR_NOMATCH) {
|
if (rc != PCRE2_ERROR_NOMATCH) {
|
||||||
pcre2_match_data_free(match_data);
|
pcre2_match_data_free(match_data);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
namespace bed::internal::vase {
|
||||||
std::vector<Vase::RegexMatch> Vase::_regex_search(
|
std::vector<RegexMatch> _regex_search(
|
||||||
std::string_view pattern, Range range, std::string_view options
|
Shard *root, std::string_view pattern, uint64_t start_offset, uint64_t end_offset, std::string_view options
|
||||||
) {
|
) {
|
||||||
bool global = false;
|
bool global = false;
|
||||||
uint64_t flags = PCRE2_MULTILINE | PCRE2_UTF;
|
uint64_t flags = PCRE2_MULTILINE | PCRE2_UTF;
|
||||||
@@ -52,9 +52,6 @@ std::vector<Vase::RegexMatch> Vase::_regex_search(
|
|||||||
|
|
||||||
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);
|
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);
|
||||||
|
|
||||||
uint64_t start_offset = offset_of(range.start);
|
|
||||||
uint64_t end_offset = offset_of(range.end);
|
|
||||||
|
|
||||||
PetalIterator it(root, Direction::Forward);
|
PetalIterator it(root, Direction::Forward);
|
||||||
it.seek_offset(start_offset);
|
it.seek_offset(start_offset);
|
||||||
|
|
||||||
@@ -67,7 +64,7 @@ std::vector<Vase::RegexMatch> Vase::_regex_search(
|
|||||||
uint64_t offset = UINT64_MAX;
|
uint64_t offset = UINT64_MAX;
|
||||||
|
|
||||||
auto record_match = [&](int rc, PCRE2_SIZE *ovector) {
|
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;
|
return;
|
||||||
RegexMatch match{
|
RegexMatch match{
|
||||||
.start = global_offset + (uint64_t)ovector[0],
|
.start = global_offset + (uint64_t)ovector[0],
|
||||||
@@ -77,8 +74,8 @@ std::vector<Vase::RegexMatch> Vase::_regex_search(
|
|||||||
PCRE2_SIZE s = ovector[2 * i];
|
PCRE2_SIZE s = ovector[2 * i];
|
||||||
PCRE2_SIZE e = ovector[2 * i + 1];
|
PCRE2_SIZE e = ovector[2 * i + 1];
|
||||||
if (s != PCRE2_UNSET) {
|
if (s != PCRE2_UNSET) {
|
||||||
match.groups[i].start = global_offset + (uint64_t)s;
|
match.groups[i - 1].start = global_offset + (uint64_t)s;
|
||||||
match.groups[i].end = global_offset + (uint64_t)e;
|
match.groups[i - 1].end = global_offset + (uint64_t)e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
results.push_back(std::move(match));
|
results.push_back(std::move(match));
|
||||||
|
|||||||
+102
-96
@@ -1,3 +1,4 @@
|
|||||||
|
#include "internal/io/io.h"
|
||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
namespace bed::internal::vase {
|
||||||
@@ -14,6 +15,7 @@ void Shard::release(Shard *n) {
|
|||||||
release(((Branch *)n)->right);
|
release(((Branch *)n)->right);
|
||||||
delete (Branch *)n;
|
delete (Branch *)n;
|
||||||
} else {
|
} else {
|
||||||
|
((Petal *)n)->source->release();
|
||||||
delete (Petal *)n;
|
delete (Petal *)n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,53 +193,38 @@ Shard *Shard::append(Shard *root, Shard *leaf) {
|
|||||||
Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
|
Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
|
||||||
if (hi - lo == 1)
|
if (hi - lo == 1)
|
||||||
return pieces[lo];
|
return pieces[lo];
|
||||||
size_t mid = lo + (hi - lo) / 2;
|
uint64_t mid = lo + (hi - lo) / 2;
|
||||||
Shard *left = build(pieces, lo, mid);
|
Shard *left = build(pieces, lo, mid);
|
||||||
Shard *right = build(pieces, mid, hi);
|
Shard *right = build(pieces, mid, hi);
|
||||||
Shard *node = new Branch(left, right);
|
Shard *node = Shard::concat(left, right);
|
||||||
Shard::release(left);
|
Shard::release(left);
|
||||||
Shard::release(right);
|
Shard::release(right);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool write_all(int fd, const void *data, size_t len) {
|
Shard *Shard::from_command(const char *cmd, bool posix_ending) {
|
||||||
const char *p = (const char *)data;
|
auto o = new OriginalStorage("/tmp");
|
||||||
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;
|
int dest_fd = o->fd;
|
||||||
if (dest_fd == -1)
|
if (dest_fd == -1) {
|
||||||
|
delete o;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
|
io::IO::cleanup();
|
||||||
FILE *pipe = popen(cmd, "r");
|
FILE *pipe = popen(cmd, "r");
|
||||||
if (!pipe)
|
if (!pipe) {
|
||||||
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
std::vector<Shard *> pieces;
|
std::vector<Shard *> pieces;
|
||||||
pieces.reserve(16);
|
pieces.reserve(16);
|
||||||
|
|
||||||
uint64_t pos = 0;
|
uint64_t pos = 0;
|
||||||
char buf[PETAL_SIZE_MAX];
|
char buf[PETAL_SIZE_MAX];
|
||||||
uint64_t buf_cursor = 0;
|
uint64_t buf_cursor = 0;
|
||||||
|
|
||||||
char ending[2] = {'\0', '\0'};
|
char ending[2] = {'\0', '\0'};
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
size_t got = fread(buf + buf_cursor, 1, sizeof(buf) - buf_cursor, pipe);
|
size_t got = fread(buf + buf_cursor, 1, sizeof(buf) - buf_cursor, pipe);
|
||||||
buf_cursor += got;
|
buf_cursor += got;
|
||||||
|
|
||||||
if (buf_cursor == PETAL_SIZE_MAX || feof(pipe)) {
|
if (buf_cursor == PETAL_SIZE_MAX || feof(pipe)) {
|
||||||
if (buf_cursor == 0)
|
if (buf_cursor == 0)
|
||||||
break;
|
break;
|
||||||
@@ -260,6 +247,8 @@ Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending
|
|||||||
}
|
}
|
||||||
if (!write_all(dest_fd, buf, buf_cursor)) {
|
if (!write_all(dest_fd, buf, buf_cursor)) {
|
||||||
pclose(pipe);
|
pclose(pipe);
|
||||||
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
pieces.push_back(new Petal(buf_cursor, lines, o, pos));
|
pieces.push_back(new Petal(buf_cursor, lines, o, pos));
|
||||||
@@ -267,19 +256,28 @@ Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending
|
|||||||
}
|
}
|
||||||
if (feof(pipe))
|
if (feof(pipe))
|
||||||
break;
|
break;
|
||||||
if (ferror(pipe))
|
if (ferror(pipe)) {
|
||||||
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int status = pclose(pipe);
|
int status = pclose(pipe);
|
||||||
if (status == -1)
|
if (status == -1) {
|
||||||
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
}
|
||||||
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||||
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
if (pieces.empty())
|
if (pieces.empty()) {
|
||||||
|
delete o;
|
||||||
|
io::IO::enable_raw();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
if (posix_ending) {
|
if (posix_ending) {
|
||||||
if (ending[1] == '\n') {
|
if (ending[1] == '\n') {
|
||||||
Petal *last = (Petal *)pieces.back();
|
Petal *last = (Petal *)pieces.back();
|
||||||
@@ -296,52 +294,59 @@ Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending
|
|||||||
last->length--;
|
last->length--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
o->initialize();
|
o->initialize();
|
||||||
|
io::IO::enable_raw();
|
||||||
if (pieces.size() == 1)
|
if (pieces.size() == 1)
|
||||||
return pieces[0];
|
return pieces[0];
|
||||||
|
|
||||||
return build(pieces.data(), 0, pieces.size());
|
return build(pieces.data(), 0, pieces.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
Shard *Shard::from_file(std::filesystem::path &path, OriginalBuffer *o, bool posix_ending) {
|
Shard *Shard::from_file(const std::filesystem::path &path, bool posix_ending) {
|
||||||
|
auto o = new OriginalStorage("/tmp");
|
||||||
int dest_fd = o->fd;
|
int dest_fd = o->fd;
|
||||||
if (dest_fd == -1)
|
if (dest_fd == -1) {
|
||||||
|
delete o;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
int src_fd = open(path.c_str(), O_RDONLY);
|
int src_fd = open(path.c_str(), O_RDONLY);
|
||||||
if (src_fd == -1)
|
if (src_fd == -1) {
|
||||||
|
delete o;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
uint64_t total = std::filesystem::file_size(path);
|
uint64_t total = std::filesystem::file_size(path);
|
||||||
if (posix_ending && total > 0) {
|
if (posix_ending && total > 0) {
|
||||||
char last;
|
char last;
|
||||||
if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1)
|
if (pread(src_fd, &last, 1, (off_t)(total - 1)) != 1) {
|
||||||
|
delete o;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
if (last == '\n') {
|
if (last == '\n') {
|
||||||
total--;
|
total--;
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
char s_last;
|
char s_last;
|
||||||
if (pread(src_fd, &s_last, 1, (off_t)(total - 1)) != 1)
|
if (pread(src_fd, &s_last, 1, (off_t)(total - 1)) != 1) {
|
||||||
|
delete o;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
if (s_last == '\r')
|
if (s_last == '\r')
|
||||||
total--;
|
total--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (total == 0)
|
if (total == 0) {
|
||||||
|
delete o;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
std::vector<Shard *> pieces;
|
std::vector<Shard *> pieces;
|
||||||
uint64_t pos = 0;
|
uint64_t pos = 0;
|
||||||
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
|
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
|
||||||
char buf[PETAL_SIZE_MAX];
|
char buf[PETAL_SIZE_MAX];
|
||||||
|
|
||||||
while (pos < total) {
|
while (pos < total) {
|
||||||
uint64_t want = std::min(PETAL_SIZE_MAX, total - pos);
|
uint64_t want = std::min(PETAL_SIZE_MAX, total - pos);
|
||||||
ssize_t got = pread(src_fd, buf, want, pos);
|
ssize_t got = pread(src_fd, buf, want, pos);
|
||||||
if (got <= 0) {
|
if (got <= 0) {
|
||||||
close(src_fd);
|
close(src_fd);
|
||||||
|
delete o;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
uint64_t take = (uint64_t)got;
|
uint64_t take = (uint64_t)got;
|
||||||
@@ -357,71 +362,72 @@ Shard *Shard::from_file(std::filesystem::path &path, OriginalBuffer *o, bool pos
|
|||||||
}
|
}
|
||||||
if (!write_all(dest_fd, buf, take)) {
|
if (!write_all(dest_fd, buf, take)) {
|
||||||
close(src_fd);
|
close(src_fd);
|
||||||
|
delete o;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
pieces.push_back(new Petal(take, lines, o, pos));
|
pieces.push_back(new Petal(take, lines, o, pos));
|
||||||
pos += take;
|
pos += take;
|
||||||
}
|
}
|
||||||
|
|
||||||
close(src_fd);
|
close(src_fd);
|
||||||
|
if (pieces.empty()) {
|
||||||
if (pieces.empty())
|
delete o;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
}
|
||||||
o->initialize();
|
o->initialize();
|
||||||
|
|
||||||
if (pieces.size() == 1)
|
if (pieces.size() == 1)
|
||||||
return pieces[0];
|
return pieces[0];
|
||||||
return build(pieces.data(), 0, pieces.size());
|
return build(pieces.data(), 0, pieces.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shard::dump(Shard *node, int depth) {
|
Shard *Shard::from_string(const char *data, uint64_t len, bool posix_ending) {
|
||||||
if (!node) {
|
auto o = new OriginalStorage("/tmp");
|
||||||
std::cout << std::string(depth * 2, ' ') << "<null>\n";
|
int dest_fd = o->fd;
|
||||||
return;
|
if (dest_fd == -1 || data == nullptr) {
|
||||||
|
delete o;
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
std::string indent(depth * 2, ' ');
|
uint64_t total = len;
|
||||||
std::cout << indent
|
if (posix_ending && total > 0) {
|
||||||
<< "Shard@" << node
|
if (data[total - 1] == '\n') {
|
||||||
<< " kind=";
|
total--;
|
||||||
switch (node->kind) {
|
if (total > 0 && data[total - 1] == '\r')
|
||||||
case Shard::Kind::Branch:
|
total--;
|
||||||
std::cout << "Branch";
|
}
|
||||||
break;
|
|
||||||
case Shard::Kind::Petal:
|
|
||||||
std::cout << "Petal";
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
std::cout
|
if (total == 0) {
|
||||||
<< " height=" << unsigned(node->height)
|
delete o;
|
||||||
<< " length=" << node->length
|
return nullptr;
|
||||||
<< " 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";
|
|
||||||
}
|
}
|
||||||
|
std::vector<Shard *> pieces;
|
||||||
|
uint64_t pos = 0;
|
||||||
|
pieces.reserve((total + PETAL_SIZE_MAX - 1) / PETAL_SIZE_MAX);
|
||||||
|
while (pos < total) {
|
||||||
|
uint64_t take = std::min(PETAL_SIZE_MAX, total - pos);
|
||||||
|
const char *buf = data + pos;
|
||||||
|
uint64_t lines = 0;
|
||||||
|
const char *p = buf;
|
||||||
|
const char *end = buf + take;
|
||||||
|
while (p < end) {
|
||||||
|
const void *nl = memchr(p, '\n', end - p);
|
||||||
|
if (!nl)
|
||||||
|
break;
|
||||||
|
lines++;
|
||||||
|
p = (const char *)nl + 1;
|
||||||
|
}
|
||||||
|
if (!write_all(dest_fd, buf, take)) {
|
||||||
|
delete o;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
pieces.push_back(new Petal(take, lines, o, pos));
|
||||||
|
pos += take;
|
||||||
|
}
|
||||||
|
if (pieces.empty()) {
|
||||||
|
delete o;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
o->initialize();
|
||||||
|
if (pieces.size() == 1)
|
||||||
|
return pieces[0];
|
||||||
|
return build(pieces.data(), 0, pieces.size());
|
||||||
}
|
}
|
||||||
} // namespace bed::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
namespace bed::internal::vase {
|
||||||
AppendBuffer::AppendBuffer(std::filesystem::path base_dir) {
|
AppendStorage::AppendStorage(std::filesystem::path base_dir) {
|
||||||
base_dir /= "tapp.XXXXXX";
|
base_dir /= "tapp.XXXXXX";
|
||||||
char *s = strdup(base_dir.c_str());
|
char *s = strdup(base_dir.c_str());
|
||||||
fd = mkstemp(s);
|
fd = mkstemp(s);
|
||||||
@@ -17,14 +17,14 @@ AppendBuffer::AppendBuffer(std::filesystem::path base_dir) {
|
|||||||
throw std::runtime_error("mmap failed");
|
throw std::runtime_error("mmap failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
AppendBuffer::~AppendBuffer() {
|
AppendStorage::~AppendStorage() {
|
||||||
if (buf && buf != MAP_FAILED)
|
if (buf && buf != MAP_FAILED)
|
||||||
munmap(buf, allocated_capacity);
|
munmap(buf, allocated_capacity);
|
||||||
if (fd != -1)
|
if (fd != -1)
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppendBuffer::grow(uint64_t len) {
|
void AppendStorage::grow(uint64_t len) {
|
||||||
if (current_size + len > allocated_capacity) {
|
if (current_size + len > allocated_capacity) {
|
||||||
uint64_t new_capacity = allocated_capacity * 2;
|
uint64_t new_capacity = allocated_capacity * 2;
|
||||||
if (new_capacity < current_size + len)
|
if (new_capacity < current_size + len)
|
||||||
@@ -46,13 +46,13 @@ void AppendBuffer::grow(uint64_t len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t AppendBuffer::append(const char c) {
|
uint64_t AppendStorage::append(const char c) {
|
||||||
grow(1);
|
grow(1);
|
||||||
buf[current_size++] = c;
|
buf[current_size++] = c;
|
||||||
return current_size - 1;
|
return current_size - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t AppendBuffer::append(const char *text, uint64_t len) {
|
uint64_t AppendStorage::append(const char *text, uint64_t len) {
|
||||||
grow(len);
|
grow(len);
|
||||||
memcpy(buf + current_size, text, len);
|
memcpy(buf + current_size, text, len);
|
||||||
uint64_t old_pos = current_size;
|
uint64_t old_pos = current_size;
|
||||||
@@ -60,13 +60,13 @@ uint64_t AppendBuffer::append(const char *text, uint64_t len) {
|
|||||||
return old_pos;
|
return old_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *AppendBuffer::read(uint64_t pos) {
|
const char *AppendStorage::read(uint64_t pos) {
|
||||||
if (pos >= current_size)
|
if (pos >= current_size)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return buf + pos;
|
return buf + pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t AppendBuffer::length() {
|
uint64_t AppendStorage::length() {
|
||||||
return current_size;
|
return current_size;
|
||||||
}
|
}
|
||||||
} // namespace bed::internal::vase
|
} // namespace bed::internal::vase
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
namespace bed::internal::vase {
|
||||||
OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) {
|
OriginalStorage::OriginalStorage(std::filesystem::path base_dir) {
|
||||||
if (!std::filesystem::exists(base_dir) || !std::filesystem::is_directory(base_dir))
|
if (!std::filesystem::exists(base_dir) || !std::filesystem::is_directory(base_dir))
|
||||||
throw std::runtime_error("Swap directory does not exist or is not a directory.");
|
throw std::runtime_error("Swap directory does not exist or is not a directory.");
|
||||||
base_dir /= "tbuf.XXXXXX";
|
base_dir /= "tbuf.XXXXXX";
|
||||||
@@ -13,14 +13,14 @@ OriginalBuffer::OriginalBuffer(std::filesystem::path base_dir) {
|
|||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
OriginalBuffer::~OriginalBuffer() {
|
OriginalStorage::~OriginalStorage() {
|
||||||
if (buf)
|
if (buf)
|
||||||
munmap((char *)buf, len);
|
munmap((char *)buf, len);
|
||||||
if (fd != -1)
|
if (fd != -1)
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OriginalBuffer::initialize() {
|
void OriginalStorage::initialize() {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstat(fd, &st) == -1)
|
if (fstat(fd, &st) == -1)
|
||||||
throw std::runtime_error("fstat failed");
|
throw std::runtime_error("fstat failed");
|
||||||
@@ -35,13 +35,13 @@ void OriginalBuffer::initialize() {
|
|||||||
fd = -1;
|
fd = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *OriginalBuffer::read(uint64_t pos) {
|
const char *OriginalStorage::read(uint64_t pos) {
|
||||||
if (pos >= len)
|
if (pos >= len)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return buf + pos;
|
return buf + pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t OriginalBuffer::length() {
|
uint64_t OriginalStorage::length() {
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
} // namespace bed::internal::vase
|
} // namespace bed::internal::vase
|
||||||
+213
-396
@@ -1,108 +1,8 @@
|
|||||||
#include "internal/vase/vase.h"
|
#include "internal/vase/vase.h"
|
||||||
|
#include "internal/io/io.h"
|
||||||
|
|
||||||
namespace bed::internal::vase {
|
namespace bed::internal::vase {
|
||||||
Vase::Vase(std::filesystem::path path, std::filesystem::path swapdir)
|
std::string to_string(Shard *root) {
|
||||||
: path(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);
|
|
||||||
if (std::filesystem::is_regular_file(path))
|
|
||||||
root = Shard::from_file(path, original, posix_ending);
|
|
||||||
else
|
|
||||||
root = nullptr;
|
|
||||||
history_top = 0;
|
|
||||||
history.push_back(root);
|
|
||||||
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() {
|
|
||||||
if (!root)
|
|
||||||
return 0;
|
|
||||||
return root->length + posix_ending;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Vase::lines() {
|
|
||||||
if (!root)
|
|
||||||
return 0;
|
|
||||||
return root->lines + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Vase::to_string() {
|
|
||||||
std::string out;
|
std::string out;
|
||||||
if (!root)
|
if (!root)
|
||||||
return out;
|
return out;
|
||||||
@@ -112,23 +12,19 @@ std::string Vase::to_string() {
|
|||||||
uint64_t len;
|
uint64_t len;
|
||||||
while (it.next(&data, &len))
|
while (it.next(&data, &len))
|
||||||
out.append(data, len);
|
out.append(data, len);
|
||||||
if (posix_ending)
|
|
||||||
out.append("\n");
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Vase::to_string(Range range) {
|
std::string to_string(Shard *root, Range range) {
|
||||||
clamp(&range.start);
|
|
||||||
clamp(&range.end);
|
|
||||||
std::string out;
|
std::string out;
|
||||||
if (!root)
|
if (!root)
|
||||||
return out;
|
return out;
|
||||||
PetalIterator it(root, Direction::Forward);
|
PetalIterator it(root, Direction::Forward);
|
||||||
uint64_t start = offset_of(range.start);
|
uint64_t start = offset_of(root, range.start);
|
||||||
it.seek_offset(start);
|
it.seek_offset(start);
|
||||||
const char *data;
|
const char *data;
|
||||||
uint64_t len;
|
uint64_t len;
|
||||||
uint64_t remaining = offset_of(range.end) - start;
|
uint64_t remaining = offset_of(root, range.end) - start;
|
||||||
while (remaining && it.next(&data, &len)) {
|
while (remaining && it.next(&data, &len)) {
|
||||||
uint64_t n = std::min(len, remaining);
|
uint64_t n = std::min(len, remaining);
|
||||||
out.append(data, n);
|
out.append(data, n);
|
||||||
@@ -137,85 +33,10 @@ std::string Vase::to_string(Range range) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator Vase::iterate(uint64_t line, Direction dir) {
|
Shard *insert(AppendStorage *ap, Shard *root, Point *point, char key) {
|
||||||
return Iterator(root, line, dir);
|
uint64_t pos = ap->append(key);
|
||||||
}
|
Shard *inserted = new Petal(1, key == '\n', ap, pos);
|
||||||
|
auto [left, right] = Shard::split(root, offset_of(root, *point));
|
||||||
bool Vase::undo() {
|
|
||||||
if (history_top == 0)
|
|
||||||
return false;
|
|
||||||
Shard::release(root);
|
|
||||||
history_top--;
|
|
||||||
root = history[history_top];
|
|
||||||
Shard::retain(root);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Vase::redo() {
|
|
||||||
if (history_top + 1 >= history.size())
|
|
||||||
return false;
|
|
||||||
Shard::release(root);
|
|
||||||
history_top++;
|
|
||||||
root = history[history_top];
|
|
||||||
Shard::retain(root);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Vase::snapshot() {
|
|
||||||
if (history[history_top] == root)
|
|
||||||
return;
|
|
||||||
while (history.size() > history_top + 1) {
|
|
||||||
Shard::release(history.back());
|
|
||||||
history.pop_back();
|
|
||||||
}
|
|
||||||
Shard::retain(root);
|
|
||||||
history.push_back(root);
|
|
||||||
history_top++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Vase::prune_history(uint64_t n) {
|
|
||||||
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() + 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;
|
|
||||||
PetalIterator it(root, Direction::Forward);
|
|
||||||
it.seek_offset(0);
|
|
||||||
const char *data;
|
|
||||||
uint64_t len;
|
|
||||||
while (it.next(&data, &len)) {
|
|
||||||
file.write(data, len);
|
|
||||||
if (!file)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (posix_ending)
|
|
||||||
file.write("\n", 1);
|
|
||||||
if (!file)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Vase::save_swap() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Vase::insert(Point *point, char key) {
|
|
||||||
uint64_t pos = append->append(key);
|
|
||||||
Shard *inserted = new Petal(1, key == '\n', append, pos);
|
|
||||||
auto [left, right] = Shard::split(root, offset_of(*point));
|
|
||||||
Shard *left2 = Shard::append(left, inserted);
|
Shard *left2 = Shard::append(left, inserted);
|
||||||
Shard::release(left);
|
Shard::release(left);
|
||||||
Shard::release(inserted);
|
Shard::release(inserted);
|
||||||
@@ -223,31 +44,28 @@ void Vase::insert(Point *point, char key) {
|
|||||||
Shard::release(left2);
|
Shard::release(left2);
|
||||||
Shard::release(right);
|
Shard::release(right);
|
||||||
Shard::release(root);
|
Shard::release(root);
|
||||||
root = new_root;
|
|
||||||
if (key == '\n')
|
if (key == '\n')
|
||||||
*point = {point->row + 1, 0};
|
*point = {point->row + 1, 0};
|
||||||
else
|
else
|
||||||
point->col++;
|
point->col++;
|
||||||
|
return new_root;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vase::insert(Point *point, std::string_view str) {
|
Shard *insert(AppendStorage *ap, Shard *root, Point *point, const char *data, uint64_t len) {
|
||||||
insert(point, str.data(), str.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Vase::insert(Point *point, const char *data, uint64_t len) {
|
|
||||||
while (len) {
|
while (len) {
|
||||||
uint64_t chunk_size = std::min<uint64_t>(len, PETAL_SIZE_MAX);
|
uint64_t chunk_size = std::min<uint64_t>(len, PETAL_SIZE_MAX);
|
||||||
_insert(point, data, chunk_size);
|
_insert(ap, &root, point, data, chunk_size);
|
||||||
len -= chunk_size;
|
len -= chunk_size;
|
||||||
data += chunk_size;
|
data += chunk_size;
|
||||||
}
|
}
|
||||||
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vase::_insert(Point *point, const char *data, uint64_t len) {
|
void _insert(AppendStorage *ap, Shard **root, Point *point, const char *data, uint64_t len) {
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
return;
|
return;
|
||||||
uint64_t offset = offset_of(*point);
|
uint64_t offset = offset_of(*root, *point);
|
||||||
uint64_t pos = append->append(data, len);
|
uint64_t pos = ap->append(data, len);
|
||||||
uint64_t lines = 0;
|
uint64_t lines = 0;
|
||||||
const char *start = data;
|
const char *start = data;
|
||||||
const char *last_line = start;
|
const char *last_line = start;
|
||||||
@@ -270,50 +88,23 @@ void Vase::_insert(Point *point, const char *data, uint64_t len) {
|
|||||||
} else {
|
} else {
|
||||||
point->col += col;
|
point->col += col;
|
||||||
}
|
}
|
||||||
Shard *inserted = new Petal(len, lines, append, pos);
|
Shard *inserted = new Petal(len, lines, ap, pos);
|
||||||
auto [left, right] = Shard::split(root, offset);
|
auto [left, right] = Shard::split(*root, offset);
|
||||||
Shard *left2 = Shard::append(left, inserted);
|
Shard *left2 = Shard::append(left, inserted);
|
||||||
Shard::release(left);
|
Shard::release(left);
|
||||||
Shard::release(inserted);
|
Shard::release(inserted);
|
||||||
Shard *new_root = Shard::concat(left2, right);
|
Shard *new_root = Shard::concat(left2, right);
|
||||||
Shard::release(left2);
|
Shard::release(left2);
|
||||||
Shard::release(right);
|
Shard::release(right);
|
||||||
Shard::release(root);
|
Shard::release(*root);
|
||||||
root = new_root;
|
*root = new_root;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vase::erase(Point *point, uint64_t amount, Direction dir) {
|
Shard *erase(Shard *root, Range range) {
|
||||||
if (amount == 0)
|
|
||||||
return;
|
|
||||||
Point start = *point;
|
|
||||||
Point end = *point;
|
|
||||||
if (dir == Direction::Forward)
|
|
||||||
move_clusters(&end, amount, Direction::Forward);
|
|
||||||
else
|
|
||||||
move_clusters(&start, amount, Direction::Backward);
|
|
||||||
uint64_t start_offset = offset_of(start);
|
|
||||||
uint64_t end_offset = offset_of(end);
|
|
||||||
if (start_offset > end_offset)
|
|
||||||
std::swap(start_offset, end_offset);
|
|
||||||
uint64_t count = end_offset - start_offset;
|
|
||||||
auto [a, b] = Shard::split(root, start_offset);
|
|
||||||
auto [d, c] = Shard::split(b, count);
|
|
||||||
Shard *new_root = Shard::concat(a, c);
|
|
||||||
Shard::release(a);
|
|
||||||
Shard::release(b);
|
|
||||||
Shard::release(c);
|
|
||||||
Shard::release(d);
|
|
||||||
Shard::release(root);
|
|
||||||
root = new_root;
|
|
||||||
if (dir == Direction::Backward)
|
|
||||||
*point = start;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Vase::erase(Range range) {
|
|
||||||
Point start = range.start;
|
Point start = range.start;
|
||||||
Point end = range.end;
|
Point end = range.end;
|
||||||
uint64_t start_offset = offset_of(start);
|
uint64_t start_offset = offset_of(root, start);
|
||||||
uint64_t end_offset = offset_of(end);
|
uint64_t end_offset = offset_of(root, end);
|
||||||
uint64_t count = end_offset - start_offset;
|
uint64_t count = end_offset - start_offset;
|
||||||
auto [a, b] = Shard::split(root, start_offset);
|
auto [a, b] = Shard::split(root, start_offset);
|
||||||
auto [d, c] = Shard::split(b, count);
|
auto [d, c] = Shard::split(b, count);
|
||||||
@@ -323,188 +114,214 @@ void Vase::erase(Range range) {
|
|||||||
Shard::release(c);
|
Shard::release(c);
|
||||||
Shard::release(d);
|
Shard::release(d);
|
||||||
Shard::release(root);
|
Shard::release(root);
|
||||||
root = new_root;
|
return new_root;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vase::replace(Range range, std::string_view str) {
|
Shard *replace(AppendStorage *ap, Shard *root, Range range, const char *data, uint64_t len) {
|
||||||
replace(range, str.data(), str.size());
|
root = erase(root, range);
|
||||||
|
return insert(ap, root, &range.start, data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vase::replace(Range range, const char *data, uint64_t len) {
|
uint64_t offset_of(Shard *root, Point point) {
|
||||||
erase(range);
|
return offset_of(root, point.row) + point.col;
|
||||||
insert(&range.start, data, len);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Vase::offset_of(Point point) {
|
uint64_t offset_of(Shard *root, uint64_t line) {
|
||||||
clamp(&point);
|
if (!root)
|
||||||
LineIterator it(root, point.row, Direction::Forward);
|
return 0;
|
||||||
std::string line;
|
if (line == 0)
|
||||||
|
return 0;
|
||||||
|
if (line == root->lines + 1)
|
||||||
|
return root->length;
|
||||||
|
if (line > root->lines + 1)
|
||||||
|
throw ed_error("line out of range");
|
||||||
uint64_t offset = 0;
|
uint64_t offset = 0;
|
||||||
if (it.next(&line)) {
|
Shard *curr = root;
|
||||||
const char *ptr = line.data();
|
while (curr->kind == Shard::Kind::Branch) {
|
||||||
uint64_t remaining = line.length();
|
auto *b = (Branch *)curr;
|
||||||
while (point.col && remaining) {
|
if (line <= b->left->lines) {
|
||||||
uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining);
|
curr = b->left;
|
||||||
remaining -= next_len;
|
} else {
|
||||||
ptr += next_len;
|
line -= b->left->lines;
|
||||||
offset += next_len;
|
offset += b->left->length;
|
||||||
point.col--;
|
curr = b->right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return it.byte_offset() + offset;
|
auto *petal = (Petal *)curr;
|
||||||
|
if (line == 0)
|
||||||
|
return offset;
|
||||||
|
const char *text = petal->source->read(petal->pos);
|
||||||
|
uint64_t local = 0;
|
||||||
|
while (line--) {
|
||||||
|
const char *nl = (const char *)memchr(text + local, '\n', petal->length - local);
|
||||||
|
if (!nl)
|
||||||
|
throw std::runtime_error("leaf line count is wrong.");
|
||||||
|
local = (nl - text) + 1;
|
||||||
|
}
|
||||||
|
return offset + local;
|
||||||
}
|
}
|
||||||
|
|
||||||
Point Vase::point_of(uint64_t offset) {
|
static Shard *newline(AppendStorage *ap) {
|
||||||
PetalIterator it(root, Direction::Backward);
|
uint64_t pos = ap->append('\n');
|
||||||
it.seek_offset(offset);
|
return new Petal(1, true, ap, pos);
|
||||||
Point p;
|
|
||||||
p.row = it.global_line;
|
|
||||||
std::string line;
|
|
||||||
const char *chunk;
|
|
||||||
uint64_t len = 0;
|
|
||||||
if (!it.next(&chunk, &len))
|
|
||||||
return p;
|
|
||||||
while (true) {
|
|
||||||
#if defined(__GLIBC__) || defined(__APPLE__)
|
|
||||||
const char *nl = (const char *)memrchr(chunk, '\n', len);
|
|
||||||
#else
|
|
||||||
const char *nl = nullptr;
|
|
||||||
const char *p = chunk + len;
|
|
||||||
while (!nl && p != chunk)
|
|
||||||
if (*(--p) == '\n')
|
|
||||||
nl = p;
|
|
||||||
#endif
|
|
||||||
if (!nl) {
|
|
||||||
if (len && chunk[len - 1] == '\r')
|
|
||||||
--len;
|
|
||||||
line.insert(0, chunk, len);
|
|
||||||
if (!it.next(&chunk, &len))
|
|
||||||
break;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const char *end = chunk + len;
|
|
||||||
const char *start = nl + 1;
|
|
||||||
const char *line_end = end;
|
|
||||||
if (line_end > start && *(line_end - 1) == '\r')
|
|
||||||
--line_end;
|
|
||||||
line.insert(0, start, line_end - start);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const char *ptr = line.data();
|
|
||||||
uint64_t remaining = line.length();
|
|
||||||
while (remaining) {
|
|
||||||
uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining);
|
|
||||||
remaining -= next_len;
|
|
||||||
ptr += next_len;
|
|
||||||
p.col++;
|
|
||||||
}
|
|
||||||
clamp(&p);
|
|
||||||
return p;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vase::move_clusters(Point *point, uint64_t amount, Direction dir) {
|
Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line) {
|
||||||
if (amount == 0)
|
|
||||||
return;
|
|
||||||
if (dir == Direction::Backward) {
|
|
||||||
LineIterator it(root, point->row, Direction::Backward);
|
|
||||||
while (amount) {
|
|
||||||
std::string line;
|
|
||||||
if (!it.next(&line))
|
|
||||||
return;
|
|
||||||
std::vector<uint64_t> clusters;
|
|
||||||
const char *ptr = line.data();
|
|
||||||
uint64_t remaining = line.size();
|
|
||||||
uint64_t byte = 0;
|
|
||||||
while (remaining) {
|
|
||||||
clusters.push_back(byte);
|
|
||||||
uint64_t len =
|
|
||||||
grapheme_next_character_break_utf8(ptr, remaining);
|
|
||||||
ptr += len;
|
|
||||||
remaining -= len;
|
|
||||||
byte += len;
|
|
||||||
}
|
|
||||||
while (amount && point->col) {
|
|
||||||
point->col--;
|
|
||||||
amount--;
|
|
||||||
}
|
|
||||||
if (amount == 0)
|
|
||||||
return;
|
|
||||||
if (point->row == 0)
|
|
||||||
return;
|
|
||||||
point->row--;
|
|
||||||
point->col = clusters.size();
|
|
||||||
amount--;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LineIterator it(root, point->row, Direction::Forward);
|
|
||||||
while (amount) {
|
|
||||||
std::string line;
|
|
||||||
if (!it.next(&line))
|
|
||||||
return;
|
|
||||||
if (point->col || amount) {
|
|
||||||
const char *ptr = line.data();
|
|
||||||
uint64_t remaining = line.size();
|
|
||||||
uint64_t col = 0;
|
|
||||||
while (col < point->col && remaining) {
|
|
||||||
uint64_t len = grapheme_next_character_break_utf8(ptr, remaining);
|
|
||||||
ptr += len;
|
|
||||||
remaining -= len;
|
|
||||||
col++;
|
|
||||||
}
|
|
||||||
while (amount && remaining) {
|
|
||||||
uint64_t len = grapheme_next_character_break_utf8(ptr, remaining);
|
|
||||||
ptr += len;
|
|
||||||
remaining -= len;
|
|
||||||
point->col++;
|
|
||||||
amount--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (amount) {
|
|
||||||
point->row++;
|
|
||||||
point->col = 0;
|
|
||||||
amount--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clamp(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Vase::clamp(Point *point) {
|
|
||||||
if (!root) {
|
if (!root) {
|
||||||
point->row = 0;
|
if (line != 0)
|
||||||
point->col = 0;
|
throw ed_error("line out of range");
|
||||||
return;
|
Shard::retain(text);
|
||||||
|
return text;
|
||||||
}
|
}
|
||||||
if (point->row > root->lines) {
|
if (line > root->lines + 1)
|
||||||
point->row = root->lines;
|
throw ed_error("line out of range");
|
||||||
point->col = UINT64_MAX;
|
Shard::retain(text);
|
||||||
|
Shard *nl = newline(ap);
|
||||||
|
if (line <= root->lines) {
|
||||||
|
uint64_t offset = offset_of(root, line);
|
||||||
|
auto [left, right] = Shard::split(root, offset);
|
||||||
|
Shard *middle = Shard::concat(text, nl);
|
||||||
|
Shard::release(text);
|
||||||
|
Shard::release(nl);
|
||||||
|
Shard *new_root = Shard::concat(left, middle);
|
||||||
|
Shard::release(left);
|
||||||
|
Shard::release(middle);
|
||||||
|
middle = Shard::concat(new_root, right);
|
||||||
|
Shard::release(new_root);
|
||||||
|
Shard::release(right);
|
||||||
|
Shard::release(root);
|
||||||
|
return middle;
|
||||||
}
|
}
|
||||||
LineIterator it(root, point->row, Direction::Forward);
|
Shard *new_root = Shard::concat(root, nl);
|
||||||
std::string line;
|
Shard::release(root);
|
||||||
uint64_t clusters = 0;
|
Shard::release(nl);
|
||||||
if (it.next(&line)) {
|
Shard *result = Shard::concat(new_root, text);
|
||||||
const char *ptr = line.data();
|
Shard::release(new_root);
|
||||||
uint64_t remaining = line.length();
|
Shard::release(text);
|
||||||
while (remaining) {
|
return result;
|
||||||
uint64_t next_len = grapheme_next_character_break_utf8(ptr, remaining);
|
|
||||||
remaining -= next_len;
|
|
||||||
ptr += next_len;
|
|
||||||
clusters++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (point->col > clusters)
|
|
||||||
point->col = clusters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vase::move_lines(Point *point, uint64_t amount, Direction dir) {
|
Shard *erase(Shard *root, uint64_t start, uint64_t end) {
|
||||||
if (dir == Direction::Forward) {
|
if (!start || !end)
|
||||||
point->row += amount;
|
throw ed_error("Invalid range.");
|
||||||
} else {
|
start--;
|
||||||
if (amount > point->row)
|
end--;
|
||||||
point->row = 0;
|
if (!root)
|
||||||
else
|
throw ed_error("line range out of bounds");
|
||||||
point->row -= amount;
|
uint64_t line_count = root->lines + 1;
|
||||||
|
if (start > end || end >= line_count)
|
||||||
|
throw ed_error("line range out of bounds");
|
||||||
|
uint64_t start_offset = offset_of(root, start);
|
||||||
|
uint64_t end_offset = offset_of(root, end + 1);
|
||||||
|
if (end == root->lines && start_offset)
|
||||||
|
start_offset--;
|
||||||
|
auto [left, rest] = Shard::split(root, start_offset);
|
||||||
|
auto [middle, right] = Shard::split(rest, end_offset - start_offset);
|
||||||
|
Shard *new_root = Shard::concat(left, right);
|
||||||
|
Shard::release(left);
|
||||||
|
Shard::release(rest);
|
||||||
|
Shard::release(middle);
|
||||||
|
Shard::release(right);
|
||||||
|
Shard::release(root);
|
||||||
|
return new_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shard *join(Shard *root, uint64_t start, uint64_t end) {
|
||||||
|
if (!start || !end)
|
||||||
|
throw ed_error("Invalid range.");
|
||||||
|
start--;
|
||||||
|
end--;
|
||||||
|
if (!root)
|
||||||
|
throw ed_error("line range out of bounds");
|
||||||
|
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);
|
||||||
|
auto [a, b] = Shard::split(root, offset);
|
||||||
|
pieces.push_back(a);
|
||||||
|
for (uint64_t line = start; line < end; line++) {
|
||||||
|
uint64_t nl_pos = offset_of(b, 1) - 1;
|
||||||
|
auto [content, rest] = Shard::split(b, nl_pos);
|
||||||
|
Shard::release(b);
|
||||||
|
auto [nl, next_b] = Shard::split(rest, 1);
|
||||||
|
Shard::release(rest);
|
||||||
|
Shard::release(nl);
|
||||||
|
pieces.push_back(content);
|
||||||
|
b = next_b;
|
||||||
}
|
}
|
||||||
clamp(point);
|
pieces.push_back(b);
|
||||||
|
Shard *joined = Shard::build(pieces.data(), 0, pieces.size());
|
||||||
|
Shard::release(root);
|
||||||
|
return joined;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shard *copy(Shard *root, uint64_t start, uint64_t end) {
|
||||||
|
if (!start || !end)
|
||||||
|
throw ed_error("Invalid range.");
|
||||||
|
start--;
|
||||||
|
end--;
|
||||||
|
if (!root)
|
||||||
|
throw ed_error("line range out of bounds");
|
||||||
|
uint64_t line_count = root->lines + 1;
|
||||||
|
if (start > end || end >= line_count)
|
||||||
|
throw ed_error("line range out of bounds");
|
||||||
|
uint64_t start_offset = offset_of(root, start);
|
||||||
|
uint64_t end_offset =
|
||||||
|
(end + 1 == line_count)
|
||||||
|
? root->length
|
||||||
|
: offset_of(root, end + 1) - 1;
|
||||||
|
auto [left, rest] = Shard::split(root, start_offset);
|
||||||
|
auto [middle, right] = Shard::split(rest, end_offset - start_offset);
|
||||||
|
Shard::release(left);
|
||||||
|
Shard::release(rest);
|
||||||
|
Shard::release(right);
|
||||||
|
return middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_file(std::filesystem::path path, Shard *text) {
|
||||||
|
std::ofstream file(path, std::ios::binary);
|
||||||
|
if (!text)
|
||||||
|
return;
|
||||||
|
PetalIterator it(text, Direction::Forward);
|
||||||
|
it.seek_offset(0);
|
||||||
|
const char *data;
|
||||||
|
uint64_t len;
|
||||||
|
while (it.next(&data, &len)) {
|
||||||
|
if (!file)
|
||||||
|
throw ed_error("Failed while writing file: " + path.string());
|
||||||
|
file.write(data, len);
|
||||||
|
}
|
||||||
|
file.write("\n", 1);
|
||||||
|
if (!file)
|
||||||
|
throw ed_error("Failed while writing file: " + path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_command(const char *cmd, Shard *text) {
|
||||||
|
io::IO::cleanup();
|
||||||
|
FILE *pipe = popen(cmd, "w");
|
||||||
|
if (!pipe) {
|
||||||
|
io::IO::enable_raw();
|
||||||
|
throw ed_error("Failed to start command: " + std::string(cmd));
|
||||||
|
}
|
||||||
|
if (!text) {
|
||||||
|
int status = pclose(pipe);
|
||||||
|
if (status == -1)
|
||||||
|
throw ed_error("Failed to wait for command: " + std::string(cmd));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PetalIterator it(text, Direction::Forward);
|
||||||
|
it.seek_offset(0);
|
||||||
|
const char *data;
|
||||||
|
uint64_t len;
|
||||||
|
while (it.next(&data, &len))
|
||||||
|
fwrite(data, 1, len, pipe);
|
||||||
|
fwrite("\n", 1, 1, pipe);
|
||||||
|
int status = pclose(pipe);
|
||||||
|
io::IO::enable_raw();
|
||||||
|
if (status == -1)
|
||||||
|
throw ed_error("Failed to wait for command: " + std::string(cmd));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} // namespace bed::internal::vase
|
} // namespace bed::internal::vase
|
||||||
|
|||||||
+4
-3
@@ -4,16 +4,17 @@
|
|||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
std::vector<std::string> args(argv, argv + argc);
|
std::vector<std::string> args(argv, argv + argc);
|
||||||
try {
|
try {
|
||||||
bed::BEd ed(args);
|
bed::internal::io::IO io = bed::internal::io::IO();
|
||||||
|
bed::BEd ed(args, io);
|
||||||
ed.run();
|
ed.run();
|
||||||
} catch (bed::fatal_error &e) {
|
} catch (bed::fatal_error &e) {
|
||||||
if (e.code)
|
if (e.code)
|
||||||
std::cout << "Fatal error: " << e.what() << std::endl;
|
printf("Fatal error: %s\n", e.what());
|
||||||
return e.code;
|
return e.code;
|
||||||
}
|
}
|
||||||
#ifndef DEBUG
|
#ifndef DEBUG
|
||||||
catch (...) {
|
catch (...) {
|
||||||
std::cout << "Unexpected error." << std::endl;
|
printf("Unexpected error.\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user