SF Console history persist as GStrv setting.

Other driveby format and wording changes to dialog title, welcome text.

Part of issue 9579 SF roadmap
This commit is contained in:
bootchk 2023-06-15 16:22:27 -04:00 committed by Jehan
parent 691ec70c0e
commit 7b9b5db69e
7 changed files with 247 additions and 49 deletions

View File

@ -53,7 +53,9 @@ console_editor_set_text_and_position (GtkWidget *self,
const gchar *text,
gint position)
{
gtk_entry_set_text (GTK_ENTRY (self), text);
/* gtk_entry_set_text not allow NULL */
if (text != NULL)
gtk_entry_set_text (GTK_ENTRY (self), text);
gtk_editable_set_position (GTK_EDITABLE (self), position);
}

View File

@ -17,10 +17,13 @@
#include "config.h"
#include <gtk/gtk.h>
#include "libgimp/gimp.h"
#include "script-fu-console-history.h"
static gint console_history_tail_position (CommandHistory *self);
static GStrv console_history_to_strv (CommandHistory *self);
/* CommandHistory
*
@ -60,24 +63,22 @@
* !!! Self does not currently write TotalHistory;
* The main console logic writes TotalHistory,
*
* FUTURE:
* CommandHistory is persistent across sessions of the ScriptFu Console,
* and across sessions of Gimp.
* When the SFConsole starts, the TotalHistory,
* is just the CommandHistory, without results.
* 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 quits the console,
* Similarly, when the user closes the console,
* only the CommandHistory is saved as settings.
*/
void
console_history_init (CommandHistory *self,
GtkTextBuffer *model_of_view)
console_history_init (CommandHistory *self)
{
self->model = g_list_append (self->model, NULL);
self->model_len = 1;
self->model_max = 50;
self->model_max = 100;
}
@ -98,7 +99,7 @@ console_history_set_tail (CommandHistory *self,
GList *list;
list = g_list_nth (self->model,
(g_list_length (self->model) - 1));
console_history_tail_position (self));
if (list->data)
g_free (list->data);
@ -107,8 +108,30 @@ console_history_set_tail (CommandHistory *self,
list->data = (gpointer) command;
}
/* Allocate an empty element at tail of CommandHistory.
* Prune head when max exceeded.
/* 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
@ -118,23 +141,26 @@ console_history_new_tail (CommandHistory *self)
if (self->model_len == self->model_max)
{
/* FIXME: is this correct, seems to be double freeing the head. */
self->model = g_list_remove (self->model, self->model->data);
if (self->model->data)
g_free (self->model->data);
console_history_remove_head (self);
}
else
{
self->model_len++;
}
self->model_cursor = g_list_length (self->model) - 1;
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 == g_list_length (self->model) - 1;
return self->model_cursor == console_history_tail_position (self);
}
void
@ -143,6 +169,7 @@ console_history_move_cursor (CommandHistory *self,
{
self->model_cursor += direction;
/* Clamp cursor in range [0, model_len-1] */
if (self->model_cursor < 0)
self->model_cursor = 0;
@ -154,4 +181,78 @@ 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,
GimpProcedureConfig *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,
GimpProcedureConfig *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;
}

View File

@ -28,8 +28,7 @@ typedef struct
} CommandHistory;
void console_history_init (CommandHistory *self,
GtkTextBuffer *total_history);
void console_history_init (CommandHistory *self);
void console_history_new_tail (CommandHistory *self);
void console_history_set_tail (CommandHistory *self,
@ -37,8 +36,14 @@ void console_history_set_tail (CommandHistory *self,
void console_history_move_cursor (CommandHistory *self,
gint direction);
void console_history_cursor_to_tail (CommandHistory *self);
gboolean console_history_is_cursor_at_tail (CommandHistory *self);
const gchar *console_history_get_at_cursor (CommandHistory *self);
GStrv console_history_from_settings (CommandHistory *self,
GimpProcedureConfig *config);
void console_history_to_settings (CommandHistory *self,
GimpProcedureConfig *config);
#endif /* __SCRIPT_FU_CONSOLE_HISTORY_H__ */

View File

@ -86,13 +86,11 @@ console_total_append_welcome (GtkTextBuffer *self)
{
const gchar * const greetings[] =
{
"strong", N_("Welcome to TinyScheme"),
"emphasis", N_("Welcome to TinyScheme"),
NULL, "\n",
NULL, "Copyright (c) Dimitrios Souflis",
"emphasis", "Copyright (c) Dimitrios Souflis",
NULL, "\n",
"strong", N_("Script-Fu Console"),
NULL, " - ",
"emphasis", N_("Interactive Scheme Development"),
"emphasis", N_("Scripting GIMP in the Scheme language"),
NULL, "\n"
};
@ -125,6 +123,7 @@ console_total_append_text_normal (GtkTextBuffer *self,
gtk_text_buffer_get_end_iter (self, &cursor);
gtk_text_buffer_insert (self, &cursor, text, len);
gtk_text_buffer_insert (self, &cursor, "\n", 1);
}
void
@ -139,6 +138,7 @@ console_total_append_text_emphasize (GtkTextBuffer *self,
&cursor,
text, len, "emphasis",
NULL);
gtk_text_buffer_insert (self, &cursor, "\n", 1);
}
/* Write newlines, prompt, and command. */
@ -150,7 +150,8 @@ console_total_append_command (GtkTextBuffer *self,
gtk_text_buffer_get_end_iter (self, &cursor);
gtk_text_buffer_insert (self, &cursor, "\n", 1);
/* assert we are just after a newline. */
/* Write repr of a prompt.
* SFConsole doesn't have a prompt in it's command line,
* But we show one in the history view to distinguish commands.

View File

@ -80,12 +80,17 @@ static void script_fu_browse_row_activated (GtkDialog *dialog);
static gboolean script_fu_editor_key_function (GtkWidget *widget,
GdkEventKey *event,
ConsoleInterface *console);
static void script_fu_console_scroll_end (GtkWidget *view);
static void script_fu_output_to_console (gboolean is_error,
const gchar *text,
gint len,
gpointer user_data);
static void script_fu_models_from_settings (ConsoleInterface *console,
GimpProcedureConfig *config);
static void script_fu_command_to_history (ConsoleInterface *console,
const gchar *command);
/*
* Function definitions
*/
@ -100,11 +105,24 @@ script_fu_console_run (GimpProcedure *procedure,
GtkWidget *scrolled_window;
GtkWidget *hbox;
GimpProcedureConfig *config;
script_fu_set_print_flag (1);
gimp_ui_init ("script-fu");
console.dialog = gimp_dialog_new (_("Script-Fu Console"),
/* Create model early so we can fill from settings. */
console.total_history = console_total_history_new ();
console_history_init (&console.history);
console_total_append_welcome (console.total_history);
/* Get previous or default settings into config. */
config = gimp_procedure_create_config (procedure);
gimp_procedure_config_begin_run (config, NULL, GIMP_RUN_INTERACTIVE, args);
script_fu_models_from_settings (&console, config);
console.dialog = gimp_dialog_new (_("Script Console"),
"gimp-script-fu-console",
NULL, 0,
gimp_standard_help_func, PROC_NAME,
@ -145,8 +163,6 @@ script_fu_console_run (GimpProcedure *procedure,
gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0);
gtk_widget_show (scrolled_window);
console.total_history = console_total_history_new ();
console.history_view = gtk_text_view_new_with_buffer (console.total_history);
/* View keeps reference. Unref our ref so buffer is destroyed with view. */
g_object_unref (console.total_history);
@ -160,8 +176,6 @@ script_fu_console_run (GimpProcedure *procedure,
gtk_container_add (GTK_CONTAINER (scrolled_window), console.history_view);
gtk_widget_show (console.history_view);
console_total_append_welcome (console.total_history);
/* An editor of a command to be executed. */
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
@ -188,10 +202,11 @@ script_fu_console_run (GimpProcedure *procedure,
G_CALLBACK (script_fu_browse_callback),
&console);
console_history_init (&console.history, console.total_history);
gtk_widget_show (console.dialog);
/* The history model may fill the view, scroll. */
script_fu_console_scroll_end (console.history_view);
gtk_main ();
if (console.save_dialog)
@ -200,6 +215,11 @@ script_fu_console_run (GimpProcedure *procedure,
if (console.dialog)
gtk_widget_destroy (console.dialog);
/* Update config with user's change to history */
console_history_to_settings (&console.history, config);
/* Persist config */
gimp_procedure_config_end_run (config, GIMP_PDB_SUCCESS);
return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL);
}
@ -425,12 +445,12 @@ script_fu_console_scroll_end (GtkWidget *view)
g_idle_add ((GSourceFunc) script_fu_console_idle_scroll_end, view);
}
/* Write results of execution to the console view.
/* Write result of eval to the console view.
* But not put results in the history model.
*/
static void
script_fu_output_to_console (gboolean is_error_msg,
const gchar *text,
const gchar *result_text,
gint len,
gpointer user_data)
{
@ -439,9 +459,9 @@ script_fu_output_to_console (gboolean is_error_msg,
if (console && console->history_view)
{
if (! is_error_msg)
console_total_append_text_normal (console->total_history, text, len);
console_total_append_text_normal (console->total_history, result_text, len);
else
console_total_append_text_emphasize (console->total_history, text, len);
console_total_append_text_emphasize (console->total_history, result_text, len);
script_fu_console_scroll_end (console->history_view);
}
@ -468,16 +488,9 @@ script_fu_editor_key_function (GtkWidget *widget,
return TRUE;
command = g_strdup (console_editor_get_text (console->editor));
/* Put to history model.
*
* We own the command.
* This transfers ownership to history model.
* We retain a reference, used below, but soon out of scope.
*/
console_history_set_tail (&console->history, command);
/* Put decorated command to total_history, distinct from history model. */
console_total_append_command (console->total_history, command);
script_fu_command_to_history (console, command);
/* Assert history advanced to new, empty tail. */
script_fu_console_scroll_end (console->history_view);
@ -503,8 +516,6 @@ script_fu_editor_key_function (GtkWidget *widget,
gimp_displays_flush ();
console_history_new_tail (&console->history);
return TRUE;
break;
@ -531,6 +542,11 @@ script_fu_editor_key_function (GtkWidget *widget,
break;
default:
/* Any other key is the user editing.
* Set cursor to tail: user is done scrolling history.
* Must do this to ensure edited command line is saved in history.
*/
console_history_cursor_to_tail (&console->history);
break;
}
@ -541,12 +557,14 @@ script_fu_editor_key_function (GtkWidget *widget,
* So any edited text is not lost if user moves cursor back to tail.
*/
command = console_editor_get_text (console->editor);
/* command can be NULL */
if (console_history_is_cursor_at_tail (&console->history))
console_history_set_tail (&console->history, g_strdup (command));
/* Now move cursor and replace editor contents. */
console_history_move_cursor (&console->history, direction);
command = console_history_get_at_cursor (&console->history);
/* command can be NULL. */
console_editor_set_text_and_position (console->editor,
command,
-1);
@ -555,4 +573,70 @@ script_fu_editor_key_function (GtkWidget *widget,
}
return FALSE;
}
/* Restore models from settings.
* This understands how to get history as a GStrv from settings
* and how to put GStrv into both models.
*
* Just the model. The view does not exist yet.
*/
static void
script_fu_models_from_settings (ConsoleInterface *console,
GimpProcedureConfig *config)
{
GStrv strings_in;
/* Assert the History model is empty, recently init. */
strings_in = console_history_from_settings (&console->history, config);
/* The history setting can be empty, and GStrv can be NULL.
* !!! But g_strv_length requires its arg!=NULL
*/
if (strings_in==NULL)
return;
/* Adding requires a new tail. */
console_history_new_tail (&console->history);
/* Order of the GStrv is earliest command first.
* Iterate ascending, i.e. earliest command to history first.
* Not concerned with performance.
*/
for (gint i = 0; i < g_strv_length (strings_in); i++)
script_fu_command_to_history (console, g_strdup (strings_in[i]));
g_strfreev (strings_in);
}
/* Append a command to history.
*
* Knows to put to both TotalHistory and History models.
*
* Transfers ownership of command to History model.
* Caller may retain a reference, for a short time.
*
* !!! The History model is finite and limits itself.
* While the TotalHistory model is nearly unlimited.
* More commands in the view than in the History model.
*/
static void
script_fu_command_to_history (ConsoleInterface *console,
const gchar *command)
{
/* Require new_tail called previously. */
/* To History model. */
console_history_set_tail (&console->history, command);
/* Advance history, editor wants preallocated tail. */
console_history_new_tail (&console->history);
/* Decorated command to TotalHistory model. */
console_total_append_command (console->total_history, command);
/* Ensure there is a new tail. */
}

View File

@ -140,6 +140,10 @@ script_fu_create_procedure (GimpPlugIn *plug_in,
GIMP_TYPE_RUN_MODE,
GIMP_RUN_INTERACTIVE,
G_PARAM_READWRITE);
GIMP_PROC_AUX_ARG_STRV (procedure, "history",
"Command history",
"History",
G_PARAM_READWRITE);
}
else if (! strcmp (name, "plug-in-script-fu-text-console"))
{

View File

@ -2,8 +2,9 @@
# marked to allow runtime translation of messages
plug-ins/script-fu/script-fu.c
plug-ins/script-fu/script-fu-console.c
plug-ins/script-fu/script-fu-eval.c
plug-ins/script-fu/console/script-fu-console.c
plug-ins/script-fu/console/script-fu-console-total.c
plug-ins/script-fu/server/script-fu-server.c
plug-ins/script-fu/server/script-fu-server-plugin.c
plug-ins/script-fu/libscriptfu/script-fu-interface.c