#include "edit.h" #include "arena_alloc.h" #include "gapbuffer.h" #include "mprintf.h" #include "self.h" #include "walloc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LINE_NUMBERS_STYLE ANSIQ_GR_SEQ (ANSIQ_BG_RGB (17, 115, 176) ANSIQ_FG_WHITE) #define STATUS_LINE_STYLE ANSIQ_GR_SEQ (ANSIQ_BG_RGB (240, 191, 88) ANSIQ_FG_BLACK) struct edit_line { struct list_node_link lines_link; struct gapbuffer gb; }; struct cursor { size_t col; size_t line; }; struct editor { struct list_node_link* lines; struct edit_line* current_line; struct cursor cursor; size_t col_offset; size_t row_offset; size_t total_lines; }; static struct editor editor; static bool prepare_lines_cb (void* ctx, const char* start, size_t len) { struct edit_line* line = malloc (sizeof (*line)); if (line == NULL) return false; memset (line, 0, sizeof (*line)); gapbuffer_init (&wmalloc, NULL, &line->gb, len); for (size_t chr = 0; chr < len; chr++) gapbuffer_insert (&wrealloc, NULL, &line->gb, start[chr]); list_append (editor.lines, &line->lines_link); if (editor.current_line == NULL) editor.current_line = line; return true; } static void prepare_lines (const char* text) { strtokenize (text, '\n', NULL, &prepare_lines_cb); } static void update_horz_scroll (size_t screen_cols) { if (editor.cursor.col < editor.col_offset) editor.col_offset = editor.cursor.col; if (editor.cursor.col >= editor.col_offset + screen_cols) editor.col_offset = editor.cursor.col - screen_cols + 1; } static void update_vert_scroll (size_t screen_rows) { if (editor.cursor.line < editor.row_offset) editor.row_offset = editor.cursor.line; if (editor.cursor.line >= editor.row_offset + (screen_rows - 1)) editor.row_offset = editor.cursor.line - (screen_rows - 1) + 1; } static size_t count_digits (size_t n) { if (n == 0) return 1; size_t count = 0; while (n > 0) { n /= 10; count++; } return count; } void edit_start (const char* file_path, const char* text) { prepare_lines (text); struct arena temp_arena; memset (&temp_arena, 0, sizeof (temp_arena)); size_t cols, rows; terminal_dimensions (&cols, &rows); mprintf (ANSIQ_SCR_CLR_ALL ANSIQ_CUR_INVISIBLE); bool edit_run = true; while (edit_run) { update_horz_scroll (cols); update_vert_scroll (rows); int w; const size_t backbuffer_max = 128 * 1024; size_t bb_remaining = backbuffer_max; char* backbuffer = arena_malloc (&temp_arena, backbuffer_max); memset (backbuffer, 0, backbuffer_max); char* bbptr = backbuffer; w = snprintf (bbptr, bb_remaining, ANSIQ_CUR_HOME); bbptr += w; bb_remaining -= w; size_t lines_drawn = 0; size_t current_idx = 0; struct list_node_link *line_link, *tmp_line_link; list_foreach (editor.lines, line_link, tmp_line_link) editor.total_lines++; size_t gutter_width = count_digits (editor.total_lines); size_t effective_cols = (cols > gutter_width + 1) ? cols - (gutter_width + 1) : cols; list_foreach (editor.lines, line_link, tmp_line_link) { if (current_idx < editor.row_offset) { current_idx++; continue; } if (lines_drawn >= rows - 1) break; struct edit_line* line = list_entry (line_link, struct edit_line, lines_link); w = snprintf (bbptr, bb_remaining, ANSIQ_SCR_CLR_LINE); bbptr += w; bb_remaining -= w; w = snprintf (bbptr, bb_remaining, LINE_NUMBERS_STYLE "%*zu " ANSIQ_GR_RESET, (int)gutter_width, current_idx + 1); bbptr += w; bb_remaining -= w; size_t part1_len = line->gb.gap_start; size_t part2_len = line->gb.size - line->gb.gap_end; size_t current_col = 0; if (part1_len > editor.col_offset) { size_t start = editor.col_offset; size_t len = part1_len - start; if (len > effective_cols) len = effective_cols; w = snprintf (bbptr, bb_remaining, "%.*s", (int)len, &line->gb.buffer[start]); bbptr += w; bb_remaining -= w; current_col += len; } if (current_col < effective_cols && part2_len > 0) { size_t skip = 0; if (editor.col_offset > part1_len) skip = editor.col_offset - part1_len; if (skip < part2_len) { size_t len = part2_len - skip; if (current_col + len > effective_cols) len = effective_cols - current_col; w = snprintf (bbptr, bb_remaining, "%.*s", (int)len, &line->gb.buffer[line->gb.gap_end + skip]); bbptr += w; bb_remaining -= w; } } w = snprintf (bbptr, bb_remaining, ANSIQ_SCR_CLR2END "\n"); bbptr += w; bb_remaining -= w; lines_drawn++; current_idx++; } w = snprintf (bbptr, bb_remaining, ANSIQ_DYN_CUR_SET ANSIQ_SCR_CLR_LINE, (int)rows, 1); bbptr += w; bb_remaining -= w; w = snprintf (bbptr, bb_remaining, STATUS_LINE_STYLE "%*s" ANSIQ_DYN_CUR_SET, (int)cols, "", (int)rows, 1); bbptr += w; bb_remaining -= w; w = snprintf (bbptr, bb_remaining, "Editing %s; line %zu col %zu" ANSIQ_GR_RESET, file_path, editor.cursor.line + 1, editor.cursor.col + 1); bbptr += w; bb_remaining -= w; w = snprintf (bbptr, bb_remaining, ANSIQ_DYN_CUR_SET ANSIQ_CUR_VISIBLE, (int)(editor.cursor.line - editor.row_offset) + 1, (int)(editor.cursor.col - editor.col_offset) + 1 + (int)gutter_width + 1); bbptr += w; bb_remaining -= w; mail_send (e_pgid, backbuffer, strlen (backbuffer)); uint8_t ch = 0; mail_receive (&ch, 1); switch (ch) { case '\b': if (editor.cursor.col > 0) { gapbuffer_move (&editor.current_line->gb, editor.cursor.col); editor.cursor.col--; gapbuffer_backspace (&editor.current_line->gb); } break; case '\n': { gapbuffer_move (&editor.current_line->gb, editor.cursor.col); struct edit_line* new_line = malloc (sizeof (*new_line)); memset (new_line, 0, sizeof (*new_line)); char* tail = gapbuffer_string_at (&warena_malloc, &temp_arena, &editor.current_line->gb, editor.cursor.col); size_t tail_size = strlen (tail); size_t init_cap = tail_size > 0 ? tail_size : 32; gapbuffer_init (&wmalloc, NULL, &new_line->gb, init_cap); if (tail_size > 0) { memcpy (new_line->gb.buffer, editor.current_line->gb.buffer + editor.current_line->gb.gap_end, tail_size); new_line->gb.gap_start = tail_size; } editor.current_line->gb.gap_end = editor.current_line->gb.size; list_insert_after (editor.lines, &editor.current_line->lines_link, &new_line->lines_link); editor.cursor.col = 0; editor.cursor.line++; editor.current_line = new_line; } break; case KB_DELETE: if (editor.cursor.col < gapbuffer_length (&editor.current_line->gb)) { gapbuffer_move (&editor.current_line->gb, editor.cursor.col); editor.current_line->gb.gap_end++; } break; case KB_LEFT: if (editor.cursor.col > 0) editor.cursor.col--; break; case KB_RIGHT: if (editor.cursor.col < gapbuffer_length (&editor.current_line->gb)) editor.cursor.col++; break; case KB_UP: { if (editor.cursor.line > 0) { editor.cursor.line--; editor.current_line = list_entry (editor.current_line->lines_link.prev, struct edit_line, lines_link); size_t len = gapbuffer_length (&editor.current_line->gb); if (editor.cursor.col > len) editor.cursor.col = len; } } break; case KB_DOWN: { if (editor.current_line->lines_link.next != NULL) { editor.cursor.line++; editor.current_line = list_entry (editor.current_line->lines_link.next, struct edit_line, lines_link); size_t len = gapbuffer_length (&editor.current_line->gb); if (editor.cursor.col > len) editor.cursor.col = len; } } break; case KB_HOME: editor.cursor.col = 0; break; case KB_END: editor.cursor.col = gapbuffer_length (&editor.current_line->gb); break; case KB_CTRL ('Q'): edit_run = false; break; default: if (isprint (ch)) { gapbuffer_move (&editor.current_line->gb, editor.cursor.col); gapbuffer_insert (&wrealloc, NULL, &editor.current_line->gb, ch); editor.cursor.col++; } break; } editor.total_lines = 0; arena_reset (&temp_arena); } mprintf (ANSIQ_SCR_CLR_ALL ANSIQ_CUR_VISIBLE); arena_destroy (&temp_arena); struct list_node_link *line_link, *tmp_line_link; list_foreach (editor.lines, line_link, tmp_line_link) { struct edit_line* line = list_entry (line_link, struct edit_line, lines_link); free (line->gb.buffer); free (line); } memset (&editor, 0, sizeof (editor)); }