Files
ivy/frontend/line-ed/line-ed.c

297 lines
5.1 KiB
C

#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;
}