frontend: add a line editor for shell input

This commit is contained in:
2024-11-18 09:53:55 +00:00
parent f44a3364b3
commit 4fc1a6ade8
22 changed files with 2137 additions and 0 deletions

40
frontend/line-ed/buffer.c Normal file
View File

@@ -0,0 +1,40 @@
#include "buffer.h"
#include "line-ed.h"
const char *line_start(struct line_ed *ed, unsigned int y)
{
const char *line = ed->l_buf;
for (unsigned int i = 0; i < y; i++) {
line += strcspn(line, "\n");
if (*line == '\n') {
line++;
}
}
return line;
}
unsigned int line_length(struct line_ed *ed, unsigned int y)
{
const char *line = ed->l_buf;
for (unsigned int i = 0; i < y; i++) {
line += strcspn(line, "\n");
if (*line == '\n') {
line++;
}
}
if (*line == '\0') {
return 0;
}
unsigned int len = strcspn(line, "\n");
if (line[len] == '\n') {
len++;
}
return len;
}

14
frontend/line-ed/buffer.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef LINE_ED_BUFFER_H_
#define LINE_ED_BUFFER_H_
struct line_ed;
/* returns a pointer to the start of the line based on the given `y`
* coordinate */
extern const char *line_start(struct line_ed *ed, unsigned int y);
/* returns the length of the line based on the given `y` coordinate.
* for any line other than the last line in the buffer, this length
* INCLUDES the trailing linefeed. */
extern unsigned int line_length(struct line_ed *ed, unsigned int y);
#endif

27
frontend/line-ed/cursor.c Normal file
View File

@@ -0,0 +1,27 @@
#include "line-ed.h"
#include "cursor.h"
#include "prompt.h"
void line_ed_coords_to_physical_coords(struct line_ed *ed,
unsigned int x, unsigned int y,
unsigned int *out_x, unsigned int *out_y)
{
unsigned int prompt_len = 0;
if (ed->l_cursor_y == 0) {
prompt_len = prompt_length(ed, PROMPT_MAIN);
} else if (ed->l_cursor_y <= ed->l_continuations) {
prompt_len = prompt_length(ed, PROMPT_CONT);
}
if (y == 0) {
x += prompt_len;
}
if (out_x) {
*out_x = x;
}
if (out_y) {
*out_y = y;
}
}

10
frontend/line-ed/cursor.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef LINE_ED_CURSOR_H_
#define LINE_ED_CURSOR_H_
struct line_ed;
extern void line_ed_coords_to_physical_coords(struct line_ed *ed,
unsigned int x, unsigned int y,
unsigned int *out_x, unsigned int *out_y);
#endif

View File

@@ -0,0 +1,67 @@
#include "line-ed.h"
#include <blue/object/array.h>
#include <blue/object/string.h>
void alloc_empty_history_entry(struct line_ed *ed)
{
b_string *str = (b_string *)b_array_at(
ed->l_history, b_array_size(ed->l_history) - 1);
if (!str || !b_string_get_size(str, B_STRLEN_NORMAL)) {
str = b_string_create();
b_array_append(ed->l_history, (b_object *)str);
}
ed->l_history_pos = b_array_size(ed->l_history) - 1;
}
void save_buf_to_history(struct line_ed *ed)
{
b_string *cur = (b_string *)b_array_get(ed->l_history, ed->l_history_pos);
b_string_replace_all(cur, ed->l_buf);
}
void append_buf_to_history(struct line_ed *ed)
{
b_string *cur = (b_string *)b_array_get(ed->l_history, ed->l_history_pos);
char s[] = {'\n', 0};
b_string_append_cstr(cur, s);
b_string_append_cstr(cur, ed->l_buf);
}
void load_buf_from_history(struct line_ed *ed)
{
b_string *cur = (b_string *)b_array_at(ed->l_history, ed->l_history_pos);
size_t len
= MIN(ed->l_buf_end - ed->l_buf - 1,
b_string_get_size(cur, B_STRLEN_NORMAL));
memcpy(ed->l_buf, b_string_ptr(cur), len);
ed->l_buf[len] = '\0';
unsigned int x = 0, y = 0;
for (size_t i = 0; ed->l_buf[i]; i++) {
if (ed->l_buf[i] == '\n') {
x = 0;
y++;
} else {
x++;
}
}
ed->l_buf_ptr = ed->l_buf + len;
ed->l_line_end = ed->l_buf_ptr;
ed->l_cursor_x = x;
ed->l_cursor_y = y;
}
const char *last_history_line(struct line_ed *ed)
{
unsigned long nlines = b_array_size(ed->l_history);
if (nlines < 2) {
return NULL;
}
b_string *last = (b_string *)b_array_at(ed->l_history, nlines - 2);
return b_string_ptr(last);
}

View File

@@ -0,0 +1,12 @@
#ifndef LINE_ED_HISTORY_H_
#define LINE_ED_HISTORY_H_
struct line_ed;
extern void alloc_empty_history_entry(struct line_ed *ed);
extern void save_buf_to_history(struct line_ed *ed);
extern void append_buf_to_history(struct line_ed *ed);
extern void load_buf_from_history(struct line_ed *ed);
extern const char *last_history_line(struct line_ed *ed);
#endif

287
frontend/line-ed/hl-range.c Normal file
View File

