Compare commits

...
5 Commits
Author SHA1 Message Date
syedm cd9583ec34 Honor suppress mode.
- Allow P command to create a new prompt string.
2026-09-03 09:53:14 +01:00
syedm 0104f96d51 Add comments and echo commands. 2026-09-03 09:52:33 +01:00
syedm abbfcc352a Support piped input. 2026-09-03 09:52:13 +01:00
syedm f8975a60d8 Fix regex and current() system. 2026-09-03 07:42:50 +01:00
syedm 33dc9ad507 Fix bugs with history setup. 2026-09-03 06:59:55 +01:00
13 changed files with 225 additions and 60 deletions
+2 -4
View File
@@ -14,10 +14,8 @@ struct HistoryItem {
struct GenericBuffer : ShardBuffer {
uint64_t base_version{0};
std::chrono::system_clock::time_point timestamp{
std::chrono::system_clock::now()
};
std::string action{"created"};
std::chrono::system_clock::time_point timestamp;
std::string action;
std::filesystem::path save_path{};
std::vector<HistoryItem> undo_stack;
std::vector<HistoryItem> redo_stack;
+13 -2
View File
@@ -58,12 +58,23 @@ struct IO {
static volatile std::atomic_bool resized;
static void handle_sigwinch(int);
static enum struct Mode {
PIPE,
TERMINAL
} mode;
std::string pipe_input;
std::deque<char> input_queue;
IO();
~IO();
IO(const IO &) = delete;
IO &operator=(const IO &) = delete;
bool interactive() const {
return mode == Mode::TERMINAL;
}
void enable_mouse();
void disable_mouse();
@@ -71,14 +82,14 @@ struct IO {
std::pair<uint16_t, uint16_t> cursor_position();
void move_cursor(uint16_t row, uint16_t col);
std::pair<std::string, bool> read_pipe();
KeyEvent read_key();
void write(const char *, uint64_t);
void write(std::string_view);
void write_line(std::string_view);
void run_pty(const std::string &);
std::deque<char> input_queue;
KeyEvent::ReadResult get_next_byte(char &out);
void enqueue_bytes(const std::string &bytes);
static int utf8_seq_len(uint8_t byte);
+2
View File
@@ -41,6 +41,8 @@ struct CommandIO {
CommandIO(BEd &);
std::pair<std::string, bool> run();
std::pair<std::string, bool> run_pipe();
std::pair<std::string, bool> run_terminal();
void redraw();
};
} // namespace bed::internal::ui
+2
View File
@@ -15,6 +15,8 @@ struct TextMode {
TextMode(BEd &);
std::pair<vase::Shard *, bool> run();
std::pair<vase::Shard *, bool> run_pipe();
std::pair<vase::Shard *, bool> run_terminal();
void grow();
void redraw();
};
+28 -14
View File
@@ -39,7 +39,7 @@ void GenericBuffer::list_history(BEd &ctx) {
std::tm tm = *std::localtime(&time);
ctx.io.write_line(
std::format(
"X {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
"* {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
current,
tm.tm_year + 1900,
tm.tm_mon + 1,
@@ -58,7 +58,7 @@ void GenericBuffer::list_history(BEd &ctx) {
std::tm tm = *std::localtime(&time);
ctx.io.write_line(
std::format(
" {} {:04}-{:02}-{:02} {:02}:{:02}:{:02}",
" {} {:04}-{:02}-{:02} {:02}:{:02}:{:02} {}",
version,
tm.tm_year + 1900,
tm.tm_mon + 1,
@@ -90,15 +90,19 @@ HistoryBuffer *GenericBuffer::get_history(uint64_t version) {
}
void GenericBuffer::snapshot(std::string action_) {
vase::Shard::retain(root);
undo_stack.push_back(
HistoryItem{
syntax::retain(parse),
root,
timestamp,
action
}
);
if (base_version == 0) {
base_version++;
} else {
vase::Shard::retain(root);
undo_stack.push_back(
HistoryItem{
syntax::retain(parse),
root,
timestamp,
action
}
);
}
for (auto &item : redo_stack) {
syntax::release(item.parse_state);
vase::Shard::release(item.text);
@@ -126,6 +130,8 @@ bool GenericBuffer::undo(BEd &ctx) {
root = prev.text;
syntax::release(parse);
parse = prev.parse_state;
timestamp = prev.timestamp;
action = prev.summary;
state = Modified;
if (!root) {
ctx.prev().buffername = name;
@@ -157,6 +163,8 @@ bool GenericBuffer::redo(BEd &ctx) {
root = next.text;
syntax::release(parse);
parse = next.parse_state;
timestamp = next.timestamp;
action = next.summary;
state = Modified;
if (!root) {
ctx.prev().buffername = name;
@@ -213,6 +221,7 @@ void GenericBuffer::load(BEd &ctx, vase::Shard *text) {
ctx.prev().start = 1;
ctx.prev().end = text->lines + 1;
}
ctx.current() = {name, lines()};
syntax::release(parse);
parse = syntax::make_parser(root, lines(), ctx.languages["ruby"]);
}
@@ -232,6 +241,7 @@ 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;
ctx.current() = {name, line + text->lines + 1};
root = vase::insert(&ctx.append, root, text, line);
ctx.marks.insert(name, line, text->lines + 1);
if (parse.lang)
@@ -243,8 +253,10 @@ void GenericBuffer::remove(BEd &ctx, uint64_t start_line, uint64_t end_line) {
snapshot(std::format("Remove lines {} to {}", start_line, 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());
uint64_t l = std::min(start_line, lines());
ctx.prev().start = l;
ctx.prev().end = l;
ctx.current() = {name, l};
ctx.marks.erase(name, start_line, end_line - start_line + 1);
if (parse.lang)
syntax::erase(parse, root, start_line, end_line - start_line + 1);
@@ -260,6 +272,7 @@ void GenericBuffer::replace(BEd &ctx, vase::Shard *text, uint64_t start_line, ui
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = start_line + text->lines;
ctx.current() = {name, start_line + text->lines};
uint64_t new_count = text->lines + 1;
uint64_t old_count = end_line - start_line + 1;
root = vase::replace(root, text, start_line, end_line);
@@ -285,6 +298,7 @@ void GenericBuffer::join(BEd &ctx, uint64_t start_line, uint64_t end_line) {
ctx.prev().buffername = name;
ctx.prev().start = start_line;
ctx.prev().end = start_line;
ctx.current() = {name, start_line};
ctx.marks.collapse(name, start_line, end_line - start_line);
if (parse.lang)
syntax::erase(parse, root, start_line, end_line - start_line);
@@ -321,11 +335,11 @@ void GenericBuffer::substitute(
if (edit)
edit->insert(line, new_lines);
}
ctx.current() = {name, line + new_lines};
}
);
if (edit)
edit->commit(root);
ctx.prev().buffername = name;
state = Modified;
}
} // namespace bed::internal::buffer
+74 -2
View File
@@ -4,6 +4,79 @@
namespace bed::internal::functions {
void Function::register_extented(BEd &ctx) {
ctx.functions.insert(
"#",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Comment.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
.handle = [](
BEd &,
const buffer::Address &,
vase::Shard *,
const Argument &,
std::vector<buffer::Line> *
) {},
}
);
ctx.functions.insert(
"echo",
Function{
.address_kind = Function::AddressKind::Range,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Echo given message.",
.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::Range>(addr_);
auto str = std::get<std::string>(arg_);
const auto first = str.find_first_not_of(" \t");
const auto last = str.find_last_not_of(" \t");
if (first == std::string::npos)
str.clear();
else
str = str.substr(first, last - first + 1);
for (size_t i = 0; i < str.size();) {
if (str[i] == '\\') {
if (i + 1 >= str.size())
break;
str.erase(i++, 1);
if (str[i - 1] == 'n')
str[i - 1] = '\n';
continue;
}
if (str[i] == '$' && i < str.size() && '1' <= str[i + 1] && str[i + 1] <= '2') {
bool one = str[i + 1] == '1';
str.erase(i, 2);
if (one) {
auto start = std::to_string(addr.start);
str.insert(i, start);
i += start.size();
} else {
auto end = std::to_string(addr.end);
str.insert(i, end);
i += end.size();
}
continue;
}
i++;
}
ctx.io.write_line(str);
},
}
);
ctx.functions.insert(
"cd",
Function{
@@ -93,7 +166,6 @@ void Function::register_extented(BEd &ctx) {
vase::Shard::release(text);
throw;
}
ctx.current() = {arg.buffername, arg.start + addr.end - addr.start + 1};
},
}
);
@@ -125,7 +197,7 @@ void Function::register_extented(BEd &ctx) {
}
);
ctx.functions.insert(
"history-list",
"hl",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
+24 -21
View File
@@ -47,7 +47,6 @@ void Function::register_posix(BEd &ctx) {
) {
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);
}
}
@@ -71,7 +70,6 @@ void Function::register_posix(BEd &ctx) {
) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).replace(ctx, text, addr.start, addr.end);
ctx.current() = {ctx.prev().buffername, ctx.prev().end};
vase::Shard::release(text);
}
}
@@ -95,7 +93,6 @@ void Function::register_posix(BEd &ctx) {
) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).remove(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.start};
}
}
);
@@ -144,8 +141,8 @@ void Function::register_posix(BEd &ctx) {
vase::Shard::release(s);
throw;
}
ctx.io.write_line(std::format("{}", buf.bytes()));
ctx.current() = {addr, buf.lines()};
if (!ctx.suppress_mode)
ctx.io.write_line(std::format("{}", buf.bytes()));
}
}
);
@@ -190,8 +187,8 @@ void Function::register_posix(BEd &ctx) {
vase::Shard::release(s);
throw;
}
ctx.io.write_line(std::format("{}", buf.bytes()));
ctx.current() = {addr, buf.lines()};
if (!ctx.suppress_mode)
ctx.io.write_line(std::format("{}", buf.bytes()));
}
}
);
@@ -289,7 +286,6 @@ void Function::register_posix(BEd &ctx) {
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);
}
}
@@ -313,7 +309,6 @@ void Function::register_posix(BEd &ctx) {
) {
auto addr = std::get<buffer::Range>(addr_);
ctx.buffer(addr.buffername).join(ctx, addr.start, addr.end);
ctx.current() = {addr.buffername, addr.start};
}
}
);
@@ -403,7 +398,6 @@ void Function::register_posix(BEd &ctx) {
vase::Shard::release(text);
throw;
}
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
},
}
);
@@ -457,9 +451,9 @@ void Function::register_posix(BEd &ctx) {
"P",
Function{
.address_kind = Function::AddressKind::None,
.argument_kind = Function::ArgumentKind::None,
.argument_kind = Function::ArgumentKind::Any,
.input_mode = Function::InputMode::None,
.desc = "Toggle prompt.",
.desc = "Toggle/set prompt.",
.default_address = "",
.accept_zero = false,
.pre_text_mode = nullptr,
@@ -467,13 +461,18 @@ void Function::register_posix(BEd &ctx) {
BEd &ctx,
const buffer::Address &,
vase::Shard *,
const Argument &,
const Argument &arg_,
std::vector<buffer::Line> *
) {
ctx.prompt_mode = !ctx.prompt_mode;
if (ctx.prompt_mode && !ctx.prompt) {
ctx.prompt = [](BEd &) { return "*"; };
auto &arg = std::get<std::string>(arg_);
if (arg.size()) {
ctx.prompt_mode = true;
ctx.prompt = [arg](BEd &) { return arg; };
return;
}
ctx.prompt_mode = !ctx.prompt_mode;
if (ctx.prompt_mode && !ctx.prompt)
ctx.prompt = [](BEd &) { return "*"; };
}
}
);
@@ -566,13 +565,13 @@ void Function::register_posix(BEd &ctx) {
};
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;
}
if (!ctx.suppress_mode)
ctx.io.write_line(std::format("{}", s ? s->length + 1 : 0));
}
}
);
@@ -642,7 +641,6 @@ void Function::register_posix(BEd &ctx) {
vase::Shard::release(text);
throw;
}
ctx.current() = {arg.buffername, arg.number + addr.end - addr.start + 1};
},
}
);
@@ -715,12 +713,13 @@ void Function::register_posix(BEd &ctx) {
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;
}
if (!ctx.suppress_mode)
ctx.io.write(std::format("{}\n", text ? text->length + 1 : 0));
}
}
);
@@ -746,6 +745,9 @@ void Function::register_posix(BEd &ctx) {
ctx.io.write_line(std::format(":{}:{}", addr.buffername, addr.start));
else
ctx.io.write_line(std::format(":{}:{},{}", addr.buffername, addr.start, addr.end));
ctx.prev().buffername = addr.buffername;
ctx.prev().start = addr.start;
ctx.prev().end = addr.end;
ctx.current() = {addr.buffername, addr.end};
},
}
@@ -774,7 +776,8 @@ void Function::register_posix(BEd &ctx) {
if (ctx.escape_command(cmd, filename.string()))
ctx.io.write(cmd + "\n");
ctx.io.run_pty(cmd);
ctx.io.write("!\n");
if (!ctx.suppress_mode)
ctx.io.write("!\n");
},
}
);
+25
View File
@@ -1,6 +1,31 @@
#include "internal/io/io.h"
namespace bed::internal::io {
std::pair<std::string, bool> IO::read_pipe() {
char buf[4096];
while (true) {
auto pos = pipe_input.find('\n');
if (pos != std::string::npos) {
std::string line = pipe_input.substr(0, pos);
pipe_input.erase(0, pos + 1);
return {line, false};
}
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
if (n > 0) {
pipe_input.append(buf, n);
continue;
}
if (n == 0) {
std::string line = std::move(pipe_input);
pipe_input.clear();
return {line, true};
}
if (errno == EINTR)
continue;
throw fatal_error("Can't read stdin.", 1);
}
}
KeyEvent::ReadResult IO::get_next_byte(char &out) {
if (!input_queue.empty()) {
out = input_queue.front();
+18 -2
View File
@@ -5,8 +5,12 @@ termios IO::orig_termios{};
termios IO::raw_termios{};
bool IO::cleaned = true;
volatile std::atomic_bool IO::resized(false);
IO::Mode IO::mode = IO::Mode::PIPE;
IO::IO() {
if (!isatty(STDIN_FILENO))
return;
mode = Mode::TERMINAL;
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
throw fatal_error("Can't get terminal state.", 1);
struct sigaction sa{};
@@ -30,16 +34,22 @@ IO::~IO() {
}
void IO::enable_mouse() {
if (mode == Mode::PIPE)
throw fatal_error("no mouse in pipe mode.", 1);
const char *seq = "\x1b[?1000h";
write_all(STDOUT_FILENO, seq, 8);
}
void IO::disable_mouse() {
if (mode == Mode::PIPE)
throw fatal_error("no mouse in pipe mode.", 1);
const char *seq = "\x1b[?1000l";
write_all(STDOUT_FILENO, seq, 8);
}
std::pair<uint16_t, uint16_t> IO::terminal_size() {
if (mode == Mode::PIPE)
throw fatal_error("no terminal size in pipe mode.", 1);
struct winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
throw fatal_error("Can't get terminal size.", 1);
@@ -47,6 +57,8 @@ std::pair<uint16_t, uint16_t> IO::terminal_size() {
}
std::pair<uint16_t, uint16_t> IO::cursor_position() {
if (mode == Mode::PIPE)
throw fatal_error("no cursor in pipe mode.", 1);
write_all(STDOUT_FILENO, "\x1b[6n", 4);
std::string response;
char c;
@@ -69,7 +81,7 @@ std::pair<uint16_t, uint16_t> IO::cursor_position() {
}
void IO::enable_raw() {
if (!cleaned)
if (!cleaned || mode == Mode::PIPE)
return;
std::string os = "\x1b[?2004h";
write_all(STDOUT_FILENO, os.c_str(), os.size());
@@ -79,7 +91,7 @@ void IO::enable_raw() {
}
void IO::cleanup() {
if (cleaned)
if (cleaned || mode == Mode::PIPE)
return;
std::string os = "\x1b[?1000l\x1b[?2004l";
write_all(STDOUT_FILENO, os.c_str(), os.size());
@@ -93,6 +105,8 @@ void IO::handle_sigwinch(int) {
}
void IO::move_cursor(uint16_t row, uint16_t col) {
if (mode == Mode::PIPE)
return;
char buf[32];
int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col);
write_all(STDOUT_FILENO, buf, n);
@@ -112,6 +126,8 @@ void IO::write_line(std::string_view s) {
}
void IO::run_pty(const std::string &cmd) {
if (mode == Mode::PIPE)
throw ed_error("Shell running not allowed in pipe mode.");
int master_fd = -1;
struct winsize ws{};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
+2 -1
View File
@@ -165,12 +165,13 @@ void Parser::operation() {
j++;
}
std::string replacement(peek_str(j));
std::string options;
if (peek(j) != '\0') {
advance(j + 1);
} else {
advance(j);
options = "p";
}
std::string options;
if (peek() != '\0') {
options = std::string(peek_str());
advance(peek_str().size());
+11
View File
@@ -69,6 +69,17 @@ CommandIO::CommandIO(BEd &bed) : bed(bed) {
}
std::pair<std::string, bool> CommandIO::run() {
if (!bed.io.interactive())
return run_pipe();
return run_terminal();
}
std::pair<std::string, bool> CommandIO::run_pipe() {
bed.io.write(prompt);
return bed.io.read_pipe();
}
std::pair<std::string, bool> CommandIO::run_terminal() {
auto [row, col] = bed.io.cursor_position();
auto [rows, cols] = bed.io.terminal_size();
if (row > rows)
+17
View File
@@ -7,6 +7,23 @@ TextMode::TextMode(BEd &bed) : bed(bed) {
}
std::pair<vase::Shard *, bool> TextMode::run() {
if (!bed.io.interactive())
return run_pipe();
return run_terminal();
}
std::pair<vase::Shard *, bool> TextMode::run_pipe() {
cmd.clear();
while (true) {
auto [str, eof] = bed.io.read_pipe();
if (str == "." || eof)
break;
cmd += str + "\n";
}
return {vase::Shard::from_string(cmd.data(), cmd.length(), true), false};
}
std::pair<vase::Shard *, bool> TextMode::run_terminal() {
auto [row, col] = bed.io.cursor_position();
auto [rows, cols] = bed.io.terminal_size();
if (row > rows)
+7 -14
View File
@@ -90,12 +90,7 @@ Shard *substitute(
if (matches.empty())
return root;
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;
uint64_t current_line = 1;
int64_t line_delta = 0;
std::vector<Shard *> pieces;
pieces.reserve(matches.size() * 2 + 1);
@@ -109,7 +104,7 @@ Shard *substitute(
Shard::release(remaining);
pieces.push_back(keep);
remaining = rest;
orig_line += keep ? keep->lines : 0;
current_line += keep ? keep->lines : 0;
}
auto [dropped, rest2] = Shard::split(remaining, match.end - match.start);
Shard::release(remaining);
@@ -152,13 +147,11 @@ Shard *substitute(
}
}
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;
uint64_t report_line = (uint64_t)((int64_t)current_line + line_delta);
if (on_edit)
on_edit(report_line, old_lines, new_lines);
line_delta += (int64_t)new_lines - (int64_t)old_lines;
current_line += old_lines;
cursor = match.end;
}
pieces.push_back(remaining);