Compare commits

...
14 Commits
Author SHA1 Message Date
syedm c0f9c6a234 Make r fucntion allow zero. 2026-08-31 10:16:25 +01:00
syedm 4b0f3b8fb7 Fix first == npos bug. 2026-08-31 10:15:07 +01:00
syedm 24914028c0 Fix incorrect error message. 2026-08-31 10:13:46 +01:00
syedm 0d126021d0 Seperate cd and pwd semantics to mimic shells 2026-08-31 10:13:14 +01:00
syedm e44267a6fe Cleanup io calls and add cd function. 2026-08-31 10:11:29 +01:00
syedm a996beb4aa Add all basic ed functions
- except global and history ones
2026-08-31 08:14:12 +01:00
syedm 010f916d35 Rename buffer.cc. 2026-08-30 19:19:14 +01:00
syedm 81bb65b7cf Add clipboard buffer. 2026-08-30 19:18:26 +01:00
syedm 49bf5a2e2d Make substitutions work and other minor fixes. 2026-08-30 17:02:34 +01:00
syedm 41049b1ab6 Make % address work with @ mode
- add `d` function.
2026-08-30 14:56:08 +01:00
syedm 4a87a1a834 Fix incorrect start of range. 2026-08-30 14:38:49 +01:00
syedm 44e58b3041 Fix off by one error. 2026-08-30 14:36:36 +01:00
syedm 7a18aa0db2 Add text mode handling. 2026-08-30 14:33:39 +01:00
syedm 87d9148b82 Fix segv on uninitialized command fields. 2026-08-30 13:08:48 +01:00
32 changed files with 1996 additions and 815 deletions
+8 -3
View File
@@ -8,6 +8,7 @@
#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/command.h"
#include "internal/ui/text_mode.h"
#include "pch.h" #include "pch.h"
namespace bed { namespace bed {
@@ -22,18 +23,20 @@ struct BEd {
internal::vase::AppendStorage append{"/tmp"}; 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; bool temporary_current = false;
std::string last_help = "";
std::string last_regex = ""; std::string last_regex = "";
std::string last_symbol = ""; std::string last_symbol = "";
std::string last_replacement = ""; 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::Range prev; internal::buffer::Range prev_1;
internal::buffer::Range prev_2;
internal::marks::MarksEngine marks; internal::marks::MarksEngine marks;
BEd(std::vector<std::string> args, internal::io::IO &io); BEd(std::vector<std::string> args, internal::io::IO &io);
@@ -41,10 +44,12 @@ struct BEd {
internal::buffer::Buffer &buffer(const std::string &); internal::buffer::Buffer &buffer(const std::string &);
internal::buffer::Line &current(); internal::buffer::Line &current();
void mark(char, internal::buffer::Line); 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
+3 -73
View File
@@ -1,76 +1,6 @@
#pragma once #pragma once
#include "definitions.h" #include "clip.h"
#include "internal/syntax/parser.h" #include "decl.h"
#include "internal/syntax/ruby/parser.h" #include "generic.h"
#include "internal/theme/theme.h"
#include "pch.h" #include "pch.h"
namespace bed::internal::buffer {
struct Buffer {
enum {
Unmodified,
Modified,
Warned
} state;
std::filesystem::path save_path = "";
vase::Shard *root;
std::string name;
std::string language;
std::optional<syntax::Parser> parser;
explicit Buffer(std::string name);
~Buffer();
uint64_t lines();
uint64_t bytes();
void load(BEd &ctx, vase::Shard *text);
vase::Shard *copy(uint64_t start_line, uint64_t end_line);
void substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
);
void join(BEd &ctx, uint64_t start_line, uint64_t end_line);
void remove(BEd &ctx, uint64_t start_line, uint64_t end_line);
void append(BEd &ctx, vase::Shard *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);
};
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>;
} // namespace bed::internal::buffer
+35
View File
@@ -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 &regex, 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
+146
View File
@@ -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 &regex, 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
+39
View File
@@ -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 &regex, 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
View File
@@ -81,5 +81,6 @@ struct Function {
handle; handle;
static void register_posix(BEd &ctx); static void register_posix(BEd &ctx);
static void register_extented(BEd &ctx);
}; };
} // namespace bed::internal::functions } // namespace bed::internal::functions
+2 -3
View File
@@ -71,12 +71,11 @@ struct IO {
std::pair<uint16_t, uint16_t> cursor_position(); std::pair<uint16_t, uint16_t> cursor_position();
void move_cursor(uint16_t row, uint16_t col); void move_cursor(uint16_t row, uint16_t col);
std::pair<std::string, bool> get_command(BEd &);
std::pair<std::string, bool> get_text(BEd &);
KeyEvent read_key(); KeyEvent read_key();
void write(const char *, uint64_t); void write(const char *, uint64_t);
void write(std::string_view); void write(std::string_view);
void write_line(std::string_view);
void run_pty(const std::string &);
std::deque<char> input_queue; std::deque<char> input_queue;
+3 -3
View File
@@ -65,10 +65,10 @@ struct AddressPromise {
struct Command { struct Command {
bool temp_address{false}; bool temp_address{false};
std::vector<AddressPromise> addresses{}; std::vector<AddressPromise> addresses{};
functions::Function *function; functions::Function *function{nullptr};
functions::Function::Argument argument; functions::Function::Argument argument{std::monostate()};
std::vector<AddressPromise> argument_addresses{}; std::vector<AddressPromise> argument_addresses{};
functions::Suffix *suffix; functions::Suffix *suffix{nullptr};
}; };
struct CompletionContext { struct CompletionContext {
+10
View File
@@ -10,6 +10,10 @@ struct Parser {
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::Shard *, uint64_t, Language); Parser(vase::Shard *, uint64_t, Language);
~Parser(); ~Parser();
@@ -21,6 +25,12 @@ struct Parser {
void insert(vase::Shard *, uint64_t, uint64_t); void insert(vase::Shard *, uint64_t, uint64_t);
void modify(vase::Shard *, 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);
+2 -3
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "internal/io/io.h" #include "definitions.h"
#include "pch.h" #include "pch.h"
namespace bed::internal::ui { namespace bed::internal::ui {
@@ -38,9 +38,8 @@ struct CommandIO {
uint16_t term_height; uint16_t term_height;
uint16_t term_width; uint16_t term_width;
BEd &bed; BEd &bed;
io::IO &io;
CommandIO(BEd &, io::IO &); CommandIO(BEd &);
std::pair<std::string, bool> run(); std::pair<std::string, bool> run();
void redraw(); void redraw();
}; };
+21
View File
@@ -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
+8 -3
View File
@@ -20,8 +20,8 @@ struct Range {
}; };
struct RegexGroup { struct RegexGroup {
uint64_t start{0}; uint64_t start{UINT64_MAX};
uint64_t end{0}; uint64_t end{UINT64_MAX};
}; };
struct RegexMatch { struct RegexMatch {
@@ -50,6 +50,8 @@ Shard *insert(AppendStorage *ap, Shard *root, Shard *text, uint64_t line);
Shard *erase(Shard *root, uint64_t start, uint64_t end); Shard *erase(Shard *root, uint64_t start, uint64_t end);
Shard *join(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); 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_next(Shard *root, std::string_view pattern, uint64_t start);
uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start); uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start);
@@ -57,7 +59,10 @@ uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start);
Shard *substitute( Shard *substitute(
AppendStorage *ap, Shard *root, AppendStorage *ap, Shard *root,
std::string_view pattern, uint64_t start, uint64_t end, std::string_view pattern, uint64_t start, uint64_t end,
std::string_view replace, std::string_view options 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 // internal
+1 -1
View File
@@ -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>
+61 -10
View File
@@ -5,6 +5,7 @@ namespace bed {
BEd::BEd(std::vector<std::string> args, internal::io::IO &io) BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
: theme(internal::theme::Theme::default_theme()), io(io) { : theme(internal::theme::Theme::default_theme()), io(io) {
internal::functions::Function::register_posix(*this); internal::functions::Function::register_posix(*this);
internal::functions::Function::register_extented(*this);
internal::functions::Suffix::register_suffixes(*this); internal::functions::Suffix::register_suffixes(*this);
std::string prompt_ = ""; std::string prompt_ = "";
std::string file = ""; std::string file = "";
@@ -17,6 +18,8 @@ BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
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);
@@ -28,13 +31,15 @@ BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
else else
prompt_mode = false; prompt_mode = false;
suppress_mode = suppress; suppress_mode = suppress;
buffers["clip"] = new internal::buffer::ClipBuffer("clip");
current() = {"default", 0};
try { try {
if (file != "") if (file != "")
handle(":default:E " + file, false); handle(":default:E " + file, false);
} catch (ed_error &e) { } catch (ed_error &e) {
std::cout << "?" << std::endl; io.write_line("?");
if (help_mode) if (help_mode)
std::cout << e.what() << std::endl; io.write_line(e.what());
last_help = e.what(); last_help = e.what();
} }
} }
@@ -46,13 +51,14 @@ BEd::~BEd() {
void BEd::run() { void BEd::run() {
while (true) { while (true) {
auto [cmd, eof] = io.get_command(*this); internal::ui::CommandIO command(*this);
auto [cmd, eof] = command.run();
try { try {
handle(cmd, eof); handle(cmd, eof);
} catch (ed_error &e) { } catch (ed_error &e) {
std::cout << "?" << std::endl; io.write_line("?");
if (help_mode) if (help_mode)
std::cout << e.what() << std::endl; io.write_line(e.what());
last_help = e.what(); last_help = e.what();
} }
} }
@@ -66,6 +72,7 @@ void BEd::handle(std::string_view cmd, bool eof) {
internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this); internal::parser::Command c = internal::parser::Parser::get_command(cmd, *this);
if (c.temp_address) { if (c.temp_address) {
marks.get(251) = marks.get(250); marks.get(251) = marks.get(250);
prev_2 = prev_1;
temporary_current = true; temporary_current = true;
} }
internal::buffer::Address address; internal::buffer::Address address;
@@ -102,12 +109,16 @@ void BEd::handle(std::string_view cmd, bool eof) {
} break; } break;
} }
if (std::holds_alternative<internal::buffer::Line>(c.argument)) { 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); auto a = internal::parser::AddressPromise::get_line(*this, c.argument_addresses);
if (a.has_value()) if (a.has_value())
c.argument = *a; c.argument = *a;
else else
c.argument = current(); c.argument = current();
} else if (std::holds_alternative<internal::buffer::Range>(c.argument)) { } 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); auto a = internal::parser::AddressPromise::get_range(*this, c.argument_addresses);
if (a.has_value()) if (a.has_value())
c.argument = *a; c.argument = *a;
@@ -124,15 +135,22 @@ void BEd::handle(std::string_view cmd, bool eof) {
throw ed_error("Line number can't be zero."); throw ed_error("Line number can't be zero.");
} }
} }
c.function->handle(*this, address, nullptr, c.argument, nullptr); 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) if (c.suffix)
c.suffix->handle(*this); c.suffix->handle(*this);
if (c.temp_address) if (c.temp_address)
temporary_current = false; temporary_current = false;
for (auto it = buffers.begin(); it != buffers.end();) { for (auto it = buffers.begin(); it != buffers.end();) {
internal::buffer::Buffer *buf = it->second; internal::buffer::Buffer *buf = it->second;
if (buf->save_path.empty() if (buf->waste()) {
&& buf->root == nullptr) {
delete buf; delete buf;
it = buffers.erase(it); it = buffers.erase(it);
} else { } else {
@@ -147,7 +165,7 @@ internal::buffer::Buffer &BEd::buffer(const std::string &name) {
auto it = buffers.find(name); auto it = buffers.find(name);
if (it != buffers.end()) if (it != buffers.end())
return *it->second; return *it->second;
auto *buf = new internal::buffer::Buffer(name); auto *buf = new internal::buffer::GenericBuffer(name);
buffers.emplace(name, buf); buffers.emplace(name, buf);
return *buf; return *buf;
} }
@@ -156,7 +174,40 @@ internal::buffer::Line &BEd::current() {
return marks.get(250 + temporary_current); return marks.get(250 + temporary_current);
} }
void BEd::mark(char m, internal::buffer::Line line) { 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; 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
-267
View File
@@ -1,267 +0,0 @@
#include "internal/buffer/buffer.h"
#include "bed.h"
namespace bed::internal::buffer {
Buffer::Buffer(std::string name)
: state(Unmodified), root(nullptr), name(name) {
}
Buffer::~Buffer() {
vase::Shard::release(root);
}
uint64_t Buffer::lines() {
if (root)
return root->lines + 1;
return 0;
}
uint64_t Buffer::bytes() {
if (root)
return root->length + 1;
return 0;
}
void Buffer::load(BEd &, vase::Shard *text) {
try {
vase::Shard::release(root);
root = text;
state = buffer::Buffer::Unmodified;
parser.emplace(root, lines(), syntax::ruby::lang_ruby());
} catch (...) {
vase::Shard::release(text);
throw;
}
}
void Buffer::append(BEd &ctx, vase::Shard *text, uint64_t line) {
ctx.prev.buffername = name;
ctx.prev.start = line;
ctx.prev.end = line + text->lines;
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 Buffer::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 = lines() ? 1 : 0;
ctx.prev.end = 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 Buffer::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 Buffer::substitute(
BEd &ctx, uint64_t start_line, uint64_t end_line,
std::string &regex, std::string &replacement, std::string &options
) {
// TODO: make substitue return a list of modifications made.
root = vase::substitute(&ctx.append, root, regex, start_line, end_line, replacement, options);
/*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);*/
state = Modified;
}
vase::Shard *Buffer::copy(uint64_t start_line, uint64_t end_line) {
return vase::copy(root, start_line, end_line);
}
inline void apply(std::ostream &out, const 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 & Highlight::Bold)
out << "\x1b[1m";
if (hl.flags & Highlight::Italic)
out << "\x1b[3m";
if (hl.flags & Highlight::Underline)
out << "\x1b[4m";
if (hl.flags & 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) {
ctx.prev.buffername = name;
ctx.prev.start = start_line;
ctx.prev.end = end_line;
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();
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(root, 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) {
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();
std::cout << std::setw(width) << start_line << "\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(root, 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
+270
View File
@@ -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 &regex, 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
+185
View File
@@ -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 &regex, 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
+58
View File
@@ -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
-296
View File
@@ -1,296 +0,0 @@
#include "internal/functions/functions.h"
#include "bed.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);
}
};
}
void Function::register_posix(BEd &ctx) {
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 = 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::Line>(addr_);
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> *) {
for (auto &[name, buffer] : ctx.buffers) {
if (buffer->state == buffer::Buffer::Modified) {
buffer->state = buffer::Buffer::Warned;
throw ed_error("Buffer " + name + " modified.");
}
}
throw fatal_error("Quitting", 0);
}
};
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(
"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> *) {
for (auto &[name, buffer] : ctx.buffers) {
if (buffer->state == buffer::Buffer::Modified) {
buffer->state = buffer::Buffer::Warned;
throw ed_error("Buffer " + name + " modified.");
}
}
throw fatal_error("Quitting", 0);
}
}
);
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(
"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(
"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(
"=",
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)
std::cout << ':' << addr.buffername << ':' << addr.start << "\n";
else
std::cout << ':' << addr.buffername << ':' << addr.start << "," << addr.end << "\n";
ctx.current() = {addr.buffername, addr.end};
},
}
);
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(
"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)) {
s = vase::Shard::from_file(std::get<std::filesystem::path>(arg), true);
buf.save_path = std::get<std::filesystem::path>(arg);
} else if (std::holds_alternative<ShellArg>(arg)) {
s = vase::Shard::from_command(std::get<ShellArg>(arg).cmd.c_str(), true);
} else if (std::holds_alternative<std::monostate>(arg)) {
if (buf.save_path != "")
s = vase::Shard::from_file(buf.save_path, true);
else
throw ed_error("Need filename to load.");
}
buf.load(ctx, s);
std::cout << buf.bytes() << std::endl;
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)) {
s = vase::Shard::from_file(std::get<std::filesystem::path>(arg), true);
buf.save_path = std::get<std::filesystem::path>(arg);
} else if (std::holds_alternative<ShellArg>(arg)) {
s = vase::Shard::from_command(std::get<ShellArg>(arg).cmd.c_str(), true);
} else if (std::holds_alternative<std::monostate>(arg)) {
if (buf.save_path != "")
s = vase::Shard::from_file(buf.save_path, true);
else
throw ed_error("Need filename to load.");
}
buf.load(ctx, s);
std::cout << buf.bytes() << std::endl;
ctx.current() = {addr, buf.lines()};
}
}
);
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)
std::cout << ctx.last_help << std::endl;
}
}
);
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> *) {
std::cout << ctx.last_help << std::endl;
}
}
);
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 "*"; };
}
}
}
);
}
} // namespace bed::internal::functions
+657
View File
@@ -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
-10
View File
@@ -1,10 +0,0 @@
#include "internal/ui/command.h"
#include "bed.h"
#include "internal/io/io.h"
namespace bed::internal::io {
std::pair<std::string, bool> IO::get_command(BEd &ctx) {
ui::CommandIO cio(ctx, *this);
return cio.run();
}
} // namespace bed::internal::io
+72
View File
@@ -105,4 +105,76 @@ void IO::write(const char *buf, uint64_t n) {
void IO::write(std::string_view s) { void IO::write(std::string_view s) {
write_all(STDOUT_FILENO, s.data(), s.size()); 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 } // namespace bed::internal::io
-28
View File
@@ -1,28 +0,0 @@
#include "bed.h"
#include "internal/io/io.h"
namespace bed::internal::io {
std::pair<std::string, bool> IO::get_text(BEd &) {
uint16_t start;
uint16_t height;
{
auto [row, col] = cursor_position();
auto [rows, cols] = terminal_size();
if (row > rows)
throw fatal_error("Invalid cursor position.", 1);
start = row;
height = rows - row + 1;
}
enable_mouse();
// uint16_t width = cols;
disable_mouse();
for (uint16_t i = 1; i < height; ++i) {
move_cursor(start + i, 1);
write_all(STDOUT_FILENO, "\x1b[2K", 4);
}
move_cursor(start, 1);
write_all(STDOUT_FILENO, "\n", 1);
return {"", true};
}
} // namespace bed::internal::io
+24 -30
View File
@@ -44,9 +44,9 @@ buffer::Line AddressPromise::resolve(BEd &ctx) {
if (re.size() == 0) if (re.size() == 0)
throw ed_error("No regex given."); throw ed_error("No regex given.");
if (addr.dir == Direction::Forward) if (addr.dir == Direction::Forward)
line.number = vase::find_next(buf.root, re, line.number); line.number = buf.find_next(re, line.number);
else else
line.number = vase::find_prev(buf.root, re, line.number); line.number = buf.find_prev(re, line.number);
ctx.last_regex = re; ctx.last_regex = re;
} else if constexpr (std::is_same_v<T, Block>) { } else if constexpr (std::is_same_v<T, Block>) {
if (line.buffername == ctx.current().buffername) if (line.buffername == ctx.current().buffername)
@@ -55,28 +55,10 @@ buffer::Line AddressPromise::resolve(BEd &ctx) {
line.number = buf.lines(); line.number = buf.lines();
if (buf.lines() > 0 && line.number == 0) if (buf.lines() > 0 && line.number == 0)
line.number = 1; line.number = 1;
if (addr.dir == Direction::Forward) { if (addr.dir == Direction::Forward)
if (buf.parser.has_value()) { line.number = buf.next_closing(line.number);
uint64_t closing = buf.parser->next_closing(line.number - 1);
if (closing == UINT64_MAX)
line.number = buf.lines();
else else
line.number = closing + 1; line.number = buf.prev_closing(line.number);
} else {
line.number += 10;
if (line.number > buf.lines())
line.number = buf.lines();
}
} else {
if (buf.parser.has_value()) {
line.number = buf.parser->prev_opening(line.number - 1) + 1;
} else {
if (line.number > 10)
line.number -= 10;
else
line.number = 0;
}
}
} else { } else {
throw ed_error("Unhandled address given."); throw ed_error("Unhandled address given.");
} }
@@ -119,8 +101,8 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
curr.offset = 0; curr.offset = 0;
prev_given = false; prev_given = false;
} else if (std::holds_alternative<LastRange>(curr.base)) { } else if (std::holds_alternative<LastRange>(curr.base)) {
curr.bufname = ctx.prev.buffername; curr.bufname = ctx.prev().buffername;
curr.base = Number(ctx.prev.end); curr.base = Number(ctx.prev().end);
prev_given = true; prev_given = true;
} else { } else {
prev_given = true; prev_given = true;
@@ -146,8 +128,8 @@ std::optional<buffer::Line> AddressPromise::get_line(BEd &ctx, std::vector<Addre
} }
} }
} else if (std::holds_alternative<LastRange>(curr.base)) { } else if (std::holds_alternative<LastRange>(curr.base)) {
curr.bufname = ctx.prev.buffername; curr.bufname = ctx.prev().buffername;
curr.base = Number(ctx.prev.end); curr.base = Number(ctx.prev().end);
} }
return curr.resolve(ctx); return curr.resolve(ctx);
} }
@@ -182,8 +164,8 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
curr.offset = 0; curr.offset = 0;
prev_given = false; prev_given = false;
} else if (std::holds_alternative<LastRange>(curr.base)) { } else if (std::holds_alternative<LastRange>(curr.base)) {
curr.bufname = ctx.prev.buffername; curr.bufname = ctx.prev().buffername;
curr.base = Number(ctx.prev.end); curr.base = Number(ctx.prev().end);
prev_given = true; prev_given = true;
} else { } else {
prev_given = true; prev_given = true;
@@ -210,7 +192,19 @@ std::optional<buffer::Range> AddressPromise::get_range(BEd &ctx, std::vector<Add
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx)); return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
} }
} else if (std::holds_alternative<LastRange>(curr.base)) { } else if (std::holds_alternative<LastRange>(curr.base)) {
return ctx.prev; 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) if (prev_given)
return buffer::Range(prev.resolve(ctx), curr.resolve(ctx)); return buffer::Range(prev.resolve(ctx), curr.resolve(ctx));
+8 -5
View File
@@ -103,6 +103,8 @@ void Parser::operation() {
command->argument = functions::Function::GlobalArg(delim, std::move(val)); command->argument = functions::Function::GlobalArg(delim, std::move(val));
} break; } break;
case functions::Function::ArgumentKind::File: case functions::Function::ArgumentKind::File:
if (!(peek() == '\t' || peek() == ' ' || peek() == '\0'))
throw ed_error("Incorrect file input usage.");
skip_ws(); skip_ws();
switch (peek()) { switch (peek()) {
case '!': case '!':
@@ -132,7 +134,7 @@ void Parser::operation() {
if (delim == '\0') if (delim == '\0')
throw ed_error("regex expected"); throw ed_error("regex expected");
advance(); advance();
uint64_t j = 0; uint16_t j = 0;
while (true) { while (true) {
if (peek(j) == '\0') if (peek(j) == '\0')
throw ed_error("Unterminated regex"); throw ed_error("Unterminated regex");
@@ -141,7 +143,7 @@ void Parser::operation() {
else if (peek(j) == '\\') else if (peek(j) == '\\')
j += 2; j += 2;
else if (peek(j) == '[') else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']') while (peek(j) != '\0' && peek(j) != ']')
j++; j++;
else else
j++; j++;
@@ -157,15 +159,16 @@ void Parser::operation() {
else if (peek(j) == '\\') else if (peek(j) == '\\')
j += 2; j += 2;
else if (peek(j) == '[') else if (peek(j) == '[')
while (peek(j) != '\0' && cmd[i] != ']') while (peek(j) != '\0' && peek(j) != ']')
j++; j++;
else else
j++; j++;
} }
std::string replacement; std::string replacement(peek_str(j));
if (peek(j) != '\0') { if (peek(j) != '\0') {
replacement = peek_str(j);
advance(j + 1); advance(j + 1);
} else {
advance(j);
} }
std::string options; std::string options;
if (peek() != '\0') { if (peek() != '\0') {
+63 -27
View File
@@ -123,36 +123,15 @@ ParseState *Parser::join_tree(ParseState *a, ParseState *b) {
} }
void Parser::erase(vase::Shard *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::Shard *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::Shard *vase, uint64_t target, uint64_t count) { void Parser::modify(vase::Shard *vase, uint64_t target, uint64_t count) {
@@ -219,6 +198,63 @@ void Parser::modify(vase::Shard *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;
+14 -15
View File
@@ -62,15 +62,15 @@ static std::string current_word(const std::string &line, size_t byte_pos) {
return line.substr(start, byte_pos - start); return line.substr(start, byte_pos - start);
}*/ }*/
CommandIO::CommandIO(BEd &bed, io::IO &io) : bed(bed), io(io) { CommandIO::CommandIO(BEd &bed) : bed(bed) {
if (bed.prompt_mode) if (bed.prompt_mode)
prompt = bed.prompt(bed); prompt = bed.prompt(bed);
cursor = 0; cursor = 0;
} }
std::pair<std::string, bool> CommandIO::run() { std::pair<std::string, bool> CommandIO::run() {
auto [row, col] = io.cursor_position(); auto [row, col] = bed.io.cursor_position();
auto [rows, cols] = io.terminal_size(); auto [rows, cols] = bed.io.terminal_size();
if (row > rows) if (row > rows)
throw fatal_error("Invalid cursor position.", 1); throw fatal_error("Invalid cursor position.", 1);
start = row; start = row;
@@ -79,11 +79,10 @@ std::pair<std::string, bool> CommandIO::run() {
term_height = rows; term_height = rows;
redraw(); redraw();
// main loop.
io::KeyEvent res; io::KeyEvent res;
bool running = true; bool running = true;
while (running) { while (running) {
res = io.read_key(); res = bed.io.read_key();
switch (res.type) { switch (res.type) {
case io::KeyEvent::KeyType::EOF_: case io::KeyEvent::KeyType::EOF_:
running = false; running = false;
@@ -139,20 +138,20 @@ std::pair<std::string, bool> CommandIO::run() {
} }
for (uint16_t i = 1; i < height; ++i) { for (uint16_t i = 1; i < height; ++i) {
io.move_cursor(start + i, 1); bed.io.move_cursor(start + i, 1);
io.write("\x1b[2K", 4); bed.io.write("\x1b[2K", 4);
} }
io.move_cursor(start, 1); bed.io.move_cursor(start, 1);
io.write("\n", 1); bed.io.write("\n", 1);
return {cmd, false}; return {cmd, false};
} }
void CommandIO::redraw() { void CommandIO::redraw() {
io.move_cursor(start, 1); bed.io.move_cursor(start, 1);
io.write("\x1b[2K", 4); bed.io.write("\x1b[2K", 4);
io.move_cursor(start, 1); bed.io.move_cursor(start, 1);
io.write(prompt); bed.io.write(prompt);
io.write(cmd); bed.io.write(cmd);
io.move_cursor(start, prompt.size() + cursor + 1); bed.io.move_cursor(start, prompt.size() + cursor + 1);
} }
} // namespace bed::internal::ui } // namespace bed::internal::ui
+188
View File
@@ -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
+54 -22
View File
@@ -25,12 +25,7 @@ std::vector<ReplacePart> parse_replace(AppendStorage *ap, 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<ReplacePart> parse_replace(AppendStorage *ap, 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<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s) {
.value = (uint8_t)(next - '0') .value = (uint8_t)(next - '0')
} }
); );
} else if (next == 'n') {
constant.push_back('\n');
} else {
constant.push_back(next);
}
++i; ++i;
continue; 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);
} }
@@ -64,7 +69,8 @@ std::vector<ReplacePart> parse_replace(AppendStorage *ap, std::string_view s) {
Shard *substitute( Shard *substitute(
AppendStorage *ap, Shard *root, AppendStorage *ap, Shard *root,
std::string_view pattern, uint64_t start, uint64_t end, std::string_view pattern, uint64_t start, uint64_t end,
std::string_view replace, std::string_view options 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
) { ) {
if (!start || !end) if (!start || !end)
throw ed_error("Invalid range."); throw ed_error("Invalid range.");
@@ -79,11 +85,18 @@ Shard *substitute(
uint64_t end_offset = uint64_t end_offset =
(end + 1 == line_count) (end + 1 == line_count)
? root->length ? root->length
: offset_of(root, end + 1); : offset_of(root, end + 1) - 1;
std::vector<RegexMatch> matches = _regex_search(root, pattern, start_offset, end_offset, options); std::vector<RegexMatch> matches = _regex_search(root, pattern, start_offset, end_offset, options);
if (matches.empty()) if (matches.empty())
return root; return root;
std::vector<ReplacePart> replace_parts = parse_replace(ap, replace); std::vector<ReplacePart> replace_parts = parse_replace(ap, 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;
@@ -96,26 +109,33 @@ Shard *substitute(
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);
@@ -124,6 +144,7 @@ Shard *substitute(
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;
@@ -131,6 +152,13 @@ Shard *substitute(
} }
} }
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);
@@ -151,7 +179,9 @@ Shard *substitute(
} }
uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) { uint64_t find_next(Shard *root, std::string_view pattern, uint64_t start) {
if (start == 0 || start > root->lines) if (!root)
throw ed_error("Invalid line number.");
if (start == 0 || start > root->lines + 1)
throw ed_error("Invalid line number."); throw ed_error("Invalid line number.");
start--; start--;
std::vector<RegexMatch> results; std::vector<RegexMatch> results;
@@ -180,7 +210,7 @@ uint64_t find_next(Shard *root, 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);
@@ -198,7 +228,7 @@ uint64_t find_next(Shard *root, 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);
@@ -213,7 +243,9 @@ 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) { uint64_t find_prev(Shard *root, std::string_view pattern, uint64_t start) {
if (start == 0 || start > root->lines) if (!root)
throw ed_error("Invalid line number.");
if (start == 0 || start > root->lines + 1)
throw ed_error("Invalid line number."); throw ed_error("Invalid line number.");
start--; start--;
std::vector<RegexMatch> results; std::vector<RegexMatch> results;
@@ -242,7 +274,7 @@ uint64_t find_prev(Shard *root, 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);
@@ -260,7 +292,7 @@ uint64_t find_prev(Shard *root, 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);
+3 -3
View File
@@ -64,7 +64,7 @@ std::vector<RegexMatch> _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],
@@ -74,8 +74,8 @@ std::vector<RegexMatch> _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));
+55 -8
View File
@@ -1,4 +1,5 @@
#include "internal/vase/vase.h" #include "internal/vase/vase.h"
#include "internal/io/io.h"
namespace bed::internal::vase { namespace bed::internal::vase {
std::string to_string(Shard *root) { std::string to_string(Shard *root) {
@@ -128,7 +129,11 @@ uint64_t offset_of(Shard *root, Point point) {
uint64_t offset_of(Shard *root, uint64_t line) { uint64_t offset_of(Shard *root, uint64_t line) {
if (!root) if (!root)
return 0; return 0;
if (line > root->lines) 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"); throw ed_error("line out of range");
uint64_t offset = 0; uint64_t offset = 0;
Shard *curr = root; Shard *curr = root;
@@ -207,10 +212,9 @@ Shard *erase(Shard *root, uint64_t start, uint64_t end) {
if (start > end || end >= line_count) if (start > end || end >= line_count)
throw ed_error("line range out of bounds"); throw ed_error("line range out of bounds");
uint64_t start_offset = offset_of(root, start); uint64_t start_offset = offset_of(root, start);
uint64_t end_offset = uint64_t end_offset = offset_of(root, end + 1);
(end + 1 == line_count) if (end == root->lines && start_offset)
? root->length start_offset--;
: offset_of(root, end + 1);
auto [left, rest] = Shard::split(root, start_offset); auto [left, rest] = Shard::split(root, start_offset);
auto [middle, right] = Shard::split(rest, end_offset - start_offset); auto [middle, right] = Shard::split(rest, end_offset - start_offset);
Shard *new_root = Shard::concat(left, right); Shard *new_root = Shard::concat(left, right);
@@ -232,7 +236,6 @@ Shard *join(Shard *root, uint64_t start, uint64_t end) {
uint64_t line_count = root->lines + 1; uint64_t line_count = root->lines + 1;
if (start >= end || end >= line_count) if (start >= end || end >= line_count)
throw ed_error("line range out of bounds"); throw ed_error("line range out of bounds");
uint64_t offset = offset_of(root, start); uint64_t offset = offset_of(root, start);
std::vector<Shard *> pieces; std::vector<Shard *> pieces;
pieces.reserve(end - start + 3); pieces.reserve(end - start + 3);
@@ -268,13 +271,57 @@ Shard *copy(Shard *root, uint64_t start, uint64_t end) {
uint64_t end_offset = uint64_t end_offset =
(end + 1 == line_count) (end + 1 == line_count)
? root->length ? root->length
: offset_of(root, end + 1); : offset_of(root, end + 1) - 1;
auto [left, rest] = Shard::split(root, start_offset); auto [left, rest] = Shard::split(root, start_offset);
auto [middle, right] = Shard::split(rest, end_offset - start_offset); auto [middle, right] = Shard::split(rest, end_offset - start_offset);
Shard::release(left); Shard::release(left);
Shard::release(rest); Shard::release(rest);
Shard::release(right); Shard::release(right);
Shard::release(root);
return middle; 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
+2 -2
View File
@@ -9,12 +9,12 @@ int main(int argc, char *argv[]) {
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