@@ -0,0 +1,287 @@
#include "hl-range.h"
#include "line-ed.h"
#include "prompt.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int compare_coords(
unsigned int ax, unsigned int ay, unsigned int bx, unsigned int by)
{
if (ay > by) {
return 1;
}
if (ay < by) {
return -1;
}
if (ax > bx) {
return 1;
}
if (ax < bx) {
return -1;
}
return 0;
}
struct hl_range *get_hl_range(struct line_ed *ed, unsigned int x, unsigned int y)
{
if (b_queue_empty(&ed->l_hl_ranges)) {
return NULL;
}
struct hl_range *best_match = NULL;
b_queue_iterator it;
b_queue_foreach (&it, &ed->l_hl_ranges) {
struct hl_range *cur = b_unbox(struct hl_range, it.entry, h_entry);
int cmp_end = compare_coords(x, y, cur->h_end_x, cur->h_end_y);
if (cmp_end == 1) {
continue;
}
return cur;
}
return NULL;
}
struct hl_range *get_next_hl_range(struct hl_range *range)
{
b_queue_entry *entry = &range->h_entry;
entry = b_queue_next(entry);
if (!entry) {
return NULL;
}
range = b_unbox(struct hl_range, entry, h_entry);
return range;
}
int apply_hl_range(
struct hl_range *range, struct s_tty *tty, unsigned int x, unsigned int y)
{
if (!range) {
s_tty_reset_vmode(tty);
return 0;
}
int cmp_start = compare_coords(x, y, range->h_start_x, range->h_start_y);
int cmp_end = compare_coords(x, y, range->h_end_x, range->h_end_y);
if (cmp_start < 0) {
s_tty_reset_vmode(tty);
}
if (cmp_start >= 0 && cmp_end <= 0) {
s_tty_set_vmode(tty, &range->h_vmode);
}
if (cmp_end == 1) {
return -1;
}
return 0;
}
struct hl_range *create_highlight(
unsigned long start_x, unsigned long start_y, unsigned long end_x,
unsigned long end_y, const struct s_tty_vmode *vmode)
{
struct hl_range *out = malloc(sizeof *out);
if (!out) {
return NULL;
}
memset(out, 0x0, sizeof *out);
out->h_start_x = start_x;
out->h_start_y = start_y;
out->h_end_x = end_x;
out->h_end_y = end_y;
memcpy(&out->h_vmode, vmode, sizeof *vmode);
return out;
}
enum hl_range_comparison compare_hl_ranges(
const struct hl_range *a, const struct hl_range *b)
{
int cmp_a_end_b_start = compare_coords(
a->h_end_x, a->h_end_y, b->h_start_x, b->h_start_y);
if (cmp_a_end_b_start == -1) {
return HL_RANGE_LESS;
}
int cmp_b_end_a_start = compare_coords(
b->h_end_x, b->h_end_y, a->h_start_x, a->h_start_y);
if (cmp_b_end_a_start == -1) {
return HL_RANGE_GREATER;
}
/* A end IS greater than B start
* B end IS greater than A start */
int cmp_a_start_b_start = compare_coords(
a->h_start_x, a->h_start_y, b->h_start_x, b->h_start_y);
int cmp_a_end_b_end
= compare_coords(a->h_end_x, a->h_end_y, b->h_end_x, b->h_end_y);
switch (cmp_a_start_b_start) {
case 1:
return cmp_a_end_b_end <= 0 ? HL_RANGE_A_IN_B
: HL_RANGE_GREATER_OVERLAP;
case 0:
switch (cmp_a_end_b_end) {
case 1:
return HL_RANGE_B_IN_A;
case 0:
return HL_RANGE_EQUAL;
case -1:
return HL_RANGE_A_IN_B;
default:
break;
}
case -1:
return cmp_a_end_b_end >= 0 ? HL_RANGE_B_IN_A
: HL_RANGE_LESS_OVERLAP;
default:
break;
}
/* should never reach here */
return HL_RANGE_EQUAL;
}
static void move_end_to_meet_start(
struct hl_range *move_end_of, struct hl_range *to_meet_start_of)
{
move_end_of->h_end_y = to_meet_start_of->h_start_y;
if (to_meet_start_of->h_start_x == 0) {
move_end_of->h_end_y--;
move_end_of->h_end_x = (unsigned int)-1;
} else {
move_end_of->h_end_x = to_meet_start_of->h_start_x - 1;
}
}
static void move_start_to_meet_end(
struct hl_range *move_start_of, struct hl_range *to_meet_end_of)
{
move_start_of->h_end_y = to_meet_end_of->h_end_y;
move_start_of->h_start_x = to_meet_end_of->h_end_x + 1;
}
void line_ed_put_highlight(
struct line_ed *ed, unsigned long start_x, unsigned long start_y,
unsigned long end_x, unsigned long end_y, const struct s_tty_vmode *vmode)
{
struct hl_range *highlight
= create_highlight(start_x, start_y, end_x, end_y, vmode);
if (!highlight) {
return;
}
struct hl_range *h2 = NULL;
b_queue_entry *entry = NULL;
entry = b_queue_first(&ed->l_hl_ranges);
if (!entry) {
b_queue_push_back(&ed->l_hl_ranges, &highlight->h_entry);
return;
}
struct hl_range *cur = NULL;
enum hl_range_comparison prev_cmp = -1;
b_queue_entry *insert_before = NULL;
b_queue_entry *insert_after = NULL;
bool end = false;
while (entry) {
b_queue_entry *next = b_queue_next(entry);
cur = b_unbox(struct hl_range, entry, h_entry);
enum hl_range_comparison cmp = compare_hl_ranges(cur, highlight);
switch (cmp) {
case HL_RANGE_A_IN_B:
b_queue_delete(&ed->l_hl_ranges, entry);
free(cur);
break;
case HL_RANGE_B_IN_A:
move_end_to_meet_start(cur, highlight);
h2 = create_highlight(
0, 0, cur->h_end_x, cur->h_end_y, &cur->h_vmode);
move_start_to_meet_end(h2, highlight);
b_queue_insert_after(
&ed->l_hl_ranges, &highlight->h_entry,
&cur->h_entry);
b_queue_insert_after(
&ed->l_hl_ranges, &h2->h_entry,
&highlight->h_entry);
insert_before = insert_after = NULL;
end = true;
break;
case HL_RANGE_LESS_OVERLAP:
move_end_to_meet_start(cur, highlight);
case HL_RANGE_LESS:
insert_after = entry;
break;
case HL_RANGE_GREATER_OVERLAP:
move_start_to_meet_end(cur, highlight);
insert_before = entry;
break;
case HL_RANGE_GREATER:
b_queue_insert_before(
&ed->l_hl_ranges, &highlight->h_entry, entry);
insert_before = insert_after = NULL;
end = true;
break;
default:
break;
}
entry = next;
if (end) {
break;
}
}
if (insert_before) {
b_queue_insert_before(
&ed->l_hl_ranges, &highlight->h_entry, insert_before);
} else if (insert_after) {
b_queue_insert_after(
&ed->l_hl_ranges, &highlight->h_entry, insert_after);
}
}
void line_ed_clear_highlights(struct line_ed *ed)
{
b_queue_entry *entry = b_queue_pop_front(&ed->l_hl_ranges);
while (entry) {
struct hl_range *range = b_unbox(struct hl_range, entry, h_entry);
free(range);
entry = b_queue_pop_front(&ed->l_hl_ranges);
}
}
void line_ed_print_highlights(struct line_ed *ed)
{
b_queue_iterator it;
b_queue_foreach (&it, &ed->l_hl_ranges) {
struct hl_range *h = b_unbox(struct hl_range, it.entry, h_entry);
printf("(%u, %u) -> (%u, %u)\n", h->h_start_x, h->h_start_y,
h->h_end_x, h->h_end_y);
}
}

View File

@@ -0,0 +1,41 @@
#ifndef LINE_ED_HL_RANGE_H_
#define LINE_ED_HL_RANGE_H_
#include "tty.h"
#include <blue/core/queue.h>
struct line_ed;
enum hl_range_comparison {
HL_RANGE_LESS,
HL_RANGE_LESS_OVERLAP,
HL_RANGE_EQUAL,
HL_RANGE_A_IN_B,
HL_RANGE_B_IN_A,
HL_RANGE_GREATER_OVERLAP,
HL_RANGE_GREATER,
};
struct hl_range {
unsigned int h_start_x, h_start_y;
unsigned int h_end_x, h_end_y;
struct s_tty_vmode h_vmode;
b_queue_entry h_entry;
};
extern int compare_coords(
unsigned int ax, unsigned int ay, unsigned int bx, unsigned int by);
extern struct hl_range *get_hl_range(
struct line_ed *ed, unsigned int x, unsigned int y);
extern struct hl_range *get_next_hl_range(struct hl_range *range);
extern int apply_hl_range(
struct hl_range *range, struct s_tty *tty, unsigned int x, unsigned int y);
extern struct hl_range *create_highlight(
unsigned long start_x, unsigned long start_y, unsigned long end_x,
unsigned long end_y, const struct s_tty_vmode *vmode);
extern enum hl_range_comparison compare_hl_ranges(
const struct hl_range *a, const struct hl_range *b);
#endif

37
frontend/line-ed/hook.c Normal file
View File

@@ -0,0 +1,37 @@
#include "hook.h"
#include "line-ed.h"
void line_ed_add_hook(struct line_ed *ed, struct line_ed_hook *hook)
{
b_queue_push_back(&ed->l_hooks, &hook->hook_entry);
}
void line_ed_remove_hook(struct line_ed *ed, struct line_ed_hook *hook)
{
b_queue_delete(&ed->l_hooks, &hook->hook_entry);
}
void hook_keypress(struct line_ed *ed, s_keycode key)
{
b_queue_iterator it;
b_queue_foreach (&it, &ed->l_hooks) {
struct line_ed_hook *hook
= b_unbox(struct line_ed_hook, it.entry, hook_entry);
if (hook->hook_keypress) {
hook->hook_keypress(ed, hook, key);
}
}
}
void hook_buffer_modified(struct line_ed *ed)
{
b_queue_iterator it;
b_queue_foreach (&it, &ed->l_hooks) {
struct line_ed_hook *hook
= b_unbox(struct line_ed_hook, it.entry, hook_entry);
if (hook->hook_buffer_modified) {
hook->hook_buffer_modified(ed, hook);
}
}
}

