#include "line-ed.h" #include "history.h" #include "hook.h" #include "input.h" #include "prompt.h" #include #include #include #include #include #include #include #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_tty = b_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] = "> "; out->l_line_source.s_readline = readline; return out; } void line_ed_destroy(struct line_ed *ed) { b_array_unref(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; size_t len = strlen(s); while (1) { size_t 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; size_t len = strlen(s); while (1) { size_t 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; } size_t line_ed_readline(struct line_ed *ed, char *out, size_t max) { 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; } b_tty *tty = ed->l_tty; b_tty_set_mode(tty, B_TTY_RAW); 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) { b_keycode key = b_tty_read_key(tty); hook_keypress(ed, key); if (key == B_TTY_CTRL_KEY('d')) { if (!input_is_empty(ed)) { continue; } eof = true; break; } if (key & B_MOD_CTRL) { continue; } switch (key) { case B_KEY_RETURN: b_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 B_KEY_BACKSPACE: backspace(ed); break; case B_KEY_ARROW_LEFT: cursor_left(ed); break; case B_KEY_ARROW_RIGHT: cursor_right(ed); break; case B_KEY_ARROW_UP: arrow_up(ed); break; case B_KEY_ARROW_DOWN: arrow_down(ed); break; default: if (iswgraph(key) || key == ' ') { put_char(ed, key); } break; } } b_tty_set_mode(tty, B_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 = 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; }