1 /*
2  *
3  *  GeanyGenDoc, a Geany plugin to ease generation of source code documentation
4  *  Copyright (C) 2010-2011  Colomban Wendling <ban@herbesfolles.org>
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 # include "config.h" /* for the gettext domain */
23 #endif
24 
25 #include "ggd-plugin.h"
26 
27 #include <glib.h>
28 #include <glib/gi18n-lib.h>
29 #include <gdk/gdkkeysyms.h> /* for the key bindings */
30 #include <ctpl/ctpl.h>
31 #include <geanyplugin.h>
32 
33 #include "ggd.h"
34 #include "ggd-file-type.h"
35 #include "ggd-file-type-manager.h"
36 #include "ggd-tag-utils.h"
37 #include "ggd-options.h"
38 
39 
40 
41 /*
42  * Questions:
43  *  * how to update tag list? (tm_source_file_buffer_update() is not found in
44  *    symbols table)
45  */
46 
47 /* These items are set by Geany before plugin_init() is called. */
48 GeanyPlugin     *geany_plugin;
49 GeanyData       *geany_data;
50 
51 /* TODO check minimum requierment */
52 PLUGIN_VERSION_CHECK (227)
53 
54 PLUGIN_SET_TRANSLATABLE_INFO (
55   LOCALEDIR, GETTEXT_PACKAGE,
56   _("Documentation Generator"),
57   _("Generates documentation basis from source code"),
58   GGD_PLUGIN_VERSION,
59   "Colomban Wendling <ban@herbesfolles.org>"
60 )
61 
62 enum
63 {
64   KB_INSERT,
65   NUM_KB
66 };
67 
68 typedef struct _PluginData
69 {
70   GgdOptGroup    *config;
71   GeanyKeyGroup  *kb_group;
72 
73   gint            editor_menu_popup_line;
74 
75   GtkWidget      *separator_item;
76   GtkWidget      *edit_menu_item;
77   GtkWidget      *tools_menu_item;
78   gulong          edit_menu_item_hid;
79 } PluginData;
80 
81 #define plugin (&plugin_data)
82 static PluginData plugin_data = {
83   NULL, NULL, 0, NULL, NULL, NULL, 0l
84 };
85 
86 /* global plugin options
87  * default values that needs to be set dynamically goes at the top of
88  * load_configuration() */
89 gchar      *GGD_OPT_doctype[GEANY_MAX_BUILT_IN_FILETYPES] = { NULL };
90 gboolean    GGD_OPT_save_to_refresh                       = FALSE;
91 gboolean    GGD_OPT_indent                                = TRUE;
92 gchar      *GGD_OPT_environ                               = NULL;
93 
94 
95 /* Gets an element of GGD_OPT_doctype, falling back to GGD_OPT_doctype[0]
96  * (default) if the requested element is not set */
97 static const gchar *
ggd_plugin_get_doctype(GeanyFiletypeID id)98 ggd_plugin_get_doctype (GeanyFiletypeID id)
99 {
100   const gchar *doctype;
101 
102   g_return_val_if_fail (id >= 0 && id < GEANY_MAX_BUILT_IN_FILETYPES, NULL);
103 
104   doctype = GGD_OPT_doctype[id];
105   if (! doctype || ! *doctype) {
106     doctype = GGD_OPT_doctype[0];
107   }
108 
109   return doctype;
110 }
111 
112 
113 /* FIXME: tm_source_file_buffer_update() is not found in symbols table */
114 /* (tries to) refresh the tag list to the file's current state */
115 static void
refresh_tag_list(TMSourceFile * tm_wo,ScintillaObject * sci,GeanyDocument * doc)116 refresh_tag_list (TMSourceFile    *tm_wo,
117                   ScintillaObject *sci,
118                   GeanyDocument   *doc)
119 {
120   /*
121   gint    len;
122   guchar *buf;
123 
124   len = sci_get_length (sci);
125   buf = g_malloc (len + 1);
126   sci_get_text (sci, len + 1, (gchar *)buf);
127   tm_source_file_buffer_update (tm_wo, buf, len, TRUE);
128   g_free (buf);
129   //*/
130   if (GGD_OPT_save_to_refresh) {
131     document_save_file (doc, FALSE);
132   }
133 }
134 
135 /* tries to insert a comment in the current document
136  * @line: The line for which insert comment, or -1 for the current line */
137 static void
insert_comment(gint line)138 insert_comment (gint line)
139 {
140   GeanyDocument *doc;
141 
142   doc = document_get_current ();
143   if (DOC_VALID (doc)) {
144     /* try to ensure tags corresponds to the actual state of the file */
145     refresh_tag_list (doc->tm_file, doc->editor->sci, doc);
146     if (line < 0) {
147       line = sci_get_current_line (doc->editor->sci);
148     }
149     ggd_insert_comment (doc, line, ggd_plugin_get_doctype (doc->file_type->id));
150   }
151 }
152 
153 /* tries to insert comments for all the document */
154 static void
insert_all_comments(void)155 insert_all_comments (void)
156 {
157   GeanyDocument *doc;
158 
159   doc = document_get_current ();
160   if (DOC_VALID (doc)) {
161     /* try to ensure tags corresponds to the actual state of the file */
162     refresh_tag_list (doc->tm_file, doc->editor->sci, doc);
163     ggd_insert_all_comments (doc, ggd_plugin_get_doctype (doc->file_type->id));
164   }
165 }
166 
167 /* Escapes a string to use it safely as a GKeyFile key.
168  * It currently replaces only "=" and "#" since GKeyFile supports UTF-8 keys. */
169 static gchar *
normalize_key(const gchar * key)170 normalize_key (const gchar *key)
171 {
172   GString *nkey;
173 
174   nkey = g_string_new (NULL);
175   for (; *key; key++) {
176     switch (*key) {
177       case '=': g_string_append (nkey, "Equal"); break;
178       case '#': g_string_append (nkey, "Sharp"); break;
179       default:  g_string_append_c (nkey, *key); break;
180     }
181   }
182 
183   return g_string_free (nkey, FALSE);
184 }
185 
186 static gboolean
load_configuration(void)187 load_configuration (void)
188 {
189   gboolean  success = FALSE;
190   gchar    *conffile;
191   GError   *err = NULL;
192   guint     i;
193 
194   /* default options that needs to be set dynamically */
195   GGD_OPT_doctype[0] = g_strdup ("doxygen");
196 
197   plugin->config = ggd_opt_group_new ("General");
198   ggd_opt_group_add_string (plugin->config, &GGD_OPT_doctype[0], "doctype");
199   for (i = 1; i < GEANY_MAX_BUILT_IN_FILETYPES; i++) {
200     gchar *name;
201     gchar *normal_ftname;
202 
203     normal_ftname = normalize_key (filetypes[i]->name);
204     name = g_strconcat ("doctype_", normal_ftname, NULL);
205     ggd_opt_group_add_string (plugin->config, &GGD_OPT_doctype[i], name);
206     g_free (name);
207     g_free (normal_ftname);
208   }
209   ggd_opt_group_add_boolean (plugin->config, &GGD_OPT_save_to_refresh, "save_to_refresh");
210   ggd_opt_group_add_boolean (plugin->config, &GGD_OPT_indent, "indent");
211   ggd_opt_group_add_string (plugin->config, &GGD_OPT_environ, "environ");
212   conffile = ggd_get_config_file ("ggd.conf", NULL, GGD_PERM_R, &err);
213   if (conffile) {
214     success = ggd_opt_group_load_from_file (plugin->config, conffile, &err);
215   }
216   if (err) {
217     GLogLevelFlags level = G_LOG_LEVEL_WARNING;
218 
219     if (err->domain == G_FILE_ERROR && err->code == G_FILE_ERROR_NOENT) {
220       level = G_LOG_LEVEL_INFO;
221     }
222     g_log (G_LOG_DOMAIN, level,
223            _("Failed to load configuration: %s"), err->message);
224     g_error_free (err);
225   }
226   g_free (conffile);
227   /* init filetype manager */
228   ggd_file_type_manager_init ();
229 
230   return success;
231 }
232 
233 static void
unload_configuration(void)234 unload_configuration (void)
235 {
236   gchar  *conffile;
237   GError *err = NULL;
238 
239   conffile = ggd_get_config_file ("ggd.conf", NULL, GGD_PERM_RW, &err);
240   if (conffile) {
241     ggd_opt_group_write_to_file (plugin->config, conffile, &err);
242   }
243   if (err) {
244     g_warning (_("Failed to save configuration: %s"), err->message);
245     g_error_free (err);
246   }
247   g_free (conffile);
248   ggd_opt_group_free (plugin->config, TRUE);
249   plugin->config = NULL;
250   /* uninit filetype manager */
251   ggd_file_type_manager_uninit ();
252 }
253 
254 /* forces reloading of configuration files */
255 static gboolean
reload_configuration(void)256 reload_configuration (void)
257 {
258   unload_configuration ();
259   return load_configuration ();
260 }
261 
262 
263 /* --- Actual Geany interaction --- */
264 
265 /* handler for the editor's popup menu entry the plugin adds */
266 static void
editor_menu_acivated_handler(GtkMenuItem * menu_item,PluginData * pdata)267 editor_menu_acivated_handler (GtkMenuItem *menu_item,
268                               PluginData  *pdata)
269 {
270   (void)menu_item;
271 
272   insert_comment (pdata->editor_menu_popup_line);
273 }
274 
275 /* hanlder for the keybinding that inserts a comment */
276 static void
insert_comment_keybinding_handler(guint key_id)277 insert_comment_keybinding_handler (guint key_id)
278 {
279   (void)key_id;
280 
281   insert_comment (-1);
282 }
283 
284 /* handler for the GeanyDocument::update-editor-menu signal.
285  * It is used to get the line at which menu popuped */
286 static void
update_editor_menu_handler(GObject * obj,const gchar * word,gint pos,GeanyDocument * doc,PluginData * pdata)287 update_editor_menu_handler (GObject        *obj,
288                             const gchar    *word,
289                             gint            pos,
290                             GeanyDocument  *doc,
291                             PluginData     *pdata)
292 {
293   pdata->editor_menu_popup_line = sci_get_line_from_position (doc->editor->sci,
294                                                               pos);
295 }
296 
297 /* handler that opens the current filetype's configuration file */
298 static void
open_current_filetype_conf_handler(GtkWidget * widget,gpointer data)299 open_current_filetype_conf_handler (GtkWidget  *widget,
300                                     gpointer    data)
301 {
302   GeanyDocument *doc;
303 
304   (void)widget;
305   (void)data;
306 
307   doc = document_get_current ();
308   if (DOC_VALID (doc)) {
309     gchar  *path_read;
310     gchar  *path_write;
311     GError *err = NULL;
312 
313     path_write = ggd_file_type_manager_get_conf_path (doc->file_type->id,
314                                                       GGD_PERM_W | GGD_PERM_NOCREAT,
315                                                       &err);
316     if (! path_write) {
317       msgwin_status_add (_("Failed to find configuration file "
318                            "for file type \"%s\": %s"),
319                          doc->file_type->name, err->message);
320       g_error_free (err);
321     } else {
322       gchar *text = NULL;
323       gchar *path_write_u8;
324 
325       path_read = ggd_file_type_manager_get_conf_path (doc->file_type->id,
326                                                        GGD_PERM_R, &err);
327       if (! path_read) {
328         text = g_strdup (_(
329           "# Configuration for this file type doesn't exist yet.\n"
330           "# To create it, just write it in this file and save it. For the description\n"
331           "# of the syntax of this file, please refer to the manual.\n"
332         ));
333       } else {
334         gchar  *content = NULL;
335         gsize   length;
336 
337         if (! g_file_get_contents (path_read, &content, &length, &err)) {
338           gchar *display_path_read;
339 
340           display_path_read = g_filename_display_name (path_read);
341           g_warning (_("Failed to load file \"%s\": %s"),
342                      display_path_read, err->message);
343           g_free (display_path_read);
344           g_error_free (err);
345         } else {
346           text = encodings_convert_to_utf8 (content, length, NULL);
347           g_free (content);
348         }
349         g_free (path_read);
350       }
351       path_write_u8 = utils_get_utf8_from_locale (path_write);
352       /* It's no Ruby, but it is the closest one I've found. It has:
353        *  - # comments
354        *  - multi-line double-quoted strings
355        */
356       document_new_file (path_write_u8, filetypes[GEANY_FILETYPES_RUBY], text);
357       g_free (path_write_u8);
358       g_free (text);
359       g_free (path_write);
360     }
361   }
362 }
363 
364 static void
open_manual_handler(GtkWidget * widget,gpointer data)365 open_manual_handler (GtkWidget  *widget,
366                      gpointer    data)
367 {
368 #ifdef G_OS_WIN32
369   gchar *prefix = g_win32_get_package_installation_directory_of_module (NULL);
370 #else
371   gchar *prefix = NULL;
372 #endif
373   gchar *path = g_build_filename (prefix ? prefix : "", PLUGINDOCDIR,
374                                   "/html/manual.html", NULL);
375 
376   utils_open_browser (path);
377 
378   g_free (path);
379   g_free (prefix);
380 }
381 
382 /* handler that reloads the configuration */
383 static void
reload_configuration_hanlder(GtkWidget * widget,gpointer data)384 reload_configuration_hanlder (GtkWidget  *widget,
385                               gpointer    data)
386 {
387   (void)widget;
388   (void)data;
389 
390   reload_configuration ();
391 }
392 
393 /* handler that documents current symbol */
394 static void
document_current_symbol_handler(GObject * obj,gpointer data)395 document_current_symbol_handler (GObject   *obj,
396                                  gpointer   data)
397 {
398   insert_comment (-1);
399 }
400 
401 /* handler that documents all symbols */
402 static void
document_all_symbols_handler(GObject * obj,gpointer data)403 document_all_symbols_handler (GObject  *obj,
404                               gpointer  data)
405 {
406   insert_all_comments ();
407 }
408 
409 
410 /* FIXME: make menu item appear in the edit menu too */
411 /* adds the menu item in the editor's popup menu */
412 static void
add_edit_menu_item(PluginData * pdata)413 add_edit_menu_item (PluginData *pdata)
414 {
415   GtkWidget *parent_menu;
416 
417   parent_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (
418     ui_lookup_widget (geany->main_widgets->editor_menu, "comments")));
419   if (! parent_menu) {
420     parent_menu = geany->main_widgets->editor_menu;
421     pdata->separator_item = gtk_separator_menu_item_new ();
422     gtk_menu_shell_append (GTK_MENU_SHELL (parent_menu), pdata->separator_item);
423     gtk_widget_show (pdata->separator_item);
424   }
425   pdata->edit_menu_item = gtk_menu_item_new_with_label (_("Insert Documentation Comment"));
426   pdata->edit_menu_item_hid = g_signal_connect (pdata->edit_menu_item, "activate",
427                                                 G_CALLBACK (editor_menu_acivated_handler),
428                                                 pdata);
429   gtk_menu_shell_append (GTK_MENU_SHELL (parent_menu), pdata->edit_menu_item);
430   gtk_widget_show (pdata->edit_menu_item);
431   /* make item document-presence sensitive */
432   ui_add_document_sensitive (pdata->edit_menu_item);
433   /* and attach a keybinding */
434   keybindings_set_item (pdata->kb_group, KB_INSERT, insert_comment_keybinding_handler,
435                         GDK_d, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
436                         "instert_doc", _("Insert Documentation Comment"),
437                         pdata->edit_menu_item);
438 }
439 
440 /* removes the menu item in the editor's popup menu */
441 static void
remove_edit_menu_item(PluginData * pdata)442 remove_edit_menu_item (PluginData *pdata)
443 {
444   g_signal_handler_disconnect (pdata->edit_menu_item, pdata->edit_menu_item_hid);
445   pdata->edit_menu_item_hid = 0l;
446   if (pdata->separator_item) {
447     gtk_widget_destroy (pdata->separator_item);
448   }
449   gtk_widget_destroy (pdata->edit_menu_item);
450 }
451 
452 static GtkWidget *
menu_add_item(GtkMenuShell * menu,const gchar * mnemonic,const gchar * tooltip,const gchar * stock_image,GCallback activate_handler,gpointer activate_data)453 menu_add_item (GtkMenuShell  *menu,
454                const gchar   *mnemonic,
455                const gchar   *tooltip,
456                const gchar   *stock_image,
457                GCallback      activate_handler,
458                gpointer       activate_data)
459 {
460   GtkWidget *item;
461 
462   if (! stock_image) {
463     item = gtk_menu_item_new_with_mnemonic (mnemonic);
464   } else {
465     item = gtk_image_menu_item_new_with_mnemonic (mnemonic);
466     gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item),
467                                    gtk_image_new_from_stock (stock_image,
468                                                              GTK_ICON_SIZE_MENU));
469   }
470   gtk_widget_set_tooltip_text (item, tooltip);
471   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
472   if (activate_handler) {
473     g_signal_connect (item, "activate", activate_handler, activate_data);
474   }
475 
476   return item;
477 }
478 
479 /* creates plugin's tool's menu */
480 static GtkWidget *
create_tools_menu_item(void)481 create_tools_menu_item (void)
482 {
483   GtkWidget  *menu;
484   GtkWidget  *item;
485 
486   /* build submenu */
487   menu = gtk_menu_new ();
488   /* build "document current symbol" item */
489   item = menu_add_item (GTK_MENU_SHELL (menu),
490                         _("_Document Current Symbol"),
491                         _("Generate documentation for the current symbol"),
492                         NULL,
493                         G_CALLBACK (document_current_symbol_handler), NULL);
494   ui_add_document_sensitive (item);
495   /* build "document all" item */
496   item = menu_add_item (GTK_MENU_SHELL (menu),
497                         _("Document _All Symbols"),
498                         _("Generate documentation for all symbols in the "
499                           "current document"),
500                         NULL,
501                         G_CALLBACK (document_all_symbols_handler), NULL);
502   ui_add_document_sensitive (item);
503   /* separator */
504   item = gtk_separator_menu_item_new ();
505   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
506   /* build "reload" item */
507   item = menu_add_item (GTK_MENU_SHELL (menu),
508                         _("_Reload Configuration Files"),
509                         _("Force reloading of the configuration files"),
510                         GTK_STOCK_REFRESH,
511                         G_CALLBACK (reload_configuration_hanlder), NULL);
512   /* language filetypes opener */
513   item = menu_add_item (GTK_MENU_SHELL (menu),
514                         _("_Edit Current Language Configuration"),
515                         _("Open the current language configuration file for "
516                           "editing"),
517                         GTK_STOCK_EDIT,
518                         G_CALLBACK (open_current_filetype_conf_handler), NULL);
519   ui_add_document_sensitive (item);
520   /* separator */
521   item = gtk_separator_menu_item_new ();
522   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
523   /* help/manual opening */
524   item = menu_add_item (GTK_MENU_SHELL (menu),
525                         _("Open _Manual"),
526                         _("Open the manual in a browser"),
527                         GTK_STOCK_HELP,
528                         G_CALLBACK (open_manual_handler), NULL);
529   /* build tools menu item */
530   item = gtk_menu_item_new_with_mnemonic (_("_Documentation Generator"));
531   gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
532   gtk_widget_show_all (item);
533 
534   return item;
535 }
536 
537 /* build plugin's menus */
538 static void
build_menus(PluginData * pdata)539 build_menus (PluginData *pdata)
540 {
541   add_edit_menu_item (pdata);
542   pdata->tools_menu_item = create_tools_menu_item ();
543   gtk_menu_shell_append (GTK_MENU_SHELL (geany->main_widgets->tools_menu),
544                          pdata->tools_menu_item);
545 }
546 
547 /* destroys plugin's menus */
548 static void
destroy_menus(PluginData * pdata)549 destroy_menus (PluginData *pdata)
550 {
551   gtk_widget_destroy (pdata->tools_menu_item);
552   pdata->tools_menu_item = NULL;
553   remove_edit_menu_item (pdata);
554 }
555 
556 void
plugin_init(GeanyData * data G_GNUC_UNUSED)557 plugin_init (GeanyData *data G_GNUC_UNUSED)
558 {
559   plugin->kb_group = plugin_set_key_group (geany_plugin, GGD_PLUGIN_CNAME,
560                                            NUM_KB, NULL);
561   load_configuration ();
562   build_menus (plugin);
563   plugin_signal_connect (geany_plugin, NULL, "update-editor-menu", FALSE,
564                          G_CALLBACK (update_editor_menu_handler), plugin);
565 }
566 
567 void
plugin_cleanup(void)568 plugin_cleanup (void)
569 {
570   destroy_menus (plugin);
571   unload_configuration ();
572   plugin->kb_group = NULL;
573 }
574 
575 void
plugin_help(void)576 plugin_help (void)
577 {
578   open_manual_handler (NULL, NULL);
579 }
580 
581 
582 /* --- Configuration dialog --- */
583 
584 #include "ggd-widget-frame.h"
585 #include "ggd-widget-doctype-selector.h"
586 
587 static GtkWidget *GGD_W_doctype_selector;
588 
589 static void
conf_dialog_response_handler(GtkDialog * dialog,gint response_id,PluginData * pdata)590 conf_dialog_response_handler (GtkDialog  *dialog,
591                               gint        response_id,
592                               PluginData *pdata)
593 {
594   switch (response_id) {
595     case GTK_RESPONSE_ACCEPT:
596     case GTK_RESPONSE_APPLY:
597     case GTK_RESPONSE_OK:
598     case GTK_RESPONSE_YES: {
599       guint i;
600 
601       ggd_opt_group_sync_from_proxies (pdata->config);
602       for (i = 0; i < GEANY_MAX_BUILT_IN_FILETYPES; i++) {
603         g_free (GGD_OPT_doctype[i]);
604         GGD_OPT_doctype[i] = ggd_doctype_selector_get_doctype (GGD_DOCTYPE_SELECTOR (GGD_W_doctype_selector),
605                                                                i);
606       }
607       break;
608     }
609 
610     default: break;
611   }
612 }
613 
614 GtkWidget *
plugin_configure(GtkDialog * dialog)615 plugin_configure (GtkDialog *dialog)
616 {
617   GtkWidget  *box;
618   GtkWidget  *box2;
619   GtkWidget  *frame;
620   GtkWidget  *widget;
621   guint       i;
622 
623   g_signal_connect (dialog, "response",
624                     G_CALLBACK (conf_dialog_response_handler), plugin);
625 
626   box = gtk_vbox_new (FALSE, 12);
627 
628   /* General */
629   frame = ggd_frame_new (_("General"));
630   gtk_box_pack_start (GTK_BOX (box), frame, FALSE, FALSE, 0);
631   box2 = gtk_vbox_new (FALSE, 0);
632   gtk_container_add (GTK_CONTAINER (frame), box2);
633   /* auto-save */
634   widget = gtk_check_button_new_with_mnemonic (_("_Save file before generating "
635                                                  "documentation"));
636   gtk_widget_set_tooltip_text (widget,
637     _("Whether the current document should be saved to disc before generating "
638       "the documentation. This is a technical detail, but it is currently "
639       "needed to have an up-to-date tag list. If you disable this option and "
640       "ask for documentation generation on a modified document, the behavior "
641       "may be surprising since the comment will be generated for the last "
642       "saved state of this document and not the current one."));
643   ggd_opt_group_set_proxy_gtktogglebutton (plugin->config, &GGD_OPT_save_to_refresh,
644                                            widget);
645   gtk_box_pack_start (GTK_BOX (box2), widget, FALSE, FALSE, 0);
646   /* indent */
647   widget = gtk_check_button_new_with_mnemonic (_("_Indent inserted documentation"));
648   gtk_widget_set_tooltip_text (widget,
649     _("Whether the inserted documentation should be indented to fit the "
650       "indentation at the insertion position."));
651   ggd_opt_group_set_proxy_gtktogglebutton (plugin->config, &GGD_OPT_indent,
652                                            widget);
653   gtk_box_pack_start (GTK_BOX (box2), widget, FALSE, FALSE, 0);
654 
655   /* Documentation type */
656   frame = ggd_frame_new (_("Documentation type"));
657   gtk_box_pack_start (GTK_BOX (box), frame, TRUE, TRUE, 0);
658   box2 = gtk_vbox_new (FALSE, 0);
659   gtk_container_add (GTK_CONTAINER (frame), box2);
660   GGD_W_doctype_selector = ggd_doctype_selector_new ();
661   for (i = 0; i < GEANY_MAX_BUILT_IN_FILETYPES; i++) {
662     ggd_doctype_selector_set_doctype (GGD_DOCTYPE_SELECTOR (GGD_W_doctype_selector),
663                                       i, GGD_OPT_doctype[i]);
664   }
665   gtk_widget_set_tooltip_text (GGD_W_doctype_selector,
666     _("Choose the documentation type to use with each file type. The special "
667       "language \"All\" on top of the list is used to choose the default "
668       "documentation type, used for all languages that haven't one set."));
669   gtk_box_pack_start (GTK_BOX (box2), GGD_W_doctype_selector, TRUE, TRUE, 0);
670 
671   /* Environ editor */
672   widget = ggd_frame_new (_("Global environment"));
673   gtk_widget_set_tooltip_text (widget,
674     _("Global environment overrides and additions. This environment will be "
675       "merged with the file-type-specific ones."));
676   {
677     GtkWidget *scrolled;
678     GtkWidget *view;
679     GObject   *proxy;
680 
681     scrolled = gtk_scrolled_window_new (NULL, NULL);
682     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
683                                     GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
684     gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
685                                          GTK_SHADOW_IN);
686     gtk_container_add (GTK_CONTAINER (widget), scrolled);
687     view = gtk_text_view_new ();
688     proxy = G_OBJECT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
689     ggd_opt_group_set_proxy (plugin->config, &GGD_OPT_environ, proxy, "text");
690     gtk_container_add (GTK_CONTAINER (scrolled), view);
691   }
692   gtk_box_pack_start (GTK_BOX (box), widget, TRUE, TRUE, 0);
693 
694 
695   gtk_widget_show_all (box);
696 
697   return box;
698 }
699