16
frontend/line-ed/hook.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef LINE_ED_HOOK_H_
#define LINE_ED_HOOK_H_
#include "tty.h"
enum hook_id {
HOOK_KEYPRESS,
HOOK_BEFORE_PAINT,
};
struct line_ed;
extern void hook_keypress(struct line_ed *ed, s_keycode key);
extern void hook_buffer_modified(struct line_ed *ed);
#endif

View File

@@ -0,0 +1,131 @@
#include "highlight.h"
#include <ivy/lang/lex.h>
#include <stdlib.h>
#include <string.h>
#define LEX_SOURCE_TO_HIGHLIGHT(p) \
((struct highlight_hook *)((char *)p \
- offsetof(struct highlight_hook, hook_src)))
#define HOOK_TO_HIGHLIGHT(p) \
((struct highlight_hook *)((char *)p \
- offsetof(struct highlight_hook, hook_base)))
static enum ivy_status readline(
struct ivy_line_source *source, char *out, size_t max, size_t *read,
const char *scope_type)
{
struct highlight_hook *highlight = LEX_SOURCE_TO_HIGHLIGHT(source);
const char *bufp = highlight->hook_bufp;
size_t r = 0;
for (size_t i = 0; i < max; i++) {
char c = *bufp;
out[i] = c;
if (c == '\0') {
break;
}
r++;
bufp++;
if (c == '\n') {
break;
}
}
highlight->hook_bufp = bufp;
*read = r;
return r > 0 ? IVY_OK : IVY_ERR_EOF;
}
static void highlight_token(
struct line_ed *ed, struct ivy_token *tok, struct s_tty_vmode *vmode)
{
/* TODO re-enable this once tokens contain positional info */
#if 0
line_ed_put_highlight(
ed, tok->t_start_col - 1, tok->t_start_row - 1,
tok->t_end_col - 1, tok->t_end_row - 1, vmode);
#endif
}
static void buffer_modified(struct line_ed *ed, struct line_ed_hook *hook)
{
#if 0
struct highlight_hook *highlight = HOOK_TO_HIGHLIGHT(hook);
line_ed_clear_highlights(ed);
lex_reset(highlight->hook_lex);
highlight->hook_bufp = ed->l_buf;
struct s_tty_vmode vmode_number = MAKE_VMODE(
MAKE_COLOUR_16(TTY_COLOUR16_CYAN), MAKE_COLOUR_DEFAULT(),
TTY_ATTRIB_NORMAL);
struct s_tty_vmode vmode_keyword = MAKE_VMODE(
MAKE_COLOUR_16(TTY_COLOUR16_BRIGHT_YELLOW),
MAKE_COLOUR_DEFAULT(), TTY_ATTRIB_BOLD);
struct s_tty_vmode vmode_var = MAKE_VMODE(
MAKE_COLOUR_16(TTY_COLOUR16_MAGENTA), MAKE_COLOUR_DEFAULT(),
TTY_ATTRIB_ITALIC);
struct lex_token *tok;
while (1) {
tok = lex_advance(highlight->hook_lex);
if (!tok) {
break;
}
if (tok->t_start_col == 0) {
lex_token_destroy(tok);
continue;
}
switch (tok->t_type) {
case TOK_NUMBER:
highlight_token(ed, tok, &vmode_number);
break;
case TOK_KEYWORD:
highlight_token(ed, tok, &vmode_keyword);
break;
case TOK_VAR:
highlight_token(ed, tok, &vmode_var);
break;
default:
break;
}
lex_token_destroy(tok);
}
#endif
}
extern struct highlight_hook *highlight_hook_create(void)
{
#if 0
struct highlight_hook *out = malloc(sizeof *out);
if (!out) {
return NULL;
}
memset(out, 0x0, sizeof *out);
out->hook_lex = lex_create(LEX_MODE_SPECULATIVE);
out->hook_base.hook_buffer_modified = buffer_modified;
out->hook_src.s_readline = readline;
lex_set_source(out->hook_lex, &out->hook_src);
return out;
#endif
return NULL;
}
extern void highlight_hook_destroy(struct highlight_hook *hook)
{
#if 0
lex_destroy(hook->hook_lex);
free(hook);
#endif
}

View File

@@ -0,0 +1,20 @@
#ifndef LINE_ED_HOOKS_HIGHLIGHT_H_
#define LINE_ED_HOOKS_HIGHLIGHT_H_
#include "../line-ed.h"
#include <ivy/lang/lex.h>
struct lex;
struct highlight_hook {
struct line_ed_hook hook_base;
struct ivy_line_source hook_src;
const char *hook_bufp;
struct lex *hook_lex;
};
extern struct highlight_hook *highlight_hook_create(void);
extern void highlight_hook_destroy(struct highlight_hook *hook);
#endif

204
frontend/line-ed/input.c Normal file
View File

@@ -0,0 +1,204 @@
#include "buffer.h"
#include "cursor.h"
#include "history.h"
#include "hook.h"
#include "line-ed.h"
#include "prompt.h"
#include "refresh.h"
#include <stdio.h>
void put_char(struct line_ed *ed, char c)
{
if (ed->l_buf_ptr > ed->l_line_end + 1) {
return;
}
struct refresh_state state = {
.r_prev_cursor_x = ed->l_cursor_x,
.r_prev_cursor_y = ed->l_cursor_y,
};
unsigned int prev_cursor = ed->l_buf_ptr - ed->l_buf;
char *dest = ed->l_buf_ptr;
unsigned int len = ed->l_line_end - ed->l_buf_ptr + 1;
if (dest < ed->l_line_end) {
memmove(dest + 1, dest, len);
}
ed->l_cursor_x++;
ed->l_line_end++;
ed->l_buf_ptr++;
*dest = c;
if (ed->l_buf_ptr == ed->l_line_end) {
*ed->l_buf_ptr = '\0';
}
hook_buffer_modified(ed);
put_refresh(ed, &state);
}
static void backspace_simple(struct line_ed *ed)
{
if (ed->l_buf_ptr == ed->l_buf) {
return;
}
struct refresh_state state = {
.r_prev_cursor_x = ed->l_cursor_x,
.r_prev_cursor_y = ed->l_cursor_y,
};
unsigned int prev_cursor = ed->l_buf_ptr - ed->l_buf;
char *dest = ed->l_buf_ptr;
unsigned int len = ed->l_line_end - ed->l_buf_ptr + 1;
memmove(dest - 1, dest, len);
ed->l_cursor_x--;
ed->l_line_end--;
ed->l_buf_ptr--;
hook_buffer_modified(ed);
backspace_simple_refresh(ed, &state);
}
static void backspace_nl(struct line_ed *ed)
{
unsigned int prev_line_len = line_length(ed, ed->l_cursor_y - 1);
struct refresh_state state = {
.r_prev_cursor_x = ed->l_cursor_x,
.r_prev_cursor_y = ed->l_cursor_y,
.r_prev_line_len = prev_line_len,
};
char *dest = ed->l_buf_ptr;
unsigned int len = ed->l_line_end - ed->l_buf_ptr + 1;
memmove(dest - 1, dest, len);
ed->l_cursor_x = prev_line_len - 1;
ed->l_cursor_y--;
ed->l_buf_ptr--;
ed->l_line_end--;
hook_buffer_modified(ed);
backspace_nl_refresh(ed, &state);
}
void backspace(struct line_ed *ed)
{
if (ed->l_buf_ptr == ed->l_buf) {
return;
}
if (ed->l_cursor_x == 0 && ed->l_cursor_y <= ed->l_continuations) {
return;
}
if (ed->l_cursor_x == 0 && ed->l_cursor_y > 0) {
backspace_nl(ed);
} else {
backspace_simple(ed);
}
}
void cursor_left(struct line_ed *ed)
{
if (ed->l_cursor_x != 0) {
fputs("\010", stdout);
fflush(stdout);
ed->l_cursor_x--;
ed->l_buf_ptr--;
return;
}
if (ed->l_cursor_y <= ed->l_continuations || ed->l_buf_ptr <= ed->l_buf) {
return;
}
ed->l_cursor_y--;
ed->l_buf_ptr--;
unsigned int prompt_len = 0;
if (ed->l_cursor_y == 0) {
prompt_len = prompt_length(ed, PROMPT_MAIN);
}
unsigned int len = line_length(ed, ed->l_cursor_y);
ed->l_cursor_x = len - 1;
printf("\033[A\033[%dG", len + prompt_len);
fflush(stdout);
}
void cursor_right(struct line_ed *ed)
{
if (ed->l_buf_ptr >= ed->l_line_end) {
return;
}
if (*ed->l_buf_ptr != '\n') {
ed->l_cursor_x++;
ed->l_buf_ptr++;
fputs("\033[C", stdout);
fflush(stdout);
return;
}
if (ed->l_buf_ptr >= ed->l_line_end) {
return;
}
ed->l_cursor_y++;
ed->l_cursor_x = 0;
ed->l_buf_ptr++;
printf("\033[B\033[G");
fflush(stdout);
}
void arrow_up(struct line_ed *ed)
{
if (ed->l_history_pos == 0) {
return;
}
if (ed->l_cursor_y > 0) {
printf("\033[%uA", ed->l_cursor_y);
}
printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1);
save_buf_to_history(ed);
ed->l_history_pos--;
load_buf_from_history(ed);
print_buffer(ed);
fflush(stdout);
}
void arrow_down(struct line_ed *ed)
{
if (ed->l_history_pos == b_array_size(ed->l_history) - 1) {
return;
}
if (ed->l_cursor_y > 0) {
printf("\033[%uA", ed->l_cursor_y);
}
printf("\033[%zuG\033[J", prompt_length(ed, PROMPT_MAIN) + 1);
save_buf_to_history(ed);
ed->l_history_pos++;
load_buf_from_history(ed);
print_buffer(ed);
fflush(stdout);
}

