Improve some sieve-related translations
[claws.git] / src / plugins / python / python-shell.c
blobfaf9f2ab098e606d820d0f23829b0c29797931b7
1 /*
2 * Copyright (c) 2008-2009 Christian Hammond
3 * Copyright (c) 2008-2009 David Trowbridge
4 * Copyright (C) 2021 the Claws Mail Team
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included
14 * in all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
25 #include "config.h"
27 #include <gdk/gdkkeysyms.h>
28 #include <string.h>
30 #include "python-hooks.h"
31 #include "python-shell.h"
33 #define MAX_HISTORY_LENGTH 20
35 #define PARASITE_PYTHON_SHELL_GET_PRIVATE(obj) \
36 (G_TYPE_INSTANCE_GET_PRIVATE((obj), PARASITE_TYPE_PYTHON_SHELL, \
37 ParasitePythonShellPrivate))
39 typedef struct
41 GtkWidget *textview;
43 GtkTextMark *scroll_mark;
44 GtkTextMark *line_start_mark;
46 GQueue *history;
47 GList *cur_history_item;
49 GString *pending_command;
50 gboolean in_block;
52 } ParasitePythonShellPrivate;
54 enum
56 LAST_SIGNAL
60 /* Widget functions */
61 static void parasite_python_shell_finalize(GObject *obj);
63 /* Python integration */
64 static void parasite_python_shell_write_prompt(GtkWidget *python_shell);
65 static char *parasite_python_shell_get_input(GtkWidget *python_shell);
67 /* Callbacks */
68 static gboolean parasite_python_shell_key_press_cb(GtkWidget *textview,
69 GdkEventKey *event,
70 GtkWidget *python_shell);
73 static GtkVBoxClass *parent_class = NULL;
74 //static guint signals[LAST_SIGNAL] = {0};
76 G_DEFINE_TYPE_WITH_CODE(ParasitePythonShell, parasite_python_shell,
77 GTK_TYPE_VBOX, G_ADD_PRIVATE(ParasitePythonShell))
80 static void
81 parasite_python_shell_class_init(ParasitePythonShellClass *klass)
83 GObjectClass *object_class = G_OBJECT_CLASS(klass);
85 parent_class = g_type_class_peek_parent(klass);
87 object_class->finalize = parasite_python_shell_finalize;
90 static void
91 parasite_python_shell_init(ParasitePythonShell *python_shell)
93 ParasitePythonShellPrivate *priv =
94 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
95 GtkWidget *swin;
96 GtkTextBuffer *buffer;
97 GtkTextIter iter;
98 PangoFontDescription *font_desc;
100 priv->history = g_queue_new();
102 gtk_box_set_spacing(GTK_BOX(python_shell), 6);
104 swin = gtk_scrolled_window_new(NULL, NULL);
105 gtk_widget_show(swin);
106 gtk_box_pack_start(GTK_BOX(python_shell), swin, TRUE, TRUE, 0);
107 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
108 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
109 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin),
110 GTK_SHADOW_IN);
112 priv->textview = gtk_text_view_new();
113 gtk_widget_show(priv->textview);
114 gtk_container_add(GTK_CONTAINER(swin), priv->textview);
115 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(priv->textview), TRUE);
116 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(priv->textview), 3);
117 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(priv->textview), 3);
118 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(priv->textview), 3);
120 g_signal_connect(G_OBJECT(priv->textview), "key_press_event",
121 G_CALLBACK(parasite_python_shell_key_press_cb),
122 python_shell);
124 /* Make the textview monospaced */
125 font_desc = pango_font_description_from_string("monospace");
126 pango_font_description_set_size(font_desc, 10 * PANGO_SCALE);
127 gtk_widget_override_font(priv->textview, font_desc);
128 pango_font_description_free(font_desc);
130 /* Create the end-of-buffer mark */
131 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
132 gtk_text_buffer_get_end_iter(buffer, &iter);
133 priv->scroll_mark = gtk_text_buffer_create_mark(buffer, "scroll_mark",
134 &iter, FALSE);
136 /* Create the beginning-of-line mark */
137 priv->line_start_mark = gtk_text_buffer_create_mark(buffer,
138 "line_start_mark",
139 &iter, TRUE);
141 /* Register some tags */
142 gtk_text_buffer_create_tag(buffer, "stdout", NULL);
143 gtk_text_buffer_create_tag(buffer, "stderr",
144 "foreground", "red",
145 "paragraph-background", "#FFFFE0",
146 NULL);
147 gtk_text_buffer_create_tag(buffer, "prompt",
148 "foreground", "blue",
149 NULL);
151 parasite_python_shell_write_prompt(GTK_WIDGET(python_shell));
154 static void
155 parasite_python_shell_finalize(GObject *python_shell)
157 ParasitePythonShellPrivate *priv =
158 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
160 g_queue_free(priv->history);
163 static void
164 parasite_python_shell_log_stdout(const char *text, gpointer python_shell)
166 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
167 text, "stdout");
170 static void
171 parasite_python_shell_log_stderr(const char *text, gpointer python_shell)
173 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
174 text, "stderr");
177 static void
178 parasite_python_shell_write_prompt(GtkWidget *python_shell)
180 ParasitePythonShellPrivate *priv =
181 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
182 GtkTextBuffer *buffer =
183 gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
184 GtkTextIter iter;
185 const char *prompt = (priv->pending_command == NULL ? ">>> " : "... ");
187 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
188 prompt, "prompt");
190 gtk_text_buffer_get_end_iter(buffer, &iter);
191 gtk_text_buffer_move_mark(buffer, priv->line_start_mark, &iter);
194 static void
195 parasite_python_shell_process_line(GtkWidget *python_shell)
197 ParasitePythonShellPrivate *priv =
198 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
200 char *command = parasite_python_shell_get_input(python_shell);
201 char last_char;
203 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
204 "\n", NULL);
206 if (*command != '\0')
208 /* Save this command in the history. */
209 g_queue_push_head(priv->history, command);
210 priv->cur_history_item = NULL;
212 if (g_queue_get_length(priv->history) > MAX_HISTORY_LENGTH)
213 g_free(g_queue_pop_tail(priv->history));
216 last_char = command[MAX(0, (gint)(strlen(command) - 1))];
218 if (last_char == ':' || last_char == '\\' ||
219 (priv->in_block && g_ascii_isspace(command[0])))
221 printf("in block.. %c, %d, %d\n",
222 last_char, priv->in_block,
223 g_ascii_isspace(command[0]));
224 /* This is a multi-line expression */
225 if (priv->pending_command == NULL)
226 priv->pending_command = g_string_new(command);
227 else
228 g_string_append(priv->pending_command, command);
230 g_string_append_c(priv->pending_command, '\n');
232 if (last_char == ':')
233 priv->in_block = TRUE;
235 else
237 if (priv->pending_command != NULL)
239 g_string_append(priv->pending_command, command);
240 g_string_append_c(priv->pending_command, '\n');
242 /* We're not actually leaking this. It's in the history. */
243 command = g_string_free(priv->pending_command, FALSE);
245 parasite_python_run(command,
246 parasite_python_shell_log_stdout,
247 parasite_python_shell_log_stderr,
248 python_shell);
249 if (priv->pending_command != NULL)
251 /* Now do the cleanup. */
252 g_free(command);
253 priv->pending_command = NULL;
254 priv->in_block = FALSE;
257 parasite_python_shell_write_prompt(python_shell);
260 static void
261 parasite_python_shell_replace_input(GtkWidget *python_shell,
262 const char *text)
264 ParasitePythonShellPrivate *priv =
265 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
267 GtkTextBuffer *buffer =
268 gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
269 GtkTextIter start_iter;
270 GtkTextIter end_iter;
272 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
273 priv->line_start_mark);
274 gtk_text_buffer_get_end_iter(buffer, &end_iter);
276 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
277 gtk_text_buffer_insert(buffer, &end_iter, text, -1);
280 static char *
281 parasite_python_shell_get_input(GtkWidget *python_shell)
283 ParasitePythonShellPrivate *priv =
284 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
285 GtkTextBuffer *buffer =
286 gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
287 GtkTextIter start_iter;
288 GtkTextIter end_iter;
290 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
291 priv->line_start_mark);
292 gtk_text_buffer_get_end_iter(buffer, &end_iter);
294 return gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
297 static const char *
298 parasite_python_shell_get_history_back(GtkWidget *python_shell)
300 ParasitePythonShellPrivate *priv =
301 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
303 if (priv->cur_history_item == NULL)
305 priv->cur_history_item = g_queue_peek_head_link(priv->history);
307 if (priv->cur_history_item == NULL)
308 return "";
310 else if (priv->cur_history_item->next != NULL)
311 priv->cur_history_item = priv->cur_history_item->next;
313 return (const char *)priv->cur_history_item->data;
316 static const char *
317 parasite_python_shell_get_history_forward(GtkWidget *python_shell)
319 ParasitePythonShellPrivate *priv =
320 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
322 if (priv->cur_history_item == NULL || priv->cur_history_item->prev == NULL)
324 priv->cur_history_item = NULL;
325 return "";
328 priv->cur_history_item = priv->cur_history_item->prev;
330 return (const char *)priv->cur_history_item->data;
333 static gboolean
334 parasite_python_shell_key_press_cb(GtkWidget *textview,
335 GdkEventKey *event,
336 GtkWidget *python_shell)
338 if (event && (event->keyval == GDK_KEY_KP_Enter ||
339 event->keyval == GDK_KEY_Return))
341 parasite_python_shell_process_line(python_shell);
342 return TRUE;
343 } else if (event && event->keyval == GDK_KEY_Up) {
344 parasite_python_shell_replace_input(python_shell,
345 parasite_python_shell_get_history_back(python_shell));
346 return TRUE;
347 } else if (event && event->keyval == GDK_KEY_Down) {
348 parasite_python_shell_replace_input(python_shell,
349 parasite_python_shell_get_history_forward(python_shell));
350 return TRUE;
351 } else if (event && event->string != NULL) {
352 ParasitePythonShellPrivate *priv =
353 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
354 GtkTextBuffer *buffer =
355 gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
356 GtkTextMark *insert_mark = gtk_text_buffer_get_insert(buffer);
357 GtkTextMark *selection_mark =
358 gtk_text_buffer_get_selection_bound(buffer);
359 GtkTextIter insert_iter;
360 GtkTextIter selection_iter;
361 GtkTextIter start_iter;
362 gint cmp_start_insert;
363 gint cmp_start_select;
364 gint cmp_insert_select;
366 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
367 priv->line_start_mark);
368 gtk_text_buffer_get_iter_at_mark(buffer, &insert_iter, insert_mark);
369 gtk_text_buffer_get_iter_at_mark(buffer, &selection_iter,
370 selection_mark);
372 cmp_start_insert = gtk_text_iter_compare(&start_iter, &insert_iter);
373 cmp_start_select = gtk_text_iter_compare(&start_iter, &selection_iter);
374 cmp_insert_select = gtk_text_iter_compare(&insert_iter,
375 &selection_iter);
377 if (cmp_start_insert == 0 && cmp_start_select == 0 &&
378 event && (event->keyval == GDK_KEY_BackSpace ||
379 event->keyval == GDK_KEY_Left)) {
380 return TRUE;
382 if (cmp_start_insert <= 0 && cmp_start_select <= 0)
383 return FALSE;
384 else if (cmp_start_insert > 0 && cmp_start_select > 0)
385 gtk_text_buffer_place_cursor(buffer, &start_iter);
386 else if (cmp_insert_select < 0)
387 gtk_text_buffer_move_mark(buffer, insert_mark, &start_iter);
388 else if (cmp_insert_select > 0)
389 gtk_text_buffer_move_mark(buffer, selection_mark, &start_iter);
392 return FALSE;
395 GtkWidget *
396 parasite_python_shell_new(void)
398 return g_object_new(PARASITE_TYPE_PYTHON_SHELL, NULL);
401 void
402 parasite_python_shell_append_text(ParasitePythonShell *python_shell,
403 const char *str,
404 const char *tag)
406 ParasitePythonShellPrivate *priv =
407 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
409 GtkTextIter end;
410 GtkTextBuffer *buffer =
411 gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
412 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
414 gtk_text_buffer_get_end_iter(buffer, &end);
415 gtk_text_buffer_move_mark(buffer, mark, &end);
416 gtk_text_buffer_insert_with_tags_by_name(buffer, &end, str, -1, tag, NULL);
417 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(priv->textview), mark,
418 0, TRUE, 0, 1);
421 void
422 parasite_python_shell_focus(ParasitePythonShell *python_shell)
424 gtk_widget_grab_focus(PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell)->textview);
427 // vim: set et ts=4: