349 lines
6.2 KiB
C
349 lines
6.2 KiB
C
#include "line-ed.h"
|
|
|
|
#include "history.h"
|
|
#include "hook.h"
|
|
#include "input.h"
|
|
#include "prompt.h"
|
|
|
|
#include <ctype.h>
|
|
#include <fx/stream.h>
|
|
#include <fx/term/tty.h>
|
|
#include <fx/wstr.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <wchar.h>
|
|
#include <wctype.h>
|
|
|
|
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 = fx_array_create();
|
|
if (!out->l_history) {
|
|
free(out->l_buf);
|
|
free(out);
|
|
return NULL;
|
|
}
|
|
|
|
pthread_mutex_init(&out->l_lock, NULL);
|
|
out->l_tty = fx_stdtty;
|
|
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] = "> ";
|
|
|
|
return out;
|
|
}
|
|
|
|
void line_ed_destroy(struct line_ed *ed)
|
|
{
|
|
fx_array_unref(ed->l_history);
|
|
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(fx_wchar *s, size_t max)
|
|
{
|
|
fx_wchar *end = s + max;
|
|
size_t len = fx_wstrlen(s);
|
|
|
|
while (1) {
|
|
size_t r = fx_wstrcspn(s, L"\\");
|
|
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(fx_wchar *s, size_t max)
|
|
{
|
|
fx_wchar *end = s + max;
|
|
size_t len = fx_wstrlen(s);
|
|
|
|
while (1) {
|
|
size_t r = fx_wstrcspn(s, L"\\");
|
|
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 fx_wchar *p = ed->l_buf;
|
|
while (p < ed->l_line_end) {
|
|
if (!fx_wchar_is_space(*p)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void check_suspended(struct line_ed *ed)
|
|
{
|
|
pthread_mutex_lock(&ed->l_lock);
|
|
while (1) {
|
|
if (!ed->l_suspended) {
|
|
break;
|
|
}
|
|
pthread_cond_wait(&ed->l_suspended_cond, &ed->l_lock);
|
|
}
|
|
|
|
pthread_mutex_unlock(&ed->l_lock);
|
|
}
|
|
|
|
size_t line_ed_readline(struct line_ed *ed, fx_stringstream *out)
|
|
{
|
|
clear_buffer(ed);
|
|
|
|
bool append_history = false;
|
|
size_t append_to_index = (size_t)-1;
|
|
if (!ed->l_scope_type) {
|
|
alloc_empty_history_entry(ed);
|
|
} else {
|
|
append_history = true;
|
|
append_to_index = ed->l_history_pos;
|
|
}
|
|
|
|
fx_tty *tty = ed->l_tty;
|
|
fx_tty_set_mode(tty, FX_TTY_RAW);
|
|
show_prompt(ed);
|
|
|
|
for (int i = 0; ed->l_buf[i]; i++) {
|
|
if (ed->l_buf[i] == '\n') {
|
|
fputc('\r', stdout);
|
|
}
|
|
|
|
fx_stream_write_char(fx_stdout, ed->l_buf[i]);
|
|
}
|
|
fflush(stdout);
|
|
|
|
bool end = false;
|
|
bool eof = false;
|
|
|
|
while (!end) {
|
|
check_suspended(ed);
|
|
|
|
fx_keycode key = fx_tty_read_key(tty);
|
|
hook_keypress(ed, key);
|
|
|
|
if (ed->l_suspended) {
|
|
continue;
|
|
}
|
|
|
|
if (key == FX_TTY_CTRL_KEY('d')) {
|
|
if (!input_is_empty(ed)) {
|
|
continue;
|
|
}
|
|
|
|
eof = true;
|
|
break;
|
|
}
|
|
|
|
if (key & FX_MOD_CTRL) {
|
|
continue;
|
|
}
|
|
|
|
switch (key) {
|
|
case FX_KEY_RETURN:
|
|
fx_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++;
|
|
fputc('\r', stdout);
|
|
fputc('\n', stdout);
|
|
// fputs("\033[G\n", stdout);
|
|
show_prompt(ed);
|
|
break;
|
|
case FX_KEY_BACKSPACE:
|
|
backspace(ed);
|
|
break;
|
|
case FX_KEY_ARROW_LEFT:
|
|
cursor_left(ed);
|
|
break;
|
|
case FX_KEY_ARROW_RIGHT:
|
|
cursor_right(ed);
|
|
break;
|
|
case FX_KEY_ARROW_UP:
|
|
arrow_up(ed);
|
|
break;
|
|
case FX_KEY_ARROW_DOWN:
|
|
arrow_down(ed);
|
|
break;
|
|
default:
|
|
if (fx_wchar_is_graph(key) || key == ' ') {
|
|
put_char(ed, key);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
fx_tty_set_mode(tty, FX_TTY_CANONICAL);
|
|
fputc('\n', stdout);
|
|
|
|
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 = fx_array_get_size(ed->l_history) - 1;
|
|
|
|
const char *last = last_history_line(ed);
|
|
if (!last || fx_wastrcmp(last, ed->l_buf) != 0) {
|
|
save_buf_to_history(ed);
|
|
}
|
|
}
|
|
|
|
size_t sz = ed->l_line_end - ed->l_buf + 1;
|
|
|
|
if ((ed->l_flags & LINE_ED_REMOVE_CONTINUATIONS)
|
|
== LINE_ED_REMOVE_CONTINUATIONS) {
|
|
remove_continuation_feeds(ed->l_buf, sz);
|
|
} else if (
|
|
(ed->l_flags & LINE_ED_CONVERT_CONTINUATIONS)
|
|
== LINE_ED_CONVERT_CONTINUATIONS) {
|
|
convert_continuation_feeds(ed->l_buf, sz);
|
|
}
|
|
|
|
fx_stream_write_wstr(out, ed->l_buf, NULL);
|
|
|
|
if (!(ed->l_flags & LINE_ED_NO_TRAILING_LINEFEED)) {
|
|
fx_stream_write_char(out, '\n');
|
|
}
|
|
|
|
return sz;
|
|
}
|
|
|
|
int line_ed_suspend(struct line_ed *ed)
|
|
{
|
|
pthread_mutex_lock(&ed->l_lock);
|
|
ed->l_suspended = true;
|
|
pthread_cond_signal(&ed->l_suspended_cond);
|
|
fx_tty_clear(ed->l_tty, FX_TTY_CLEAR_LINE | FX_TTY_CLEAR_ALL);
|
|
fx_tty_set_mode(ed->l_tty, FX_TTY_CANONICAL);
|
|
fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, 0);
|
|
pthread_mutex_unlock(&ed->l_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int line_ed_resume(struct line_ed *ed)
|
|
{
|
|
pthread_mutex_lock(&ed->l_lock);
|
|
fx_tty_set_mode(ed->l_tty, FX_TTY_RAW);
|
|
fx_tty_clear(ed->l_tty, FX_TTY_CLEAR_LINE | FX_TTY_CLEAR_ALL);
|
|
show_prompt(ed);
|
|
|
|
size_t prompt_len = 0;
|
|
if (ed->l_cursor_y == 0) {
|
|
prompt_len = prompt_length(ed, PROMPT_MAIN);
|
|
}
|
|
|
|
size_t x = 0;
|
|
for (int i = 0; ed->l_buf[i]; i++) {
|
|
if (ed->l_buf[i] == '\n') {
|
|
fputc('\r', stdout);
|
|
}
|
|
|
|
fx_stream_write_char(fx_stdout, ed->l_buf[i]);
|
|
x++;
|
|
}
|
|
|
|
fflush(stdout);
|
|
fx_tty_move_cursor_x(ed->l_tty, FX_TTY_POS_START, prompt_len + x);
|
|
ed->l_suspended = false;
|
|
pthread_cond_signal(&ed->l_suspended_cond);
|
|
pthread_mutex_unlock(&ed->l_lock);
|
|
|
|
return 0;
|
|
}
|