13
frontend/line-ed/input.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef LINE_ED_INPUT_H_
#define LINE_ED_INPUT_H_
struct line_ed;
extern void put_char(struct line_ed *ed, char c);
extern void backspace(struct line_ed *ed);
extern void cursor_left(struct line_ed *ed);
extern void cursor_right(struct line_ed *ed);
extern void arrow_up(struct line_ed *ed);
extern void arrow_down(struct line_ed *ed);
#endif

296
frontend/line-ed/line-ed.c Normal file
View File

@@ -0,0 +1,296 @@
#include "line-ed.h"
#include "history.h"
#include "hook.h"
#include "input.h"
#include "prompt.h"
#include "tty.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LINE_ED_FROM_LEX_SOURCE(p) \
((struct line_ed *)((char *)p - offsetof(struct line_ed, l_line_source)))
static enum ivy_status readline(
struct ivy_line_source *src, char *out, size_t max, size_t *read,
const char *scope_type)
{
struct line_ed *ed = LINE_ED_FROM_LEX_SOURCE(src);
line_ed_set_scope_type(ed, scope_type);
long r = line_ed_readline(ed, out, max);
line_ed_set_scope_type(ed, NULL);
if (r < 0) {
return IVY_ERR_EOF;
}
*read = r;
return IVY_OK;
}
struct line_ed *line_ed_create(void)
{
struct line_ed *out = malloc(sizeof *out);
if (!out) {
return NULL;
}
memset(out, 0x0, sizeof *out);
out->l_buf = malloc(LINE_MAX);
if (!out->l_buf) {
free(out);
return NULL;
}
out->l_history = b_array_create();
if (!out->l_history) {
free(out->l_buf);
free(out);
return NULL;
}
out->l_buf_end = out->l_buf + LINE_MAX;
out->l_buf_ptr = out->l_buf;
out->l_line_end = out->l_buf;
out->l_prompt[PROMPT_MAIN] = ">>> ";
out->l_prompt[PROMPT_CONT] = "> ";
out->l_line_source.s_readline = readline;
return out;
}
void line_ed_destroy(struct line_ed *ed)
{
b_array_release(ed->l_history);
line_ed_clear_highlights(ed);
free(ed->l_buf);
free(ed);
}
void line_ed_set_flags(struct line_ed *ed, enum line_ed_flags flags)
{
ed->l_flags |= flags;
}
void line_ed_set_scope_type(struct line_ed *ed, const char *scope_type)
{
ed->l_scope_type = scope_type;
}
static void clear_buffer(struct line_ed *ed)
{
memset(ed->l_buf, 0x0, ed->l_buf_end - ed->l_buf);
ed->l_buf_ptr = ed->l_buf;
ed->l_line_end = ed->l_buf;
ed->l_cursor_x = 0;
ed->l_cursor_y = 0;
ed->l_continuations = 0;
}
static void convert_continuation_feeds(char *s, size_t max)
{
char *end = s + max;
unsigned int len = strlen(s);
while (1) {
unsigned int r = strcspn(s, "\\");
if (s + r > end) {
break;
}
s += r;
len -= r;
if (*s == '\0') {
break;
}
if (*(s + 1) != '\n') {
s++;
continue;
}
memmove(s, s + 1, len);
s += 1;
}
}
static void remove_continuation_feeds(char *s, size_t max)
{
char *end = s + max;
unsigned int len = strlen(s);
while (1) {
unsigned int r = strcspn(s, "\\");
if (s + r > end) {
break;
}
s += r;
len -= r;
if (*s == '\0') {
break;
}
if (*(s + 1) != '\n') {
s++;
continue;
}
memmove(s, s + 2, len);
s += 2;
}
}
static bool input_is_empty(struct line_ed *ed)
{
const char *p = ed->l_buf;
while (p < ed->l_line_end) {
if (!isspace(*p)) {
return false;
}
}
return true;
}
long line_ed_readline(struct line_ed *ed, char *out, size_t max)
{
clear_buffer(ed);
bool append_history = false;
unsigned int append_to_index = (unsigned int)-1;
if (!ed->l_scope_type) {
alloc_empty_history_entry(ed);
} else {
append_history = true;
append_to_index = ed->l_history_pos;
}
struct s_tty *tty = s_get_tty();
s_tty_set_raw(tty);
show_prompt(ed);
for (int i = 0; ed->l_buf[i]; i++) {
if (ed->l_buf[i] == '\n') {
fputc('\r', stdout);
}
fputc(ed->l_buf[i], stdout);
}
fflush(stdout);
bool end = false;
bool eof = false;
while (!end) {
s_keycode key = s_tty_read_key(tty);
hook_keypress(ed, key);
if (key == S_TTY_CTRL_KEY('d')) {
if (!input_is_empty(ed)) {
continue;
}
eof = true;
break;
}
switch (key) {
case S_KEY_RETURN:
s_tty_reset_vmode(tty);
if (ed->l_line_end > ed->l_buf
&& *(ed->l_line_end - 1) != '\\') {
end = true;
break;
}
if (input_is_empty(ed)) {
fputc('\r', stdout);
fputc('\n', stdout);
clear_buffer(ed);
show_prompt(ed);
break;
}
*ed->l_line_end = '\n';
ed->l_line_end++;
ed->l_buf_ptr = ed->l_line_end;
ed->l_cursor_x = 0;
ed->l_cursor_y++;
ed->l_continuations++;
fputs("\033[G\n", stdout);
show_prompt(ed);
break;
case S_KEY_BACKSPACE:
backspace(ed);
break;
case S_KEY_ARROW_LEFT:
cursor_left(ed);
break;
case S_KEY_ARROW_RIGHT:
cursor_right(ed);
break;
case S_KEY_ARROW_UP:
arrow_up(ed);
break;
case S_KEY_ARROW_DOWN:
arrow_down(ed);
break;
default:
if (isgraph(key) || key == ' ') {
put_char(ed, key);
}
break;
}
}
s_tty_set_canon(tty);
printf("\n");
if (*ed->l_buf == '\0') {
return eof ? -1 : 0;
}
if (append_history) {
ed->l_history_pos = append_to_index;
append_buf_to_history(ed);
} else {
ed->l_history_pos = b_array_size(ed->l_history) - 1;
const char *last = last_history_line(ed);
if (!last || strcmp(last, ed->l_buf) != 0) {
save_buf_to_history(ed);
}
}
size_t sz = ed->l_line_end - ed->l_buf + 1;
sz = MIN(max - 1, sz);
memcpy(out, ed->l_buf, sz);
out[sz - 1] = '\n';
out[sz] = '\0';
if ((ed->l_flags & LINE_ED_REMOVE_CONTINUATIONS)
== LINE_ED_REMOVE_CONTINUATIONS) {
remove_continuation_feeds(out, max);
} else if (
(ed->l_flags & LINE_ED_CONVERT_CONTINUATIONS)
== LINE_ED_CONVERT_CONTINUATIONS) {
convert_continuation_feeds(out, max);
}
sz = strlen(out);
return sz;
}

