чување 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 | + |
| M | TODO | | | 6 | ++++-- |
| M | config.def.h | | | 17 | ++++++++++++----- |
| M | config.h | | | 17 | ++++++++++++----- |
| M | draw.c | | | 35 | +++++++++++++++-------------------- |
| M | sled.1.in | | | 111 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
| M | sled.c | | | 225 | ++++++++++++++++++++++++++++++++++++++++++------------------------------------- |
| M | util.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