frontend: add a line editor for shell input
This commit is contained in:
40
frontend/line-ed/buffer.c
Normal file
40
frontend/line-ed/buffer.c
Normal 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
14
frontend/line-ed/buffer.h
Normal 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
27
frontend/line-ed/cursor.c
Normal 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
10
frontend/line-ed/cursor.h
Normal 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
|
||||
67
frontend/line-ed/history.c
Normal file
67
frontend/line-ed/history.c
Normal 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);
|
||||
}
|
||||
12
frontend/line-ed/history.h
Normal file
12
frontend/line-ed/history.h
Normal 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
287
frontend/line-ed/hl-range.c
Normal 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);
|
||||
}
|
||||
}
|
||||
41
frontend/line-ed/hl-range.h
Normal file
41
frontend/line-ed/hl-range.h
Normal 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
37
frontend/line-ed/hook.c
Normal 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
16
frontend/line-ed/hook.h
Normal 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
|
||||
131
frontend/line-ed/hooks/highlight.c
Normal file
131
frontend/line-ed/hooks/highlight.c
Normal 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
|
||||
}
|
||||
20
frontend/line-ed/hooks/highlight.h
Normal file
20
frontend/line-ed/hooks/highlight.h
Normal 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
204
frontend/line-ed/input.c
Normal 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
13
frontend/line-ed/input.h
Normal 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
296
frontend/line-ed/line-ed.c
Normal 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;
|
||||
}
|
||||
96
frontend/line-ed/line-ed.h
Normal file
96
frontend/line-ed/line-ed.h
Normal 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
27
frontend/line-ed/prompt.c
Normal 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
12
frontend/line-ed/prompt.h
Normal 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
242
frontend/line-ed/refresh.c
Normal 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);
|
||||
}
|
||||
21
frontend/line-ed/refresh.h
Normal file
21
frontend/line-ed/refresh.h
Normal 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
384
frontend/line-ed/tty.c
Normal 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
140
frontend/line-ed/tty.h
Normal 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
|
||||
Reference in New Issue
Block a user