View File

@@ -0,0 +1,96 @@
#ifndef LINE_ED_H_
#define LINE_ED_H_
#define LINE_MAX 4096
#include "tty.h"
#include <blue/core/queue.h>
#include <blue/object/array.h>
#include <ivy/line-source.h>
#include <stddef.h>
struct s_tty_vmode;
struct line_ed;
struct line_ed_hook {
void (*hook_keypress)(struct line_ed *, struct line_ed_hook *, s_keycode);
void (*hook_buffer_modified)(struct line_ed *, struct line_ed_hook *);
b_queue_entry hook_entry;
};
enum line_ed_flags {
/* always reprint an entire line when a character is added/deleted.
* without this flag, only the modified character any subsequent
* characters are reprinted. */
LINE_ED_FULL_REPRINT = 0x01u,
/* keep line continuation (backslash-newline) tokens in the output
* buffer (default behaviour) */
LINE_ED_KEEP_CONTINUATIONS = 0x00u,
/* convert line continuation tokens in the output buffer to simple
* linefeeds. */
LINE_ED_CONVERT_CONTINUATIONS = 0x02u,
/* remove line continuation tokens from the output buffer, so that all
* chars are on a single line */
LINE_ED_REMOVE_CONTINUATIONS = 0x06u,
};
struct line_ed {
enum line_ed_flags l_flags;
/* array of basic prompt strings */
const char *l_prompt[2];
/* input buffer, pointer to the buffer cell that corresponds to
* the current cursor position, and pointer to the byte AFTER the last
* usable byte in the buffer */
char *l_buf, *l_buf_ptr, *l_buf_end;
/* pointer to the byte AFTER the last byte of the user's input */
char *l_line_end;
/* 2-dimensional coordinates of the current cursor position.
* this does NOT include any prompts that are visible on the terminal */
unsigned int l_cursor_x, l_cursor_y;
/* the number of line continuations that have been inputted */
unsigned int l_continuations;
struct ivy_line_source l_line_source;
/* the lexical scope that we are currently in.
* this is provided by components further up the input pipeline,
* for example, when we are inside a string or if-statement. */
const char *l_scope_type;
/* array of previously entered commands */
b_array *l_history;
/* index of the currently selected history entry */
unsigned int l_history_pos;
/* list of defined highlight ranges */
b_queue l_hl_ranges;
/* list of installed hooks */
b_queue l_hooks;
};
extern struct line_ed *line_ed_create(void);
extern void line_ed_destroy(struct line_ed *ed);
extern void line_ed_set_flags(struct line_ed *ed, enum line_ed_flags flags);
extern void line_ed_set_scope_type(struct line_ed *ed, const char *scope_type);
extern void line_ed_put_highlight(
struct line_ed *ed, unsigned long start_x, unsigned long start_y,
unsigned long end_x, unsigned long end_y, const struct s_tty_vmode *vmode);
extern void line_ed_clear_highlights(struct line_ed *ed);
extern void line_ed_print_highlights(struct line_ed *ed);
extern void line_ed_add_hook(struct line_ed *ed, struct line_ed_hook *hook);
extern void line_ed_remove_hook(struct line_ed *ed, struct line_ed_hook *hook);
extern long line_ed_readline(struct line_ed *ed, char *out, size_t max);
static inline struct ivy_line_source *line_ed_to_line_source(struct line_ed *ed)
{
return &ed->l_line_source;
}
#endif

27
frontend/line-ed/prompt.c Normal file
View File

@@ -0,0 +1,27 @@
#include <stdio.h>
#include "line-ed.h"
#include "prompt.h"
void show_prompt(struct line_ed *ed)
{
int type = PROMPT_MAIN;
if (ed->l_scope_type) {
type = PROMPT_CONT;
/* this is a temporary solution to show the current
* scope type, until prompts are implemented properly. */
fputs(ed->l_scope_type, stdout);
}
if (ed->l_continuations > 0) {
type = PROMPT_CONT;
}
fputs(ed->l_prompt[type], stdout);
fflush(stdout);
}
unsigned long prompt_length(struct line_ed *ed, int prompt_id)
{
return strlen(ed->l_prompt[prompt_id]);
}

12
frontend/line-ed/prompt.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef LINE_ED_PROMPT_H_
#define LINE_ED_PROMPT_H_
#define PROMPT_MAIN 0
#define PROMPT_CONT 1
struct line_ed;
extern void show_prompt(struct line_ed *ed);
extern unsigned long prompt_length(struct line_ed *ed, int prompt_id);
#endif

242
frontend/line-ed/refresh.c Normal file
View File

