aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/ring.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ring.c')
-rw-r--r--src/ring.c121
1 files changed, 109 insertions, 12 deletions
diff --git a/src/ring.c b/src/ring.c
index 0dbe911..afd6b25 100644
--- a/src/ring.c
+++ b/src/ring.c
@@ -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)");