sled

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

чување dcf29bc2f8378d4131be7f7a534914e8781d00a9
родитељ 6ded9c4a0779056b7a2bca3ad908b2766a708afc
Аутор: Страхиња Радић <contact@strahinja.org>
Датум:   Fri, 18 Feb 2022 13:17:00 +0100

Add reread/write operations; update manpage; fix mem errors/leaks

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

Diffstat:
M.gitignore | 1+
MTODO | 6++++--
Mconfig.def.h | 17++++++++++++-----
Mconfig.h | 17++++++++++++-----
Mdraw.c | 35+++++++++++++++--------------------
Msled.1.in | 111++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msled.c | 225++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mutil.h | 1+
измењених датотека: 8, додавања: 273(+), брисања: 140(-)

diff --git a/.gitignore b/.gitignore @@ -6,6 +6,7 @@ .do_built.dir *~ *.bak +*.err *.orig *.rej *.o diff --git a/TODO b/TODO @@ -11,14 +11,16 @@ [ ] Copy/paste? [ ] Add vi V - [ ] Update manpage + [x] Update manpage [ ] Allow creating files (error out if directory not writeable, only create file when saving) [ ] Add vi H/L - [ ] Write operation; create backup files + [~] Write operation; create backup files + + [x] Reread file operation < > Search? diff --git a/config.def.h b/config.def.h @@ -38,8 +38,11 @@ int move_right(struct DrawState* state); int move_start(struct DrawState* state); int move_top(struct DrawState* state); int move_up(struct DrawState* state); +int reread_or_callback(struct DrawState* state); int show_help(struct DrawState* state); int toggle_selection(struct DrawState* state); +int write_file(struct DrawState* state); + /* clang-format off */ @@ -74,10 +77,14 @@ static const struct Key global_keys[] = { .callback = move_page_down }, { .ev = { .key = TB_KEY_CTRL_K }, .callback = erase_to_end }, + { .ev = { .key = TB_KEY_CTRL_O }, + .callback = reread_or_callback }, { .ev = { .key = TB_KEY_CTRL_R }, .callback = move_prev_word }, { .ev = { .key = TB_KEY_CTRL_SPACE }, .callback = toggle_selection }, + { .ev = { .key = TB_KEY_CTRL_S }, + .callback = write_file }, { .ev = { .key = TB_KEY_CTRL_T }, .callback = move_next_word }, { .ev = { .key = TB_KEY_CTRL_U }, @@ -134,22 +141,22 @@ static const struct StatusSegment status_segments[] = { {.format = "%s%s", .callback = format_filename, .fg = TB_WHITE, - .bg = TB_DEFAULT, + .bg = TB_BLUE, .alignment = LEFT}, { .format = "%s", .callback = format_mode, .fg = TB_YELLOW | TB_BOLD, - .bg = TB_DEFAULT, + .bg = TB_BLUE, .alignment = CENTER}, - {.format = "%ld:%ld (%ld%%)", + {.format = "%5ld:%5ld (%3ld%%)", .callback = format_position, .fg = TB_WHITE | TB_BOLD, - .bg = TB_DEFAULT, + .bg = TB_BLUE, .alignment = CENTER}, {.format = "F1=HELP, C-Q=QUIT", .callback = NULL, .fg = TB_WHITE, - .bg = TB_DEFAULT, + .bg = TB_BLUE, .alignment = RIGHT}, }; /* clang-format on */ diff --git a/config.h b/config.h @@ -38,8 +38,11 @@ int move_right(struct DrawState* state); int move_start(struct DrawState* state); int move_top(struct DrawState* state); int move_up(struct DrawState* state); +int reread_or_callback(struct DrawState* state); int show_help(struct DrawState* state); int toggle_selection(struct DrawState* state); +int write_file(struct DrawState* state); + /* clang-format off */ @@ -74,10 +77,14 @@ static const struct Key global_keys[] = { .callback = move_page_down }, { .ev = { .key = TB_KEY_CTRL_K }, .callback = erase_to_end }, + { .ev = { .key = TB_KEY_CTRL_O }, + .callback = reread_or_callback }, { .ev = { .key = TB_KEY_CTRL_R }, .callback = move_prev_word }, { .ev = { .key = TB_KEY_CTRL_SPACE }, .callback = toggle_selection }, + { .ev = { .key = TB_KEY_CTRL_S }, + .callback = write_file }, { .ev = { .key = TB_KEY_CTRL_T }, .callback = move_next_word }, { .ev = { .key = TB_KEY_CTRL_U }, @@ -134,22 +141,22 @@ static const struct StatusSegment status_segments[] = { {.format = "%s%s", .callback = format_filename, .fg = TB_WHITE, - .bg = TB_DEFAULT, + .bg = TB_BLUE, .alignment = LEFT}, { .format = "%s", .callback = format_mode, .fg = TB_YELLOW | TB_BOLD, - .bg = TB_DEFAULT, + .bg = TB_BLUE, .alignment = CENTER}, - {.format = "%ld:%ld (%ld%%)", + {.format = "%5ld:%5ld (%3ld%%)", .callback = format_position, .fg = TB_WHITE | TB_BOLD, - .bg = TB_DEFAULT, + .bg = TB_BLUE, .alignment = CENTER}, {.format = "F1=HELP, C-Q=QUIT", .callback = NULL, .fg = TB_WHITE, - .bg = TB_DEFAULT, + .bg = TB_BLUE, .alignment = RIGHT}, }; /* clang-format on */ diff --git a/draw.c b/draw.c @@ -17,13 +17,15 @@ static const char* help[] = { "Esc - Close dialog, clear error, cancel", " selection", "F1 - Show this screen", - "C-C - Switch command <-> insert mode", "Home or C-A - Go to start of line", - "PgUp or C-B - Go to previous page", - "PgDown or C-F - Go to next page", + "C-C - Switch command <-> insert mode", "End or C-E - Go to end of line", - "C-Space - Toggle selection mode", + "C-O - Reread (reopen) file", "C-Q - Quit", + "C-R - Go to previous word", + "C-S - Write (save) file", + "C-Space - Toggle selection mode", + "C-T - Go to next word", "", "Arrow keys work in insert, selection and command modes.", "Some vi-like keys work in command and selection modes.", @@ -44,11 +46,12 @@ const char* errors[] = { [ERR_DLG_OPEN_QUIT] = "Press Esc to close dialog", [ERR_CANT_ALLOC] = "Memory allocation failed", [ERR_CANT_SAVE] = "File is not writeable", + [ERR_CANT_OPEN] = "Cannot read file", [ERR_CANT_MOVE] = "Cannot move temporary file"}; -const char* program_name = "sled"; -const char* prompt_dirty = "File changed, save [y/n/ESC]?"; -const char* prompt_cancel_dirty = "Entry changed, save [y/n/ESC]?"; +const char* program_name = "sled"; +const char* prompt_dirty = "File changed, save [y/n/ESC]?"; const char* prompt_overwrite = "Not empty, overwrite? [y/n/ESC]?"; +const char* prompt_reread_dirty = "File changed, lose changes [y/n/ESC]?"; /* clang-format on */ void @@ -108,20 +111,12 @@ init_drawstate(struct DrawState* state, char* error, char* filename, void free_drawstate(struct DrawState* state) { - struct BufferLine* current = NULL; - size_t i = 0; - - for (i = 0; i < state->rows_count; i++) - { - current = state->buffer + i; - free_bufferline(current); - } + size_t row = 0; + for (row = 0; row < state->rows_count; row++) + free_bufferline(state->buffer + row); free(state->buffer); - for (i = 0; i < state->paste_rows_count; i++) - { - current = state->paste_buffer + i; - free_bufferline(current); - } + for (row = 0; row < state->paste_rows_count; row++) + free_bufferline(state->paste_buffer + row); free(state->paste_buffer); } diff --git a/sled.1.in b/sled.1.in @@ -56,26 +56,129 @@ the code taken from the termbox library, see the file .LP .B sled is a simple text editor with a terminal UI. +. +.SH MODES +. +.LP +.B sled +has three modes of operation. The initial mode which is active on startup is the +.BR "insert mode" . +It behaves like one would expect from a \[lq]normal\[rq] text editor: most +characters are self-inserting, arrow keys and backspace/delete work as expected, +and so on. +.B Command mode +behaves more like +.BR vi (1): +characters like \fCh\fP, \fCj\fP, \fCk\fP, \fCl\fP no longer insert themselves +but execute commands (in this case movement commands). Last, in +.BR "selection mode" , +moving around the file extends the selection, and most commands that alter text +instead delete the selection and exit selection mode. +. .SH "KEY BINDINGS" . +.B Global key bindings +are active in all modes, and are as follows: +. +.TS +box tab(@); +l l. +\fBKey\fP@\fBAction\fP +_ +\fCEsc\fP@Close dialog, clear error, cancel selection +\fCF1\fP@Show help 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 +\fCC-C\fP@Switch between command and insert mode +\fCDelete\fP@Erase character under cursor +\fCEnd\fP or \fCC-E\fP@Go to end of line +\fCPgDown\fP or \fCC-F\fP@Go to next page +\fCC-K\fP@Erase to end of line +\fCC-O\fP@Reread (reopen) file +\fCC-Q\fP@Quit +\fCC-R\fP@Go to previous word +\fCC-S\fP@Write (save) file +\fCC-Space\fP@Toggle selection mode +\fCC-T\fP@Go to next word +\fCC-U\fP@Erase to start of line +.TE +. +.sp 1 +. .LP -Main window key bindings are as follows: +In +.BR "insert mode" , +characters are self-inserting, and \fCEnter\fP inserts new line. All the global +keys apply to insert mode as well. +. +.LP +In +.BR "command mode" , +the following +.BR vi (1)-like +keys are in effect: . .TS box tab(@); l l. \fBKey\fP@\fBAction\fP _ -\fCEsc\fP@Close dialog or clear error -\fCH\fP or \fCF1\fP@Show help dialog +\fC0\fP@Go to start of line +\fC$\fP@Go to end of line +\fCG\fP@Go to last line in the file +\fCg\fP@Go to first line in the file +\fCH\fP@Show help dialog +\fCh\fP@Go to previous character +\fCj\fP@Go to next line +\fCk\fP@Go to previous line +\fCl\fP@Go to next character +\fCq\fP@Quit +\fCv\fP@Toggle selection mode +\fCW\fP@Go to previous word +\fCw\fP@Go to next word .TE . .sp 1 . +.LP +If +.B selection mode +was entered from insert mode (or insert mode is activated during selection +mode), typing any self-inserting character or activating key bindings which +modify text, like \fCDelete\fP or \fCC-K\fP, will erase the selected text +instead, and in the case of self-inserting characters, insert the character in +question, exiting the selection mode. +. +.PP +Similar holds true when the selection mode is entered from the command mode or +the command mode is activated during the selection mode, except that vi-like key +bindings which modify text will have the effect on the selection. +. +.sp 1 +. +. .SH "STATUS LINE" . .LP -Status line is comprised of a number of segments, by default three.. +Status line is comprised of a number of segments, by default four. +. +.IP \(bu 4 +The leftmost segment displays the current filename, with an asterisk (\fC*\fP) +if there are any unsaved changes. +. +.IP \(bu +Second segment displays the current mode. +. +.IP \(bu +Third segment displays the current position within the file, in the form +. +.CDS 8 +[column]:[row] ([percentage of total lines]%) +.CDE +. +.IP \(bu 4 +Fourth segment shows quick help. . .SH AUTHOR . diff --git a/sled.c b/sled.c @@ -39,8 +39,8 @@ enum { extern const char* errors[]; extern const char* mode_labels[]; extern const char* prompt_dirty; -extern const char* prompt_cancel_dirty; extern const char* prompt_overwrite; +extern const char* prompt_reread_dirty; extern const char* program_name; int print_error(const int code, const char* msg, ...); @@ -52,9 +52,11 @@ void format_position(char* result, size_t result_size, const char* format, const struct DrawState* state); void format_msgs(char* result, size_t result_size, const char* format, const struct DrawState* state); +void reread_file(struct DrawState* state); void overwrite_copy_msgid_callback(struct DrawState* state, struct tb_event* ev); void overwrite_paste_callback(struct DrawState* state, struct tb_event* ev); void cancel_callback(struct DrawState* state, struct tb_event* ev); +void reread_callback(struct DrawState* state, struct tb_event* ev); void quit_callback(struct DrawState* state, struct tb_event* ev); struct BufferLine* load_info(struct DrawState* state); struct DrawState* load_msgstr(struct DrawState* state); @@ -141,33 +143,59 @@ format_position(char* result, size_t result_size, const char* format, } void +reread_file(struct DrawState* state) +{ + long lineno = 1; + long col = 1; + size_t row = 0; + int result; + + for (row = 0; row < state->rows_count; row++) + free_bufferline(state->buffer + row); + free(state->buffer); + for (row = 0; row < state->paste_rows_count; row++) + free_bufferline(state->paste_buffer + row); + free(state->paste_buffer); + init_drawstate(state, state->error, state->filename, state->prompt); + + result = load_file(state, &lineno, &col); + switch (result) + { + case LOAD_ERR_CANT_ALLOC: + strcpy(state->error, errors[ERR_CANT_ALLOC]); + break; + case LOAD_ERR_CANT_OPEN_FILE: + strcpy(state->error, errors[ERR_CANT_OPEN]); + break; + default:; + } +} + +void +reread_callback(struct DrawState* state, struct tb_event* ev) +{ + if (!state) + return; + + if (ev->ch == 'y') + reread_file(state); + state->prompt_callback = NULL; +} + +void quit_callback(struct DrawState* state, struct tb_event* ev) { if (!state) return; if (ev->ch == 'y') - { - int result = save_file(state); - switch (result) - { - case SAVE_ERR_CANT_ALLOC: - strcpy(state->error, errors[ERR_CANT_ALLOC]); - break; - case SAVE_ERR_CANT_OPEN_FILE: - strcpy(state->error, errors[ERR_CANT_SAVE]); - break; - default: - state->error[0] = 0; - } - } - state->dirty = 0; + write_file(state); state->prompt_callback = NULL; state->running = 0; } int -load_file(struct DrawState* dstate, long* lineno, long* col) +load_file(struct DrawState* state, long* lineno, long* col) { struct BufferLine* newchunk = calloc(ALLOC_DELTA, sizeof(struct BufferLine)); @@ -178,12 +206,12 @@ load_file(struct DrawState* dstate, long* lineno, long* col) if (!newchunk) return LOAD_ERR_CANT_ALLOC; - dstate->buffer = newchunk; - line = dstate->buffer; - dstate->buffer_size = ALLOC_DELTA; - dstate->rows_count = 0; + state->buffer = newchunk; + line = state->buffer; + state->buffer_size = ALLOC_DELTA; + state->rows_count = 0; - if (!(input = fopen(dstate->filename, "rt"))) + if (!(input = fopen(state->filename, "rt"))) return LOAD_ERR_CANT_OPEN_FILE; while (!feof(input)) @@ -197,26 +225,26 @@ load_file(struct DrawState* dstate, long* lineno, long* col) if (eol) *eol = 0; - line = dstate->buffer + dstate->rows_count; - if (line == dstate->buffer + dstate->buffer_size) + line = state->buffer + state->rows_count; + if (line == state->buffer + state->buffer_size) { - dstate->buffer_size += ALLOC_DELTA; - newchunk = realloc(dstate->buffer, - dstate->buffer_size * sizeof(struct BufferLine)); + state->buffer_size += ALLOC_DELTA; + newchunk = realloc(state->buffer, + state->buffer_size * sizeof(struct BufferLine)); if (!newchunk) return LOAD_ERR_CANT_ALLOC; - dstate->buffer = newchunk; - line = dstate->buffer + dstate->buffer_size - - ALLOC_DELTA; + state->buffer = newchunk; + line = state->buffer + state->buffer_size - ALLOC_DELTA; } init_bufferline(line); newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); if (!newtext) return LOAD_ERR_CANT_ALLOC; + line->length = u32_u8_strcpy(newtext, input_line); line->text = newtext; - line->length = u32_u8_strcpy(line->text, input_line); - dstate->rows_count++; + state->rows_count++; } + fclose(input); return LOAD_ERR_NONE; } @@ -224,6 +252,23 @@ load_file(struct DrawState* dstate, long* lineno, long* col) int save_file(struct DrawState* state) { + FILE* output = NULL; + char* buf = NULL; + + if (!(buf = calloc(MAXBUFLINE, 1))) + return SAVE_ERR_CANT_ALLOC; + + if (!(output = fopen(state->filename, "wt"))) + return SAVE_ERR_CANT_OPEN_FILE; + + for (size_t row = 0; row < state->rows_count; row++) + { + unicode_string_to_u8(buf, state->buffer[row].text, MAXBUFLINE); + fprintf(output, "%s\n", buf); + } + + fclose(output); + free(buf); return SAVE_ERR_NONE; } @@ -843,6 +888,45 @@ insert_line(struct DrawState* state) return 0; } +int +reread_or_callback(struct DrawState* state) +{ + if (!state) + return 1; + if (state->dirty) + { + state->in_prompt = 1; + strcpy(state->prompt, prompt_reread_dirty); + state->prompt_callback = reread_callback; + } + else + reread_file(state); + return 0; +} + +int +write_file(struct DrawState* state) +{ + if (!state) + return 1; + + int result = save_file(state); + switch (result) + { + case SAVE_ERR_CANT_ALLOC: + strcpy(state->error, errors[ERR_CANT_ALLOC]); + break; + case SAVE_ERR_CANT_OPEN_FILE: + strcpy(state->error, errors[ERR_CANT_SAVE]); + break; + default: + state->error[0] = 0; + state->dirty = 0; + } + + return 0; +} + struct DrawState* join_lines(struct DrawState* state, size_t first_line, int forward) { @@ -1014,6 +1098,11 @@ erase_selection(struct DrawState* state) size_t ser = 0; size_t sec = 0; uint32_t* buf = NULL; + + buf = calloc(MAXBUFLINE, sizeof(uint32_t)); + if (!buf) + exit(print_error(ENOMEM, "Memory allocation failed")); + if (state->sel_start_row <= state->sel_end_row) { ssr = state->sel_start_row; @@ -1029,9 +1118,6 @@ erase_selection(struct DrawState* state) sec = state->sel_start_col; } - buf = calloc(state->buffer[ssr].length, sizeof(uint32_t)); - if (!buf) - exit(print_error(ENOMEM, "Memory allocation failed")); u32_strcpy(buf, state->buffer[ser].text + sec); *(state->buffer[ssr].text + ssc) = 0; @@ -1208,75 +1294,6 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) } strcpy(state->error, errors[ERR_UNKNOWN_KEY]); - /* -else if (ev->key == TB_KEY_CTRL_B || ev->key == TB_KEY_PGUP) - move_page_up(state); -else if (ev->key == TB_KEY_CTRL_F || ev->key == TB_KEY_PGDN) - move_page_down(state); -else if (ev->key == TB_KEY_CTRL_S || ev->ch == 'w') -{ - if (!state->dirty) - return; - int result = save_file(state->entries, state->real_msgid_count, - state->filename, state->nplurals); - switch (result) - { - case SAVE_ERR_CANT_ALLOC: - strcpy(state->error, errors[ERR_CANT_ALLOC]); - state->running = 0; - break; - case SAVE_ERR_CANT_OPEN_FILE: - strcpy(state->error, errors[ERR_CANT_SAVE]); - state->running = 0; - break; - case SAVE_ERR_CANT_MOVE_FILE: - strcpy(state->error, strerror(errno)); - state->running = 0; - break; - case SAVE_ERR_NONE: - default: - state->error[0] = 0; - } - state->dirty = 0; -} -else if (ev->key == TB_KEY_CTRL_Z || ev->ch == 'z') - ; // toggle_fuzzy(state); -else if (ev->ch == 'G') - move_bottom(state); -else if (ev->key == TB_KEY_END) - move_end(state); -else if (ev->key == TB_KEY_ENTER) -{ - if (state->msgid_count == 0) - strcpy(state->error, errors[ERR_EMPTY_FILE]); - else - { - state->msgstr_index = 0; - show_edit(state); - } -} -else if (ev->key == TB_KEY_F1 || ev->ch == 'H') -{ - state->show_help = 1; - *state->error = 0; -} -else if (ev->key == TB_KEY_F3 || ev->ch == 'n') - ; // next_match(state); -else if (ev->key == TB_KEY_F4 || ev->ch == 'N') - ; // prev_match(state); -else if (ev->key == TB_KEY_F7 || ev->ch == '/') - ; // show_search(state); -else if (ev->key == TB_KEY_F8 || ev->ch == 'u') - ; // next_untranslated(state); -else if (ev->key == TB_KEY_F9 || ev->ch == 'f') - ; // next_fuzzy(state); -else if (ev->ch == 'g') - move_top(state); -else if (ev->key == TB_KEY_HOME) - move_start(state); -else - strcpy(state->error, errors[ERR_UNKNOWN_KEY]); - */ } void diff --git a/util.h b/util.h @@ -22,6 +22,7 @@ enum { ERR_EMPTY_FILE, ERR_CANT_ALLOC, ERR_CANT_SAVE, + ERR_CANT_OPEN, ERR_CANT_MOVE, ERR_ILLEGAL_ON_FIRST, ERR_NO_PLURAL_FORMS