#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) #define TAB_INSERT " " struct edit_line { struct list_node_link lines_link; struct list_node_link select_link; size_t select_start; size_t select_end; struct gapbuffer gb; }; struct cursor { size_t col; size_t line; }; #define EDIT_MODE_NORMAL 0 #define EDIT_MODE_COMAMND 1 static const char* string_modes[] = { [EDIT_MODE_NORMAL] = "Normal", [EDIT_MODE_COMAMND] = "Command", }; struct edit_select { struct list_node_link* lines; }; 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; struct edit_select select; int mode; }; static struct editor editor; static bool prepare_lines_cb (void* ctx, const char* start, size_t len) { (void)ctx; 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; editor.total_lines++; 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) editor.row_offset = editor.cursor.line - screen_rows + 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) { mprintf (ANSIQ_SCR_SAVE); prepare_lines (text); struct arena temp_arena; memset (&temp_arena, 0, sizeof (temp_arena)); size_t cols, rows; terminal_dimensions (&cols, &rows); mprintf (ANSIQ_CUR_INVISIBLE); bool edit_run = true; while (edit_run) { size_t gutter_width = count_digits (editor.total_lines); size_t effective_cols = (cols > gutter_width) ? cols - gutter_width : cols; update_horz_scroll (effective_cols); update_vert_scroll (rows - 1); const size_t backbuffer_max = 256 * 1024; char* backbuffer = arena_malloc (&temp_arena, backbuffer_max); char* bbptr = backbuffer; memcpy (bbptr, ANSIQ_CUR_HOME, sizeof (ANSIQ_CUR_HOME) - 1); bbptr += sizeof (ANSIQ_CUR_HOME) - 1; 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) { 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); bbptr += snprintf (bbptr, (backbuffer_max - (bbptr - backbuffer)), ANSIQ_SCR_CLR_LINE LINE_NUMBERS_STYLE "%*zu" ANSIQ_GR_RESET, (int)gutter_width, current_idx + 1); 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; memcpy (bbptr, &line->gb.buffer[start], len); bbptr += len; 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; memcpy (bbptr, &line->gb.buffer[line->gb.gap_end + skip], len); bbptr += len; } } bbptr += snprintf (bbptr, (backbuffer_max - (bbptr - backbuffer)), ANSIQ_SCR_CLR2END "\n"); lines_drawn++; current_idx++; } bbptr += snprintf (bbptr, (backbuffer_max - (bbptr - backbuffer)), ANSIQ_DYN_CUR_SET ANSIQ_SCR_CLR_LINE STATUS_LINE_STYLE "%*s" ANSIQ_DYN_CUR_SET " Editing %s; line %zu col %zu; Mode: %s" ANSIQ_GR_RESET ANSIQ_DYN_CUR_SET ANSIQ_CUR_VISIBLE, (int)rows, 1, (int)cols, "", (int)rows, 1, file_path, editor.cursor.line + 1, editor.cursor.col + 1, string_modes[editor.mode], (int)(editor.cursor.line - editor.row_offset) + 1, (int)(editor.cursor.col - editor.col_offset) + 1 + (int)gutter_width); mail_send (e_pgid, backbuffer, bbptr - backbuffer); uint8_t ch = 0; mail_receive (&ch, 1); switch (ch) { case '\b': if (editor.mode == EDIT_MODE_NORMAL) { if (editor.cursor.col > 0) { gapbuffer_move (&editor.current_line->gb, editor.cursor.col); editor.cursor.col--; gapbuffer_backspace (&editor.current_line->gb); } else { if (editor.cursor.line > 0) { struct list_node_link* prev = editor.current_line->lines_link.prev; struct edit_line* prev_line = list_entry (prev, struct edit_line, lines_link); size_t prev_len = gapbuffer_length (&prev_line->gb); gapbuffer_concat (&wrealloc, NULL, &prev_line->gb, &editor.current_line->gb); list_remove (editor.lines, &editor.current_line->lines_link); free (editor.current_line->gb.buffer); free (editor.current_line); editor.current_line = prev_line; editor.cursor.col = prev_len; editor.cursor.line--; editor.total_lines--; } } } break; case '\t': if (editor.mode == EDIT_MODE_NORMAL) { gapbuffer_move (&editor.current_line->gb, editor.cursor.col); for (size_t i = 0; i < sizeof (TAB_INSERT) - 1; i++) gapbuffer_insert (&wrealloc, NULL, &editor.current_line->gb, TAB_INSERT[i]); editor.cursor.col += sizeof (TAB_INSERT) - 1; } break; case '\n': { if (editor.mode == EDIT_MODE_NORMAL) { 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; editor.total_lines++; } } break; case KB_DELETE: if (editor.mode == EDIT_MODE_NORMAL) { size_t len = gapbuffer_length (&editor.current_line->gb); if (editor.cursor.col < len) { gapbuffer_move (&editor.current_line->gb, editor.cursor.col); if (editor.current_line->gb.gap_end < editor.current_line->gb.size) editor.current_line->gb.gap_end++; } else { if (editor.cursor.line > 0) { struct list_node_link* next = editor.current_line->lines_link.next; struct edit_line* next_line = list_entry (next, struct edit_line, lines_link); gapbuffer_concat (&wrealloc, NULL, &editor.current_line->gb, &next_line->gb); list_remove (editor.lines, &next_line->lines_link); free (next_line->gb.buffer); free (next_line); } } } 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_ESCAPE: editor.mode = EDIT_MODE_NORMAL; break; case KB_CTRL ('X'): editor.mode = EDIT_MODE_COMAMND; default: if (isprint (ch)) { if (editor.mode == EDIT_MODE_COMAMND) { switch (ch) { case 'q': edit_run = false; editor.mode = EDIT_MODE_NORMAL; break; default: editor.mode = EDIT_MODE_NORMAL; break; } } else if (editor.mode == EDIT_MODE_NORMAL) { gapbuffer_move (&editor.current_line->gb, editor.cursor.col); gapbuffer_insert (&wrealloc, NULL, &editor.current_line->gb, ch); editor.cursor.col++; } } break; } 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)); mprintf (ANSIQ_SCR_RESTORE); }