/*
* 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