262 lines
7.5 KiB
C
262 lines
7.5 KiB
C
|
/* 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 <https://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#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;
|
||
|
}
|