From 7a18aa0db21d1369862113b588d50071e03e4bf2 Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Sun, 30 Aug 2026 14:33:39 +0100 Subject: [PATCH] Add text mode handling. --- include/bed.h | 1 + include/internal/io/io.h | 3 - include/internal/ui/command.h | 5 +- include/internal/ui/text_mode.h | 21 +++ src/bed/run.cc | 12 +- src/internal/functions/functions.cc | 18 +++ src/internal/io/command.cc | 10 -- src/internal/io/text_mode.cc | 28 ---- src/internal/ui/command/command.cc | 29 ++-- src/internal/ui/text_mode/text_mode.cc | 188 +++++++++++++++++++++++++ 10 files changed, 254 insertions(+), 61 deletions(-) create mode 100644 include/internal/ui/text_mode.h delete mode 100644 src/internal/io/command.cc delete mode 100644 src/internal/io/text_mode.cc create mode 100644 src/internal/ui/text_mode/text_mode.cc diff --git a/include/bed.h b/include/bed.h index 1675f4a..553cbda 100644 --- a/include/bed.h +++ b/include/bed.h @@ -8,6 +8,7 @@ #include "internal/marks/marks.h" #include "internal/theme/theme.h" #include "internal/ui/command.h" +#include "internal/ui/text_mode.h" #include "pch.h" namespace bed { diff --git a/include/internal/io/io.h b/include/internal/io/io.h index 296e07e..d92df91 100644 --- a/include/internal/io/io.h +++ b/include/internal/io/io.h @@ -71,9 +71,6 @@ struct IO { std::pair cursor_position(); void move_cursor(uint16_t row, uint16_t col); - std::pair get_command(BEd &); - std::pair get_text(BEd &); - KeyEvent read_key(); void write(const char *, uint64_t); void write(std::string_view); diff --git a/include/internal/ui/command.h b/include/internal/ui/command.h index f5a988e..e036828 100644 --- a/include/internal/ui/command.h +++ b/include/internal/ui/command.h @@ -1,6 +1,6 @@ #pragma once -#include "internal/io/io.h" +#include "definitions.h" #include "pch.h" namespace bed::internal::ui { @@ -38,9 +38,8 @@ struct CommandIO { uint16_t term_height; uint16_t term_width; BEd &bed; - io::IO &io; - CommandIO(BEd &, io::IO &); + CommandIO(BEd &); std::pair run(); void redraw(); }; diff --git a/include/internal/ui/text_mode.h b/include/internal/ui/text_mode.h new file mode 100644 index 0000000..d499b98 --- /dev/null +++ b/include/internal/ui/text_mode.h @@ -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 run(); + void grow(size_t required_height); + void redraw(); +}; +} // namespace bed::internal::ui diff --git a/src/bed/run.cc b/src/bed/run.cc index c6cdcf2..7903a3c 100644 --- a/src/bed/run.cc +++ b/src/bed/run.cc @@ -46,7 +46,8 @@ BEd::~BEd() { void BEd::run() { while (true) { - auto [cmd, eof] = io.get_command(*this); + internal::ui::CommandIO command(*this); + auto [cmd, eof] = command.run(); try { handle(cmd, eof); } catch (ed_error &e) { @@ -124,8 +125,15 @@ void BEd::handle(std::string_view cmd, bool eof) { throw ed_error("Line number can't be zero."); } } + internal::vase::Shard *text = nullptr; + if (c.function->input_mode == internal::functions::Function::InputMode::Text) { + internal::ui::TextMode tm(*this); + auto [a, b] = tm.run(); + if (!b) + text = a; + } if (c.function->handle) - c.function->handle(*this, address, nullptr, c.argument, nullptr); + c.function->handle(*this, address, text, c.argument, nullptr); if (c.suffix) c.suffix->handle(*this); if (c.temp_address) diff --git a/src/internal/functions/functions.cc b/src/internal/functions/functions.cc index 7d2dd39..5400b90 100644 --- a/src/internal/functions/functions.cc +++ b/src/internal/functions/functions.cc @@ -53,6 +53,24 @@ void Function::register_posix(BEd &ctx) { throw fatal_error("Quitting", 0); } }; + 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 *) { + auto addr = std::get(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( "j", Function{ diff --git a/src/internal/io/command.cc b/src/internal/io/command.cc deleted file mode 100644 index ab6fe3f..0000000 --- a/src/internal/io/command.cc +++ /dev/null @@ -1,10 +0,0 @@ -#include "internal/ui/command.h" -#include "bed.h" -#include "internal/io/io.h" - -namespace bed::internal::io { -std::pair IO::get_command(BEd &ctx) { - ui::CommandIO cio(ctx, *this); - return cio.run(); -} -} // namespace bed::internal::io diff --git a/src/internal/io/text_mode.cc b/src/internal/io/text_mode.cc deleted file mode 100644 index 8996b59..0000000 --- a/src/internal/io/text_mode.cc +++ /dev/null @@ -1,28 +0,0 @@ -#include "bed.h" -#include "internal/io/io.h" - -namespace bed::internal::io { -std::pair 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 diff --git a/src/internal/ui/command/command.cc b/src/internal/ui/command/command.cc index 1787020..402679b 100644 --- a/src/internal/ui/command/command.cc +++ b/src/internal/ui/command/command.cc @@ -62,15 +62,15 @@ static std::string current_word(const std::string &line, size_t byte_pos) { 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) prompt = bed.prompt(bed); cursor = 0; } std::pair CommandIO::run() { - auto [row, col] = io.cursor_position(); - auto [rows, cols] = io.terminal_size(); + 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; @@ -79,11 +79,10 @@ std::pair CommandIO::run() { term_height = rows; redraw(); - // main loop. io::KeyEvent res; bool running = true; while (running) { - res = io.read_key(); + res = bed.io.read_key(); switch (res.type) { case io::KeyEvent::KeyType::EOF_: running = false; @@ -139,20 +138,20 @@ std::pair CommandIO::run() { } for (uint16_t i = 1; i < height; ++i) { - io.move_cursor(start + i, 1); - io.write("\x1b[2K", 4); + bed.io.move_cursor(start + i, 1); + bed.io.write("\x1b[2K", 4); } - io.move_cursor(start, 1); - io.write("\n", 1); + bed.io.move_cursor(start, 1); + bed.io.write("\n", 1); return {cmd, false}; } void CommandIO::redraw() { - io.move_cursor(start, 1); - io.write("\x1b[2K", 4); - io.move_cursor(start, 1); - io.write(prompt); - io.write(cmd); - io.move_cursor(start, prompt.size() + cursor + 1); + bed.io.move_cursor(start, 1); + bed.io.write("\x1b[2K", 4); + bed.io.move_cursor(start, 1); + bed.io.write(prompt); + bed.io.write(cmd); + bed.io.move_cursor(start, prompt.size() + cursor + 1); } } // namespace bed::internal::ui diff --git a/src/internal/ui/text_mode/text_mode.cc b/src/internal/ui/text_mode/text_mode.cc new file mode 100644 index 0000000..e615e32 --- /dev/null +++ b/src/internal/ui/text_mode/text_mode.cc @@ -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 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