Basic renderers and input handlers
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
*.a
|
*.a
|
||||||
*.o
|
*.o
|
||||||
*.so
|
*.so
|
||||||
|
*.yml
|
||||||
|
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
|||||||
11
compile.sh
Executable file
11
compile.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
|
||||||
|
|
||||||
|
mkdir -p "$DIR/builds"
|
||||||
|
|
||||||
|
g++ -O2 -std=c++20 -shared -fPIC -Wall -Wextra \
|
||||||
|
-o "$DIR/builds/C-crib.so" $DIR/src/cpp/*.cpp
|
||||||
|
strip "$DIR/builds/C-crib.so"
|
||||||
53
crib.rb
Executable file
53
crib.rb
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require_relative "./src/ruby/mod"
|
||||||
|
|
||||||
|
# C.start_screen
|
||||||
|
#
|
||||||
|
# at_exit do
|
||||||
|
# C.end_screen
|
||||||
|
# puts "bye"
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class Tester
|
||||||
|
# @current_row = 0
|
||||||
|
#
|
||||||
|
# class << self
|
||||||
|
# def debug_print(text)
|
||||||
|
# text.each_char.with_index do |ch, i|
|
||||||
|
# C.update(@current_row, i, ch, 0xFFFFFE, 0x000001, 0)
|
||||||
|
# end
|
||||||
|
# @current_row += 1
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def reset
|
||||||
|
# @current_row = 0
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# render_thread = Thread.new do
|
||||||
|
# loop do
|
||||||
|
# sleep(1.0/20)
|
||||||
|
# C.render
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# loop do
|
||||||
|
# sleep 0.001
|
||||||
|
# event = C.read_key
|
||||||
|
# break if event[:key_type] == 0 && event[:c] == 'q'.ord
|
||||||
|
# Tester.reset if event[:key_type] == 0 && event[:c] == 'r'.ord
|
||||||
|
# Tester.debug_print(C.get_size.to_s) if event[:key_type] == 0 && event[:c] == 's'.ord
|
||||||
|
# Tester.debug_print(event.to_s)
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# render_thread.kill
|
||||||
|
# render_thread.join
|
||||||
|
|
||||||
|
require_relative("./src/ruby/editor.rb")
|
||||||
|
|
||||||
|
test = Buffer.new("hello world\nwow")
|
||||||
|
|
||||||
|
pp test.render_text(0, 0, 10, 10)
|
||||||
125
src/cpp/input.cpp
Normal file
125
src/cpp/input.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#include "../headers/header.hpp"
|
||||||
|
|
||||||
|
int read_input(char *buf, size_t buflen) {
|
||||||
|
size_t i = 0;
|
||||||
|
int n;
|
||||||
|
n = read(STDIN_FILENO, &buf[i], 1);
|
||||||
|
if (n <= 0)
|
||||||
|
return -1;
|
||||||
|
i++;
|
||||||
|
if (buf[0] == '\x1b') {
|
||||||
|
while (i < buflen - 1) {
|
||||||
|
n = read(STDIN_FILENO, &buf[i], 1);
|
||||||
|
if (n <= 0)
|
||||||
|
break;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf[i] = '\0';
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
void capture_mouse(char *buf, KeyEvent *ret) {
|
||||||
|
uint8_t byte = buf[3];
|
||||||
|
ret->mouse_modifier = (byte >> 3) & 0x03;
|
||||||
|
uint8_t aa = (byte >> 5) & 0x03;
|
||||||
|
uint8_t cc = byte & 0x03;
|
||||||
|
ret->mouse_x = buf[4] - 33;
|
||||||
|
ret->mouse_y = buf[5] - 33;
|
||||||
|
ret->mouse_direction = 4;
|
||||||
|
if (aa == 1 && cc == 3) {
|
||||||
|
ret->mouse_state = RELEASE;
|
||||||
|
ret->mouse_button = NONE_BTN;
|
||||||
|
} else if (aa == 1) {
|
||||||
|
ret->mouse_state = PRESS;
|
||||||
|
ret->mouse_button = cc;
|
||||||
|
} else if (aa == 2) {
|
||||||
|
ret->mouse_state = DRAG;
|
||||||
|
ret->mouse_button = cc;
|
||||||
|
} else if (aa == 3) {
|
||||||
|
ret->mouse_button = SCROLL_BTN;
|
||||||
|
ret->mouse_state = SCROLL;
|
||||||
|
ret->mouse_direction = cc;
|
||||||
|
} else {
|
||||||
|
ret->mouse_state = RELEASE;
|
||||||
|
ret->mouse_button = NONE_BTN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent read_key_nonblock() {
|
||||||
|
KeyEvent ret;
|
||||||
|
char buf[7];
|
||||||
|
int n = read_input(buf, sizeof(buf));
|
||||||
|
if (n <= 0) {
|
||||||
|
ret.key_type = KEY_NONE;
|
||||||
|
ret.c = '\0';
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (n == 1) {
|
||||||
|
ret.key_type = KEY_CHAR;
|
||||||
|
ret.c = buf[0];
|
||||||
|
} else if (buf[0] == '\x1b' && buf[1] == '[' && buf[2] == 'M') {
|
||||||
|
ret.key_type = KEY_MOUSE;
|
||||||
|
capture_mouse(buf, &ret);
|
||||||
|
} else {
|
||||||
|
ret.key_type = KEY_SPECIAL;
|
||||||
|
if (buf[0] == '\x1b' && buf[1] == '[') {
|
||||||
|
int using_modifiers = buf[3] == ';';
|
||||||
|
int pos;
|
||||||
|
if (!using_modifiers) {
|
||||||
|
pos = 2;
|
||||||
|
ret.special_modifier = 0;
|
||||||
|
} else {
|
||||||
|
pos = 4;
|
||||||
|
switch (buf[3]) {
|
||||||
|
case '2':
|
||||||
|
ret.special_modifier = SHIFT;
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
ret.special_modifier = ALT;
|
||||||
|
break;
|
||||||
|
case '5':
|
||||||
|
ret.special_modifier = CNTRL;
|
||||||
|
break;
|
||||||
|
case '7':
|
||||||
|
ret.special_modifier = CNTRL_ALT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret.special_modifier = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (buf[pos]) {
|
||||||
|
case 'A':
|
||||||
|
ret.special_key = KEY_UP;
|
||||||
|
break;
|
||||||
|
case 'B':
|
||||||
|
ret.special_key = KEY_DOWN;
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
ret.special_key = KEY_RIGHT;
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
ret.special_key = KEY_LEFT;
|
||||||
|
break;
|
||||||
|
case '3':
|
||||||
|
ret.special_key = KEY_DELETE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret.special_key = 99;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent read_key() {
|
||||||
|
KeyEvent ret;
|
||||||
|
while (1) {
|
||||||
|
ret = read_key_nonblock();
|
||||||
|
if (ret.key_type != KEY_NONE)
|
||||||
|
return ret;
|
||||||
|
usleep(2500);
|
||||||
|
}
|
||||||
|
}
|
||||||
260
src/cpp/renderer.cpp
Normal file
260
src/cpp/renderer.cpp
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
// includes
|
||||||
|
#include "../../libs/libgrapheme/grapheme.h"
|
||||||
|
#include "../../libs/unicode_width/unicode_width.h"
|
||||||
|
#include "../headers/header.hpp"
|
||||||
|
|
||||||
|
struct termios orig_termios;
|
||||||
|
|
||||||
|
int rows, cols;
|
||||||
|
bool show_cursor = false;
|
||||||
|
std::vector<ScreenCell> screen;
|
||||||
|
std::vector<ScreenCell> old_screen;
|
||||||
|
std::mutex screen_mutex;
|
||||||
|
|
||||||
|
int real_width(std::string str) {
|
||||||
|
if (!str.size())
|
||||||
|
return 0;
|
||||||
|
const char *p = str.c_str();
|
||||||
|
if (str[0] == '\t')
|
||||||
|
return 4;
|
||||||
|
unicode_width_state_t state;
|
||||||
|
unicode_width_init(&state);
|
||||||
|
int width = 0;
|
||||||
|
for (size_t j = 0; j < str.size(); j++) {
|
||||||
|
unsigned char c = str[j];
|
||||||
|
if (c < 128) {
|
||||||
|
int char_width = unicode_width_process(&state, c);
|
||||||
|
if (char_width > 0)
|
||||||
|
width += char_width;
|
||||||
|
} else {
|
||||||
|
uint_least32_t cp;
|
||||||
|
size_t bytes = grapheme_decode_utf8(p + j, str.size() - j, &cp);
|
||||||
|
if (bytes > 1) {
|
||||||
|
int char_width = unicode_width_process(&state, cp);
|
||||||
|
if (char_width > 0)
|
||||||
|
width += char_width;
|
||||||
|
j += bytes - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_terminal_size() {
|
||||||
|
struct winsize w;
|
||||||
|
ioctl(0, TIOCGWINSZ, &w);
|
||||||
|
rows = w.ws_row;
|
||||||
|
cols = w.ws_col;
|
||||||
|
}
|
||||||
|
|
||||||
|
void die(const char *s) {
|
||||||
|
perror(s);
|
||||||
|
disable_raw_mode();
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void enable_raw_mode() {
|
||||||
|
if (tcgetattr(STDIN_FILENO, &orig_termios) == -1)
|
||||||
|
die("tcgetattr");
|
||||||
|
atexit(disable_raw_mode);
|
||||||
|
|
||||||
|
struct termios raw = orig_termios;
|
||||||
|
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||||
|
raw.c_oflag &= ~(OPOST);
|
||||||
|
raw.c_cflag |= (CS8);
|
||||||
|
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||||
|
raw.c_cc[VMIN] = 0;
|
||||||
|
raw.c_cc[VTIME] = 0;
|
||||||
|
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
|
||||||
|
die("tcsetattr");
|
||||||
|
|
||||||
|
std::string os = "\x1b[?1049h\x1b[2 q\x1b[?1002h\x1b[?25l";
|
||||||
|
write(STDOUT_FILENO, os.c_str(), os.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void disable_raw_mode() {
|
||||||
|
std::string os = "\x1b[?1049l\x1b[?25h";
|
||||||
|
write(STDOUT_FILENO, os.c_str(), os.size());
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
|
||||||
|
perror("tcsetattr");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_screen() {
|
||||||
|
enable_raw_mode();
|
||||||
|
get_terminal_size();
|
||||||
|
screen.assign(rows * cols, {}); // allocate & zero-init
|
||||||
|
old_screen.assign(rows * cols, {}); // allocate & zero-init
|
||||||
|
}
|
||||||
|
|
||||||
|
void end_screen() { disable_raw_mode(); }
|
||||||
|
|
||||||
|
void update(int row, int col, const char *utf8, uint32_t fg, uint32_t bg,
|
||||||
|
uint8_t flags) {
|
||||||
|
if (row < 0 || row >= rows || col < 0 || col >= cols)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int idx = row * cols + col;
|
||||||
|
std::lock_guard<std::mutex> lock(screen_mutex);
|
||||||
|
|
||||||
|
screen[idx].utf8 = utf8 ? utf8 : ""; // nullptr => empty string
|
||||||
|
screen[idx].fg = fg;
|
||||||
|
screen[idx].bg = bg;
|
||||||
|
screen[idx].flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
coords get_size() { return {rows, cols}; }
|
||||||
|
|
||||||
|
void render() {
|
||||||
|
static bool first_render = true;
|
||||||
|
uint32_t current_fg = 0;
|
||||||
|
uint32_t current_bg = 0;
|
||||||
|
bool current_italic = false;
|
||||||
|
bool current_bold = false;
|
||||||
|
bool current_underline = false;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(screen_mutex);
|
||||||
|
|
||||||
|
std::string out;
|
||||||
|
// reserve a conservative amount to avoid repeated reallocs
|
||||||
|
out.reserve(static_cast<size_t>(rows) * static_cast<size_t>(cols) * 4 + 256);
|
||||||
|
|
||||||
|
// save cursor + hide
|
||||||
|
out += "\x1b[s\x1b[?25l";
|
||||||
|
|
||||||
|
if (first_render) {
|
||||||
|
out += "\x1b[2J\x1b[H";
|
||||||
|
first_render = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int row = 0; row < rows; ++row) {
|
||||||
|
int first_change_col = -1;
|
||||||
|
int last_change_col = -1;
|
||||||
|
|
||||||
|
// detect change span in this row
|
||||||
|
for (int col = 0; col < cols; ++col) {
|
||||||
|
int idx = row * cols + col;
|
||||||
|
ScreenCell &old_cell = old_screen[idx];
|
||||||
|
ScreenCell &new_cell = screen[idx];
|
||||||
|
|
||||||
|
bool old_empty = old_cell.utf8.empty();
|
||||||
|
bool new_empty = new_cell.utf8.empty();
|
||||||
|
|
||||||
|
bool content_changed =
|
||||||
|
(old_empty && !new_empty) || (!old_empty && new_empty) ||
|
||||||
|
(!old_empty && !new_empty && old_cell.utf8 != new_cell.utf8);
|
||||||
|
|
||||||
|
bool style_changed =
|
||||||
|
(old_cell.fg != new_cell.fg) || (old_cell.bg != new_cell.bg) ||
|
||||||
|
((old_cell.flags & CF_ITALIC) != (new_cell.flags & CF_ITALIC)) ||
|
||||||
|
((old_cell.flags & CF_BOLD) != (new_cell.flags & CF_BOLD)) ||
|
||||||
|
((old_cell.flags & CF_UNDERLINE) != (new_cell.flags & CF_UNDERLINE));
|
||||||
|
|
||||||
|
if (content_changed || style_changed) {
|
||||||
|
if (first_change_col == -1)
|
||||||
|
first_change_col = col;
|
||||||
|
last_change_col = col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first_change_col == -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// move cursor once to the start of change region
|
||||||
|
char buf[32];
|
||||||
|
int n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1,
|
||||||
|
first_change_col + 1);
|
||||||
|
out.append(buf, n);
|
||||||
|
|
||||||
|
// render changed region
|
||||||
|
for (int col = first_change_col; col <= last_change_col; ++col) {
|
||||||
|
int idx = row * cols + col;
|
||||||
|
ScreenCell &old_cell = old_screen[idx];
|
||||||
|
ScreenCell &new_cell = screen[idx];
|
||||||
|
|
||||||
|
// foreground change
|
||||||
|
if (current_fg != new_cell.fg) {
|
||||||
|
if (new_cell.fg) {
|
||||||
|
char fb[64];
|
||||||
|
int m = snprintf(
|
||||||
|
fb, sizeof(fb), "\x1b[38;2;%d;%d;%dm", (new_cell.fg >> 16) & 0xFF,
|
||||||
|
(new_cell.fg >> 8) & 0xFF, (new_cell.fg >> 0) & 0xFF);
|
||||||
|
out.append(fb, m);
|
||||||
|
} else {
|
||||||
|
out += "\x1b[39m";
|
||||||
|
}
|
||||||
|
current_fg = new_cell.fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// background change
|
||||||
|
if (current_bg != new_cell.bg) {
|
||||||
|
if (new_cell.bg) {
|
||||||
|
char bb[64];
|
||||||
|
int m = snprintf(
|
||||||
|
bb, sizeof(bb), "\x1b[48;2;%d;%d;%dm", (new_cell.bg >> 16) & 0xFF,
|
||||||
|
(new_cell.bg >> 8) & 0xFF, (new_cell.bg >> 0) & 0xFF);
|
||||||
|
out.append(bb, m);
|
||||||
|
} else {
|
||||||
|
out += "\x1b[49m";
|
||||||
|
}
|
||||||
|
current_bg = new_cell.bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// italic
|
||||||
|
bool italic = (new_cell.flags & CF_ITALIC) != 0;
|
||||||
|
if (italic != current_italic) {
|
||||||
|
out += italic ? "\x1b[3m" : "\x1b[23m";
|
||||||
|
current_italic = italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bold
|
||||||
|
bool bold = (new_cell.flags & CF_BOLD) != 0;
|
||||||
|
if (bold != current_bold) {
|
||||||
|
out += bold ? "\x1b[1m" : "\x1b[22m";
|
||||||
|
current_bold = bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
// underline
|
||||||
|
bool underline = (new_cell.flags & CF_UNDERLINE) != 0;
|
||||||
|
if (underline != current_underline) {
|
||||||
|
out += underline ? "\x1b[4m" : "\x1b[24m";
|
||||||
|
current_underline = underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
// content
|
||||||
|
if (!new_cell.utf8.empty()) {
|
||||||
|
if (new_cell.utf8[0] == '\t')
|
||||||
|
out.append(" ");
|
||||||
|
else
|
||||||
|
out.append(new_cell.utf8);
|
||||||
|
} else {
|
||||||
|
out.append(1, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy new -> old (std::string assignment, no strdup/free)
|
||||||
|
old_cell.utf8 = new_cell.utf8;
|
||||||
|
old_cell.fg = new_cell.fg;
|
||||||
|
old_cell.bg = new_cell.bg;
|
||||||
|
old_cell.flags = new_cell.flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// final reset + restore cursor + show cursor
|
||||||
|
out += "\x1b[0m";
|
||||||
|
out += "\x1b[u";
|
||||||
|
if (show_cursor)
|
||||||
|
out += "\x1b[?25h";
|
||||||
|
|
||||||
|
// single syscall to write the whole frame
|
||||||
|
ssize_t written = write(STDOUT_FILENO, out.data(), out.size());
|
||||||
|
(void)written; // you may check for errors in debug builds
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_cursor(int row, int col, bool show_cursor_param) {
|
||||||
|
char buf[32];
|
||||||
|
int n = snprintf(buf, sizeof(buf), "\x1b[%d;%dH", row + 1, col + 1);
|
||||||
|
show_cursor = show_cursor_param;
|
||||||
|
write(STDOUT_FILENO, buf, n);
|
||||||
|
}
|
||||||
102
src/headers/header.hpp
Normal file
102
src/headers/header.hpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define KEY_CHAR 0
|
||||||
|
#define KEY_SPECIAL 1
|
||||||
|
#define KEY_MOUSE 2
|
||||||
|
#define KEY_NONE 3
|
||||||
|
|
||||||
|
#define KEY_UP 0
|
||||||
|
#define KEY_DOWN 1
|
||||||
|
#define KEY_LEFT 2
|
||||||
|
#define KEY_RIGHT 3
|
||||||
|
#define KEY_DELETE 4
|
||||||
|
|
||||||
|
#define KEY_ESC '\x1b'
|
||||||
|
|
||||||
|
#define PRESS 0
|
||||||
|
#define RELEASE 1
|
||||||
|
#define DRAG 2
|
||||||
|
#define SCROLL 3
|
||||||
|
|
||||||
|
#define LEFT_BTN 0
|
||||||
|
#define MIDDLE_BTN 1
|
||||||
|
#define RIGHT_BTN 2
|
||||||
|
#define SCROLL_BTN 3
|
||||||
|
#define NONE_BTN 4
|
||||||
|
|
||||||
|
#define SCROLL_UP 0
|
||||||
|
#define SCROLL_DOWN 1
|
||||||
|
#define SCROLL_LEFT 2
|
||||||
|
#define SCROLL_RIGHT 3
|
||||||
|
|
||||||
|
#define ALT 1
|
||||||
|
#define CNTRL 2
|
||||||
|
#define CNTRL_ALT 3
|
||||||
|
#define SHIFT 4
|
||||||
|
|
||||||
|
enum CellFlags : uint8_t {
|
||||||
|
CF_NONE = 0,
|
||||||
|
CF_ITALIC = 1 << 0,
|
||||||
|
CF_BOLD = 1 << 1,
|
||||||
|
CF_UNDERLINE = 1 << 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScreenCell {
|
||||||
|
std::string utf8; // empty => no content
|
||||||
|
uint32_t fg = 0;
|
||||||
|
uint32_t bg = 0;
|
||||||
|
uint8_t flags = CF_NONE;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyEvent {
|
||||||
|
uint8_t key_type;
|
||||||
|
|
||||||
|
char c;
|
||||||
|
|
||||||
|
uint8_t special_key;
|
||||||
|
uint8_t special_modifier;
|
||||||
|
|
||||||
|
uint8_t mouse_x;
|
||||||
|
uint8_t mouse_y;
|
||||||
|
uint8_t mouse_button;
|
||||||
|
uint8_t mouse_state;
|
||||||
|
uint8_t mouse_direction;
|
||||||
|
uint8_t mouse_modifier;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int rows, cols;
|
||||||
|
extern std::vector<ScreenCell> screen; // size rows*cols
|
||||||
|
extern std::vector<ScreenCell> old_screen;
|
||||||
|
extern std::mutex screen_mutex;
|
||||||
|
|
||||||
|
struct coords {
|
||||||
|
int row;
|
||||||
|
int col;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
void get_terminal_size();
|
||||||
|
void die(const char *s);
|
||||||
|
void enable_raw_mode();
|
||||||
|
void disable_raw_mode();
|
||||||
|
void start_screen();
|
||||||
|
void end_screen();
|
||||||
|
void update(int row, int col, const char *utf8, uint32_t fg, uint32_t bg,
|
||||||
|
uint8_t flags);
|
||||||
|
void set_cursor(int row, int col, bool show_cursor_param);
|
||||||
|
void render();
|
||||||
|
coords get_size();
|
||||||
|
|
||||||
|
int real_width(std::string str);
|
||||||
|
|
||||||
|
int read_input(char *buf, size_t buflen);
|
||||||
|
KeyEvent read_key_nonblock();
|
||||||
|
KeyEvent read_key();
|
||||||
|
}
|
||||||
70
src/ruby/editor.rb
Normal file
70
src/ruby/editor.rb
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
class Buffer
|
||||||
|
# Simple structs for clarity
|
||||||
|
Diagnostic = Struct.new(:x0, :y0, :x1, :y1, :message)
|
||||||
|
Highlight = Struct.new(:x0, :y0, :x1, :y1, :fg, :bg)
|
||||||
|
VirtualText = Struct.new(:x, :y, :lines)
|
||||||
|
Cursor = Struct.new(:x, :y)
|
||||||
|
|
||||||
|
attr_accessor :text, :cursor, :selection_start,
|
||||||
|
:diagnostics, :highlights, :virtual_texts
|
||||||
|
|
||||||
|
def initialize(initial_text = "")
|
||||||
|
@text = initial_text
|
||||||
|
@cursor = Cursor.new(0, 0)
|
||||||
|
@selection_start = Cursor.new(0, 0)
|
||||||
|
@diagnostics = []
|
||||||
|
@highlights = []
|
||||||
|
@virtual_texts = []
|
||||||
|
end
|
||||||
|
|
||||||
|
# Utility methods
|
||||||
|
def lines
|
||||||
|
@text.split("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def line_count
|
||||||
|
lines.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def line(y)
|
||||||
|
lines[y] || ""
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert(x, y, str)
|
||||||
|
current_line = lines[y] || ""
|
||||||
|
before = current_line[0...x] || ""
|
||||||
|
after = current_line[x..-1] || ""
|
||||||
|
lines_arr = lines
|
||||||
|
lines_arr[y] = before + str + after
|
||||||
|
@text = lines_arr.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def erase(x0, y0, x1, y1)
|
||||||
|
lines_arr = lines
|
||||||
|
if y0 == y1
|
||||||
|
line = lines_arr[y0] || ""
|
||||||
|
lines_arr[y0] = line[0...x0] + line[x1..-1].to_s
|
||||||
|
else
|
||||||
|
first = lines_arr[y0][0...x0]
|
||||||
|
last = lines_arr[y1][x1..-1].to_s
|
||||||
|
lines_arr[y0..y1] = [first + last]
|
||||||
|
end
|
||||||
|
@text = lines_arr.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add overlays
|
||||||
|
def add_diagnostic(x0, y0, x1, y1, message)
|
||||||
|
@diagnostics << Diagnostic.new(x0, y0, x1, y1, message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_highlight(x0, y0, x1, y1, fg: 0xFFFFFF, bg: 0x000000)
|
||||||
|
@highlights << Highlight.new(x0, y0, x1, y1, fg, bg)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_virtual_text(x, y, lines)
|
||||||
|
@virtual_texts << VirtualText.new(x, y, lines)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render()
|
||||||
|
end
|
||||||
|
end
|
||||||
57
src/ruby/mod.rb
Normal file
57
src/ruby/mod.rb
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
require "ffi"
|
||||||
|
require_relative "utils"
|
||||||
|
|
||||||
|
module C
|
||||||
|
extend FFI::Library
|
||||||
|
ffi_lib File.join(__dir__, "../../builds/C-crib.so")
|
||||||
|
|
||||||
|
class KeyEvent < FFI::Struct
|
||||||
|
layout :key_type, :uint8,
|
||||||
|
:c, :char,
|
||||||
|
:special_key, :uint8,
|
||||||
|
:special_modifier, :uint8,
|
||||||
|
:mouse_x, :uint8,
|
||||||
|
:mouse_y, :uint8,
|
||||||
|
:mouse_button, :uint8,
|
||||||
|
:mouse_state, :uint8,
|
||||||
|
:mouse_direction, :uint8,
|
||||||
|
:mouse_modifier, :uint8
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
case KEY_TYPE[self[:key_type]]
|
||||||
|
when :char
|
||||||
|
"#<KeyEvent char=#{self[:c].inspect}>"
|
||||||
|
when :special
|
||||||
|
"#<KeyEvent special key=#{SPECIAL_KEY[self[:special_key]]} mod=#{MODIFIER[self[:special_modifier]]}>"
|
||||||
|
when :mouse
|
||||||
|
"#<KeyEvent mouse x=#{self[:mouse_x]} y=#{self[:mouse_y]} " \
|
||||||
|
"btn=#{MOUSE_BUTTON[self[:mouse_button]]} state=#{MOUSE_STATE[self[:mouse_state]]} " \
|
||||||
|
"scroll_dir=#{SCROLL_DIR[self[:mouse_direction]]} mod=#{MODIFIER[self[:mouse_modifier]]}>"
|
||||||
|
else
|
||||||
|
"#<KeyEvent type=#{self[:key_type]} unknown=true>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Coords < FFI::Struct
|
||||||
|
layout :x, :int,
|
||||||
|
:y, :int
|
||||||
|
|
||||||
|
def to_ary
|
||||||
|
[self[:x], self[:y]]
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
"#<Coords x=#{self[:x]} y=#{self[:y]}>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attach_function :start_screen, [], :void
|
||||||
|
attach_function :end_screen, [], :void
|
||||||
|
attach_function :update, [:int, :int, :string, :uint32, :uint32, :uint8], :void
|
||||||
|
attach_function :render, [], :void
|
||||||
|
attach_function :set_cursor, [:int, :int, :int], :void
|
||||||
|
attach_function :read_key, [], KeyEvent.by_value
|
||||||
|
attach_function :get_size, [], Coords.by_value
|
||||||
|
attach_function :real_width, [:string], :int
|
||||||
|
end
|
||||||
56
src/ruby/utils.rb
Normal file
56
src/ruby/utils.rb
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
def ctrl_key(k)
|
||||||
|
k.ord & 0x1F
|
||||||
|
end
|
||||||
|
|
||||||
|
# Key types
|
||||||
|
KEY_TYPE = {
|
||||||
|
0 => :char,
|
||||||
|
1 => :special,
|
||||||
|
2 => :mouse
|
||||||
|
}
|
||||||
|
|
||||||
|
# Special keys
|
||||||
|
SPECIAL_KEY = {
|
||||||
|
0 => :up,
|
||||||
|
1 => :down,
|
||||||
|
2 => :left,
|
||||||
|
3 => :right,
|
||||||
|
4 => :delete
|
||||||
|
}
|
||||||
|
|
||||||
|
# Control key
|
||||||
|
KEY_ESC = "\x1b"
|
||||||
|
|
||||||
|
# Mouse states
|
||||||
|
MOUSE_STATE = {
|
||||||
|
0 => :press,
|
||||||
|
1 => :release,
|
||||||
|
2 => :drag,
|
||||||
|
3 => :scroll
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mouse buttons
|
||||||
|
MOUSE_BUTTON = {
|
||||||
|
0 => :left,
|
||||||
|
1 => :middle,
|
||||||
|
2 => :right,
|
||||||
|
3 => :scroll,
|
||||||
|
4 => :none
|
||||||
|
}
|
||||||
|
|
||||||
|
# Scroll directions
|
||||||
|
SCROLL_DIR = {
|
||||||
|
0 => :up,
|
||||||
|
1 => :down,
|
||||||
|
2 => :left,
|
||||||
|
3 => :right,
|
||||||
|
4 => :none
|
||||||
|
}
|
||||||
|
|
||||||
|
# Modifiers
|
||||||
|
MODIFIER = {
|
||||||
|
1 => :alt,
|
||||||
|
2 => :cntrl,
|
||||||
|
3 => :cntrl_alt,
|
||||||
|
4 => :shift
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user