@@ -0,0 +1,242 @@
#include <stdio.h>
#include <stdlib.h>
#include "line-ed.h"
#include "refresh.h"
#include "buffer.h"
#include "cursor.h"
#include "hl-range.h"
/* prints the provided string to the terminal, applying any relevant highlight ranges.
* this function prints all characters in `s` until it encounters a null char (\0) or
* linefeed (\n).
*
* the (x, y) coordinates provided should be the data coordinates of the
* first character in `s`.
*/
void print_text(struct line_ed *ed, unsigned int x, unsigned int y, const char *s)
{
struct s_tty *tty = s_get_tty();
struct hl_range *cur_range = get_hl_range(ed, x, y);
for (unsigned int i = 0; s[i] != '\n' && s[i] != '\0'; i++) {
if (!cur_range) {
s_tty_reset_vmode(tty);
fputc(s[i], stdout);
continue;
}
while (1) {
if (!cur_range) {
break;
}
int hl_result = apply_hl_range(cur_range, tty, x + i, y);
if (hl_result == 0) {
break;
}
cur_range = get_next_hl_range(cur_range);
apply_hl_range(cur_range, tty, x + i, y);
}
fputc(s[i], stdout);
}
}
void print_buffer(struct line_ed *ed)
{
const char *line_buf = ed->l_buf;
size_t line_len = strcspn(line_buf, "\n");
unsigned int y = 0;
while (1) {
print_text(ed,0, y, line_buf);
line_buf += line_len;
if (*line_buf == '\n') {
line_buf++;
}
if (*line_buf == '\0') {
break;
}
y++;
line_len = strcspn(line_buf, "\n");
fputc('\r', stdout);
fputc('\n', stdout);
}
}
/* this function is called after a character is inserted into the line_ed buffer.
* the function performs the following steps:
* 1. get a pointer to the start of the line that was modified.
* 2. determine the first character in the line that needs to be redrawn.
* this may result in an append, a partial reprint, or a full reprint.
* 3. re-print the relevant portion of the buffer:
* for an append (a character added to the end of the line):
* * write the inserted char to the terminal.
* * done.
* for a partial reprint:
* * clear all printed chars from the logical cursor position to the end of the line.
* * print the portion of the line corresponding to the cleared section.
* * move the physical (terminal) cursor backwards until its position
* matches the logical (line_ed) cursor.
* for a full reprint:
* * same as a partial reprint except that, rather than reprinting
* from the logical cursor position, the entire line is reprinted.
*/
void put_refresh(struct line_ed *ed, struct refresh_state *state)
{
/* get the data for the line that is being refreshed */
const char *line_buf = line_start(ed, ed->l_cursor_y);
size_t line_len = strcspn(line_buf, "\n");
/* the index of the first char in line_buf that needs to be reprinted */
unsigned int start_x = state->r_prev_cursor_x;
/* the distance between the first char to be reprinted and the end
* of the line.
* the physical cursor will be moved back by this amount after the
* line is reprinted. */
unsigned int cursor_rdelta = line_len - start_x;
if (ed->l_flags & LINE_ED_FULL_REPRINT) {
if (start_x) {
fprintf(stdout, "\033[%uD", start_x);
}
start_x = 0;
}
print_text(ed, start_x, ed->l_cursor_y, line_buf + start_x);
/* decrement the rdelta (move the cursor back one fewer cells),
* so that the physical cursor will be placed AFTER the character that
* was just inserted. */
cursor_rdelta--;
for (unsigned int i = 0; i < cursor_rdelta; i++) {
fputs("\010", stdout);
}
fflush(stdout);
}
/* this function is called after a character is removed from the line_ed buffer.
* IF the character was a linefeed.
*
* this is separate from backspace_simple_refresh because, in this situation,
* the cursor position depends on the length of the previous line before
* the linefeed was deleted, and we have to reprint every line following the
* two that were combined.
*/
void backspace_nl_refresh(struct line_ed *ed, struct refresh_state *state)
{
/* get the data for the line that is being refreshed.
* relative to where the cursor was before the linefeed was deleted,
* this is the PREVIOUS line. */
const char *line_buf = line_start(ed, ed->l_cursor_y);
size_t line_len = strcspn(line_buf, "\n");
/* the index of the first char in line_buf that needs to be reprinted.
* subtract one to account for the linefeed that has since been deleted. */
unsigned int start_x = state->r_prev_line_len - 1;
/* the column to move the physical cursor to after it has been moved
* to the previous line.
* NOTE that this number includes the length of the prompt!
* we add 1 to start_x to ensure that the cursor is moved to the cell
* AFTER the last char of the line. */
unsigned int new_x;
line_ed_coords_to_physical_coords(ed, start_x + 1, ed->l_cursor_y, &new_x, NULL);
/* the physical cursor is currently at the beginning of the line that
* has just been moved up. from here, clear this line and the rest
* from the screen. */
fputs("\033[J", stdout);
if (ed->l_flags & LINE_ED_FULL_REPRINT) {
/* next, move the physical cursor up and to the beginning of the previous line */
unsigned int tmp_x;
line_ed_coords_to_physical_coords(ed, 0, ed->l_cursor_y, &tmp_x, NULL);
fprintf(stdout, "\033[A\033[%uG", tmp_x + 1);
start_x = 0;
} else {
/* next, move the physical cursor up and to the end of the previous line */
fprintf(stdout, "\033[A\033[%uG", new_x);
}
/* now reprint all of the buffer lines, starting with the first of the
* two lines that were concatenated. */
unsigned int ydiff = 0;
while (1) {
print_text(ed, start_x, ed->l_cursor_y + ydiff, line_buf + start_x);
line_buf += line_len + 1;
line_len = strcspn(line_buf, "\n");
start_x = 0;
if (*line_buf == '\0') {
break;
}
fputc('\r', stdout);
fputc('\n', stdout);
ydiff++;
}
/* finally, move the cursor BACK to the point where the two lines
* were concatenated. */
if (ydiff) {
fprintf(stdout, "\033[%uA", ydiff);
}
fprintf(stdout, "\033[%uG", new_x);
fflush(stdout);
}
/* this function is called after a character is removed from the line_ed buffer.
* IF the character was not a linefeed.
*/
void backspace_simple_refresh(struct line_ed *ed, struct refresh_state *state)
{
/* get the data for the line that is being refreshed */
const char *line_buf = line_start(ed, ed->l_cursor_y);
size_t line_len = strcspn(line_buf, "\n");
/* the index of the first char in line_buf that needs to be reprinted */
unsigned int start_x = ed->l_cursor_x;
//get_data_cursor_position(ed, &start_x, NULL);
/* the distance between the first char to be reprinted and the end
* of the line.
* the physical cursor will be moved back by this amount after the
* line is reprinted. */
unsigned int cursor_rdelta = line_len - start_x;
if (ed->l_flags & LINE_ED_FULL_REPRINT) {
if (start_x) {
fprintf(stdout, "\033[%uD", start_x);
}
start_x = 0;
}
fputc('\010', stdout);
print_text(ed, start_x, ed->l_cursor_y, line_buf + start_x);
fputc(' ', stdout);
/* increment the rdelta (move the cursor back one more cell), so
* that the cursor will appear to move back one cell (to accord with
* the fact that backspace was just pressed) */
cursor_rdelta++;
for (unsigned int i = 0; i < cursor_rdelta; i++) {
fputs("\010", stdout);
}
fflush(stdout);
}

View File

@@ -0,0 +1,21 @@
#ifndef LINE_ED_REFRESH_H_
#define LINE_ED_REFRESH_H_
struct line_ed;
struct refresh_state {
/* cursor position before the update was performed (excluding the prompt) */
unsigned int r_prev_cursor_x, r_prev_cursor_y;
/* when a backspace results in two separate lines being combined,
* this property contains the length of the first of the two combined
* lines BEFORE the concotenation was performed */
unsigned int r_prev_line_len;
};
extern void print_text(struct line_ed *ed, unsigned int x, unsigned int y, const char *s);
extern void print_buffer(struct line_ed *ed);
extern void put_refresh(struct line_ed *ed, struct refresh_state *state);
extern void backspace_nl_refresh(struct line_ed *ed, struct refresh_state *state);
extern void backspace_simple_refresh(struct line_ed *ed, struct refresh_state *state);
#endif

384
frontend/line-ed/tty.c Normal file
View File

