ledit

Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit 22ffb413e9e0be6a5764de4bcc951e6a0480bad3
parent b8557a969d0da663fe3175d19269cb2f37eb9e72
Author: lumidify <nobody@lumidify.org>
Date:   Fri, 10 Dec 2021 20:23:49 +0100

Improve file handling

Diffstat:
Mbuffer.c | 16++++++++++++----
Mbuffer.h | 2++
Mkeys_basic.c | 22++++++++++++++--------
Mkeys_command.c | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mledit.c | 57+++++++++++++++++++++++++++++++++++++++++++++++----------
Mtheme_config.h | 1+
Mwindow.c | 96++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mwindow.h | 14++++++++++++++
8 files changed, 231 insertions(+), 58 deletions(-)

diff --git a/buffer.c b/buffer.c @@ -191,8 +191,8 @@ buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, si } if (marklist->len == marklist->alloc) { size_t new_alloc = marklist->alloc > 0 ? marklist->alloc * 2 : 4; - marklist->marks = ledit_realloc( - marklist->marks, new_alloc * sizeof(ledit_buffer_mark) + marklist->marks = ledit_reallocarray( + marklist->marks, new_alloc, sizeof(ledit_buffer_mark) ); marklist->alloc = new_alloc; } @@ -248,6 +248,7 @@ buffer_create(ledit_common *common) { buffer->marklist = marklist_create(); buffer->filename = NULL; + memset(&buffer->file_mtime, 0, sizeof(buffer->file_mtime)); buffer->lines = NULL; buffer->lines_num = 0; buffer->lines_cap = 0; @@ -255,6 +256,7 @@ buffer_create(ledit_common *common) { buffer->views = NULL; buffer->views_num = 0; buffer->hard_line_based = 1; + buffer->modified = 0; /* add one empty line to buffer */ resize_and_move_line_gap(buffer, 1, 0); @@ -337,7 +339,6 @@ buffer_recalc_all_views_from_line(ledit_buffer *buffer, size_t line) { } } -/* FIXME: don't generate extra blank line at end! */ /* WARNING: errstr must be copied as soon as possible! */ int buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errstr) { @@ -355,6 +356,7 @@ buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errst if (fseek(file, 0, SEEK_SET)) goto errorclose; ll = buffer_get_line(buffer, line); + /* FIXME: insert in chunks instead of allocating huge buffer */ file_contents = ledit_malloc(len + 2); /* mimic nvi (or at least the openbsd version) - if the line is empty, insert directly, otherwise insert after the line */ @@ -367,7 +369,7 @@ buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errst if (ferror(file)) goto errorclose; file_contents[len + off] = '\0'; /* don't generate extra newline at end */ - if (len + off > 1 && file_contents[len + off - 1 == '\n']) { + if (len + off > 0 && file_contents[len + off - 1] == '\n') { file_contents[len + off - 1] = '\0'; len--; } @@ -377,6 +379,7 @@ buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errst buffer, line, ll->len, file_contents, len + off, NULL, NULL ); free(file_contents); + buffer->modified = 0; return 0; error: if (*errstr) @@ -399,6 +402,7 @@ buffer_write_to_file(ledit_buffer *buffer, FILE *file, char **errstr) { if (fprintf(file, "%s\n", ll->text) < 0) goto errorclose; } if (fclose(file)) goto error; + buffer->modified = 0; return 0; error: if (*errstr) @@ -951,6 +955,7 @@ buffer_undo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t buffer, min_line > 0 ? min_line - 1 : min_line ); } + buffer->modified = 1; } void @@ -965,6 +970,7 @@ buffer_redo(ledit_buffer *buffer, enum ledit_mode mode, size_t *cur_line, size_t buffer, min_line > 0 ? min_line - 1 : min_line ); } + buffer->modified = 1; } void @@ -989,6 +995,7 @@ buffer_delete_with_undo_base( ); if (text_ret == NULL) txtbuf_destroy(buf); + buffer->modified = 1; } void @@ -1039,6 +1046,7 @@ buffer_insert_with_undo_base( *line_ret = new_line; if (byte_ret != NULL) *byte_ret = new_byte; + buffer->modified = 1; } void diff --git a/buffer.h b/buffer.h @@ -28,6 +28,7 @@ typedef struct { struct ledit_buffer { ledit_common *common; /* common stuff, e.g. display, etc. */ char *filename; /* last opened filename */ + struct timespec file_mtime; /* last modified time of file */ undo_stack *undo; /* undo manager */ ledit_buffer_marklist *marklist; /* list of mark positions set */ ledit_line *lines; /* array of lines */ @@ -36,6 +37,7 @@ struct ledit_buffer { size_t lines_cap; /* size of lines array */ size_t lines_gap; /* position of gap for line gap buffer */ size_t lines_num; /* number of lines */ + int modified; /* whether buffer was modified since loading a file */ int hard_line_based; /* whether operations should work on soft- or hardlines Note that this doesn't actually change any behavior of the buffer functions, it is just stored here so all diff --git a/keys_basic.c b/keys_basic.c @@ -147,8 +147,8 @@ push_key_stack(void) { struct key_stack_elem *e; if (key_stack.len >= key_stack.alloc) { size_t new_alloc = key_stack.alloc > 0 ? key_stack.alloc * 2 : 4; - key_stack.stack = ledit_realloc( - key_stack.stack, new_alloc * sizeof(struct key_stack_elem) + key_stack.stack = ledit_reallocarray( + key_stack.stack, new_alloc, sizeof(struct key_stack_elem) ); key_stack.alloc = new_alloc; } @@ -275,9 +275,9 @@ push_repetition_stack(void) { struct repetition_stack_elem *e; if (repetition_stack.tmp_len >= repetition_stack.tmp_alloc) { size_t new_alloc = repetition_stack.tmp_alloc > 0 ? repetition_stack.tmp_alloc * 2 : 4; - repetition_stack.tmp_stack = ledit_realloc( + repetition_stack.tmp_stack = ledit_reallocarray( repetition_stack.tmp_stack, - new_alloc * sizeof(struct repetition_stack_elem) + new_alloc, sizeof(struct repetition_stack_elem) ); for (size_t i = repetition_stack.tmp_alloc; i < new_alloc; i++) { repetition_stack.tmp_stack[i].key_text = NULL; @@ -2281,13 +2281,19 @@ basic_key_handler(ledit_view *view, XEvent *event, int lang_index) { re->key_state = key_state; re->lang_index = lang_index; - /* FIXME: only hide when actually necessary */ - window_hide_message(view->window); + /* FIXME: figure out when to actually hide message and + ensure cursor shown */ + /* FIXME: clean up interface (what has a setter method and what not) */ int found = 0; + int msg_shown = view->window->message_shown; + view->window->message_shown = 0; /* FIXME: this is hacky */ struct action act = handle_key(view, buf, (size_t)n, sym, key_state, lang_index, &found); + if (found && n > 0 && !view->window->message_shown) + window_hide_message(view->window); + else + view->window->message_shown = msg_shown; - /* FIXME: only do this when necessary */ - if (found) + if (found && n > 0) view_ensure_cursor_shown(view); /* FIXME: maybe show error if not found */ return act; diff --git a/keys_command.c b/keys_command.c @@ -2,6 +2,8 @@ #include <stdio.h> #include <ctype.h> #include <stdlib.h> +#include <stdint.h> +#include <unistd.h> #include <X11/Xlib.h> #include <X11/Xutil.h> @@ -109,28 +111,74 @@ static int handle_substitute(ledit_view *view, char *cmd, size_t l1, size_t l2); static int parse_range(ledit_view *view, char *cmd, size_t len, char **cmd_ret, size_t *line1_ret, size_t *line2_ret, int *l1_valid, int *l2_valid); static int handle_cmd(ledit_view *view, char *cmd, size_t len); +/* FIXME: remove command name before passing to handlers */ static int handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) { - (void)view; - (void)cmd; (void)l1; (void)l2; - /* FIXME: Implement properly; handle error */ + /* FIXME: allow writing only part of file */ + char *filename = view->buffer->filename; + int stored = 1; + cmd++; /* remove 'w' */ + int force = 0; + if (*cmd == '!') { + force = 1; + cmd++; + } + /* FIXME: string parsing instead of just taking the rest of the line */ + if (cmd[0] == ' ' && cmd[1] != '\0') { + filename = cmd + 1; + stored = 0; + } + /* FIXME: file locks */ char *errstr; - if (view->buffer->filename) - buffer_write_to_filename(view->buffer, view->buffer->filename, &errstr); + if (filename) { + struct stat sb; + /* There technically is a race between checking stat and actually + trying to write the file, but I don't care at the moment. */ + int ret = 0; + if (!(ret = stat(filename, &sb)) && !force && stored && + (sb.st_mtim.tv_sec != view->buffer->file_mtime.tv_sec || + sb.st_mtim.tv_nsec != view->buffer->file_mtime.tv_nsec)) { + window_show_message_fmt( + view->window, + "%s: file modification time changed; use ! to override", + filename + ); + /* FIXME: I guess the file can still exist if stat returns an error, + but the writing itself will probably fail then as well. */ + } else if (!ret && !force && !stored) { + window_show_message_fmt( + view->window, + "%s: file exists; use ! to override", + filename + ); + } else if (buffer_write_to_filename(view->buffer, filename, &errstr)) { + window_show_message_fmt(view->window, "Error writing %s: %s", filename, errstr); + } else { + /* FIXME: better message */ + window_show_message_fmt(view->window, "Wrote file %s", filename); + } + } else { + window_show_message(view->window, "No file name", -1); + } return 0; } static int handle_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) { - (void)view; - (void)cmd; (void)l1; (void)l2; - /* FIXME: ask to save changes */ - ledit_cleanup(); - exit(0); + cmd++; + int force = 0; + if (*cmd == '!') + force = 1; + if (view->buffer->modified && !force) { + window_show_message(view->window, "File modified; write or use ! to override", -1); + } else { + ledit_cleanup(); + exit(0); + } return 0; } @@ -161,11 +209,9 @@ close_view(ledit_view *view, char *cmd, size_t l1, size_t l2) { static int handle_write_quit(ledit_view *view, char *cmd, size_t l1, size_t l2) { - (void)view; - (void)cmd; - (void)l1; - (void)l2; - printf("write quit\n"); + handle_write(view, cmd + 1, l1, l2); + ledit_cleanup(); + exit(0); return 0; } @@ -633,7 +679,10 @@ edit_submit(ledit_view *view, char *key_text, size_t len) { text += min_pos; } /* FIXME: this is hacky */ - return handle_cmd(view, text, (size_t)textlen); + char *cmd = ledit_strndup(text, textlen); + int ret = handle_cmd(view, cmd, (size_t)textlen); + free(cmd); + return ret; } static int diff --git a/ledit.c b/ledit.c @@ -234,21 +234,58 @@ setup(int argc, char *argv[]) { theme = theme_create(&common); buffer = buffer_create(&common); + buffer_add_view(buffer, theme, NORMAL, 0, 0, 0); + /* FIXME: don't access view directly here */ + ledit_view *view = buffer->views[0]; + view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); /* FIXME: Support multiple buffers/files */ + /* FIXME: check if file may be binary */ if (argc > 1) { + /* FIXME: move this to different file */ char *load_err; - if (buffer_load_file(buffer, argv[1], 0, &load_err)) { - fprintf(stderr, "Error opening file '%s': %s\n", argv[1], load_err); - ledit_cleanup(); - exit(1); + struct stat sb; + int newfile = 0; + int readonly = 0; + int error = 0; + /* FIXME: maybe copy vi and open file in /tmp by default? */ + if (stat(argv[1], &sb)) { + if (errno == ENOENT) { + /* note that there may still be a failure + when trying to write if a directory in + the path does not exist */ + newfile = 1; + } else { + window_show_message_fmt( + view->window, "Error opening file '%s': %s", + argv[1], strerror(errno) + ); + error = 1; + } + } + if (access(argv[1], W_OK)) { + readonly = 1; + } + if (!newfile) { + if (buffer_load_file(buffer, argv[1], 0, &load_err)) { + window_show_message_fmt( + view->window, "Error opening file '%s': %s", + argv[1], load_err + ); + error = 1; + } + buffer->file_mtime = sb.st_mtim; + } + if (!error) { + buffer->filename = ledit_strdup(argv[1]); + if (newfile) { + window_show_message_fmt(view->window, "%s: new file", argv[1]); + } else if (readonly) { + window_show_message_fmt(view->window, "%s: readonly", argv[1]); + } else { + window_show_message(view->window, argv[1], -1); + } } - /* FIXME: encapsulate */ - buffer->filename = ledit_strdup(argv[1]); } - buffer_add_view(buffer, theme, NORMAL, 0, 0, 0); - /* FIXME: don't access view directly here */ - ledit_view *view = buffer->views[0]; - view_set_line_cursor_attrs(view, view->cur_line, view->cur_index); redraw(); } diff --git a/theme_config.h b/theme_config.h @@ -1,3 +1,4 @@ +/* FIXME: different bg for bottom bar */ /* FIXME: configure font here */ static const int TEXT_SIZE = 12; static const char *TEXT_FG = "#000000"; diff --git a/window.c b/window.c @@ -3,6 +3,7 @@ #include <math.h> #include <stdio.h> #include <stdlib.h> +#include <stdarg.h> #include <X11/Xlib.h> #include <X11/Xatom.h> @@ -88,16 +89,44 @@ window_get_primary_clipboard_buffer(void) { return xsel.primary; } -/* FIXME static void ensure_cursor_shown(void); */ - /* FIXME: shouldn't window->bottom_text_shown also be true when message_shown? */ +/* FIXME: guard against negative width/height */ static void recalc_text_size(ledit_window *window) { int bar_h = window->bb->mode_h; - if ((window->bottom_text_shown || window->message_shown) && window->bb->line_h > bar_h) + if (window->bottom_text_shown || window->message_shown) bar_h = window->bb->line_h; window->text_w = window->w - window->theme->scrollbar_width; window->text_h = window->h - bar_h; + if (window->text_w < 0) + window->text_w = 0; + if (window->text_h < 0) + window->text_h = 0; +} + +static void +resize_line_text(ledit_window *window, int min_size) { + if (min_size > window->bb->line_alloc || window->bb->line_text == NULL) { + /* FIXME: read up on what the best values are here */ + /* FIXME: overflow */ + window->bb->line_alloc = + window->bb->line_alloc * 2 > min_size ? + window->bb->line_alloc * 2 : + min_size; + window->bb->line_text = ledit_realloc(window->bb->line_text, window->bb->line_alloc); + } +} + +static void +redraw_line_text(ledit_window *window) { + /* FIXME: set_text doesn't really belong here */ + pango_layout_set_text(window->bb->line, window->bb->line_text, window->bb->line_len); + pango_layout_get_pixel_size(window->bb->line, &window->bb->line_w, &window->bb->line_h); + draw_grow(window, window->bb->line_draw, window->bb->line_w, window->bb->line_h); + XftDrawRect(window->bb->line_draw->xftdraw, &window->theme->text_bg, 0, 0, window->bb->line_w, window->bb->line_h); + pango_xft_render_layout(window->bb->line_draw->xftdraw, &window->theme->text_fg, window->bb->line, 0, 0); + recalc_text_size(window); + window->redraw = 1; } /* FIXME: allow lines longer than window width to be displayed properly */ @@ -109,14 +138,7 @@ window_insert_bottom_bar_text(ledit_window *window, char *text, int len) { if (len == -1) len = strlen(text); /* \0 not included in len */ - if (window->bb->line_len + len + 1 > window->bb->line_alloc || window->bb->line_text == NULL) { - /* FIXME: read up on what the best values are here */ - window->bb->line_alloc = - window->bb->line_alloc * 2 > window->bb->line_len + len + 1 ? - window->bb->line_alloc * 2 : - window->bb->line_len + len + 1; - window->bb->line_text = ledit_realloc(window->bb->line_text, window->bb->line_alloc); - } + resize_line_text(window, window->bb->line_len + len + 1); memmove( window->bb->line_text + window->bb->line_cur_pos + len, window->bb->line_text + window->bb->line_cur_pos, @@ -125,13 +147,7 @@ window_insert_bottom_bar_text(ledit_window *window, char *text, int len) { memcpy(window->bb->line_text + window->bb->line_cur_pos, text, len); window->bb->line_len += len; window->bb->line_text[window->bb->line_len] = '\0'; - pango_layout_set_text(window->bb->line, window->bb->line_text, window->bb->line_len); - pango_layout_get_pixel_size(window->bb->line, &window->bb->line_w, &window->bb->line_h); - draw_grow(window, window->bb->line_draw, window->bb->line_w, window->bb->line_h); - XftDrawRect(window->bb->line_draw->xftdraw, &window->theme->text_bg, 0, 0, window->bb->line_w, window->bb->line_h); - pango_xft_render_layout(window->bb->line_draw->xftdraw, &window->theme->text_fg, window->bb->line, 0, 0); - recalc_text_size(window); - window->redraw = 1; + redraw_line_text(window); } void @@ -243,12 +259,17 @@ void window_set_bottom_bar_text_shown(ledit_window *window, int shown) { window->bottom_text_shown = shown; window->redraw = 1; + if (shown) { + window->message_shown = 0; + pango_layout_set_width(window->bb->line, -1); + redraw_line_text(window); + recalc_text_size(window); + } } int ledit_window_bottom_bar_text_shown(ledit_window *window) { return window->bottom_text_shown; - window->redraw = 1; } void @@ -275,6 +296,7 @@ window_get_bottom_bar_text(ledit_window *window) { void window_show_message(ledit_window *window, char *text, int len) { + pango_layout_set_width(window->bb->line, window->w * PANGO_SCALE); window_set_bottom_bar_text(window, text, len); /* FIXME: rename these */ window->bottom_text_shown = 0; @@ -282,6 +304,26 @@ window_show_message(ledit_window *window, char *text, int len) { window->redraw = 1; } +void +window_show_message_fmt(ledit_window *window, char *fmt, ...) { + va_list args; + va_start(args, fmt); + int len = vsnprintf(window->bb->line_text, window->bb->line_alloc, fmt, args); + if (len >= window->bb->line_alloc) { + va_end(args); + va_start(args, fmt); + /* +1 because of terminating '\0' */ + resize_line_text(window, len + 1); + vsnprintf(window->bb->line_text, window->bb->line_alloc, fmt, args); + } + window->bb->line_len = len; + va_end(args); + pango_layout_set_width(window->bb->line, window->w * PANGO_SCALE); + window->bottom_text_shown = 0; + window->message_shown = 1; + redraw_line_text(window); +} + int window_message_shown(ledit_window *window) { return window->message_shown; @@ -291,6 +333,7 @@ void window_hide_message(ledit_window *window) { window->message_shown = 0; window->redraw = 1; + recalc_text_size(window); } void @@ -543,6 +586,14 @@ window_create(ledit_common *common, ledit_theme *theme, enum ledit_mode mode) { window->bb->mode_draw = draw_create(window, 10, 10); window->bb->line = pango_layout_new(window->context); pango_layout_set_font_description(window->bb->line, window->font); + pango_layout_set_wrap(window->bb->line, PANGO_WRAP_WORD_CHAR); + #if PANGO_VERSION_CHECK(1, 44, 0) + PangoAttrList *pattrs = pango_attr_list_new(); + PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE); + pango_attr_list_insert(pattrs, no_hyphens); + pango_layout_set_attributes(window->bb->line, pattrs); + pango_attr_list_unref(pattrs); + #endif window->bb->line_draw = draw_create(window, 10, 10); window->bb->line_w = window->bb->line_h = 10; window->bb->line_text = NULL; @@ -766,7 +817,12 @@ void window_resize(ledit_window *window, int w, int h) { window->w = w; window->h = h; - recalc_text_size(window); + if (window->message_shown) { + pango_layout_set_width(window->bb->line, window->w * PANGO_SCALE); + redraw_line_text(window); + } else { + recalc_text_size(window); + } if (window->resize_callback) window->resize_callback(window->resize_cb_data); window->redraw = 1; diff --git a/window.h b/window.h @@ -5,6 +5,11 @@ * partially here and partially in keys_command, but that's the way it is for now. */ +#ifndef _WINDOW_H_ +#define _WINDOW_H_ + +#include <stdarg.h> + typedef struct bottom_bar bottom_bar; typedef struct { @@ -175,6 +180,13 @@ char *window_get_bottom_bar_text(ledit_window *window); void window_show_message(ledit_window *window, char *text, int len); /* + * Show a non-editable message that is given as a + * format string and arguments as interpreted by + * vsnprintf(3) + */ +void window_show_message_fmt(ledit_window *window, char *fmt, ...); + +/* * Hide the non-editable message. */ void window_hide_message(ledit_window *window); @@ -323,3 +335,5 @@ void window_clipboard_event(ledit_window *window, XEvent *event); * the position that text is being inserted at. */ void xximspot(ledit_window *window, int x, int y); + +#endif