sled

Једноставни уређивач текста
git clone https://git.sr.ht/~strahinja/sled
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

чување 498b168ddf448357aa38a6dc371c7b3c4b5bb833
родитељ dbc32069c15dab0cd99e098c6f02d4929d132e57
Аутор: Страхиња Радић <contact@strahinja.org>
Датум:   Sun, 16 Jun 2024 18:11:49 +0200

Code cleanup, sort decls, consistent retvals; modal modes in statusbar

Diffstat:
Mconfig.def.h | 67+------------------------------------------------------------------
Mdraw.c | 974+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mdraw.h | 85++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mlimit.h | 11+++++++----
Msled.1.in | 10++++++++++
Msled.c | 2218+++++++++++++++++++++++++++++++++++++++----------------------------------------
Asled.h | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutil.h | 25+++++++++----------------
измењених датотека: 8, додавања: 1768(+), брисања: 1712(-)

diff --git a/config.def.h b/config.def.h @@ -51,65 +51,6 @@ const char* backup_suffix = "~"; #define TITLE_BG TB_WHITE #ifdef SLED_C_GLOBALS -int cancel_close(struct DrawState* state); -int cut_to_paste(struct DrawState* state); -int enter_command_mode(struct DrawState* state); -int enter_insert_mode(struct DrawState* state); -int erase_backwards(struct DrawState* state); -int erase_forward(struct DrawState* state); -int erase_line(struct DrawState* state); -int erase_prev_word(struct DrawState* state); -int erase_to_end(struct DrawState* state); -int erase_to_start(struct DrawState* state); -int exit_program(struct DrawState* state); -int input_cancel(struct DrawState* state); -int input_enter(struct DrawState* state); -int input_erase_backwards(struct DrawState* state); -int input_erase_forward(struct DrawState* state); -int input_erase_to_end(struct DrawState* state); -int input_erase_to_start(struct DrawState* state); -int input_insert_space(struct DrawState* state); -int input_insert_tab(struct DrawState* state); -int input_move_end(struct DrawState* state); -int input_move_left(struct DrawState* state); -int input_move_right(struct DrawState* state); -int input_move_start(struct DrawState* state); -int input_move_prev_word(struct DrawState* state); -int input_move_next_word(struct DrawState* state); -int insert_line(struct DrawState* state); -int insert_space(struct DrawState* state); -int insert_tab(struct DrawState* state); -int join_lines(struct DrawState* state); -int move_bottom(struct DrawState* state); -int move_down(struct DrawState* state); -int move_end(struct DrawState* state); -int move_left(struct DrawState* state); -int move_next_word(struct DrawState* state); -int move_page_down(struct DrawState* state); -int move_page_up(struct DrawState* state); -int move_prev_word(struct DrawState* state); -int move_right(struct DrawState* state); -int move_screen_bottom(struct DrawState* state); -int move_screen_middle(struct DrawState* state); -int move_screen_top(struct DrawState* state); -int move_start(struct DrawState* state); -int move_top(struct DrawState* state); -int move_up(struct DrawState* state); -int next_match(struct DrawState* state); -int paste_from_paste(struct DrawState* state); -int prev_match(struct DrawState* state); -int reread_or_callback(struct DrawState* state); -int show_go_to_line(struct DrawState* state); -int show_help(struct DrawState* state); -int show_replace(struct DrawState* state); -int show_search(struct DrawState* state); -int show_search_replace(struct DrawState* state); -int toggle_overwrite(struct DrawState* state); -int toggle_selection(struct DrawState* state); -int write_file(struct DrawState* state); -int yank_to_paste(struct DrawState* state); -int suspend_program(struct DrawState* state); - /* clang-format off */ /* Always processed */ @@ -284,16 +225,10 @@ static const struct Key command_keys[] = { { .ev = { .ch = 'y' }, .callback = yank_to_paste }, { .ev = { .ch = 'Z' }, .callback = write_file } }; + /* clang-format on */ #endif /* SLED_C_GLOBALS */ -void format_filename(char* result, size_t result_size, const char* format, - const int seg_size, const struct DrawState* state); -void format_mode(char* result, size_t result_size, const char* format, - const int seg_size, const struct DrawState* state); -void format_position(char* result, size_t result_size, const char* format, - const int seg_size, const struct DrawState* state); - /* clang-format off */ static const struct StatusSegment status_segments[] = { {.format = "%s%s", diff --git a/draw.c b/draw.c @@ -13,6 +13,18 @@ #include "draw.h" #include "config.h" +enum { + UNKNOWN_MODE, + COMMAND_MODE, + HELP_MODE, + INSERT_MODE, + OVERWRITE_MODE, + PROMPT_MODE, + INPUT_MDOE, + INPUT_NUM_MODE, + SELECTION_MODE +}; + /* clang-format off */ static const char* help_title = "sled help"; static const char* help[] = { @@ -42,6 +54,8 @@ const char* mode_labels[] = { [INSERT_MODE] = "INSERT", [OVERWRITE_MODE] = "OVERWRITE", [PROMPT_MODE] = "PROMPT", + [INPUT_MDOE] = "INPUT", + [INPUT_NUM_MODE] = "NUM INPUT", [SELECTION_MODE] = "SELECTION" }; const char* errors[] = { @@ -72,169 +86,45 @@ void get_selection(int* ssr, int* ssc, int* ser, int* sec, const struct DrawState* state); void -init_bufferline(struct BufferLine* bl) -{ - if (!bl) - return; - bl->text = NULL; - bl->length = 0; - bl->size = 0; - bl->fg = 0; - bl->bg = 0; -} - -void -init_coord(struct Coord* c, const int col, const int row) -{ - c->col = col; - c->row = row; -} - -void -init_drawstate(struct DrawState* state, char* error, char* action, - char* filename, char* prompt, int skip_paste_and_mode_init) -{ - state->error = error; - state->action = action; - state->filename = filename; - state->real_filename = NULL; - state->backup_filename = NULL; - state->rfn_len = 0; - state->input = NULL; - state->search = NULL; - state->chain_dialog = 0; - state->buffer = NULL; - init_coord(&state->coord, 0, 0); - state->display_column = 0; - init_coord(&state->first_shown, 0, 0); - state->input_first_shown_column = 0; - state->input_display_column = 0; - state->input_column = 0; - state->input_title = NULL; - state->rows_count = 0; - state->saved_column = -1; - state->help_width = calculate_help_width(state); - state->input_dialog_width = calculate_input_dialog_width(state); - if (!skip_paste_and_mode_init) - { - state->paste = NULL; - state->paste_count = 0; - state->paste_size = 0; - CLEAR(state->mode, M_OVERWRITE | M_COMMAND); - } - state->prompt = prompt; - state->prompt_callback = NULL; - state->input_callback = NULL; - state->input_update_callback = NULL; - state->running = 1; - state->dirty = 0; - state->new_file = 0; - state->recalculate_size = 1; - CLEAR(state->mode, - M_HELP | M_INPUT | M_INPUT_NUM | M_PROMPT | M_SELECTION); - init_coord(&state->sel_start, 0, 0); - init_coord(&state->sel_end, 0, 0); - state->backup_possible = 1; -} - -void -free_bufferline(struct BufferLine* bl, const int free_text) -{ - if (!bl) - return; - if (free_text) - free(bl->text); - bl->text = NULL; - bl->length = 0; - bl->fg = 0; - bl->bg = 0; -} - -void -free_drawstate(struct DrawState* state, int skip_paste_free) -{ - size_t row = 0; - for (row = 0; row < state->buffer_size; row++) - free_bufferline(state->buffer + row, 1); - free(state->backup_filename); - free(state->real_filename); - free(state->buffer); - if (!skip_paste_free) - { - for (row = 0; row < state->paste_size; row++) - free_bufferline(state->paste + row, 1); - free(state->paste); - } - free(state->input); - free(state->search); -} - -void assign_coord(struct Coord* dest, const struct Coord src) { init_coord(dest, src.col, src.row); } -void -format_filename(char* result, size_t result_size, const char* format, - const int seg_size, const struct DrawState* state) +/* Calculates buffer length from a display length. + * max_index = the display index up to which the length is calculated + */ +size_t +buffer_length(const struct DrawState* state, uint32_t* buffer, + const size_t max_index) { - char* basename = NULL; - if (!result || seg_size < 2) - return; - - basename = strdup(u8_basename(state->filename)); + size_t len = 0; + uint32_t* pbuf = buffer; + size_t i = 0; - if (u8_strlen(basename) >= seg_size - 2) + for (i = 0; *pbuf && len < max_index; i++) { - if (state->dirty) - basename[seg_size - 4] = 0; + pbuf = buffer + i; + if (*pbuf == '\t') + len += MIN(TAB_SIZE - (len % TAB_SIZE), max_index - len); else - basename[seg_size - 3] = 0; - snprintf(result, result_size, format, basename, - state->dirty ? "…*" : "…"); + len++; } - else - snprintf(result, result_size, format, basename, - state->dirty ? "*" : ""); - free(basename); + + return i; } -void -format_mode(char* result, size_t result_size, const char* format, - const int seg_size, const struct DrawState* state) +int +calculate_help_width(const struct DrawState* state) { - const char* mode_label = mode_labels[UNKNOWN_MODE]; - if (!result) - goto format_print; - if (IN(state->mode, M_HELP)) - mode_label = mode_labels[HELP_MODE]; - else if (IN(state->mode, M_PROMPT)) - mode_label = mode_labels[PROMPT_MODE]; - else if (IN(state->mode, M_SELECTION)) - mode_label = mode_labels[SELECTION_MODE]; - else if (IN(state->mode, M_COMMAND)) - mode_label = mode_labels[COMMAND_MODE]; - else if (IN(state->mode, M_OVERWRITE)) - mode_label = mode_labels[OVERWRITE_MODE]; - else - mode_label = mode_labels[INSERT_MODE]; - -format_print: - snprintf(result, result_size, format, mode_label); + return state->maxx > HELP_WIDTH + 2 ? HELP_WIDTH : state->maxx - 2; } -void -format_position(char* result, size_t result_size, const char* format, - const int seg_size, const struct DrawState* state) +int +calculate_input_dialog_width(const struct DrawState* state) { - if (!result) - return; - snprintf(result, result_size, format, state->display_column + 1, - state->coord.row + 1, - state->rows_count > 1 - ? (state->coord.row * 100) / (state->rows_count - 1) - : 0); + return state->maxx > INPUT_DLG_WIDTH + 2 ? INPUT_DLG_WIDTH + : state->maxx - 2; } /* Calculates display length of a buffer line. @@ -262,49 +152,27 @@ display_length(const struct DrawState* state, uint32_t* buffer, return len; } -/* Calculates buffer length from a display length. - * max_index = the display index up to which the length is calculated - */ -size_t -buffer_length(const struct DrawState* state, uint32_t* buffer, - const size_t max_index) +int +draw(const struct DrawState* state) { - size_t len = 0; - uint32_t* pbuf = buffer; - size_t i = 0; + if (!state) + return 1; - for (i = 0; *pbuf && len < max_index; i++) - { - pbuf = buffer + i; - if (*pbuf == '\t') - len += MIN(TAB_SIZE - (len % TAB_SIZE), max_index - len); - else - len++; - } + if (draw_screen(state)) + return 1; - return i; -} + if (IN(state->mode, M_HELP)) + if (draw_help(state)) + return 1; -int -calculate_help_width(const struct DrawState* state) -{ - return state->maxx > HELP_WIDTH + 2 ? HELP_WIDTH : state->maxx - 2; -} + if (IN(state->mode, M_INPUT)) + if (draw_input_dialog(state)) + return 1; -int -calculate_input_dialog_width(const struct DrawState* state) -{ - return state->maxx > INPUT_DLG_WIDTH + 2 ? INPUT_DLG_WIDTH - : state->maxx - 2; -} + if (draw_status(state)) + return 1; -void -draw_rect(const int startx, const int endx, const int starty, const int endy, - const uint16_t fg, const uint16_t bg) -{ - for (int y = starty; y < endy; y++) - for (int x = startx; x < endx; x++) - tb_change_cell(x, y, ' ', fg, bg); + return 0; } void @@ -333,41 +201,461 @@ draw_box(const int startx, const int endx, const int starty, const int endy, tb_change_cell(endx - 1, endy - 2, BORDER_SE, fg, bg); tb_change_cell(endx, endy - 2, ' ', SHADOW_FG, SHADOW_BG); - for (x = startx + 1; x < endx + 1; x++) - tb_change_cell(x, endy - 1, ' ', SHADOW_FG, SHADOW_BG); + for (x = startx + 1; x < endx + 1; x++) + tb_change_cell(x, endy - 1, ' ', SHADOW_FG, SHADOW_BG); +} + +int +draw_help(const struct DrawState* state) +{ + int startx, endx, starty, endy; + int w, h; + int row = 0; + int maxx; + int maxy; + + if (!state) + return 1; + + w = state->help_width; + h = HELP_HEIGHT; + maxx = state->maxx; + maxy = state->maxy; + + startx = w > maxx ? 0 : (maxx - w) / 2; + endx = w > maxx ? maxx - 1 : startx + w; + + starty = h > maxy - 1 ? 0 : (maxy - h) / 2; + endy = h > maxy - 1 ? maxy - 2 : starty + h; + + draw_box(startx, endx, starty, endy, DLG_FG, DLG_BG); + if (draw_string(startx, starty, TITLE_FG, TITLE_BG, help_title, + strlen(help_title) + 2, w, 1, 1, CENTER, 0, 0, NULL, 0, 0, + 0, 0, 0, 0)) + return 1; + while (row < LEN(help)) + { + if (draw_string(startx + 1, starty + 1 + row, DLG_FG, DLG_BG, + help[row], w - 2, w - 2, 1, 1, LEFT, 0, 1, NULL, 0, + 0, 0, 0, 0, 0)) + return 1; + row++; + } + tb_hide_cursor(); + return 0; +} + +int +draw_input_dialog(const struct DrawState* state) +{ + int startx, endx, starty, endy; + int w, h; + int maxx, maxy; + size_t len; + size_t dlen = 0; + + if (!state) + return 1; + + w = state->input_dialog_width; + h = INPUT_DLG_HEIGHT; + maxx = state->maxx; + maxy = state->maxy; + len = u32_strlen(state->input); + + startx = w > maxx ? 0 : (maxx - w) / 2; + endx = w > maxx ? maxx - 1 : startx + w; + + starty = maxy - 1 - h; + endy = maxy - 1; + + draw_box(startx, endx, starty, endy, INPUT_FG, INPUT_BG); + draw_rect(startx + 1, endx - 1, starty + 1, endy - 2, INPUT_EDIT_FG, + INPUT_EDIT_BG); + if (state->input_title) + if (draw_string(startx, starty, TITLE_FG, TITLE_BG, + state->input_title, strlen(state->input_title) + 2, + w, 1, 1, CENTER, 0, 0, NULL, 0, 0, 0, 0, 0, 0)) + return 1; + dlen = display_length(state, state->input, len); + if (dlen > state->input_first_shown_column) + u32_draw_string(startx + 1, starty + 1, INPUT_EDIT_FG, + INPUT_EDIT_BG, state->input, w - 2, w - 2, 1, 1, LEFT, + state->input_first_shown_column, 1, NULL, 0, 0, 0, 0, 0, + 0); + tb_set_cursor(startx + 2 + state->input_display_column + - state->input_first_shown_column, + starty + 1); + return 0; +} + +void +draw_rect(const int startx, const int endx, const int starty, const int endy, + const uint16_t fg, const uint16_t bg) +{ + for (int y = starty; y < endy; y++) + for (int x = startx; x < endx; x++) + tb_change_cell(x, y, ' ', fg, bg); +} + +int +draw_screen(const struct DrawState* state) +{ + const struct BufferLine* current = NULL; + int max_screen_lines; + int cy = 0; + int i; + /* Selection start/end index - passed to u32_draw_string */ + int ssi = 0; + int sei = 0; + /* Selection start row/col */ + int ssr = 0; + int ssc = 0; + /* Selection end row/col */ + int ser = 0; + int sec = 0; + + if (!state) + return 1; + + if (state->rows_count == 0) + return 0; + + max_screen_lines = state->maxy - 1; + i = state->first_shown.row; + + while ((i < state->rows_count) && (cy < max_screen_lines)) + { + current = state->buffer + i; + + ssi = 0; + sei = 0; + + get_selection(&ssr, &ssc, &ser, &sec, state); + if ((i > ssr) && (i < ser)) + { + ssi = 0; + sei = current->length; + } + else + { + if (i == ssr) + { + ssi = ssc; + if (i != ser) + sei = current->length; + } + if (i == ser) + { + sei = sec; + } + } + + if (current->text) + u32_draw_string(0, cy, TEXT_FG, TEXT_BG, current->text, + state->maxx, state->maxx, 0, 0, CENTER, + state->first_shown.col, 0, state->search, + SEARCH_FG, SEARCH_BG, ssi, sei, SEL_FG, SEL_BG); + i++; + cy++; + } + + tb_set_cursor(state->display_column - state->first_shown.col, + state->coord.row - state->first_shown.row); + + return 0; +} + +int +draw_status(const struct DrawState* state) +{ + char* buf = NULL; + int seg_size; + int current_start = 0; + const struct StatusSegment* pseg; + + if (!state) + return 1; + + seg_size = state->maxx / LEN(status_segments); + pseg = status_segments; + + if (IN(state->mode, M_PROMPT) && *state->prompt) + { + if (draw_string(0, state->maxy - 1, PROMPT_FG, PROMPT_BG, + state->prompt, strlen(state->prompt) + 2, + state->maxx, 1, 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, + 0, 0)) + return 1; + } + else if (state->action && *state->action) + { + if (draw_string(0, state->maxy - 1, ACTION_FG, ACTION_BG, + state->action, strlen(state->action) + 2, + state->maxx, 1, 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, + 0, 0)) + return 1; + } + else if (state->error && *state->error) + { + if (draw_string(0, state->maxy - 1, ERROR_FG, ERROR_BG, + state->error, strlen(state->error) + 2, state->maxx, + 1, 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0)) + return 1; + } + else + while (pseg < status_segments + LEN(status_segments)) + { + int last = pseg - status_segments + == LEN(status_segments) - 1; + + buf = calloc(limits[L_LINE_MAX], 1); + if (!buf) + return 1; + + *buf = 0; + if (pseg->callback) + pseg->callback(buf, limits[L_LINE_MAX], + pseg->format, seg_size, state); + else + memccpy(buf, pseg->format, 0, + limits[L_LINE_MAX]); + if (pseg == status_segments + LEN(status_segments) - 1) + seg_size = state->maxx - 1 - current_start; + if (draw_string(current_start, state->maxy - 1, + pseg->fg, pseg->bg, buf, + MIN(seg_size, strlen(buf) + 2), + last ? seg_size + 1 : seg_size, 1, 1, + pseg->alignment, 0, 1, NULL, 0, 0, 0, 0, 0, + 0)) + { + free(buf); + return 1; + } + free(buf); + pseg++; + current_start += seg_size; + } + + return 0; +} + +/* mc - (max cols) pack text and padding into this many columns + * fc - (fill cols) this many columns from the left/right/center will be + * filled + * p_? - (padding) this many cells will be skipped from ? side + * ds - (display start) string starts being displayed from this display column + * counted from the start of the string; an "entry point" joining string + * index with screen + * fill - boolean; 1 = fill fc, 0 = don't fill fc + * srch - search string; if empty or NULL no highlighting happens + * sfg - fg color of search highlght + * sbg - bg color of search highlight + * ssi - selection start index (>=) + * sei - selection end index (<) + * slfg - selection fg color + * slbg - selection bg color + * */ +int +draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, + const char* s, const int mc, const int fc, const int p_l, const int p_r, + const Alignment align, const int ds, const int fill, + const uint32_t* srch, const uint16_t sfg, const uint16_t sbg, + const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg) +{ + uint32_t* us = calloc(strlen(s) + 1, sizeof(uint32_t)); + if (!us) + return 1; + u8_string_to_unicode(us, s, mc); + u32_draw_string(x, y, fg, bg, us, mc, fc, p_l, p_r, align, ds, fill, + srch, sfg, sbg, ssi, sei, slfg, slbg); + free(us); + return 0; +} + +void +format_filename(char* result, size_t result_size, const char* format, + const int seg_size, const struct DrawState* state) +{ + char* basename = NULL; + if (!result || seg_size < 2) + return; + + basename = strdup(u8_basename(state->filename)); + + if (u8_strlen(basename) >= seg_size - 2) + { + if (state->dirty) + basename[seg_size - 4] = 0; + else + basename[seg_size - 3] = 0; + snprintf(result, result_size, format, basename, + state->dirty ? "…*" : "…"); + } + else + snprintf(result, result_size, format, basename, + state->dirty ? "*" : ""); + free(basename); +} + +void +format_mode(char* result, size_t result_size, const char* format, + const int seg_size, const struct DrawState* state) +{ + const char* mode_label = mode_labels[UNKNOWN_MODE]; + if (!result) + goto format_print; + if (IN(state->mode, M_INPUT_NUM)) + mode_label = mode_labels[INPUT_NUM_MODE]; + else if (IN(state->mode, M_INPUT)) + mode_label = mode_labels[INPUT_MDOE]; + else if (IN(state->mode, M_HELP)) + mode_label = mode_labels[HELP_MODE]; + else if (IN(state->mode, M_PROMPT)) + mode_label = mode_labels[PROMPT_MODE]; + else if (IN(state->mode, M_SELECTION)) + mode_label = mode_labels[SELECTION_MODE]; + else if (IN(state->mode, M_COMMAND)) + mode_label = mode_labels[COMMAND_MODE]; + else if (IN(state->mode, M_OVERWRITE)) + mode_label = mode_labels[OVERWRITE_MODE]; + else + mode_label = mode_labels[INSERT_MODE]; + +format_print: + snprintf(result, result_size, format, mode_label); +} + +void +format_position(char* result, size_t result_size, const char* format, + const int seg_size, const struct DrawState* state) +{ + if (!result) + return; + snprintf(result, result_size, format, state->display_column + 1, + state->coord.row + 1, + state->rows_count > 1 + ? (state->coord.row * 100) / (state->rows_count - 1) + : 0); +} + +void +free_bufferline(struct BufferLine* bl, const int free_text) +{ + if (!bl) + return; + if (free_text) + free(bl->text); + bl->text = NULL; + bl->length = 0; + bl->fg = 0; + bl->bg = 0; +} + +void +free_drawstate(struct DrawState* state, int skip_paste_free) +{ + size_t row = 0; + for (row = 0; row < state->buffer_size; row++) + free_bufferline(state->buffer + row, 1); + free(state->backup_filename); + free(state->real_filename); + free(state->buffer); + if (!skip_paste_free) + { + for (row = 0; row < state->paste_size; row++) + free_bufferline(state->paste + row, 1); + free(state->paste); + } + free(state->input); + free(state->search); +} + +void +init_bufferline(struct BufferLine* bl) +{ + if (!bl) + return; + bl->text = NULL; + bl->length = 0; + bl->size = 0; + bl->fg = 0; + bl->bg = 0; +} + +void +init_coord(struct Coord* c, const int col, const int row) +{ + c->col = col; + c->row = row; +} + +void +init_drawstate(struct DrawState* state, char* error, char* action, + char* filename, char* prompt, int skip_paste_and_mode_init) +{ + state->error = error; + state->action = action; + state->filename = filename; + state->real_filename = NULL; + state->backup_filename = NULL; + state->rfn_len = 0; + state->input = NULL; + state->search = NULL; + state->chain_dialog = 0; + state->buffer = NULL; + init_coord(&state->coord, 0, 0); + state->display_column = 0; + init_coord(&state->first_shown, 0, 0); + state->input_first_shown_column = 0; + state->input_display_column = 0; + state->input_column = 0; + state->input_title = NULL; + state->rows_count = 0; + state->saved_column = -1; + state->help_width = calculate_help_width(state); + state->input_dialog_width = calculate_input_dialog_width(state); + if (!skip_paste_and_mode_init) + { + state->paste = NULL; + state->paste_count = 0; + state->paste_size = 0; + CLEAR(state->mode, M_OVERWRITE | M_COMMAND); + } + state->prompt = prompt; + state->prompt_callback = NULL; + state->input_callback = NULL; + state->input_update_callback = NULL; + state->running = 1; + state->dirty = 0; + state->new_file = 0; + state->recalculate_size = 1; + CLEAR(state->mode, + M_HELP | M_INPUT | M_INPUT_NUM | M_PROMPT | M_SELECTION); + init_coord(&state->sel_start, 0, 0); + init_coord(&state->sel_end, 0, 0); + state->backup_possible = 1; +} + +void +recalculate_size(struct DrawState* state) +{ + state->maxx = tb_width(); + state->help_width = calculate_help_width(state); + state->input_dialog_width = calculate_input_dialog_width(state); + state->maxy = tb_height(); } -/* mc - (max cols) pack text and padding into this many columns - * fc - (fill cols) this many columns from the left/right/center will be - * filled - * p_? - (padding) this many cells will be skipped from ? side - * ds - (display start) string starts being displayed from this display column - * counted from the start of the string; an "entry point" joining string - * index with screen - * fill - boolean; 1 = fill fc, 0 = don't fill fc - * srch - search string; if empty or NULL no highlighting happens - * sfg - fg color of search highlght - * sbg - bg color of search highlight - * ssi - selection start index (>=) - * sei - selection end index (<) - * slfg - selection fg color - * slbg - selection bg color - * */ -const char* -draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, - const char* s, const int mc, const int fc, const int p_l, const int p_r, - const Alignment align, const int ds, const int fill, - const uint32_t* srch, const uint16_t sfg, const uint16_t sbg, - const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg) +void +redraw_status(struct DrawState* state) { - uint32_t* us = calloc(strlen(s) + 1, sizeof(uint32_t)); - if (!us) - return NULL; - u8_string_to_unicode(us, s, mc); - u32_draw_string(x, y, fg, bg, us, mc, fc, p_l, p_r, align, ds, fill, - srch, sfg, sbg, ssi, sei, slfg, slbg); - free(us); - return s; + if (state->recalculate_size) + { + recalculate_size(state); + state->recalculate_size = 0; + } + (void)draw_status(state); + tb_present(); } /* mc - (max cols) pack text and padding into this many columns @@ -591,257 +879,3 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, for (int j = 0; j < p_r; j++) tb_change_cell(x + d + p_l + c + j, y, ' ', fg, bg); } - -const struct DrawState* -draw_help(const struct DrawState* state) -{ - if (!state) - return NULL; - - int startx, endx, starty, endy; - int w = state->help_width; - int h = HELP_HEIGHT; - int row = 0; - int maxx = state->maxx; - int maxy = state->maxy; - - startx = w > maxx ? 0 : (maxx - w) / 2; - endx = w > maxx ? maxx - 1 : startx + w; - - starty = h > maxy - 1 ? 0 : (maxy - h) / 2; - endy = h > maxy - 1 ? maxy - 2 : starty + h; - - draw_box(startx, endx, starty, endy, DLG_FG, DLG_BG); - if (!draw_string(startx, starty, TITLE_FG, TITLE_BG, help_title, - strlen(help_title) + 2, w, 1, 1, CENTER, 0, 0, NULL, 0, 0, - 0, 0, 0, 0)) - return NULL; - while (row < LEN(help)) - { - if (!draw_string(startx + 1, starty + 1 + row, DLG_FG, DLG_BG, - help[row], w - 2, w - 2, 1, 1, LEFT, 0, 1, NULL, 0, - 0, 0, 0, 0, 0)) - return NULL; - row++; - } - tb_hide_cursor(); - return state; -} - -const struct DrawState* -draw_input_dialog(const struct DrawState* state) -{ - int startx, endx, starty, endy; - int w = state->input_dialog_width; - int h = INPUT_DLG_HEIGHT; - int maxx = state->maxx; - int maxy = state->maxy; - size_t len = u32_strlen(state->input); - size_t dlen = 0; - - startx = w > maxx ? 0 : (maxx - w) / 2; - endx = w > maxx ? maxx - 1 : startx + w; - - starty = maxy - 1 - h; - endy = maxy - 1; - - draw_box(startx, endx, starty, endy, INPUT_FG, INPUT_BG); - draw_rect(startx + 1, endx - 1, starty + 1, endy - 2, INPUT_EDIT_FG, - INPUT_EDIT_BG); - if (state->input_title) - if (!draw_string(startx, starty, TITLE_FG, TITLE_BG, - state->input_title, strlen(state->input_title) + 2, - w, 1, 1, CENTER, 0, 0, NULL, 0, 0, 0, 0, 0, 0)) - return NULL; - dlen = display_length(state, state->input, len); - if (dlen > state->input_first_shown_column) - u32_draw_string(startx + 1, starty + 1, INPUT_EDIT_FG, - INPUT_EDIT_BG, state->input, w - 2, w - 2, 1, 1, LEFT, - state->input_first_shown_column, 1, NULL, 0, 0, 0, 0, 0, - 0); - tb_set_cursor(startx + 2 + state->input_display_column - - state->input_first_shown_column, - starty + 1); - return state; -} - -const struct DrawState* -draw_screen(const struct DrawState* state) -{ - const struct BufferLine* current = NULL; - int max_screen_lines = state->maxy - 1; - int cy = 0; - int i = state->first_shown.row; - /* Selection start/end index - passed to u32_draw_string */ - int ssi = 0; - int sei = 0; - /* Selection start row/col */ - int ssr = 0; - int ssc = 0; - /* Selection end row/col */ - int ser = 0; - int sec = 0; - - if (!state) - return NULL; - - if (state->rows_count == 0) - return state; - - while ((i < state->rows_count) && (cy < max_screen_lines)) - { - current = state->buffer + i; - - ssi = 0; - sei = 0; - - get_selection(&ssr, &ssc, &ser, &sec, state); - if ((i > ssr) && (i < ser)) - { - ssi = 0; - sei = current->length; - } - else - { - if (i == ssr) - { - ssi = ssc; - if (i != ser) - sei = current->length; - } - if (i == ser) - { - sei = sec; - } - } - - if (current->text) - u32_draw_string(0, cy, TEXT_FG, TEXT_BG, current->text, - state->maxx, state->maxx, 0, 0, CENTER, - state->first_shown.col, 0, state->search, - SEARCH_FG, SEARCH_BG, ssi, sei, SEL_FG, SEL_BG); - i++; - cy++; - } - - tb_set_cursor(state->display_column - state->first_shown.col, - state->coord.row - state->first_shown.row); - - return state; -} - -const struct DrawState* -draw_status(const struct DrawState* state) -{ - char* buf = NULL; - int seg_size; - int current_start = 0; - const struct StatusSegment* pseg; - - if (!state) - return NULL; - - seg_size = state->maxx / LEN(status_segments); - pseg = status_segments; - - if (IN(state->mode, M_PROMPT) && *state->prompt) - { - if (!draw_string(0, state->maxy - 1, PROMPT_FG, PROMPT_BG, - state->prompt, strlen(state->prompt) + 2, - state->maxx, 1, 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, - 0, 0)) - return NULL; - } - else if (state->action && *state->action) - { - if (!draw_string(0, state->maxy - 1, ACTION_FG, ACTION_BG, - state->action, strlen(state->action) + 2, - state->maxx, 1, 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, - 0, 0)) - return NULL; - } - else if (state->error && *state->error) - { - if (!draw_string(0, state->maxy - 1, ERROR_FG, ERROR_BG, - state->error, strlen(state->error) + 2, state->maxx, - 1, 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0)) - return NULL; - } - else - while (pseg < status_segments + LEN(status_segments)) - { - int last = pseg - status_segments - == LEN(status_segments) - 1; - - buf = calloc(limits[L_LINE_MAX], 1); - if (!buf) - return NULL; - - buf[0] = 0; - if (pseg->callback) - pseg->callback(buf, limits[L_LINE_MAX], - pseg->format, seg_size, state); - else - memccpy(buf, pseg->format, 0, - limits[L_LINE_MAX]); - if (pseg == status_segments + LEN(status_segments) - 1) - seg_size = state->maxx - 1 - current_start; - if (!draw_string(current_start, state->maxy - 1, - pseg->fg, pseg->bg, buf, - MIN(seg_size, strlen(buf) + 2), - last ? seg_size + 1 : seg_size, 1, 1, - pseg->alignment, 0, 1, NULL, 0, 0, 0, 0, 0, - 0)) - { - free(buf); - return NULL; - } - free(buf); - pseg++; - current_start += seg_size; - } - - return state; -} - -void -recalculate_size(struct DrawState* state) -{ - state->maxx = tb_width(); - state->help_width = calculate_help_width(state); - state->input_dialog_width = calculate_input_dialog_width(state); - state->maxy = tb_height(); - state->recalculate_size = 0; -} - -void -redraw_status(struct DrawState* state) -{ - if (state->recalculate_size) - recalculate_size(state); - draw_status(state); - tb_present(); -} - -const struct DrawState* -draw(const struct DrawState* state) -{ - if (!state) - return NULL; - - if (!draw_screen(state)) - return NULL; - - if (IN(state->mode, M_HELP)) - if (!draw_help(state)) - return NULL; - - if (IN(state->mode, M_INPUT)) - if (!draw_input_dialog(state)) - return NULL; - - if (!draw_status(state)) - return NULL; - - return state; -} diff --git a/draw.h b/draw.h @@ -22,21 +22,16 @@ #define INPUT_DLG_HEIGHT 4 #define TAB_SIZE 8 -#define M_NONE 0 -#define M_COMMAND 1 -#define M_OVERWRITE 1 << 1 -#define M_SELECTION 1 << 2 -#define M_PROMPT 1 << 3 -#define M_HELP 1 << 4 -#define M_INPUT 1 << 5 -#define M_INPUT_NUM 1 << 6 - -/* clang-format off */ -#define CLEAR(v, c) do { (v) &= ~(c); } while(0) -#define IN(v, c) (((v) & (c)) == (c)) -#define SET(v, c) do { (v) |= (c); } while(0) -#define TOGGLE(v, c) do { (v) ^= (c); } while(0) -/* clang-format on */ +enum { + M_NONE = 0, + M_COMMAND = 1, + M_OVERWRITE = 1 << 1, + M_SELECTION = 1 << 2, + M_PROMPT = 1 << 3, + M_HELP = 1 << 4, + M_INPUT = 1 << 5, + M_INPUT_NUM = 1 << 6 +}; typedef enum { LEFT, CENTER, RIGHT } Alignment; @@ -47,7 +42,7 @@ struct Coord { struct BufferLine { uint32_t* text; - size_t length; /* both in uint32_t units */ + size_t length; /* both in uint32_t units */ size_t size; uint16_t fg; uint16_t bg; @@ -80,9 +75,9 @@ struct DrawState { struct BufferLine* paste; size_t paste_count; size_t paste_size; - void (*prompt_callback)(struct DrawState*, struct tb_event*); - void (*input_callback)(struct DrawState*); - void (*input_update_callback)(struct DrawState*); + int (*prompt_callback)(struct DrawState*, struct tb_event*); + int (*input_callback)(struct DrawState*); + int (*input_update_callback)(struct DrawState*); int running; int dirty; int new_file; @@ -109,39 +104,45 @@ struct StatusSegment { Alignment alignment; }; -void init_bufferline(struct BufferLine* bl); -void init_coord(struct Coord* c, const int col, const int row); -void init_drawstate(struct DrawState* state, char* error, char* action, - char* filename, char* prompt, int skip_paste_and_mode_init); -void free_bufferline(struct BufferLine* bl, const int free_text); -void free_drawstate(struct DrawState* state, int skip_paste_free); void assign_coord(struct Coord* dest, const struct Coord src); -size_t display_length(const struct DrawState* state, uint32_t* buffer, - const size_t max_index); size_t buffer_length(const struct DrawState* state, uint32_t* buffer, const size_t max_index); int calculate_help_width(const struct DrawState* state); int calculate_input_dialog_width(const struct DrawState* state); -void draw_rect(const int startx, const int endx, const int starty, - const int endy, const uint16_t fg, const uint16_t bg); +size_t display_length(const struct DrawState* state, uint32_t* buffer, + const size_t max_index); +int draw(const struct DrawState* state); void draw_box(const int startx, const int endx, const int starty, const int endy, const uint16_t fg, const uint16_t bg); -const char* draw_string(const int x, const int y, const uint16_t fg, - const uint16_t bg, const char* s, const int max_cols, - const int fill_cols, const int padding_left, const int padding_right, - const Alignment align, const int ds, const int fill, - const uint32_t* srch, const uint16_t sfg, const uint16_t sbg, - const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg); +int draw_help(const struct DrawState* state); +int draw_input_dialog(const struct DrawState* state); +void draw_rect(const int startx, const int endx, const int starty, + const int endy, const uint16_t fg, const uint16_t bg); +int draw_screen(const struct DrawState* state); +int draw_status(const struct DrawState* state); +int draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, + const char* s, const int max_cols, const int fill_cols, + const int padding_left, const int padding_right, const Alignment align, + const int ds, const int fill, const uint32_t* srch, const uint16_t sfg, + const uint16_t sbg, const int ssi, const int sei, const uint16_t slfg, + const uint16_t slbg); +void format_filename(char* result, size_t result_size, const char* format, + const int seg_size, const struct DrawState* state); +void format_mode(char* result, size_t result_size, const char* format, + const int seg_size, const struct DrawState* state); +void format_position(char* result, size_t result_size, const char* format, + const int seg_size, const struct DrawState* state); +void free_bufferline(struct BufferLine* bl, const int free_text); +void free_drawstate(struct DrawState* state, int skip_paste_free); +void init_bufferline(struct BufferLine* bl); +void init_coord(struct Coord* c, const int col, const int row); +void init_drawstate(struct DrawState* state, char* error, char* action, + char* filename, char* prompt, int skip_paste_and_mode_init); +void recalculate_size(struct DrawState* state); +void redraw_status(struct DrawState* state); void u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, const uint32_t* s, const int max_cols, const int fill_cols, const int padding_left, const int padding_right, const Alignment align, const int ds, const int fill, const uint32_t* srch, const uint16_t sfg, const uint16_t sbg, const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg); -const struct DrawState* draw_help(const struct DrawState* state); -const struct DrawState* draw_input_dialog(const struct DrawState* state); -const struct DrawState* draw_screen(const struct DrawState* state); -const struct DrawState* draw_status(const struct DrawState* state); -void recalculate_size(struct DrawState* state); -void redraw_status(struct DrawState* state); -const struct DrawState* draw(const struct DrawState* state); diff --git a/limit.h b/limit.h @@ -13,11 +13,14 @@ enum { extern long limits[]; -#define ALLOC_DELTA 50 +/* buffer growth factor, in lines */ +#define ALLOC_DELTA 50 +/* line growth factor, in characters */ #define LINE_ALLOC_DELTA 85 -#define LINE_DEFAULT 85 -#define MAXCOPYBUF 8192 -#define MAXINPUT (512 + 1) +/* initial line size (not length) */ +#define LINE_DEFAULT 85 +/* maximum size of field in input dialog */ +#define MAXINPUT (512 + 1) /* maximum chars for UTF-8 representations of Unicode chars, per Unicode char */ #define UTF8REPMAX 6 diff --git a/sled.1.in b/sled.1.in @@ -56,6 +56,16 @@ the existing ones instead of being inserted among them. In this mode, moving around the file extends the selection, and most commands that alter text instead delete the selection and exit selection mode. .El +.Pp +.Nm +additionally has three +.Dq modal modes , +which are active as long a modal dialog is shown on screen. +They are: +.Sy help mode , +.Sy input mode +and +.Sy numerical input mode. . .Ss Global key bindings These are active in all modes, and are as follows: diff --git a/sled.c b/sled.c @@ -29,11 +29,12 @@ #define SLED_C_GLOBALS #define ONLY_DEFS +#include "draw.h" +#include "sled.h" #include "config.h" #include "version.h" #include "limit.h" #include "util.h" -#include "draw.h" #undef ONLY_DEFS #include "config.h" @@ -70,31 +71,29 @@ extern const char* prompt_overwrite; extern const char* prompt_reread_dirty; extern const char* program_name; -int clear_selection(struct DrawState* state); -int cp(const char* from, const char* to); -int create_file(struct DrawState* dstate); -char* dirname(char* dir, char* path, const size_t path_size); -int do_search_replace(struct DrawState* state); -int erase_selection(struct DrawState* state); -void get_selection(int* ssr, int* ssc, int* ser, int* sec, - const struct DrawState* state); -void go_to_line_callback(struct DrawState* state); -void handle_key_event(struct tb_event* ev, struct DrawState* state); -int input_insert_char(struct DrawState* state, uint32_t ch); -void input_position_to(struct DrawState* state, const int column); -int insert_char(struct DrawState* state, uint32_t ch); -int load_file(struct DrawState* dstate, long* lineno, long* col); -void position_to(struct DrawState* state, const int row, const int column); -int position_to_selection_start(struct DrawState* state); -int print_error(const int code, const char* msg, ...); -void reread_callback(struct DrawState* state, struct tb_event* ev); -void reread_file(struct DrawState* state); -void quit_callback(struct DrawState* state, struct tb_event* ev); -int save_file(struct DrawState* state); -void search_dialog_callback(struct DrawState* state); -void simple_insert_line(struct DrawState* state); -void simple_join_lines(struct DrawState* state); -void simple_yank(struct DrawState* state); +int +cancel_close(struct DrawState* state) +{ + char* temp = NULL; + + if (!*state->error && !IN(state->mode, M_PROMPT) + && !IN(state->mode, M_HELP)) + { + if (SEL_NOT_EMPTY(state)) + clear_selection(state); + else + MEMCCPY(state->error, errors[ERR_EXIT_KEY], + LINE_DEFAULT, temp); + } + else + { + *state->error = 0; + *state->prompt = 0; + CLEAR(state->mode, M_PROMPT | M_HELP); + } + + return 0; +} int clear_selection(struct DrawState* state) @@ -107,6 +106,8 @@ clear_selection(struct DrawState* state) return 0; } +#define MAXCOPYBUF 8192 + int cp(const char* from, const char* to) { @@ -120,19 +121,19 @@ cp(const char* from, const char* to) fromfd = open(from, O_RDONLY); if (fromfd < 0) - return -1; + return 1; tofd = open(to, O_WRONLY | O_CREAT | O_TRUNC); if (tofd < 0) - return -1; + return 1; if (fstat(fromfd, &st) < 0) { - result = -1; + result = 1; goto cp_cleanup; } if (fchmod(tofd, st.st_mode) < 0) { - result = -1; + result = 1; goto cp_cleanup; } times[0] = st.st_atim; @@ -144,7 +145,7 @@ cp(const char* from, const char* to) nwritten = write(tofd, buf, nread); if (nwritten < nread) { - result = -1; + result = 1; goto cp_cleanup; } result += nwritten; @@ -152,13 +153,13 @@ cp(const char* from, const char* to) if (nread < 0) { - result = -1; + result = 1; goto cp_cleanup; } errno = 0; if (futimens(tofd, times) < 0) - result = -1; + result = 1; cp_cleanup: close(tofd); @@ -200,6 +201,16 @@ create_file(struct DrawState* state) return LOAD_ERR_NONE; } +int +cut_to_paste(struct DrawState* state) +{ + simple_yank(state); + erase_selection(state); + position_to_selection_start(state); + clear_selection(state); + return 0; +} + char* dirname(char* dir, char* path, const size_t dir_size) { @@ -313,7 +324,154 @@ do_search_replace(struct DrawState* state) do_search_replace_error: exit(print_error(ENOMEM, "Memory allocation failed")); /* Unreachable */ - return -1; + return 1; +} + +int +enter_command_mode(struct DrawState* state) +{ + if (!state) + return 1; + SET(state->mode, M_COMMAND); + return 0; +} + +int +enter_insert_mode(struct DrawState* state) +{ + if (!state) + return 1; + CLEAR(state->mode, M_COMMAND); + return 0; +} + +int +erase_backwards(struct DrawState* state) +{ + struct BufferLine* buffer_line = &state->buffer[state->coord.row]; + + if (SEL_NOT_EMPTY(state)) + { + erase_selection(state); + position_to_selection_start(state); + clear_selection(state); + return 0; + } + + if (state->coord.col > 0) + { + size_t i = state->coord.col; + do + { + buffer_line->text[i - 1] = buffer_line->text[i]; + i++; + } while (buffer_line->text[i]); + buffer_line->text[i - 1] = 0; + buffer_line->length--; + position_to(state, state->coord.row, state->coord.col - 1); + state->dirty = 1; + } + else if (state->coord.row > 0) + { + move_up(state); + move_end(state); + erase_forward(state); + } + return 0; +} + +int +erase_forward(struct DrawState* state) +{ + struct BufferLine* buffer_line = &state->buffer[state->coord.row]; + + if (SEL_NOT_EMPTY(state)) + { + erase_selection(state); + position_to_selection_start(state); + clear_selection(state); + return 0; + } + + if (buffer_line->text[state->coord.col]) + { + size_t i = state->coord.col; + while (i + 2 < buffer_line->size && buffer_line->text[i + 1]) + { + buffer_line->text[i] = buffer_line->text[i + 1]; + i++; + } + buffer_line->text[i] = 0; + buffer_line->length--; + state->dirty = 1; + } + else if (state->coord.row < state->rows_count - 1) + { + // struct BufferLine* current = state->buffer + state->coord.row; + if (state->coord.row + 1 == state->rows_count) + goto skip_join_lines; + simple_join_lines(state); + skip_join_lines:; + } + return 0; +} + +int +erase_line(struct DrawState* state) +{ + struct BufferLine* current = NULL; + if (state->rows_count == 1) + { + *state->buffer[0].text = 0; + state->buffer[0].length = 0; + return 0; + } + free(state->buffer[state->coord.row].text); + for (size_t row = state->coord.row; row < state->rows_count - 1; row++) + { + current = state->buffer + row; + current->text = state->buffer[row + 1].text; + current->size = state->buffer[row + 1].size; + current->length = state->buffer[row + 1].length; + } + free_bufferline(&state->buffer[state->rows_count - 1], 0); + if (state->coord.row == state->rows_count - 1) + state->coord.row--; + position_to(state, state->coord.row, 0); + state->rows_count--; + state->dirty = 1; + return 0; +} + +int +erase_prev_word(struct DrawState* state) +{ + int i = state->coord.col; + struct BufferLine* current = NULL; + int d; + + current = state->buffer + state->coord.row; + + if (i > 0 && u32_is_word_boundary(current->text[i - 1], 0)) + i--; + while (i > 0 && u32_is_word_boundary(current->text[i], 0)) + i--; + while (i > 0 && !u32_is_word_boundary(current->text[i], 0) + && !u32_is_word_boundary(current->text[i - 1], 0)) + i--; + + d = state->coord.col - i; + + for (int j = i; j < current->length - d; j++) + current->text[j] = current->text[j + d]; + + current->length -= d; + current->text[current->length] = 0; + state->coord.col = i; + state->display_column + = display_length(state, current->text, state->coord.col); + state->dirty = 1; + return 0; } int @@ -369,6 +527,70 @@ erase_selection(struct DrawState* state) return 0; } +int +erase_to_end(struct DrawState* state) +{ + struct BufferLine* buffer_line = &state->buffer[state->coord.row]; + + if (SEL_NOT_EMPTY(state)) + { + erase_selection(state); + position_to_selection_start(state); + clear_selection(state); + return 0; + } + + buffer_line->text[state->coord.col] = 0; + buffer_line->length = state->coord.col; + move_end(state); + state->dirty = 1; + return 0; +} + +int +erase_to_start(struct DrawState* state) +{ + size_t i = state->coord.col; + struct BufferLine* buffer_line = &state->buffer[state->coord.row]; + size_t len = buffer_line->length; + + if (SEL_NOT_EMPTY(state)) + { + erase_selection(state); + position_to_selection_start(state); + clear_selection(state); + return 0; + } + + do + { + buffer_line->text[i - state->coord.col] = buffer_line->text[i]; + i++; + } while (buffer_line->text[i]); + buffer_line->text[len - state->coord.col] = 0; + buffer_line->length = len - state->coord.col; + move_start(state); + state->dirty = 1; + return 0; +} + +int +exit_program(struct DrawState* state) +{ + char* temp = NULL; + + if (state->dirty) + { + SET(state->mode, M_PROMPT); + MEMCCPY(state->prompt, prompt_dirty, LINE_DEFAULT, temp); + state->prompt_callback = quit_callback; + } + else + state->running = 0; + *state->error = 0; + return 0; +} + void get_selection(int* ssr, int* ssc, int* ser, int* sec, const struct DrawState* state) @@ -419,7 +641,7 @@ get_selection(int* ssr, int* ssc, int* ser, int* sec, } } -void +int go_to_line_callback(struct DrawState* state) { size_t lineno = 0; @@ -428,7 +650,7 @@ go_to_line_callback(struct DrawState* state) char* temp = NULL; if (!state) - return; + return 1; if (*state->input) { @@ -439,7 +661,7 @@ go_to_line_callback(struct DrawState* state) MEMCCPY(state->error, errors[ERR_CANT_ALLOC], LINE_DEFAULT, temp); state->running = 0; - return; + return 1; } unicode_string_to_u8(u8_input, state->input, len + 1); errno = 0; @@ -460,6 +682,7 @@ go_to_line_callback(struct DrawState* state) } if (!*state->error) state->input_callback = NULL; + return 0; } void @@ -585,6 +808,115 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) } int +input_cancel(struct DrawState* state) +{ + if (*state->error) + { + *state->error = 0; + return 0; + } + *state->input = 0; + if (state->input_update_callback) + state->input_update_callback(state); + CLEAR(state->mode, M_INPUT | M_INPUT_NUM); + state->input_callback = NULL; + state->input_update_callback = NULL; + state->chain_dialog = 0; + return 0; +} + +int +input_enter(struct DrawState* state) +{ + int hide_dialog = 1; + + if (state->input_callback) + { + state->input_callback(state); + hide_dialog = (!state->search || !*state->search + || !state->chain_dialog || *state->input != 0); + if (hide_dialog) + { + if (!*state->error) + CLEAR(state->mode, M_INPUT | M_INPUT_NUM); + state->input_callback = NULL; + } + } + else if (hide_dialog) + CLEAR(state->mode, M_INPUT | M_INPUT_NUM); + if (hide_dialog) + state->input_update_callback = NULL; + return 0; +} + +int +input_erase_backwards(struct DrawState* state) +{ + if (state->input_column > 0) + { + size_t i = state->input_column; + do + { + state->input[i - 1] = state->input[i]; + i++; + } while (state->input[i]); + state->input[i - 1] = 0; + input_position_to(state, state->input_column - 1); + if (state->input_update_callback) + state->input_update_callback(state); + } + return 0; +} + +int +input_erase_forward(struct DrawState* state) +{ + if (state->input) + { + size_t i = state->input_column; + while (i + 2 < MAXINPUT && state->input[i + 1]) + { + state->input[i] = state->input[i + 1]; + i++; + } + state->input[i] = 0; + if (state->input_update_callback) + state->input_update_callback(state); + } + return 0; +} + +int +input_erase_to_end(struct DrawState* state) +{ + uint32_t* temp = NULL; + state->input[state->input_column] = 0; + input_move_end(state); + U32_MEMCCPY(state->search, state->input, MAXINPUT, temp); + if (state->input_update_callback) + state->input_update_callback(state); + return 0; +} + +int +input_erase_to_start(struct DrawState* state) +{ + size_t i = state->input_column; + size_t len = u32_strlen(state->input); + + do + { + state->input[i - state->input_column] = state->input[i]; + i++; + } while (state->input[i]); + state->input[len - state->input_column] = 0; + input_move_start(state); + if (state->input_update_callback) + state->input_update_callback(state); + return 0; +} + +int input_insert_char(struct DrawState* state, uint32_t ch) { size_t len = u32_strlen(state->input); @@ -607,6 +939,80 @@ input_insert_char(struct DrawState* state, uint32_t ch) return 0; } +int +input_insert_space(struct DrawState* state) +{ + return input_insert_char(state, ' '); +} + +int +input_insert_tab(struct DrawState* state) +{ + return input_insert_char(state, '\t'); +} + +int +input_move_end(struct DrawState* state) +{ + input_position_to(state, u32_strlen(state->input)); + return 0; +} + +int +input_move_left(struct DrawState* state) +{ + if (state->input_column == 0) + return 0; + input_position_to(state, state->input_column - 1); + return 0; +} + +int +input_move_next_word(struct DrawState* state) +{ + int i = state->input_column; + uint32_t* pline = state->input; + size_t len = u32_strlen(state->input); + + while (i < len && !u32_is_word_boundary(pline[i], 0)) + i++; + while (i < len && u32_is_word_boundary(pline[i], 0)) + i++; + input_position_to(state, i); + return 0; +} + +int +input_move_prev_word(struct DrawState* state) +{ + int i = state->input_column; + uint32_t* pline = state->input; + + if (i > 0 && u32_is_word_boundary(pline[i - 1], 0)) + i--; + while (i > 0 && u32_is_word_boundary(pline[i], 0)) + i--; + while (i > 0 && !u32_is_word_boundary(pline[i], 0) + && !u32_is_word_boundary(pline[i - 1], 0)) + i--; + input_position_to(state, i); + return 0; +} + +int +input_move_right(struct DrawState* state) +{ + input_position_to(state, state->input_column + 1); + return 0; +} + +int +input_move_start(struct DrawState* state) +{ + input_position_to(state, 0); + return 0; +} + void input_position_to(struct DrawState* state, const int column) { @@ -695,7 +1101,61 @@ insert_char(struct DrawState* state, uint32_t ch) return 0; insert_char_error: - return -1; + return 1; +} + +int +insert_line(struct DrawState* state) +{ + struct BufferLine* buffer_line = NULL; + + if (SEL_NOT_EMPTY(state)) + { + erase_selection(state); + position_to_selection_start(state); + clear_selection(state); + } + +#ifdef ENABLE_WRAP + while (state->display_column >= WRAP_WIDTH) + { + int saved_column = state->coord.col; + buffer_line = state->buffer + state->coord.row; + state->coord.col + = buffer_length(state, buffer_line->text, WRAP_WIDTH); + while (state->coord.col > 0 + && !u32_is_word_boundary(buffer_line->text[state->coord.col + - 1], + 1)) + state->coord.col--; + if (state->coord.col == 0) + { + state->coord.col = saved_column; + break; + } + simple_insert_line(state); + state->saved_column = -1; + position_to(state, state->coord.row + 1, + saved_column - state->coord.col); + } +#endif /* ENABLE_WRAP */ + simple_insert_line(state); + state->saved_column = -1; + position_to(state, state->coord.row + 1, 0); + state->dirty = 1; + return 0; +} + +int +insert_space(struct DrawState* state) +{ + return insert_char(state, ' '); +} + +int +insert_tab(struct DrawState* state) +{ + return insert_char(state, '\t'); } int @@ -873,1000 +1333,465 @@ load_file_alloc_error: return LOAD_ERR_CANT_ALLOC; } -/* - * state->saved_column - display coordinate - * row, column - buffer coordinates (not display) - */ -void -position_to(struct DrawState* state, const int row, const int column) +int +move_bottom(struct DrawState* state) { - size_t dlen; + position_to(state, state->rows_count > 1 ? state->rows_count - 1 : 0, + state->coord.col); + return 0; +} - if (row != state->coord.row) - { - state->coord.row = row; - if (state->coord.row > state->rows_count - 1) - state->coord.row = state->rows_count - 1; - if (state->first_shown.row > state->coord.row) - state->first_shown.row = state->coord.row; - if (state->first_shown.row + (state->maxy - 2) - < state->coord.row) - state->first_shown.row - = state->coord.row - (state->maxy - 2); - if (state->saved_column == -1) - { - state->saved_column = state->display_column; - state->coord.col = column; - } - else - state->coord.col = buffer_length(state, - state->buffer[state->coord.row].text, - state->saved_column); - if (state->coord.col < 0) - state->coord.col = 0; - else if (state->coord.col - > (int)state->buffer[state->coord.row].length) - state->coord.col - = state->buffer[state->coord.row].length; - dlen = display_length(state, - state->buffer[state->coord.row].text, state->coord.col); - if (dlen < state->saved_column) - state->display_column = dlen; - else - state->display_column = state->saved_column; - } - else - { - state->coord.col = column; - if (state->coord.col < 0) - { - if (state->coord.row > 0) - { - state->coord.row--; - state->coord.col - = state->buffer[state->coord.row].length; - } - else - state->coord.col = 0; - } - else if (state->coord.col - > (int)state->buffer[state->coord.row].length) - { - if (state->coord.row + 1 < state->rows_count) - { - state->coord.row++; - state->coord.col = 0; - } - else - state->coord.col - = state->buffer[state->coord.row].length; - } - state->display_column = display_length(state, - state->buffer[state->coord.row].text, state->coord.col); - state->saved_column = state->display_column; - } - if (state->first_shown.col > state->display_column) - state->first_shown.col = state->display_column; - if (state->first_shown.col + (state->maxx - 1) < state->display_column) - state->first_shown.col - = state->display_column - (state->maxx - 1); +int +move_down(struct DrawState* state) +{ + position_to(state, state->coord.row + 1, state->coord.col); + return 0; +} - if (IN(state->mode, M_SELECTION)) - assign_coord(&state->sel_end, state->coord); +int +move_end(struct DrawState* state) +{ + position_to(state, state->coord.row, + state->buffer[state->coord.row].length); + return 0; } int -position_to_selection_start(struct DrawState* state) +move_left(struct DrawState* state) { - if (SEL_NOT_EMPTY(state)) - { - int ssr = 0; - int ssc = 0; + position_to(state, state->coord.row, state->coord.col - 1); + return 0; +} - get_selection(&ssr, &ssc, NULL, NULL, state); +int +move_next_word(struct DrawState* state) +{ + int i = state->coord.col; + uint32_t* pline = state->buffer[state->coord.row].text; + size_t len = state->buffer[state->coord.row].length; - state->saved_column - = display_length(state, state->buffer[ssr].text, ssc); - position_to(state, ssr, ssc); - state->saved_column = state->display_column; - } + while (i < len && !u32_is_word_boundary(pline[i], 0)) + i++; + while (i < len && u32_is_word_boundary(pline[i], 0)) + i++; + position_to(state, state->coord.row, i); return 0; } int -print_error(const int code, const char* msg, ...) +move_page_down(struct DrawState* state) { - char buf[LINE_DEFAULT]; - va_list args; - va_start(args, msg); - vsnprintf(buf, LINE_DEFAULT, msg, args); - va_end(args); - fprintf(stderr, "%s: %s\n", program_name, buf); - return code; + if (state->coord.row + state->maxy - 2 < state->rows_count) + position_to(state, state->coord.row + state->maxy - 2, + state->coord.col); + else + move_bottom(state); + return 0; } -void -reread_callback(struct DrawState* state, struct tb_event* ev) +int +move_page_up(struct DrawState* state) { - if (!state) - return; + if (state->coord.row > state->maxy - 2) + position_to(state, state->coord.row - (state->maxy - 2), + state->coord.col); + else + move_top(state); + return 0; +} - if (strchr("Yy", ev->ch)) - reread_file(state); - state->prompt_callback = NULL; +int +move_prev_word(struct DrawState* state) +{ + int i = state->coord.col; + uint32_t* pline = state->buffer[state->coord.row].text; + + if (i > 0 && u32_is_word_boundary(pline[i - 1], 0)) + i--; + while (i > 0 && u32_is_word_boundary(pline[i], 0)) + i--; + while (i > 0 && !u32_is_word_boundary(pline[i], 0) + && !u32_is_word_boundary(pline[i - 1], 0)) + i--; + position_to(state, state->coord.row, i); + return 0; } -void -reread_file(struct DrawState* state) +int +move_right(struct DrawState* state) { - long lineno = 1; - long col = 1; - int result; - char* temp = NULL; + position_to(state, state->coord.row, state->coord.col + 1); + return 0; +} - free_drawstate(state, 1); - init_drawstate(state, state->error, state->action, state->filename, - state->prompt, 1); +int +move_screen_bottom(struct DrawState* state) +{ + if (state->rows_count < state->maxy - 2) + position_to(state, state->rows_count - 1, state->coord.col); + else + { + if (state->first_shown.row + state->maxy - 2 < state->rows_count) + position_to(state, + state->first_shown.row + state->maxy - 2, + state->coord.col); + } + return 0; +} - result = load_file(state, &lineno, &col); - switch (result) +int +move_screen_middle(struct DrawState* state) +{ + if (state->rows_count < state->maxy - 2) + position_to(state, state->rows_count / 2, state->coord.col); + else { - case LOAD_ERR_CANT_ALLOC: - MEMCCPY(state->error, errors[ERR_CANT_ALLOC], LINE_DEFAULT, - temp); - state->running = 0; - break; - case LOAD_ERR_CANT_OPEN_FILE: - MEMCCPY(state->error, errors[ERR_CANT_OPEN], LINE_DEFAULT, temp); - state->running = 0; - break; - default:; + int row = state->first_shown.row + (state->maxy - 2) / 2; + if (state->rows_count - row < (state->maxy - 2) / 2) + row = state->rows_count - (state->maxy - 2) / 2; + position_to(state, row, state->coord.col); } + return 0; } -void -quit_callback(struct DrawState* state, struct tb_event* ev) +int +move_screen_top(struct DrawState* state) { - if (!state) - return; + position_to(state, state->first_shown.row, state->coord.col); + return 0; +} - if (ev->ch == 'y') - write_file(state); - state->prompt_callback = NULL; - state->running = 0; +int +move_start(struct DrawState* state) +{ + position_to(state, state->coord.row, 0); + return 0; } int -save_file(struct DrawState* state) +move_top(struct DrawState* state) { - FILE* output = NULL; - char* buf = NULL; - char* pbuf = NULL; - size_t bufsize = LINE_DEFAULT; -#ifdef CREATE_BACKUPS - struct stat st; -#endif - int result = SAVE_ERR_NONE; - char* temp = NULL; + position_to(state, 0, state->coord.col); + return 0; +} - if (!(buf = calloc(bufsize, 1))) - return SAVE_ERR_CANT_ALLOC; +int +move_up(struct DrawState* state) +{ + position_to(state, state->coord.row > 1 ? state->coord.row - 1 : 0, + state->coord.col); + return 0; +} - MEMCCPY(state->action, actions[ACT_WRITING], LINE_DEFAULT, temp); - redraw_status(state); - errno = 0; +int +next_match(struct DrawState* state) +{ + struct BufferLine* current = NULL; + const uint32_t* found = NULL; + size_t idelta; -#ifdef CREATE_BACKUPS - if (state->new_file) - goto save_skip_backup_copy; + if (!state->search || !*state->search) + return 0; -#ifdef BACKUP_FAIL_OK - if (state->backup_possible) + *state->error = 0; + for (size_t row = state->coord.row; row < state->rows_count; row++) { - if (unlink(state->backup_filename) < 0 && errno != ENOENT) + current = state->buffer + row; + found = current->text; + if (row == state->coord.row) + found += state->coord.col + 1; + found = u32_strstr(found, state->search); + if (found) { - result = SAVE_ERR_CANT_COPY_FILE; - goto save_file_cleanup; + idelta = found - (const uint32_t*)current->text; + state->saved_column + = display_length(state, current->text, idelta); + if (*current->text) + { + position_to(state, row, idelta); + return 0; + } } - errno = 0; - cp(state->rfn_len > 0 ? state->real_filename : state->filename, - state->backup_filename); - errno = 0; /* ignore errors */ } -#else /* !BACKUP_FAIL_OK */ - if (unlink(state->backup_filename) < 0 && errno != ENOENT) + return 0; +} + +int +paste_from_paste(struct DrawState* state) +{ + uint32_t* newtext = NULL; + uint32_t* ptext = NULL; + size_t newsize = 0; + struct BufferLine* line = NULL; + struct BufferLine* newbuf = NULL; + uint32_t* rest = NULL; + size_t rest_size = 0; + struct Coord newpos; + char* temp = NULL; + + if (!state->paste_count) + return 0; + + if (SEL_NOT_EMPTY(state)) { - result = SAVE_ERR_CANT_COPY_FILE; - goto save_file_cleanup; + erase_selection(state); + position_to_selection_start(state); + clear_selection(state); } - if (cp(state->rfn_len > 0 ? state->real_filename : state->filename, - state->backup_filename) - == -1) + if (state->paste_count > 1) { - result = SAVE_ERR_CANT_COPY_FILE; - goto save_file_cleanup; + if (state->rows_count + state->paste_count > state->buffer_size) + { + size_t old_size = state->buffer_size; + while (state->rows_count + state->paste_count + > state->buffer_size) + state->buffer_size += ALLOC_DELTA; + newbuf = realloc(state->buffer, + state->buffer_size * sizeof(struct BufferLine)); + for (size_t row = old_size; row < state->buffer_size; + row++) + { + init_bufferline(newbuf + row); + newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); + newbuf[row].size = LINE_DEFAULT; + newbuf[row].text = newtext; + } + state->buffer = newbuf; + } } -#endif /* BACKUP_FAIL_OK */ -#endif /* CREATE_BACKUPS */ -save_skip_backup_copy: - if (!(output = fopen(state->rfn_len > 0 ? state->real_filename - : state->filename, - "w"))) + line = state->buffer + state->coord.row; + newsize = line->length + state->paste[0].length + 1; + newtext = calloc(newsize, sizeof(uint32_t)); + if (!newtext) { - result = SAVE_ERR_CANT_OPEN_FILE; - goto save_file_cleanup; + state->running = 0; + MEMCCPY(state->error, errors[ERR_CANT_ALLOC], LINE_DEFAULT, + temp); + goto paste_from_paste_cleanup; } + U32_MEMCCPY(newtext, line->text, state->coord.col + 1, ptext); + U32_MEMCCPY((newtext + state->coord.col), state->paste[0].text, newsize, + ptext); -#ifdef CREATE_BACKUPS - if (state->new_file) - goto save_skip_backup_symlink; + assign_coord(&newpos, state->coord); - if (lstat(state->rfn_len > 0 ? state->real_filename : state->filename, - &st) - < 0) + if (state->paste_count == 1) { - result = SAVE_ERR_CANT_STAT_FILE; - goto save_file_cleanup; + if (ptext) + U32_MEMCCPY_EXT((ptext - 1), newtext, + line->text + state->coord.col, + line->length - state->coord.col, newsize, + ptext); + U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); + newpos.col += state->paste[0].length; } -#ifdef BACKUP_FAIL_OK - if (state->backup_possible) + else { - if (state->rfn_len > 0) + state->rows_count += state->paste_count - 1; + rest_size = line->size - state->coord.col; + rest = calloc(rest_size, sizeof(uint32_t)); + if (!rest) { - if (unlink(state->filename) == -1) - { - result = SAVE_ERR_CANT_UNLINK; - goto save_file_cleanup; - } - if (symlink(state->real_filename, state->filename) == -1) - { - result = SAVE_ERR_CANT_SYMLINK; - goto save_file_cleanup; - } + state->running = 0; + MEMCCPY(state->error, errors[ERR_CANT_ALLOC], + LINE_DEFAULT, temp); + goto paste_from_paste_cleanup; } - chmod(state->rfn_len > 0 ? state->real_filename - : state->filename, - st.st_mode); - } -#else /* !BACKUP_FAIL_OK */ - if (state->rfn_len > 0) - { - if (unlink(state->filename) == -1) + U32_MEMCCPY(rest, line->text + state->coord.col, rest_size, + ptext); + U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); + for (size_t row = state->rows_count - state->coord.row + - (state->paste_count - 2) - 1; + row > 0; row--) { - result = SAVE_ERR_CANT_UNLINK; - goto save_file_cleanup; + struct BufferLine* copied_line + = &state->buffer[state->coord.row + row - 1]; + + line = state->buffer + state->coord.row + + state->paste_count - 2 + row; + U32_SAFE_COPY(line->text, ptext, line->size, + copied_line->text, copied_line->size, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); } - if (symlink(state->real_filename, state->filename) == -1) + line = state->buffer + state->coord.row; + U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); + for (size_t row = 1; row < state->paste_count - 1; row++) { - result = SAVE_ERR_CANT_SYMLINK; - goto save_file_cleanup; + line = state->buffer + state->coord.row + row; + U32_SAFE_COPY(line->text, ptext, line->size, + state->paste[row].text, state->paste[row].size, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); } + line = state->buffer + state->coord.row + state->paste_count + - 1; + U32_SAFE_COPY(newtext, ptext, newsize, + state->paste[state->paste_count - 1].text, + state->paste[state->paste_count - 1].size + rest_size, + paste_from_paste_cleanup); + if (ptext) + U32_MEMCCPY_EXT((ptext - 1), newtext, rest, + newsize - (ptext - newtext - 1), newsize, + ptext); + U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, + paste_from_paste_cleanup); + line->length = u32_strlen(line->text); + newpos.col = state->paste[state->paste_count - 1].length; + newpos.row += state->paste_count - 1; + state->saved_column = display_length(state, + state->paste[state->paste_count - 1].text, newpos.col); } - chmod(state->rfn_len > 0 ? state->real_filename : state->filename, - st.st_mode); -#endif /* BACKUP_FAIL_OK */ -#endif /* CREATE_BACKUPS */ - -save_skip_backup_symlink: - if (state->rows_count == 1 && state->buffer[0].length == 0) - goto save_file_cleanup; - - for (size_t row = 0; row < state->rows_count; row++) - { - ENSURE_SIZE(buf, pbuf, bufsize, - UTF8REPMAX * state->buffer[row].length + 1, - UTF8REPMAX * state->buffer[row].length - + LINE_ALLOC_DELTA, - save_file_alloc_error, char); - unicode_string_to_u8(buf, state->buffer[row].text, bufsize); - fprintf(output, "%s\n", buf); - } - - state->new_file = 0; - goto save_file_cleanup; -save_file_alloc_error: - result = SAVE_ERR_CANT_ALLOC; - -save_file_cleanup: - fclose(output); - free(buf); - *state->action = 0; - return result; -} + position_to(state, newpos.row, newpos.col); + state->dirty = 1; -void -search_dialog_callback(struct DrawState* state) -{ - next_match(state); - if (!*state->error) - state->input_callback = NULL; +paste_from_paste_cleanup: + free(rest); + free(newtext); + return 0; } +/* + * state->saved_column - display coordinate + * row, column - buffer coordinates (not display) + */ void -search_replace_search_dialog_callback(struct DrawState* state) +position_to(struct DrawState* state, const int row, const int column) { - /*next_match(state);*/ - if (!*state->error) - state->input_callback = NULL; - *state->input = 0; - show_replace(state); -} + size_t dlen; -void -search_replace_replace_dialog_callback(struct DrawState* state) -{ - do_search_replace(state); - *state->search = 0; - /*u32_strcpy(state->search, state->input); - next_match(state);*/ - if (!*state->error) - state->input_callback = NULL; - state->chain_dialog = 0; -} - -void -search_update_callback(struct DrawState* state) -{ - uint32_t* temp = NULL; - U32_MEMCCPY(state->search, state->input, MAXINPUT, temp); -} - -int -cancel_close(struct DrawState* state) -{ - char* temp = NULL; - - if (!*state->error && !IN(state->mode, M_PROMPT) - && !IN(state->mode, M_HELP)) - { - if (SEL_NOT_EMPTY(state)) - clear_selection(state); - else - MEMCCPY(state->error, errors[ERR_EXIT_KEY], - LINE_DEFAULT, temp); - } - else - { - *state->error = 0; - *state->prompt = 0; - CLEAR(state->mode, M_PROMPT | M_HELP); - } - - return 0; -} - -int -cut_to_paste(struct DrawState* state) -{ - simple_yank(state); - erase_selection(state); - position_to_selection_start(state); - clear_selection(state); - return 0; -} - -int -enter_command_mode(struct DrawState* state) -{ - if (!state) - return 1; - SET(state->mode, M_COMMAND); - return 0; -} - -int -enter_insert_mode(struct DrawState* state) -{ - if (!state) - return 1; - CLEAR(state->mode, M_COMMAND); - return 0; -} - -int -erase_backwards(struct DrawState* state) -{ - struct BufferLine* buffer_line = &state->buffer[state->coord.row]; - - if (SEL_NOT_EMPTY(state)) - { - erase_selection(state); - position_to_selection_start(state); - clear_selection(state); - return 0; - } - - if (state->coord.col > 0) - { - size_t i = state->coord.col; - do - { - buffer_line->text[i - 1] = buffer_line->text[i]; - i++; - } while (buffer_line->text[i]); - buffer_line->text[i - 1] = 0; - buffer_line->length--; - position_to(state, state->coord.row, state->coord.col - 1); - state->dirty = 1; - } - else if (state->coord.row > 0) - { - move_up(state); - move_end(state); - erase_forward(state); - } - return 0; -} - -int -erase_forward(struct DrawState* state) -{ - struct BufferLine* buffer_line = &state->buffer[state->coord.row]; - - if (SEL_NOT_EMPTY(state)) - { - erase_selection(state); - position_to_selection_start(state); - clear_selection(state); - return 0; - } - - if (buffer_line->text[state->coord.col]) - { - size_t i = state->coord.col; - while (i + 2 < buffer_line->size && buffer_line->text[i + 1]) - { - buffer_line->text[i] = buffer_line->text[i + 1]; - i++; - } - buffer_line->text[i] = 0; - buffer_line->length--; - state->dirty = 1; - } - else if (state->coord.row < state->rows_count - 1) - { - // struct BufferLine* current = state->buffer + state->coord.row; - if (state->coord.row + 1 == state->rows_count) - goto skip_join_lines; - simple_join_lines(state); - skip_join_lines:; - } - return 0; -} - -int -erase_line(struct DrawState* state) -{ - struct BufferLine* current = NULL; - if (state->rows_count == 1) - { - *state->buffer[0].text = 0; - state->buffer[0].length = 0; - return 0; - } - free(state->buffer[state->coord.row].text); - for (size_t row = state->coord.row; row < state->rows_count - 1; row++) - { - current = state->buffer + row; - current->text = state->buffer[row + 1].text; - current->size = state->buffer[row + 1].size; - current->length = state->buffer[row + 1].length; - } - free_bufferline(&state->buffer[state->rows_count - 1], 0); - if (state->coord.row == state->rows_count - 1) - state->coord.row--; - position_to(state, state->coord.row, 0); - state->rows_count--; - state->dirty = 1; - return 0; -} - -int -erase_prev_word(struct DrawState* state) -{ - int i = state->coord.col; - struct BufferLine* current = NULL; - int d; - - current = state->buffer + state->coord.row; - - if (i > 0 && u32_is_word_boundary(current->text[i - 1], 0)) - i--; - while (i > 0 && u32_is_word_boundary(current->text[i], 0)) - i--; - while (i > 0 && !u32_is_word_boundary(current->text[i], 0) - && !u32_is_word_boundary(current->text[i - 1], 0)) - i--; - - d = state->coord.col - i; - - for (int j = i; j < current->length - d; j++) - current->text[j] = current->text[j + d]; - - current->length -= d; - current->text[current->length] = 0; - state->coord.col = i; - state->display_column - = display_length(state, current->text, state->coord.col); - state->dirty = 1; - return 0; -} - -int -erase_to_end(struct DrawState* state) -{ - struct BufferLine* buffer_line = &state->buffer[state->coord.row]; - - if (SEL_NOT_EMPTY(state)) - { - erase_selection(state); - position_to_selection_start(state); - clear_selection(state); - return 0; - } - - buffer_line->text[state->coord.col] = 0; - buffer_line->length = state->coord.col; - move_end(state); - state->dirty = 1; - return 0; -} - -int -erase_to_start(struct DrawState* state) -{ - size_t i = state->coord.col; - struct BufferLine* buffer_line = &state->buffer[state->coord.row]; - size_t len = buffer_line->length; - - if (SEL_NOT_EMPTY(state)) - { - erase_selection(state); - position_to_selection_start(state); - clear_selection(state); - return 0; - } - - do - { - buffer_line->text[i - state->coord.col] = buffer_line->text[i]; - i++; - } while (buffer_line->text[i]); - buffer_line->text[len - state->coord.col] = 0; - buffer_line->length = len - state->coord.col; - move_start(state); - state->dirty = 1; - return 0; -} - -int -exit_program(struct DrawState* state) -{ - char* temp = NULL; - - if (state->dirty) - { - SET(state->mode, M_PROMPT); - MEMCCPY(state->prompt, prompt_dirty, LINE_DEFAULT, temp); - state->prompt_callback = quit_callback; - } - else - state->running = 0; - *state->error = 0; - return 0; -} - -int -input_cancel(struct DrawState* state) -{ - if (*state->error) - { - *state->error = 0; - return 0; - } - *state->input = 0; - if (state->input_update_callback) - state->input_update_callback(state); - CLEAR(state->mode, M_INPUT | M_INPUT_NUM); - state->input_callback = NULL; - state->input_update_callback = NULL; - state->chain_dialog = 0; - return 0; -} - -int -input_enter(struct DrawState* state) -{ - int hide_dialog = 1; - - if (state->input_callback) - { - state->input_callback(state); - hide_dialog = (!state->search || !*state->search - || !state->chain_dialog || *state->input != 0); - if (hide_dialog) - { - if (!*state->error) - CLEAR(state->mode, M_INPUT | M_INPUT_NUM); - state->input_callback = NULL; - } - } - else if (hide_dialog) - CLEAR(state->mode, M_INPUT | M_INPUT_NUM); - if (hide_dialog) - state->input_update_callback = NULL; - return 0; -} - -int -input_erase_backwards(struct DrawState* state) -{ - if (state->input_column > 0) - { - size_t i = state->input_column; - do - { - state->input[i - 1] = state->input[i]; - i++; - } while (state->input[i]); - state->input[i - 1] = 0; - input_position_to(state, state->input_column - 1); - if (state->input_update_callback) - state->input_update_callback(state); - } - return 0; -} - -int -input_erase_forward(struct DrawState* state) -{ - if (state->input) - { - size_t i = state->input_column; - while (i + 2 < MAXINPUT && state->input[i + 1]) - { - state->input[i] = state->input[i + 1]; - i++; - } - state->input[i] = 0; - if (state->input_update_callback) - state->input_update_callback(state); - } - return 0; -} - -int -input_erase_to_end(struct DrawState* state) -{ - uint32_t* temp = NULL; - state->input[state->input_column] = 0; - input_move_end(state); - U32_MEMCCPY(state->search, state->input, MAXINPUT, temp); - if (state->input_update_callback) - state->input_update_callback(state); - return 0; -} - -int -input_erase_to_start(struct DrawState* state) -{ - size_t i = state->input_column; - size_t len = u32_strlen(state->input); - - do - { - state->input[i - state->input_column] = state->input[i]; - i++; - } while (state->input[i]); - state->input[len - state->input_column] = 0; - input_move_start(state); - if (state->input_update_callback) - state->input_update_callback(state); - return 0; -} - -int -input_insert_space(struct DrawState* state) -{ - return input_insert_char(state, ' '); -} - -int -input_insert_tab(struct DrawState* state) -{ - return input_insert_char(state, '\t'); -} - -int -input_move_end(struct DrawState* state) -{ - input_position_to(state, u32_strlen(state->input)); - return 0; -} - -int -input_move_left(struct DrawState* state) -{ - if (state->input_column == 0) - return 0; - input_position_to(state, state->input_column - 1); - return 0; -} - -int -input_move_right(struct DrawState* state) -{ - input_position_to(state, state->input_column + 1); - return 0; -} - -int -input_move_start(struct DrawState* state) -{ - input_position_to(state, 0); - return 0; -} - -int -input_move_prev_word(struct DrawState* state) -{ - int i = state->input_column; - uint32_t* pline = state->input; - - if (i > 0 && u32_is_word_boundary(pline[i - 1], 0)) - i--; - while (i > 0 && u32_is_word_boundary(pline[i], 0)) - i--; - while (i > 0 && !u32_is_word_boundary(pline[i], 0) - && !u32_is_word_boundary(pline[i - 1], 0)) - i--; - input_position_to(state, i); - return 0; -} - -int -input_move_next_word(struct DrawState* state) -{ - int i = state->input_column; - uint32_t* pline = state->input; - size_t len = u32_strlen(state->input); - - while (i < len && !u32_is_word_boundary(pline[i], 0)) - i++; - while (i < len && u32_is_word_boundary(pline[i], 0)) - i++; - input_position_to(state, i); - return 0; -} - -int -insert_line(struct DrawState* state) -{ - struct BufferLine* buffer_line = NULL; - - if (SEL_NOT_EMPTY(state)) - { - erase_selection(state); - position_to_selection_start(state); - clear_selection(state); - } - -#ifdef ENABLE_WRAP - while (state->display_column >= WRAP_WIDTH) + if (row != state->coord.row) { - int saved_column = state->coord.col; - buffer_line = state->buffer + state->coord.row; - state->coord.col - = buffer_length(state, buffer_line->text, WRAP_WIDTH); - while (state->coord.col > 0 - && !u32_is_word_boundary(buffer_line->text[state->coord.col - - 1], - 1)) - state->coord.col--; - if (state->coord.col == 0) + state->coord.row = row; + if (state->coord.row > state->rows_count - 1) + state->coord.row = state->rows_count - 1; + if (state->first_shown.row > state->coord.row) + state->first_shown.row = state->coord.row; + if (state->first_shown.row + (state->maxy - 2) + < state->coord.row) + state->first_shown.row + = state->coord.row - (state->maxy - 2); + if (state->saved_column == -1) { - state->coord.col = saved_column; - break; + state->saved_column = state->display_column; + state->coord.col = column; } - simple_insert_line(state); - state->saved_column = -1; - position_to(state, state->coord.row + 1, - saved_column - state->coord.col); - } -#endif /* ENABLE_WRAP */ - simple_insert_line(state); - state->saved_column = -1; - position_to(state, state->coord.row + 1, 0); - state->dirty = 1; - return 0; -} - -int -insert_space(struct DrawState* state) -{ - return insert_char(state, ' '); -} - -int -insert_tab(struct DrawState* state) -{ - return insert_char(state, '\t'); -} - -int -move_bottom(struct DrawState* state) -{ - position_to(state, state->rows_count > 1 ? state->rows_count - 1 : 0, - state->coord.col); - return 0; -} - -int -move_down(struct DrawState* state) -{ - position_to(state, state->coord.row + 1, state->coord.col); - return 0; -} - -int -move_end(struct DrawState* state) -{ - position_to(state, state->coord.row, - state->buffer[state->coord.row].length); - return 0; -} - -int -move_left(struct DrawState* state) -{ - position_to(state, state->coord.row, state->coord.col - 1); - return 0; -} - -int -move_next_word(struct DrawState* state) -{ - int i = state->coord.col; - uint32_t* pline = state->buffer[state->coord.row].text; - size_t len = state->buffer[state->coord.row].length; - - while (i < len && !u32_is_word_boundary(pline[i], 0)) - i++; - while (i < len && u32_is_word_boundary(pline[i], 0)) - i++; - position_to(state, state->coord.row, i); - return 0; -} - -int -move_page_down(struct DrawState* state) -{ - if (state->coord.row + state->maxy - 2 < state->rows_count) - position_to(state, state->coord.row + state->maxy - 2, - state->coord.col); - else - move_bottom(state); - return 0; -} - -int -move_page_up(struct DrawState* state) -{ - if (state->coord.row > state->maxy - 2) - position_to(state, state->coord.row - (state->maxy - 2), - state->coord.col); - else - move_top(state); - return 0; -} - -int -move_prev_word(struct DrawState* state) -{ - int i = state->coord.col; - uint32_t* pline = state->buffer[state->coord.row].text; - - if (i > 0 && u32_is_word_boundary(pline[i - 1], 0)) - i--; - while (i > 0 && u32_is_word_boundary(pline[i], 0)) - i--; - while (i > 0 && !u32_is_word_boundary(pline[i], 0) - && !u32_is_word_boundary(pline[i - 1], 0)) - i--; - position_to(state, state->coord.row, i); - return 0; -} - -int -move_right(struct DrawState* state) -{ - position_to(state, state->coord.row, state->coord.col + 1); - return 0; -} - -int -move_screen_bottom(struct DrawState* state) -{ - if (state->rows_count < state->maxy - 2) - position_to(state, state->rows_count - 1, state->coord.col); - else - { - if (state->first_shown.row + state->maxy - 2 < state->rows_count) - position_to(state, - state->first_shown.row + state->maxy - 2, - state->coord.col); - } - return 0; -} - -int -move_screen_middle(struct DrawState* state) -{ - if (state->rows_count < state->maxy - 2) - position_to(state, state->rows_count / 2, state->coord.col); + else + state->coord.col = buffer_length(state, + state->buffer[state->coord.row].text, + state->saved_column); + if (state->coord.col < 0) + state->coord.col = 0; + else if (state->coord.col + > (int)state->buffer[state->coord.row].length) + state->coord.col + = state->buffer[state->coord.row].length; + dlen = display_length(state, + state->buffer[state->coord.row].text, state->coord.col); + if (dlen < state->saved_column) + state->display_column = dlen; + else + state->display_column = state->saved_column; + } else { - int row = state->first_shown.row + (state->maxy - 2) / 2; - if (state->rows_count - row < (state->maxy - 2) / 2) - row = state->rows_count - (state->maxy - 2) / 2; - position_to(state, row, state->coord.col); + state->coord.col = column; + if (state->coord.col < 0) + { + if (state->coord.row > 0) + { + state->coord.row--; + state->coord.col + = state->buffer[state->coord.row].length; + } + else + state->coord.col = 0; + } + else if (state->coord.col + > (int)state->buffer[state->coord.row].length) + { + if (state->coord.row + 1 < state->rows_count) + { + state->coord.row++; + state->coord.col = 0; + } + else + state->coord.col + = state->buffer[state->coord.row].length; + } + state->display_column = display_length(state, + state->buffer[state->coord.row].text, state->coord.col); + state->saved_column = state->display_column; } - return 0; -} + if (state->first_shown.col > state->display_column) + state->first_shown.col = state->display_column; + if (state->first_shown.col + (state->maxx - 1) < state->display_column) + state->first_shown.col + = state->display_column - (state->maxx - 1); -int -move_screen_top(struct DrawState* state) -{ - position_to(state, state->first_shown.row, state->coord.col); - return 0; + if (IN(state->mode, M_SELECTION)) + assign_coord(&state->sel_end, state->coord); } int -move_start(struct DrawState* state) +position_to_selection_start(struct DrawState* state) { - position_to(state, state->coord.row, 0); - return 0; -} + if (SEL_NOT_EMPTY(state)) + { + int ssr = 0; + int ssc = 0; -int -move_top(struct DrawState* state) -{ - position_to(state, 0, state->coord.col); - return 0; -} + get_selection(&ssr, &ssc, NULL, NULL, state); -int -move_up(struct DrawState* state) -{ - position_to(state, state->coord.row > 1 ? state->coord.row - 1 : 0, - state->coord.col); + state->saved_column + = display_length(state, state->buffer[ssr].text, ssc); + position_to(state, ssr, ssc); + state->saved_column = state->display_column; + } return 0; } int -next_match(struct DrawState* state) +prev_match(struct DrawState* state) { struct BufferLine* current = NULL; const uint32_t* found = NULL; size_t idelta; + size_t row; if (!state->search || !*state->search) return 0; *state->error = 0; - for (size_t row = state->coord.row; row < state->rows_count; row++) + + row = state->coord.row; + while (row >= 0) { + const uint32_t* save_found; current = state->buffer + row; found = current->text; + if (!found) + return 0; if (row == state->coord.row) - found += state->coord.col + 1; - found = u32_strstr(found, state->search); + found += state->coord.col; + else + found += current->length; + + prev_match_loop: + save_found = found; + found = u32_strrstr(current->text, state->search, found); if (found) { - idelta = found - (const uint32_t*)current->text; + idelta = found - current->text; state->saved_column = display_length(state, current->text, idelta); if (*current->text) @@ -1875,221 +1800,282 @@ next_match(struct DrawState* state) return 0; } } + if (save_found == current->text) + goto prev_match_end; + found = save_found - 1; + goto prev_match_loop; + prev_match_end: + if (!row) + break; + row--; } return 0; } int -paste_from_paste(struct DrawState* state) +print_error(const int code, const char* msg, ...) { - uint32_t* newtext = NULL; - uint32_t* ptext = NULL; - size_t newsize = 0; - struct BufferLine* line = NULL; - struct BufferLine* newbuf = NULL; - uint32_t* rest = NULL; - size_t rest_size = 0; - struct Coord newpos; + char buf[LINE_DEFAULT]; + va_list args; + va_start(args, msg); + vsnprintf(buf, LINE_DEFAULT, msg, args); + va_end(args); + fprintf(stderr, "%s: %s\n", program_name, buf); + return code; +} + +int +quit_callback(struct DrawState* state, struct tb_event* ev) +{ + if (!state) + return 1; + + if (ev->ch == 'y') + if (write_file(state)) + return 1; + state->prompt_callback = NULL; + state->running = 0; + return 0; +} + +int +reread_callback(struct DrawState* state, struct tb_event* ev) +{ + if (!state) + return 1; + + if (strchr("Yy", ev->ch)) + if (reread_file(state)) + return 1; + state->prompt_callback = NULL; + return 0; +} + +int +reread_file(struct DrawState* state) +{ + long lineno = 1; + long col = 1; + int result; char* temp = NULL; - if (!state->paste_count) - return 0; + free_drawstate(state, 1); + init_drawstate(state, state->error, state->action, state->filename, + state->prompt, 1); - if (SEL_NOT_EMPTY(state)) + result = load_file(state, &lineno, &col); + switch (result) { - erase_selection(state); - position_to_selection_start(state); - clear_selection(state); + case LOAD_ERR_CANT_ALLOC: + MEMCCPY(state->error, errors[ERR_CANT_ALLOC], LINE_DEFAULT, + temp); + state->running = 0; + break; + case LOAD_ERR_CANT_OPEN_FILE: + MEMCCPY(state->error, errors[ERR_CANT_OPEN], LINE_DEFAULT, temp); + state->running = 0; + break; + default:; } + return result; +} - if (state->paste_count > 1) +int +reread_or_callback(struct DrawState* state) +{ + char* temp = NULL; + + if (!state) + return 1; + if (state->dirty) { - if (state->rows_count + state->paste_count > state->buffer_size) + SET(state->mode, M_PROMPT); + MEMCCPY(state->prompt, prompt_reread_dirty, LINE_DEFAULT, temp); + state->prompt_callback = reread_callback; + } + else + reread_file(state); + return 0; +} + +int +save_file(struct DrawState* state) +{ + FILE* output = NULL; + char* buf = NULL; + char* pbuf = NULL; + size_t bufsize = LINE_DEFAULT; +#ifdef CREATE_BACKUPS + struct stat st; +#endif + int result = SAVE_ERR_NONE; + char* temp = NULL; + + if (!(buf = calloc(bufsize, 1))) + return SAVE_ERR_CANT_ALLOC; + + MEMCCPY(state->action, actions[ACT_WRITING], LINE_DEFAULT, temp); + redraw_status(state); + errno = 0; + +#ifdef CREATE_BACKUPS + if (state->new_file) + goto save_skip_backup_copy; + +#ifdef BACKUP_FAIL_OK + if (state->backup_possible) + { + if (unlink(state->backup_filename) < 0 && errno != ENOENT) { - size_t old_size = state->buffer_size; - while (state->rows_count + state->paste_count - > state->buffer_size) - state->buffer_size += ALLOC_DELTA; - newbuf = realloc(state->buffer, - state->buffer_size * sizeof(struct BufferLine)); - for (size_t row = old_size; row < state->buffer_size; - row++) - { - init_bufferline(newbuf + row); - newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); - newbuf[row].size = LINE_DEFAULT; - newbuf[row].text = newtext; - } - state->buffer = newbuf; + result = SAVE_ERR_CANT_COPY_FILE; + goto save_file_cleanup; } + errno = 0; + cp(state->rfn_len > 0 ? state->real_filename : state->filename, + state->backup_filename); + errno = 0; /* ignore errors */ + } +#else /* !BACKUP_FAIL_OK */ + if (unlink(state->backup_filename) < 0 && errno != ENOENT) + { + result = SAVE_ERR_CANT_COPY_FILE; + goto save_file_cleanup; } - line = state->buffer + state->coord.row; - newsize = line->length + state->paste[0].length + 1; - newtext = calloc(newsize, sizeof(uint32_t)); - if (!newtext) + if (cp(state->rfn_len > 0 ? state->real_filename : state->filename, + state->backup_filename)) + { + result = SAVE_ERR_CANT_COPY_FILE; + goto save_file_cleanup; + } +#endif /* BACKUP_FAIL_OK */ +#endif /* CREATE_BACKUPS */ + +save_skip_backup_copy: + if (!(output = fopen(state->rfn_len > 0 ? state->real_filename + : state->filename, + "w"))) { - state->running = 0; - MEMCCPY(state->error, errors[ERR_CANT_ALLOC], LINE_DEFAULT, - temp); - goto paste_from_paste_cleanup; + result = SAVE_ERR_CANT_OPEN_FILE; + goto save_file_cleanup; } - U32_MEMCCPY(newtext, line->text, state->coord.col + 1, ptext); - U32_MEMCCPY((newtext + state->coord.col), state->paste[0].text, newsize, - ptext); - assign_coord(&newpos, state->coord); +#ifdef CREATE_BACKUPS + if (state->new_file) + goto save_skip_backup_symlink; - if (state->paste_count == 1) + if (lstat(state->rfn_len > 0 ? state->real_filename : state->filename, + &st) + < 0) { - if (ptext) - U32_MEMCCPY_EXT((ptext - 1), newtext, - line->text + state->coord.col, - line->length - state->coord.col, newsize, - ptext); - U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, - paste_from_paste_cleanup); - line->length = u32_strlen(line->text); - newpos.col += state->paste[0].length; + result = SAVE_ERR_CANT_STAT_FILE; + goto save_file_cleanup; } - else +#ifdef BACKUP_FAIL_OK + if (state->backup_possible) { - state->rows_count += state->paste_count - 1; - rest_size = line->size - state->coord.col; - rest = calloc(rest_size, sizeof(uint32_t)); - if (!rest) + if (state->rfn_len > 0) { - state->running = 0; - MEMCCPY(state->error, errors[ERR_CANT_ALLOC], - LINE_DEFAULT, temp); - goto paste_from_paste_cleanup; + if (unlink(state->filename) == -1) + { + result = SAVE_ERR_CANT_UNLINK; + goto save_file_cleanup; + } + if (symlink(state->real_filename, state->filename) == -1) + { + result = SAVE_ERR_CANT_SYMLINK; + goto save_file_cleanup; + } } - U32_MEMCCPY(rest, line->text + state->coord.col, rest_size, - ptext); - U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, - paste_from_paste_cleanup); - line->length = u32_strlen(line->text); - for (size_t row = state->rows_count - state->coord.row - - (state->paste_count - 2) - 1; - row > 0; row--) + chmod(state->rfn_len > 0 ? state->real_filename + : state->filename, + st.st_mode); + } +#else /* !BACKUP_FAIL_OK */ + if (state->rfn_len > 0) + { + if (unlink(state->filename) == -1) { - struct BufferLine* copied_line - = &state->buffer[state->coord.row + row - 1]; - - line = state->buffer + state->coord.row - + state->paste_count - 2 + row; - U32_SAFE_COPY(line->text, ptext, line->size, - copied_line->text, copied_line->size, - paste_from_paste_cleanup); - line->length = u32_strlen(line->text); + result = SAVE_ERR_CANT_UNLINK; + goto save_file_cleanup; } - line = state->buffer + state->coord.row; - U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, - paste_from_paste_cleanup); - line->length = u32_strlen(line->text); - for (size_t row = 1; row < state->paste_count - 1; row++) + if (symlink(state->real_filename, state->filename) == -1) { - line = state->buffer + state->coord.row + row; - U32_SAFE_COPY(line->text, ptext, line->size, - state->paste[row].text, state->paste[row].size, - paste_from_paste_cleanup); - line->length = u32_strlen(line->text); + result = SAVE_ERR_CANT_SYMLINK; + goto save_file_cleanup; } - line = state->buffer + state->coord.row + state->paste_count - - 1; - U32_SAFE_COPY(newtext, ptext, newsize, - state->paste[state->paste_count - 1].text, - state->paste[state->paste_count - 1].size + rest_size, - paste_from_paste_cleanup); - if (ptext) - U32_MEMCCPY_EXT((ptext - 1), newtext, rest, - newsize - (ptext - newtext - 1), newsize, - ptext); - U32_SAFE_COPY(line->text, ptext, line->size, newtext, newsize, - paste_from_paste_cleanup); - line->length = u32_strlen(line->text); - newpos.col = state->paste[state->paste_count - 1].length; - newpos.row += state->paste_count - 1; - state->saved_column = display_length(state, - state->paste[state->paste_count - 1].text, newpos.col); } + chmod(state->rfn_len > 0 ? state->real_filename : state->filename, + st.st_mode); +#endif /* BACKUP_FAIL_OK */ +#endif /* CREATE_BACKUPS */ - position_to(state, newpos.row, newpos.col); - state->dirty = 1; +save_skip_backup_symlink: + if (state->rows_count == 1 && state->buffer[0].length == 0) + goto save_file_cleanup; -paste_from_paste_cleanup: - free(rest); - free(newtext); - return 0; -} + for (size_t row = 0; row < state->rows_count; row++) + { + ENSURE_SIZE(buf, pbuf, bufsize, + UTF8REPMAX * state->buffer[row].length + 1, + UTF8REPMAX * state->buffer[row].length + + LINE_ALLOC_DELTA, + save_file_alloc_error, char); + unicode_string_to_u8(buf, state->buffer[row].text, bufsize); + fprintf(output, "%s\n", buf); + } -int -prev_match(struct DrawState* state) -{ - struct BufferLine* current = NULL; - const uint32_t* found = NULL; - size_t idelta; - size_t row; + state->new_file = 0; + goto save_file_cleanup; - if (!state->search || !*state->search) - return 0; +save_file_alloc_error: + result = SAVE_ERR_CANT_ALLOC; - *state->error = 0; +save_file_cleanup: + fclose(output); + free(buf); + *state->action = 0; + return result; +} - row = state->coord.row; - while (row >= 0) - { - const uint32_t* save_found; - current = state->buffer + row; - found = current->text; - if (!found) - return 0; - if (row == state->coord.row) - found += state->coord.col; - else - found += current->length; +int +search_dialog_callback(struct DrawState* state) +{ + next_match(state); + if (!*state->error) + state->input_callback = NULL; + return 0; +} - prev_match_loop: - save_found = found; - found = u32_strrstr(current->text, state->search, found); - if (found) - { - idelta = found - current->text; - state->saved_column - = display_length(state, current->text, idelta); - if (*current->text) - { - position_to(state, row, idelta); - return 0; - } - } - if (save_found == current->text) - goto prev_match_end; - found = save_found - 1; - goto prev_match_loop; - prev_match_end: - if (!row) - break; - row--; - } +int +search_replace_replace_dialog_callback(struct DrawState* state) +{ + do_search_replace(state); + *state->search = 0; + /*u32_strcpy(state->search, state->input); + next_match(state);*/ + if (!*state->error) + state->input_callback = NULL; + state->chain_dialog = 0; return 0; } int -reread_or_callback(struct DrawState* state) +search_replace_search_dialog_callback(struct DrawState* state) { - char* temp = NULL; + /*next_match(state);*/ + if (!*state->error) + state->input_callback = NULL; + *state->input = 0; + show_replace(state); + return 0; +} - if (!state) - return 1; - if (state->dirty) - { - SET(state->mode, M_PROMPT); - MEMCCPY(state->prompt, prompt_reread_dirty, LINE_DEFAULT, temp); - state->prompt_callback = reread_callback; - } - else - reread_file(state); +int +search_update_callback(struct DrawState* state) +{ + uint32_t* temp = NULL; + U32_MEMCCPY(state->search, state->input, MAXINPUT, temp); return 0; } @@ -2118,6 +2104,14 @@ show_go_to_line(struct DrawState* state) } int +show_help(struct DrawState* state) +{ + SET(state->mode, M_HELP); + *state->error = 0; + return 0; +} + +int show_replace(struct DrawState* state) { char* temp = NULL; @@ -2218,14 +2212,6 @@ show_search_replace(struct DrawState* state) return 0; } -int -show_help(struct DrawState* state) -{ - SET(state->mode, M_HELP); - *state->error = 0; - return 0; -} - void simple_insert_line(struct DrawState* state) { @@ -2433,7 +2419,8 @@ suspend_program(struct DrawState* state) for (int x = 0; x < state->maxx; x++) tb_set_cell(x, y, ' ', TB_WHITE, TB_DEFAULT); - draw(state); + if (draw(state)) + return 1; tb_present(); return 0; } @@ -2495,7 +2482,7 @@ write_file(struct DrawState* state) default:; } - return 0; + return result; } int @@ -2845,8 +2832,11 @@ init_termbox: tb_clear(); if (state.recalculate_size) + { recalculate_size(&state); - if (!draw(&state)) + state.recalculate_size = 0; + } + if (draw(&state)) { state.running = 0; MEMCCPY(state.error, errors[ERR_CANT_ALLOC], diff --git a/sled.h b/sled.h @@ -0,0 +1,90 @@ +/* This program is licensed under the terms of GNU GPL v3 or (at your option) + * any later version. Copyright (C) 2022-2024 Страхиња Радић. + * See the file LICENSE for exact copyright and license details. */ + +int cancel_close(struct DrawState* state); +int clear_selection(struct DrawState* state); +int cp(const char* from, const char* to); +int create_file(struct DrawState* dstate); +int cut_to_paste(struct DrawState* state); +char* dirname(char* dir, char* path, const size_t path_size); +int do_search_replace(struct DrawState* state); +int enter_command_mode(struct DrawState* state); +int enter_insert_mode(struct DrawState* state); +int erase_backwards(struct DrawState* state); +int erase_forward(struct DrawState* state); +int erase_line(struct DrawState* state); +int erase_prev_word(struct DrawState* state); +int erase_selection(struct DrawState* state); +int erase_to_end(struct DrawState* state); +int erase_to_start(struct DrawState* state); +int exit_program(struct DrawState* state); +void get_selection(int* ssr, int* ssc, int* ser, int* sec, + const struct DrawState* state); +int go_to_line_callback(struct DrawState* state); +void handle_key_event(struct tb_event* ev, struct DrawState* state); +int input_cancel(struct DrawState* state); +int input_enter(struct DrawState* state); +int input_erase_backwards(struct DrawState* state); +int input_erase_forward(struct DrawState* state); +int input_erase_to_end(struct DrawState* state); +int input_erase_to_start(struct DrawState* state); +int input_insert_char(struct DrawState* state, uint32_t ch); +int input_insert_space(struct DrawState* state); +int input_insert_tab(struct DrawState* state); +int input_move_end(struct DrawState* state); +int input_move_left(struct DrawState* state); +int input_move_next_word(struct DrawState* state); +int input_move_prev_word(struct DrawState* state); +int input_move_right(struct DrawState* state); +int input_move_start(struct DrawState* state); +void input_position_to(struct DrawState* state, const int column); +int insert_char(struct DrawState* state, uint32_t ch); +int insert_line(struct DrawState* state); +int insert_space(struct DrawState* state); +int insert_tab(struct DrawState* state); +int join_lines(struct DrawState* state); +int load_file(struct DrawState* dstate, long* lineno, long* col); +int move_bottom(struct DrawState* state); +int move_down(struct DrawState* state); +int move_end(struct DrawState* state); +int move_left(struct DrawState* state); +int move_next_word(struct DrawState* state); +int move_page_down(struct DrawState* state); +int move_page_up(struct DrawState* state); +int move_prev_word(struct DrawState* state); +int move_right(struct DrawState* state); +int move_screen_bottom(struct DrawState* state); +int move_screen_middle(struct DrawState* state); +int move_screen_top(struct DrawState* state); +int move_start(struct DrawState* state); +int move_top(struct DrawState* state); +int move_up(struct DrawState* state); +int next_match(struct DrawState* state); +int paste_from_paste(struct DrawState* state); +void position_to(struct DrawState* state, const int row, const int column); +int position_to_selection_start(struct DrawState* state); +int prev_match(struct DrawState* state); +int print_error(const int code, const char* msg, ...); +int quit_callback(struct DrawState* state, struct tb_event* ev); +int reread_callback(struct DrawState* state, struct tb_event* ev); +int reread_file(struct DrawState* state); +int reread_or_callback(struct DrawState* state); +int save_file(struct DrawState* state); +int search_dialog_callback(struct DrawState* state); +int search_replace_replace_dialog_callback(struct DrawState* state); +int search_replace_search_dialog_callback(struct DrawState* state); +int search_update_callback(struct DrawState* state); +int show_go_to_line(struct DrawState* state); +int show_help(struct DrawState* state); +int show_replace(struct DrawState* state); +int show_search(struct DrawState* state); +int show_search_replace(struct DrawState* state); +void simple_insert_line(struct DrawState* state); +void simple_join_lines(struct DrawState* state); +void simple_yank(struct DrawState* state); +int suspend_program(struct DrawState* state); +int toggle_overwrite(struct DrawState* state); +int toggle_selection(struct DrawState* state); +int write_file(struct DrawState* state); +int yank_to_paste(struct DrawState* state); diff --git a/util.h b/util.h @@ -6,10 +6,15 @@ #include <sys/types.h> #include <unistd.h> -#define ABS(a) ((a < 0) ? (-a) : (a)) -#define LEN(x) (sizeof(x) / sizeof(x[0])) -#define MAX(a, b) ((a > b) ? (a) : (b)) -#define MIN(a, b) ((a < b) ? (a) : (b)) +/* clang-format off */ +#define CLEAR(v, c) do { (v) &= ~(c); } while(0) +#define IN(v, c) (((v) & (c)) == (c)) +#define LEN(x) (sizeof(x) / sizeof(x[0])) +#define MIN(a, b) ((a < b) ? (a) : (b)) +#define SET(v, c) do { (v) |= (c); } while(0) +#define TOGGLE(v, c) do { (v) ^= (c); } while(0) +/* clang-format on */ + #define MEMCCPY(to, from, tolen, temp) \ do \ { \ @@ -93,16 +98,6 @@ } while (0) enum { - UNKNOWN_MODE, - COMMAND_MODE, - HELP_MODE, - INSERT_MODE, - OVERWRITE_MODE, - PROMPT_MODE, - SELECTION_MODE -}; - -enum { ERR_UNKNOWN_KEY, ERR_EXIT_KEY, ERR_DLG_OPEN, @@ -120,8 +115,6 @@ enum { ACT_WRITING, ACT_READING }; #define TB_KEY_CTRL_SPACE 0x00 -extern int utlerrno; - size_t u8_string_to_unicode(uint32_t* us, const char* s, const size_t max); size_t unicode_string_to_u8(char* s, const uint32_t* us, const size_t max); uint32_t* u32_memccpy(uint32_t* dst, const uint32_t* src, uint32_t c,