Don't show diff of removed or added files
[geanyvc.git] / geanyvc.c
blob58a13e8587f66b9a947c053d643d07140606bfd7
1 /*
2 * geanyvc.c - Plugin to geany light IDE to work with vc
4 * Copyright 2007 Frank Lanitz <frank(at)frank(dot)uvena(dot)de>
5 * Copyright 2007 Enrico Tröger <enrico.troeger@uvena.de>
6 * Copyright 2007 Nick Treleaven <nick.treleaven@btinternet.com>
7 * Copyright 2007 Yura Siamashka <yurand2@gmail.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 /* VC plugin */
25 /* This plugin allow to works with cvs/svn/git inside geany light IDE. */
27 #include "geany.h"
28 #include "support.h"
29 #include "plugindata.h"
30 #include "document.h"
31 #include "filetypes.h"
32 #include "utils.h"
33 #include "project.h"
34 #include "pluginmacros.h"
36 #include "geanyvc.h"
38 PluginFields *plugin_fields;
39 GeanyData *geany_data;
42 VERSION_CHECK(27);
43 PLUGIN_INFO(_("VC"), _("Interface to different Version Control systems."), VERSION,
44 _("Yura Siamashka <yurand2@gmail.com>,\nFrank Lanitz <frank@frank.uvena.de>"));
46 /* Some global variables */
47 static gboolean set_changed_flag;
48 static gboolean set_add_confirmation;
49 static gchar *config_file;
51 static gboolean enable_cvs;
52 static gboolean enable_git;
53 static gboolean enable_svn;
54 static gboolean enable_svk;
56 static GSList *VC = NULL;
58 /* The addresses of these strings act as enums, their contents are not used. */
59 const gchar DIRNAME[] = "*DIRNAME*";
60 const gchar FILENAME[] = "*FILENAME*";
61 const gchar BASE_FILENAME[] = "*BASE_FILENAME*";
62 const gchar FILE_LIST[] = "*FILE_LIST*";
63 const gchar MESSAGE[] = "*MESSAGE*";
65 /* this string is used when action require to run several commands */
66 const gchar CMD_SEPARATOR[] = "*CMD-SEPARATOR*";
68 /* commit status */
69 const gchar FILE_STATUS_MODIFIED[] = "Modified";
70 const gchar FILE_STATUS_ADDED[] = "Added";
71 const gchar FILE_STATUS_DELETED[] = "Deleted";
72 const gchar FILE_STATUS_UNKNOWN[] = "Unknown";
74 void *NO_ENV[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
76 static void registrate();
78 GSList *
79 get_commit_files_null(const gchar * dir)
81 return NULL;
84 static void
85 free_commit_list(GSList * lst)
87 GSList *tmp;
88 if (!lst)
89 return;
90 for (tmp = lst; tmp != NULL; tmp = g_slist_next(tmp))
92 g_free(((CommitItem *) (tmp->data))->path);
93 g_free((CommitItem *) (tmp->data));
95 g_slist_free(lst);
98 gchar *
99 find_subdir_path(const gchar * filename, const gchar * subdir)
101 gboolean ret = FALSE;
102 gchar *base;
103 gchar *gitdir;
104 gchar *base_prev = g_strdup(":");
106 if (g_file_test(filename, G_FILE_TEST_IS_DIR))
107 base = g_strdup(filename);
108 else
109 base = g_path_get_dirname(filename);
111 while (strcmp(base, base_prev) != 0)
113 gitdir = g_build_path("/", base, subdir, NULL);
114 ret = g_file_test(gitdir, G_FILE_TEST_IS_DIR);
115 g_free(gitdir);
116 if (ret)
117 break;
118 g_free(base_prev);
119 base_prev = base;
120 base = g_path_get_dirname(base);
123 g_free(base_prev);
124 if (ret)
125 return base;
126 g_free(base);
127 return NULL;
130 static gboolean
131 find_subdir(const gchar * filename, const gchar * subdir)
133 gchar *basedir;
134 basedir = find_subdir_path(filename, subdir);
135 if (basedir)
137 g_free(basedir);
138 return TRUE;
140 return FALSE;
143 gboolean
144 find_dir(const gchar * filename, const char *find, gboolean recursive)
146 gboolean ret;
147 gchar *base;
148 gchar *dir;
149 gchar *path;
151 if (!filename)
152 return FALSE;
154 if (recursive)
156 ret = find_subdir(filename, find);
158 else
160 if (g_file_test(filename, G_FILE_TEST_IS_DIR))
161 base = g_strdup(filename);
162 else
163 base = g_path_get_dirname(filename);
164 dir = g_build_path("/", base, find, NULL);
166 ret = g_file_test(dir, G_FILE_TEST_IS_DIR);
168 g_free(base);
169 g_free(dir);
171 return ret;
175 static const VC_RECORD *
176 find_vc(const char *filename)
178 GSList *tmp;
180 for (tmp = VC; tmp != NULL; tmp = g_slist_next(tmp))
182 if (((VC_RECORD *) tmp->data)->in_vc(filename))
184 return (VC_RECORD *) tmp->data;
187 return NULL;
190 static void *
191 find_cmd_env(gint cmd_type, gboolean cmd, const gchar * filename)
193 const VC_RECORD *vc;
194 vc = find_vc(filename);
195 if (vc)
197 if (cmd)
198 return vc->commands[cmd_type];
199 else
200 return vc->envs[cmd_type];
202 return NULL;
205 /* Get list of commands for given command spec*/
206 static GSList *
207 get_cmd(const gchar ** argv, const gchar * filename, GSList * filelist, const gchar * message)
209 gint i, j;
210 gint len = 0;
211 gchar **ret;
212 gchar *dir;
213 gchar *base_filename;
214 GSList *head = NULL;
215 GSList *tmp;
217 if (g_file_test(filename, G_FILE_TEST_IS_DIR))
219 dir = g_strdup(filename);
221 else
223 dir = g_path_get_dirname(filename);
225 base_filename = g_path_get_basename(filename);
227 while (1)
229 if (argv[len] == NULL)
230 break;
231 len++;
233 if (filelist)
234 ret = g_malloc0(sizeof(gchar *) * (len * g_slist_length(filelist) + 1));
235 else
236 ret = g_malloc0(sizeof(gchar *) * (len + 1));
238 head = g_slist_alloc();
239 head->data = ret;
241 for (i = 0, j = 0; i < len; i++, j++)
243 if (argv[i] == CMD_SEPARATOR)
245 if (filelist)
246 ret = g_malloc0(sizeof(gchar *) *
247 (len * g_slist_length(filelist) + 1));
248 else
249 ret = g_malloc0(sizeof(gchar *) * (len + 1));
250 j = -1;
251 head = g_slist_append(head, ret);
253 else if (argv[i] == DIRNAME)
255 ret[j] = g_strdup(dir);
257 else if (argv[i] == FILENAME)
259 ret[j] = g_strdup(filename);
261 else if (argv[i] == BASE_FILENAME)
263 ret[j] = g_strdup(base_filename);
265 else if (argv[i] == FILE_LIST)
267 for (tmp = filelist; tmp != NULL; tmp = g_slist_next(tmp))
269 ret[j] = g_strdup((gchar *) tmp->data);
270 j++;
272 j--;
274 else if (argv[i] == MESSAGE)
276 ret[j] = g_strdup(message);
278 else
279 ret[j] = g_strdup(argv[i]);
281 g_free(dir);
282 g_free(base_filename);
283 return head;
286 static GSList *
287 get_cmd_int(gint cmd_type, const gchar * filename, GSList * list, const gchar * message)
289 gint i, j;
290 gint len = 0;
291 const gchar **argv;
292 gchar **ret;
293 gchar *dir;
294 gchar *base_filename;
295 GSList *head = NULL;
297 argv = find_cmd_env(cmd_type, TRUE, filename);
298 if (!argv)
299 return NULL;
301 return get_cmd(argv, filename, list, message);
305 static int
306 find_by_filename(const gchar * filename)
308 guint i;
310 for (i = 0; i < doc_array->len; i++)
312 if (doc_list[i].is_valid && doc_list[i].file_name &&
313 strcmp(doc_list[i].file_name, filename) == 0)
314 return i;
316 return -1;
320 /* name_prefix should be in UTF-8, and can have a path. */
321 static void
322 show_output(const gchar * std_output, const gchar * name, const gchar * force_encoding)
324 gint idx, page;
325 GtkNotebook *book;
327 if (std_output)
329 idx = find_by_filename(name);
330 if (idx == -1)
332 idx = documents->new_file(name,
333 geany_data->filetypes[GEANY_FILETYPES_DIFF],
334 std_output);
336 else
338 scintilla->set_text(doc_list[idx].sci, std_output);
339 book = GTK_NOTEBOOK(app->notebook);
340 page = gtk_notebook_page_num(book, GTK_WIDGET(doc_list[idx].sci));
341 gtk_notebook_set_current_page(book, page);
343 doc_list[idx].changed = TRUE;
344 if (!set_changed_flag)
345 doc_list[idx].changed = FALSE;
346 documents->set_text_changed(idx);
347 documents->set_encoding(idx, (force_encoding ? force_encoding : "utf-8"));
349 else
351 ui->set_statusbar(FALSE, _("Could not parse the output of the diff"));
356 * Execute command by command spec, return std_out std_err
358 * @argv - command spec
359 * @env - envirounment
360 * @std_out - if not NULL here will be returned standard output converted to utf8 of last command in spec
361 * @std_err - if not NULL here will be returned standard error converted to utf8 of last command in spec
362 * @filename - filename for spec, commands will be running in it's basedir . Used to replace FILENAME, BASE_FILENAME in spec
363 * @list - used to replace FILE_LIST in spec
364 * @message - used to replace MESSAGE in spec
366 * @return - exit code of last command in spec
368 gint
369 execute_custom_command(const gchar ** argv, const gchar ** env, gchar ** std_out, gchar ** std_err,
370 const gchar * filename, GSList * list, const gchar * message)
372 gint exit_code;
373 gchar *text = NULL;
374 gchar *dir;
375 GSList *cur;
376 GSList *largv = get_cmd(argv, filename, list, message);
377 GError *error = NULL;
378 gchar *detect_enc = NULL;
379 gchar *force_encoding = NULL;
381 if (std_out)
382 *std_out = NULL;
383 if (std_err)
384 *std_err = NULL;
386 if (!largv)
388 return 0;
391 if (g_file_test(filename, G_FILE_TEST_IS_DIR))
393 dir = g_strdup(filename);
395 else
397 dir = g_path_get_dirname(filename);
400 for (cur = largv; cur != NULL; cur = g_slist_next(cur))
402 argv = cur->data;
403 if (cur != g_slist_last(largv))
405 g_spawn_sync(dir, cur->data, (gchar **) env,
406 G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL |
407 G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, &exit_code,
408 &error);
410 else
412 g_spawn_sync(dir, cur->data, (gchar **) env,
413 G_SPAWN_SEARCH_PATH | (std_out ? 0 :
414 G_SPAWN_STDOUT_TO_DEV_NULL) | (std_err ?
416 G_SPAWN_STDERR_TO_DEV_NULL),
417 NULL, NULL, std_out, std_err, &exit_code, &error);
419 if (error)
421 fprintf(stderr, "s_spawn_sync error: %s", error->message);
422 g_error_free(error);
423 ui->set_statusbar(FALSE, _("Something went really wrong."));
426 // need to convert output text from the encoding of the original file into
427 // UTF-8 because internally Geany always needs UTF-8
428 if (std_out && *std_out)
430 if (force_encoding)
432 setptr(*std_out,
433 encodings->convert_to_utf8_from_charset(*std_out, -1,
434 force_encoding,
435 TRUE));
437 else
439 setptr(*std_out,
440 encodings->convert_to_utf8(*std_out, -1, &detect_enc));
442 if (detect_enc)
444 g_free(detect_enc);
445 detect_enc = NULL;
447 if (!NZV(*std_out))
449 g_free(*std_out);
450 *std_out = NULL;
453 if (std_err && *std_err)
455 if (force_encoding)
457 setptr(*std_err,
458 encodings->convert_to_utf8_from_charset(*std_err, -1,
459 force_encoding,
460 TRUE));
462 else
464 setptr(*std_err,
465 encodings->convert_to_utf8(*std_err, -1, &detect_enc));
467 if (detect_enc)
469 g_free(detect_enc);
470 detect_enc = NULL;
472 if (!NZV(*std_err))
474 g_free(*std_err);
475 *std_err = NULL;
478 g_strfreev(cur->data);
480 g_free(dir);
481 g_slist_free(largv);
482 return exit_code;
485 gint
486 execute_command(const VC_RECORD * vc, gchar ** std_out, gchar ** std_err, const gchar * filename,
487 gint cmd, GSList * list, const gchar * message)
489 return execute_custom_command(vc->commands[cmd], vc->envs[cmd], std_out, std_err, filename,
490 list, message);
493 /* Callback if menu item for a single file was activated */
494 static void
495 vcfile_activated(GtkMenuItem * menuitem, gpointer gdata)
497 gint idx;
498 gchar *locale_filename, *text = NULL;
499 gchar *name;
500 const VC_RECORD *vc;
502 idx = documents->get_cur_idx();
504 g_return_if_fail(DOC_IDX_VALID(idx) && doc_list[idx].file_name != NULL);
506 if (doc_list[idx].changed)
508 documents->save_file(idx, FALSE);
511 locale_filename = utils->get_locale_from_utf8(doc_list[idx].file_name);
513 vc = find_vc(doc_list[idx].file_name);
514 g_return_if_fail(vc);
516 execute_command(vc, &text, NULL, locale_filename, VC_COMMAND_DIFF_FILE, NULL, NULL);
517 if (text)
519 name = g_strconcat(doc_list[idx].file_name, ".vc.diff", NULL);
520 show_output(text, name, doc_list[idx].encoding);
521 g_free(text);
522 g_free(name);
524 g_free(locale_filename);
527 /* Make a diff from the current directory */
528 static void
529 vcdirectory_activated(GtkMenuItem * menuitem, gpointer gdata)
531 gint idx;
532 gchar *base_name = NULL;
533 gchar *locale_filename = NULL;
534 gchar *text = NULL;
535 const VC_RECORD *vc;
537 idx = documents->get_cur_idx();
539 g_return_if_fail(DOC_IDX_VALID(idx) && doc_list[idx].file_name != NULL);
541 if (doc_list[idx].changed)
543 documents->save_file(idx, FALSE);
546 locale_filename = utils->get_locale_from_utf8(doc_list[idx].file_name);
547 base_name = g_path_get_dirname(locale_filename);
549 vc = find_vc(base_name);
550 g_return_if_fail(vc);
552 execute_command(vc, &text, NULL, base_name, VC_COMMAND_DIFF_DIR, NULL, NULL);
553 if (text)
555 setptr(base_name, g_strconcat(base_name, ".vc.diff", NULL));
556 show_output(text, base_name, NULL);
557 g_free(text);
560 g_free(base_name);
561 g_free(locale_filename);
565 /* Callback if menu item for the current project was activated */
566 static void
567 vcproject_activated(GtkMenuItem * menuitem, gpointer gdata)
569 gint idx;
570 gchar *locale_filename = NULL;
571 gchar *text = NULL;
572 gchar *name;
573 const VC_RECORD *vc;
575 idx = documents->get_cur_idx();
577 g_return_if_fail(project != NULL && NZV(project->base_path));
579 if (DOC_IDX_VALID(idx) && doc_list[idx].changed && doc_list[idx].file_name != NULL)
581 documents->save_file(idx, FALSE);
584 locale_filename = utils->get_locale_from_utf8(project->base_path);
586 vc = find_vc(project->base_path);
587 g_return_if_fail(vc);
589 execute_command(vc, &text, NULL, project->base_path, VC_COMMAND_DIFF_DIR, NULL, NULL);
590 if (text)
592 name = g_strconcat(project->name, ".vc.diff", NULL);
593 show_output(text, name, NULL);
594 g_free(text);
595 g_free(name);
597 g_free(locale_filename);
600 static void
601 vclog_file_activated(GtkMenuItem * menuitem, gpointer gdata)
603 gint idx;
604 gchar *locale_filename;
605 gchar *output = NULL;
606 const VC_RECORD *vc;
608 idx = documents->get_cur_idx();
610 g_return_if_fail(DOC_IDX_VALID(idx) && doc_list[idx].file_name != NULL);
612 locale_filename = utils->get_locale_from_utf8(doc_list[idx].file_name);
614 ui->set_statusbar(TRUE, locale_filename);
616 vc = find_vc(doc_list[idx].file_name);
617 g_return_if_fail(vc);
619 execute_command(vc, &output, NULL, locale_filename, VC_COMMAND_LOG_FILE, NULL, NULL);
620 if (output)
622 show_output(output, "*VC-LOG*", NULL);
623 g_free(output);
625 g_free(locale_filename);
628 static void
629 vclog_dir_activated(GtkMenuItem * menuitem, gpointer gdata)
631 gint idx;
632 gchar *base_name = NULL;
633 gchar *locale_filename = NULL;
634 gchar *text = NULL;
635 const VC_RECORD *vc;
637 idx = documents->get_cur_idx();
639 g_return_if_fail(DOC_IDX_VALID(idx) && doc_list[idx].file_name != NULL);
641 locale_filename = utils->get_locale_from_utf8(doc_list[idx].file_name);
642 base_name = g_path_get_dirname(locale_filename);
644 vc = find_vc(base_name);
645 g_return_if_fail(vc);
647 execute_command(vc, &text, NULL, base_name, VC_COMMAND_LOG_DIR, NULL, NULL);
648 if (text)
650 show_output(text, "*VC-LOG*", NULL);
651 g_free(text);
654 g_free(base_name);
655 g_free(locale_filename);
658 static void
659 vclog_project_activated(GtkMenuItem * menuitem, gpointer gdata)
661 gint idx;
662 gchar *locale_filename = NULL;
663 gchar *text = NULL;
664 const VC_RECORD *vc;
666 idx = documents->get_cur_idx();
668 g_return_if_fail(project != NULL && NZV(project->base_path));
670 locale_filename = utils->get_locale_from_utf8(project->base_path);
672 vc = find_vc(locale_filename);
673 g_return_if_fail(vc);
675 execute_command(vc, &text, NULL, locale_filename, VC_COMMAND_LOG_DIR, NULL, NULL);
676 if (text)
678 show_output(text, "*VC-LOG*", NULL);
679 g_free(text);
681 g_free(locale_filename);
684 /* Show status from the current directory */
685 static void
686 vcstatus_activated(GtkMenuItem * menuitem, gpointer gdata)
688 gint idx;
689 gchar *base_name = NULL;
690 gchar *locale_filename = NULL;
691 gchar *text = NULL;
692 const VC_RECORD *vc;
694 idx = documents->get_cur_idx();
696 g_return_if_fail(DOC_IDX_VALID(idx) && doc_list[idx].file_name != NULL);
698 if (doc_list[idx].changed)
700 documents->save_file(idx, FALSE);
703 locale_filename = utils->get_locale_from_utf8(doc_list[idx].file_name);
704 base_name = g_path_get_dirname(locale_filename);
706 vc = find_vc(doc_list[idx].file_name);
707 g_return_if_fail(vc);
709 execute_command(vc, &text, NULL, base_name, VC_COMMAND_STATUS, NULL, NULL);
710 if (text)
712 show_output(text, "*VC-STATUS*", NULL);
713 g_free(text);
716 g_free(base_name);
717 g_free(locale_filename);
720 static gboolean
721 command_with_question_activated(GtkMenuItem * menuitem, gpointer gdata, gint cmd,
722 const gchar * question, gboolean ask)
724 gint idx;
725 gchar *locale_filename, *text;
726 GtkWidget *dialog;
727 gint result;
728 gchar *dir;
729 const VC_RECORD *vc;
731 idx = documents->get_cur_idx();
733 g_return_val_if_fail(DOC_IDX_VALID(idx) && doc_list[idx].file_name != NULL, FALSE);
735 if (doc_list[idx].changed)
737 documents->save_file(idx, FALSE);
740 locale_filename = utils->get_locale_from_utf8(doc_list[idx].file_name);
742 if (ask)
744 dialog = gtk_message_dialog_new(GTK_WINDOW(app->window),
745 GTK_DIALOG_DESTROY_WITH_PARENT,
746 GTK_MESSAGE_QUESTION,
747 GTK_BUTTONS_YES_NO, question, locale_filename);
748 result = gtk_dialog_run(GTK_DIALOG(dialog));
749 gtk_widget_destroy(dialog);
751 else
753 result = GTK_RESPONSE_YES;
756 if (result == GTK_RESPONSE_YES)
758 dir = g_path_get_dirname(doc_list[idx].file_name);
759 vc = find_vc(dir);
760 g_return_if_fail(vc);
762 execute_command(vc, &text, NULL, locale_filename, cmd, NULL, NULL);
763 if (text)
765 g_free(text);
767 g_free(dir);
769 g_free(locale_filename);
770 return (result == GTK_RESPONSE_YES) ? TRUE : FALSE;
773 static void
774 vcrevert_activated(GtkMenuItem * menuitem, gpointer gdata)
776 gint idx;
777 gboolean ret;
778 ret = command_with_question_activated(menuitem, gdata, VC_COMMAND_REVERT_FILE,
779 _("Do you really want to revert: %s"), TRUE);
780 if (ret)
782 idx = documents->get_cur_idx();
783 documents->reload_file(idx, NULL);
788 static void
789 vcadd_activated(GtkMenuItem * menuitem, gpointer gdata)
791 command_with_question_activated(menuitem, gdata, VC_COMMAND_ADD,
792 _("Do you really want to add: %s"), set_add_confirmation);
795 static void
796 vcremove_activated(GtkMenuItem * menuitem, gpointer gdata)
798 gboolean ret;
800 ret = command_with_question_activated(menuitem, gdata, VC_COMMAND_REMOVE,
801 _("Do you really want to remove: %s"), TRUE);
802 if (ret)
804 documents->remove(gtk_notebook_get_current_page(GTK_NOTEBOOK(app->notebook)));
808 #include "generated/interface.c"
810 enum
812 COLUMN_COMMIT,
813 COLUMN_STATUS,
814 COLUMN_PATH,
815 NUM_COLUMNS
818 static GtkTreeModel *
819 create_commit_model(const GSList * commit)
821 GtkListStore *store;
822 GtkTreeIter iter;
823 const GSList *cur;
825 /* create list store */
826 store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING);
828 /* add data to the list store */
830 for (cur = commit; cur != NULL; cur = g_slist_next(cur))
832 gtk_list_store_append(store, &iter);
833 gtk_list_store_set(store, &iter,
834 COLUMN_COMMIT, TRUE,
835 COLUMN_STATUS, ((CommitItem *) (cur->data))->status,
836 COLUMN_PATH, ((CommitItem *) (cur->data))->path, -1);
839 return GTK_TREE_MODEL(store);
842 static gboolean
843 get_commit_files_foreach(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter,
844 gpointer data)
846 GSList **files = (GSList **) data;
847 gboolean commit;
848 gchar *filename;
850 gtk_tree_model_get(model, iter, COLUMN_COMMIT, &commit, -1);
851 if (!commit)
852 return FALSE;
854 gtk_tree_model_get(model, iter, COLUMN_PATH, &filename, -1);
855 *files = g_slist_prepend(*files, filename);
856 return FALSE;
859 static gboolean
860 get_commit_diff_foreach(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, gpointer data)
862 gchar **diff = (gchar **) data;
863 gboolean commit;
864 gchar *filename;
865 gchar *tmp = NULL;
866 gchar *status;
867 const VC_RECORD *vc;
869 gtk_tree_model_get(model, iter, COLUMN_COMMIT, &commit, -1);
870 if (!commit)
871 return FALSE;
873 gtk_tree_model_get(model, iter, COLUMN_STATUS, &status, -1);
875 if (strcmp(status, FILE_STATUS_MODIFIED) != 0)
877 g_free(status);
878 return FALSE;
881 gtk_tree_model_get(model, iter, COLUMN_PATH, &filename, -1);
883 vc = find_vc(filename);
884 g_return_val_if_fail(vc, FALSE);
886 execute_command(vc, &tmp, NULL, filename, VC_COMMAND_DIFF_FILE, NULL, NULL);
887 setptr(*diff, g_strdup_printf("%s%s", *diff, tmp));
889 g_free(tmp);
890 g_free(filename);
891 return FALSE;
894 static gchar *
895 get_commit_diff(GtkTreeView * treeview)
897 GtkTreeModel *model = gtk_tree_view_get_model(treeview);
898 GtkTreeIter iter;
899 gchar *ret = g_strdup("");
901 gtk_tree_model_foreach(model, get_commit_diff_foreach, &ret);
902 return ret;
905 static void
906 commit_toggled(GtkCellRendererToggle * cell, gchar * path_str, gpointer data)
908 GtkTreeView *treeview = GTK_TREE_VIEW(data);
909 GtkWidget *diffView = lookup_widget(GTK_WIDGET(treeview), "textDiff");
910 GtkTreeModel *model = gtk_tree_view_get_model(treeview);
911 GtkTreeIter iter;
912 GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
913 gboolean fixed;
914 gchar *diff;
916 /* get toggled iter */
917 gtk_tree_model_get_iter(model, &iter, path);
918 gtk_tree_model_get(model, &iter, COLUMN_COMMIT, &fixed, -1);
920 /* do something with the value */
921 fixed ^= 1;
923 /* set new value */
924 gtk_list_store_set(GTK_LIST_STORE(model), &iter, COLUMN_COMMIT, fixed, -1);
926 diff = get_commit_diff(GTK_TREE_VIEW(treeview));
927 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(diffView)), diff, -1);
929 /* clean up */
930 gtk_tree_path_free(path);
931 g_free(diff);
934 static void
935 add_commit_columns(GtkTreeView * treeview)
937 GtkCellRenderer *renderer;
938 GtkTreeViewColumn *column;
939 GtkTreeModel *model = gtk_tree_view_get_model(treeview);
941 /* column for fixed toggles */
942 renderer = gtk_cell_renderer_toggle_new();
943 g_signal_connect(renderer, "toggled", G_CALLBACK(commit_toggled), treeview);
945 column = gtk_tree_view_column_new_with_attributes("Commit?",
946 renderer, "active", COLUMN_COMMIT, NULL);
948 /* set this column to a fixed sizing (of 50 pixels) */
949 gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column), GTK_TREE_VIEW_COLUMN_FIXED);
950 gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(column), 70);
951 gtk_tree_view_append_column(treeview, column);
953 /* column for bug numbers */
954 renderer = gtk_cell_renderer_text_new();
955 column = gtk_tree_view_column_new_with_attributes("Status",
956 renderer, "text", COLUMN_STATUS, NULL);
957 gtk_tree_view_column_set_sort_column_id(column, COLUMN_STATUS);
958 gtk_tree_view_append_column(treeview, column);
960 /* column for severities */
961 renderer = gtk_cell_renderer_text_new();
962 column = gtk_tree_view_column_new_with_attributes("Path",
963 renderer, "text", COLUMN_PATH, NULL);
964 gtk_tree_view_column_set_sort_column_id(column, COLUMN_PATH);
965 gtk_tree_view_append_column(treeview, column);
968 GtkWidget *
969 lookup_widget(GtkWidget * widget, const gchar * widget_name)
971 GtkWidget *parent, *found_widget;
973 for (;;)
975 if (GTK_IS_MENU(widget))
976 parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
977 else
978 parent = widget->parent;
979 if (!parent)
980 parent = (GtkWidget *) g_object_get_data(G_OBJECT(widget),
981 "GladeParentKey");
982 if (parent == NULL)
983 break;
984 widget = parent;
987 found_widget = (GtkWidget *) g_object_get_data(G_OBJECT(widget), widget_name);
988 if (!found_widget)
989 g_warning("Widget not found: %s", widget_name);
990 return found_widget;
993 static void
994 vccommit_activated(GtkMenuItem * menuitem, gpointer gdata)
996 document *doc;
997 gint result;
998 const VC_RECORD *vc;
999 GSList *lst;
1000 GtkTreeModel *model;
1001 GtkWidget *commit = create_commitDialog();
1002 GtkWidget *treeview = lookup_widget(commit, "treeSelect");
1003 GtkWidget *diffView = lookup_widget(commit, "textDiff");
1004 GtkWidget *messageView = lookup_widget(commit, "textCommitMessage");
1005 GtkTextBuffer *mbuf;
1006 GtkTextIter begin;
1007 GtkTextIter end;
1008 GSList *selected_files = NULL;
1010 gchar *dir;
1011 gchar *message;
1012 gchar *diff;
1014 doc = documents->get_current();
1015 g_return_if_fail(doc);
1016 g_return_if_fail(doc->file_name);
1017 dir = g_path_get_dirname(doc->file_name);
1019 vc = find_vc(dir);
1020 g_return_if_fail(vc);
1022 lst = vc->get_commit_files(dir);
1023 model = create_commit_model(lst);
1024 gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), model);
1025 g_object_unref(model);
1027 /* add columns to the tree view */
1028 add_commit_columns(GTK_TREE_VIEW(treeview));
1030 diff = get_commit_diff(GTK_TREE_VIEW(treeview));
1031 gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(diffView)), diff, -1);
1033 gtk_window_maximize(GTK_WINDOW(commit));
1035 result = gtk_dialog_run(GTK_DIALOG(commit));
1036 if (result == GTK_RESPONSE_APPLY)
1038 mbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(messageView));
1039 gtk_text_buffer_get_start_iter(mbuf, &begin);
1040 gtk_text_buffer_get_end_iter(mbuf, &end);
1041 message = gtk_text_buffer_get_text(mbuf, &begin, &end, FALSE);
1042 gtk_tree_model_foreach(model, get_commit_files_foreach, &selected_files);
1043 execute_command(vc, NULL, NULL, dir, VC_COMMAND_COMMIT, selected_files, message);
1044 g_free(message);
1046 gtk_widget_destroy(commit);
1047 free_commit_list(lst);
1048 g_free(dir);
1049 g_free(diff);
1052 static GtkWidget *menu_vc_diff_file = NULL;
1053 static GtkWidget *menu_vc_diff_dir = NULL;
1054 static GtkWidget *menu_vc_diff_project = NULL;
1055 static GtkWidget *menu_vc_log_file = NULL;
1056 static GtkWidget *menu_vc_log_dir = NULL;
1057 static GtkWidget *menu_vc_log_project = NULL;
1058 static GtkWidget *menu_vc_status = NULL;
1059 static GtkWidget *menu_vc_revert_file = NULL;
1060 static GtkWidget *menu_vc_add_file = NULL;
1061 static GtkWidget *menu_vc_remove_file = NULL;
1062 static GtkWidget *menu_vc_commit = NULL;
1064 static void
1065 update_menu_items()
1067 document *doc;
1068 gboolean have_file;
1069 gboolean p_have_vc = FALSE;
1070 gboolean d_have_vc = FALSE;
1071 gboolean f_have_vc = FALSE;
1073 gchar *dir;
1075 doc = documents->get_current();
1076 have_file = doc && doc->file_name && g_path_is_absolute(doc->file_name);
1078 if (have_file)
1080 dir = g_path_get_dirname(doc->file_name);
1081 if (find_cmd_env(VC_COMMAND_DIFF_FILE, TRUE, dir))
1082 d_have_vc = TRUE;
1084 if (find_cmd_env(VC_COMMAND_DIFF_FILE, TRUE, doc->file_name))
1085 f_have_vc = TRUE;
1086 g_free(dir);
1089 if (project != NULL && NZV(project->base_path) &&
1090 find_cmd_env(VC_COMMAND_DIFF_DIR, TRUE, project->base_path))
1091 p_have_vc = TRUE;
1093 gtk_widget_set_sensitive(menu_vc_diff_file, f_have_vc);
1094 gtk_widget_set_sensitive(menu_vc_diff_dir, d_have_vc);
1095 gtk_widget_set_sensitive(menu_vc_diff_project, p_have_vc);
1097 gtk_widget_set_sensitive(menu_vc_log_file, f_have_vc);
1098 gtk_widget_set_sensitive(menu_vc_log_dir, d_have_vc);
1099 gtk_widget_set_sensitive(menu_vc_log_project, p_have_vc);
1101 gtk_widget_set_sensitive(menu_vc_status, d_have_vc);
1103 gtk_widget_set_sensitive(menu_vc_revert_file, f_have_vc);
1105 gtk_widget_set_sensitive(menu_vc_remove_file, f_have_vc);
1106 gtk_widget_set_sensitive(menu_vc_add_file, d_have_vc && !f_have_vc);
1108 gtk_widget_set_sensitive(menu_vc_commit, d_have_vc);
1111 void
1112 configure(GtkWidget * parent)
1114 GtkWidget *dialog, *vbox, *checkbox, *checkbox2, *checkbox3, *checkbox4, *checkbox5,
1115 *checkbox6;
1116 GtkTooltips *tooltip = NULL;
1118 tooltip = gtk_tooltips_new();
1119 dialog = gtk_dialog_new_with_buttons(_("Version Control"),
1120 GTK_WINDOW(parent), GTK_DIALOG_DESTROY_WITH_PARENT,
1121 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK,
1122 GTK_RESPONSE_ACCEPT, NULL);
1123 vbox = ui->dialog_vbox_new(GTK_DIALOG(dialog));
1124 gtk_widget_set_name(dialog, "GeanyDialog");
1125 gtk_box_set_spacing(GTK_BOX(vbox), 2);
1127 checkbox =
1128 gtk_check_button_new_with_label(_
1129 ("Set Changed-flag for document tabs created by the plugin"));
1130 gtk_tooltips_set_tip(tooltip, checkbox,
1132 ("If this option is activated, every new by the VC-plugin created document tab "
1133 "will be marked as changed. Even this option is useful in some cases, it could cause "
1134 "a big number of annoying \"Do you want to save\"-dialogs."), NULL);
1135 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox), FALSE);
1136 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox), set_changed_flag);
1137 gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 2);
1139 checkbox2 = gtk_check_button_new_with_label(_("Confirm adding new files to a VCS"));
1140 gtk_tooltips_set_tip(tooltip, checkbox2,
1142 ("Shows a confirmation dialog on adding a new (created) file to VCS."),
1143 NULL);
1144 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox2), FALSE);
1145 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox2), set_add_confirmation);
1146 gtk_box_pack_start(GTK_BOX(vbox), checkbox2, TRUE, FALSE, 2);
1148 checkbox3 = gtk_check_button_new_with_label(_("Enable CVS"));
1149 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox3), FALSE);
1150 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox3), enable_cvs);
1151 gtk_box_pack_start(GTK_BOX(vbox), checkbox3, TRUE, FALSE, 2);
1153 checkbox4 = gtk_check_button_new_with_label(_("Enable GIT"));
1154 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox4), FALSE);
1155 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox4), enable_git);
1156 gtk_box_pack_start(GTK_BOX(vbox), checkbox4, TRUE, FALSE, 2);
1158 checkbox5 = gtk_check_button_new_with_label(_("Enable SVN"));
1159 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox5), FALSE);
1160 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox5), enable_svn);
1161 gtk_box_pack_start(GTK_BOX(vbox), checkbox5, TRUE, FALSE, 2);
1163 checkbox6 = gtk_check_button_new_with_label(_("Enable SVK"));
1164 gtk_button_set_focus_on_click(GTK_BUTTON(checkbox6), FALSE);
1165 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox6), enable_svk);
1166 gtk_box_pack_start(GTK_BOX(vbox), checkbox6, TRUE, FALSE, 2);
1168 gtk_widget_show_all(vbox);
1170 // run the dialog and check for the response code
1171 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
1173 GKeyFile *config = g_key_file_new();
1174 gchar *data;
1175 gchar *config_dir = g_path_get_dirname(config_file);
1177 set_changed_flag = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox));
1178 set_add_confirmation = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox2));
1180 enable_cvs = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox3));
1181 enable_git = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox4));
1182 enable_svn = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox5));
1183 enable_svk = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox6));
1185 g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL);
1187 g_key_file_set_boolean(config, "VC", "set_changed_flag", set_changed_flag);
1188 g_key_file_set_boolean(config, "VC", "set_add_confirmation", set_add_confirmation);
1190 g_key_file_set_boolean(config, "VC", "enable_cvs", enable_cvs);
1191 g_key_file_set_boolean(config, "VC", "enable_git", enable_git);
1192 g_key_file_set_boolean(config, "VC", "enable_svn", enable_svn);
1193 g_key_file_set_boolean(config, "VC", "enable_svk", enable_svk);
1195 if (!g_file_test(config_dir, G_FILE_TEST_IS_DIR)
1196 && utils->mkdir(config_dir, TRUE) != 0)
1198 dialogs->show_msgbox(GTK_MESSAGE_ERROR,
1200 ("Plugin configuration directory could not be created."));
1202 else
1204 // write config to file
1205 data = g_key_file_to_data(config, NULL, NULL);
1206 utils->write_file(config_file, data);
1207 g_free(data);
1210 g_free(config_dir);
1211 g_key_file_free(config);
1213 registrate();
1215 gtk_widget_destroy(dialog);
1218 static void
1219 load_config()
1221 GError *error = NULL;
1222 GKeyFile *config = g_key_file_new();
1224 g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL);
1225 set_changed_flag = g_key_file_get_boolean(config, "VC", "set_changed_flag", &error);
1226 if (error != NULL)
1228 // Set default value if something went wrong while reading file
1229 set_changed_flag = FALSE;
1230 g_error_free(error);
1231 error = NULL;
1233 set_add_confirmation = g_key_file_get_boolean(config, "VC", "set_add_confirmation", &error);
1234 if (error != NULL)
1236 // Set default value if something went wrong while reading file
1237 set_add_confirmation = TRUE;
1238 g_error_free(error);
1239 error = NULL;
1242 enable_cvs = g_key_file_get_boolean(config, "VC", "enable_cvs", &error);
1243 if (error != NULL)
1245 // Set default value if something went wrong while reading file
1246 enable_cvs = TRUE;
1247 g_error_free(error);
1248 error = NULL;
1251 enable_git = g_key_file_get_boolean(config, "VC", "enable_git", &error);
1252 if (error != NULL)
1254 // Set default value if something went wrong while reading file
1255 enable_git = TRUE;
1256 g_error_free(error);
1257 error = NULL;
1260 enable_svn = g_key_file_get_boolean(config, "VC", "enable_svn", &error);
1261 if (error != NULL)
1263 // Set default value if something went wrong while reading file
1264 enable_svn = TRUE;
1265 g_error_free(error);
1266 error = NULL;
1269 enable_svk = g_key_file_get_boolean(config, "VC", "enable_svk", &error);
1270 if (error != NULL)
1272 // Set default value if something went wrong while reading file
1273 enable_svk = TRUE;
1274 g_error_free(error);
1275 error = NULL;
1277 g_key_file_free(config);
1280 static void
1281 registrate()
1283 gchar *path;
1284 if (VC)
1286 g_slist_free(VC);
1287 VC = NULL;
1289 REGISTER_VC(GIT, enable_git);
1290 REGISTER_VC(SVN, enable_svn);
1291 REGISTER_VC(CVS, enable_cvs);
1292 REGISTER_VC(SVK, enable_svk);
1295 /* Called by Geany to initialize the plugin */
1296 void
1297 init(GeanyData * data)
1299 GtkWidget *menu_vc = NULL;
1300 GtkWidget *menu_vc_menu = NULL;
1301 GtkTooltips *tooltips = NULL;
1302 gchar *path;
1304 config_file = g_strconcat(app->configdir, G_DIR_SEPARATOR_S, "plugins", G_DIR_SEPARATOR_S,
1305 "VC", G_DIR_SEPARATOR_S, "VC.conf", NULL);
1307 load_config();
1308 registrate();
1310 tooltips = gtk_tooltips_new();
1312 menu_vc = gtk_image_menu_item_new_with_mnemonic(_("_VC"));
1313 gtk_container_add(GTK_CONTAINER(data->tools_menu), menu_vc);
1315 g_signal_connect((gpointer) menu_vc, "activate", G_CALLBACK(update_menu_items), NULL);
1317 menu_vc_menu = gtk_menu_new();
1318 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_vc), menu_vc_menu);
1320 // Single file
1321 menu_vc_diff_file = gtk_menu_item_new_with_mnemonic(_("Diff From Current File"));
1322 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_diff_file);
1323 gtk_tooltips_set_tip(tooltips, menu_vc_diff_file,
1324 _("Make a diff from the current active file"), NULL);
1326 g_signal_connect((gpointer) menu_vc_diff_file, "activate",
1327 G_CALLBACK(vcfile_activated), NULL);
1329 // Directory
1330 menu_vc_diff_dir = gtk_menu_item_new_with_mnemonic(_("Diff From Current Directory"));
1331 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_diff_dir);
1332 gtk_tooltips_set_tip(tooltips, menu_vc_diff_dir,
1333 _("Make a diff from the directory of the current active file"), NULL);
1335 g_signal_connect((gpointer) menu_vc_diff_dir, "activate",
1336 G_CALLBACK(vcdirectory_activated), NULL);
1338 // Project
1339 menu_vc_diff_project = gtk_menu_item_new_with_mnemonic(_("Diff From Current Project"));
1340 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_diff_project);
1341 gtk_tooltips_set_tip(tooltips, menu_vc_diff_project,
1342 _("Make a diff from the current project's base path"), NULL);
1344 g_signal_connect((gpointer) menu_vc_diff_project, "activate",
1345 G_CALLBACK(vcproject_activated), NULL);
1347 gtk_container_add(GTK_CONTAINER(menu_vc_menu), gtk_separator_menu_item_new());
1349 // Log
1350 menu_vc_log_file = gtk_menu_item_new_with_mnemonic(_("Log Of Current File"));
1351 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_log_file);
1352 gtk_tooltips_set_tip(tooltips, menu_vc_log_file,
1353 _("Shows the log of the current file"), NULL);
1355 g_signal_connect((gpointer) menu_vc_log_file, "activate",
1356 G_CALLBACK(vclog_file_activated), NULL);
1358 menu_vc_log_dir = gtk_menu_item_new_with_mnemonic(_("Log Of Current Directory"));
1359 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_log_dir);
1360 gtk_tooltips_set_tip(tooltips, menu_vc_log_dir,
1361 _("Shows the log of the current directory"), NULL);
1363 g_signal_connect((gpointer) menu_vc_log_dir, "activate",
1364 G_CALLBACK(vclog_dir_activated), NULL);
1366 menu_vc_log_project = gtk_menu_item_new_with_mnemonic(_("Log Of Current Project"));
1367 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_log_project);
1368 gtk_tooltips_set_tip(tooltips, menu_vc_log_project,
1369 _("Shows the log of the current project"), NULL);
1371 g_signal_connect((gpointer) menu_vc_log_project, "activate",
1372 G_CALLBACK(vclog_project_activated), NULL);
1374 gtk_container_add(GTK_CONTAINER(menu_vc_menu), gtk_separator_menu_item_new());
1376 // Status
1377 menu_vc_status = gtk_menu_item_new_with_mnemonic(_("Status"));
1378 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_status);
1379 gtk_tooltips_set_tip(tooltips, menu_vc_status, _("Show status."), NULL);
1381 g_signal_connect((gpointer) menu_vc_status, "activate",
1382 G_CALLBACK(vcstatus_activated), NULL);
1384 gtk_container_add(GTK_CONTAINER(menu_vc_menu), gtk_separator_menu_item_new());
1386 // Revert file
1387 menu_vc_revert_file = gtk_menu_item_new_with_mnemonic(_("Revert File"));
1388 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_revert_file);
1389 gtk_tooltips_set_tip(tooltips, menu_vc_revert_file,
1390 _("Restore pristine working copy file (undo local edits)."), NULL);
1392 g_signal_connect((gpointer) menu_vc_revert_file, "activate",
1393 G_CALLBACK(vcrevert_activated), NULL);
1395 gtk_container_add(GTK_CONTAINER(menu_vc_menu), gtk_separator_menu_item_new());
1397 // Add file
1398 menu_vc_add_file = gtk_menu_item_new_with_mnemonic(_("Add File"));
1399 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_add_file);
1400 gtk_tooltips_set_tip(tooltips, menu_vc_add_file, _("Add file to repository."), NULL);
1402 g_signal_connect((gpointer) menu_vc_add_file, "activate",
1403 G_CALLBACK(vcadd_activated), NULL);
1405 // Remove file
1406 menu_vc_remove_file = gtk_menu_item_new_with_mnemonic(_("Remove File"));
1407 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_remove_file);
1408 gtk_tooltips_set_tip(tooltips, menu_vc_remove_file,
1409 _("Remove file from repository."), NULL);
1411 g_signal_connect((gpointer) menu_vc_remove_file, "activate",
1412 G_CALLBACK(vcremove_activated), NULL);
1414 gtk_container_add(GTK_CONTAINER(menu_vc_menu), gtk_separator_menu_item_new());
1416 // Commit
1417 menu_vc_commit = gtk_menu_item_new_with_mnemonic(_("Commit"));
1418 gtk_container_add(GTK_CONTAINER(menu_vc_menu), menu_vc_commit);
1419 gtk_tooltips_set_tip(tooltips, menu_vc_commit, _("Commit changes."), NULL);
1421 g_signal_connect((gpointer) menu_vc_commit, "activate",
1422 G_CALLBACK(vccommit_activated), NULL);
1424 gtk_widget_show_all(menu_vc);
1426 plugin_fields->menu_item = menu_vc;
1427 plugin_fields->flags = PLUGIN_IS_DOCUMENT_SENSITIVE;
1431 /* Called by Geany before unloading the plugin. */
1432 void
1433 cleanup()
1435 // remove the menu item added in init()
1436 gtk_widget_destroy(plugin_fields->menu_item);
1437 g_slist_free(VC);
1438 VC = NULL;
1439 g_free(config_file);