diff options
Diffstat (limited to 'src/ring.c')
| -rw-r--r-- | src/ring.c | 121 |
1 files changed, 109 insertions, 12 deletions
@@ -21,6 +21,7 @@ #include <glib.h> #include <glib/gprintf.h> +#include <glib/gstdio.h> #include <Scintilla.h> @@ -75,16 +76,21 @@ teco_buffer_undo_edit(teco_buffer_t *ctx) } /** @private @memberof teco_buffer_t */ +static inline gchar * +teco_buffer_get_backup(teco_buffer_t *ctx) +{ + return g_strconcat(ctx->filename, "~", NULL); +} + +/** @private @memberof teco_buffer_t */ static gboolean teco_buffer_load(teco_buffer_t *ctx, const gchar *filename, GError **error) { if (!teco_view_load(ctx->view, filename, TRUE, error)) return FALSE; -#if 0 /* NOTE: currently buffer cannot be dirty */ - undo__teco_interface_info_update_buffer(ctx); - teco_undo_gboolean(ctx->dirty) = FALSE; -#endif + /* currently buffer cannot be dirty */ + g_assert(ctx->state == TECO_BUFFER_CLEAN); teco_buffer_set_filename(ctx, filename); return TRUE; @@ -110,7 +116,13 @@ teco_buffer_save(teco_buffer_t *ctx, const gchar *filename, GError **error) */ if (ctx == teco_ring_current && !teco_qreg_current) undo__teco_interface_info_update_buffer(ctx); - teco_undo_gboolean(ctx->dirty) = FALSE; + if (ctx->state == TECO_BUFFER_DIRTY_BACKEDUP) { + g_autofree gchar *filename_backup = teco_buffer_get_backup(ctx); + g_unlink(filename_backup); + /* on rubout, we do not restore the backup file */ + ctx->state = TECO_BUFFER_DIRTY; + } + teco_undo_guint(ctx->state) = TECO_BUFFER_CLEAN; /* * FIXME: necessary also if the filename was not specified but the file @@ -129,6 +141,11 @@ teco_buffer_save(teco_buffer_t *ctx, const gchar *filename, GError **error) static inline void teco_buffer_free(teco_buffer_t *ctx) { + if (ctx->state == TECO_BUFFER_DIRTY_BACKEDUP) { + g_autofree gchar *filename_backup = teco_buffer_get_backup(ctx); + g_unlink(filename_backup); + } + teco_view_free(ctx->view); g_free(ctx->filename); g_free(ctx); @@ -222,15 +239,40 @@ teco_ring_find_by_id(teco_int_t id) return NULL; } +static void +teco_ring_undirtify(void) +{ + if (teco_ring_current->state == TECO_BUFFER_DIRTY_BACKEDUP) { + g_autofree gchar *filename_backup = teco_buffer_get_backup(teco_ring_current); + g_unlink(filename_backup); + } + + teco_ring_current->state = TECO_BUFFER_CLEAN; + teco_interface_info_update(teco_ring_current); +} + +TECO_DEFINE_UNDO_CALL(teco_ring_undirtify); + void teco_ring_dirtify(void) { - if (teco_qreg_current || teco_ring_current->dirty) + if (teco_qreg_current) return; - undo__teco_interface_info_update_buffer(teco_ring_current); - teco_undo_gboolean(teco_ring_current->dirty) = TRUE; - teco_interface_info_update(teco_ring_current); + teco_buffer_state_t old_state = teco_ring_current->state; + teco_ring_current->state = TECO_BUFFER_DIRTY; + switch (old_state) { + case TECO_BUFFER_CLEAN: + teco_interface_info_update(teco_ring_current); + undo__teco_ring_undirtify(); + break; + case TECO_BUFFER_DIRTY: + break; + case TECO_BUFFER_DIRTY_BACKEDUP: + /* set to TECO_BUFFER_DIRTY on rubout */ + teco_undo_guint(teco_ring_current->state); + break; + } } /** Get id of first dirty buffer, or otherwise 0 */ @@ -241,7 +283,7 @@ teco_ring_get_first_dirty(void) for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) { teco_buffer_t *buffer = (teco_buffer_t *)cur; - if (buffer->dirty) + if (buffer->state > TECO_BUFFER_CLEAN) return id; id++; } @@ -255,13 +297,68 @@ teco_ring_save_all_dirty_buffers(GError **error) for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) { teco_buffer_t *buffer = (teco_buffer_t *)cur; /* NOTE: Will fail for a dirty unnamed file */ - if (buffer->dirty && !teco_buffer_save(buffer, NULL, error)) + if (buffer->state > TECO_BUFFER_CLEAN && + !teco_buffer_save(buffer, NULL, error)) return FALSE; } return TRUE; } +/** + * Backup interval in seconds or 0 if disabled. + * It's not currently enforced in batch mode. + */ +guint teco_ring_backup_interval = 5*60; + +/** + * Back up all dirty buffers. + * + * Should be called by the interface every teco_ring_backup_interval seconds. + * This does not generate or expect undo tokens, so it can be called + * even when idlying. + */ +void +teco_ring_backup(void) +{ + g_assert(teco_ring_backup_interval > 0); + + for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) { + teco_buffer_t *buffer = (teco_buffer_t *)cur; + /* + * Dirty unnamed buffers cannot be backed up. + * Already backed-up buffers don't have to be written again. + * FIXME: Perhaps they should be under ~/UNNAMED~? + */ + if (buffer->state != TECO_BUFFER_DIRTY || !buffer->filename) + continue; + + g_autofree gchar *filename_backup = teco_buffer_get_backup(buffer); + + g_autoptr(GIOChannel) channel = g_io_channel_new_file(filename_backup, "w", NULL); + if (!channel) + continue; + + /* + * teco_view_save_to_channel() expects a buffered and blocking channel. + */ + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, TRUE); + + /* + * This does not use teco_view_save_to_file() since we must not + * emit undo tokens. + * + * FIXME: Errors are silently ignored. + * Should we log warnings instead? + */ + if (!teco_view_save_to_channel(buffer->view, channel, NULL)) + continue; + + buffer->state = TECO_BUFFER_DIRTY_BACKEDUP; + } +} + gboolean teco_ring_edit_by_name(const gchar *filename, GError **error) { @@ -719,7 +816,7 @@ teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error) if (teco_machine_main_eval_colon(ctx) > 0) { if (!teco_buffer_save(buffer, NULL, error)) return; - } else if (!force && buffer->dirty) { + } else if (!force && buffer->state > TECO_BUFFER_CLEAN) { g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Buffer \"%s\" is dirty", buffer->filename ? : "(Unnamed)"); |
