/* * Copyright (C) 2012-2024 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "sciteco.h" #include "file-utils.h" #include "interface.h" #include "view.h" #include "undo.h" #include "parser.h" #include "core-commands.h" #include "expressions.h" #include "qreg.h" #include "glob.h" #include "error.h" #include "list.h" #include "ring.h" /** @private @static @memberof teco_buffer_t */ static teco_buffer_t * teco_buffer_new(void) { teco_buffer_t *ctx = g_new0(teco_buffer_t, 1); ctx->view = teco_view_new(); teco_view_setup(ctx->view); return ctx; } /** @private @memberof teco_buffer_t */ static void teco_buffer_set_filename(teco_buffer_t *ctx, const gchar *filename) { gchar *resolved = teco_file_get_absolute_path(filename); g_free(ctx->filename); ctx->filename = resolved; teco_interface_info_update(ctx); } /** @memberof teco_buffer_t */ void teco_buffer_edit(teco_buffer_t *ctx) { teco_interface_show_view(ctx->view); teco_interface_info_update(ctx); } /** @memberof teco_buffer_t */ void teco_buffer_undo_edit(teco_buffer_t *ctx) { undo__teco_interface_info_update_buffer(ctx); undo__teco_interface_show_view(ctx->view); } /** @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, 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 teco_buffer_set_filename(ctx, filename); return TRUE; } /** @private @memberof teco_buffer_t */ static gboolean teco_buffer_save(teco_buffer_t *ctx, const gchar *filename, GError **error) { if (!filename && !ctx->filename) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Cannot save the unnamed file " "without providing a file name"); return FALSE; } if (!teco_view_save(ctx->view, filename ? : ctx->filename, error)) return FALSE; /* * Undirtify * NOTE: info update is performed by set_filename() */ undo__teco_interface_info_update_buffer(ctx); teco_undo_gboolean(ctx->dirty) = FALSE; /* * FIXME: necessary also if the filename was not specified but the file * is (was) new, in order to canonicalize the filename. * May be circumvented by cananonicalizing without requiring the file * name to exist (like readlink -f) * NOTE: undo_info_update is already called above */ teco_undo_cstring(ctx->filename); teco_buffer_set_filename(ctx, filename ? : ctx->filename); return TRUE; } /** @private @memberof teco_buffer_t */ static inline void teco_buffer_free(teco_buffer_t *ctx) { teco_view_free(ctx->view); g_free(ctx->filename); g_free(ctx); } TECO_DEFINE_UNDO_CALL(teco_buffer_free, teco_buffer_t *); static teco_tailq_entry_t teco_ring_head = TECO_TAILQ_HEAD_INITIALIZER(&teco_ring_head); teco_buffer_t *teco_ring_current = NULL; teco_buffer_t * teco_ring_first(void) { return (teco_buffer_t *)teco_ring_head.first; } teco_buffer_t * teco_ring_last(void) { return (teco_buffer_t *)teco_ring_head.last->prev->next; } static void teco_undo_ring_edit_action(teco_buffer_t **buffer, gboolean run) { if (run) { /* * assumes that buffer still has correct prev/next * pointers */ if (teco_buffer_next(*buffer)) teco_tailq_insert_before((*buffer)->entry.next, &(*buffer)->entry); else teco_tailq_insert_tail(&teco_ring_head, &(*buffer)->entry); teco_ring_current = *buffer; teco_buffer_edit(*buffer); } else { teco_buffer_free(*buffer); } } /* * Emitted after a buffer close * The pointer is the only remaining reference to the buffer! */ static void teco_undo_ring_edit(teco_buffer_t *buffer) { teco_buffer_t **ctx = teco_undo_push_size((teco_undo_action_t)teco_undo_ring_edit_action, sizeof(buffer)); if (ctx) *ctx = buffer; else teco_buffer_free(buffer); } teco_int_t teco_ring_get_id(teco_buffer_t *buffer) { teco_int_t ret = 1; for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != &buffer->entry; cur = cur->next) ret++; return ret; } teco_buffer_t * teco_ring_find_by_name(const gchar *filename) { g_autofree gchar *resolved = teco_file_get_absolute_path(filename); for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) { teco_buffer_t *buffer = (teco_buffer_t *)cur; if (!g_strcmp0(buffer->filename, resolved)) return buffer; } return NULL; } teco_buffer_t * teco_ring_find_by_id(teco_int_t id) { for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) { if (!--id) return (teco_buffer_t *)cur; } return NULL; } void teco_ring_dirtify(void) { if (teco_qreg_current || teco_ring_current->dirty) 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); } gboolean teco_ring_is_any_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) return TRUE; } return FALSE; } gboolean 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)) return FALSE; } return TRUE; } gboolean teco_ring_edit_by_name(const gchar *filename, GError **error) { teco_buffer_t *buffer = teco_ring_find(filename); teco_qreg_table_current = NULL; teco_qreg_current = NULL; if (buffer) { teco_ring_current = buffer; teco_buffer_edit(buffer); return teco_ed_hook(TECO_ED_HOOK_EDIT, error); } buffer = teco_buffer_new(); teco_tailq_insert_tail(&teco_ring_head, &buffer->entry); teco_ring_current = buffer; teco_ring_undo_close(); teco_buffer_edit(buffer); if (filename && g_file_test(filename, G_FILE_TEST_IS_REGULAR)) { if (!teco_buffer_load(buffer, filename, error)) return FALSE; teco_interface_msg(TECO_MSG_INFO, "Added file \"%s\" to ring", filename); } else { teco_buffer_set_filename(buffer, filename); if (filename) teco_interface_msg(TECO_MSG_INFO, "Added new file \"%s\" to ring", filename); else teco_interface_msg(TECO_MSG_INFO, "Added new unnamed file to ring."); } return teco_ed_hook(TECO_ED_HOOK_ADD, error); } gboolean teco_ring_edit_by_id(teco_int_t id, GError **error) { teco_buffer_t *buffer = teco_ring_find(id); if (!buffer) { g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Invalid buffer id %" TECO_INT_FORMAT, id); return FALSE; } teco_qreg_table_current = NULL; teco_qreg_current = NULL; teco_ring_current = buffer; teco_buffer_edit(buffer); return teco_ed_hook(TECO_ED_HOOK_EDIT, error); } static void teco_ring_close_buffer(teco_buffer_t *buffer) { teco_tailq_remove(&teco_ring_head, &buffer->entry); if (buffer->filename) teco_interface_msg(TECO_MSG_INFO, "Removed file \"%s\" from the ring", buffer->filename); else teco_interface_msg(TECO_MSG_INFO, "Removed unnamed file from the ring."); } TECO_DEFINE_UNDO_CALL(teco_riHTTP/1.1 200 OK Connection: keep-alive Connection: keep-alive Content-Disposition: inline; filename="ring.c" Content-Disposition: inline; filename="ring.c" Content-Length: 15537 Content-Length: 15537 Content-Security-Policy: default-src 'none' Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Type: text/plain; charset=UTF-8 Date: Sun, 19 Oct 2025 09:44:47 UTC ETag: "d6faddab8bcad67f2346d452ffa21d646f342c9f" ETag: "d6faddab8bcad67f2346d452ffa21d646f342c9f" Expires: Wed, 17 Oct 2035 09:44:47 GMT Expires: Wed, 17 Oct 2035 09:44:47 GMT Last-Modified: Sun, 19 Oct 2025 09:44:47 GMT Last-Modified: Sun, 19 Oct 2025 09:44:47 GMT Server: OpenBSD httpd Server: OpenBSD httpd X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff /* * Copyright (C) 2012-2024 Robin Haberkorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "sciteco.h" #include "file-utils.h" #include "interface.h" #include "view.h" #include "undo.h" #include "parser.h" #include "core-commands.h" #include "expressions.h" #include "qreg.h" #include "glob.h" #include "error.h" #include "list.h" #include "ring.h" /** @private @static @memberof teco_buffer_t */ static teco_buffer_t * teco_buffer_new(void) { teco_buffer_t *ctx = g_new0(teco_buffer_t, 1); ctx->view = teco_view_new(); teco_view_setup(ctx->view); return ctx; } /** @private @memberof teco_buffer_t */ static void teco_buffer_set_filename(teco_buffer_t *ctx, const gchar *filename) { gchar *resolved = teco_file_get_absolute_path(filename); g_free(ctx->filename); ctx->filename = resolved; teco_interface_info_update(ctx); } /** @memberof teco_buffer_t */ void teco_buffer_edit(teco_buffer_t *ctx) { teco_interface_show_view(ctx->view); teco_interface_info_update(ctx); } /** @memberof teco_buffer_t */ void teco_buffer_undo_edit(teco_buffer_t *ctx) { undo__teco_interface_info_update_buffer(ctx); undo__teco_interface_show_view(ctx->view); } /** @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, 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 teco_buffer_set_filename(ctx, filename); return TRUE; } /** @private @memberof teco_buffer_t */ static gboolean teco_buffer_save(teco_buffer_t *ctx, const gchar *filename, GError **error) { if (!filename && !ctx->filename) { g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, "Cannot save the unnamed file " "without providing a file name"); return FALSE; } if (!teco_view_save(ctx->view, filename ? : ctx->filename, error)) return FALSE; /* * Undirtify * NOTE: info update is performed by set_filename() */ undo__teco_interface_info_update_buffer(ctx); teco_undo_gboolean(ctx->dirty) = FALSE; /* * FIXME: necessary also if the filename was not specified but the file * is (was) new, in order to canonicalize the filename. * May be circumvented by cananonicalizing without requiring the file * name to exist (like readlink -f) * NOTE: undo_info_update is already called above */ teco_undo_cstring(ctx->filename); teco_buffer_set_filename(ctx, filename ? : ctx->filename); return TRUE; } /** @private @memberof teco_buffer_t */ static inline void teco_buffer_free(teco_buffer_t *ctx) { teco_view_free(ctx->view); g_free(ctx->filename); g_free(ctx); } TECO_DEFINE_UNDO_CALL(teco_buffer_free, teco_buffer_t *); static teco_tailq_entry_t teco_ring_head = TECO_TAILQ_HEAD_INITIALIZER(&teco_ring_head); teco_buffer_t *teco_ring_current = NULL; teco_buffer_t * teco_ring_first(void) { return (teco_buffer_t *)teco_ring_head.first; } teco_buffer_t * teco_ring_last(void) { return (teco_buffer_t *)teco_ring_head.last->prev->next; } static void teco_undo_ring_edit_action(teco_buffer_t **buffer, gboolean run) { if (run) { /* * assumes that buffer still has correct prev/next * pointers */ if (teco_buffer_next(*buffer)) teco_tailq_insert_before((*buffer)->entry.next, &(*buffer)->entry); else teco_tailq_insert_tail(&teco_ring_head, &(*buffer)->entry); teco_ring_current = *buffer; teco_buffer_edit(*buffer); } else { teco_buffer_free(*buffer); } } /* * Emitted after a buffer close * The pointer is the only remaining reference to the buffer! */ static void teco_undo_ring_edit(teco_buffer_t *buffer) { teco_buffer_t **ctx = teco_undo_push_size((teco_undo_action_t)teco_undo_ring_edit_action, sizeof(buffer)); if (ctx) *ctx = buffer; else teco_buffer_free(buffer); } teco_int_t teco_ring_get_id(teco_buffer_t *buffer) { teco_int_t ret = 1; for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != &buffer->entry; cur = cur->next) ret++; return ret; } teco_buffer_t * teco_ring_find_by_name(const gchar *filename) { g_autofree gchar *resolved = teco_file_get_absolute_path(filename); for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) { teco_buffer_t *buffer = (teco_buffer_t *)cur; if (!g_strcmp0(buffer->filename, resolved)) return buffer; } return NULL; } teco_buffer_t * teco_ring_find_by_id(teco_int_t id) { for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) { if (!--id) return (teco_buffer_t *)cur; } return NULL; } void teco_ring_dirtify(void) { if (teco_qreg_current || teco_ring_current->dirty) 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); } gboolean teco_ring_is_any_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) return TRUE; } return FALSE; } gboolean 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)) return FALSE; } return TRUE; } gboolean teco_ring_edit_by_name(const gchar *filename, GError **error) { teco_buffer_t *buffer = teco_ring_find(filename); teco_qreg_table_current = NULL; teco_qreg_current = NULL; if (buffer) { teco_ring_current = buffer; teco_buffer_ed