1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3 * plugin.c
4 * Copyright (C) Ishan Chattopadhyaya 2009 <ichattopadhyaya@gmail.com>
5 *
6 * plugin.c is free software.
7 *
8 * You may redistribute it and/or modify it under the terms of the
9 * GNU General Public License, as published by the Free Software
10 * Foundation; either version 2 of the License, or (at your option)
11 * any later version.
12 *
13 * plugin.c is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 * See the GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with plugin.c. If not, write to:
20 * The Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301, USA.
23 */
24
25 #include <config.h>
26 #include <ctype.h>
27 #include <stdlib.h>
28 #include <libanjuta/anjuta-shell.h>
29 #include <libanjuta/anjuta-debug.h>
30 #include <libanjuta/anjuta-launcher.h>
31 #include <libanjuta/anjuta-preferences.h>
32 #include <libanjuta/anjuta-utils.h>
33 #include <libanjuta/interfaces/ianjuta-iterable.h>
34 #include <libanjuta/interfaces/ianjuta-document.h>
35 #include <libanjuta/interfaces/ianjuta-document-manager.h>
36 #include <libanjuta/interfaces/ianjuta-editor.h>
37 #include <libanjuta/interfaces/ianjuta-editor-cell.h>
38 #include <libanjuta/interfaces/ianjuta-editor-language.h>
39 #include <libanjuta/interfaces/ianjuta-editor-selection.h>
40 #include <libanjuta/interfaces/ianjuta-editor-assist.h>
41 #include <libanjuta/interfaces/ianjuta-editor-glade-signal.h>
42 #include <libanjuta/interfaces/ianjuta-preferences.h>
43 #include <libanjuta/interfaces/ianjuta-symbol.h>
44 #include <libanjuta/interfaces/ianjuta-language.h>
45 #include <libanjuta/interfaces/ianjuta-indenter.h>
46
47 #include "plugin.h"
48 #include "python-assist.h"
49
50 #define UI_FILE PACKAGE_DATA_DIR"/ui/anjuta-language-support-python.xml"
51 #define PROPERTIES_FILE_UI PACKAGE_DATA_DIR"/glade/anjuta-language-support-python.ui"
52 #define ICON_FILE "anjuta-language-support-python-plugin.png"
53
54 /* Preferences keys */
55
56 #define ANJUTA_PREF_SCHEMA_PREFIX "org.gnome.anjuta."
57 #define PREF_SCHEMA "org.gnome.anjuta.plugins.python"
58
59
60 #define PREF_NO_ROPE_WARNING "no-rope-warning"
61 #define PREF_INTERPRETER_PATH "interpreter-path"
62
63 static gpointer parent_class;
64
65 static void
on_check_finished(AnjutaLauncher * launcher,int child_pid,int exit_status,gulong time,gpointer user_data)66 on_check_finished (AnjutaLauncher* launcher,
67 int child_pid, int exit_status,
68 gulong time, gpointer user_data)
69 {
70 PythonPlugin* plugin = ANJUTA_PLUGIN_PYTHON (user_data);
71 if (exit_status != 0)
72 {
73 GtkWidget* dialog = gtk_dialog_new_with_buttons (_("Python support warning"),
74 NULL,
75 GTK_DIALOG_MODAL,
76 GTK_STOCK_OK,
77 GTK_RESPONSE_ACCEPT,
78 NULL);
79 GtkWidget* area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
80
81 GtkWidget* label = gtk_label_new (_("Either python path is wrong or python-rope (http://rope.sf.net) libraries\n"
82 "aren't installed. Both are required for autocompletion in python files.\n"
83 "Please install them and check the python path in the preferences."));
84 GtkWidget* check_button = gtk_check_button_new_with_label (_("Do not show that warning again"));
85
86 gtk_box_pack_start (GTK_BOX (area), label,
87 TRUE, TRUE, 5);
88 gtk_box_pack_start (GTK_BOX (area), check_button,
89 TRUE, TRUE, 5);
90 gtk_widget_show_all (dialog);
91
92 gtk_dialog_run (GTK_DIALOG(dialog));
93
94 /* Save "Do not show again settings" */
95 g_settings_set_boolean (plugin->settings,
96 PREF_NO_ROPE_WARNING,
97 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_button)));
98
99 gtk_widget_destroy (dialog);
100 }
101 g_object_unref (launcher);
102 }
103
104 static void
check_support(PythonPlugin * python_plugin)105 check_support (PythonPlugin *python_plugin)
106 {
107 if (!g_settings_get_boolean (python_plugin->settings,
108 PREF_NO_ROPE_WARNING))
109 {
110 AnjutaLauncher* launcher = anjuta_launcher_new ();
111 gchar* python_path = g_settings_get_string (python_plugin->settings,
112 PREF_INTERPRETER_PATH);
113 gchar* command = g_strdup_printf ("%s -c \"import rope\"", python_path);
114
115 g_signal_connect (launcher, "child-exited",
116 G_CALLBACK(on_check_finished), python_plugin);
117 anjuta_launcher_execute (launcher, command, NULL, NULL);
118
119 g_free (python_path);
120 g_free (command);
121 }
122 }
123
124 /* Glade support */
125 static gchar*
language_support_check_param_name(const gchar * name,GList ** names)126 language_support_check_param_name (const gchar* name,
127 GList** names)
128 {
129 gint index = 0;
130 GString* real_name = g_string_new (name);
131 while (g_list_find_custom (*names, real_name->str, (GCompareFunc) strcmp))
132 {
133 g_string_free (real_name, TRUE);
134 real_name = g_string_new (name);
135 g_string_append_printf (real_name, "%d", ++index);
136 }
137 *names = g_list_append (*names, real_name->str);
138 return g_string_free (real_name, FALSE);
139 }
140
141 static const gchar*
language_support_get_signal_parameter(const gchar * type_name,GList ** names)142 language_support_get_signal_parameter (const gchar* type_name, GList** names)
143 {
144 const gchar* c;
145 const gchar* param_name = NULL;
146 GString* param_string;
147 gchar* real_name;
148 /* Search for the second upper character */
149 for (c = type_name + 1; *c != '\0'; c++)
150 {
151 if (g_ascii_isupper (*c))
152 {
153 param_name = c;
154 break;
155 }
156 }
157 if (param_name && strlen (param_name))
158 {
159 param_string = g_string_new (param_name);
160 g_string_ascii_down (param_string);
161 }
162 else
163 {
164 param_string = g_string_new ("arg");
165 }
166 real_name = language_support_check_param_name (g_string_free (param_string, FALSE), names);
167
168 return real_name;
169 }
170
171 static void
on_glade_drop(IAnjutaEditor * editor,IAnjutaIterable * iterator,const gchar * signal_data,PythonPlugin * lang_plugin)172 on_glade_drop (IAnjutaEditor* editor,
173 IAnjutaIterable* iterator,
174 const gchar* signal_data,
175 PythonPlugin* lang_plugin)
176 {
177 GSignalQuery query;
178 GType type;
179 guint id;
180
181 const gchar* widget;
182 const gchar* signal;
183 const gchar* handler;
184 GList* names = NULL;
185 GString* str = g_string_new (NULL);
186 int i;
187 IAnjutaIterable* start, * end;
188
189 GStrv data = g_strsplit(signal_data, ":", 5);
190
191 widget = data[0];
192 signal = data[1];
193 handler = data[2];
194
195 type = g_type_from_name (widget);
196 id = g_signal_lookup (signal, type);
197
198 g_signal_query (id, &query);
199
200 g_string_append_printf (str, "\ndef %s (self, %s", handler,
201 language_support_get_signal_parameter (widget,
202 &names));
203 for (i = 0; i < query.n_params; i++)
204 {
205 const gchar* type_name = g_type_name (query.param_types[i]);
206 const gchar* param_name = language_support_get_signal_parameter (type_name,
207 &names);
208
209 g_string_append_printf (str, ", %s", param_name);
210 }
211 g_string_append (str, "):\n");
212
213 ianjuta_editor_insert (editor, iterator,
214 str->str, -1, NULL);
215
216 /* Indent code correctly */
217 start = iterator;
218 end = ianjuta_iterable_clone (iterator, NULL);
219 ianjuta_iterable_set_position (end,
220 ianjuta_iterable_get_position (iterator, NULL)
221 + g_utf8_strlen (str->str, -1),
222 NULL);
223 ianjuta_indenter_indent (IANJUTA_INDENTER (lang_plugin),
224 start, end, NULL);
225 g_object_unref (end);
226
227 g_string_free (str, TRUE);
228 anjuta_util_glist_strings_free (names);
229
230 g_strfreev (data);
231 }
232
233 static void
install_support(PythonPlugin * lang_plugin)234 install_support (PythonPlugin *lang_plugin)
235 {
236 IAnjutaLanguage* lang_manager =
237 anjuta_shell_get_interface (ANJUTA_PLUGIN (lang_plugin)->shell,
238 IAnjutaLanguage, NULL);
239 IAnjutaSymbolManager* sym_manager =
240 anjuta_shell_get_interface (ANJUTA_PLUGIN (lang_plugin)->shell,
241 IAnjutaSymbolManager,
242 NULL);
243
244 if (!lang_manager || !sym_manager)
245 return;
246
247 if (lang_plugin->support_installed)
248 return;
249
250 lang_plugin->current_language =
251 ianjuta_language_get_name_from_editor (lang_manager,
252 IANJUTA_EDITOR_LANGUAGE (lang_plugin->current_editor), NULL);
253
254 if (!(lang_plugin->current_language &&
255 (g_str_equal (lang_plugin->current_language, "Python"))))
256 return;
257
258 /* Disable editor intern auto-indent */
259 ianjuta_editor_set_auto_indent (IANJUTA_EDITOR(lang_plugin->current_editor),
260 FALSE, NULL);
261
262 if (IANJUTA_IS_EDITOR_ASSIST (lang_plugin->current_editor) )
263 {
264 AnjutaPlugin *plugin;
265 IAnjutaEditor* ieditor;
266
267 const gchar *project_root;
268
269 check_support (lang_plugin);
270
271 plugin = ANJUTA_PLUGIN (lang_plugin);
272 ieditor = IANJUTA_EDITOR (lang_plugin->current_editor);
273
274 g_assert (lang_plugin->assist == NULL);
275
276 project_root = ANJUTA_PLUGIN_PYTHON(plugin)->project_root_directory;
277
278 lang_plugin->assist = python_assist_new (ieditor,
279 sym_manager,
280 lang_plugin->settings,
281 plugin,
282 project_root);
283 }
284
285 if (IANJUTA_IS_EDITOR_GLADE_SIGNAL (lang_plugin->current_editor))
286 {
287 g_signal_connect (lang_plugin->current_editor,
288 "drop-possible", G_CALLBACK (gtk_true), NULL);
289 g_signal_connect (lang_plugin->current_editor,
290 "drop", G_CALLBACK (on_glade_drop),
291 lang_plugin);
292 }
293
294 lang_plugin->support_installed = TRUE;
295 }
296
297 static void
uninstall_support(PythonPlugin * lang_plugin)298 uninstall_support (PythonPlugin *lang_plugin)
299 {
300 if (!lang_plugin->support_installed)
301 return;
302
303 if (lang_plugin->assist)
304 {
305 g_object_unref (lang_plugin->assist);
306 lang_plugin->assist = NULL;
307 }
308
309 if (IANJUTA_IS_EDITOR_GLADE_SIGNAL (lang_plugin->current_editor))
310 {
311 g_signal_handlers_disconnect_by_func (lang_plugin->current_editor,
312 gtk_true, NULL);
313 g_signal_handlers_disconnect_by_func (lang_plugin->current_editor,
314 on_glade_drop, lang_plugin);
315 }
316
317 lang_plugin->support_installed = FALSE;
318 }
319
320 static void
on_editor_language_changed(IAnjutaEditor * editor,const gchar * new_language,PythonPlugin * plugin)321 on_editor_language_changed (IAnjutaEditor *editor,
322 const gchar *new_language,
323 PythonPlugin *plugin)
324 {
325 uninstall_support (plugin);
326 install_support (plugin);
327 }
328
329 static void
on_editor_added(AnjutaPlugin * plugin,const gchar * name,const GValue * value,gpointer data)330 on_editor_added (AnjutaPlugin *plugin, const gchar *name,
331 const GValue *value, gpointer data)
332 {
333 PythonPlugin *lang_plugin;
334 IAnjutaDocument* doc = IANJUTA_DOCUMENT(g_value_get_object (value));
335 lang_plugin = ANJUTA_PLUGIN_PYTHON(plugin);
336
337
338 if (IANJUTA_IS_EDITOR(doc))
339 {
340 lang_plugin->current_editor = G_OBJECT(doc);
341 }
342 else
343 {
344 lang_plugin->current_editor = NULL;
345 return;
346 }
347 if (lang_plugin->current_editor)
348 {
349 install_support (lang_plugin);
350 g_signal_connect (lang_plugin->current_editor, "language-changed",
351 G_CALLBACK (on_editor_language_changed),
352 plugin);
353 }
354 }
355
356 static void
on_editor_removed(AnjutaPlugin * plugin,const gchar * name,gpointer data)357 on_editor_removed (AnjutaPlugin *plugin, const gchar *name,
358 gpointer data)
359 {
360 PythonPlugin *lang_plugin;
361 lang_plugin = ANJUTA_PLUGIN_PYTHON (plugin);
362
363 if (lang_plugin->current_editor)
364 g_signal_handlers_disconnect_by_func (lang_plugin->current_editor,
365 G_CALLBACK (on_editor_language_changed),
366 plugin);
367
368 uninstall_support (lang_plugin);
369
370 lang_plugin->current_editor = NULL;
371 lang_plugin->current_language = NULL;
372 }
373
374 static GtkActionEntry actions[] = {
375 {
376 "ActionMenuEdit",
377 NULL, N_("_Edit"),
378 NULL, NULL, NULL
379 }
380 };
381
382 static void
on_project_root_added(AnjutaPlugin * plugin,const gchar * name,const GValue * value,gpointer user_data)383 on_project_root_added (AnjutaPlugin *plugin, const gchar *name,
384 const GValue *value, gpointer user_data)
385 {
386 PythonPlugin *python_plugin;
387 gchar *project_root_uri;
388 GFile *file;
389
390 python_plugin = ANJUTA_PLUGIN_PYTHON (plugin);
391
392 g_free (python_plugin->project_root_directory);
393 project_root_uri = g_value_dup_string (value);
394 file = g_file_new_for_uri (project_root_uri);
395 python_plugin->project_root_directory = g_file_get_path (file);
396 g_object_unref (file);
397 g_free (project_root_uri);
398 }
399
400 static void
on_project_root_removed(AnjutaPlugin * plugin,const gchar * name,gpointer user_data)401 on_project_root_removed (AnjutaPlugin *plugin, const gchar *name,
402 gpointer user_data)
403 {
404 PythonPlugin *python_plugin;
405
406 python_plugin = ANJUTA_PLUGIN_PYTHON (plugin);
407
408 g_free (python_plugin->project_root_directory);
409 python_plugin->project_root_directory = NULL;
410 }
411
412 static gboolean
python_plugin_activate(AnjutaPlugin * plugin)413 python_plugin_activate (AnjutaPlugin *plugin)
414 {
415 AnjutaUI *ui;
416
417 PythonPlugin *python_plugin;
418
419 python_plugin = (PythonPlugin*) plugin;
420
421 python_plugin->prefs = anjuta_shell_get_preferences (plugin->shell, NULL);
422
423 /* Add all UI actions and merge UI */
424 ui = anjuta_shell_get_ui (plugin->shell, NULL);
425
426 python_plugin->action_group =
427 anjuta_ui_add_action_group_entries (ui, "ActionGroupPythonAssist",
428 _("Python Assistance"),
429 actions,
430 G_N_ELEMENTS (actions),
431 GETTEXT_PACKAGE, TRUE,
432 plugin);
433 python_plugin->uiid = anjuta_ui_merge (ui, UI_FILE);
434
435 /* Add watches */
436 python_plugin->project_root_watch_id = anjuta_plugin_add_watch (plugin,
437 IANJUTA_PROJECT_MANAGER_PROJECT_ROOT_URI,
438 on_project_root_added,
439 on_project_root_removed,
440 NULL);
441
442 python_plugin->editor_watch_id = anjuta_plugin_add_watch (plugin,
443 IANJUTA_DOCUMENT_MANAGER_CURRENT_DOCUMENT,
444 on_editor_added,
445 on_editor_removed,
446 NULL);
447 return TRUE;
448 }
449
450 static gboolean
python_plugin_deactivate(AnjutaPlugin * plugin)451 python_plugin_deactivate (AnjutaPlugin *plugin)
452 {
453
454 AnjutaUI *ui;
455 PythonPlugin *lang_plugin;
456 lang_plugin = (PythonPlugin*) (plugin);
457
458 anjuta_plugin_remove_watch (plugin,
459 lang_plugin->editor_watch_id,
460 TRUE);
461 anjuta_plugin_remove_watch (plugin,
462 lang_plugin->project_root_watch_id,
463 TRUE);
464
465
466 ui = anjuta_shell_get_ui (plugin->shell, NULL);
467 anjuta_ui_remove_action_group (ui, ANJUTA_PLUGIN_PYTHON(plugin)->action_group);
468 anjuta_ui_unmerge (ui, ANJUTA_PLUGIN_PYTHON(plugin)->uiid);
469
470 return TRUE;
471 }
472
473 static void
python_plugin_finalize(GObject * obj)474 python_plugin_finalize (GObject *obj)
475 {
476 /* Finalization codes here */
477 G_OBJECT_CLASS (parent_class)->finalize (obj);
478 }
479
480 static void
python_plugin_dispose(GObject * obj)481 python_plugin_dispose (GObject *obj)
482 {
483 /* Disposition codes */
484 PythonPlugin *plugin = (PythonPlugin*)obj;
485
486 if (plugin->settings)
487 g_object_unref (plugin->settings);
488 plugin->settings = NULL;
489
490 G_OBJECT_CLASS (parent_class)->dispose (obj);
491 }
492
493 static void
python_plugin_instance_init(GObject * obj)494 python_plugin_instance_init (GObject *obj)
495 {
496 PythonPlugin *plugin = (PythonPlugin*)obj;
497 plugin->action_group = NULL;
498 plugin->current_editor = NULL;
499 plugin->current_language = NULL;
500 plugin->editor_watch_id = 0;
501 plugin->uiid = 0;
502 plugin->assist = NULL;
503 plugin->settings = g_settings_new (PREF_SCHEMA);
504 }
505
506 static void
python_plugin_class_init(GObjectClass * klass)507 python_plugin_class_init (GObjectClass *klass)
508 {
509 AnjutaPluginClass *plugin_class = ANJUTA_PLUGIN_CLASS (klass);
510
511 parent_class = g_type_class_peek_parent (klass);
512
513 plugin_class->activate = python_plugin_activate;
514 plugin_class->deactivate = python_plugin_deactivate;
515 klass->finalize = python_plugin_finalize;
516 klass->dispose = python_plugin_dispose;
517 }
518
519 #define PREF_WIDGET_SPACE "preferences:completion-space-after-func"
520 #define PREF_WIDGET_BRACE "preferences:completion-brace-after-func"
521 #define PREF_WIDGET_CLOSEBRACE "preferences:completion-closebrace-after-func"
522 #define PREF_WIDGET_AUTO "preferences:completion-enable"
523
524 static void
on_autocompletion_toggled(GtkToggleButton * button,PythonPlugin * plugin)525 on_autocompletion_toggled (GtkToggleButton* button,
526 PythonPlugin* plugin)
527 {
528 GtkWidget* widget;
529 gboolean sensitive = gtk_toggle_button_get_active (button);
530
531 widget = GTK_WIDGET (gtk_builder_get_object (plugin->bxml, PREF_WIDGET_SPACE));
532 gtk_widget_set_sensitive (widget, sensitive);
533 widget = GTK_WIDGET (gtk_builder_get_object (plugin->bxml, PREF_WIDGET_BRACE));
534 gtk_widget_set_sensitive (widget, sensitive);
535 widget = GTK_WIDGET (gtk_builder_get_object (plugin->bxml, PREF_WIDGET_CLOSEBRACE));
536 gtk_widget_set_sensitive (widget, sensitive);
537 }
538
539 static void
ipreferences_merge(IAnjutaPreferences * ipref,AnjutaPreferences * prefs,GError ** e)540 ipreferences_merge (IAnjutaPreferences* ipref, AnjutaPreferences* prefs,
541 GError** e)
542 {
543 /* Add preferences */
544 GError* error = NULL;
545 PythonPlugin* plugin = ANJUTA_PLUGIN_PYTHON (ipref);
546 plugin->bxml = gtk_builder_new ();
547 GtkWidget* toggle;
548
549 if (!gtk_builder_add_from_file (plugin->bxml, PROPERTIES_FILE_UI, &error))
550 {
551 g_warning ("Couldn't load builder file: %s", error->message);
552 g_error_free (error);
553 }
554 anjuta_preferences_add_from_builder (prefs,
555 plugin->bxml,
556 plugin->settings,
557 "preferences", _("Python"),
558 ICON_FILE);
559 toggle = GTK_WIDGET (gtk_builder_get_object (plugin->bxml, PREF_WIDGET_AUTO));
560 g_signal_connect (toggle, "toggled", G_CALLBACK (on_autocompletion_toggled),
561 plugin);
562 on_autocompletion_toggled (GTK_TOGGLE_BUTTON (toggle), plugin);
563 }
564
565 static void
ipreferences_unmerge(IAnjutaPreferences * ipref,AnjutaPreferences * prefs,GError ** e)566 ipreferences_unmerge (IAnjutaPreferences* ipref, AnjutaPreferences* prefs,
567 GError** e)
568 {
569 PythonPlugin* plugin = ANJUTA_PLUGIN_PYTHON (ipref);
570 anjuta_preferences_remove_page(prefs, _("Python"));
571 g_object_unref (plugin->bxml);
572 }
573
574 static void
ipreferences_iface_init(IAnjutaPreferencesIface * iface)575 ipreferences_iface_init (IAnjutaPreferencesIface* iface)
576 {
577 iface->merge = ipreferences_merge;
578 iface->unmerge = ipreferences_unmerge;
579 }
580
581 ANJUTA_PLUGIN_BEGIN (PythonPlugin, python_plugin);
582 ANJUTA_PLUGIN_ADD_INTERFACE(ipreferences, IANJUTA_TYPE_PREFERENCES);
583 ANJUTA_PLUGIN_END;
584
585 ANJUTA_SIMPLE_PLUGIN (PythonPlugin, python_plugin);
586