/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * 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 . */ #include "config.h" #include "libpika/pika.h" #include "script-fu-console-history.h" static gint console_history_tail_position (CommandHistory *self); static GStrv console_history_to_strv (CommandHistory *self); /* CommandHistory * * Not a true class, just a struct with methods. * Does not inherit GObject. * * Is a model that affects a view of the "console history." * The model is a sequence of ExecutedCommands. * The sequence is a time-ordered queue. * You can only append to the end, called the tail. * An ExecutedCommand does not contain the result of interpretation, * only the string that was interpreted. * * The sequence is finite. * When you append to the tail, * commands might be discarded from the head. * * Has a cursor. * You can only get the command at the cursor. * The user scrolling through the history moves the cursor. * This scrolling is arrow keys in the editor pane, * (not scroll bars in the history view pane.) * See the main console logic: * when user hits arrow keys in the editor, * move cursor in the history, get the command at the cursor, * and display it in the editor, ready to execute. * * A CommandHistory is a model, * but there is also a distinct TotalHistory model for a scrolling view of the history * (e.g. a GtkTextBuffer model for a GtkTextView.) * * CommandHistory <-supersets- TotalHistory <-views- ConsoleView * * TotalHistory contains more than the commands in CommandHistory. * TotalHistory contains e.g. splash, prompts, and interpretation results. * * !!! Self does not currently write TotalHistory; * The main console logic writes TotalHistory, * * CommandHistory is persistent across sessions of the ScriptFu Console, * and across sessions of Pika. * When the SFConsole starts, the TotalHistory, * is just the CommandHistory, without results of eval. * Old results are not meaningful since the environment changed. * Specifically, a new session of SFConsole has a new initialized interpreter. * Similarly, when the user closes the console, * only the CommandHistory is saved as settings. */ void console_history_init (CommandHistory *self) { self->model = g_list_append (self->model, NULL); self->model_len = 1; self->model_max = 100; } /* Store the command in tail of CommandHistory. * The tail is the most recent added element, which was created prior by new_tail. * * @commmand transfer full * * !!! The caller is executing the command. * The caller updates TotalHistory, with a prompt string and the command string. * Self does not update TotalHistory, the model of the view. */ void console_history_set_tail (CommandHistory *self, const gchar *command) { GList *list; list = g_list_nth (self->model, console_history_tail_position (self)); if (list->data) g_free (list->data); /* Discarding const. */ list->data = (gpointer) command; } /* Remove the head of the history and free its string. * * GList doesn't have such a direct method. * Search web to find this solution. * !!! g_list_remove does not free the data of the removed element. * * Remove the element whose data (a string) * matches the data of the first element. * Then free the data of the first element. */ static void console_history_remove_head (CommandHistory *self) { gpointer * data; g_return_if_fail (self->model != NULL); data = self->model->data; self->model = g_list_remove (self->model, data); g_free (data); } /* Append NULL string at tail of CommandHistory. * Prune head when max exceeded, freeing the string. * Position the cursor at last element. */ void console_history_new_tail (CommandHistory *self) { self->model = g_list_append (self->model, NULL); if (self->model_len == self->model_max) { console_history_remove_head (self); } else { self->model_len++; } self->model_cursor = console_history_tail_position (self); } void console_history_cursor_to_tail (CommandHistory *self) { self->model_cursor = console_history_tail_position (self); } gboolean console_history_is_cursor_at_tail (CommandHistory *self) { return self->model_cursor == console_history_tail_position (self); } void console_history_move_cursor (CommandHistory *self, gint direction) { self->model_cursor += direction; /* Clamp cursor in range [0, model_len-1] */ if (self->model_cursor < 0) self->model_cursor = 0; if (self->model_cursor >= self->model_len) self->model_cursor = self->model_len - 1; } const gchar * console_history_get_at_cursor (CommandHistory *self) { return g_list_nth (self->model, self->model_cursor)->data; } /* Methods for persisting history as a setting. */ /* Return a GStrv of the history from settings. * The Console knows how to put GStrv to both models! * * !!! Handle attack on settings file. * The returned cardinality of the set of strings * may be zero or very many. * Elsewhere ensure we don't overflow models. */ GStrv console_history_from_settings (CommandHistory *self, PikaProcedureConfig *config) { GStrv in_history; /* Get aux arg from property of config. */ g_object_get (config, "history", &in_history, NULL); return in_history; } void console_history_to_settings (CommandHistory *self, PikaProcedureConfig *config) { GStrv out_history; out_history = console_history_to_strv (self); /* set an aux arg in config. */ g_object_set (config, "history", out_history, NULL); } /* Return history model as GStrv. * Converts from interal list into a string array. * * !!! The exported history may have a tail * which is user's edits to the command line, * that the user never evaluated. * Exported history does not have an empty tail. * * Caller must g_strfreev the returned GStrv. */ static GStrv console_history_to_strv (CommandHistory *self) { GStrv history_strv; GStrvBuilder *builder; builder = g_strv_builder_new (); /* Order is earliest first. */ for (GList *l = self->model; l != NULL; l = l->next) { /* Don't write an empty pre-allocated tail. */ if (l->data != NULL) g_strv_builder_add (builder, l->data); } history_strv = g_strv_builder_end (builder); g_strv_builder_unref (builder); return history_strv; } static gint console_history_tail_position (CommandHistory *self) { return g_list_length (self->model) - 1; }