Compare commits
2
Commits
1f3b53753a
...
2ac71085db
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ac71085db
|
||
|
|
3563324a1c
|
+16
-1
@@ -4,11 +4,25 @@
|
|||||||
#include "internal/buffer/buffer.h"
|
#include "internal/buffer/buffer.h"
|
||||||
#include "internal/commands/commands.h"
|
#include "internal/commands/commands.h"
|
||||||
#include "internal/commands/suffixes.h"
|
#include "internal/commands/suffixes.h"
|
||||||
|
#include "internal/io/io.h"
|
||||||
#include "internal/marks/marks.h"
|
#include "internal/marks/marks.h"
|
||||||
#include "internal/theme/theme.h"
|
#include "internal/theme/theme.h"
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed {
|
namespace bed {
|
||||||
|
struct Suggestion {
|
||||||
|
enum : uint8_t {
|
||||||
|
Command,
|
||||||
|
Address,
|
||||||
|
Suffix,
|
||||||
|
History,
|
||||||
|
Regex
|
||||||
|
} type;
|
||||||
|
std::string_view name;
|
||||||
|
std::string_view desc;
|
||||||
|
std::string completion;
|
||||||
|
};
|
||||||
|
|
||||||
struct BEd {
|
struct BEd {
|
||||||
internal::trie::Trie<internal::commands::Command> commands;
|
internal::trie::Trie<internal::commands::Command> commands;
|
||||||
internal::commands::Command no_op;
|
internal::commands::Command no_op;
|
||||||
@@ -16,6 +30,7 @@ struct BEd {
|
|||||||
std::array<std::optional<internal::commands::Suffix>, 26> suffixes;
|
std::array<std::optional<internal::commands::Suffix>, 26> suffixes;
|
||||||
internal::theme::Theme theme;
|
internal::theme::Theme theme;
|
||||||
std::unordered_map<std::string, internal::syntax::Language> languages;
|
std::unordered_map<std::string, internal::syntax::Language> languages;
|
||||||
|
internal::io::IO &io;
|
||||||
|
|
||||||
bool help_mode = false;
|
bool help_mode = false;
|
||||||
std::string last_help = "";
|
std::string last_help = "";
|
||||||
@@ -27,7 +42,7 @@ struct BEd {
|
|||||||
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
|
std::unordered_map<std::string, internal::buffer::Buffer *> buffers;
|
||||||
internal::buffer::Buffer *active;
|
internal::buffer::Buffer *active;
|
||||||
|
|
||||||
BEd(std::vector<std::string> args);
|
BEd(std::vector<std::string> args, internal::io::IO &io);
|
||||||
~BEd();
|
~BEd();
|
||||||
void handle(std::string_view cmd, bool eof);
|
void handle(std::string_view cmd, bool eof);
|
||||||
void run();
|
void run();
|
||||||
|
|||||||
@@ -14,5 +14,18 @@ struct ed_error : std::runtime_error {
|
|||||||
ed_error(std::string msg) : std::runtime_error(msg) {}
|
ed_error(std::string msg) : std::runtime_error(msg) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Highlight {
|
||||||
|
enum : uint8_t {
|
||||||
|
None = 0,
|
||||||
|
Bold = 1 << 0,
|
||||||
|
Italic = 1 << 1,
|
||||||
|
Strikethrough = 1 << 2,
|
||||||
|
Underline = 1 << 3,
|
||||||
|
};
|
||||||
|
uint32_t fg;
|
||||||
|
uint32_t bg;
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
struct BEd;
|
struct BEd;
|
||||||
} // namespace bed
|
} // namespace bed
|
||||||
|
|||||||
@@ -7,4 +7,20 @@ enum struct Direction : uint8_t {
|
|||||||
Forward,
|
Forward,
|
||||||
Backward
|
Backward
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline static bool write_all(int fd, const void *data, size_t len) {
|
||||||
|
const char *p = (const char *)data;
|
||||||
|
while (len > 0) {
|
||||||
|
ssize_t n = write(fd, p, len);
|
||||||
|
if (n > 0) {
|
||||||
|
p += n;
|
||||||
|
len -= (size_t)n;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (n == -1 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
} // namespace bed::internal
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "definitions.h"
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
namespace bed::internal::io {
|
||||||
|
struct KeyEvent {
|
||||||
|
enum struct KeyType {
|
||||||
|
NONE,
|
||||||
|
CHAR,
|
||||||
|
SPECIAL,
|
||||||
|
MOUSE,
|
||||||
|
PASTE
|
||||||
|
};
|
||||||
|
enum struct SpecialKey {
|
||||||
|
UP,
|
||||||
|
DOWN,
|
||||||
|
LEFT,
|
||||||
|
RIGHT,
|
||||||
|
DELETE,
|
||||||
|
UNKNOWN
|
||||||
|
};
|
||||||
|
enum struct Modifier {
|
||||||
|
NONE,
|
||||||
|
SHIFT,
|
||||||
|
ALT,
|
||||||
|
CTRL,
|
||||||
|
CTRL_ALT
|
||||||
|
};
|
||||||
|
enum struct MouseState {
|
||||||
|
PRESS,
|
||||||
|
RELEASE
|
||||||
|
};
|
||||||
|
|
||||||
|
KeyType type = KeyType::NONE;
|
||||||
|
std::string text;
|
||||||
|
|
||||||
|
SpecialKey special_key = SpecialKey::UNKNOWN;
|
||||||
|
Modifier modifier = Modifier::NONE;
|
||||||
|
|
||||||
|
MouseState mouse_state = MouseState::PRESS;
|
||||||
|
uint16_t mouse_x = 0;
|
||||||
|
uint16_t mouse_y = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IO {
|
||||||
|
IO();
|
||||||
|
~IO();
|
||||||
|
|
||||||
|
IO(const IO &) = delete;
|
||||||
|
IO &operator=(const IO &) = delete;
|
||||||
|
|
||||||
|
void enable_mouse();
|
||||||
|
void disable_mouse();
|
||||||
|
|
||||||
|
std::pair<uint16_t, uint16_t> terminal_size();
|
||||||
|
std::pair<uint16_t, uint16_t> cursor_position();
|
||||||
|
void move_cursor(uint16_t row, uint16_t col);
|
||||||
|
|
||||||
|
std::pair<std::string, bool> get_command(BEd &);
|
||||||
|
|
||||||
|
KeyEvent read_key();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static termios orig_termios;
|
||||||
|
static bool cleaned;
|
||||||
|
static void cleanup();
|
||||||
|
|
||||||
|
std::deque<char> input_queue;
|
||||||
|
|
||||||
|
bool get_next_byte(char &out);
|
||||||
|
void enqueue_bytes(const std::string &bytes);
|
||||||
|
static int utf8_seq_len(uint8_t byte);
|
||||||
|
|
||||||
|
std::string read_next_unit();
|
||||||
|
bool read_bracketed_paste(std::string &out);
|
||||||
|
|
||||||
|
KeyEvent parse_mouse(const std::string &buf);
|
||||||
|
KeyEvent parse_escape(const std::string &buf);
|
||||||
|
};
|
||||||
|
} // namespace bed::internal::io
|
||||||
@@ -5,19 +5,6 @@
|
|||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
namespace bed::internal::theme {
|
namespace bed::internal::theme {
|
||||||
struct Highlight {
|
|
||||||
enum : uint8_t {
|
|
||||||
None = 0,
|
|
||||||
Bold = 1 << 0,
|
|
||||||
Italic = 1 << 1,
|
|
||||||
Strikethrough = 1 << 2,
|
|
||||||
Underline = 1 << 3,
|
|
||||||
};
|
|
||||||
uint32_t fg;
|
|
||||||
uint32_t bg;
|
|
||||||
uint8_t flags;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Theme {
|
struct Theme {
|
||||||
std::array<Highlight, internal::syntax::Token::Count> hl;
|
std::array<Highlight, internal::syntax::Token::Count> hl;
|
||||||
|
|
||||||
|
|||||||
+3
-6
@@ -1,8 +1,8 @@
|
|||||||
#include "bed.h"
|
#include "bed.h"
|
||||||
|
|
||||||
namespace bed {
|
namespace bed {
|
||||||
BEd::BEd(std::vector<std::string> args)
|
BEd::BEd(std::vector<std::string> args, internal::io::IO &io)
|
||||||
: theme(internal::theme::Theme::default_theme()) {
|
: theme(internal::theme::Theme::default_theme()), io(io) {
|
||||||
internal::commands::Command::register_posix(*this);
|
internal::commands::Command::register_posix(*this);
|
||||||
internal::commands::Suffix::register_suffixes(*this);
|
internal::commands::Suffix::register_suffixes(*this);
|
||||||
std::string prompt_ = "";
|
std::string prompt_ = "";
|
||||||
@@ -40,10 +40,7 @@ BEd::~BEd() {
|
|||||||
|
|
||||||
void BEd::run() {
|
void BEd::run() {
|
||||||
while (true) {
|
while (true) {
|
||||||
std::string cmd;
|
auto [cmd, eof] = io.get_command(*this);
|
||||||
if (prompt_mode)
|
|
||||||
std::cout << prompt(*this);
|
|
||||||
bool eof = !std::getline(std::cin, cmd);
|
|
||||||
try {
|
try {
|
||||||
handle(cmd, eof);
|
handle(cmd, eof);
|
||||||
} catch (ed_error &e) {
|
} catch (ed_error &e) {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ void Buffer::join(uint64_t start_line, uint64_t end_line) {
|
|||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void apply(std::ostream &out, const theme::Highlight &hl) {
|
inline void apply(std::ostream &out, const Highlight &hl) {
|
||||||
out << "\x1b[0m";
|
out << "\x1b[0m";
|
||||||
const uint8_t r = (hl.fg >> 16) & 0xff;
|
const uint8_t r = (hl.fg >> 16) & 0xff;
|
||||||
const uint8_t g = (hl.fg >> 8) & 0xff;
|
const uint8_t g = (hl.fg >> 8) & 0xff;
|
||||||
@@ -124,13 +124,13 @@ inline void apply(std::ostream &out, const theme::Highlight &hl) {
|
|||||||
<< (unsigned)bg << ';'
|
<< (unsigned)bg << ';'
|
||||||
<< (unsigned)bb << 'm';
|
<< (unsigned)bb << 'm';
|
||||||
}
|
}
|
||||||
if (hl.flags & theme::Highlight::Bold)
|
if (hl.flags & Highlight::Bold)
|
||||||
out << "\x1b[1m";
|
out << "\x1b[1m";
|
||||||
if (hl.flags & theme::Highlight::Italic)
|
if (hl.flags & Highlight::Italic)
|
||||||
out << "\x1b[3m";
|
out << "\x1b[3m";
|
||||||
if (hl.flags & theme::Highlight::Underline)
|
if (hl.flags & Highlight::Underline)
|
||||||
out << "\x1b[4m";
|
out << "\x1b[4m";
|
||||||
if (hl.flags & theme::Highlight::Strikethrough)
|
if (hl.flags & Highlight::Strikethrough)
|
||||||
out << "\x1b[9m";
|
out << "\x1b[9m";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ void Command::register_posix(BEd &ctx) {
|
|||||||
.desc = "Join a set of lines (default: .,.+1)",
|
.desc = "Join a set of lines (default: .,.+1)",
|
||||||
.accept_zero = true,
|
.accept_zero = true,
|
||||||
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
|
.handle = [](BEd &ctx, std::span<const uint64_t> addresses, std::string_view) {
|
||||||
std::cout << addresses.size() << "\n";
|
|
||||||
uint64_t start_line = ctx.active->line;
|
uint64_t start_line = ctx.active->line;
|
||||||
uint64_t end_line = ctx.active->line + 1;
|
uint64_t end_line = ctx.active->line + 1;
|
||||||
if (addresses.size() == 1)
|
if (addresses.size() == 1)
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
#include "bed.h"
|
||||||
|
#include "internal/io/io.h"
|
||||||
|
|
||||||
|
namespace bed::internal::io {
|
||||||
|
std::pair<std::string, bool> IO::get_command(BEd &ctx) {
|
||||||
|
auto [row, col] = cursor_position();
|
||||||
|
auto [rows, cols] = terminal_size();
|
||||||
|
if (row > rows)
|
||||||
|
throw fatal_error("Invalid cursor position.", 1);
|
||||||
|
uint16_t start = row;
|
||||||
|
uint16_t height = rows - row + 1;
|
||||||
|
// uint16_t width = cols;
|
||||||
|
// TODO: support multiline wrapped commands.
|
||||||
|
// also handle ctrl+l to try and clear screen.
|
||||||
|
|
||||||
|
std::string cmd;
|
||||||
|
size_t cursor = 0;
|
||||||
|
|
||||||
|
std::string prompt;
|
||||||
|
if (ctx.prompt_mode)
|
||||||
|
prompt = ctx.prompt(ctx);
|
||||||
|
|
||||||
|
auto redraw = [&] {
|
||||||
|
move_cursor(start, 1);
|
||||||
|
write_all(STDOUT_FILENO, "\x1b[2K", 4);
|
||||||
|
write_all(STDOUT_FILENO, prompt.c_str(), prompt.size());
|
||||||
|
write_all(STDOUT_FILENO, cmd.c_str(), cmd.size());
|
||||||
|
move_cursor(start, cursor + prompt.size() + 1);
|
||||||
|
};
|
||||||
|
redraw();
|
||||||
|
|
||||||
|
bool eof = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
KeyEvent ev = read_key();
|
||||||
|
if (
|
||||||
|
(ev.type == KeyEvent::KeyType::CHAR
|
||||||
|
&& ev.modifier == KeyEvent::Modifier::CTRL
|
||||||
|
&& ev.text[0] == 'd')
|
||||||
|
|| ev.type == KeyEvent::KeyType::NONE
|
||||||
|
) {
|
||||||
|
eof = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ev.type == KeyEvent::KeyType::CHAR
|
||||||
|
&& ev.modifier == KeyEvent::Modifier::CTRL
|
||||||
|
&& ev.text[0] == 'w') {
|
||||||
|
// TODO: delete previous word here.
|
||||||
|
if (cursor == 0)
|
||||||
|
continue;
|
||||||
|
cmd.erase(cursor -= 1, 1);
|
||||||
|
redraw();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ev.type == KeyEvent::KeyType::CHAR
|
||||||
|
&& ev.modifier == KeyEvent::Modifier::NONE) {
|
||||||
|
if (ev.text == "\r" || ev.text == "\n")
|
||||||
|
break;
|
||||||
|
if (ev.text.size() == 1
|
||||||
|
&& (ev.text[0] == '\x7f' || ev.text[0] == '\x08')) {
|
||||||
|
if (cursor == 0)
|
||||||
|
continue;
|
||||||
|
cmd.erase(cursor -= 1, 1);
|
||||||
|
redraw();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cmd.insert(cursor, ev.text);
|
||||||
|
cursor += ev.text.size();
|
||||||
|
redraw();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ev.type == KeyEvent::KeyType::SPECIAL) {
|
||||||
|
if (ev.special_key == KeyEvent::SpecialKey::LEFT) {
|
||||||
|
if (cursor == 0)
|
||||||
|
continue;
|
||||||
|
cursor -= 1;
|
||||||
|
} else if (ev.special_key == KeyEvent::SpecialKey::RIGHT) {
|
||||||
|
if (cursor >= cmd.size())
|
||||||
|
continue;
|
||||||
|
cursor += 1;
|
||||||
|
} else if (ev.special_key == KeyEvent::SpecialKey::DELETE) {
|
||||||
|
if (cursor >= cmd.size())
|
||||||
|
continue;
|
||||||
|
cmd.erase(cursor, 1);
|
||||||
|
}
|
||||||
|
redraw();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ev.type == KeyEvent::KeyType::PASTE) {
|
||||||
|
std::string text = ev.text;
|
||||||
|
if (!text.empty() && text.front() == '\n')
|
||||||
|
text.erase(text.begin());
|
||||||
|
if (!text.empty() && text.back() == '\n')
|
||||||
|
text.pop_back();
|
||||||
|
bool multiline = text.find('\n') != std::string::npos;
|
||||||
|
if (multiline) {
|
||||||
|
size_t line_count = 1 + std::count(text.begin(), text.end(), '\n');
|
||||||
|
if (height == 1) {
|
||||||
|
write_all(STDOUT_FILENO, "\n", 1);
|
||||||
|
--start;
|
||||||
|
++height;
|
||||||
|
}
|
||||||
|
bool go_ahead;
|
||||||
|
std::string msg =
|
||||||
|
"* paste "
|
||||||
|
+ std::to_string(line_count)
|
||||||
|
+ " lines into a single-line command? (y/n) ";
|
||||||
|
move_cursor(start + 1, 1);
|
||||||
|
write_all(STDOUT_FILENO, "\x1b[2K", 4);
|
||||||
|
write_all(STDOUT_FILENO, msg.c_str(), msg.size());
|
||||||
|
while (true) {
|
||||||
|
KeyEvent ev = read_key();
|
||||||
|
if (ev.type == KeyEvent::KeyType::CHAR && ev.text.size() == 1) {
|
||||||
|
if (ev.text[0] == 'y' || ev.text[0] == 'Y') {
|
||||||
|
go_ahead = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ev.text[0] == 'n' || ev.text[0] == 'N') {
|
||||||
|
go_ahead = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ev.type == KeyEvent::KeyType::NONE) {
|
||||||
|
go_ahead = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
move_cursor(start + 1, 1);
|
||||||
|
write_all(STDOUT_FILENO, "\x1b[2K", 4);
|
||||||
|
if (!go_ahead) {
|
||||||
|
redraw();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::replace(text.begin(), text.end(), '\n', ' ');
|
||||||
|
}
|
||||||
|
cmd.insert(cursor, text);
|
||||||
|
cursor += text.size();
|
||||||
|
redraw();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {cmd, eof};
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::io
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
#include "internal/io/io.h"
|
||||||
|
|
||||||
|
namespace bed::internal::io {
|
||||||
|
bool IO::get_next_byte(char &out) {
|
||||||
|
if (!input_queue.empty()) {
|
||||||
|
out = input_queue.front();
|
||||||
|
input_queue.pop_front();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return read(STDIN_FILENO, &out, 1) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::enqueue_bytes(const std::string &bytes) {
|
||||||
|
input_queue.insert(input_queue.begin(), bytes.begin(), bytes.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
int IO::utf8_seq_len(uint8_t byte) {
|
||||||
|
if ((byte & 0x80) == 0x00)
|
||||||
|
return 1;
|
||||||
|
if ((byte & 0xE0) == 0xC0)
|
||||||
|
return 2;
|
||||||
|
if ((byte & 0xF0) == 0xE0)
|
||||||
|
return 3;
|
||||||
|
if ((byte & 0xF8) == 0xF0)
|
||||||
|
return 4;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string IO::read_next_unit() {
|
||||||
|
std::string buf;
|
||||||
|
char header;
|
||||||
|
if (!get_next_byte(header))
|
||||||
|
return buf;
|
||||||
|
if (header == '\x1b') {
|
||||||
|
buf.push_back(header);
|
||||||
|
char c;
|
||||||
|
if (!get_next_byte(c))
|
||||||
|
return buf;
|
||||||
|
buf.push_back(c);
|
||||||
|
if (c != '[')
|
||||||
|
return buf;
|
||||||
|
if (!get_next_byte(c))
|
||||||
|
return buf;
|
||||||
|
buf.push_back(c);
|
||||||
|
if (c == 'M') {
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
if (!get_next_byte(c))
|
||||||
|
break;
|
||||||
|
buf.push_back(c);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
while ((uint8_t)c < 0x40 || (uint8_t)c > 0x7E) {
|
||||||
|
if (buf.size() >= 32)
|
||||||
|
break;
|
||||||
|
if (!get_next_byte(c))
|
||||||
|
break;
|
||||||
|
buf.push_back(c);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
int seq_len = utf8_seq_len((uint8_t)header);
|
||||||
|
buf.push_back(header);
|
||||||
|
if (seq_len == 1)
|
||||||
|
return buf;
|
||||||
|
for (int i = 1; i < seq_len; i++) {
|
||||||
|
char c;
|
||||||
|
if (!get_next_byte(c)) {
|
||||||
|
enqueue_bytes(buf);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
buf.push_back(c);
|
||||||
|
}
|
||||||
|
uint32_t prev_cp, cur_cp;
|
||||||
|
grapheme_decode_utf8(buf.data(), buf.size(), &prev_cp);
|
||||||
|
uint16_t state = 0;
|
||||||
|
while (true) {
|
||||||
|
char next_header;
|
||||||
|
if (!get_next_byte(next_header))
|
||||||
|
break;
|
||||||
|
int next_len = utf8_seq_len((uint8_t)next_header);
|
||||||
|
std::string next_seq(1, next_header);
|
||||||
|
bool complete = true;
|
||||||
|
for (int i = 1; i < next_len; i++) {
|
||||||
|
char c;
|
||||||
|
if (!get_next_byte(c)) {
|
||||||
|
complete = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
next_seq.push_back(c);
|
||||||
|
}
|
||||||
|
if (!complete) {
|
||||||
|
enqueue_bytes(next_seq);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
grapheme_decode_utf8(next_seq.data(), next_seq.size(), &cur_cp);
|
||||||
|
if (grapheme_is_character_break(prev_cp, cur_cp, &state)) {
|
||||||
|
enqueue_bytes(next_seq);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buf += next_seq;
|
||||||
|
prev_cp = cur_cp;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IO::read_bracketed_paste(std::string &out) {
|
||||||
|
out.clear();
|
||||||
|
std::string window;
|
||||||
|
while (true) {
|
||||||
|
char c;
|
||||||
|
if (!get_next_byte(c))
|
||||||
|
return false;
|
||||||
|
window.push_back(c);
|
||||||
|
if (window.size() == 5 && window == "\x1b[201") {
|
||||||
|
char tilde;
|
||||||
|
if (!get_next_byte(tilde))
|
||||||
|
return false;
|
||||||
|
if (tilde == '~')
|
||||||
|
return true;
|
||||||
|
out += window;
|
||||||
|
out.push_back(tilde);
|
||||||
|
window.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (window.size() == 5) {
|
||||||
|
out.push_back(window.front());
|
||||||
|
window.erase(window.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent IO::parse_mouse(const std::string &buf) {
|
||||||
|
KeyEvent ev;
|
||||||
|
if (buf.size() < 6)
|
||||||
|
return ev;
|
||||||
|
uint8_t code = (uint8_t)buf[3] - 32;
|
||||||
|
uint8_t button = code & 0x03;
|
||||||
|
if (button != 0 && button != 3)
|
||||||
|
return ev;
|
||||||
|
ev.type = KeyEvent::KeyType::MOUSE;
|
||||||
|
ev.mouse_state = (button == 3) ? KeyEvent::MouseState::RELEASE
|
||||||
|
: KeyEvent::MouseState::PRESS;
|
||||||
|
ev.mouse_x = (uint8_t)buf[4] - 33;
|
||||||
|
ev.mouse_y = (uint8_t)buf[5] - 33;
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent IO::parse_escape(const std::string &buf) {
|
||||||
|
KeyEvent ev;
|
||||||
|
ev.type = KeyEvent::KeyType::SPECIAL;
|
||||||
|
bool has_modifier = buf.size() > 3 && buf[3] == ';';
|
||||||
|
size_t pos;
|
||||||
|
if (!has_modifier) {
|
||||||
|
pos = 2;
|
||||||
|
} else {
|
||||||
|
pos = 5;
|
||||||
|
switch (buf.size() > 4 ? buf[4] : 0) {
|
||||||
|
case '2':
|
||||||
|
ev.modifier = KeyEvent::Modifier::SHIFT;
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
ev.modifier = KeyEvent::Modifier::ALT;
|
||||||
|
break;
|
||||||
|
case '5':
|
||||||
|
ev.modifier = KeyEvent::Modifier::CTRL;
|
||||||
|
break;
|
||||||
|
case '7':
|
||||||
|
ev.modifier = KeyEvent::Modifier::CTRL_ALT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ev.modifier = KeyEvent::Modifier::NONE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char key = pos < buf.size() ? buf[pos] : 0;
|
||||||
|
switch (key) {
|
||||||
|
case 'A':
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::UP;
|
||||||
|
break;
|
||||||
|
case 'B':
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::DOWN;
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::RIGHT;
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::LEFT;
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::DELETE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ev.special_key = KeyEvent::SpecialKey::UNKNOWN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent IO::read_key() {
|
||||||
|
while (true) {
|
||||||
|
std::string buf = read_next_unit();
|
||||||
|
if (buf.empty()) {
|
||||||
|
KeyEvent ev;
|
||||||
|
ev.type = KeyEvent::KeyType::NONE;
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
if (buf.size() >= 6 && buf[0] == '\x1b' && buf[1] == '[' && buf.compare(2, 4, "200~") == 0) {
|
||||||
|
KeyEvent ev;
|
||||||
|
std::string pasted;
|
||||||
|
if (read_bracketed_paste(pasted)) {
|
||||||
|
ev.type = KeyEvent::KeyType::PASTE;
|
||||||
|
ev.text = std::move(pasted);
|
||||||
|
} else {
|
||||||
|
ev.type = KeyEvent::KeyType::NONE;
|
||||||
|
}
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
if (buf.size() >= 3 && buf[0] == '\x1b' && buf[1] == '[' && buf[2] == 'M') {
|
||||||
|
KeyEvent ev = parse_mouse(buf);
|
||||||
|
if (ev.type == KeyEvent::KeyType::NONE)
|
||||||
|
continue;
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
if (buf.size() >= 2 && buf[0] == '\x1b' && buf[1] == '[')
|
||||||
|
return parse_escape(buf);
|
||||||
|
KeyEvent ev;
|
||||||
|
ev.type = KeyEvent::KeyType::CHAR;
|
||||||
|
ev.modifier = KeyEvent::Modifier::NONE;
|
||||||
|
if (buf.size() == 1) {
|
||||||
|
uint8_t c = (uint8_t)buf[0];
|
||||||
|
if (c >= 1 && c <= 26 && c != '\t' && c != '\n' && c != '\r' && c != '\x08') {
|
||||||
|
ev.modifier = KeyEvent::Modifier::CTRL;
|
||||||
|
ev.text = 'a' + c - 1;
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buf.size() == 2 && (uint8_t)buf[0] == 0x1B) {
|
||||||
|
uint8_t c = (uint8_t)buf[1];
|
||||||
|
if (c >= 1 && c <= 26 && c != '\t' && c != '\n' && c != '\r' && c != '\x08') {
|
||||||
|
ev.modifier = KeyEvent::Modifier::CTRL_ALT;
|
||||||
|
ev.text = 'a' + c - 1;
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
ev.modifier = KeyEvent::Modifier::ALT;
|
||||||
|
ev.text = buf.substr(1);
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
ev.text = std::move(buf);
|
||||||
|
return ev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::io
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#include "internal/io/io.h"
|
||||||
|
|
||||||
|
namespace bed::internal::io {
|
||||||
|
termios IO::orig_termios{};
|
||||||
|
bool IO::cleaned = false;
|
||||||
|
|
||||||
|
IO::IO() {
|
||||||
|
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
|
||||||
|
throw fatal_error("Can't get terminal state.", 1);
|
||||||
|
atexit(cleanup);
|
||||||
|
struct termios raw = orig_termios;
|
||||||
|
raw.c_iflag &= ~(BRKINT | ISTRIP | IXON);
|
||||||
|
raw.c_cflag |= (CS8);
|
||||||
|
raw.c_lflag &= ~(ECHO | ICANON | ISIG);
|
||||||
|
raw.c_cc[VMIN] = 1;
|
||||||
|
raw.c_cc[VTIME] = 0;
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
|
||||||
|
throw fatal_error("Can't set terminal state.", 1);
|
||||||
|
std::string os = "\x1b[?2004h";
|
||||||
|
write_all(STDOUT_FILENO, os.c_str(), os.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
IO::~IO() {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::enable_mouse() {
|
||||||
|
const char *seq = "\x1b[?1000h";
|
||||||
|
write_all(STDOUT_FILENO, seq, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::disable_mouse() {
|
||||||
|
const char *seq = "\x1b[?1000l";
|
||||||
|
write_all(STDOUT_FILENO, seq, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint16_t, uint16_t> IO::terminal_size() {
|
||||||
|
struct winsize ws{};
|
||||||
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
|
||||||
|
throw fatal_error("Can't get terminal size.", 1);
|
||||||
|
return {ws.ws_row, ws.ws_col};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint16_t, uint16_t> IO::cursor_position() {
|
||||||
|
write_all(STDOUT_FILENO, "\x1b[6n", 4);
|
||||||
|
std::string response;
|
||||||
|
char c;
|
||||||
|
if (read(STDIN_FILENO, &c, 1) != 1 || c != '\x1b')
|
||||||
|
throw fatal_error("Invalid cursor position response.", 1);
|
||||||
|
if (read(STDIN_FILENO, &c, 1) != 1 || c != '[')
|
||||||
|
throw fatal_error("Invalid cursor position response.", 1);
|
||||||
|
while (true) {
|
||||||
|
if (read(STDIN_FILENO, &c, 1) != 1)
|
||||||
|
throw fatal_error("Invalid cursor position response.", 1);
|
||||||
|
if (c == 'R')
|
||||||
|
break;
|
||||||
|
response += c;
|
||||||
|
}
|
||||||
|
unsigned row;
|
||||||
|
unsigned col;
|
||||||
|
if (sscanf(response.c_str(), "%u;%u", &row, &col) != 2)
|
||||||
|
throw fatal_error("Invalid cursor position response.", 1);
|
||||||
|
return {(uint16_t)row, (uint16_t)col};
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::cleanup() {
|
||||||
|
if (cleaned)
|
||||||
|
return;
|
||||||
|
std::string os = "\x1b[?1000l\x1b[?2004l";
|
||||||
|
write_all(STDOUT_FILENO, os.c_str(), os.size());
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1)
|
||||||
|
perror("Can't clean up terminal.");
|
||||||
|
cleaned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IO::move_cursor(uint16_t row, uint16_t col) {
|
||||||
|
char buf[32];
|
||||||
|
int n = snprintf(buf, sizeof(buf), "\x1b[%u;%uH", row, col);
|
||||||
|
write_all(STDOUT_FILENO, buf, n);
|
||||||
|
}
|
||||||
|
} // namespace bed::internal::io
|
||||||
@@ -200,22 +200,6 @@ Shard *Shard::build(Shard **pieces, uint64_t lo, uint64_t hi) {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool write_all(int fd, const void *data, size_t len) {
|
|
||||||
const char *p = (const char *)data;
|
|
||||||
while (len > 0) {
|
|
||||||
ssize_t n = write(fd, p, len);
|
|
||||||
if (n > 0) {
|
|
||||||
p += n;
|
|
||||||
len -= (size_t)n;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (n == -1 && errno == EINTR)
|
|
||||||
continue;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending) {
|
Shard *Shard::from_command(const char *cmd, OriginalBuffer *o, bool posix_ending) {
|
||||||
int dest_fd = o->fd;
|
int dest_fd = o->fd;
|
||||||
if (dest_fd == -1)
|
if (dest_fd == -1)
|
||||||
|
|||||||
+2
-1
@@ -4,7 +4,8 @@
|
|||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
std::vector<std::string> args(argv, argv + argc);
|
std::vector<std::string> args(argv, argv + argc);
|
||||||
try {
|
try {
|
||||||
bed::BEd ed(args);
|
bed::internal::io::IO io = bed::internal::io::IO();
|
||||||
|
bed::BEd ed(args, io);
|
||||||
ed.run();
|
ed.run();
|
||||||
} catch (bed::fatal_error &e) {
|
} catch (bed::fatal_error &e) {
|
||||||
if (e.code)
|
if (e.code)
|
||||||
|
|||||||
Reference in New Issue
Block a user