sled

Једноставни уређивач текста
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

чување 22f4c4021dbfa9be3525d7909f3aad7058f32fca
родитељ 6bd6096738037e6b5504b6caeeb20140afbe47e3
Аутор: Страхиња Радић <contact@strahinja.org>
Датум:   Mon, 28 Feb 2022 22:45:08 +0100

Add search functionality

Signed-off-by: Страхиња Радић <contact@strahinja.org>

Diffstat:
MTODO | 4++--
Mconfig.def.h | 15+++++++++++++++
Mconfig.h | 15+++++++++++++++
Mdraw.c | 59+++++++++++++++++++++++++++++++----------------------------
Mdraw.h | 5++++-
Msled.1.in | 8++++++++
Msled.c | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mutil.c | 17+++++++++++++++--
Mutil.h | 5+++--
измењених датотека: 9, додавања: 280(+), брисања: 97(-)

diff --git a/TODO b/TODO @@ -1,9 +1,9 @@ TODO ==== - < > Search? + <x> Search? - < > Replace? + </> Replace? Legend diff --git a/config.def.h b/config.def.h @@ -19,6 +19,8 @@ static const char* backup_suffix = "~"; #define INPUT_EDIT_BG TB_BLACK #define PROMPT_FG TB_YELLOW | TB_BOLD #define PROMPT_BG TB_MAGENTA +#define SEARCH_FG TB_WHITE | TB_BOLD +#define SEARCH_BG TB_GREEN #define SEL_FG TB_BLACK #define SEL_BG TB_WHITE #define SHADOW_FG TB_BLACK @@ -79,10 +81,13 @@ 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_search(struct DrawState* state); int toggle_overwrite(struct DrawState* state); int toggle_selection(struct DrawState* state); int write_file(struct DrawState* state); @@ -125,8 +130,12 @@ static const struct Key global_keys[] = { .callback = erase_to_end }, { .ev = { .key = TB_KEY_CTRL_L }, .callback = show_go_to_line }, + { .ev = { .key = TB_KEY_CTRL_N }, + .callback = next_match }, { .ev = { .key = TB_KEY_CTRL_O }, .callback = reread_or_callback }, + { .ev = { .key = TB_KEY_CTRL_P }, + .callback = prev_match }, { .ev = { .key = TB_KEY_CTRL_R }, .callback = move_prev_word }, { .ev = { .key = TB_KEY_CTRL_SPACE }, @@ -146,6 +155,9 @@ static const struct Key global_keys[] = { { .ev = { .key = TB_KEY_DELETE }, .callback = erase_forward }, { .ev = { .key = TB_KEY_F1 }, .callback = show_help }, + { .ev = { .key = TB_KEY_F3 }, .callback = next_match }, + { .ev = { .key = TB_KEY_F4 }, .callback = prev_match }, + { .ev = { .key = TB_KEY_F7 }, .callback = show_search }, { .ev = { .key = TB_KEY_END }, .callback = move_end }, { .ev = { .key = TB_KEY_HOME }, .callback = move_start }, { .ev = { .key = TB_KEY_INSERT }, @@ -205,6 +217,7 @@ static const struct Key insert_keys[] = { static const struct Key command_keys[] = { { .ev = { .key = TB_KEY_CTRL_BACKSLASH }, .callback = enter_insert_mode }, + { .ev = { .ch = '/' }, .callback = show_search }, { .ev = { .ch = ':' }, .callback = show_go_to_line }, { .ev = { .ch = '0' }, .callback = move_start }, { .ev = { .ch = '$' }, .callback = move_end }, @@ -218,6 +231,8 @@ static const struct Key command_keys[] = { { .ev = { .ch = 'L' }, .callback = move_screen_bottom }, { .ev = { .ch = 'l' }, .callback = move_right }, { .ev = { .ch = 'M' }, .callback = move_screen_middle }, + { .ev = { .ch = 'N' }, .callback = prev_match }, + { .ev = { .ch = 'n' }, .callback = next_match }, { .ev = { .ch = 'P' }, .callback = paste_from_paste }, { .ev = { .ch = 'q' }, .callback = exit_program }, { .ev = { .ch = 'R' }, .callback = toggle_overwrite }, diff --git a/config.h b/config.h @@ -19,6 +19,8 @@ static const char* backup_suffix = "~"; #define INPUT_EDIT_BG TB_BLACK #define PROMPT_FG TB_YELLOW | TB_BOLD #define PROMPT_BG TB_MAGENTA +#define SEARCH_FG TB_WHITE | TB_BOLD +#define SEARCH_BG TB_GREEN #define SEL_FG TB_BLACK #define SEL_BG TB_WHITE #define SHADOW_FG TB_BLACK @@ -79,10 +81,13 @@ 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_search(struct DrawState* state); int toggle_overwrite(struct DrawState* state); int toggle_selection(struct DrawState* state); int write_file(struct DrawState* state); @@ -125,8 +130,12 @@ static const struct Key global_keys[] = { .callback = erase_to_end }, { .ev = { .key = TB_KEY_CTRL_L }, .callback = show_go_to_line }, + { .ev = { .key = TB_KEY_CTRL_N }, + .callback = next_match }, { .ev = { .key = TB_KEY_CTRL_O }, .callback = reread_or_callback }, + { .ev = { .key = TB_KEY_CTRL_P }, + .callback = prev_match }, { .ev = { .key = TB_KEY_CTRL_R }, .callback = move_prev_word }, { .ev = { .key = TB_KEY_CTRL_SPACE }, @@ -146,6 +155,9 @@ static const struct Key global_keys[] = { { .ev = { .key = TB_KEY_DELETE }, .callback = erase_forward }, { .ev = { .key = TB_KEY_F1 }, .callback = show_help }, + { .ev = { .key = TB_KEY_F3 }, .callback = next_match }, + { .ev = { .key = TB_KEY_F4 }, .callback = prev_match }, + { .ev = { .key = TB_KEY_F7 }, .callback = show_search }, { .ev = { .key = TB_KEY_END }, .callback = move_end }, { .ev = { .key = TB_KEY_HOME }, .callback = move_start }, { .ev = { .key = TB_KEY_INSERT }, @@ -205,6 +217,7 @@ static const struct Key insert_keys[] = { static const struct Key command_keys[] = { { .ev = { .key = TB_KEY_CTRL_BACKSLASH }, .callback = enter_insert_mode }, + { .ev = { .ch = '/' }, .callback = show_search }, { .ev = { .ch = ':' }, .callback = show_go_to_line }, { .ev = { .ch = '0' }, .callback = move_start }, { .ev = { .ch = '$' }, .callback = move_end }, @@ -218,6 +231,8 @@ static const struct Key command_keys[] = { { .ev = { .ch = 'L' }, .callback = move_screen_bottom }, { .ev = { .ch = 'l' }, .callback = move_right }, { .ev = { .ch = 'M' }, .callback = move_screen_middle }, + { .ev = { .ch = 'N' }, .callback = prev_match }, + { .ev = { .ch = 'n' }, .callback = next_match }, { .ev = { .ch = 'P' }, .callback = paste_from_paste }, { .ev = { .ch = 'q' }, .callback = exit_program }, { .ev = { .ch = 'R' }, .callback = toggle_overwrite }, diff --git a/draw.c b/draw.c @@ -80,18 +80,6 @@ init_bufferline(struct BufferLine* bl) } void -free_bufferline(struct BufferLine* bl) -{ - if (!bl) - return; - free(bl->text); - bl->text = NULL; - bl->length = 0; - bl->fg = 0; - bl->bg = 0; -} - -void init_drawstate(struct DrawState* state, char* error, char* action, char* filename, char* prompt, int skip_paste_and_mode_init) { @@ -99,6 +87,7 @@ init_drawstate(struct DrawState* state, char* error, char* action, state->action = action; state->filename = filename; state->input = NULL; + state->search = NULL; state->buffer = NULL; state->column = 0; state->display_column = 0; @@ -121,20 +110,33 @@ init_drawstate(struct DrawState* state, char* error, char* action, state->in_overwrite_mode = 0; state->in_command_mode = 0; } - state->prompt = prompt; - state->prompt_callback = NULL; - state->input_callback = NULL; - state->running = 1; - state->dirty = 0; - state->in_prompt = 0; - state->in_selection_mode = 0; - state->show_help = 0; - state->show_input_dialog = 0; - state->input_numerical = 0; - state->sel_start_row = 0; - state->sel_start_col = 0; - state->sel_end_row = 0; - state->sel_end_col = 0; + state->prompt = prompt; + state->prompt_callback = NULL; + state->input_callback = NULL; + state->input_update_callback = NULL; + state->running = 1; + state->dirty = 0; + state->in_prompt = 0; + state->in_selection_mode = 0; + state->show_help = 0; + state->show_input_dialog = 0; + state->input_numerical = 0; + state->sel_start_row = 0; + state->sel_start_col = 0; + state->sel_end_row = 0; + state->sel_end_col = 0; +} + +void +free_bufferline(struct BufferLine* bl) +{ + if (!bl) + return; + free(bl->text); + bl->text = NULL; + bl->length = 0; + bl->fg = 0; + bl->bg = 0; } void @@ -151,6 +153,7 @@ free_drawstate(struct DrawState* state, int skip_paste_free) free(state->paste); } free(state->input); + free(state->search); } void @@ -693,8 +696,8 @@ draw_screen(const struct DrawState* state) if (current->text) u32_draw_string(0, cy, TEXT_FG, TEXT_BG, current->text, state->maxx, state->maxx, 0, 0, CENTER, - state->first_shown_column, 0, NULL, 0, 0, ssi, - sei, SEL_FG, SEL_BG); + state->first_shown_column, 0, state->search, + SEARCH_FG, SEARCH_BG, ssi, sei, SEL_FG, SEL_BG); i++; cy++; } diff --git a/draw.h b/draw.h @@ -36,6 +36,7 @@ struct DrawState { char* action; char* filename; uint32_t* input; + uint32_t* search; struct BufferLine* buffer; size_t buffer_size; int column; @@ -56,6 +57,7 @@ struct DrawState { size_t paste_size; void (*prompt_callback)(struct DrawState*, struct tb_event*); void (*input_callback)(struct DrawState*); + void (*input_update_callback)(struct DrawState*); int running; int dirty; int in_prompt; @@ -87,9 +89,9 @@ struct StatusSegment { }; void init_bufferline(struct BufferLine* bl); -void free_bufferline(struct BufferLine* bl); 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); void free_drawstate(struct DrawState* state, int skip_paste_free); size_t display_length(const struct DrawState* state, uint32_t* buffer, const size_t max_index); @@ -116,6 +118,7 @@ void u32_draw_string(const int x, const int y, const uint16_t fg, const size_t ssi, const size_t 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); const struct DrawState* redraw_status(const struct DrawState* state); diff --git a/sled.1.in b/sled.1.in @@ -90,6 +90,9 @@ l l. _ \fCEsc\fP@Close dialog, clear error, cancel selection \fCF1\fP@Show help dialog +\fCF3\fP@Go to next found match +\fCF4\fP@Go to previous found match +\fCF7\fP@Show search dialog \fCHome\fP or \fCC-A\fP@Go to start of line \fCPgUp\fP or \fCC-B\fP@Go to previous page \fCBackspace\fP@Erase character before cursor @@ -101,7 +104,9 @@ _ \fCPgDown\fP or \fCC-F\fP@Go to next page \fCC-K\fP@Erase to end of line \fCC-L\fP@Show Go to line dialog +\fCC-N\fP@Go to next found match \fCC-O\fP@Reread (reopen) file +\fCC-P\fP@Go to previous found match \fCC-Q\fP@Quit \fCC-R\fP@Go to previous word \fCC-S\fP@Write (save) file @@ -137,6 +142,7 @@ box tab(@); l l. \fBKey\fP@\fBAction\fP _ +\fC/\fP@Show search dialog \fC:\fP@Show Go to line dialog \fC0\fP@Go to start of line \fC$\fP@Go to end of line @@ -150,6 +156,8 @@ _ \fCL\fP@Go to last line on the screen \fCl\fP@Go to next character \fCM\fP@Go to middle line on the screen +\fCN\fP@Go to previous found match +\fCn\fP@Go to next found match \fCP\fP@Paste from paste buffer \fCq\fP@Quit \fCR\fP@Toggle overwrite mode diff --git a/sled.c b/sled.c @@ -68,6 +68,7 @@ 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); int clear_selection(struct DrawState* state) @@ -177,6 +178,46 @@ erase_selection(struct DrawState* state) } void +go_to_line_callback(struct DrawState* state) +{ + size_t lineno = 0; + size_t len = 0; + char* u8_input = NULL; + + if (!state) + return; + + if (*state->input) + { + len = u32_strlen(state->input); + u8_input = calloc(len + 1, UTF8REPMAX); + if (!u8_input) + { + strcpy(state->error, errors[ERR_CANT_ALLOC]); + state->running = 0; + return; + } + unicode_string_to_u8(u8_input, state->input, len + 1); + errno = 0; + lineno = strtoul(u8_input, NULL, 10); + if (errno) + strcpy(state->error, errors[ERR_INVALID_NUM]); + if (!*state->error) + { + if (lineno > state->rows_count - 1) + lineno = state->rows_count; + else if (lineno < 1) + lineno = 1; + position_to(state, lineno - 1, state->column); + *state->input = 0; + } + free(u8_input); + } + if (!*state->error) + state->input_callback = NULL; +} + +void handle_key_event(struct tb_event* ev, struct DrawState* state) { const struct Key* current = NULL; @@ -307,6 +348,8 @@ input_insert_char(struct DrawState* state, uint32_t ch) input_position_to(state, state->input_column + 1); if (add_null) state->input[state->input_column] = 0; + if (state->input_update_callback) + state->input_update_callback(state); } return 0; } @@ -314,14 +357,17 @@ input_insert_char(struct DrawState* state, uint32_t ch) void input_position_to(struct DrawState* state, const int column) { + size_t len = u32_strlen(state->input); state->input_column = column; - if (state->input_column >= MAXINPUT) - state->input_column = MAXINPUT - 1; + if (state->input_column > len) + state->input_column = len; state->input_display_column = display_length(state, state->input, state->input_column); - if (state->input_display_column > state->input_first_shown_column - + state->input_dialog_width - 4) + if (state->input_first_shown_column > state->input_display_column) + state->input_first_shown_column = state->input_display_column; + if (state->input_first_shown_column + (state->input_dialog_width - 4) + < state->input_column) state->input_first_shown_column = state->input_display_column - (state->input_dialog_width - 4); } @@ -527,8 +573,7 @@ position_to(struct DrawState* state, const int row, const int column) } if (state->first_shown_column > state->display_column) state->first_shown_column = state->display_column; - if (state->first_shown_column + (state->maxx - 1) - < state->column) + if (state->first_shown_column + (state->maxx - 1) < state->column) state->first_shown_column = state->display_column - (state->maxx - 1); @@ -688,12 +733,24 @@ save_file_cleanup: return SAVE_ERR_NONE; } +void +search_dialog_callback(struct DrawState* state) +{ + next_match(state); + if (!*state->error) + state->input_callback = NULL; +} + +void +search_update_callback(struct DrawState* state) +{ + if (*state->input) + u32_strcpy(state->search, state->input); +} + int cancel_close(struct DrawState* state) { - if (!state) - return 1; - if (!*state->error && !state->in_prompt && !state->show_help) { if (SEL_NOT_EMPTY(state)) @@ -928,8 +985,10 @@ input_cancel(struct DrawState* state) *state->error = 0; return 0; } - state->show_input_dialog = 0; - *state->input = 0; + state->show_input_dialog = 0; + *state->input = 0; + state->input_callback = NULL; + state->input_update_callback = NULL; return 0; } @@ -960,7 +1019,8 @@ input_erase_backwards(struct DrawState* state) } while (state->input[i]); state->input[i - 1] = 0; input_position_to(state, state->input_column - 1); - state->dirty = 1; + if (state->input_update_callback) + state->input_update_callback(state); } return 0; } @@ -977,7 +1037,8 @@ input_erase_forward(struct DrawState* state) i++; } state->input[i] = 0; - state->dirty = 1; + if (state->input_update_callback) + state->input_update_callback(state); } return 0; } @@ -987,7 +1048,9 @@ input_erase_to_end(struct DrawState* state) { state->input[state->input_column] = 0; input_move_end(state); - state->dirty = 1; + u32_strcpy(state->search, state->input); + if (state->input_update_callback) + state->input_update_callback(state); return 0; } @@ -1004,7 +1067,8 @@ input_erase_to_start(struct DrawState* state) } while (state->input[i]); state->input[len - state->input_column] = 0; input_move_start(state); - state->dirty = 1; + if (state->input_update_callback) + state->input_update_callback(state); return 0; } @@ -1299,6 +1363,41 @@ move_up(struct DrawState* state) } int +next_match(struct DrawState* state) +{ + struct BufferLine* current = NULL; + const uint32_t* found = NULL; + size_t xpos; + + if (!state->search || !*state->search) + return 0; + + *state->error = 0; + for (size_t row = state->row; row < state->rows_count; row++) + { + current = state->buffer + row; + found = current->text; + if (row == state->row) + found += state->column + 1; + do + { + found = u32_strstr(found, state->search); + if (found) + { + xpos = found - (const uint32_t*)current->text; + state->saved_column = xpos; + if (*current->text) + { + position_to(state, row, xpos); + return 0; + } + } + } while (found); + } + return 0; +} + +int paste_from_paste(struct DrawState* state) { uint32_t* newtext = NULL; @@ -1401,6 +1500,41 @@ paste_from_paste_cleanup: } int +prev_match(struct DrawState* state) +{ + struct BufferLine* current = NULL; + const uint32_t* found = NULL; + size_t xpos; + + if (!state->search || !*state->search) + return 0; + + *state->error = 0; + for (size_t row = state->row + 1; row > 0; row--) + { + current = state->buffer + row - 1; + found = current->text; + if (row == state->row + 1) + found += state->column + 1; + do + { + found = u32_strstr(found, state->search); + if (found) + { + xpos = found - (const uint32_t*)current->text; + state->saved_column = xpos; + if (*current->text) + { + position_to(state, row - 1, xpos); + return 0; + } + } + } while (found); + } + return 0; +} + +int reread_or_callback(struct DrawState* state) { if (!state) @@ -1430,13 +1564,44 @@ show_go_to_line(struct DrawState* state) return 0; } } - state->input_numerical = 1; - state->show_input_dialog = 1; - state->input_column = 0; - state->input_display_column = 0; - state->input_first_shown_column = 0; - *state->input = 0; - *state->error = 0; + state->input_numerical = 1; + state->show_input_dialog = 1; + *state->input = 0; + *state->error = 0; + input_position_to(state, 0); + return 0; +} + +int +show_search(struct DrawState* state) +{ + if (!state->search) + { + if (!(state->search = calloc(MAXINPUT, sizeof(uint32_t)))) + { + state->running = 0; + strcpy(state->error, errors[ERR_CANT_ALLOC]); + return 0; + } + } + *state->search = 0; + state->input_title = (char*)search_dialog_title; + state->input_callback = search_dialog_callback; + state->input_update_callback = search_update_callback; + if (!state->input) + { + if (!(state->input = calloc(MAXINPUT, sizeof(uint32_t)))) + { + state->running = 0; + strcpy(state->error, errors[ERR_CANT_ALLOC]); + return 0; + } + } + *state->input = 0; + state->input_numerical = 0; + state->show_input_dialog = 1; + *state->error = 0; + input_position_to(state, 0); return 0; } @@ -1597,46 +1762,6 @@ yank_to_paste(struct DrawState* state) return 0; } -void -go_to_line_callback(struct DrawState* state) -{ - size_t lineno = 0; - size_t len = 0; - char* u8_input = NULL; - - if (!state) - return; - - if (*state->input) - { - len = u32_strlen(state->input); - u8_input = calloc(len + 1, UTF8REPMAX); - if (!u8_input) - { - strcpy(state->error, errors[ERR_CANT_ALLOC]); - state->running = 0; - return; - } - unicode_string_to_u8(u8_input, state->input, len + 1); - errno = 0; - lineno = strtoul(u8_input, NULL, 10); - if (errno) - strcpy(state->error, errors[ERR_INVALID_NUM]); - if (!*state->error) - { - if (lineno > state->rows_count - 1) - lineno = state->rows_count; - else if (lineno < 1) - lineno = 1; - position_to(state, lineno - 1, state->column); - *state->input = 0; - } - free(u8_input); - } - if (!*state->error) - state->input_callback = NULL; -} - int main(int argc, char** argv) { diff --git a/util.c b/util.c @@ -69,6 +69,19 @@ u32_strlen(const uint32_t* s) } const uint32_t* +u32_strstr(const uint32_t* haystack, const uint32_t* needle) +{ + const uint32_t* ph = haystack; + while (*ph) + { + if (u32_starts_with(ph, needle)) + return ph; + ph++; + } + return NULL; +} + +const uint32_t* u32_strchr(const uint32_t* haystack, const uint32_t needle) { const uint32_t* ph = haystack; @@ -174,7 +187,7 @@ u32_is_word_boundary(const uint32_t ch, const int strictly_whitespace) : u32_strchr((uint32_t*)L" \v\t()[]{},.;:/\\", ch) != NULL; } -int +const int starts_with(const char* s, const char* with) { while (*s && *s == *with) @@ -187,7 +200,7 @@ starts_with(const char* s, const char* with) return *with == 0; } -int +const int u32_starts_with(const uint32_t* s, const uint32_t* with) { while (*s && *s == *with) diff --git a/util.h b/util.h @@ -48,6 +48,7 @@ enum { 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); size_t u32_strlen(const uint32_t* s); +const uint32_t* u32_strstr(const uint32_t* haystack, const uint32_t* needle); const uint32_t* u32_strchr(const uint32_t* haystack, const uint32_t needle); size_t u32_strcpy(uint32_t* to, const uint32_t* from); size_t u32_strncpy(uint32_t* to, const uint32_t* from, size_t max); @@ -55,6 +56,6 @@ size_t u32_strcat(uint32_t* to, const uint32_t* from); size_t u32_strncat(uint32_t* to, const uint32_t* from, size_t max); size_t u32_u8_strcpy(uint32_t* to, const char* from); int u32_is_word_boundary(const uint32_t ch, const int strictly_whitespace); -int starts_with(const char* s, const char* with); -int u32_starts_with(const uint32_t* s, const uint32_t* with); +const int starts_with(const char* s, const char* with); +const int u32_starts_with(const uint32_t* s, const uint32_t* with); const char* u8_basename(const char* path);