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
27 #include <gdk/gdkkeysyms.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))
43 GtkTextMark
*scroll_mark
;
44 GtkTextMark
*line_start_mark
;
47 GList
*cur_history_item
;
49 GString
*pending_command
;
52 } ParasitePythonShellPrivate
;
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
);
68 static gboolean
parasite_python_shell_key_press_cb(GtkWidget
*textview
,
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
))
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
;
91 parasite_python_shell_init(ParasitePythonShell
*python_shell
)
93 ParasitePythonShellPrivate
*priv
=
94 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell
);
96 GtkTextBuffer
*buffer
;
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
),
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
),
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",
136 /* Create the beginning-of-line mark */
137 priv
->line_start_mark
= gtk_text_buffer_create_mark(buffer
,
141 /* Register some tags */
142 gtk_text_buffer_create_tag(buffer
, "stdout", NULL
);
143 gtk_text_buffer_create_tag(buffer
, "stderr",
145 "paragraph-background", "#FFFFE0",
147 gtk_text_buffer_create_tag(buffer
, "prompt",
148 "foreground", "blue",
151 parasite_python_shell_write_prompt(GTK_WIDGET(python_shell
));
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
);
164 parasite_python_shell_log_stdout(const char *text
, gpointer python_shell
)
166 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell
),
171 parasite_python_shell_log_stderr(const char *text
, gpointer python_shell
)
173 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell
),
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
));
185 const char *prompt
= (priv
->pending_command
== NULL
? ">>> " : "... ");
187 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell
),
190 gtk_text_buffer_get_end_iter(buffer
, &iter
);
191 gtk_text_buffer_move_mark(buffer
, priv
->line_start_mark
, &iter
);
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
);
203 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell
),
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
);
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
;
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
,
249 if (priv
->pending_command
!= NULL
)
251 /* Now do the cleanup. */
253 priv
->pending_command
= NULL
;
254 priv
->in_block
= FALSE
;
257 parasite_python_shell_write_prompt(python_shell
);
261 parasite_python_shell_replace_input(GtkWidget
*python_shell
,
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);
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
);
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
)
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
;
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
;
328 priv
->cur_history_item
= priv
->cur_history_item
->prev
;
330 return (const char *)priv
->cur_history_item
->data
;
334 parasite_python_shell_key_press_cb(GtkWidget
*textview
,
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
);
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
));
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
));
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
,
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
,
377 if (cmp_start_insert
== 0 && cmp_start_select
== 0 &&
378 event
&& (event
->keyval
== GDK_KEY_BackSpace
||
379 event
->keyval
== GDK_KEY_Left
)) {
382 if (cmp_start_insert
<= 0 && cmp_start_select
<= 0)
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
);
396 parasite_python_shell_new(void)
398 return g_object_new(PARASITE_TYPE_PYTHON_SHELL
, NULL
);
402 parasite_python_shell_append_text(ParasitePythonShell
*python_shell
,
406 ParasitePythonShellPrivate
*priv
=
407 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell
);
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
,
422 parasite_python_shell_focus(ParasitePythonShell
*python_shell
)
424 gtk_widget_grab_focus(PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell
)->textview
);