#include "line-ed.h" #include "history.h" #include "hook.h" #include "input.h" #include "prompt.h" #include #include #include #include #include #include #include #include #include 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; }