@@ -0,0 +1,384 @@
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include "tty.h"
#define ANSI_BOLD_ON "1"
#define ANSI_BOLD_OFF "22"
#define ANSI_ITALIC_ON "3"
#define ANSI_ITALIC_OFF "23"
#define ANSI_UNDERLINE_ON "4"
#define ANSI_UNDERLINE_OFF "24"
#define ANSI_DEFAULTCOLOUR_FG "39"
#define ANSI_DEFAULTCOLOUR_BG "49"
#define ANSI_256COLOUR_FG "38;5"
#define ANSI_256COLOUR_BG "48;5"
#define ANSI_TRUECOLOUR_FG "38;2"
#define ANSI_TRUECOLOUR_BG "48;2"
enum tty_flags {
TTY_INIT = 0x01u,
TTY_INTERACTIVE = 0x02u,
};
struct s_tty {
FILE *t_in, *t_out;
enum tty_flags t_flags;
struct termios t_ios_raw, t_ios_default;
struct s_tty_vmode t_vmode;
unsigned char t_mcount;
};
static struct s_tty tty = {};
static const char *ansi_colour16_fg[] = {
[TTY_COLOUR16_BLACK] = "30",
[TTY_COLOUR16_RED] = "31",
[TTY_COLOUR16_GREEN] = "32",
[TTY_COLOUR16_YELLOW] = "33",
[TTY_COLOUR16_BLUE] = "34",
[TTY_COLOUR16_MAGENTA] = "35",
[TTY_COLOUR16_CYAN] = "36",
[TTY_COLOUR16_WHITE] = "37",
[TTY_COLOUR16_GREY] = "90",
[TTY_COLOUR16_BRIGHT_RED] = "91",
[TTY_COLOUR16_BRIGHT_GREEN] = "92",
[TTY_COLOUR16_BRIGHT_YELLOW] = "93",
[TTY_COLOUR16_BRIGHT_BLUE] = "94",
[TTY_COLOUR16_BRIGHT_MAGENTA] = "95",
[TTY_COLOUR16_BRIGHT_CYAN] = "96",
[TTY_COLOUR16_BRIGHT_WHITE] = "97",
};
static const char *ansi_colour16_bg[] = {
[TTY_COLOUR16_BLACK] = "40",
[TTY_COLOUR16_RED] = "41",
[TTY_COLOUR16_GREEN] = "42",
[TTY_COLOUR16_YELLOW] = "43",
[TTY_COLOUR16_BLUE] = "44",
[TTY_COLOUR16_MAGENTA] = "45",
[TTY_COLOUR16_CYAN] = "46",
[TTY_COLOUR16_WHITE] = "47",
[TTY_COLOUR16_GREY] = "100",
[TTY_COLOUR16_BRIGHT_RED] = "101",
[TTY_COLOUR16_BRIGHT_GREEN] = "102",
[TTY_COLOUR16_BRIGHT_YELLOW] = "103",
[TTY_COLOUR16_BRIGHT_BLUE] = "104",
[TTY_COLOUR16_BRIGHT_MAGENTA] = "105",
[TTY_COLOUR16_BRIGHT_CYAN] = "106",
[TTY_COLOUR16_BRIGHT_WHITE] = "107",
};
static void init_tty(struct s_tty *tty, FILE *in, FILE *out)
{
tty->t_in = in;
tty->t_out = out;
int fd = fileno(in);
if (isatty(fd)) {
tty->t_flags |= TTY_INTERACTIVE;
}
tcgetattr(fd, &tty->t_ios_default);
memcpy(&tty->t_ios_raw, &tty->t_ios_default, sizeof tty->t_ios_raw);
tty->t_ios_raw.c_iflag &= ~(ICRNL | IXON);
tty->t_ios_raw.c_oflag &= ~(OPOST);
tty->t_ios_raw.c_lflag &= ~(ECHO | ICANON | IEXTEN);
tty->t_flags |= TTY_INIT;
}
struct s_tty *s_get_tty(void)
{
if (!(tty.t_flags & TTY_INIT)) {
init_tty(&tty, stdin, stdout);
}
return &tty;
}
bool s_tty_is_interactive(const struct s_tty *tty)
{
return (tty->t_flags & TTY_INTERACTIVE) == TTY_INTERACTIVE;
}
void s_tty_set_raw(struct s_tty *tty)
{
tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty->t_ios_raw);
}
void s_tty_set_canon(struct s_tty *tty)
{
tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty->t_ios_default);
}
void s_tty_reset_vmode(struct s_tty *tty)
{
if (tty->t_vmode.v_fg.c_mode == TTY_COLOUR_NONE
&& tty->t_vmode.v_bg.c_mode == TTY_COLOUR_NONE
&& tty->t_vmode.v_attrib == TTY_ATTRIB_NORMAL) {
return;
}
fprintf(tty->t_out, "\033[0;39;49m");
memset(&tty->t_vmode, 0x0, sizeof tty->t_vmode);
tty->t_vmode.v_fg.c_mode = TTY_COLOUR_NONE;
tty->t_vmode.v_bg.c_mode = TTY_COLOUR_NONE;
tty->t_vmode.v_attrib = TTY_ATTRIB_NORMAL;
}
static int compare_colour(const struct s_tty_colour *a, const struct s_tty_colour *b)
{
if (a->c_mode != b->c_mode) {
return -1;
}
switch (a->c_mode) {
case TTY_COLOUR_16:
if (a->c_16.value != b->c_16.value) {
return -1;
}
break;
case TTY_COLOUR_256:
if (a->c_256.value != b->c_256.value) {
return -1;
}
break;
case TTY_COLOUR_TRUE:
if (a->c_true.r != b->c_true.r) {
return -1;
}
if (a->c_true.g != b->c_true.g) {
return -1;
}
if (a->c_true.b != b->c_true.b) {
return -1;
}
break;
default:
break;
}
return 0;
}
static int compare_vmode(const struct s_tty_vmode *a, const struct s_tty_vmode *b)
{
if (a->v_attrib != b->v_attrib) {
return -1;
}
if (compare_colour(&a->v_fg, &b->v_fg) != 0) {
return -1;
}
if (compare_colour(&a->v_bg, &b->v_bg) != 0) {
return -1;
}
return 0;
}
static void put_ansi_attrib(struct s_tty *tty, const char *s)
{
if (tty->t_mcount > 0) {
fputs(";", tty->t_out);
}
fputs(s, tty->t_out);
tty->t_mcount++;
}
static void set_attrib(struct s_tty *tty, enum s_tty_attrib old, enum s_tty_attrib new)
{
if (old == new) {
return;
}
/* Bold ON */
if (!(old & TTY_ATTRIB_BOLD) && new & TTY_ATTRIB_BOLD) {
put_ansi_attrib(tty, ANSI_BOLD_ON);
}
/* Bold OFF */
if (old & TTY_ATTRIB_BOLD && !(new & TTY_ATTRIB_BOLD)) {
put_ansi_attrib(tty, ANSI_BOLD_OFF);
}
/* Underline ON */
if (!(old & TTY_ATTRIB_UNDERLINE) && new & TTY_ATTRIB_UNDERLINE) {
put_ansi_attrib(tty, ANSI_UNDERLINE_ON);
}
/* Underline OFF */
if (old & TTY_ATTRIB_UNDERLINE && !(new & TTY_ATTRIB_UNDERLINE)) {
put_ansi_attrib(tty, ANSI_UNDERLINE_OFF);
}
/* Italic ON */
if (!(old & TTY_ATTRIB_ITALIC) && new & TTY_ATTRIB_ITALIC) {
put_ansi_attrib(tty, ANSI_ITALIC_ON);
}
/* Italic OFF */
if (old & TTY_ATTRIB_ITALIC && !(new & TTY_ATTRIB_ITALIC)) {
put_ansi_attrib(tty, ANSI_ITALIC_OFF);
}
}
static void set_fg(struct s_tty *tty, const struct s_tty_colour *old, const struct s_tty_colour *new)
{
if (compare_colour(old, new) == 0) {
return;
}
char buf[8];
switch (new->c_mode) {
case TTY_COLOUR_NONE:
put_ansi_attrib(tty, ANSI_DEFAULTCOLOUR_FG);
break;
case TTY_COLOUR_16:
put_ansi_attrib(tty, ansi_colour16_fg[new->c_16.value]);
break;
case TTY_COLOUR_256:
put_ansi_attrib(tty, ANSI_256COLOUR_FG);
snprintf(buf, sizeof buf, "%u", new->c_256.value);
put_ansi_attrib(tty, buf);
break;
case TTY_COLOUR_TRUE:
put_ansi_attrib(tty, ANSI_TRUECOLOUR_FG);
snprintf(buf, sizeof buf, "%u", new->c_true.r);
put_ansi_attrib(tty, buf);
snprintf(buf, sizeof buf, "%u", new->c_true.g);
put_ansi_attrib(tty, buf);
snprintf(buf, sizeof buf, "%u", new->c_true.b);
put_ansi_attrib(tty, buf);
break;
default:
break;
}
}
static void set_bg(struct s_tty *tty, const struct s_tty_colour *old, const struct s_tty_colour *new)
{
if (compare_colour(old, new) == 0) {
return;
}
char buf[8];
switch (new->c_mode) {
case TTY_COLOUR_NONE:
put_ansi_attrib(tty, ANSI_DEFAULTCOLOUR_BG);
break;
case TTY_COLOUR_16:
put_ansi_attrib(tty, ansi_colour16_bg[new->c_16.value]);
break;
case TTY_COLOUR_256:
put_ansi_attrib(tty, ANSI_256COLOUR_BG);
snprintf(buf, sizeof buf, "%u", new->c_256.value);
put_ansi_attrib(tty, buf);
break;
case TTY_COLOUR_TRUE:
put_ansi_attrib(tty, ANSI_TRUECOLOUR_BG);
snprintf(buf, sizeof buf, "%u", new->c_true.r);
put_ansi_attrib(tty, buf);
snprintf(buf, sizeof buf, "%u", new->c_true.g);
put_ansi_attrib(tty, buf);
snprintf(buf, sizeof buf, "%u", new->c_true.b);
put_ansi_attrib(tty, buf);
break;
default:
break;
}
}
void s_tty_set_vmode(struct s_tty *tty, const struct s_tty_vmode *vmode)
{
if (compare_vmode(&tty->t_vmode, vmode) == 0) {
return;
}
tty->t_mcount = 0;
fprintf(tty->t_out, "\033[");
set_attrib(tty, tty->t_vmode.v_attrib, vmode->v_attrib);
set_fg(tty, &tty->t_vmode.v_fg, &vmode->v_fg);
set_bg(tty, &tty->t_vmode.v_bg, &vmode->v_bg);
fprintf(tty->t_out, "m");
memcpy(&tty->t_vmode, vmode, sizeof *vmode);
}
s_keycode s_tty_read_key(struct s_tty *tty)
{
char c;
int v;
int fd = fileno(tty->t_in);
while (1) {
v = read(fd, &c, 1);
if (v < 1) {
return S_KEY_EOF;
}
if (c == '\r' || c == '\n') {
return S_KEY_RETURN;
}
if (c == '\b' || c == 0x7F) {
return S_KEY_BACKSPACE;
}
if (c >= 1 && c <= 26) {
return S_TTY_CTRL_KEY(c + 'a' - 1);
}
if (c != 0x1b) {
return c;
}
v = read(fd, &c, 1);
if (v < 1) {
return S_KEY_EOF;
}
if (c != '[') {
continue;
}
v = read(fd, &c, 1);
if (v < 1) {
return S_KEY_EOF;
}
switch (c) {
case 'A':
return S_KEY_ARROW_UP;
case 'B':
return S_KEY_ARROW_DOWN;
case 'C':
return S_KEY_ARROW_RIGHT;
case 'D':
return S_KEY_ARROW_LEFT;
default:
continue;
}
}
return c;
}

