commit 22ffb413e9e0be6a5764de4bcc951e6a0480bad3
parent b8557a969d0da663fe3175d19269cb2f37eb9e72
Author: lumidify <nobody@lumidify.org>
Date: Fri, 10 Dec 2021 20:23:49 +0100
Improve file handling
Diffstat:
M | buffer.c | | | 16 | ++++++++++++---- |
M | buffer.h | | | 2 | ++ |
M | keys_basic.c | | | 22 | ++++++++++++++-------- |
M | keys_command.c | | | 81 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
M | ledit.c | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++---------- |
M | theme_config.h | | | 1 | + |
M | window.c | | | 96 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------- |
M | window.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