2 * Copyright (c) 2008-2009 Christian Hammond
3 * Copyright (c) 2008-2009 David Trowbridge
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 #include <gdk/gdkkeysyms.h>
26 #include "python-hooks.h"
27 #include "python-shell.h"
29 #define MAX_HISTORY_LENGTH 20
31 #define PARASITE_PYTHON_SHELL_GET_PRIVATE(obj) \
32 (G_TYPE_INSTANCE_GET_PRIVATE((obj), PARASITE_TYPE_PYTHON_SHELL, \
33 ParasitePythonShellPrivate))
39 GtkTextMark *scroll_mark;
40 GtkTextMark *line_start_mark;
43 GList *cur_history_item;
45 GString *pending_command;
48 } ParasitePythonShellPrivate;
56 /* Widget functions */
57 static void parasite_python_shell_finalize(GObject *obj);
59 /* Python integration */
60 static void parasite_python_shell_write_prompt(GtkWidget *python_shell);
61 static char *parasite_python_shell_get_input(GtkWidget *python_shell);
64 static gboolean parasite_python_shell_key_press_cb(GtkWidget *textview,
66 GtkWidget *python_shell);
69 static GtkVBoxClass *parent_class = NULL;
70 //static guint signals[LAST_SIGNAL] = {0};
72 G_DEFINE_TYPE(ParasitePythonShell, parasite_python_shell, GTK_TYPE_VBOX);
76 parasite_python_shell_class_init(ParasitePythonShellClass *klass)
78 GObjectClass *object_class = G_OBJECT_CLASS(klass);
80 parent_class = g_type_class_peek_parent(klass);
82 object_class->finalize = parasite_python_shell_finalize;
84 g_type_class_add_private(klass, sizeof(ParasitePythonShellPrivate));
88 parasite_python_shell_init(ParasitePythonShell *python_shell)
90 ParasitePythonShellPrivate *priv =
91 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
93 GtkTextBuffer *buffer;
95 PangoFontDescription *font_desc;
97 priv->history = g_queue_new();
99 gtk_box_set_spacing(GTK_BOX(python_shell), 6);
101 swin = gtk_scrolled_window_new(NULL, NULL);
102 gtk_widget_show(swin);
103 gtk_box_pack_start(GTK_BOX(python_shell), swin, TRUE, TRUE, 0);
104 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
105 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
106 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin),
109 priv->textview = gtk_text_view_new();
110 gtk_widget_show(priv->textview);
111 gtk_container_add(GTK_CONTAINER(swin), priv->textview);
112 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(priv->textview), TRUE);
113 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(priv->textview), 3);
114 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(priv->textview), 3);
115 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(priv->textview), 3);
117 g_signal_connect(G_OBJECT(priv->textview), "key_press_event",
118 G_CALLBACK(parasite_python_shell_key_press_cb),
121 /* Make the textview monospaced */
122 font_desc = pango_font_description_from_string("monospace");
123 pango_font_description_set_size(font_desc, 10 * PANGO_SCALE);
124 gtk_widget_modify_font(priv->textview, font_desc);
125 pango_font_description_free(font_desc);
127 /* Create the end-of-buffer mark */
128 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
129 gtk_text_buffer_get_end_iter(buffer, &iter);
130 priv->scroll_mark = gtk_text_buffer_create_mark(buffer, "scroll_mark",
133 /* Create the beginning-of-line mark */
134 priv->line_start_mark = gtk_text_buffer_create_mark(buffer,
138 /* Register some tags */
139 gtk_text_buffer_create_tag(buffer, "stdout", NULL);
140 gtk_text_buffer_create_tag(buffer, "stderr",
142 "paragraph-background", "#FFFFE0",
144 gtk_text_buffer_create_tag(buffer, "prompt",
145 "foreground", "blue",
148 parasite_python_shell_write_prompt(GTK_WIDGET(python_shell));
152 parasite_python_shell_finalize(GObject *python_shell)
154 ParasitePythonShellPrivate *priv =
155 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
157 g_queue_free(priv->history);
161 parasite_python_shell_log_stdout(const char *text, gpointer python_shell)
163 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
168 parasite_python_shell_log_stderr(const char *text, gpointer python_shell)
170 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
175 parasite_python_shell_write_prompt(GtkWidget *python_shell)
177 ParasitePythonShellPrivate *priv =
178 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
179 GtkTextBuffer *buffer =
180 gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
182 const char *prompt = (priv->pending_command == NULL ? ">>> " : "... ");
184 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
187 gtk_text_buffer_get_end_iter(buffer, &iter);
188 gtk_text_buffer_move_mark(buffer, priv->line_start_mark, &iter);
192 parasite_python_shell_process_line(GtkWidget *python_shell)
194 ParasitePythonShellPrivate *priv =
195 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
197 char *command = parasite_python_shell_get_input(python_shell);
200 parasite_python_shell_append_text(PARASITE_PYTHON_SHELL(python_shell),
203 if (*command != '\0')
205 /* Save this command in the history. */
206 g_queue_push_head(priv->history, command);
207 priv->cur_history_item = NULL;
209 if (g_queue_get_length(priv->history) > MAX_HISTORY_LENGTH)
210 g_free(g_queue_pop_tail(priv->history));
213 last_char = command[MAX(0, strlen(command) - 1)];
215 if (last_char == ':' || last_char == '\\' ||
216 (priv->in_block && g_ascii_isspace(command[0])))
218 printf("in block.. %c, %d, %d\n",
219 last_char, priv->in_block,
220 g_ascii_isspace(command[0]));
221 /* This is a multi-line expression */
222 if (priv->pending_command == NULL)
223 priv->pending_command = g_string_new(command);
225 g_string_append(priv->pending_command, command);
227 g_string_append_c(priv->pending_command, '\n');
229 if (last_char == ':')
230 priv->in_block = TRUE;
234 if (priv->pending_command != NULL)
236 g_string_append(priv->pending_command, command);
237 g_string_append_c(priv->pending_command, '\n');
239 /* We're not actually leaking this. It's in the history. */
240 command = g_string_free(priv->pending_command, FALSE);
242 parasite_python_run(command,
243 parasite_python_shell_log_stdout,
244 parasite_python_shell_log_stderr,
246 if (priv->pending_command != NULL)
248 /* Now do the cleanup. */
250 priv->pending_command = NULL;
251 priv->in_block = FALSE;
254 parasite_python_shell_write_prompt(python_shell);
258 parasite_python_shell_replace_input(GtkWidget *python_shell,
261 ParasitePythonShellPrivate *priv =
262 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
264 GtkTextBuffer *buffer =
265 gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
266 GtkTextIter start_iter;
267 GtkTextIter end_iter;
269 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
270 priv->line_start_mark);
271 gtk_text_buffer_get_end_iter(buffer, &end_iter);
273 gtk_text_buffer_delete(buffer, &start_iter, &end_iter);
274 gtk_text_buffer_insert(buffer, &end_iter, text, -1);
278 parasite_python_shell_get_input(GtkWidget *python_shell)
280 ParasitePythonShellPrivate *priv =
281 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
282 GtkTextBuffer *buffer =
283 gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
284 GtkTextIter start_iter;
285 GtkTextIter end_iter;
287 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
288 priv->line_start_mark);
289 gtk_text_buffer_get_end_iter(buffer, &end_iter);
291 return gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE);
295 parasite_python_shell_get_history_back(GtkWidget *python_shell)
297 ParasitePythonShellPrivate *priv =
298 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
300 if (priv->cur_history_item == NULL)
302 priv->cur_history_item = g_queue_peek_head_link(priv->history);
304 if (priv->cur_history_item == NULL)
307 else if (priv->cur_history_item->next != NULL)
308 priv->cur_history_item = priv->cur_history_item->next;
310 return (const char *)priv->cur_history_item->data;
314 parasite_python_shell_get_history_forward(GtkWidget *python_shell)
316 ParasitePythonShellPrivate *priv =
317 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
319 if (priv->cur_history_item == NULL || priv->cur_history_item->prev == NULL)
321 priv->cur_history_item = NULL;
325 priv->cur_history_item = priv->cur_history_item->prev;
327 return (const char *)priv->cur_history_item->data;
331 parasite_python_shell_key_press_cb(GtkWidget *textview,
333 GtkWidget *python_shell)
335 if (event->keyval == GDK_Return)
337 parasite_python_shell_process_line(python_shell);
340 else if (event->keyval == GDK_Up)
342 parasite_python_shell_replace_input(python_shell,
343 parasite_python_shell_get_history_back(python_shell));
346 else if (event->keyval == GDK_Down)
348 parasite_python_shell_replace_input(python_shell,
349 parasite_python_shell_get_history_forward(python_shell));
352 else if (event->string != NULL)
354 ParasitePythonShellPrivate *priv =
355 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
356 GtkTextBuffer *buffer =
357 gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
358 GtkTextMark *insert_mark = gtk_text_buffer_get_insert(buffer);
359 GtkTextMark *selection_mark =
360 gtk_text_buffer_get_selection_bound(buffer);
361 GtkTextIter insert_iter;
362 GtkTextIter selection_iter;
363 GtkTextIter start_iter;
364 gint cmp_start_insert;
365 gint cmp_start_select;
366 gint cmp_insert_select;
368 gtk_text_buffer_get_iter_at_mark(buffer, &start_iter,
369 priv->line_start_mark);
370 gtk_text_buffer_get_iter_at_mark(buffer, &insert_iter, insert_mark);
371 gtk_text_buffer_get_iter_at_mark(buffer, &selection_iter,
374 cmp_start_insert = gtk_text_iter_compare(&start_iter, &insert_iter);
375 cmp_start_select = gtk_text_iter_compare(&start_iter, &selection_iter);
376 cmp_insert_select = gtk_text_iter_compare(&insert_iter,
379 if (cmp_start_insert == 0 && cmp_start_select == 0 &&
380 (event->keyval == GDK_BackSpace ||
381 event->keyval == GDK_Left))
385 if (cmp_start_insert <= 0 && cmp_start_select <= 0)
389 else if (cmp_start_insert > 0 && cmp_start_select > 0)
391 gtk_text_buffer_place_cursor(buffer, &start_iter);
393 else if (cmp_insert_select < 0)
395 gtk_text_buffer_move_mark(buffer, insert_mark, &start_iter);
397 else if (cmp_insert_select > 0)
399 gtk_text_buffer_move_mark(buffer, selection_mark, &start_iter);
407 parasite_python_shell_new(void)
409 return g_object_new(PARASITE_TYPE_PYTHON_SHELL, NULL);
413 parasite_python_shell_append_text(ParasitePythonShell *python_shell,
417 ParasitePythonShellPrivate *priv =
418 PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell);
421 GtkTextBuffer *buffer =
422 gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview));
423 GtkTextMark *mark = gtk_text_buffer_get_insert(buffer);
425 gtk_text_buffer_get_end_iter(buffer, &end);
426 gtk_text_buffer_move_mark(buffer, mark, &end);
427 gtk_text_buffer_insert_with_tags_by_name(buffer, &end, str, -1, tag, NULL);
428 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(priv->textview), mark,
433 parasite_python_shell_focus(ParasitePythonShell *python_shell)
435 gtk_widget_grab_focus(PARASITE_PYTHON_SHELL_GET_PRIVATE(python_shell)->textview);