140
frontend/line-ed/tty.h Normal file
View File

@@ -0,0 +1,140 @@
#ifndef DARWIN_TTY_H_
#define DARWIN_TTY_H_
#include <stdbool.h>
#include <stdint.h>
#define S_TTY_CTRL_KEY(c) ((c) | S_MOD_CTRL)
struct s_tty;
#define MAKE_VMODE(fg, bg, a) \
{ \
.v_fg = fg, \
.v_bg = bg, \
.v_attrib = (a) \
}
#define MAKE_COLOUR_DEFAULT(v) \
{ \
.c_mode = TTY_COLOUR_NONE, \
}
#define MAKE_COLOUR_16(v) \
{ \
.c_mode = TTY_COLOUR_16, \
.c_16 = { \
.value = (v) \
} \
}
#define MAKE_COLOUR_256(v) \
{ \
.c_mode = TTY_COLOUR_256, \
.c_256 = { \
.value = (v) \
} \
}
#define MAKE_COLOUR_TRUE(cr, cg, cb) \
{ \
.c_mode = TTY_COLOUR_TRUE, \
.c_true = { \
.r = (cr), \
.g = (cg), \
.b = (cb) \
} \
}
typedef uint32_t s_keycode;
/* codepoints U+F0000 to U+FFFFD are reserved areas in Unicode, free for
* application use. store special keycodes here */
enum s_keys {
S_KEY_ARROW_LEFT = 0xF0000,
S_KEY_ARROW_RIGHT,
S_KEY_ARROW_UP,
S_KEY_ARROW_DOWN,
S_KEY_BACKSPACE,
S_KEY_RETURN,
S_MOD_CTRL = 0x10000000,
S_KEY_EOF = 0xFFFFFFFF,
};
enum s_tty_colour16 {
TTY_COLOUR16_BLACK = 0,
TTY_COLOUR16_RED,
TTY_COLOUR16_GREEN,
TTY_COLOUR16_YELLOW,
TTY_COLOUR16_BLUE,
TTY_COLOUR16_MAGENTA,
TTY_COLOUR16_CYAN,
TTY_COLOUR16_WHITE,
TTY_COLOUR16_GREY,
TTY_COLOUR16_BRIGHT_RED,
TTY_COLOUR16_BRIGHT_GREEN,
TTY_COLOUR16_BRIGHT_YELLOW,
TTY_COLOUR16_BRIGHT_BLUE,
TTY_COLOUR16_BRIGHT_MAGENTA,
TTY_COLOUR16_BRIGHT_CYAN,
TTY_COLOUR16_BRIGHT_WHITE,
};
enum s_tty_colour_mode {
TTY_COLOUR_NONE = 0,
TTY_COLOUR_16,
TTY_COLOUR_256,
TTY_COLOUR_TRUE,
};
enum s_tty_attrib {
TTY_ATTRIB_NORMAL = 0x00u,
TTY_ATTRIB_BOLD = 0x01u,
TTY_ATTRIB_UNDERLINE = 0x02u,
TTY_ATTRIB_ITALIC = 0x04u,
};
struct s_tty_colour {
enum s_tty_colour_mode c_mode;
union {
struct {
uint8_t value;
} c_16;
struct {
uint8_t value;
} c_256;
struct {
uint8_t r;
uint8_t g;
uint8_t b;
} c_true;
};
};
struct s_tty_vmode {
enum s_tty_attrib v_attrib;
struct s_tty_colour v_fg;
struct s_tty_colour v_bg;
};
static inline unsigned int s_keycode_get_key(s_keycode v)
{
return v & 0x0FFFFFFF;
}
extern struct s_tty *s_get_tty(void);
extern bool s_tty_is_interactive(const struct s_tty *tty);
extern void s_tty_set_raw(struct s_tty *tty);
extern void s_tty_set_canon(struct s_tty *tty);
extern void s_tty_reset_vmode(struct s_tty *tty);
extern void s_tty_set_vmode(struct s_tty *tty, const struct s_tty_vmode *vmode);
extern s_keycode s_tty_read_key(struct s_tty *tty);
#endif