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 b8557a969d0da663fe3175d19269cb2f37eb9e72
parent 7501cea8774e0b58705da53357947723f213cbf5
Author: lumidify <nobody@lumidify.org>
Date:   Thu,  9 Dec 2021 23:13:50 +0100

Change assert to dump contents of buffer before abort

Diffstat:
MMakefile | 2++
Aassert.c | 31+++++++++++++++++++++++++++++++
Aassert.h | 8++++++++
Mbuffer.c | 54+++++++++++++++++++++++++++++++++++++++---------------
Mbuffer.h | 18++++++++++++++++--
Mcache.c | 6+++---
Mcleanup.h | 1+
Mkeys_basic.c | 1-
Mkeys_command.c | 2+-
Mledit.c | 43+++++++++++++++++++++++++++++++++++++++++--
Mmemory.c | 14+++++++-------
Mundo.c | 4++--
Mview.c | 30+++++++++++++++---------------
Mwindow.c | 8++++----
14 files changed, 170 insertions(+), 52 deletions(-)

diff --git a/Makefile b/Makefile @@ -10,6 +10,7 @@ BIN = ${NAME} MAN1 = ${BIN:=.1} OBJ = \ + assert.o \ buffer.o \ view.o \ cache.o \ @@ -27,6 +28,7 @@ OBJ = \ pango-compat.o HDR = \ + assert.h \ buffer.h \ view.h \ cache.h \ diff --git a/assert.c b/assert.c @@ -0,0 +1,31 @@ +/* FIXME: sort out the stupid includes */ +#include <stdio.h> +#include <stdlib.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include <pango/pangoxft.h> +#include <X11/extensions/Xdbe.h> + +#include "pango-compat.h" +#include "memory.h" +#include "common.h" +#include "txtbuf.h" +#include "undo.h" +#include "cache.h" +#include "theme.h" +#include "window.h" +#include "buffer.h" +#include "cleanup.h" + +void +ledit_assert_impl(const char *file, int line, const char *func, const char *failedexpr) +{ + (void)fprintf(stderr, + "assertion \"%s\" failed: file \"%s\", line %d, function \"%s\"\n", + failedexpr, file, line, func); + ledit_emergencydump(); + abort(); + /* NOTREACHED */ +} diff --git a/assert.h b/assert.h @@ -0,0 +1,8 @@ +#ifndef _LEDIT_ASSERT_H_ +#define _LEDIT_ASSERT_H_ + +/* based on the assert found in OpenBSD */ +void ledit_assert_impl(const char *file, int line, const char *func, const char *failedexpr); +#define ledit_assert(e) ((e) ? (void)0 : ledit_assert_impl(__FILE__, __LINE__, __func__, #e)) + +#endif diff --git a/buffer.c b/buffer.c @@ -6,9 +6,9 @@ #include <stdio.h> #include <errno.h> #include <string.h> -#include <assert.h> #include <limits.h> #include <stdlib.h> +#include <unistd.h> #include <X11/Xlib.h> #include <X11/Xutil.h> @@ -25,6 +25,7 @@ #include "theme.h" #include "window.h" #include "buffer.h" +#include "assert.h" /* * Important notes: @@ -388,13 +389,9 @@ errorclose: return 1; } -/* FIXME: allow to write only certain lines */ int -buffer_write_to_file(ledit_buffer *buffer, char *filename, char **errstr) { - FILE *file; +buffer_write_to_file(ledit_buffer *buffer, FILE *file, char **errstr) { ledit_line *ll; - file = fopen(filename, "w"); - if (!file) goto error; clearerr(file); for (size_t i = 0; i < buffer->lines_num; i++) { ll = buffer_get_line(buffer, i); @@ -414,6 +411,32 @@ errorclose: return 1; } +int +buffer_write_to_fd(ledit_buffer *buffer, int fd, char **errstr) { + FILE *file = fdopen(fd, "w"); + if (!file) goto error; + return buffer_write_to_file(buffer, file, errstr); +error: + if (*errstr) + *errstr = strerror(errno); + /* catching errors on the close wouldn't + really make much sense anymore */ + close(fd); + return 1; +} + +/* FIXME: allow to write only certain lines */ +int +buffer_write_to_filename(ledit_buffer *buffer, char *filename, char **errstr) { + FILE *file = fopen(filename, "w"); + if (!file) goto error; + return buffer_write_to_file(buffer, file, errstr); +error: + if (*errstr) + *errstr = strerror(errno); + return 1; +} + void buffer_destroy(ledit_buffer *buffer) { ledit_line *l; @@ -439,8 +462,9 @@ buffer_normalize_line(ledit_line *line) { ); line->gap = line->len; } - /* FIXME: check if enough space, just to be sure */ - assert(line->len < line->cap); + /* this should never happen because the functions always + make sure to allocate one more for the '\0' */ + ledit_assert(line->len < line->cap); line->text[line->len] = '\0'; } @@ -455,7 +479,7 @@ buffer_insert_text_from_line_base( size_t dst_line, size_t dst_index, size_t src_line, size_t src_index, size_t src_len, txtbuf *text_ret) { - assert(dst_line != src_line); + ledit_assert(dst_line != src_line); ledit_line *ll = buffer_get_line(buffer, src_line); if (text_ret != NULL) { txtbuf_grow(text_ret, src_len); @@ -684,9 +708,9 @@ buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_ind static void buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2) { ledit_line *l; - assert (index2 >= index1); + ledit_assert(index2 >= index1); /* it isn't allowed to delete all lines */ - assert(index2 - index1 != buffer->lines_num); + ledit_assert(index2 - index1 != buffer->lines_num); for (size_t i = index1; i <= index2; i++) { l = buffer_get_line(buffer, i); free(l->text); @@ -700,7 +724,7 @@ buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t inde ledit_line * buffer_get_line(ledit_buffer *buffer, size_t index) { - assert(index < buffer->lines_num); + ledit_assert(index < buffer->lines_num); return index < buffer->lines_gap ? &buffer->lines[index] : &buffer->lines[index + buffer->lines_cap - buffer->lines_num]; @@ -729,7 +753,7 @@ buffer_recalc_all_lines(ledit_buffer *buffer) { size_t buffer_textlen(ledit_buffer *buffer, size_t line1, size_t byte1, size_t line2, size_t byte2) { - assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); + ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); size_t len = 0; ledit_line *ll = buffer_get_line(buffer, line1); if (line1 == line2) { @@ -753,7 +777,7 @@ buffer_textlen(ledit_buffer *buffer, size_t line1, size_t byte1, size_t line2, s backend is added, it would be good to optimize this, though. */ void buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2) { - assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); + ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); ledit_line *ll1 = buffer_get_line(buffer, line1); ledit_line *ll2 = buffer_get_line(buffer, line2); buffer_normalize_line(ll1); @@ -787,7 +811,7 @@ buffer_copy_text_to_txtbuf( txtbuf *buf, size_t line1, size_t byte1, size_t line2, size_t byte2) { - assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); + ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); size_t len = buffer_textlen(buffer, line1, byte1, line2, byte2); txtbuf_grow(buf, len + 1); buffer_copy_text(buffer, buf->text, line1, byte1, line2, byte2); diff --git a/buffer.h b/buffer.h @@ -95,12 +95,26 @@ void buffer_recalc_all_views_from_line(ledit_buffer *buffer, size_t line); int buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errstr); /* - * Write the buffer to a file. + * Write the buffer to a FILE pointer. * Returns 0 on success and 1 on error. In case of an error, *errstr is filled * with an error message which must be copied as soon as possible because it may * be overwritten by subsequent function calls. + * In all cases, 'file' is closed. */ -int buffer_write_to_file(ledit_buffer *buffer, char *filename, char **errstr); +int buffer_write_to_file(ledit_buffer *buffer, FILE *file, char **errstr); + +/* + * Write the buffer to a filename. + * Behaves the same as 'buffer_write_to_file'. + */ +int buffer_write_to_filename(ledit_buffer *buffer, char *filename, char **errstr); + +/* + * Write the buffer to a file descriptor. + * Behaves the same as 'buffer_write_to_file', but if the + * file descriptor cannot be fdopen'd, it is closed before returning. + */ +int buffer_write_to_fd(ledit_buffer *buffer, int fd, char **errstr); /* * Destroy a buffer. diff --git a/cache.c b/cache.c @@ -1,5 +1,4 @@ #include <stdio.h> -#include <assert.h> #include <limits.h> #include <stdlib.h> #include <X11/Xlib.h> @@ -10,6 +9,7 @@ #include "common.h" #include "memory.h" #include "cache.h" +#include "assert.h" ledit_cache * cache_create(Display *dpy) { @@ -84,13 +84,13 @@ cache_destroy(ledit_cache *cache) { cache_pixmap * cache_get_pixmap(ledit_cache *cache, size_t index) { - assert(index < cache->num_pixmaps); + ledit_assert(index < cache->num_pixmaps); return &cache->pixmaps[index]; } cache_layout * cache_get_layout(ledit_cache *cache, size_t index) { - assert(index < cache->num_layouts); + ledit_assert(index < cache->num_layouts); return &cache->layouts[index]; } diff --git a/cleanup.h b/cleanup.h @@ -1,3 +1,4 @@ /* This is here so it can be called from other places even though the function definition is in ledit.c */ void ledit_cleanup(void); +void ledit_emergencydump(void); diff --git a/keys_basic.c b/keys_basic.c @@ -11,7 +11,6 @@ them reliably yet */ #include <stdio.h> #include <stdlib.h> -#include <assert.h> #include <X11/Xlib.h> #include <X11/Xutil.h> diff --git a/keys_command.c b/keys_command.c @@ -118,7 +118,7 @@ handle_write(ledit_view *view, char *cmd, size_t l1, size_t l2) { /* FIXME: Implement properly; handle error */ char *errstr; if (view->buffer->filename) - buffer_write_to_file(view->buffer, view->buffer->filename, &errstr); + buffer_write_to_filename(view->buffer, view->buffer->filename, &errstr); return 0; } diff --git a/ledit.c b/ledit.c @@ -1,4 +1,4 @@ -/* FIXME: Add special assert that dumps currently edited file to backup on error */ +/* FIXME: clean up asserts a bit; clean up includes */ /* FIXME: On large files, expose event takes a long time for some reason -> but somehow only sometimes... */ /* FIXME: generally optimize redrawing */ @@ -18,7 +18,6 @@ #include <time.h> #include <stdio.h> #include <errno.h> -#include <assert.h> #include <string.h> #include <stdlib.h> #include <limits.h> @@ -255,6 +254,46 @@ setup(int argc, char *argv[]) { } void +ledit_emergencydump(void) { + if (!buffer) + return; + /* FIXME: maybe write assertion message to file? */ + char *orig = buffer->filename ? buffer->filename : "ledit"; + char *suffix = "-emergency-dump-XXXXXXXXXX"; + size_t len1, len2; + len1 = strlen(orig); + len2 = strlen(suffix); + /* This doesn't use ledit_strcat so a memory allocation + failure doesn't interfere with the abort in the assertion + that calls this function. */ + char *template = malloc(len1 + len2 + 1); + if (!template) + return; + strcpy(template, orig); + strcpy(template + len1, suffix); + int fd = mkstemp(template); + if (fd == -1) { + fprintf( + stderr, + "Unable to open file for emergency dump: %s\n", + strerror(errno) + ); + } + char *errstr; + if (buffer_write_to_fd(buffer, fd, &errstr)) { + fprintf( + stderr, + "Unable to perform emergency dump: %s\n", + errstr + ); + /* FIXME: maybe just leave the file in case at + least part of it was written? */ + unlink(template); + } + free(template); +} + +void ledit_cleanup(void) { /* FIXME: check for other things to clean up */ search_cleanup(); diff --git a/memory.c b/memory.c @@ -2,9 +2,9 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> -#include <assert.h> #include "cleanup.h" +#include "assert.h" static void fatal_err(const char *msg) { @@ -65,7 +65,7 @@ ledit_realloc(void *ptr, size_t size) { the actual strcat. Aborts program on error */ char * ledit_strcat(const char *str1, const char *str2) { - int len1, len2; + size_t len1, len2; char *ret; len1 = strlen(str1); @@ -103,9 +103,9 @@ move_gap( void *array, size_t elem_size, size_t index, size_t gap, size_t cap, size_t len, size_t *new_gap_ret) { - assert(array != NULL); - assert(index <= len); - assert(len <= cap); + ledit_assert(array != NULL); + ledit_assert(index <= len); + ledit_assert(len <= cap); char *carray = (char *)array; /* cast to char * for pointer arithmetic */ /* since the array has size cap * elem_size, it is assumed that no overflow happens */ if (index > gap) { @@ -139,8 +139,8 @@ resize_and_move_gap( size_t old_gap, size_t old_cap, size_t len, size_t min_size, size_t index, size_t *new_gap_ret, size_t *new_cap_ret) { - assert(index <= len); - assert(len <= old_cap); + ledit_assert(index <= len); + ledit_assert(len <= old_cap); size_t gap_size = old_cap - len; size_t new_cap = old_cap; /* FIXME: read up on what the best values are here */ diff --git a/undo.c b/undo.c @@ -1,5 +1,4 @@ #include <string.h> -#include <assert.h> #include <stdlib.h> #include <stdint.h> @@ -12,6 +11,7 @@ #include "txtbuf.h" #include "cache.h" #include "undo.h" +#include "assert.h" /* FIXME: more sanity checks in case text is inserted/deleted without adding to undo stack */ @@ -66,7 +66,7 @@ undo_stack_destroy(undo_stack *undo) { /* FIXME: resize text buffers when they aren't needed anymore */ static undo_elem * push_undo_elem(undo_stack *undo) { - assert(undo->cur >= -1); + ledit_assert(undo->cur >= -1); undo->cur++; undo->len = undo->cur + 1; if (undo->len > undo->cap) { diff --git a/view.c b/view.c @@ -4,7 +4,6 @@ #include <stdio.h> #include <errno.h> #include <string.h> -#include <assert.h> #include <limits.h> #include <stdlib.h> @@ -23,6 +22,7 @@ #include "theme.h" #include "window.h" #include "buffer.h" +#include "assert.h" /* Basic attributes set for all text. */ static PangoAttrList *basic_attrs = NULL; @@ -162,7 +162,7 @@ view_unlock(ledit_view *view) { ledit_view_line * view_get_line(ledit_view *view, size_t index) { - assert(index < view->lines_num); + ledit_assert(index < view->lines_num); return index < view->lines_gap ? &view->lines[index] : &view->lines[index + view->lines_cap - view->lines_num]; @@ -386,7 +386,7 @@ void render_line(ledit_view *view, size_t line_index) { /* FIXME: check for <= 0 on size */ ledit_view_line *ll = view_get_line(view, line_index); - assert(!ll->h_dirty); /* FIXME */ + ledit_assert(!ll->h_dirty); /* FIXME */ PangoLayout *layout = get_pango_layout(view, line_index); if (!ll->cache_pixmap_valid) { cache_assign_pixmap_index( @@ -884,9 +884,9 @@ void view_get_pos_softline_bounds( ledit_view *view, size_t line, size_t pos, size_t *start_byte_ret, size_t *end_byte_ret) { - assert(line < view->lines_num); + ledit_assert(line < view->lines_num); ledit_line *ll = buffer_get_line(view->buffer, line); - assert(pos <= ll->len); + ledit_assert(pos <= ll->len); PangoLayout *layout = get_pango_layout(view, line); int x, sli; if (pos > INT_MAX) @@ -901,10 +901,10 @@ void view_get_softline_bounds( ledit_view *view, size_t line, int softline, size_t *start_byte_ret, size_t *end_byte_ret) { - assert(line < view->lines_num); + ledit_assert(line < view->lines_num); ledit_view_line *vl = view_get_line(view, line); PangoLayout *layout = get_pango_layout(view, line); - assert(softline < vl->softlines); + ledit_assert(softline < vl->softlines); PangoLayoutLine *pl = pango_layout_get_line_readonly(layout, softline); *start_byte_ret = (size_t)pl->start_index; *end_byte_ret = (size_t)(pl->start_index + pl->length); @@ -912,7 +912,7 @@ view_get_softline_bounds( int view_get_softline_count(ledit_view *view, size_t line) { - assert(line < view->lines_num); + ledit_assert(line < view->lines_num); ledit_view_line *vl = view_get_line(view, line); if (vl->text_dirty) set_pango_text_and_highlight(view, line); @@ -921,9 +921,9 @@ view_get_softline_count(ledit_view *view, size_t line) { int view_pos_to_softline(ledit_view *view, size_t line, size_t pos) { - assert(line < view->lines_num); + ledit_assert(line < view->lines_num); ledit_line *ll = buffer_get_line(view->buffer, line); - assert(pos <= ll->len); + ledit_assert(pos <= ll->len); PangoLayout *layout = get_pango_layout(view, line); int x, sli; if (pos > INT_MAX) @@ -934,9 +934,9 @@ view_pos_to_softline(ledit_view *view, size_t line, size_t pos) { void view_get_cursor_pixel_pos(ledit_view *view, size_t line, size_t pos, int *x_ret, int *y_ret, int *h_ret) { - assert(line < view->lines_num); + ledit_assert(line < view->lines_num); ledit_line *ll = buffer_get_line(view->buffer, line); - assert(pos <= ll->len); + ledit_assert(pos <= ll->len); PangoLayout *layout = get_pango_layout(view, line); PangoRectangle strong, weak; if (pos > INT_MAX) @@ -1196,8 +1196,8 @@ view_delete_range_base( size_t cur_byte = byte_index1; view_sort_selection(&line_index1, &byte_index1, &line_index2, &byte_index2); size_t new_line = 0, new_byte = 0; - assert(line_index1 < view->lines_num); - assert(line_index2 < view->lines_num); + ledit_assert(line_index1 < view->lines_num); + ledit_assert(line_index2 < view->lines_num); ledit_range cur_range = {0, 0, 0, 0}; /* FIXME: could this be simplified by just calculating the range and then using the non-line-based version? */ @@ -1295,7 +1295,7 @@ view_delete_range_base( rgl1, rgb1, rgl2, rgb2, text_ret ); } else { - assert(pl2->start_index + pl2->length >= pl1->start_index); + ledit_assert(pl2->start_index + pl2->length >= pl1->start_index); rgl1 = rgl2 = line_index1; rgb1 = (size_t)pl1->start_index; rgb2 = (size_t)(pl2->start_index + pl2->length); diff --git a/window.c b/window.c @@ -2,7 +2,6 @@ #include <time.h> #include <math.h> #include <stdio.h> -#include <assert.h> #include <stdlib.h> #include <X11/Xlib.h> @@ -21,6 +20,7 @@ #include "util.h" #include "macros.h" #include "config.h" +#include "assert.h" /* FIXME: Everything to do with the bottom bar is extremely hacky */ struct bottom_bar { @@ -103,8 +103,8 @@ recalc_text_size(ledit_window *window) { /* FIXME: allow lines longer than window width to be displayed properly */ void window_insert_bottom_bar_text(ledit_window *window, char *text, int len) { - assert(len >= -1); - assert(window->bb->line_cur_pos <= window->bb->line_len); + ledit_assert(len >= -1); + ledit_assert(window->bb->line_cur_pos <= window->bb->line_len); if (len == -1) len = strlen(text); @@ -136,7 +136,7 @@ window_insert_bottom_bar_text(ledit_window *window, char *text, int len) { void window_move_bottom_bar_cursor(ledit_window *window, int movement) { - assert(window->bb->line_cur_pos <= window->bb->line_len); + ledit_assert(window->bb->line_cur_pos <= window->bb->line_len); int trailing = 0; int new_index = window->bb->line_cur_pos; pango_layout_move_cursor_visually(