summaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plugins/docinfo/docinfo.plugin.desktop.in8
-rw-r--r--plugins/docinfo/gedit-docinfo-plugin.c638
-rw-r--r--plugins/docinfo/gedit-docinfo-plugin.h63
-rw-r--r--plugins/docinfo/meson.build34
-rw-r--r--plugins/docinfo/resources/gedit-docinfo.gresource.xml6
-rw-r--r--plugins/docinfo/resources/meson.build8
-rw-r--r--plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui337
-rw-r--r--plugins/externaltools/data/build.desktop.in9
-rwxr-xr-xplugins/externaltools/data/build.tool.in15
-rw-r--r--plugins/externaltools/data/meson.build46
-rw-r--r--plugins/externaltools/data/open-terminal-here-osx.desktop.in9
-rwxr-xr-xplugins/externaltools/data/open-terminal-here-osx.tool.in16
-rw-r--r--plugins/externaltools/data/open-terminal-here.desktop.in9
-rwxr-xr-xplugins/externaltools/data/open-terminal-here.tool.in4
-rw-r--r--plugins/externaltools/data/remove-trailing-spaces.desktop.in9
-rwxr-xr-xplugins/externaltools/data/remove-trailing-spaces.tool.in3
-rw-r--r--plugins/externaltools/data/run-command.desktop.in8
-rwxr-xr-xplugins/externaltools/data/run-command.tool.in4
-rw-r--r--plugins/externaltools/data/send-to-fpaste.desktop.in11
-rwxr-xr-xplugins/externaltools/data/send-to-fpaste.tool.in26
-rw-r--r--plugins/externaltools/externaltools.plugin.desktop.in9
-rw-r--r--plugins/externaltools/meson.build35
-rw-r--r--plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml20
-rwxr-xr-xplugins/externaltools/scripts/gedit-tool-merge.pl78
-rw-r--r--plugins/externaltools/scripts/meson.build13
-rw-r--r--plugins/externaltools/tests/meson.build21
-rw-r--r--plugins/externaltools/tests/testlinkparsing.py203
-rw-r--r--plugins/externaltools/tools/__init__.py26
-rw-r--r--plugins/externaltools/tools/appactivatable.py178
-rw-r--r--plugins/externaltools/tools/capture.py268
-rw-r--r--plugins/externaltools/tools/filelookup.py165
-rw-r--r--plugins/externaltools/tools/functions.py365
-rw-r--r--plugins/externaltools/tools/library.py520
-rw-r--r--plugins/externaltools/tools/linkparsing.py252
-rw-r--r--plugins/externaltools/tools/manager.py878
-rw-r--r--plugins/externaltools/tools/meson.build36
-rw-r--r--plugins/externaltools/tools/outputpanel.py247
-rw-r--r--plugins/externaltools/tools/outputpanel.ui49
-rw-r--r--plugins/externaltools/tools/tools.ui548
-rw-r--r--plugins/externaltools/tools/windowactivatable.py141
-rw-r--r--plugins/filebrowser/filebrowser.plugin.desktop.in12
-rw-r--r--plugins/filebrowser/gedit-file-bookmarks-store.c920
-rw-r--r--plugins/filebrowser/gedit-file-bookmarks-store.h91
-rw-r--r--plugins/filebrowser/gedit-file-browser-enum-register.c.template20
-rw-r--r--plugins/filebrowser/gedit-file-browser-enum-types-stage1.c.template45
-rw-r--r--plugins/filebrowser/gedit-file-browser-enum-types.h.template28
-rw-r--r--plugins/filebrowser/gedit-file-browser-error.h41
-rw-r--r--plugins/filebrowser/gedit-file-browser-messages.c1074
-rw-r--r--plugins/filebrowser/gedit-file-browser-messages.h33
-rw-r--r--plugins/filebrowser/gedit-file-browser-plugin.c972
-rw-r--r--plugins/filebrowser/gedit-file-browser-plugin.h70
-rw-r--r--plugins/filebrowser/gedit-file-browser-store.c3672
-rw-r--r--plugins/filebrowser/gedit-file-browser-store.h188
-rw-r--r--plugins/filebrowser/gedit-file-browser-utils.c223
-rw-r--r--plugins/filebrowser/gedit-file-browser-utils.h46
-rw-r--r--plugins/filebrowser/gedit-file-browser-view.c1323
-rw-r--r--plugins/filebrowser/gedit-file-browser-view.h84
-rw-r--r--plugins/filebrowser/gedit-file-browser-widget.c3150
-rw-r--r--plugins/filebrowser/gedit-file-browser-widget.h126
-rw-r--r--plugins/filebrowser/meson.build127
-rw-r--r--plugins/filebrowser/messages.xml47
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-activation.c105
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-activation.h69
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c162
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h69
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c128
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h70
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-get-root.c127
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-get-root.h69
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-get-view.c127
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-get-view.h69
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-id-location.c190
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-id-location.h70
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-id.c107
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-id.h69
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c142
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h69
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c143
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h70
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-set-root.c149
-rw-r--r--plugins/filebrowser/messages/gedit-file-browser-message-set-root.h69
-rw-r--r--plugins/filebrowser/messages/meson.build25
-rw-r--r--plugins/filebrowser/messages/messages.h16
-rw-r--r--plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml58
-rw-r--r--plugins/filebrowser/resources/gedit-file-browser.gresource.xml7
-rw-r--r--plugins/filebrowser/resources/meson.build8
-rw-r--r--plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui84
-rw-r--r--plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui275
-rwxr-xr-xplugins/generate-list.sh44
-rw-r--r--plugins/list-of-gedit-plugins.md45
-rw-r--r--plugins/meson.build31
-rw-r--r--plugins/modelines/gedit-modeline-plugin.c225
-rw-r--r--plugins/modelines/gedit-modeline-plugin.h60
-rw-r--r--plugins/modelines/language-mappings14
-rw-r--r--plugins/modelines/meson.build42
-rw-r--r--plugins/modelines/modeline-parser.c899
-rw-r--r--plugins/modelines/modeline-parser.h36
-rw-r--r--plugins/modelines/modelines.plugin.desktop.in8
-rw-r--r--plugins/pythonconsole/meson.build31
-rw-r--r--plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml30
-rw-r--r--plugins/pythonconsole/pythonconsole.plugin.desktop.in12
-rw-r--r--plugins/pythonconsole/pythonconsole/__init__.py75
-rw-r--r--plugins/pythonconsole/pythonconsole/config.py74
-rw-r--r--plugins/pythonconsole/pythonconsole/config.ui70
-rw-r--r--plugins/pythonconsole/pythonconsole/console.py415
-rw-r--r--plugins/pythonconsole/pythonconsole/meson.build24
-rw-r--r--plugins/quickhighlight/gedit-quick-highlight-plugin.c483
-rw-r--r--plugins/quickhighlight/gedit-quick-highlight-plugin.h63
-rw-r--r--plugins/quickhighlight/meson.build32
-rw-r--r--plugins/quickhighlight/quickhighlight.plugin.desktop.in8
-rw-r--r--plugins/quickopen/meson.build19
-rw-r--r--plugins/quickopen/quickopen.plugin.desktop.in12
-rw-r--r--plugins/quickopen/quickopen/__init__.py193
-rw-r--r--plugins/quickopen/quickopen/popup.py617
-rw-r--r--plugins/quickopen/quickopen/virtualdirs.py87
-rw-r--r--plugins/snippets/data/c.xml281
-rw-r--r--plugins/snippets/data/chdr.xml258
-rw-r--r--plugins/snippets/data/cpp.xml180
-rw-r--r--plugins/snippets/data/css.xml557
-rw-r--r--plugins/snippets/data/docbook.xml2645
-rw-r--r--plugins/snippets/data/fortran.xml164
-rw-r--r--plugins/snippets/data/global.xml2
-rw-r--r--plugins/snippets/data/haskell.xml14
-rw-r--r--plugins/snippets/data/html.xml252
-rw-r--r--plugins/snippets/data/idl.xml49
-rw-r--r--plugins/snippets/data/java.xml91
-rw-r--r--plugins/snippets/data/javascript.xml10
-rw-r--r--plugins/snippets/data/lang/snippets.lang160
-rw-r--r--plugins/snippets/data/latex.xml38
-rw-r--r--plugins/snippets/data/mallard.xml316
-rw-r--r--plugins/snippets/data/markdown.xml98
-rw-r--r--plugins/snippets/data/perl.xml126
-rw-r--r--plugins/snippets/data/php.xml192
-rw-r--r--plugins/snippets/data/python.xml112
-rw-r--r--plugins/snippets/data/rpmspec.xml22
-rw-r--r--plugins/snippets/data/ruby.xml166
-rw-r--r--plugins/snippets/data/sh.xml47
-rw-r--r--plugins/snippets/data/snippets.xml98
-rw-r--r--plugins/snippets/data/tcl.xml55
-rw-r--r--plugins/snippets/data/xml.xml25
-rw-r--r--plugins/snippets/data/xslt.xml143
-rw-r--r--plugins/snippets/meson.build23
-rw-r--r--plugins/snippets/snippets.plugin.desktop.in9
-rw-r--r--plugins/snippets/snippets/__init__.py26
-rw-r--r--plugins/snippets/snippets/appactivatable.py133
-rw-r--r--plugins/snippets/snippets/completion.py187
-rw-r--r--plugins/snippets/snippets/document.py1097
-rw-r--r--plugins/snippets/snippets/exporter.py122
-rw-r--r--plugins/snippets/snippets/helper.py204
-rw-r--r--plugins/snippets/snippets/importer.py134
-rw-r--r--plugins/snippets/snippets/languagemanager.py40
-rw-r--r--plugins/snippets/snippets/library.py989
-rw-r--r--plugins/snippets/snippets/manager.py1143
-rw-r--r--plugins/snippets/snippets/meson.build39
-rw-r--r--plugins/snippets/snippets/parser.py256
-rw-r--r--plugins/snippets/snippets/placeholder.py714
-rw-r--r--plugins/snippets/snippets/shareddata.py83
-rw-r--r--plugins/snippets/snippets/signals.py90
-rw-r--r--plugins/snippets/snippets/singleton.py27
-rw-r--r--plugins/snippets/snippets/snippet.py360
-rw-r--r--plugins/snippets/snippets/snippets.ui417
-rw-r--r--plugins/snippets/snippets/substitutionparser.py203
-rw-r--r--plugins/snippets/snippets/windowactivatable.py186
-rw-r--r--plugins/sort/gedit-sort-plugin.c418
-rw-r--r--plugins/sort/gedit-sort-plugin.h59
-rw-r--r--plugins/sort/meson.build34
-rw-r--r--plugins/sort/resources/gedit-sort.gresource.xml6
-rw-r--r--plugins/sort/resources/meson.build8
-rw-r--r--plugins/sort/resources/ui/gedit-sort-plugin.ui160
-rw-r--r--plugins/sort/sort.plugin.desktop.in11
-rw-r--r--plugins/spell/gedit-spell-app-activatable.c184
-rw-r--r--plugins/spell/gedit-spell-app-activatable.h38
-rw-r--r--plugins/spell/gedit-spell-plugin.c810
-rw-r--r--plugins/spell/gedit-spell-plugin.h62
-rw-r--r--plugins/spell/meson.build54
-rw-r--r--plugins/spell/org.gnome.gedit.plugins.spell.gschema.xml9
-rw-r--r--plugins/spell/resources/gedit-spell.gresource.xml6
-rw-r--r--plugins/spell/resources/meson.build8
-rw-r--r--plugins/spell/resources/ui/gedit-spell-setup-dialog.ui97
-rw-r--r--plugins/spell/spell.plugin.desktop.in11
-rw-r--r--plugins/time/gedit-time-plugin.c1078
-rw-r--r--plugins/time/gedit-time-plugin.h61
-rw-r--r--plugins/time/meson.build68
-rw-r--r--plugins/time/org.gnome.gedit.plugins.time.gschema.xml19
-rw-r--r--plugins/time/resources/gedit-time.gresource.xml7
-rw-r--r--plugins/time/resources/meson.build8
-rw-r--r--plugins/time/resources/ui/gedit-time-dialog.ui198
-rw-r--r--plugins/time/resources/ui/gedit-time-setup-dialog.ui214
-rw-r--r--plugins/time/time.plugin.desktop.in8
189 files changed, 40021 insertions, 0 deletions
diff --git a/plugins/docinfo/docinfo.plugin.desktop.in b/plugins/docinfo/docinfo.plugin.desktop.in
new file mode 100644
index 0000000..b493bb4
--- /dev/null
+++ b/plugins/docinfo/docinfo.plugin.desktop.in
@@ -0,0 +1,8 @@
+[Plugin]
+Module=docinfo
+IAge=3
+Name=Document Statistics
+Description=Report the number of words, lines and characters in a document.
+Authors=Paolo Maggi <paolo.maggi@polito.it>;Jorge Alberto Torres <jorge@deadoak.com>
+Copyright=Copyright © 2002-2005 Paolo Maggi
+Website=http://www.gedit.org
diff --git a/plugins/docinfo/gedit-docinfo-plugin.c b/plugins/docinfo/gedit-docinfo-plugin.c
new file mode 100644
index 0000000..5073254
--- /dev/null
+++ b/plugins/docinfo/gedit-docinfo-plugin.c
@@ -0,0 +1,638 @@
+/*
+ * gedit-docinfo-plugin.c
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include "gedit-docinfo-plugin.h"
+
+#include <string.h> /* For strlen (...) */
+
+#include <glib/gi18n.h>
+#include <pango/pango-break.h>
+#include <gmodule.h>
+
+#include <gedit/gedit-app.h>
+#include <gedit/gedit-window.h>
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-utils.h>
+#include <gedit/gedit-menu-extension.h>
+#include <gedit/gedit-app-activatable.h>
+#include <gedit/gedit-window-activatable.h>
+
+struct _GeditDocinfoPluginPrivate
+{
+ GeditWindow *window;
+
+ GSimpleAction *action;
+
+ GtkWidget *dialog;
+ GtkWidget *header_bar;
+ GtkWidget *lines_label;
+ GtkWidget *words_label;
+ GtkWidget *chars_label;
+ GtkWidget *chars_ns_label;
+ GtkWidget *bytes_label;
+ GtkWidget *document_label;
+ GtkWidget *document_lines_label;
+ GtkWidget *document_words_label;
+ GtkWidget *document_chars_label;
+ GtkWidget *document_chars_ns_label;
+ GtkWidget *document_bytes_label;
+ GtkWidget *selection_label;
+ GtkWidget *selected_lines_label;
+ GtkWidget *selected_words_label;
+ GtkWidget *selected_chars_label;
+ GtkWidget *selected_chars_ns_label;
+ GtkWidget *selected_bytes_label;
+
+ GeditApp *app;
+ GeditMenuExtension *menu_ext;
+};
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW,
+ PROP_APP
+};
+
+static void gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface);
+static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditDocinfoPlugin,
+ gedit_docinfo_plugin,
+ PEAS_TYPE_EXTENSION_BASE,
+ 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_APP_ACTIVATABLE,
+ gedit_app_activatable_iface_init)
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE,
+ gedit_window_activatable_iface_init)
+ G_ADD_PRIVATE_DYNAMIC (GeditDocinfoPlugin))
+
+static void
+calculate_info (GeditDocument *doc,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gint *chars,
+ gint *words,
+ gint *white_chars,
+ gint *bytes)
+{
+ gchar *text;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ text = gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc),
+ start,
+ end,
+ TRUE);
+
+ *chars = g_utf8_strlen (text, -1);
+ *bytes = strlen (text);
+
+ if (*chars > 0)
+ {
+ PangoLogAttr *attrs;
+ gint i;
+
+ attrs = g_new0 (PangoLogAttr, *chars + 1);
+
+ pango_get_log_attrs (text,
+ -1,
+ 0,
+ pango_language_from_string ("C"),
+ attrs,
+ *chars + 1);
+
+ for (i = 0; i < (*chars); i++)
+ {
+ if (attrs[i].is_white)
+ ++(*white_chars);
+
+ if (attrs[i].is_word_start)
+ ++(*words);
+ }
+
+ g_free (attrs);
+ }
+ else
+ {
+ *white_chars = 0;
+ *words = 0;
+ }
+
+ g_free (text);
+}
+
+static void
+update_document_info (GeditDocinfoPlugin *plugin,
+ GeditDocument *doc)
+{
+ GeditDocinfoPluginPrivate *priv;
+ GtkTextIter start, end;
+ gint words = 0;
+ gint chars = 0;
+ gint white_chars = 0;
+ gint lines = 0;
+ gint bytes = 0;
+ gchar *doc_name;
+ gchar *tmp_str;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc),
+ &start,
+ &end);
+
+ lines = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (doc));
+
+ calculate_info (doc,
+ &start, &end,
+ &chars, &words, &white_chars, &bytes);
+
+ if (chars == 0)
+ {
+ lines = 0;
+ }
+
+ gedit_debug_message (DEBUG_PLUGINS, "Chars: %d", chars);
+ gedit_debug_message (DEBUG_PLUGINS, "Lines: %d", lines);
+ gedit_debug_message (DEBUG_PLUGINS, "Words: %d", words);
+ gedit_debug_message (DEBUG_PLUGINS, "Chars non-space: %d", chars - white_chars);
+ gedit_debug_message (DEBUG_PLUGINS, "Bytes: %d", bytes);
+
+ doc_name = gedit_document_get_short_name_for_display (doc);
+ gtk_header_bar_set_subtitle (GTK_HEADER_BAR (priv->header_bar), doc_name);
+ g_free (doc_name);
+
+ tmp_str = g_strdup_printf("%d", lines);
+ gtk_label_set_text (GTK_LABEL (priv->document_lines_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", words);
+ gtk_label_set_text (GTK_LABEL (priv->document_words_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", chars);
+ gtk_label_set_text (GTK_LABEL (priv->document_chars_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", chars - white_chars);
+ gtk_label_set_text (GTK_LABEL (priv->document_chars_ns_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", bytes);
+ gtk_label_set_text (GTK_LABEL (priv->document_bytes_label), tmp_str);
+ g_free (tmp_str);
+}
+
+static void
+update_selection_info (GeditDocinfoPlugin *plugin,
+ GeditDocument *doc)
+{
+ GeditDocinfoPluginPrivate *priv;
+ gboolean sel;
+ GtkTextIter start, end;
+ gint words = 0;
+ gint chars = 0;
+ gint white_chars = 0;
+ gint lines = 0;
+ gint bytes = 0;
+ gchar *tmp_str;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ sel = gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ &start,
+ &end);
+
+ if (sel)
+ {
+ lines = gtk_text_iter_get_line (&end) - gtk_text_iter_get_line (&start) + 1;
+
+ calculate_info (doc,
+ &start, &end,
+ &chars, &words, &white_chars, &bytes);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Selected chars: %d", chars);
+ gedit_debug_message (DEBUG_PLUGINS, "Selected lines: %d", lines);
+ gedit_debug_message (DEBUG_PLUGINS, "Selected words: %d", words);
+ gedit_debug_message (DEBUG_PLUGINS, "Selected chars non-space: %d", chars - white_chars);
+ gedit_debug_message (DEBUG_PLUGINS, "Selected bytes: %d", bytes);
+
+ gtk_widget_set_sensitive (priv->selection_label, TRUE);
+ gtk_widget_set_sensitive (priv->selected_words_label, TRUE);
+ gtk_widget_set_sensitive (priv->selected_bytes_label, TRUE);
+ gtk_widget_set_sensitive (priv->selected_lines_label, TRUE);
+ gtk_widget_set_sensitive (priv->selected_chars_label, TRUE);
+ gtk_widget_set_sensitive (priv->selected_chars_ns_label, TRUE);
+ }
+ else
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "Selection empty");
+
+ gtk_widget_set_sensitive (priv->selection_label, FALSE);
+ gtk_widget_set_sensitive (priv->selected_words_label, FALSE);
+ gtk_widget_set_sensitive (priv->selected_bytes_label, FALSE);
+ gtk_widget_set_sensitive (priv->selected_lines_label, FALSE);
+ gtk_widget_set_sensitive (priv->selected_chars_label, FALSE);
+ gtk_widget_set_sensitive (priv->selected_chars_ns_label, FALSE);
+ }
+
+ if (chars == 0)
+ lines = 0;
+
+ tmp_str = g_strdup_printf("%d", lines);
+ gtk_label_set_text (GTK_LABEL (priv->selected_lines_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", words);
+ gtk_label_set_text (GTK_LABEL (priv->selected_words_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", chars);
+ gtk_label_set_text (GTK_LABEL (priv->selected_chars_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", chars - white_chars);
+ gtk_label_set_text (GTK_LABEL (priv->selected_chars_ns_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", bytes);
+ gtk_label_set_text (GTK_LABEL (priv->selected_bytes_label), tmp_str);
+ g_free (tmp_str);
+}
+
+static void
+docinfo_dialog_response_cb (GtkDialog *widget,
+ gint res_id,
+ GeditDocinfoPlugin *plugin)
+{
+ GeditDocinfoPluginPrivate *priv;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ switch (res_id)
+ {
+ case GTK_RESPONSE_CLOSE:
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CLOSE");
+
+ gtk_widget_destroy (priv->dialog);
+
+ break;
+ }
+
+ case GTK_RESPONSE_OK:
+ {
+ GeditDocument *doc;
+
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK");
+
+ doc = gedit_window_get_active_document (priv->window);
+
+ update_document_info (plugin, doc);
+ update_selection_info (plugin, doc);
+
+ break;
+ }
+ }
+}
+
+static void
+create_docinfo_dialog (GeditDocinfoPlugin *plugin)
+{
+ GeditDocinfoPluginPrivate *priv;
+ GtkBuilder *builder;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_resource (builder, "/org/gnome/gedit/plugins/docinfo/ui/gedit-docinfo-plugin.ui", NULL);
+ priv->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "dialog"));
+ priv->header_bar = GTK_WIDGET (gtk_builder_get_object (builder, "header_bar"));
+ priv->words_label = GTK_WIDGET (gtk_builder_get_object (builder, "words_label"));
+ priv->bytes_label = GTK_WIDGET (gtk_builder_get_object (builder, "bytes_label"));
+ priv->lines_label = GTK_WIDGET (gtk_builder_get_object (builder, "lines_label"));
+ priv->chars_label = GTK_WIDGET (gtk_builder_get_object (builder, "chars_label"));
+ priv->chars_ns_label = GTK_WIDGET (gtk_builder_get_object (builder, "chars_ns_label"));
+ priv->document_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_label"));
+ priv->document_words_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_words_label"));
+ priv->document_bytes_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_bytes_label"));
+ priv->document_lines_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_lines_label"));
+ priv->document_chars_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_chars_label"));
+ priv->document_chars_ns_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_chars_ns_label"));
+ priv->selection_label = GTK_WIDGET (gtk_builder_get_object (builder, "selection_label"));
+ priv->selected_words_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_words_label"));
+ priv->selected_bytes_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_bytes_label"));
+ priv->selected_lines_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_lines_label"));
+ priv->selected_chars_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_chars_label"));
+ priv->selected_chars_ns_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_chars_ns_label"));
+ g_object_unref (builder);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
+ GTK_RESPONSE_OK);
+ gtk_window_set_transient_for (GTK_WINDOW (priv->dialog),
+ GTK_WINDOW (priv->window));
+
+ g_signal_connect (priv->dialog,
+ "destroy",
+ G_CALLBACK (gtk_widget_destroyed),
+ &priv->dialog);
+ g_signal_connect (priv->dialog,
+ "response",
+ G_CALLBACK (docinfo_dialog_response_cb),
+ plugin);
+
+ /* We set this explictely with code since glade does not
+ * save the can_focus property when set to false :(
+ * Making sure the labels are not focusable is needed to
+ * prevent loosing the selection in the document when
+ * creating the dialog.
+ */
+ gtk_widget_set_can_focus (priv->words_label, FALSE);
+ gtk_widget_set_can_focus (priv->bytes_label, FALSE);
+ gtk_widget_set_can_focus (priv->lines_label, FALSE);
+ gtk_widget_set_can_focus (priv->chars_label, FALSE);
+ gtk_widget_set_can_focus (priv->chars_ns_label, FALSE);
+ gtk_widget_set_can_focus (priv->document_label, FALSE);
+ gtk_widget_set_can_focus (priv->document_words_label, FALSE);
+ gtk_widget_set_can_focus (priv->document_bytes_label, FALSE);
+ gtk_widget_set_can_focus (priv->document_lines_label, FALSE);
+ gtk_widget_set_can_focus (priv->document_chars_label, FALSE);
+ gtk_widget_set_can_focus (priv->document_chars_ns_label, FALSE);
+ gtk_widget_set_can_focus (priv->selection_label, FALSE);
+ gtk_widget_set_can_focus (priv->selected_words_label, FALSE);
+ gtk_widget_set_can_focus (priv->selected_bytes_label, FALSE);
+ gtk_widget_set_can_focus (priv->selected_lines_label, FALSE);
+ gtk_widget_set_can_focus (priv->selected_chars_label, FALSE);
+ gtk_widget_set_can_focus (priv->selected_chars_ns_label, FALSE);
+}
+
+static void
+docinfo_cb (GAction *action,
+ GVariant *parameter,
+ GeditDocinfoPlugin *plugin)
+{
+ GeditDocinfoPluginPrivate *priv;
+ GeditDocument *doc;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ doc = gedit_window_get_active_document (priv->window);
+
+ if (priv->dialog != NULL)
+ {
+ gtk_window_present (GTK_WINDOW (priv->dialog));
+ gtk_widget_grab_focus (GTK_WIDGET (priv->dialog));
+ }
+ else
+ {
+ create_docinfo_dialog (plugin);
+ gtk_widget_show (GTK_WIDGET (priv->dialog));
+ }
+
+ update_document_info (plugin, doc);
+ update_selection_info (plugin, doc);
+}
+
+static void
+gedit_docinfo_plugin_init (GeditDocinfoPlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditDocinfoPlugin initializing");
+
+ plugin->priv = gedit_docinfo_plugin_get_instance_private (plugin);
+}
+
+static void
+gedit_docinfo_plugin_dispose (GObject *object)
+{
+ GeditDocinfoPlugin *plugin = GEDIT_DOCINFO_PLUGIN (object);
+
+ gedit_debug_message (DEBUG_PLUGINS, "GeditDocinfoPlugin dispose");
+
+ g_clear_object (&plugin->priv->action);
+ g_clear_object (&plugin->priv->window);
+ g_clear_object (&plugin->priv->menu_ext);
+ g_clear_object (&plugin->priv->app);
+
+ G_OBJECT_CLASS (gedit_docinfo_plugin_parent_class)->dispose (object);
+}
+
+
+static void
+gedit_docinfo_plugin_finalize (GObject *object)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditDocinfoPlugin finalizing");
+
+ G_OBJECT_CLASS (gedit_docinfo_plugin_parent_class)->finalize (object);
+}
+
+static void
+gedit_docinfo_plugin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditDocinfoPlugin *plugin = GEDIT_DOCINFO_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value));
+ break;
+ case PROP_APP:
+ plugin->priv->app = GEDIT_APP (g_value_dup_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_docinfo_plugin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditDocinfoPlugin *plugin = GEDIT_DOCINFO_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value, plugin->priv->window);
+ break;
+ case PROP_APP:
+ g_value_set_object (value, plugin->priv->app);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+update_ui (GeditDocinfoPlugin *plugin)
+{
+ GeditDocinfoPluginPrivate *priv;
+ GeditView *view;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ view = gedit_window_get_active_view (priv->window);
+
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (priv->action), view != NULL);
+
+ if (priv->dialog != NULL)
+ {
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog),
+ GTK_RESPONSE_OK,
+ (view != NULL));
+ }
+}
+
+static void
+gedit_docinfo_plugin_app_activate (GeditAppActivatable *activatable)
+{
+ GeditDocinfoPluginPrivate *priv;
+ GMenuItem *item;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv;
+
+ priv->menu_ext = gedit_app_activatable_extend_menu (activatable, "tools-section");
+ item = g_menu_item_new (_("_Document Statistics"), "win.docinfo");
+ gedit_menu_extension_append_menu_item (priv->menu_ext, item);
+ g_object_unref (item);
+}
+
+static void
+gedit_docinfo_plugin_app_deactivate (GeditAppActivatable *activatable)
+{
+ GeditDocinfoPluginPrivate *priv;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv;
+
+ g_clear_object (&priv->menu_ext);
+}
+
+static void
+gedit_docinfo_plugin_window_activate (GeditWindowActivatable *activatable)
+{
+ GeditDocinfoPluginPrivate *priv;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv;
+
+ priv->action = g_simple_action_new ("docinfo", NULL);
+ g_signal_connect (priv->action, "activate",
+ G_CALLBACK (docinfo_cb), activatable);
+ g_action_map_add_action (G_ACTION_MAP (priv->window),
+ G_ACTION (priv->action));
+
+ update_ui (GEDIT_DOCINFO_PLUGIN (activatable));
+}
+
+static void
+gedit_docinfo_plugin_window_deactivate (GeditWindowActivatable *activatable)
+{
+ GeditDocinfoPluginPrivate *priv;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv;
+
+ g_action_map_remove_action (G_ACTION_MAP (priv->window), "docinfo");
+}
+
+static void
+gedit_docinfo_plugin_window_update_state (GeditWindowActivatable *activatable)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ update_ui (GEDIT_DOCINFO_PLUGIN (activatable));
+}
+
+static void
+gedit_docinfo_plugin_class_init (GeditDocinfoPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_docinfo_plugin_dispose;
+ object_class->finalize = gedit_docinfo_plugin_finalize;
+ object_class->set_property = gedit_docinfo_plugin_set_property;
+ object_class->get_property = gedit_docinfo_plugin_get_property;
+
+ g_object_class_override_property (object_class, PROP_WINDOW, "window");
+ g_object_class_override_property (object_class, PROP_APP, "app");
+}
+
+static void
+gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface)
+{
+ iface->activate = gedit_docinfo_plugin_app_activate;
+ iface->deactivate = gedit_docinfo_plugin_app_deactivate;
+}
+
+static void
+gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface)
+{
+ iface->activate = gedit_docinfo_plugin_window_activate;
+ iface->deactivate = gedit_docinfo_plugin_window_deactivate;
+ iface->update_state = gedit_docinfo_plugin_window_update_state;
+}
+
+static void
+gedit_docinfo_plugin_class_finalize (GeditDocinfoPluginClass *klass)
+{
+}
+
+
+G_MODULE_EXPORT void
+peas_register_types (PeasObjectModule *module)
+{
+ gedit_docinfo_plugin_register_type (G_TYPE_MODULE (module));
+
+ peas_object_module_register_extension_type (module,
+ GEDIT_TYPE_APP_ACTIVATABLE,
+ GEDIT_TYPE_DOCINFO_PLUGIN);
+ peas_object_module_register_extension_type (module,
+ GEDIT_TYPE_WINDOW_ACTIVATABLE,
+ GEDIT_TYPE_DOCINFO_PLUGIN);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/docinfo/gedit-docinfo-plugin.h b/plugins/docinfo/gedit-docinfo-plugin.h
new file mode 100644
index 0000000..e657c9f
--- /dev/null
+++ b/plugins/docinfo/gedit-docinfo-plugin.h
@@ -0,0 +1,63 @@
+/*
+ * gedit-docinfo-plugin.h
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef GEDIT_DOCINFO_PLUGIN_H
+#define GEDIT_DOCINFO_PLUGIN_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libpeas/peas-extension-base.h>
+#include <libpeas/peas-object-module.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_DOCINFO_PLUGIN (gedit_docinfo_plugin_get_type ())
+#define GEDIT_DOCINFO_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocinfoPlugin))
+#define GEDIT_DOCINFO_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocinfoPluginClass))
+#define GEDIT_IS_DOCINFO_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_DOCINFO_PLUGIN))
+#define GEDIT_IS_DOCINFO_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_DOCINFO_PLUGIN))
+#define GEDIT_DOCINFO_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocinfoPluginClass))
+
+typedef struct _GeditDocinfoPlugin GeditDocinfoPlugin;
+typedef struct _GeditDocinfoPluginPrivate GeditDocinfoPluginPrivate;
+typedef struct _GeditDocinfoPluginClass GeditDocinfoPluginClass;
+
+struct _GeditDocinfoPlugin
+{
+ PeasExtensionBase parent;
+
+ /*< private >*/
+ GeditDocinfoPluginPrivate *priv;
+};
+
+struct _GeditDocinfoPluginClass
+{
+ PeasExtensionBaseClass parent_class;
+};
+
+GType gedit_docinfo_plugin_get_type (void) G_GNUC_CONST;
+
+G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
+
+G_END_DECLS
+
+#endif /* GEDIT_DOCINFO_PLUGIN_H */
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/docinfo/meson.build b/plugins/docinfo/meson.build
new file mode 100644
index 0000000..200ace5
--- /dev/null
+++ b/plugins/docinfo/meson.build
@@ -0,0 +1,34 @@
+libdocinfo_sources = files(
+ 'gedit-docinfo-plugin.c',
+)
+
+libdocinfo_deps = [
+ libgedit_dep,
+]
+
+subdir('resources')
+
+libdocinfo_sha = shared_module(
+ 'docinfo',
+ sources: libdocinfo_sources,
+ include_directories: root_include_dir,
+ dependencies: libdocinfo_deps,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ ),
+ name_suffix: module_suffix,
+)
+
+custom_target(
+ 'docinfo.plugin',
+ input: 'docinfo.plugin.desktop.in',
+ output: 'docinfo.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
diff --git a/plugins/docinfo/resources/gedit-docinfo.gresource.xml b/plugins/docinfo/resources/gedit-docinfo.gresource.xml
new file mode 100644
index 0000000..9d61659
--- /dev/null
+++ b/plugins/docinfo/resources/gedit-docinfo.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/gedit/plugins/docinfo">
+ <file preprocess="xml-stripblanks">ui/gedit-docinfo-plugin.ui</file>
+ </gresource>
+</gresources>
diff --git a/plugins/docinfo/resources/meson.build b/plugins/docinfo/resources/meson.build
new file mode 100644
index 0000000..b1f4d4d
--- /dev/null
+++ b/plugins/docinfo/resources/meson.build
@@ -0,0 +1,8 @@
+libdocinfo_res = gnome.compile_resources(
+ 'gedit-docinfo-resources',
+ 'gedit-docinfo.gresource.xml',
+)
+
+libdocinfo_sources += [
+ libdocinfo_res.get(0),
+]
diff --git a/plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui b/plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui
new file mode 100644
index 0000000..84cf918
--- /dev/null
+++ b/plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui
@@ -0,0 +1,337 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <object class="GtkDialog" id="dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Document Statistics</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="header_bar">
+ <property name="title" translatable="yes">Document Statistics</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show-close-button">True</property>
+ <child>
+ <object class="GtkButton" id="update_button">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <property name="can_focus">True</property>
+ <property name="use_underline">True</property>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="reload_button_image">
+ <property name="visible">True</property>
+ <property name="icon_size">1</property>
+ <property name="icon_name">view-refresh-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">start</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkBox" id="docinfo_dialog_content">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkGrid" id="grid_table">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">18</property>
+ <child>
+ <object class="GtkLabel" id="document_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Document</property>
+ <property name="use_markup">True</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selection_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Selection</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="lines_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Lines</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="words_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Words</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="chars_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Characters (with spaces)</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="chars_ns_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Characters (no spaces)</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="bytes_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Bytes</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="document_lines_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="document_words_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="document_chars_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="document_chars_ns_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="document_bytes_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">5</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selected_lines_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selected_words_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selected_chars_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selected_chars_ns_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selected_bytes_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="label">0</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-5">update_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/externaltools/data/build.desktop.in b/plugins/externaltools/data/build.desktop.in
new file mode 100644
index 0000000..14dfebd
--- /dev/null
+++ b/plugins/externaltools/data/build.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Tool]
+Name=Build
+Comment=Run “make” in the document directory
+Input=nothing
+Output=output-panel
+Shortcut=<Control>F8
+Applicability=local
+Save-files=all
+Languages=
diff --git a/plugins/externaltools/data/build.tool.in b/plugins/externaltools/data/build.tool.in
new file mode 100755
index 0000000..0b81d5b
--- /dev/null
+++ b/plugins/externaltools/data/build.tool.in
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+EHOME=`echo $HOME | sed "s/#/\#/"`
+DIR=$GEDIT_CURRENT_DOCUMENT_DIR
+while test "$DIR" != "/"; do
+ for m in GNUmakefile makefile Makefile; do
+ if [ -f "${DIR}/${m}" ]; then
+ echo "Using ${m} from ${DIR}" | sed "s#$EHOME#~#" > /dev/stderr
+ make -C "${DIR}"
+ exit
+ fi
+ done
+ DIR=`dirname "${DIR}"`
+done
+echo "No Makefile found!" > /dev/stderr
diff --git a/plugins/externaltools/data/meson.build b/plugins/externaltools/data/meson.build
new file mode 100644
index 0000000..02d5d6c
--- /dev/null
+++ b/plugins/externaltools/data/meson.build
@@ -0,0 +1,46 @@
+externaltools_tools = [
+ 'build',
+ 'remove-trailing-spaces',
+ 'send-to-fpaste',
+]
+
+if host_machine.system() == 'darwin'
+ externaltools_tools += [
+ 'open-terminal-here-osx',
+ ]
+elif host_machine.system() != 'windows'
+ externaltools_tools += [
+ 'open-terminal-here',
+ 'run-command',
+ ]
+endif
+
+foreach tool_name: externaltools_tools
+ dektop_file = custom_target(
+ '@0@.desktop'.format(tool_name),
+ input: '@0@.desktop.in'.format(tool_name),
+ output: '@0@.desktop'.format(tool_name),
+ command: msgfmt_externaltools_cmd,
+ install: false,
+ )
+
+ custom_target(
+ '@0@.tool'.format(tool_name),
+ input: '@0@.tool.in'.format(tool_name),
+ output: '@0@'.format(tool_name),
+ depends: dektop_file,
+ command: [
+ merge_tool_prg,
+ '@INPUT@',
+ dektop_file.full_path(),
+ ],
+ capture: true,
+ install: true,
+ install_dir: join_paths(
+ pkgdatadir,
+ 'plugins',
+ 'externaltools',
+ 'tools',
+ )
+ )
+endforeach
diff --git a/plugins/externaltools/data/open-terminal-here-osx.desktop.in b/plugins/externaltools/data/open-terminal-here-osx.desktop.in
new file mode 100644
index 0000000..846ff9a
--- /dev/null
+++ b/plugins/externaltools/data/open-terminal-here-osx.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Tool]
+Name=Open terminal here
+Comment=Open a terminal in the document location
+Input=nothing
+Output=output-panel
+Applicability=local
+Save-files=nothing
+Languages=
+Shortcut=<Primary><Alt>t
diff --git a/plugins/externaltools/data/open-terminal-here-osx.tool.in b/plugins/externaltools/data/open-terminal-here-osx.tool.in
new file mode 100755
index 0000000..c336006
--- /dev/null
+++ b/plugins/externaltools/data/open-terminal-here-osx.tool.in
@@ -0,0 +1,16 @@
+#!/usr/bin/osascript
+
+set the_path to system attribute "GEDIT_CURRENT_DOCUMENT_DIR"
+set cmd to "cd " & quoted form of the_path
+
+tell application "System Events" to set terminalIsRunning to exists application process "Terminal"
+
+tell application "Terminal"
+ activate
+
+ if terminalIsRunning is true then
+ do script with command cmd
+ else
+ do script with command cmd in window 1
+ end if
+end tell
diff --git a/plugins/externaltools/data/open-terminal-here.desktop.in b/plugins/externaltools/data/open-terminal-here.desktop.in
new file mode 100644
index 0000000..846ff9a
--- /dev/null
+++ b/plugins/externaltools/data/open-terminal-here.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Tool]
+Name=Open terminal here
+Comment=Open a terminal in the document location
+Input=nothing
+Output=output-panel
+Applicability=local
+Save-files=nothing
+Languages=
+Shortcut=<Primary><Alt>t
diff --git a/plugins/externaltools/data/open-terminal-here.tool.in b/plugins/externaltools/data/open-terminal-here.tool.in
new file mode 100755
index 0000000..9365648
--- /dev/null
+++ b/plugins/externaltools/data/open-terminal-here.tool.in
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+#TODO: use "gconftool-2 -g /desktop/gnome/applications/terminal/exec"
+gnome-terminal --working-directory="$GEDIT_CURRENT_DOCUMENT_DIR" &
diff --git a/plugins/externaltools/data/remove-trailing-spaces.desktop.in b/plugins/externaltools/data/remove-trailing-spaces.desktop.in
new file mode 100644
index 0000000..9a34de7
--- /dev/null
+++ b/plugins/externaltools/data/remove-trailing-spaces.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Tool]
+Name=Remove trailing spaces
+Comment=Remove useless trailing spaces in your file
+Input=document
+Output=replace-document
+Shortcut=<Alt>F12
+Applicability=all
+Save-files=nothing
+Languages=
diff --git a/plugins/externaltools/data/remove-trailing-spaces.tool.in b/plugins/externaltools/data/remove-trailing-spaces.tool.in
new file mode 100755
index 0000000..83e4c19
--- /dev/null
+++ b/plugins/externaltools/data/remove-trailing-spaces.tool.in
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+sed 's/[[:blank:]]*$//'
diff --git a/plugins/externaltools/data/run-command.desktop.in b/plugins/externaltools/data/run-command.desktop.in
new file mode 100644
index 0000000..ca7b7da
--- /dev/null
+++ b/plugins/externaltools/data/run-command.desktop.in
@@ -0,0 +1,8 @@
+[Gedit Tool]
+Name=Run command
+Comment=Execute a custom command and put its output in a new document
+Input=nothing
+Output=new-document
+Applicability=all
+Save-files=nothing
+Languages=
diff --git a/plugins/externaltools/data/run-command.tool.in b/plugins/externaltools/data/run-command.tool.in
new file mode 100755
index 0000000..77ad4e6
--- /dev/null
+++ b/plugins/externaltools/data/run-command.tool.in
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+#TODO: use "gconftool-2 -g /desktop/gnome/applications/terminal/exec"
+eval $(zenity --entry --title="Run Command - gedit" --text="Command to run:")
diff --git a/plugins/externaltools/data/send-to-fpaste.desktop.in b/plugins/externaltools/data/send-to-fpaste.desktop.in
new file mode 100644
index 0000000..40282c8
--- /dev/null
+++ b/plugins/externaltools/data/send-to-fpaste.desktop.in
@@ -0,0 +1,11 @@
+[Gedit Tool]
+Name=Send to fpaste
+Comment=Paste selected text or current document to fpaste
+Input=selection-document
+Output=output-panel
+Shortcut=<Shift><Super>p
+Applicability=always
+Save-files=nothing
+Languages=
+
+
diff --git a/plugins/externaltools/data/send-to-fpaste.tool.in b/plugins/externaltools/data/send-to-fpaste.tool.in
new file mode 100755
index 0000000..d392173
--- /dev/null
+++ b/plugins/externaltools/data/send-to-fpaste.tool.in
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+
+import os, urllib, json, sys, urllib.request
+from gi.repository import Gtk, Gdk
+
+text = sys.stdin.read()
+
+lang = os.getenv('GEDIT_CURRENT_DOCUMENT_LANGUAGE')
+if lang is None:
+ lang = "text"
+
+url_params = urllib.parse.urlencode({'paste_data': text, 'paste_lang': lang, 'mode':'json', 'api_submit':'true'})
+openfpaste = urllib.request.urlopen("http://fpaste.org", bytes(url_params, 'utf-8')).read().decode("utf-8")
+if openfpaste is None:
+ print("Failed to send fpaste request.")
+
+final_data = json.loads(openfpaste)
+
+paste_url = "http://fpaste.org/" + final_data['result']['id']
+
+disp = Gdk.Display.get_default()
+clipboard = Gtk.Clipboard.get_for_display(disp, Gdk.SELECTION_CLIPBOARD)
+clipboard.set_text(paste_url, len(paste_url))
+clipboard.store()
+
+print(paste_url + " has been copied to the clipboard.")
diff --git a/plugins/externaltools/externaltools.plugin.desktop.in b/plugins/externaltools/externaltools.plugin.desktop.in
new file mode 100644
index 0000000..f575818
--- /dev/null
+++ b/plugins/externaltools/externaltools.plugin.desktop.in
@@ -0,0 +1,9 @@
+[Plugin]
+Loader=python3
+Module=externaltools
+IAge=3
+Name=External Tools
+Description=Execute external commands and shell scripts.
+Authors=Steve Frécinaux <steve@istique.net>
+Copyright=Copyright © 2005 Steve Frécinaux
+Website=http://www.gedit.org
diff --git a/plugins/externaltools/meson.build b/plugins/externaltools/meson.build
new file mode 100644
index 0000000..d46d0dd
--- /dev/null
+++ b/plugins/externaltools/meson.build
@@ -0,0 +1,35 @@
+subdir('scripts')
+subdir('tools')
+subdir('data')
+
+externaltools_gschema_file = files('org.gnome.gedit.plugins.externaltools.gschema.xml')
+install_data(
+ externaltools_gschema_file,
+ install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas')
+)
+
+if xmllint.found()
+ test(
+ 'validate-externaltools-gschema',
+ xmllint,
+ args: [
+ '--noout',
+ '--dtdvalid', gschema_dtd,
+ externaltools_gschema_file,
+ ]
+ )
+endif
+
+custom_target(
+ 'externaltools.plugin',
+ input: 'externaltools.plugin.desktop.in',
+ output: 'externaltools.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
+
+subdir('tests')
diff --git a/plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml b/plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml
new file mode 100644
index 0000000..d760de2
--- /dev/null
+++ b/plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml
@@ -0,0 +1,20 @@
+<schemalist gettext-domain="gedit">
+ <schema id="org.gnome.gedit.plugins.externaltools" path="/org/gnome/gedit/plugins/externaltools/">
+ <key name="use-system-font" type="b">
+ <default>true</default>
+ <summary>Whether to use the system font</summary>
+ <description>
+ If true, the external tools will use the desktop-global standard
+ font if it’s monospace (and the most similar font it can
+ come up with otherwise).
+ </description>
+ </key>
+ <key name="font" type="s">
+ <default>'Monospace 10'</default>
+ <summary>Font</summary>
+ <description>
+ A Pango font name. Examples are “Sans 12” or “Monospace Bold 14”.
+ </description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/plugins/externaltools/scripts/gedit-tool-merge.pl b/plugins/externaltools/scripts/gedit-tool-merge.pl
new file mode 100755
index 0000000..b7cbd77
--- /dev/null
+++ b/plugins/externaltools/scripts/gedit-tool-merge.pl
@@ -0,0 +1,78 @@
+#!/usr/bin/env perl
+
+# gedit-tool-merge.pl
+# This file is part of gedit
+#
+# Copyright (C) 2006 - Steve Frécinaux <code@istique.net>
+#
+# gedit is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# gedit is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with gedit; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301 USA
+
+# This script merges a script file with a desktop file containing
+# metadata about the external tool. This is required in order to
+# have translatable tools (bug #342042) since intltool can't extract
+# string directly from tool files (a tool file being the combination
+# of a script file and a metadata section).
+#
+# The desktop file is embedded in a comment of the script file, under
+# the assumption that any scripting language supports # as a comment
+# mark (this is likely to be true since the shebang uses #!). The
+# section is placed at the top of the tool file, after the shebang and
+# modelines if present.
+
+use strict;
+use warnings;
+use Getopt::Long;
+
+sub usage {
+ print <<EOF;
+Usage: ${0} [OPTION]... [SCRIPT] [DESKTOP]
+Merges a script file with a desktop file, embedding it in a comment.
+
+ -o, --output=FILE Specify the output file name. [default: stdout]
+EOF
+ exit;
+}
+
+my $output = "";
+my $help;
+
+GetOptions ("help|h" => \$help, "output|o=s" => \$output) or &usage;
+usage if $help or @ARGV lt 2;
+
+open INFILE, "<", $ARGV[0];
+open DFILE, "<", $ARGV[1];
+open STDOUT, ">", $output if $output;
+
+# Put shebang and various modelines at the top of the generated file.
+$_ = <INFILE>;
+print and $_ = <INFILE> if /^#!/;
+print and $_ = <INFILE> if /-\*-/;
+print and $_ = <INFILE> if /(ex|vi|vim):/;
+
+# Put a blank line before the info block if there is one in INFILE.
+print and $_ = <INFILE> if /^\s*$/;
+seek INFILE, -length, 1;
+
+# Embed the desktop file...
+print "# $_" while <DFILE>;
+print "\n";
+
+# ...and write the remaining part of the script.
+print while <INFILE>;
+
+close INFILE;
+close DFILE;
+close STDOUT;
diff --git a/plugins/externaltools/scripts/meson.build b/plugins/externaltools/scripts/meson.build
new file mode 100644
index 0000000..c5f50e9
--- /dev/null
+++ b/plugins/externaltools/scripts/meson.build
@@ -0,0 +1,13 @@
+msgfmt_externaltools_cmd = [
+ find_program('msgfmt'),
+ '--desktop',
+ '--keyword=Name',
+ '--keyword=Comment',
+ '--template=@INPUT@',
+ '-d', join_paths(srcdir, 'po'),
+ '--output=@OUTPUT@'
+]
+
+merge_tool_prg = find_program(
+ files('gedit-tool-merge.pl'),
+)
diff --git a/plugins/externaltools/tests/meson.build b/plugins/externaltools/tests/meson.build
new file mode 100644
index 0000000..6cee9fe
--- /dev/null
+++ b/plugins/externaltools/tests/meson.build
@@ -0,0 +1,21 @@
+externaltools_tests = {
+ 'LinkParser': files('testlinkparsing.py'),
+}
+
+externaltools_srcdir = join_paths(
+ srcdir,
+ 'plugins',
+ 'externaltools',
+ 'tools',
+)
+
+foreach test_name, test_script : externaltools_tests
+ test(
+ 'test-externaltools-@0@'.format(test_name),
+ python3,
+ args: [test_script],
+ env: [
+ 'PYTHONPATH=@0@'.format(externaltools_srcdir),
+ ]
+ )
+endforeach
diff --git a/plugins/externaltools/tests/testlinkparsing.py b/plugins/externaltools/tests/testlinkparsing.py
new file mode 100644
index 0000000..3b8a78e
--- /dev/null
+++ b/plugins/externaltools/tests/testlinkparsing.py
@@ -0,0 +1,203 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009-2010 Per Arneng <per.arneng@anyplanet.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import unittest
+from linkparsing import LinkParser
+
+
+class TestLinkParser(unittest.TestCase):
+
+ def setUp(self):
+ self.p = LinkParser()
+
+ def assert_link_count(self, links, expected_count):
+ self.assertEqual(len(links), expected_count, 'incorrect nr of links')
+
+ def assert_link(self, actual, path, line_nr, col_nr=0):
+ self.assertEqual(actual.path, path, "incorrect path")
+ self.assertEqual(actual.line_nr, line_nr, "incorrect line nr")
+ self.assertEqual(actual.col_nr, col_nr, "incorrect col nr")
+
+ def assert_link_text(self, text, link, link_text):
+ self.assertEqual(text[link.start:link.end], link_text,
+ "the expected link text does not match the text within the string")
+
+ def test_parse_gcc_simple_test_with_real_output(self):
+ gcc_output = """
+test.c: In function 'f':
+test.c:5:6: warning: passing argument 1 of 'f' makes integer from pointer without a cast
+test.c:3:7: note: expected 'int' but argument is of type 'char *'
+test.c: In function 'main':
+test.c:11:10: warning: initialization makes pointer from integer without a cast
+test.c:12:11: warning: initialization makes integer from pointer without a cast
+test.c:13:12: error: too few arguments to function 'f'
+test.c:14:13: error: expected ';' before 'return'
+"""
+ links = self.p.parse(gcc_output)
+ self.assert_link_count(links, 6)
+ lnk = links[2]
+ self.assert_link(lnk, "test.c", 11, 10)
+ self.assert_link_text(gcc_output, lnk, "test.c:11:10")
+
+ def test_parse_gcc_one_line(self):
+ line = "/tmp/myfile.c:1212:12: error: ..."
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "/tmp/myfile.c", 1212, 12)
+ self.assert_link_text(line, lnk, "/tmp/myfile.c:1212:12")
+
+ def test_parse_gcc_empty_string(self):
+ links = self.p.parse("")
+ self.assert_link_count(links, 0)
+
+ def test_parse_gcc_no_files_in_text(self):
+ links = self.p.parse("no file links in this string")
+ self.assert_link_count(links, 0)
+
+ def test_parse_gcc_none_as_argument(self):
+ self.assertRaises(ValueError, self.p.parse, None)
+
+ def test_parse_grep_one_line(self):
+ line = "libnautilus-private/nautilus-canvas-container.h:45:#define NAUTILUS_CANVAS_ICON_DATA(pointer)"
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "libnautilus-private/nautilus-canvas-container.h", 45)
+ self.assert_link_text(line, lnk, "libnautilus-private/nautilus-canvas-container.h:45")
+
+ def test_parse_python_simple_test_with_real_output(self):
+ output = """
+Traceback (most recent call last):
+ File "test.py", line 10, in <module>
+ err()
+ File "test.py", line 7, in err
+ real_err()
+ File "test.py", line 4, in real_err
+ int('xxx')
+ValueError: invalid literal for int() with base 10: 'xxx'
+"""
+ links = self.p.parse(output)
+ self.assert_link_count(links, 3)
+ lnk = links[2]
+ self.assert_link(lnk, "test.py", 4)
+ self.assert_link_text(output, lnk, '"test.py", line 4')
+
+ def test_parse_python_one_line(self):
+ line = " File \"test.py\", line 1\n def a()"
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "test.py", 1)
+ self.assert_link_text(line, lnk, '"test.py", line 1')
+
+ def test_parse_bash_one_line(self):
+ line = "test.sh: line 5: gerp: command not found"
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "test.sh", 5)
+ self.assert_link_text(line, lnk, 'test.sh: line 5')
+
+ def test_parse_javac_one_line(self):
+ line = "/tmp/Test.java:10: incompatible types"
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "/tmp/Test.java", 10)
+ self.assert_link_text(line, lnk, '/tmp/Test.java:10')
+
+ def test_parse_valac_simple_test_with_real_output(self):
+ output = """
+Test.vala:14.13-14.21: error: Assignment: Cannot convert from `string' to `int'
+ int a = "xxx";
+ ^^^^^^^^^
+"""
+ links = self.p.parse(output)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "Test.vala", 14)
+ self.assert_link_text(output, lnk, 'Test.vala:14.13-14.21')
+
+ def test_parse_ruby_simple_test_with_real_output(self):
+ output = """
+test.rb:5: undefined method `fake_method' for main:Object (NoMethodError)
+ from test.rb:3:in `each'
+ from test.rb:3
+"""
+ links = self.p.parse(output)
+ self.assert_link_count(links, 3)
+ lnk = links[0]
+ self.assert_link(lnk, "test.rb", 5)
+ self.assert_link_text(output, lnk, 'test.rb:5')
+ lnk = links[1]
+ self.assert_link(lnk, "test.rb", 3)
+ self.assert_link_text(output, lnk, 'test.rb:3')
+
+ def test_parse_scalac_one_line(self):
+ line = "Test.scala:7: error: not found: value fakeMethod"
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "Test.scala", 7)
+ self.assert_link_text(line, lnk, 'Test.scala:7')
+
+ def test_parse_sbt_one_line(self):
+ line = "[error] /home/hank/foo/Test.scala:7: not found: value fakeMethod"
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "/home/hank/foo/Test.scala", 7)
+ self.assert_link_text(line, lnk, '/home/hank/foo/Test.scala:7')
+
+ def test_parse_go_6g_one_line(self):
+ line = "test.go:9: undefined: FakeMethod"
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "test.go", 9)
+ self.assert_link_text(line, lnk, 'test.go:9')
+
+ def test_parse_perl_one_line(self):
+ line = 'syntax error at test.pl line 889, near "$fake_var'
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "test.pl", 889)
+ self.assert_link_text(line, lnk, 'test.pl line 889')
+
+ def test_parse_mcs_one_line(self):
+ line = 'Test.cs(12,7): error CS0103: The name `fakeMethod'
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "Test.cs", 12)
+ self.assert_link_text(line, lnk, 'Test.cs(12,7)')
+
+ def test_parse_pas_one_line(self):
+ line = 'hello.pas(11,1) Fatal: Syntax error, ":" expected but "BEGIN"'
+ links = self.p.parse(line)
+ self.assert_link_count(links, 1)
+ lnk = links[0]
+ self.assert_link(lnk, "hello.pas", 11)
+ self.assert_link_text(line, lnk, 'hello.pas(11,1)')
+
+if __name__ == '__main__':
+ unittest.main()
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/__init__.py b/plugins/externaltools/tools/__init__.py
new file mode 100644
index 0000000..0cc0b4f
--- /dev/null
+++ b/plugins/externaltools/tools/__init__.py
@@ -0,0 +1,26 @@
+# -*- coding: UTF-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2010 Ignacio Casal Quinteiro <icq@gnome.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gi
+gi.require_version('Gedit', '3.0')
+gi.require_version('Gtk', '3.0')
+
+from .appactivatable import AppActivatable
+from .windowactivatable import WindowActivatable
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/appactivatable.py b/plugins/externaltools/tools/appactivatable.py
new file mode 100644
index 0000000..87e1226
--- /dev/null
+++ b/plugins/externaltools/tools/appactivatable.py
@@ -0,0 +1,178 @@
+# -*- coding: UTF-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gi.repository import GLib, Gio, GObject, Gtk, Gdk, Gedit
+from .library import ToolLibrary
+from .manager import Manager
+import os
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class ToolMenu(object):
+ def __init__(self, library, menu):
+ super(ToolMenu, self).__init__()
+ self._library = library
+ self._menu = menu
+ self._action_tools = {}
+
+ self.update()
+
+ def deactivate(self):
+ self.remove()
+
+ def remove(self):
+ self._menu.remove_all()
+
+ for name, tool in self._action_tools.items():
+ if tool.shortcut:
+ app = Gio.Application.get_default()
+ app.remove_accelerator(tool.shortcut)
+
+ def _insert_directory(self, directory, menu):
+ for d in sorted(directory.subdirs, key=lambda x: x.name.lower()):
+ submenu = Gio.Menu()
+ menu.append_submenu(d.name.replace('_', '__'), submenu)
+ section = Gio.Menu()
+ submenu.append_section(None, section)
+
+ self._insert_directory(d, section)
+
+ for tool in sorted(directory.tools, key=lambda x: x.name.lower()):
+ # FIXME: find a better way to share the action name
+ action_name = 'external-tool-%X-%X' % (id(tool), id(tool.name))
+ item = Gio.MenuItem.new(tool.name.replace('_', '__'), "win.%s" % action_name)
+ item.set_attribute_value("hidden-when", GLib.Variant.new_string("action-disabled"))
+ menu.append_item(item)
+
+ if tool.shortcut:
+ app = Gio.Application.get_default()
+ app.add_accelerator(tool.shortcut, "win.%s" % action_name, None)
+
+ def update(self):
+ self.remove()
+ self._insert_directory(self._library.tree, self._menu)
+
+
+# FIXME: restore the launch of the manager on configure using PeasGtk.Configurable
+class AppActivatable(GObject.Object, Gedit.AppActivatable):
+ __gtype_name__ = "ExternalToolsAppActivatable"
+
+ app = GObject.Property(type=Gedit.App)
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+ self.menu = None
+ self._manager = None
+ self._manager_default_size = None
+
+ def do_activate(self):
+ self._library = ToolLibrary()
+ self._library.set_locations(os.path.join(self.plugin_info.get_data_dir(), 'tools'))
+
+ action = Gio.SimpleAction(name="manage-tools")
+ action.connect("activate", lambda action, parameter: self._open_dialog())
+ self.app.add_action(action)
+
+ self.css = Gtk.CssProvider()
+ self.css.load_from_data("""
+.gedit-tool-manager-paned {
+ border-style: solid;
+ border-color: @borders;
+}
+
+.gedit-tool-manager-paned:dir(ltr) {
+ border-width: 0 1px 0 0;
+}
+
+.gedit-tool-manager-paned:dir(rtl) {
+ border-width: 0 0 0 1px;
+}
+
+.gedit-tool-manager-view {
+ border-width: 0 0 1px 0;
+}
+
+.gedit-tool-manager-treeview {
+ border-top-width: 0;
+}
+
+.gedit-tool-manager-treeview:dir(ltr) {
+ border-left-width: 0;
+}
+
+.gedit-tool-manager-treeview:dir(rtl) {
+ border-right-width: 0;
+}
+""".encode('utf-8'))
+
+ Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
+ self.css, 600)
+
+ self.menu_ext = self.extend_menu("preferences-section")
+ item = Gio.MenuItem.new(_("Manage _External Tools…"), "app.manage-tools")
+ self.menu_ext.append_menu_item(item)
+
+ self.submenu_ext = self.extend_menu("tools-section-1")
+ external_tools_submenu = Gio.Menu()
+ item = Gio.MenuItem.new_submenu(_("External _Tools"), external_tools_submenu)
+ self.submenu_ext.append_menu_item(item)
+ external_tools_submenu_section = Gio.Menu()
+ external_tools_submenu.append_section(None, external_tools_submenu_section)
+
+ self.menu = ToolMenu(self._library, external_tools_submenu_section)
+
+ def do_deactivate(self):
+ self.menu.deactivate()
+ self.menu_ext = None
+ self.submenu_ext = None
+
+ self.app.remove_action("manage-tools")
+
+ Gtk.StyleContext.remove_provider_for_screen(Gdk.Screen.get_default(),
+ self.css)
+
+ def _open_dialog(self):
+ if not self._manager:
+ self._manager = Manager(self.plugin_info.get_data_dir())
+
+ if self._manager_default_size:
+ self._manager.dialog.set_default_size(*self._manager_default_size)
+
+ self._manager.dialog.connect('destroy', self._on_manager_destroy)
+ self._manager.connect('tools-updated', self._on_manager_tools_updated)
+
+ self._manager.run(self.app.get_active_window())
+
+ return self._manager.dialog
+
+ def _on_manager_destroy(self, dialog):
+ self._manager_default_size = self._manager.get_final_size()
+ self._manager = None
+
+ def _on_manager_tools_updated(self, manager):
+ for window in self.app.get_main_windows():
+ window.external_tools_window_activatable.update_actions()
+ self.menu.update()
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/capture.py b/plugins/externaltools/tools/capture.py
new file mode 100644
index 0000000..d7560c5
--- /dev/null
+++ b/plugins/externaltools/tools/capture.py
@@ -0,0 +1,268 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+__all__ = ('Capture', )
+
+import os
+import sys
+import signal
+import locale
+import subprocess
+import fcntl
+from gi.repository import GLib, GObject
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class Capture(GObject.Object):
+ CAPTURE_STDOUT = 0x01
+ CAPTURE_STDERR = 0x02
+ CAPTURE_BOTH = 0x03
+ CAPTURE_NEEDS_SHELL = 0x04
+
+ WRITE_BUFFER_SIZE = 0x4000
+
+ __gsignals__ = {
+ 'stdout-line': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)),
+ 'stderr-line': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)),
+ 'begin-execute': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, tuple()),
+ 'end-execute': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_INT,))
+ }
+
+ def __init__(self, command, cwd=None, env={}):
+ GObject.GObject.__init__(self)
+ self.pipe = None
+ self.env = env
+ self.cwd = cwd
+ self.flags = self.CAPTURE_BOTH | self.CAPTURE_NEEDS_SHELL
+ self.command = command
+ self.input_text = None
+
+ def set_env(self, **values):
+ self.env.update(**values)
+
+ def set_command(self, command):
+ self.command = command
+
+ def set_flags(self, flags):
+ self.flags = flags
+
+ def set_input(self, text):
+ self.input_text = text.encode("UTF-8") if text else None
+
+ def set_cwd(self, cwd):
+ self.cwd = cwd
+
+ def execute(self):
+ if self.command is None:
+ return
+
+ # Initialize pipe
+ popen_args = {
+ 'cwd': self.cwd,
+ 'shell': self.flags & self.CAPTURE_NEEDS_SHELL,
+ 'env': self.env
+ }
+
+ if self.input_text is not None:
+ popen_args['stdin'] = subprocess.PIPE
+ if self.flags & self.CAPTURE_STDOUT:
+ popen_args['stdout'] = subprocess.PIPE
+ if self.flags & self.CAPTURE_STDERR:
+ popen_args['stderr'] = subprocess.PIPE
+
+ self.tried_killing = False
+ self.in_channel = None
+ self.out_channel = None
+ self.err_channel = None
+ self.in_channel_id = 0
+ self.out_channel_id = 0
+ self.err_channel_id = 0
+
+ try:
+ self.pipe = subprocess.Popen(self.command, **popen_args)
+ except OSError as e:
+ self.pipe = None
+ self.emit('stderr-line', _('Could not execute command: %s') % (e, ))
+ return
+
+ self.emit('begin-execute')
+
+ if self.input_text is not None:
+ self.in_channel, self.in_channel_id = self.add_in_watch(self.pipe.stdin.fileno(),
+ self.on_in_writable)
+
+ if self.flags & self.CAPTURE_STDOUT:
+ self.out_channel, self.out_channel_id = self.add_out_watch(self.pipe.stdout.fileno(),
+ self.on_output)
+
+ if self.flags & self.CAPTURE_STDERR:
+ self.err_channel, self.err_channel_id = self.add_out_watch(self.pipe.stderr.fileno(),
+ self.on_err_output)
+
+ # Wait for the process to complete
+ GLib.child_watch_add(GLib.PRIORITY_DEFAULT,
+ self.pipe.pid,
+ self.on_child_end)
+
+ def add_in_watch(self, fd, io_func):
+ channel = GLib.IOChannel.unix_new(fd)
+ channel.set_flags(channel.get_flags() | GLib.IOFlags.NONBLOCK)
+ channel.set_encoding(None)
+ channel_id = GLib.io_add_watch(channel,
+ GLib.PRIORITY_DEFAULT,
+ GLib.IOCondition.OUT | GLib.IOCondition.HUP | GLib.IOCondition.ERR,
+ io_func)
+ return (channel, channel_id)
+
+ def add_out_watch(self, fd, io_func):
+ channel = GLib.IOChannel.unix_new(fd)
+ channel.set_flags(channel.get_flags() | GLib.IOFlags.NONBLOCK)
+ channel_id = GLib.io_add_watch(channel,
+ GLib.PRIORITY_DEFAULT,
+ GLib.IOCondition.IN | GLib.IOCondition.HUP | GLib.IOCondition.ERR,
+ io_func)
+ return (channel, channel_id)
+
+ def write_chunk(self, dest, condition):
+ if condition & (GObject.IO_OUT):
+ status = GLib.IOStatus.NORMAL
+ l = len(self.input_text)
+ while status == GLib.IOStatus.NORMAL:
+ if l == 0:
+ return False
+ m = min(l, self.WRITE_BUFFER_SIZE)
+ try:
+ (status, length) = dest.write_chars(self.input_text, m)
+ self.input_text = self.input_text[length:]
+ l -= length
+ except Exception as e:
+ return False
+ if status != GLib.IOStatus.AGAIN:
+ return False
+
+ if condition & ~(GObject.IO_OUT):
+ return False
+
+ return True
+
+ def on_in_writable(self, dest, condition):
+ ret = self.write_chunk(dest, condition)
+ if ret is False:
+ self.input_text = None
+ try:
+ self.in_channel.shutdown(True)
+ except:
+ pass
+ self.in_channel = None
+ self.in_channel_id = 0
+ self.cleanup_pipe()
+
+ return ret
+
+ def handle_source(self, source, condition, signalname):
+ if condition & (GObject.IO_IN | GObject.IO_PRI):
+ status = GLib.IOStatus.NORMAL
+ while status == GLib.IOStatus.NORMAL:
+ try:
+ (status, buf, length, terminator_pos) = source.read_line()
+ except Exception as e:
+ return False
+ if buf:
+ self.emit(signalname, buf)
+ if status != GLib.IOStatus.AGAIN:
+ return False
+
+ if condition & ~(GObject.IO_IN | GObject.IO_PRI):
+ return False
+
+ return True
+
+ def on_output(self, source, condition):
+ ret = self.handle_source(source, condition, 'stdout-line')
+ if ret is False and self.out_channel:
+ try:
+ self.out_channel.shutdown(True)
+ except:
+ pass
+ self.out_channel = None
+ self.out_channel_id = 0
+ self.cleanup_pipe()
+
+ return ret
+
+ def on_err_output(self, source, condition):
+ ret = self.handle_source(source, condition, 'stderr-line')
+ if ret is False and self.err_channel:
+ try:
+ self.err_channel.shutdown(True)
+ except:
+ pass
+ self.err_channel = None
+ self.err_channel_id = 0
+ self.cleanup_pipe()
+
+ return ret
+
+ def cleanup_pipe(self):
+ if self.in_channel is None and self.out_channel is None and self.err_channel is None:
+ self.pipe = None
+
+ def stop(self, error_code=-1):
+ if self.in_channel_id:
+ GLib.source_remove(self.in_channel_id)
+ self.in_channel.shutdown(True)
+ self.in_channel = None
+ self.in_channel_id = 0
+
+ if self.out_channel_id:
+ GLib.source_remove(self.out_channel_id)
+ self.out_channel.shutdown(True)
+ self.out_channel = None
+ self.out_channel_id = 0
+
+ if self.err_channel_id:
+ GLib.source_remove(self.err_channel_id)
+ self.err_channel.shutdown(True)
+ self.err_channel = None
+ self.err_channel = 0
+
+ if self.pipe is not None:
+ if not self.tried_killing:
+ os.kill(self.pipe.pid, signal.SIGTERM)
+ self.tried_killing = True
+ else:
+ os.kill(self.pipe.pid, signal.SIGKILL)
+
+ self.pipe = None
+
+ def emit_end_execute(self, error_code):
+ self.emit('end-execute', error_code)
+ return False
+
+ def on_child_end(self, pid, error_code):
+ # In an idle, so it is emitted after all the std*-line signals
+ # have been intercepted
+ GLib.idle_add(self.emit_end_execute, error_code)
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/filelookup.py b/plugins/externaltools/tools/filelookup.py
new file mode 100644
index 0000000..f256eea
--- /dev/null
+++ b/plugins/externaltools/tools/filelookup.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009-2010 Per Arneng <per.arneng@anyplanet.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+from gi.repository import Gio, Gedit
+from .functions import *
+
+
+class FileLookup:
+ """
+ This class is responsible for looking up files given a part or the whole
+ path of a real file. The lookup is delegated to providers wich use
+ different methods of trying to find the real file.
+ """
+
+ def __init__(self, window):
+ self.providers = []
+ self.providers.append(AbsoluteFileLookupProvider())
+ self.providers.append(BrowserRootFileLookupProvider(window))
+ self.providers.append(CwdFileLookupProvider())
+ self.providers.append(OpenDocumentRelPathFileLookupProvider())
+ self.providers.append(OpenDocumentFileLookupProvider())
+
+ def lookup(self, path):
+ """
+ Tries to find a file specified by the path parameter. It delegates to
+ different lookup providers and the first match is returned. If no file
+ was found then None is returned.
+
+ path -- the path to find
+ """
+ found_file = None
+ for provider in self.providers:
+ found_file = provider.lookup(path)
+ if found_file is not None:
+ break
+
+ return found_file
+
+
+class FileLookupProvider:
+ """
+ The base class of all file lookup providers.
+ """
+
+ def lookup(self, path):
+ """
+ This method must be implemented by subclasses. Implementors will be
+ given a path and will try to find a matching file. If no file is found
+ then None is returned.
+ """
+ raise NotImplementedError("need to implement a lookup method")
+
+
+class AbsoluteFileLookupProvider(FileLookupProvider):
+ """
+ This file tries to see if the path given is an absolute path and that the
+ path references a file.
+ """
+
+ def lookup(self, path):
+ if os.path.isabs(path) and os.path.isfile(path):
+ return Gio.file_new_for_path(path)
+ else:
+ return None
+
+
+class BrowserRootFileLookupProvider(FileLookupProvider):
+ """
+ This lookup provider tries to find a file specified by the path relative to
+ the file browser root.
+ """
+ def __init__(self, window):
+ self.window = window
+
+ def lookup(self, path):
+ root = file_browser_root(self.window)
+ if root:
+ real_path = os.path.join(root, path)
+ if os.path.isfile(real_path):
+ return Gio.file_new_for_path(real_path)
+
+ return None
+
+
+class CwdFileLookupProvider(FileLookupProvider):
+ """
+ This lookup provider tries to find a file specified by the path relative to
+ the current working directory.
+ """
+
+ def lookup(self, path):
+ try:
+ cwd = os.getcwd()
+ except OSError:
+ cwd = os.getenv('HOME')
+
+ real_path = os.path.join(cwd, path)
+
+ if os.path.isfile(real_path):
+ return Gio.file_new_for_path(real_path)
+ else:
+ return None
+
+
+class OpenDocumentRelPathFileLookupProvider(FileLookupProvider):
+ """
+ Tries to see if the path is relative to any directories where the
+ currently open documents reside in. Example: If you have a document opened
+ '/tmp/Makefile' and a lookup is made for 'src/test2.c' then this class
+ will try to find '/tmp/src/test2.c'.
+ """
+
+ def lookup(self, path):
+ if path.startswith('/'):
+ return None
+
+ for doc in Gio.Application.get_default().get_documents():
+ if doc.get_file().is_local():
+ location = doc.get_file().get_location()
+ if location:
+ rel_path = location.get_parent().get_path()
+ joined_path = os.path.join(rel_path, path)
+ if os.path.isfile(joined_path):
+ return Gio.file_new_for_path(joined_path)
+
+ return None
+
+
+class OpenDocumentFileLookupProvider(FileLookupProvider):
+ """
+ Makes a guess that the if the path that was looked for matches the end
+ of the path of a currently open document then that document is the one
+ that is looked for. Example: If a document is opened called '/tmp/t.c'
+ and a lookup is made for 't.c' or 'tmp/t.c' then both will match since
+ the open document ends with the path that is searched for.
+ """
+
+ def lookup(self, path):
+ if path.startswith('/'):
+ return None
+
+ for doc in Gio.Application.get_default().get_documents():
+ if doc.get_file().is_local():
+ location = doc.get_file().get_location()
+ if location and location.get_uri().endswith(path):
+ return location
+ return None
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/functions.py b/plugins/externaltools/tools/functions.py
new file mode 100644
index 0000000..bc755be
--- /dev/null
+++ b/plugins/externaltools/tools/functions.py
@@ -0,0 +1,365 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+from gi.repository import Gio, Gtk, Gdk, GtkSource, Gedit
+from .capture import *
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+def default(val, d):
+ if val is not None:
+ return val
+ else:
+ return d
+
+
+def current_word(document):
+ piter = document.get_iter_at_mark(document.get_insert())
+ start = piter.copy()
+
+ if not piter.starts_word() and (piter.inside_word() or piter.ends_word()):
+ start.backward_word_start()
+
+ if not piter.ends_word() and piter.inside_word():
+ piter.forward_word_end()
+
+ return (start, piter)
+
+
+def file_browser_root(window):
+ bus = window.get_message_bus()
+
+ if bus.is_registered('/plugins/filebrowser', 'get_root'):
+ msg = bus.send_sync('/plugins/filebrowser', 'get_root')
+
+ if msg:
+ browser_root = msg.props.location
+
+ if browser_root and browser_root.is_native():
+ return browser_root.get_path()
+
+ return None
+
+
+# ==== Capture related functions ====
+def run_external_tool(window, panel, node):
+ # Configure capture environment
+ try:
+ cwd = os.getcwd()
+ except OSError:
+ cwd = os.getenv('HOME')
+
+ capture = Capture(node.command, cwd)
+ capture.env = os.environ.copy()
+ capture.set_env(GEDIT_CWD=cwd)
+
+ view = window.get_active_view()
+ document = None
+
+ if view is not None:
+ # Environment vars relative to current document
+ document = view.get_buffer()
+ location = document.get_file().get_location()
+
+ # Current line number
+ piter = document.get_iter_at_mark(document.get_insert())
+ capture.set_env(GEDIT_CURRENT_LINE_NUMBER=str(piter.get_line() + 1))
+
+ # Current line text
+ piter.set_line_offset(0)
+ end = piter.copy()
+
+ if not end.ends_line():
+ end.forward_to_line_end()
+
+ capture.set_env(GEDIT_CURRENT_LINE=piter.get_text(end))
+
+ if document.get_language() is not None:
+ capture.set_env(GEDIT_CURRENT_DOCUMENT_LANGUAGE=document.get_language().get_id())
+
+ # Selected text (only if input is not selection)
+ if node.input != 'selection' and node.input != 'selection-document':
+ bounds = document.get_selection_bounds()
+
+ if bounds:
+ capture.set_env(GEDIT_SELECTED_TEXT=bounds[0].get_text(bounds[1]))
+
+ bounds = current_word(document)
+ capture.set_env(GEDIT_CURRENT_WORD=bounds[0].get_text(bounds[1]))
+
+ capture.set_env(GEDIT_CURRENT_DOCUMENT_TYPE=document.get_mime_type())
+
+ if location is not None:
+ scheme = location.get_uri_scheme()
+ name = location.get_basename()
+ capture.set_env(GEDIT_CURRENT_DOCUMENT_URI=location.get_uri(),
+ GEDIT_CURRENT_DOCUMENT_NAME=name,
+ GEDIT_CURRENT_DOCUMENT_SCHEME=scheme)
+ if location.has_uri_scheme('file'):
+ path = location.get_path()
+ cwd = os.path.dirname(path)
+ capture.set_cwd(cwd)
+ capture.set_env(GEDIT_CURRENT_DOCUMENT_PATH=path,
+ GEDIT_CURRENT_DOCUMENT_DIR=cwd)
+
+ documents_location = [doc.get_file().get_location()
+ for doc in window.get_documents()
+ if doc.get_file().get_location() is not None]
+ documents_uri = [location.get_uri()
+ for location in documents_location
+ if location.get_uri() is not None]
+ documents_path = [location.get_path()
+ for location in documents_location
+ if location.has_uri_scheme('file')]
+ capture.set_env(GEDIT_DOCUMENTS_URI=' '.join(documents_uri),
+ GEDIT_DOCUMENTS_PATH=' '.join(documents_path))
+
+ # set file browser root env var if possible
+ browser_root = file_browser_root(window)
+ if browser_root:
+ capture.set_env(GEDIT_FILE_BROWSER_ROOT=browser_root)
+
+ flags = capture.CAPTURE_BOTH
+
+ if not node.has_hash_bang():
+ flags |= capture.CAPTURE_NEEDS_SHELL
+
+ capture.set_flags(flags)
+
+ # Get input text
+ input_type = node.input
+ output_type = node.output
+
+ # Clear the panel
+ panel.clear()
+
+ if output_type == 'output-panel':
+ panel.show()
+
+ # Assign the error output to the output panel
+ panel.set_process(capture)
+
+ if input_type != 'nothing' and view is not None:
+ if input_type == 'document':
+ start, end = document.get_bounds()
+ elif input_type == 'selection' or input_type == 'selection-document':
+ try:
+ start, end = document.get_selection_bounds()
+ except ValueError:
+ if input_type == 'selection-document':
+ start, end = document.get_bounds()
+
+ if output_type == 'replace-selection':
+ document.select_range(start, end)
+ else:
+ start = document.get_iter_at_mark(document.get_insert())
+ end = start.copy()
+
+ elif input_type == 'line':
+ start = document.get_iter_at_mark(document.get_insert())
+ end = start.copy()
+ if not start.starts_line():
+ start.set_line_offset(0)
+ if not end.ends_line():
+ end.forward_to_line_end()
+ elif input_type == 'word':
+ start = document.get_iter_at_mark(document.get_insert())
+ end = start.copy()
+ if not start.inside_word():
+ panel.write(_('You must be inside a word to run this command'),
+ panel.error_tag)
+ return
+ if not start.starts_word():
+ start.backward_word_start()
+ if not end.ends_word():
+ end.forward_word_end()
+
+ input_text = document.get_text(start, end, False)
+ capture.set_input(input_text)
+
+ # Assign the standard output to the chosen "file"
+ if output_type == 'new-document':
+ tab = window.create_tab(True)
+ view = tab.get_view()
+ document = tab.get_document()
+ pos = document.get_start_iter()
+ capture.connect('stdout-line', capture_stdout_line_document, document, pos)
+ document.begin_user_action()
+ view.set_editable(False)
+ view.set_cursor_visible(False)
+ elif output_type != 'output-panel' and output_type != 'nothing' and view is not None:
+ document.begin_user_action()
+ view.set_editable(False)
+ view.set_cursor_visible(False)
+
+ if output_type.startswith('replace-'):
+ if output_type == 'replace-selection':
+ try:
+ start_iter, end_iter = document.get_selection_bounds()
+ except ValueError:
+ start_iter = document.get_iter_at_mark(document.get_insert())
+ end_iter = start_iter.copy()
+ elif output_type == 'replace-document':
+ start_iter, end_iter = document.get_bounds()
+ capture.connect('stdout-line', capture_delayed_replace,
+ document, start_iter, end_iter)
+ else:
+ if output_type == 'insert':
+ pos = document.get_iter_at_mark(document.get_insert())
+ else:
+ pos = document.get_end_iter()
+ capture.connect('stdout-line', capture_stdout_line_document, document, pos)
+ elif output_type != 'nothing':
+ capture.connect('stdout-line', capture_stdout_line_panel, panel)
+
+ if not document is None:
+ document.begin_user_action()
+
+ capture.connect('stderr-line', capture_stderr_line_panel, panel)
+ capture.connect('begin-execute', capture_begin_execute_panel, panel, view, node.name)
+ capture.connect('end-execute', capture_end_execute_panel, panel, view, output_type)
+
+ # Run the command
+ capture.execute()
+
+ if output_type != 'nothing':
+ if not document is None:
+ document.end_user_action()
+
+class MultipleDocumentsSaver:
+ def __init__(self, window, panel, all_docs, node):
+ self._window = window
+ self._panel = panel
+ self._node = node
+
+ if all_docs:
+ docs = window.get_documents()
+ else:
+ docs = [window.get_active_document()]
+
+ self._docs_to_save = [doc for doc in docs if doc.get_modified()]
+ self.save_next_document()
+
+ def save_next_document(self):
+ if len(self._docs_to_save) == 0:
+ # The documents are saved, we can run the tool.
+ run_external_tool(self._window, self._panel, self._node)
+ else:
+ next_doc = self._docs_to_save[0]
+ self._docs_to_save.remove(next_doc)
+
+ Gedit.commands_save_document_async(next_doc,
+ self._window,
+ None,
+ self.on_document_saved,
+ None)
+
+ def on_document_saved(self, doc, result, user_data):
+ saved = Gedit.commands_save_document_finish(doc, result)
+ if saved:
+ self.save_next_document()
+
+
+def capture_menu_action(action, parameter, window, panel, node):
+ if node.save_files == 'document' and window.get_active_document():
+ MultipleDocumentsSaver(window, panel, False, node)
+ return
+ elif node.save_files == 'all':
+ MultipleDocumentsSaver(window, panel, True, node)
+ return
+
+ run_external_tool(window, panel, node)
+
+
+def capture_stderr_line_panel(capture, line, panel):
+ if not panel.visible():
+ panel.show()
+
+ panel.write(line, panel.error_tag)
+
+
+def capture_begin_execute_panel(capture, panel, view, label):
+ if view:
+ view.get_window(Gtk.TextWindowType.TEXT).set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
+
+ panel['stop'].set_sensitive(True)
+ panel.clear()
+ panel.write(_("Running tool:"), panel.italic_tag)
+ panel.write(" %s\n\n" % label, panel.bold_tag)
+
+
+def capture_end_execute_panel(capture, exit_code, panel, view, output_type):
+ panel['stop'].set_sensitive(False)
+
+ if view:
+ if output_type in ('new-document', 'replace-document'):
+ doc = view.get_buffer()
+ start = doc.get_start_iter()
+ end = start.copy()
+ end.forward_chars(300)
+ uri = ''
+
+ mtype, uncertain = Gio.content_type_guess(None, doc.get_text(start, end, False).encode('utf-8'))
+ lmanager = GtkSource.LanguageManager.get_default()
+
+ location = doc.get_file().get_location()
+ if location:
+ uri = location.get_uri()
+ language = lmanager.guess_language(uri, mtype)
+
+ if language is not None:
+ doc.set_language(language)
+
+ view.get_window(Gtk.TextWindowType.TEXT).set_cursor(Gdk.Cursor.new(Gdk.CursorType.XTERM))
+ view.set_cursor_visible(True)
+ view.set_editable(True)
+
+ if exit_code == 0:
+ panel.write("\n" + _("Done.") + "\n", panel.italic_tag)
+ else:
+ panel.write("\n" + _("Exited") + ":", panel.italic_tag)
+ panel.write(" %d\n" % exit_code, panel.bold_tag)
+
+
+def capture_stdout_line_panel(capture, line, panel):
+ panel.write(line)
+
+
+def capture_stdout_line_document(capture, line, document, pos):
+ document.insert(pos, line)
+
+
+def capture_delayed_replace(capture, line, document, start_iter, end_iter):
+ document.delete(start_iter, end_iter)
+
+ # Must be done after deleting the text
+ pos = document.get_iter_at_mark(document.get_insert())
+
+ capture_stdout_line_document(capture, line, document, pos)
+
+ capture.disconnect_by_func(capture_delayed_replace)
+ capture.connect('stdout-line', capture_stdout_line_document, document, pos)
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/library.py b/plugins/externaltools/tools/library.py
new file mode 100644
index 0000000..adfd943
--- /dev/null
+++ b/plugins/externaltools/tools/library.py
@@ -0,0 +1,520 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2006 Steve Frécinaux <code@istique.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import re
+import locale
+import platform
+from gi.repository import GLib
+
+
+class Singleton(object):
+ _instance = None
+
+ def __new__(cls, *args, **kwargs):
+ if not cls._instance:
+ cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
+ cls._instance.__init_once__()
+
+ return cls._instance
+
+
+class ToolLibrary(Singleton):
+ def __init_once__(self):
+ self.locations = []
+
+ def set_locations(self, datadir):
+ self.locations = []
+
+ if platform.platform() != 'Windows':
+ for d in self.get_xdg_data_dirs():
+ self.locations.append(os.path.join(d, 'gedit', 'plugins', 'externaltools', 'tools'))
+
+ self.locations.append(datadir)
+
+ # self.locations[0] is where we save the custom scripts
+ if platform.platform() == 'Windows':
+ toolsdir = os.path.expanduser('~/gedit/tools')
+ else:
+ userdir = os.getenv('GNOME22_USER_DIR')
+ if userdir:
+ toolsdir = os.path.join(userdir, 'gedit/tools')
+ else:
+ toolsdir = os.path.join(GLib.get_user_config_dir(), 'gedit/tools')
+
+ self.locations.insert(0, toolsdir)
+
+ if not os.path.isdir(self.locations[0]):
+ os.makedirs(self.locations[0])
+ self.tree = ToolDirectory(self, '')
+ self.import_old_xml_store()
+ else:
+ self.tree = ToolDirectory(self, '')
+
+ # cf. http://standards.freedesktop.org/basedir-spec/latest/
+ def get_xdg_data_dirs(self):
+ dirs = os.getenv('XDG_DATA_DIRS')
+ if dirs:
+ dirs = dirs.split(os.pathsep)
+ else:
+ dirs = ('/usr/local/share', '/usr/share')
+ return dirs
+
+ # This function is meant to be ran only once, when the tools directory is
+ # created. It imports eventual tools that have been saved in the old XML
+ # storage file.
+ def import_old_xml_store(self):
+ import xml.etree.ElementTree as et
+ userdir = os.getenv('GNOME22_USER_DIR')
+ if userdir:
+ filename = os.path.join(userdir, 'gedit/gedit-tools.xml')
+ else:
+ filename = os.path.join(GLib.get_user_config_dir(), 'gedit/gedit-tools.xml')
+
+ if not os.path.isfile(filename):
+ return
+
+ print("External tools: importing old tools into the new store...")
+
+ xtree = et.parse(filename)
+ xroot = xtree.getroot()
+
+ for xtool in xroot:
+ for i in self.tree.tools:
+ if i.name == xtool.get('label'):
+ tool = i
+ break
+ else:
+ tool = Tool(self.tree)
+ tool.name = xtool.get('label')
+ tool.autoset_filename()
+ self.tree.tools.append(tool)
+ tool.comment = xtool.get('description')
+ tool.shortcut = xtool.get('accelerator')
+ tool.applicability = xtool.get('applicability')
+ tool.output = xtool.get('output')
+ tool.input = xtool.get('input')
+
+ tool.save_with_script(xtool.text)
+
+ def get_full_path(self, path, mode='r', system=True, local=True):
+ assert (system or local)
+ if path is None:
+ return None
+ if mode == 'r':
+ if system and local:
+ locations = self.locations
+ elif local and not system:
+ locations = [self.locations[0]]
+ elif system and not local:
+ locations = self.locations[1:]
+ else:
+ raise ValueError("system and local can't be both set to False")
+
+ for i in locations:
+ p = os.path.join(i, path)
+ if os.path.lexists(p):
+ return p
+ return None
+ else:
+ path = os.path.join(self.locations[0], path)
+ dirname = os.path.dirname(path)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
+ return path
+
+
+class ToolDirectory(object):
+ def __init__(self, parent, dirname):
+ super(ToolDirectory, self).__init__()
+ self.subdirs = list()
+ self.tools = list()
+ if isinstance(parent, ToolDirectory):
+ self.parent = parent
+ self.library = parent.library
+ else:
+ self.parent = None
+ self.library = parent
+ self.dirname = dirname
+ self._load()
+
+ def listdir(self):
+ elements = dict()
+ for l in self.library.locations:
+ d = os.path.join(l, self.dirname)
+ if not os.path.isdir(d):
+ continue
+ for i in os.listdir(d):
+ elements[i] = None
+ keys = sorted(elements.keys())
+ return keys
+
+ def _load(self):
+ for p in self.listdir():
+ path = os.path.join(self.dirname, p)
+ full_path = self.library.get_full_path(path)
+ if os.path.isdir(full_path):
+ self.subdirs.append(ToolDirectory(self, p))
+ elif os.path.isfile(full_path) and os.access(full_path, os.X_OK):
+ self.tools.append(Tool(self, p))
+
+ def get_path(self):
+ if self.parent is None:
+ return self.dirname
+ else:
+ return os.path.join(self.parent.get_path(), self.dirname)
+ path = property(get_path)
+
+ def get_name(self):
+ return os.path.basename(self.dirname)
+ name = property(get_name)
+
+ def delete_tool(self, tool):
+ # Only remove it if it lays in $HOME
+ if tool in self.tools:
+ path = tool.get_path()
+ if path is not None:
+ filename = os.path.join(self.library.locations[0], path)
+ if os.path.isfile(filename):
+ os.unlink(filename)
+ self.tools.remove(tool)
+ return True
+ else:
+ return False
+
+ def revert_tool(self, tool):
+ # Only remove it if it lays in $HOME
+ filename = os.path.join(self.library.locations[0], tool.get_path())
+ if tool in self.tools and os.path.isfile(filename):
+ os.unlink(filename)
+ tool._load()
+ return True
+ else:
+ return False
+
+
+class Tool(object):
+ RE_KEY = re.compile('^([a-zA-Z_][a-zA-Z0-9_.\-]*)(\[([a-zA-Z_@]+)\])?$')
+
+ def __init__(self, parent, filename=None):
+ super(Tool, self).__init__()
+ self.parent = parent
+ self.library = parent.library
+ self.filename = filename
+ self.changed = False
+ self._properties = dict()
+ self._transform = {
+ 'Languages': [self._to_list, self._from_list]
+ }
+ self._load()
+
+ def _to_list(self, value):
+ if value.strip() == '':
+ return []
+ else:
+ return [x.strip() for x in value.split(',')]
+
+ def _from_list(self, value):
+ return ','.join(value)
+
+ def _parse_value(self, key, value):
+ if key in self._transform:
+ return self._transform[key][0](value)
+ else:
+ return value
+
+ def _load(self):
+ if self.filename is None:
+ return
+
+ filename = self.library.get_full_path(self.get_path())
+ if filename is None:
+ return
+
+ fp = open(filename, 'r', 1, encoding='utf-8')
+ in_block = False
+ lang = locale.getlocale(locale.LC_MESSAGES)[0]
+
+ for line in fp:
+ if not in_block:
+ in_block = line.startswith('# [Gedit Tool]')
+ continue
+ if line.startswith('##') or line.startswith('# #'):
+ continue
+ if not line.startswith('# '):
+ break
+
+ try:
+ (key, value) = [i.strip() for i in line[2:].split('=', 1)]
+ m = self.RE_KEY.match(key)
+ if m.group(3) is None:
+ self._properties[m.group(1)] = self._parse_value(m.group(1), value)
+ elif lang is not None and lang.startswith(m.group(3)):
+ self._properties[m.group(1)] = self._parse_value(m.group(1), value)
+ except ValueError:
+ break
+ fp.close()
+ self.changed = False
+
+ def _set_property_if_changed(self, key, value):
+ if value != self._properties.get(key):
+ self._properties[key] = value
+
+ self.changed = True
+
+ def is_global(self):
+ return self.library.get_full_path(self.get_path(), local=False) is not None
+
+ def is_local(self):
+ return self.library.get_full_path(self.get_path(), system=False) is not None
+
+ def is_global(self):
+ return self.library.get_full_path(self.get_path(), local=False) is not None
+
+ def get_path(self):
+ if self.filename is not None:
+ return os.path.join(self.parent.get_path(), self.filename)
+ else:
+ return None
+ path = property(get_path)
+
+ # This command is the one that is meant to be ran
+ # (later, could have an Exec key or something)
+ def get_command(self):
+ return self.library.get_full_path(self.get_path())
+ command = property(get_command)
+
+ def get_applicability(self):
+ applicability = self._properties.get('Applicability')
+ if applicability:
+ return applicability
+ return 'all'
+
+ def set_applicability(self, value):
+ self._set_property_if_changed('Applicability', value)
+
+ applicability = property(get_applicability, set_applicability)
+
+ def get_name(self):
+ name = self._properties.get('Name')
+ if name:
+ return name
+ return os.path.basename(self.filename)
+
+ def set_name(self, value):
+ self._set_property_if_changed('Name', value)
+
+ name = property(get_name, set_name)
+
+ def get_shortcut(self):
+ shortcut = self._properties.get('Shortcut')
+ if shortcut:
+ return shortcut
+ return None
+
+ def set_shortcut(self, value):
+ self._set_property_if_changed('Shortcut', value)
+
+ shortcut = property(get_shortcut, set_shortcut)
+
+ def get_comment(self):
+ comment = self._properties.get('Comment')
+ if comment:
+ return comment
+ return self.filename
+
+ def set_comment(self, value):
+ self._set_property_if_changed('Comment', value)
+
+ comment = property(get_comment, set_comment)
+
+ def get_input(self):
+ input = self._properties.get('Input')
+ if input:
+ return input
+ return 'nothing'
+
+ def set_input(self, value):
+ self._set_property_if_changed('Input', value)
+
+ input = property(get_input, set_input)
+
+ def get_output(self):
+ output = self._properties.get('Output')
+ if output:
+ return output
+ return 'output-panel'
+
+ def set_output(self, value):
+ self._set_property_if_changed('Output', value)
+
+ output = property(get_output, set_output)
+
+ def get_save_files(self):
+ save_files = self._properties.get('Save-files')
+ if save_files:
+ return save_files
+ return 'nothing'
+
+ def set_save_files(self, value):
+ self._set_property_if_changed('Save-files', value)
+
+ save_files = property(get_save_files, set_save_files)
+
+ def get_languages(self):
+ languages = self._properties.get('Languages')
+ if languages:
+ return languages
+ return []
+
+ def set_languages(self, value):
+ self._set_property_if_changed('Languages', value)
+
+ languages = property(get_languages, set_languages)
+
+ def has_hash_bang(self):
+ if self.filename is None:
+ return True
+
+ filename = self.library.get_full_path(self.get_path())
+ if filename is None:
+ return True
+
+ fp = open(filename, 'r', 1, encoding='utf-8')
+ for line in fp:
+ if line.strip() == '':
+ continue
+ return line.startswith('#!')
+
+ # There is no property for this one because this function is quite
+ # expensive to perform
+ def get_script(self):
+ if self.filename is None:
+ return ["#!/bin/sh\n"]
+
+ filename = self.library.get_full_path(self.get_path())
+ if filename is None:
+ return ["#!/bin/sh\n"]
+
+ fp = open(filename, 'r', 1, encoding='utf-8')
+ lines = list()
+
+ # before entering the data block
+ for line in fp:
+ if line.startswith('# [Gedit Tool]'):
+ break
+ lines.append(line)
+ # in the block:
+ for line in fp:
+ if line.startswith('##'):
+ continue
+ if not (line.startswith('# ') and '=' in line):
+ # after the block: strip one emtpy line (if present)
+ if line.strip() != '':
+ lines.append(line)
+ break
+ # after the block
+ for line in fp:
+ lines.append(line)
+ fp.close()
+ return lines
+
+ def _dump_properties(self):
+ lines = ['# [Gedit Tool]']
+ for item in self._properties.items():
+ if item[0] in self._transform:
+ lines.append('# %s=%s' % (item[0], self._transform[item[0]][1](item[1])))
+ elif item[1] is not None:
+ lines.append('# %s=%s' % item)
+ return '\n'.join(lines) + '\n'
+
+ def save_with_script(self, script):
+ filename = self.library.get_full_path(self.filename, 'w')
+ fp = open(filename, 'w', 1, encoding='utf-8')
+
+ # Make sure to first print header (shebang, modeline), then
+ # properties, and then actual content
+ header = []
+ content = []
+ inheader = True
+
+ # Parse
+ for line in script:
+ line = line.rstrip("\n")
+ if not inheader:
+ content.append(line)
+ elif line.startswith('#!'):
+ # Shebang (should be always present)
+ header.append(line)
+ elif line.strip().startswith('#') and ('-*-' in line or 'ex:' in line or 'vi:' in line or 'vim:' in line):
+ header.append(line)
+ else:
+ content.append(line)
+ inheader = False
+
+ # Write out header
+ for line in header:
+ fp.write(line + "\n")
+
+ fp.write(self._dump_properties())
+ fp.write("\n")
+
+ for line in content:
+ fp.write(line + "\n")
+
+ fp.close()
+ os.chmod(filename, 0o750)
+ self.changed = False
+
+ def save(self):
+ if self.changed:
+ self.save_with_script(self.get_script())
+
+ def autoset_filename(self):
+ if self.filename is not None:
+ return
+ dirname = self.parent.path
+ if dirname != '':
+ dirname += os.path.sep
+
+ basename = self.name.lower().replace(' ', '-').replace('/', '-')
+
+ if self.library.get_full_path(dirname + basename):
+ i = 2
+ while self.library.get_full_path(dirname + "%s-%d" % (basename, i)):
+ i += 1
+ basename = "%s-%d" % (basename, i)
+ self.filename = basename
+
+if __name__ == '__main__':
+ library = ToolLibrary()
+ library.set_locations(os.path.expanduser("~/.config/gedit/tools"))
+
+ def print_tool(t, indent):
+ print(indent * " " + "%s: %s" % (t.filename, t.name))
+
+ def print_dir(d, indent):
+ print(indent * " " + d.dirname + '/')
+ for i in d.subdirs:
+ print_dir(i, indent + 1)
+ for i in d.tools:
+ print_tool(i, indent + 1)
+
+ print_dir(library.tree, 0)
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/linkparsing.py b/plugins/externaltools/tools/linkparsing.py
new file mode 100644
index 0000000..d9c09a5
--- /dev/null
+++ b/plugins/externaltools/tools/linkparsing.py
@@ -0,0 +1,252 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009-2010 Per Arneng <per.arneng@anyplanet.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import re
+
+
+class Link:
+ """
+ This class represents a file link from within a string given by the
+ output of some software tool. A link contains a reference to a file, the
+ line number within the file and the boundaries within the given output
+ string that should be marked as a link.
+ """
+
+ def __init__(self, path, line_nr, col_nr, start, end):
+ """
+ path -- the path of the file (that could be extracted)
+ line_nr -- the line nr of the specified file
+ col_nr -- the col nr of the specific file
+ start -- the index within the string that the link starts at
+ end -- the index within the string where the link ends at
+ """
+ self.path = path
+ self.line_nr = int(line_nr)
+ self.col_nr = int(col_nr)
+ self.start = start
+ self.end = end
+
+ def __repr__(self):
+ return "%s[%s][%s](%s:%s)" % (self.path, self.line_nr, self.col_nr,
+ self.start, self.end)
+
+
+class LinkParser:
+ """
+ Parses a text using different parsing providers with the goal of finding one
+ or more file links within the text. A typical example could be the output
+ from a compiler that specifies an error in a specific file. The path of the
+ file, the line nr and some more info is then returned so that it can be used
+ to be able to navigate from the error output in to the specific file.
+
+ The actual work of parsing the text is done by instances of classes that
+ inherits from AbstractLinkParser or by regular expressions. To add a new
+ parser just create a class that inherits from AbstractLinkParser and then
+ register in this class cunstructor using the method add_parser. If you want
+ to add a regular expression then just call add_regexp in this class
+ constructor and provide your regexp string as argument.
+ """
+
+ def __init__(self):
+ self._providers = []
+ self.add_regexp(REGEXP_STANDARD)
+ self.add_regexp(REGEXP_PYTHON)
+ self.add_regexp(REGEXP_VALAC)
+ self.add_regexp(REGEXP_BASH)
+ self.add_regexp(REGEXP_RUBY)
+ self.add_regexp(REGEXP_PERL)
+ self.add_regexp(REGEXP_MCS)
+
+ def add_parser(self, parser):
+ self._providers.append(parser)
+
+ def add_regexp(self, regexp):
+ """
+ Adds a regular expression string that should match a link using
+ re.MULTILINE and re.VERBOSE regexp. The area marked as a link should
+ be captured by a group named lnk. The path of the link should be
+ captured by a group named pth. The line number should be captured by
+ a group named ln. To read more about this look at the documentation
+ for the RegexpLinkParser constructor.
+ """
+ self.add_parser(RegexpLinkParser(regexp))
+
+ def parse(self, text):
+ """
+ Parses the given text and returns a list of links that are parsed from
+ the text. This method delegates to parser providers that can parse
+ output from different kinds of formats. If no links are found then an
+ empty list is returned.
+
+ text -- the text to scan for file links. 'text' can not be None.
+ """
+ if text is None:
+ raise ValueError("text can not be None")
+
+ links = []
+
+ for provider in self._providers:
+ links.extend(provider.parse(text))
+
+ return links
+
+
+class AbstractLinkParser(object):
+ """The "abstract" base class for link parses"""
+
+ def parse(self, text):
+ """
+ This method should be implemented by subclasses. It takes a text as
+ argument (never None) and then returns a list of Link objects. If no
+ links are found then an empty list is expected. The Link class is
+ defined in this module. If you do not override this method then a
+ NotImplementedError will be thrown.
+
+ text -- the text to parse. This argument is never None.
+ """
+ raise NotImplementedError("need to implement a parse method")
+
+
+class RegexpLinkParser(AbstractLinkParser):
+ """
+ A class that represents parsers that only use one single regular expression.
+ It can be used by subclasses or by itself. See the constructor documentation
+ for details about the rules surrouning the regexp.
+ """
+
+ def __init__(self, regex):
+ """
+ Creates a new RegexpLinkParser based on the given regular expression.
+ The regular expression is multiline and verbose (se python docs on
+ compilation flags). The regular expression should contain three named
+ capturing groups 'lnk', 'pth' and 'ln'. 'lnk' represents the area wich
+ should be marked as a link in the text. 'pth' is the path that should
+ be looked for and 'ln' is the line number in that file.
+ """
+ self.re = re.compile(regex, re.MULTILINE | re.VERBOSE)
+
+ def parse(self, text):
+ links = []
+ for m in re.finditer(self.re, text):
+ groups = m.groups()
+
+ path = m.group("pth")
+ line_nr = m.group("ln")
+ start = m.start("lnk")
+ end = m.end("lnk")
+
+ # some regexes may have a col group
+ if len(groups) > 3 and groups[3] != None:
+ col_nr = m.group("col")
+ else:
+ col_nr = 0
+
+ link = Link(path, line_nr, col_nr, start, end)
+ links.append(link)
+
+ return links
+
+# gcc 'test.c:13: warning: ...'
+# grep 'test.c:5:int main(...'
+# javac 'Test.java:13: ...'
+# ruby 'test.rb:5: ...'
+# scalac 'Test.scala:5: ...'
+# sbt (scala) '[error] test.scala:4: ...'
+# 6g (go) 'test.go:9: ...'
+REGEXP_STANDARD = r"""
+^
+(?:\[(?:error|warn)\]\ )?
+(?P<lnk>
+ (?P<pth> [^ \:\n]* )
+ \:
+ (?P<ln> \d+)
+ \:?
+ (?P<col> \d+)?
+)
+\:"""
+
+# python ' File "test.py", line 13'
+REGEXP_PYTHON = r"""
+^\s\sFile\s
+(?P<lnk>
+ \"
+ (?P<pth> [^\"]+ )
+ \",\sline\s
+ (?P<ln> \d+ )
+)"""
+
+# python 'test.sh: line 5:'
+REGEXP_BASH = r"""
+^(?P<lnk>
+ (?P<pth> .* )
+ \:\sline\s
+ (?P<ln> \d+ )
+)\:"""
+
+# valac 'Test.vala:13.1-13.3: ...'
+REGEXP_VALAC = r"""
+^(?P<lnk>
+ (?P<pth>
+ .*vala
+ )
+ \:
+ (?P<ln>
+ \d+
+ )
+ \.\d+-\d+\.\d+
+ )\: """
+
+#ruby
+#test.rb:5: ...
+# from test.rb:3:in `each'
+# fist line parsed by REGEXP_STANDARD
+REGEXP_RUBY = r"""
+^\s+from\s
+(?P<lnk>
+ (?P<pth>
+ .*
+ )
+ \:
+ (?P<ln>
+ \d+
+ )
+ )"""
+
+# perl 'syntax error at test.pl line 88, near "$fake_var'
+REGEXP_PERL = r"""
+\sat\s
+(?P<lnk>
+ (?P<pth> .* )
+ \sline\s
+ (?P<ln> \d+ )
+)"""
+
+# mcs (C#) 'Test.cs(12,7): error CS0103: The name `fakeMethod'
+# fpc (Pascal) 'hello.pas(11,1) Fatal: Syntax error, ":" expected but "BEGIN"'
+REGEXP_MCS = r"""
+^
+(?P<lnk>
+ (?P<pth> \S+ )
+ \(
+ (?P<ln> \d+ )
+ ,\d+\)
+)
+\:?\s
+"""
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/manager.py b/plugins/externaltools/tools/manager.py
new file mode 100644
index 0000000..072286b
--- /dev/null
+++ b/plugins/externaltools/tools/manager.py
@@ -0,0 +1,878 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+__all__ = ('Manager', )
+
+import os.path
+from .library import *
+from .functions import *
+import hashlib
+from xml.sax import saxutils
+from gi.repository import Gio, GObject, Gtk, GtkSource, Gedit
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class LanguagesPopup(Gtk.Popover):
+ __gtype_name__ = "LanguagePopup"
+
+ COLUMN_NAME = 0
+ COLUMN_ID = 1
+ COLUMN_ENABLED = 2
+
+ def __init__(self, widget, languages):
+ Gtk.Popover.__init__(self, relative_to=widget)
+
+ self.props.can_focus = True
+
+ self.build()
+ self.init_languages(languages)
+
+ self.view.get_selection().select_path((0,))
+
+ def build(self):
+ self.model = Gtk.ListStore(str, str, bool)
+
+ self.sw = Gtk.ScrolledWindow()
+ self.sw.set_size_request(-1, 200)
+ self.sw.show()
+
+ self.sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
+ self.sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
+
+ self.view = Gtk.TreeView(model=self.model)
+ self.view.show()
+
+ self.view.set_headers_visible(False)
+
+ column = Gtk.TreeViewColumn()
+
+ renderer = Gtk.CellRendererToggle()
+ column.pack_start(renderer, False)
+ column.add_attribute(renderer, 'active', self.COLUMN_ENABLED)
+
+ renderer.connect('toggled', self.on_language_toggled)
+
+ renderer = Gtk.CellRendererText()
+ column.pack_start(renderer, True)
+ column.add_attribute(renderer, 'text', self.COLUMN_NAME)
+
+ self.view.append_column(column)
+ self.view.set_row_separator_func(self.on_separator, None)
+
+ self.sw.add(self.view)
+
+ self.add(self.sw)
+
+ def enabled_languages(self, model, path, piter, ret):
+ enabled = model.get_value(piter, self.COLUMN_ENABLED)
+
+ if path.get_indices()[0] == 0 and enabled:
+ return True
+
+ if enabled:
+ ret.append(model.get_value(piter, self.COLUMN_ID))
+
+ return False
+
+ def languages(self):
+ ret = []
+
+ self.model.foreach(self.enabled_languages, ret)
+ return ret
+
+ def on_separator(self, model, piter, user_data=None):
+ val = model.get_value(piter, self.COLUMN_NAME)
+ return val == '-'
+
+ def init_languages(self, languages):
+ manager = GtkSource.LanguageManager()
+ langs = [manager.get_language(x) for x in manager.get_language_ids()]
+ langs.sort(key=lambda x: x.get_name())
+
+ self.model.append([_('All languages'), None, not languages])
+ self.model.append(['-', None, False])
+ self.model.append([_('Plain Text'), 'plain', 'plain' in languages])
+ self.model.append(['-', None, False])
+
+ for lang in langs:
+ self.model.append([lang.get_name(), lang.get_id(), lang.get_id() in languages])
+
+ def correct_all(self, model, path, piter, enabled):
+ if path.get_indices()[0] == 0:
+ return False
+
+ model.set_value(piter, self.COLUMN_ENABLED, enabled)
+
+ def on_language_toggled(self, renderer, path):
+ piter = self.model.get_iter(path)
+
+ enabled = self.model.get_value(piter, self.COLUMN_ENABLED)
+ self.model.set_value(piter, self.COLUMN_ENABLED, not enabled)
+
+ if path == '0':
+ self.model.foreach(self.correct_all, False)
+ else:
+ self.model.set_value(self.model.get_iter_first(), self.COLUMN_ENABLED, False)
+
+
+class Manager(GObject.Object):
+ TOOL_COLUMN = 0 # For Tree
+ NAME_COLUMN = 1 # For Combo
+
+ __gsignals__ = {
+ 'tools-updated': (GObject.SignalFlags.RUN_LAST, None, ())
+ }
+
+ def __init__(self, datadir):
+ GObject.Object.__init__(self)
+ self.datadir = datadir
+ self.dialog = None
+ self._size = (0, 0)
+ self._languages = {}
+ self._tool_rows = {}
+
+ self.build()
+
+ def get_final_size(self):
+ return self._size
+
+ def build(self):
+ callbacks = {
+ 'on_add_tool_button_clicked': self.on_add_tool_button_clicked,
+ 'on_remove_tool_button_clicked': self.on_remove_tool_button_clicked,
+ 'on_tool_manager_dialog_delete_event': self.on_tool_manager_dialog_delete_event,
+ 'on_tool_manager_dialog_focus_out': self.on_tool_manager_dialog_focus_out,
+ 'on_tool_manager_dialog_configure_event': self.on_tool_manager_dialog_configure_event,
+ 'on_accelerator_key_press': self.on_accelerator_key_press,
+ 'on_accelerator_focus_in': self.on_accelerator_focus_in,
+ 'on_accelerator_focus_out': self.on_accelerator_focus_out,
+ 'on_accelerator_backspace': self.on_accelerator_backspace,
+ 'on_applicability_changed': self.on_applicability_changed,
+ 'on_languages_button_clicked': self.on_languages_button_clicked
+ }
+
+ # Load the "main-window" widget from the ui file.
+ self.ui = Gtk.Builder()
+ self.ui.add_from_file(os.path.join(self.datadir, 'ui', 'tools.ui'))
+ self.ui.connect_signals(callbacks)
+ self.dialog = self.ui.get_object('tool-manager-dialog')
+
+ self.view = self['view']
+
+ self.__init_tools_model()
+ self.__init_tools_view()
+
+ # join treeview and toolbar
+ context = self['scrolled_window1'].get_style_context()
+ context.set_junction_sides(Gtk.JunctionSides.BOTTOM)
+ context = self['toolbar1'].get_style_context()
+ context.set_junction_sides(Gtk.JunctionSides.TOP)
+ context.set_junction_sides(Gtk.JunctionSides.BOTTOM)
+
+ for name in ['input', 'output', 'applicability', 'save-files']:
+ self.__init_combobox(name)
+
+ self.do_update()
+
+ def expand_from_doc(self, doc):
+ row = None
+
+ if doc:
+ if doc.get_language():
+ lid = doc.get_language().get_id()
+
+ if lid in self._languages:
+ row = self._languages[lid]
+ elif 'plain' in self._languages:
+ row = self._languages['plain']
+
+ if not row and None in self._languages:
+ row = self._languages[None]
+
+ if not row:
+ return
+
+ self.view.expand_row(row.get_path(), False)
+ self.view.get_selection().select_path(row.get_path())
+
+ def run(self, window):
+ if self.dialog is None:
+ self.build()
+
+ # Open up language
+ self.expand_from_doc(window.get_active_document())
+
+ self.dialog.set_transient_for(window)
+ window.get_group().add_window(self.dialog)
+ self.dialog.present()
+
+ def add_accelerator(self, item):
+ if not item.shortcut:
+ return
+
+ if item.shortcut in self.accelerators:
+ if not item in self.accelerators[item.shortcut]:
+ self.accelerators[item.shortcut].append(item)
+ else:
+ self.accelerators[item.shortcut] = [item]
+
+ def remove_accelerator(self, item, shortcut=None):
+ if not shortcut:
+ shortcut = item.shortcut
+
+ if not shortcut in self.accelerators:
+ return
+
+ self.accelerators[shortcut].remove(item)
+
+ if not self.accelerators[shortcut]:
+ del self.accelerators[shortcut]
+
+ def add_tool_to_language(self, tool, language):
+ if isinstance(language, GtkSource.Language):
+ lid = language.get_id()
+ else:
+ lid = language
+
+ if not lid in self._languages:
+ piter = self.model.append(None, [language])
+
+ parent = Gtk.TreeRowReference.new(self.model, self.model.get_path(piter))
+ self._languages[lid] = parent
+ else:
+ parent = self._languages[lid]
+
+ piter = self.model.get_iter(parent.get_path())
+ child = self.model.append(piter, [tool])
+
+ if not tool in self._tool_rows:
+ self._tool_rows[tool] = []
+
+ self._tool_rows[tool].append(Gtk.TreeRowReference.new(self.model, self.model.get_path(child)))
+ return child
+
+ def add_tool(self, tool):
+ manager = GtkSource.LanguageManager()
+ ret = None
+
+ for lang in tool.languages:
+ l = manager.get_language(lang)
+
+ if l:
+ ret = self.add_tool_to_language(tool, l)
+ elif lang == 'plain':
+ ret = self.add_tool_to_language(tool, 'plain')
+
+ if not ret:
+ ret = self.add_tool_to_language(tool, None)
+
+ self.add_accelerator(tool)
+ return ret
+
+ def __init_tools_model(self):
+ self.tools = ToolLibrary()
+ self.current_node = None
+ self.script_hash = None
+ self.accelerators = dict()
+
+ self.model = Gtk.TreeStore(object)
+ self.view.set_model(self.model)
+
+ for tool in self.tools.tree.tools:
+ self.add_tool(tool)
+
+ self.model.set_default_sort_func(self.sort_tools)
+ self.model.set_sort_column_id(-1, Gtk.SortType.ASCENDING)
+
+ def sort_tools(self, model, iter1, iter2, user_data=None):
+ # For languages, sort All before everything else, otherwise alphabetical
+ t1 = model.get_value(iter1, self.TOOL_COLUMN)
+ t2 = model.get_value(iter2, self.TOOL_COLUMN)
+
+ if model.iter_parent(iter1) is None:
+ if t1 is None:
+ return -1
+
+ if t2 is None:
+ return 1
+
+ def lang_name(lang):
+ if isinstance(lang, GtkSource.Language):
+ return lang.get_name()
+ else:
+ return _('Plain Text')
+
+ n1 = lang_name(t1)
+ n2 = lang_name(t2)
+ else:
+ n1 = t1.name
+ n2 = t2.name
+
+ if n1.lower() < n2.lower():
+ return -1
+ elif n1.lower() > n2.lower():
+ return 1
+ else:
+ return 0
+
+ def __init_tools_view(self):
+ # Tools column
+ column = Gtk.TreeViewColumn('Tools')
+ renderer = Gtk.CellRendererText()
+ column.pack_start(renderer, False)
+ renderer.set_property('editable', True)
+ self.view.append_column(column)
+
+ column.set_cell_data_func(renderer, self.get_cell_data_cb, None)
+
+ renderer.connect('edited', self.on_view_label_cell_edited)
+ renderer.connect('editing-started', self.on_view_label_cell_editing_started)
+
+ self.selection_changed_id = self.view.get_selection().connect('changed', self.on_view_selection_changed, None)
+
+ def __init_combobox(self, name):
+ combo = self[name]
+ combo.set_active(0)
+
+ # Convenience function to get an object from its name
+ def __getitem__(self, key):
+ return self.ui.get_object(key)
+
+ def set_active_by_name(self, combo_name, option_name):
+ combo = self[combo_name]
+ model = combo.get_model()
+ piter = model.get_iter_first()
+ while piter is not None:
+ if model.get_value(piter, self.NAME_COLUMN) == option_name:
+ combo.set_active_iter(piter)
+ return True
+ piter = model.iter_next(piter)
+ return False
+
+ def get_selected_tool(self):
+ model, piter = self.view.get_selection().get_selected()
+
+ if piter is not None:
+ tool = model.get_value(piter, self.TOOL_COLUMN)
+
+ if not isinstance(tool, Tool):
+ tool = None
+
+ return piter, tool
+ else:
+ return None, None
+
+ def compute_hash(self, string):
+ return hashlib.md5(string.encode('utf-8')).hexdigest()
+
+ def save_current_tool(self):
+ if self.current_node is None:
+ return
+
+ if self.current_node.filename is None:
+ self.current_node.autoset_filename()
+
+ def combo_value(o, name):
+ combo = o[name]
+ return combo.get_model().get_value(combo.get_active_iter(), self.NAME_COLUMN)
+
+ self.current_node.input = combo_value(self, 'input')
+ self.current_node.output = combo_value(self, 'output')
+ self.current_node.applicability = combo_value(self, 'applicability')
+ self.current_node.save_files = combo_value(self, 'save-files')
+
+ buf = self['commands'].get_buffer()
+ (start, end) = buf.get_bounds()
+ script = buf.get_text(start, end, False)
+ h = self.compute_hash(script)
+ if h != self.script_hash:
+ # script has changed -> save it
+ self.current_node.save_with_script([line + "\n" for line in script.splitlines()])
+ self.script_hash = h
+ else:
+ self.current_node.save()
+
+ self.update_remove_revert()
+
+ def clear_fields(self):
+ self['accelerator'].set_text('')
+
+ buf = self['commands'].get_buffer()
+ buf.begin_not_undoable_action()
+ buf.set_text('')
+ buf.end_not_undoable_action()
+
+ for nm in ('input', 'output', 'applicability', 'save-files'):
+ self[nm].set_active(0)
+
+ self['languages_label'].set_text(_('All Languages'))
+
+ def fill_languages_button(self):
+ if not self.current_node or not self.current_node.languages:
+ self['languages_label'].set_text(_('All Languages'))
+ else:
+ manager = GtkSource.LanguageManager()
+ langs = []
+
+ for lang in self.current_node.languages:
+ if lang == 'plain':
+ langs.append(_('Plain Text'))
+ else:
+ l = manager.get_language(lang)
+
+ if l:
+ langs.append(l.get_name())
+
+ self['languages_label'].set_text(', '.join(langs))
+
+ def fill_fields(self):
+ self.update_accelerator_label()
+
+ buf = self['commands'].get_buffer()
+ script = default(''.join(self.current_node.get_script()), '')
+
+ buf.begin_not_undoable_action()
+ buf.set_text(script)
+ buf.end_not_undoable_action()
+
+ self.script_hash = self.compute_hash(script)
+
+ contenttype, uncertain = Gio.content_type_guess(None, script.encode('utf-8'))
+ lmanager = GtkSource.LanguageManager.get_default()
+ language = lmanager.guess_language(None, contenttype)
+
+ if language is not None:
+ buf.set_language(language)
+ buf.set_highlight_syntax(True)
+ else:
+ buf.set_highlight_syntax(False)
+
+ for nm in ('input', 'output', 'applicability', 'save-files'):
+ model = self[nm].get_model()
+ piter = model.get_iter_first()
+ self.set_active_by_name(nm,
+ default(self.current_node.__getattribute__(nm.replace('-', '_')),
+ model.get_value(piter, self.NAME_COLUMN)))
+
+ self.fill_languages_button()
+
+ def update_accelerator_label(self):
+ if self.current_node.shortcut:
+ key, mods = Gtk.accelerator_parse(self.current_node.shortcut)
+ label = Gtk.accelerator_get_label(key, mods)
+ self['accelerator'].set_text(label)
+ else:
+ self['accelerator'].set_text('')
+
+ def update_remove_revert(self):
+ piter, node = self.get_selected_tool()
+
+ removable = node is not None and node.is_local()
+
+ self['remove-tool-button'].set_sensitive(removable)
+ self['revert-tool-button'].set_sensitive(removable)
+
+ if node is not None and node.is_global():
+ self['remove-tool-button'].hide()
+ self['revert-tool-button'].show()
+ else:
+ self['remove-tool-button'].show()
+ self['revert-tool-button'].hide()
+
+ def do_update(self):
+ self.update_remove_revert()
+
+ piter, node = self.get_selected_tool()
+ self.current_node = node
+
+ if node is not None:
+ self.fill_fields()
+ self['tool-grid'].set_sensitive(True)
+ else:
+ self.clear_fields()
+ self['tool-grid'].set_sensitive(False)
+
+ def language_id_from_iter(self, piter):
+ if not piter:
+ return None
+
+ tool = self.model.get_value(piter, self.TOOL_COLUMN)
+
+ if isinstance(tool, Tool):
+ piter = self.model.iter_parent(piter)
+ tool = self.model.get_value(piter, self.TOOL_COLUMN)
+
+ if isinstance(tool, GtkSource.Language):
+ return tool.get_id()
+ elif tool:
+ return 'plain'
+
+ return None
+
+ def selected_language_id(self):
+ # Find current language if there is any
+ model, piter = self.view.get_selection().get_selected()
+
+ return self.language_id_from_iter(piter)
+
+ def on_add_tool_button_clicked(self, button):
+ self.save_current_tool()
+
+ # block handlers while inserting a new item
+ self.view.get_selection().handler_block(self.selection_changed_id)
+
+ self.current_node = Tool(self.tools.tree);
+ self.current_node.name = _('New tool')
+ self.tools.tree.tools.append(self.current_node)
+
+ lang = self.selected_language_id()
+
+ if lang:
+ self.current_node.languages = [lang]
+
+ piter = self.add_tool(self.current_node)
+
+ self.view.set_cursor(self.model.get_path(piter),
+ self.view.get_column(self.TOOL_COLUMN),
+ True)
+ self.fill_fields()
+
+ self['tool-grid'].set_sensitive(True)
+ self.view.get_selection().handler_unblock(self.selection_changed_id)
+
+ def tool_changed(self, tool, refresh=False):
+ for row in self._tool_rows[tool]:
+ self.model.set_value(self.model.get_iter(row.get_path()),
+ self.TOOL_COLUMN,
+ tool)
+
+ if refresh and tool == self.current_node:
+ self.fill_fields()
+
+ self.update_remove_revert()
+
+ def on_remove_tool_button_clicked(self, button):
+ piter, node = self.get_selected_tool()
+
+ if not node:
+ return
+
+ if node.is_global():
+ shortcut = node.shortcut
+
+ if node.parent.revert_tool(node):
+ self.remove_accelerator(node, shortcut)
+ self.add_accelerator(node)
+
+ self['revert-tool-button'].set_sensitive(False)
+ self.fill_fields()
+
+ self.tool_changed(node)
+ else:
+ parent = self.model.iter_parent(piter)
+ language = self.language_id_from_iter(parent)
+
+ self.model.remove(piter)
+
+ if language in node.languages:
+ node.languages.remove(language)
+
+ self._tool_rows[node] = [x for x in self._tool_rows[node] if x.valid()]
+
+ if not self._tool_rows[node]:
+ del self._tool_rows[node]
+
+ if node.parent.delete_tool(node):
+ self.remove_accelerator(node)
+ self.current_node = None
+ self.script_hash = None
+
+ if self.model.iter_is_valid(piter):
+ self.view.set_cursor(self.model.get_path(piter),
+ self.view.get_column(self.TOOL_COLUMN),
+ False)
+
+ self.view.grab_focus()
+
+ path = self._languages[language].get_path()
+ parent = self.model.get_iter(path)
+
+ if not self.model.iter_has_child(parent):
+ self.model.remove(parent)
+ del self._languages[language]
+
+ def on_view_label_cell_edited(self, cell, path, new_text):
+ if new_text != '':
+ piter = self.model.get_iter(path)
+ tool = self.model.get_value(piter, self.TOOL_COLUMN)
+
+ tool.name = new_text
+
+ self.save_current_tool()
+ self.tool_changed(tool)
+
+ def on_view_label_cell_editing_started(self, renderer, editable, path):
+ piter = self.model.get_iter(path)
+ tool = self.model.get_value(piter, self.TOOL_COLUMN)
+
+ if isinstance(editable, Gtk.Entry):
+ editable.set_text(tool.name)
+ editable.grab_focus()
+
+ def on_view_selection_changed(self, selection, userdata):
+ self.save_current_tool()
+ self.do_update()
+
+ def accelerator_collision(self, name, node):
+ if not name in self.accelerators:
+ return []
+
+ ret = []
+
+ for other in self.accelerators[name]:
+ if not other.languages or not node.languages:
+ ret.append(other)
+ continue
+
+ for lang in other.languages:
+ if lang in node.languages:
+ ret.append(other)
+ continue
+
+ return ret
+
+ def set_accelerator(self, keyval, mod):
+ # Check whether accelerator already exists
+ self.remove_accelerator(self.current_node)
+
+ name = Gtk.accelerator_name(keyval, mod)
+
+ if name == '':
+ self.current_node.shorcut = None
+ self.save_current_tool()
+ return True
+
+ col = self.accelerator_collision(name, self.current_node)
+
+ if col:
+ dialog = Gtk.MessageDialog(self.dialog,
+ Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.ERROR,
+ Gtk.ButtonsType.CLOSE,
+ _('This accelerator is already bound to %s') % (', '.join(map(lambda x: x.name, col)),))
+
+ dialog.run()
+ dialog.destroy()
+
+ self.add_accelerator(self.current_node)
+ return False
+
+ self.current_node.shortcut = name
+ self.add_accelerator(self.current_node)
+ self.save_current_tool()
+
+ return True
+
+ def on_accelerator_key_press(self, entry, event):
+ mask = event.state & Gtk.accelerator_get_default_mod_mask()
+
+ if event.keyval == Gdk.KEY_Escape:
+ self.update_accelerator_label()
+ self['commands'].grab_focus()
+ return True
+ elif event.keyval == Gdk.KEY_BackSpace:
+ return False
+ elif event.keyval in range(Gdk.KEY_F1, Gdk.KEY_F12 + 1):
+ # New accelerator
+ if self.set_accelerator(event.keyval, mask):
+ self.update_accelerator_label()
+ self['commands'].grab_focus()
+
+ # Capture all `normal characters`
+ return True
+ elif Gdk.keyval_to_unicode(event.keyval):
+ if mask:
+ # New accelerator
+ if self.set_accelerator(event.keyval, mask):
+ self.update_accelerator_label()
+ self['commands'].grab_focus()
+ # Capture all `normal characters`
+ return True
+ else:
+ return False
+
+ def on_accelerator_focus_in(self, entry, event):
+ if self.current_node is None:
+ return
+ if self.current_node.shortcut:
+ entry.set_text(_('Type a new accelerator, or press Backspace to clear'))
+ else:
+ entry.set_text(_('Type a new accelerator'))
+
+ def on_accelerator_focus_out(self, entry, event):
+ if self.current_node is not None:
+ self.update_accelerator_label()
+ self.tool_changed(self.current_node)
+
+ def on_accelerator_backspace(self, entry):
+ entry.set_text('')
+ self.remove_accelerator(self.current_node)
+ self.current_node.shortcut = None
+ self['commands'].grab_focus()
+
+ def on_tool_manager_dialog_delete_event(self, dialog, event):
+ self.save_current_tool()
+ return False
+
+ def on_tool_manager_dialog_focus_out(self, dialog, event):
+ self.save_current_tool()
+ self.emit('tools-updated')
+
+ def on_tool_manager_dialog_configure_event(self, dialog, event):
+ if dialog.get_realized():
+ alloc = dialog.get_allocation()
+ self._size = (alloc.width, alloc.height)
+
+ def on_applicability_changed(self, combo):
+ applicability = combo.get_model().get_value(combo.get_active_iter(),
+ self.NAME_COLUMN)
+
+ if applicability == 'always':
+ if self.current_node is not None:
+ self.current_node.languages = []
+
+ self.fill_languages_button()
+
+ self['languages_button'].set_sensitive(applicability != 'always')
+
+ def get_cell_data_cb(self, column, cell, model, piter, user_data=None):
+ tool = model.get_value(piter, self.TOOL_COLUMN)
+
+ if tool is None or not isinstance(tool, Tool):
+ if tool is None:
+ label = _('All Languages')
+ elif not isinstance(tool, GtkSource.Language):
+ label = _('Plain Text')
+ else:
+ label = tool.get_name()
+
+ markup = saxutils.escape(label)
+ editable = False
+ else:
+ escaped = saxutils.escape(tool.name)
+
+ if tool.shortcut:
+ key, mods = Gtk.accelerator_parse(tool.shortcut)
+ label = Gtk.accelerator_get_label(key, mods)
+ markup = '%s (<b>%s</b>)' % (escaped, label)
+ else:
+ markup = escaped
+
+ editable = True
+
+ cell.set_properties(markup=markup, editable=editable)
+
+ def tool_in_language(self, tool, lang):
+ if not lang in self._languages:
+ return False
+
+ ref = self._languages[lang]
+ parent = ref.get_path()
+
+ for row in self._tool_rows[tool]:
+ path = row.get_path()
+
+ if path.get_indices()[0] == parent.get_indices()[0]:
+ return True
+
+ return False
+
+ def update_languages(self, popup):
+ self.current_node.languages = popup.languages()
+ self.fill_languages_button()
+
+ piter, node = self.get_selected_tool()
+ ret = None
+
+ if node:
+ ref = Gtk.TreeRowReference.new(self.model, self.model.get_path(piter))
+
+ # Update languages, make sure to inhibit selection change stuff
+ self.view.get_selection().handler_block(self.selection_changed_id)
+
+ # Remove all rows that are no longer
+ for row in list(self._tool_rows[self.current_node]):
+ piter = self.model.get_iter(row.get_path())
+ language = self.language_id_from_iter(piter)
+
+ if (not language and not self.current_node.languages) or \
+ (language in self.current_node.languages):
+ continue
+
+ # Remove from language
+ self.model.remove(piter)
+ self._tool_rows[self.current_node].remove(row)
+
+ # If language is empty, remove it
+ parent = self.model.get_iter(self._languages[language].get_path())
+
+ if not self.model.iter_has_child(parent):
+ self.model.remove(parent)
+ del self._languages[language]
+
+ # Now, add for any that are new
+ manager = GtkSource.LanguageManager()
+
+ for lang in self.current_node.languages:
+ if not self.tool_in_language(self.current_node, lang):
+ l = manager.get_language(lang)
+
+ if not l:
+ l = 'plain'
+
+ self.add_tool_to_language(self.current_node, l)
+
+ if not self.current_node.languages and not self.tool_in_language(self.current_node, None):
+ self.add_tool_to_language(self.current_node, None)
+
+ # Check if we can still keep the current
+ if not ref or not ref.valid():
+ # Change selection to first language
+ path = self._tool_rows[self.current_node][0].get_path()
+ piter = self.model.get_iter(path)
+ parent = self.model.iter_parent(piter)
+
+ # Expand parent, select child and scroll to it
+ self.view.expand_row(self.model.get_path(parent), False)
+ self.view.get_selection().select_path(path)
+ self.view.set_cursor(path, self.view.get_column(self.TOOL_COLUMN), False)
+
+ self.view.get_selection().handler_unblock(self.selection_changed_id)
+
+ def on_languages_button_clicked(self, button):
+ popup = LanguagesPopup(button, self.current_node.languages)
+ popup.show()
+ popup.connect('closed', self.update_languages)
+
+# ex:et:ts=4:
diff --git a/plugins/externaltools/tools/meson.build b/plugins/externaltools/tools/meson.build
new file mode 100644
index 0000000..bd623cf
--- /dev/null
+++ b/plugins/externaltools/tools/meson.build
@@ -0,0 +1,36 @@
+externaltools_sources = files(
+ '__init__.py',
+ 'appactivatable.py',
+ 'capture.py',
+ 'filelookup.py',
+ 'functions.py',
+ 'library.py',
+ 'linkparsing.py',
+ 'manager.py',
+ 'outputpanel.py',
+ 'windowactivatable.py',
+)
+
+install_data(
+ externaltools_sources,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ 'externaltools',
+ )
+)
+
+externaltools_data = files(
+ 'outputpanel.ui',
+ 'tools.ui',
+)
+
+install_data(
+ externaltools_data,
+ install_dir: join_paths(
+ pkgdatadir,
+ 'plugins',
+ 'externaltools',
+ 'ui',
+ )
+)
diff --git a/plugins/externaltools/tools/outputpanel.py b/plugins/externaltools/tools/outputpanel.py
new file mode 100644
index 0000000..e9fc241
--- /dev/null
+++ b/plugins/externaltools/tools/outputpanel.py
@@ -0,0 +1,247 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net>
+# Copyright (C) 2010 Per Arneng <per.arneng@anyplanet.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+__all__ = ('OutputPanel', 'UniqueById')
+
+import os
+from weakref import WeakKeyDictionary
+from .capture import *
+import re
+from . import linkparsing
+from . import filelookup
+from gi.repository import GLib, Gio, Gdk, Gtk, Pango, Gedit
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class UniqueById:
+ __shared_state = WeakKeyDictionary()
+
+ def __init__(self, i):
+ if i in self.__class__.__shared_state:
+ self.__dict__ = self.__class__.__shared_state[i]
+ return True
+ else:
+ self.__class__.__shared_state[i] = self.__dict__
+ return False
+
+ def states(self):
+ return self.__class__.__shared_state
+
+
+class OutputPanel(UniqueById):
+ def __init__(self, datadir, window):
+ if UniqueById.__init__(self, window):
+ return
+
+ callbacks = {
+ 'on_stop_clicked': self.on_stop_clicked,
+ 'on_view_visibility_notify_event': self.on_view_visibility_notify_event,
+ 'on_view_motion_notify_event': self.on_view_motion_notify_event
+ }
+
+ self.profile_settings = self.get_profile_settings()
+ self.profile_settings.connect("changed", self.font_changed)
+ self.system_settings = Gio.Settings.new("org.gnome.desktop.interface")
+ self.system_settings.connect("changed::monospace-font-name", self.font_changed)
+
+ self.window = window
+ self.ui = Gtk.Builder()
+ self.ui.add_from_file(os.path.join(datadir, 'ui', 'outputpanel.ui'))
+ self.ui.connect_signals(callbacks)
+ self['view'].connect('button-press-event', self.on_view_button_press_event)
+
+ self.panel = self["output-panel"]
+ self.font_changed()
+
+ buffer = self['view'].get_buffer()
+
+ self.normal_tag = buffer.create_tag('normal')
+
+ self.error_tag = buffer.create_tag('error')
+ self.error_tag.set_property('foreground', 'red')
+
+ self.italic_tag = buffer.create_tag('italic')
+ self.italic_tag.set_property('style', Pango.Style.OBLIQUE)
+
+ self.bold_tag = buffer.create_tag('bold')
+ self.bold_tag.set_property('weight', Pango.Weight.BOLD)
+
+ self.invalid_link_tag = buffer.create_tag('invalid_link')
+
+ self.link_tag = buffer.create_tag('link')
+ self.link_tag.set_property('underline', Pango.Underline.SINGLE)
+
+ self.link_cursor = Gdk.Cursor.new(Gdk.CursorType.HAND2)
+ self.normal_cursor = Gdk.Cursor.new(Gdk.CursorType.XTERM)
+
+ self.process = None
+
+ self.links = []
+
+ self.link_parser = linkparsing.LinkParser()
+ self.file_lookup = filelookup.FileLookup(window)
+
+ def get_profile_settings(self):
+ #FIXME return either the gnome-terminal settings or the gedit one
+ return Gio.Settings.new("org.gnome.gedit.plugins.externaltools")
+
+ def font_changed(self, settings=None, key=None):
+ if self.profile_settings.get_boolean("use-system-font"):
+ font = self.system_settings.get_string("monospace-font-name")
+ else:
+ font = self.profile_settings.get_string("font")
+
+ font_desc = Pango.font_description_from_string(font)
+
+ self["view"].override_font(font_desc)
+
+ def set_process(self, process):
+ self.process = process
+
+ def __getitem__(self, key):
+ # Convenience function to get an object from its name
+ return self.ui.get_object(key)
+
+ def on_stop_clicked(self, widget, *args):
+ if self.process is not None:
+ self.write("\n" + _('Stopped.') + "\n",
+ self.italic_tag)
+ self.process.stop(-1)
+
+ def scroll_to_end(self):
+ iter = self['view'].get_buffer().get_end_iter()
+ self['view'].scroll_to_iter(iter, 0.0, False, 0.5, 0.5)
+ return False # don't requeue this handler
+
+ def clear(self):
+ self['view'].get_buffer().set_text("")
+ self.links = []
+
+ def visible(self):
+ panel = self.window.get_bottom_panel()
+ return panel.props.visible and panel.props.visible_child == self.panel
+
+ def write(self, text, tag=None):
+ buffer = self['view'].get_buffer()
+
+ end_iter = buffer.get_end_iter()
+ insert = buffer.create_mark(None, end_iter, True)
+
+ if tag is None:
+ buffer.insert(end_iter, text)
+ else:
+ buffer.insert_with_tags(end_iter, text, tag)
+
+ # find all links and apply the appropriate tag for them
+ links = self.link_parser.parse(text)
+ for lnk in links:
+ insert_iter = buffer.get_iter_at_mark(insert)
+ lnk.start = insert_iter.get_offset() + lnk.start
+ lnk.end = insert_iter.get_offset() + lnk.end
+
+ start_iter = buffer.get_iter_at_offset(lnk.start)
+ end_iter = buffer.get_iter_at_offset(lnk.end)
+
+ tag = None
+
+ # if the link points to an existing file then it is a valid link
+ if self.file_lookup.lookup(lnk.path) is not None:
+ self.links.append(lnk)
+ tag = self.link_tag
+ else:
+ tag = self.invalid_link_tag
+
+ buffer.apply_tag(tag, start_iter, end_iter)
+
+ buffer.delete_mark(insert)
+ GLib.idle_add(self.scroll_to_end)
+
+ def show(self):
+ panel = self.window.get_bottom_panel()
+ panel.props.visible_child = self.panel
+ panel.show()
+
+ def update_cursor_style(self, view, x, y):
+ if self.get_link_at_location(view, x, y) is not None:
+ cursor = self.link_cursor
+ else:
+ cursor = self.normal_cursor
+
+ view.get_window(Gtk.TextWindowType.TEXT).set_cursor(cursor)
+
+ def on_view_motion_notify_event(self, view, event):
+ if event.window == view.get_window(Gtk.TextWindowType.TEXT):
+ self.update_cursor_style(view, int(event.x), int(event.y))
+
+ return False
+
+ def on_view_visibility_notify_event(self, view, event):
+ if event.window == view.get_window(Gtk.TextWindowType.TEXT):
+ win, x, y, flags = event.window.get_pointer()
+ self.update_cursor_style(view, x, y)
+
+ return False
+
+ def idle_grab_focus(self):
+ self.window.get_active_view().grab_focus()
+ return False
+
+ def get_link_at_location(self, view, x, y):
+ """
+ Get the link under a specified x,y coordinate. If no link exists then
+ None is returned.
+ """
+
+ # get the offset within the buffer from the x,y coordinates
+ buff_x, buff_y = view.window_to_buffer_coords(Gtk.TextWindowType.TEXT, x, y)
+ (over_text, iter_at_xy) = view.get_iter_at_location(buff_x, buff_y)
+ if not over_text:
+ return None
+ offset = iter_at_xy.get_offset()
+
+ # find the first link that contains the offset
+ for lnk in self.links:
+ if offset >= lnk.start and offset <= lnk.end:
+ return lnk
+
+ # no link was found at x,y
+ return None
+
+ def on_view_button_press_event(self, view, event):
+ if event.button != 1 or event.type != Gdk.EventType.BUTTON_PRESS or \
+ event.window != view.get_window(Gtk.TextWindowType.TEXT):
+ return False
+
+ link = self.get_link_at_location(view, int(event.x), int(event.y))
+ if link is None:
+ return False
+
+ gfile = self.file_lookup.lookup(link.path)
+
+ if gfile:
+ Gedit.commands_load_location(self.window, gfile, None, link.line_nr, link.col_nr)
+ GLib.idle_add(self.idle_grab_focus)
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/outputpanel.ui b/plugins/externaltools/tools/outputpanel.ui
new file mode 100644
index 0000000..4c163c2
--- /dev/null
+++ b/plugins/externaltools/tools/outputpanel.ui
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.6 -->
+ <object class="GtkOverlay" id="output-panel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkTextView" id="view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="wrap_mode">word</property>
+ <property name="cursor_visible">False</property>
+ <property name="accepts_tab">False</property>
+ <signal name="visibility-notify-event" handler="on_view_visibility_notify_event" swapped="no"/>
+ <signal name="motion-notify-event" handler="on_view_motion_notify_event" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkButton" id="stop">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="valign">end</property>
+ <property name="halign">end</property>
+ <property name="margin_bottom">2</property>
+ <property name="margin_end">2</property>
+ <property name="tooltip_text" translatable="yes">Stop Tool</property>
+ <signal name="clicked" handler="on_stop_clicked" swapped="no"/>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">process-stop-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/plugins/externaltools/tools/tools.ui b/plugins/externaltools/tools/tools.ui
new file mode 100644
index 0000000..a3f0ab1
--- /dev/null
+++ b/plugins/externaltools/tools/tools.ui
@@ -0,0 +1,548 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.16.0 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GeditDocument" id="commands_buffer"/>
+ <object class="GtkListStore" id="model_applicability">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Always available</col>
+ <col id="1">always</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">All documents</col>
+ <col id="1">all</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">All documents except untitled ones</col>
+ <col id="1">titled</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Local files only</col>
+ <col id="1">local</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Remote files only</col>
+ <col id="1">remote</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Untitled documents only</col>
+ <col id="1">untitled</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model_input">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Nothing</col>
+ <col id="1">nothing</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current document</col>
+ <col id="1">document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current selection</col>
+ <col id="1">selection</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current selection (default to document)</col>
+ <col id="1">selection-document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current line</col>
+ <col id="1">line</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current word</col>
+ <col id="1">word</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model_output">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Nothing</col>
+ <col id="1">nothing</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Display in bottom pane</col>
+ <col id="1">output-panel</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Create new document</col>
+ <col id="1">new-document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Append to current document</col>
+ <col id="1">append-document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Replace current document</col>
+ <col id="1">replace-document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Replace current selection</col>
+ <col id="1">replace-selection</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Insert at cursor position</col>
+ <col id="1">insert</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model_save_files">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Nothing</col>
+ <col id="1">nothing</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current document</col>
+ <col id="1">document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">All documents</col>
+ <col id="1">all</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkWindow" id="tool-manager-dialog">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Manage External Tools</property>
+ <property name="default_width">800</property>
+ <property name="default_height">600</property>
+ <property name="type_hint">dialog</property>
+ <signal name="configure-event" handler="on_tool_manager_dialog_configure_event" swapped="no"/>
+ <signal name="delete-event" handler="on_tool_manager_dialog_delete_event" swapped="no"/>
+ <signal name="focus-out-event" handler="on_tool_manager_dialog_focus_out" swapped="no"/>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="headerbar">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Manage External Tools</property>
+ <property name="show_close_button">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPaned" id="paned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="vexpand">True</property>
+ <property name="position">275</property>
+ <property name="position_set">True</property>
+ <style>
+ <class name="gedit-tool-manager-paned"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <style>
+ <class name="gedit-tool-manager-treeview"/>
+ </style>
+ <child>
+ <object class="GtkTreeView" id="view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="reorderable">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="toolbar1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="toolbar_style">icons</property>
+ <style>
+ <class name="inline-toolbar"/>
+ </style>
+ <property name="icon_size">1</property>
+ <child>
+ <object class="GtkToolButton" id="add-tool-button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Add a new tool</property>
+ <property name="label" translatable="yes">Add Tool</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">list-add-symbolic</property>
+ <signal name="clicked" handler="on_add_tool_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="remove-tool-button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Remove selected tool</property>
+ <property name="label" translatable="yes">Remove Tool</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">list-remove-symbolic</property>
+ <signal name="clicked" handler="on_remove_tool_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="revert-tool-button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Revert tool</property>
+ <property name="label" translatable="yes">Revert Tool</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">edit-undo-symbolic</property>
+ <signal name="clicked" handler="on_remove_tool_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkBox" id="hbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkGrid" id="tool-grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">6</property>
+ <property name="margin_end">6</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Shortcut _key:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">accelerator</property>
+ <property name="hexpand">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="accelerator">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <signal name="backspace" handler="on_accelerator_backspace" swapped="no"/>
+ <signal name="focus-in-event" handler="on_accelerator_focus_in" swapped="no"/>
+ <signal name="focus-out-event" handler="on_accelerator_focus_out" swapped="no"/>
+ <signal name="key-press-event" handler="on_accelerator_key_press" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Save:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">save-files</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="save-files">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model_save_files</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Input:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">input</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="input">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model_input</property>
+ <child>
+ <object class="GtkCellRendererText" id="input_renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Output:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">output</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="output">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model_output</property>
+ <child>
+ <object class="GtkCellRendererText" id="output_renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label23">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Applicability:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">applicability</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkComboBox" id="applicability">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model_applicability</property>
+ <signal name="changed" handler="on_applicability_changed" swapped="no"/>
+ <child>
+ <object class="GtkCellRendererText" id="applicability_renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="languages_button">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <signal name="clicked" handler="on_languages_button_clicked" swapped="no"/>
+ <child>
+ <object class="GtkLabel" id="languages_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">All Languages</property>
+ <property name="ellipsize">end</property>
+ <property name="width_chars">13</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
+ <style>
+ <class name="gedit-tool-manager-view"/>
+ </style>
+ <child>
+ <object class="GeditView" id="commands">
+ <property name="buffer">commands_buffer</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/plugins/externaltools/tools/windowactivatable.py b/plugins/externaltools/tools/windowactivatable.py
new file mode 100644
index 0000000..5949598
--- /dev/null
+++ b/plugins/externaltools/tools/windowactivatable.py
@@ -0,0 +1,141 @@
+# -*- coding: UTF-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+__all__ = ('ExternalToolsPlugin', 'OutputPanel', 'Capture', 'UniqueById')
+
+from gi.repository import GLib, Gio, GObject, Gtk, Gedit
+from .library import ToolLibrary
+from .outputpanel import OutputPanel
+from .capture import Capture
+from .functions import *
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class ToolActions(object):
+ def __init__(self, library, window, panel):
+ super(ToolActions, self).__init__()
+ self._library = library
+ self._window = window
+ self._panel = panel
+ self._action_tools = {}
+
+ self.update()
+
+ def deactivate(self):
+ self.remove()
+
+ def remove(self):
+ for name, tool in self._action_tools.items():
+ self._window.remove_action(name)
+ self._action_tools = {}
+
+ def _insert_directory(self, directory):
+ for tool in sorted(directory.tools, key=lambda x: x.name.lower()):
+ # FIXME: find a better way to share the action name
+ action_name = 'external-tool-%X-%X' % (id(tool), id(tool.name))
+ self._action_tools[action_name] = tool
+
+ action = Gio.SimpleAction(name=action_name)
+ action.connect('activate', capture_menu_action, self._window, self._panel, tool)
+ self._window.add_action(action)
+
+ def update(self):
+ self.remove()
+ self._insert_directory(self._library.tree)
+ self.filter(self._window.get_active_document())
+
+ def filter_language(self, language, item):
+ if not item.languages:
+ return True
+
+ if not language and 'plain' in item.languages:
+ return True
+
+ if language and (language.get_id() in item.languages):
+ return True
+ else:
+ return False
+
+ def filter(self, document):
+ if document is None:
+ titled = False
+ remote = False
+ language = None
+ else:
+ titled = document.get_file().get_location() is not None
+ remote = not document.get_file().is_local()
+ language = document.get_language()
+
+ states = {
+ 'always': True,
+ 'all': document is not None,
+ 'local': titled and not remote,
+ 'remote': titled and remote,
+ 'titled': titled,
+ 'untitled': not titled,
+ }
+
+ for name, tool in self._action_tools.items():
+ action = self._window.lookup_action(name)
+ if action:
+ action.set_enabled(states[tool.applicability] and
+ self.filter_language(language, tool))
+
+
+class WindowActivatable(GObject.Object, Gedit.WindowActivatable):
+ __gtype_name__ = "ExternalToolsWindowActivatable"
+
+ window = GObject.Property(type=Gedit.Window)
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+ self.actions = None
+
+ def do_activate(self):
+ self.window.external_tools_window_activatable = self
+
+ self._library = ToolLibrary()
+
+ # Create output console
+ self._output_buffer = OutputPanel(self.plugin_info.get_data_dir(), self.window)
+
+ self.actions = ToolActions(self._library, self.window, self._output_buffer)
+
+ bottom = self.window.get_bottom_panel()
+ bottom.add_titled(self._output_buffer.panel, "GeditExternalToolsShellOutput", _("Tool Output"))
+
+ def do_update_state(self):
+ if self.actions is not None:
+ self.actions.filter(self.window.get_active_document())
+
+ def do_deactivate(self):
+ self.actions.deactivate()
+ bottom = self.window.get_bottom_panel()
+ bottom.remove(self._output_buffer.panel)
+ self.window.external_tools_window_activatable = None
+
+ def update_actions(self):
+ self.actions.update()
+
+# ex:ts=4:et:
diff --git a/plugins/filebrowser/filebrowser.plugin.desktop.in b/plugins/filebrowser/filebrowser.plugin.desktop.in
new file mode 100644
index 0000000..706168d
--- /dev/null
+++ b/plugins/filebrowser/filebrowser.plugin.desktop.in
@@ -0,0 +1,12 @@
+[Plugin]
+Loader=C
+Module=filebrowser
+IAge=3
+Name=File Browser Panel
+Description=Easy file access from the side panel.
+# TRANSLATORS: Do NOT translate or transliterate this text!
+# This is an icon file name.
+Icon=system-file-manager
+Authors=Jesse van den Kieboom <jesse@icecrew.nl>
+Copyright=Copyright © 2006 Jesse van den Kieboom
+Website=http://www.gedit.org
diff --git a/plugins/filebrowser/gedit-file-bookmarks-store.c b/plugins/filebrowser/gedit-file-bookmarks-store.c
new file mode 100644
index 0000000..df0968d
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-bookmarks-store.c
@@ -0,0 +1,920 @@
+/*
+ * gedit-file-bookmarks-store.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gedit/gedit-utils.h>
+
+#include "gedit-file-bookmarks-store.h"
+#include "gedit-file-browser-utils.h"
+
+struct _GeditFileBookmarksStorePrivate
+{
+ GVolumeMonitor *volume_monitor;
+ GFileMonitor *bookmarks_monitor;
+};
+
+static void remove_node (GtkTreeModel *model,
+ GtkTreeIter *iter);
+
+static void on_fs_changed (GVolumeMonitor *monitor,
+ GObject *object,
+ GeditFileBookmarksStore *model);
+
+static void on_bookmarks_file_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ GeditFileBookmarksStore *model);
+static gboolean find_with_flags (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer obj,
+ guint flags,
+ guint notflags);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBookmarksStore,
+ gedit_file_bookmarks_store,
+ GTK_TYPE_TREE_STORE,
+ 0,
+ G_ADD_PRIVATE_DYNAMIC (GeditFileBookmarksStore))
+
+static void
+gedit_file_bookmarks_store_dispose (GObject *object)
+{
+ GeditFileBookmarksStore *obj = GEDIT_FILE_BOOKMARKS_STORE (object);
+
+ if (obj->priv->volume_monitor != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (obj->priv->volume_monitor,
+ on_fs_changed,
+ obj);
+
+ g_object_unref (obj->priv->volume_monitor);
+ obj->priv->volume_monitor = NULL;
+ }
+
+ g_clear_object (&obj->priv->bookmarks_monitor);
+
+ G_OBJECT_CLASS (gedit_file_bookmarks_store_parent_class)->dispose (object);
+}
+
+static void
+gedit_file_bookmarks_store_class_init (GeditFileBookmarksStoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_file_bookmarks_store_dispose;
+}
+
+static void
+gedit_file_bookmarks_store_class_finalize (GeditFileBookmarksStoreClass *klass)
+{
+}
+
+static void
+gedit_file_bookmarks_store_init (GeditFileBookmarksStore *obj)
+{
+ obj->priv = gedit_file_bookmarks_store_get_instance_private (obj);
+}
+
+/* Private */
+static void
+add_node (GeditFileBookmarksStore *model,
+ GdkPixbuf *pixbuf,
+ const gchar *icon_name,
+ const gchar *name,
+ GObject *obj,
+ guint flags,
+ GtkTreeIter *iter)
+{
+ GtkTreeIter newiter;
+
+ gtk_tree_store_append (GTK_TREE_STORE (model), &newiter, NULL);
+
+ gtk_tree_store_set (GTK_TREE_STORE (model), &newiter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, pixbuf,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON_NAME, icon_name,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, name,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, obj,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, flags,
+ -1);
+
+ if (iter != NULL)
+ *iter = newiter;
+}
+
+static gboolean
+add_file (GeditFileBookmarksStore *model,
+ GFile *file,
+ const gchar *name,
+ guint flags,
+ GtkTreeIter *iter)
+{
+ gboolean native = g_file_is_native (file);
+ gchar *icon_name = NULL;
+ gchar *newname;
+
+ if (native && !g_file_query_exists (file, NULL))
+ return FALSE;
+
+ if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_HOME)
+ icon_name = g_strdup ("user-home-symbolic");
+ else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP)
+ icon_name = g_strdup ("user-desktop-symbolic");
+ else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT)
+ icon_name = g_strdup ("drive-harddisk-symbolic");
+ else
+ {
+ /* getting the icon is a sync get_info call, so we just do it for local files */
+ if (native)
+ icon_name = gedit_file_browser_utils_symbolic_icon_name_from_file (file);
+ else
+ icon_name = g_strdup ("folder-symbolic");
+ }
+
+ if (name == NULL)
+ newname = gedit_file_browser_utils_file_basename (file);
+ else
+ newname = g_strdup (name);
+
+ add_node (model, NULL, icon_name, newname, G_OBJECT (file), flags, iter);
+
+ g_free (icon_name);
+ g_free (newname);
+
+ return TRUE;
+}
+
+static void
+check_mount_separator (GeditFileBookmarksStore *model,
+ guint flags,
+ gboolean added)
+{
+ GtkTreeIter iter;
+ gboolean found = find_with_flags (GTK_TREE_MODEL (model), &iter, NULL,
+ flags | GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR,
+ 0);
+
+ if (added && !found)
+ {
+ /* Add the separator */
+ add_node (model, NULL, NULL, NULL, NULL,
+ flags | GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR,
+ NULL);
+ }
+ else if (!added && found)
+ {
+ remove_node (GTK_TREE_MODEL (model), &iter);
+ }
+}
+
+static void
+init_special_directories (GeditFileBookmarksStore *model)
+{
+ gchar const *path = g_get_home_dir ();
+ GFile *file;
+
+ if (path != NULL)
+ {
+ file = g_file_new_for_path (path);
+ add_file (model,
+ file,
+ _("Home"),
+ GEDIT_FILE_BOOKMARKS_STORE_IS_HOME | GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR,
+ NULL);
+ g_object_unref (file);
+ }
+
+#if defined(G_OS_WIN32) || defined(OS_OSX)
+ path = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
+ if (path != NULL)
+ {
+ file = g_file_new_for_path (path);
+ add_file (model,
+ file,
+ NULL,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP | GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR,
+ NULL);
+ g_object_unref (file);
+ }
+
+ path = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
+ if (path != NULL)
+ {
+ file = g_file_new_for_path (path);
+ add_file (model,
+ file,
+ NULL,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_DOCUMENTS | GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR,
+ NULL);
+ g_object_unref (file);
+ }
+#endif
+
+ file = g_file_new_for_uri ("file:///");
+ add_file (model, file, _("File System"), GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT, NULL);
+ g_object_unref (file);
+
+ check_mount_separator (model, GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT, TRUE);
+}
+
+static void
+get_fs_properties (gpointer fs,
+ gchar **name,
+ gchar **icon_name,
+ guint *flags)
+{
+ GIcon *icon = NULL;
+
+ *flags = GEDIT_FILE_BOOKMARKS_STORE_IS_FS;
+ *name = NULL;
+ *icon_name = NULL;
+
+ if (G_IS_DRIVE (fs))
+ {
+ icon = g_drive_get_symbolic_icon (G_DRIVE (fs));
+ *name = g_drive_get_name (G_DRIVE (fs));
+ *icon_name = gedit_file_browser_utils_name_from_themed_icon (icon);
+
+ *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE;
+ }
+ else if (G_IS_VOLUME (fs))
+ {
+ icon = g_volume_get_symbolic_icon (G_VOLUME (fs));
+ *name = g_volume_get_name (G_VOLUME (fs));
+ *icon_name = gedit_file_browser_utils_name_from_themed_icon (icon);
+
+ *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME;
+ }
+ else if (G_IS_MOUNT (fs))
+ {
+ icon = g_mount_get_symbolic_icon (G_MOUNT (fs));
+ *name = g_mount_get_name (G_MOUNT (fs));
+ *icon_name = gedit_file_browser_utils_name_from_themed_icon (icon);
+
+ *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT;
+ }
+
+ if (icon)
+ g_object_unref (icon);
+}
+
+static void
+add_fs (GeditFileBookmarksStore *model,
+ gpointer fs,
+ guint flags,
+ GtkTreeIter *iter)
+{
+ gchar *icon_name = NULL;
+ gchar *name = NULL;
+ guint fsflags;
+
+ get_fs_properties (fs, &name, &icon_name, &fsflags);
+ add_node (model, NULL, icon_name, name, fs, flags | fsflags, iter);
+
+ g_free (name);
+ g_free (icon_name);
+ check_mount_separator (model, GEDIT_FILE_BOOKMARKS_STORE_IS_FS, TRUE);
+}
+
+static void
+process_volume_cb (GVolume *volume,
+ GeditFileBookmarksStore *model)
+{
+ GMount *mount = g_volume_get_mount (volume);
+ guint flags = GEDIT_FILE_BOOKMARKS_STORE_NONE;
+
+ /* CHECK: should we use the LOCAL/REMOTE thing still? */
+ if (mount)
+ {
+ /* Show mounted volume */
+ add_fs (model, mount, flags, NULL);
+ g_object_unref (mount);
+ }
+ else if (g_volume_can_mount (volume))
+ {
+ /* We also show the unmounted volume here so users can
+ mount it if they want to access it */
+ add_fs (model, volume, flags, NULL);
+ }
+}
+
+static void
+process_drive_novolumes (GeditFileBookmarksStore *model,
+ GDrive *drive)
+{
+ if (g_drive_is_media_removable (drive) &&
+ !g_drive_is_media_check_automatic (drive) &&
+ g_drive_can_poll_for_media (drive))
+ {
+ /* This can be the case for floppy drives or other
+ drives where media detection fails. We show the
+ drive and poll for media when the user activates
+ it */
+ add_fs (model, drive, GEDIT_FILE_BOOKMARKS_STORE_NONE, NULL);
+ }
+}
+
+static void
+process_drive_cb (GDrive *drive,
+ GeditFileBookmarksStore *model)
+{
+ GList *volumes = g_drive_get_volumes (drive);
+
+ if (volumes)
+ {
+ /* Add all volumes for the drive */
+ g_list_foreach (volumes, (GFunc)process_volume_cb, model);
+ g_list_free (volumes);
+ }
+ else
+ {
+ process_drive_novolumes (model, drive);
+ }
+}
+
+static void
+init_drives (GeditFileBookmarksStore *model)
+{
+ GList *drives = g_volume_monitor_get_connected_drives (model->priv->volume_monitor);
+
+ g_list_foreach (drives, (GFunc)process_drive_cb, model);
+ g_list_free_full (drives, g_object_unref);
+}
+
+static void
+process_volume_nodrive_cb (GVolume *volume,
+ GeditFileBookmarksStore *model)
+{
+ GDrive *drive = g_volume_get_drive (volume);
+
+ if (drive)
+ {
+ g_object_unref (drive);
+ return;
+ }
+
+ process_volume_cb (volume, model);
+}
+
+static void
+init_volumes (GeditFileBookmarksStore *model)
+{
+ GList *volumes = g_volume_monitor_get_volumes (model->priv->volume_monitor);
+
+ g_list_foreach (volumes, (GFunc)process_volume_nodrive_cb, model);
+ g_list_free_full (volumes, g_object_unref);
+}
+
+static void
+process_mount_novolume_cb (GMount *mount,
+ GeditFileBookmarksStore *model)
+{
+ GVolume *volume = g_mount_get_volume (mount);
+
+ if (volume)
+ {
+ g_object_unref (volume);
+ }
+ else if (!g_mount_is_shadowed (mount))
+ {
+ /* Add the mount */
+ add_fs (model, mount, GEDIT_FILE_BOOKMARKS_STORE_NONE, NULL);
+ }
+}
+
+static void
+init_mounts (GeditFileBookmarksStore *model)
+{
+ GList *mounts = g_volume_monitor_get_mounts (model->priv->volume_monitor);
+
+ g_list_foreach (mounts, (GFunc)process_mount_novolume_cb, model);
+ g_list_free_full (mounts, g_object_unref);
+}
+
+static void
+init_fs (GeditFileBookmarksStore *model)
+{
+ if (model->priv->volume_monitor == NULL)
+ {
+ const gchar **ptr;
+ const gchar *signals[] = {
+ "drive-connected", "drive-changed", "drive-disconnected",
+ "volume-added", "volume-removed", "volume-changed",
+ "mount-added", "mount-removed", "mount-changed",
+ NULL
+ };
+
+ model->priv->volume_monitor = g_volume_monitor_get ();
+
+ /* Connect signals */
+ for (ptr = signals; *ptr != NULL; ++ptr)
+ {
+ g_signal_connect (model->priv->volume_monitor,
+ *ptr,
+ G_CALLBACK (on_fs_changed), model);
+ }
+ }
+
+ /* First go through all the connected drives */
+ init_drives (model);
+
+ /* Then add all volumes, not associated with a drive */
+ init_volumes (model);
+
+ /* Then finally add all mounts that have no volume */
+ init_mounts (model);
+}
+
+static gboolean
+add_bookmark (GeditFileBookmarksStore *model,
+ gchar const *name,
+ gchar const *uri)
+{
+ GFile *file = g_file_new_for_uri (uri);
+ guint flags = GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK;
+ GtkTreeIter iter;
+ gboolean ret;
+
+ if (g_file_is_native (file))
+ flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_LOCAL_BOOKMARK;
+ else
+ flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK;
+
+ ret = add_file (model, file, name, flags, &iter);
+
+ g_object_unref (file);
+ return ret;
+}
+
+static gchar *
+get_bookmarks_file (void)
+{
+ return g_build_filename (g_get_user_config_dir (), "gtk-3.0", "bookmarks", NULL);
+}
+
+static gchar *
+get_legacy_bookmarks_file (void)
+{
+ return g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL);
+}
+
+static gboolean
+parse_bookmarks_file (GeditFileBookmarksStore *model,
+ const gchar *bookmarks,
+ gboolean *added)
+{
+ GError *error = NULL;
+ gchar *contents;
+ gchar **lines;
+ gchar **line;
+
+ if (!g_file_get_contents (bookmarks, &contents, NULL, &error))
+ {
+ /* The bookmarks file doesn't exist (which is perfectly fine) */
+ g_error_free (error);
+
+ return FALSE;
+ }
+
+ lines = g_strsplit (contents, "\n", 0);
+
+ for (line = lines; *line; ++line)
+ {
+ if (**line)
+ {
+ GFile *location;
+
+ gchar *pos;
+ gchar *name;
+
+ /* CHECK: is this really utf8? */
+ pos = g_utf8_strchr (*line, -1, ' ');
+
+ if (pos != NULL)
+ {
+ *pos = '\0';
+ name = pos + 1;
+ }
+ else
+ {
+ name = NULL;
+ }
+
+ /* the bookmarks file should contain valid
+ * URIs, but paranoia is good */
+ location = g_file_new_for_uri (*line);
+ if (gedit_utils_is_valid_location (location))
+ {
+ *added |= add_bookmark (model, name, *line);
+ }
+ g_object_unref (location);
+ }
+ }
+
+ g_strfreev (lines);
+ g_free (contents);
+
+ /* Add a watch */
+ if (model->priv->bookmarks_monitor == NULL)
+ {
+ GFile *file = g_file_new_for_path (bookmarks);
+
+ model->priv->bookmarks_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
+ g_object_unref (file);
+
+ g_signal_connect (model->priv->bookmarks_monitor,
+ "changed",
+ G_CALLBACK (on_bookmarks_file_changed),
+ model);
+ }
+
+ return TRUE;
+}
+
+static void
+init_bookmarks (GeditFileBookmarksStore *model)
+{
+ gchar *bookmarks = get_bookmarks_file ();
+ gboolean added = FALSE;
+
+ if (!parse_bookmarks_file (model, bookmarks, &added))
+ {
+ g_free (bookmarks);
+
+ /* try the old location (gtk <= 3.4) */
+ bookmarks = get_legacy_bookmarks_file ();
+ parse_bookmarks_file (model, bookmarks, &added);
+ }
+
+ if (added)
+ {
+ /* Bookmarks separator */
+ add_node (model, NULL, NULL, NULL, NULL,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK | GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR,
+ NULL);
+ }
+
+ g_free (bookmarks);
+}
+
+static gint flags_order[] = {
+ GEDIT_FILE_BOOKMARKS_STORE_IS_HOME,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_FS,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK,
+ -1
+};
+
+static gint
+utf8_casecmp (gchar const *s1, const gchar *s2)
+{
+ gchar *n1;
+ gchar *n2;
+ gint result;
+
+ n1 = g_utf8_casefold (s1, -1);
+ n2 = g_utf8_casefold (s2, -1);
+
+ result = g_utf8_collate (n1, n2);
+
+ g_free (n1);
+ g_free (n2);
+
+ return result;
+}
+
+static gint
+bookmarks_compare_names (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b)
+{
+ gchar *n1;
+ gchar *n2;
+ gint result;
+ guint f1;
+ guint f2;
+
+ gtk_tree_model_get (model, a,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &n1,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f1,
+ -1);
+ gtk_tree_model_get (model, b,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &n2,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f2,
+ -1);
+
+ /* do not sort actual bookmarks to keep same order as in nautilus */
+ if ((f1 & GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK) &&
+ (f2 & GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK))
+ {
+ result = 0;
+ }
+ else if (n1 == NULL && n2 == NULL)
+ {
+ result = 0;
+ }
+ else if (n1 == NULL)
+ {
+ result = -1;
+ }
+ else if (n2 == NULL)
+ {
+ result = 1;
+ }
+ else
+ {
+ result = utf8_casecmp (n1, n2);
+ }
+
+ g_free (n1);
+ g_free (n2);
+
+ return result;
+}
+
+static gint
+bookmarks_compare_flags (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b)
+{
+ guint sep = GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR;
+ guint f1;
+ guint f2;
+ gint *flags;
+
+ gtk_tree_model_get (model, a,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f1,
+ -1);
+ gtk_tree_model_get (model, b,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f2,
+ -1);
+
+ for (flags = flags_order; *flags != -1; ++flags)
+ {
+ if ((f1 & *flags) != (f2 & *flags))
+ {
+ if (f1 & *flags)
+ return -1;
+ else
+ return 1;
+ }
+ else if ((f1 & *flags) && (f1 & sep) != (f2 & sep))
+ {
+ if (f1 & sep)
+ return -1;
+ else
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static gint
+bookmarks_compare_func (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer user_data)
+{
+ gint result = bookmarks_compare_flags (model, a, b);
+
+ if (result == 0)
+ result = bookmarks_compare_names (model, a, b);
+
+ return result;
+}
+
+static gboolean
+find_with_flags (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer obj,
+ guint flags,
+ guint notflags)
+{
+ GtkTreeIter child;
+ guint childflags = 0;
+ GObject *childobj;
+ gboolean fequal;
+
+ if (!gtk_tree_model_get_iter_first (model, &child))
+ return FALSE;
+
+ do
+ {
+ gtk_tree_model_get (model,
+ &child,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, &childobj,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &childflags,
+ -1);
+
+ fequal = (obj == childobj);
+
+ if (childobj)
+ g_object_unref (childobj);
+
+ if ((obj == NULL || fequal) &&
+ (childflags & flags) == flags &&
+ !(childflags & notflags))
+ {
+ *iter = child;
+ return TRUE;
+ }
+ }
+ while (gtk_tree_model_iter_next (model, &child));
+
+ return FALSE;
+}
+
+static void
+remove_node (GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ guint flags;
+
+ gtk_tree_model_get (model,
+ iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!(flags & GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR) &&
+ flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS)
+ {
+ check_mount_separator (GEDIT_FILE_BOOKMARKS_STORE (model),
+ flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS,
+ FALSE);
+ }
+
+ gtk_tree_store_remove (GTK_TREE_STORE (model), iter);
+}
+
+static void
+remove_bookmarks (GeditFileBookmarksStore *model)
+{
+ GtkTreeIter iter;
+
+ while (find_with_flags (GTK_TREE_MODEL (model), &iter, NULL,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK,
+ 0))
+ {
+ remove_node (GTK_TREE_MODEL (model), &iter);
+ }
+}
+
+static void
+initialize_fill (GeditFileBookmarksStore *model)
+{
+ init_special_directories (model);
+ init_fs (model);
+ init_bookmarks (model);
+}
+
+/* Public */
+GeditFileBookmarksStore *
+gedit_file_bookmarks_store_new (void)
+{
+ GeditFileBookmarksStore *model;
+ GType column_types[] = {
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_OBJECT,
+ G_TYPE_UINT
+ };
+
+ model = g_object_new (GEDIT_TYPE_FILE_BOOKMARKS_STORE, NULL);
+ gtk_tree_store_set_column_types (GTK_TREE_STORE (model),
+ GEDIT_FILE_BOOKMARKS_STORE_N_COLUMNS,
+ column_types);
+
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model),
+ bookmarks_compare_func,
+ NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+ GTK_SORT_ASCENDING);
+
+ initialize_fill (model);
+
+ return model;
+}
+
+GFile *
+gedit_file_bookmarks_store_get_location (GeditFileBookmarksStore *model,
+ GtkTreeIter *iter)
+{
+ GObject *obj;
+ GFile *file = NULL;
+ guint flags;
+ GFile * ret = NULL;
+ gboolean isfs;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BOOKMARKS_STORE (model), NULL);
+ g_return_val_if_fail (iter != NULL, NULL);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &flags,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, &obj,
+ -1);
+
+ if (obj == NULL)
+ return NULL;
+
+ isfs = (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS);
+
+ if (isfs && (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT))
+ file = g_mount_get_root (G_MOUNT (obj));
+ else if (!isfs)
+ file = (GFile *)g_object_ref (obj);
+
+ g_object_unref (obj);
+
+ if (file)
+ {
+ ret = g_file_dup (file);
+ g_object_unref (file);
+ }
+
+ return ret;
+}
+
+void
+gedit_file_bookmarks_store_refresh (GeditFileBookmarksStore *model)
+{
+ gtk_tree_store_clear (GTK_TREE_STORE (model));
+ initialize_fill (model);
+}
+
+static void
+on_fs_changed (GVolumeMonitor *monitor,
+ GObject *object,
+ GeditFileBookmarksStore *model)
+{
+ GtkTreeModel *tree_model = GTK_TREE_MODEL (model);
+ guint flags = GEDIT_FILE_BOOKMARKS_STORE_IS_FS;
+ guint noflags = GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR;
+ GtkTreeIter iter;
+
+ /* clear all fs items */
+ while (find_with_flags (tree_model, &iter, NULL, flags, noflags))
+ remove_node (tree_model, &iter);
+
+ /* then reinitialize */
+ init_fs (model);
+}
+
+static void
+on_bookmarks_file_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ GeditFileBookmarksStore *model)
+{
+ switch (event_type)
+ {
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ case G_FILE_MONITOR_EVENT_CREATED:
+ /* Re-initialize bookmarks */
+ remove_bookmarks (model);
+ init_bookmarks (model);
+ break;
+ /* FIXME: shouldn't we also monitor the directory? */
+ case G_FILE_MONITOR_EVENT_DELETED:
+ /* Remove bookmarks */
+ remove_bookmarks (model);
+ g_object_unref (monitor);
+ model->priv->bookmarks_monitor = NULL;
+ break;
+ default:
+ break;
+ }
+}
+
+void
+_gedit_file_bookmarks_store_register_type (GTypeModule *type_module)
+{
+ gedit_file_bookmarks_store_register_type (type_module);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-bookmarks-store.h b/plugins/filebrowser/gedit-file-bookmarks-store.h
new file mode 100644
index 0000000..19de53b
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-bookmarks-store.h
@@ -0,0 +1,91 @@
+/*
+ * gedit-file-bookmarks-store.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_FILE_BOOKMARKS_STORE_H
+#define GEDIT_FILE_BOOKMARKS_STORE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define GEDIT_TYPE_FILE_BOOKMARKS_STORE (gedit_file_bookmarks_store_get_type ())
+#define GEDIT_FILE_BOOKMARKS_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStore))
+#define GEDIT_FILE_BOOKMARKS_STORE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStore const))
+#define GEDIT_FILE_BOOKMARKS_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStoreClass))
+#define GEDIT_IS_FILE_BOOKMARKS_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE))
+#define GEDIT_IS_FILE_BOOKMARKS_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BOOKMARKS_STORE))
+#define GEDIT_FILE_BOOKMARKS_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStoreClass))
+
+typedef struct _GeditFileBookmarksStore GeditFileBookmarksStore;
+typedef struct _GeditFileBookmarksStoreClass GeditFileBookmarksStoreClass;
+typedef struct _GeditFileBookmarksStorePrivate GeditFileBookmarksStorePrivate;
+
+enum
+{
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON = 0,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON_NAME,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS,
+ GEDIT_FILE_BOOKMARKS_STORE_N_COLUMNS
+};
+
+enum
+{
+ GEDIT_FILE_BOOKMARKS_STORE_NONE = 0,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR = 1 << 0, /* Separator item */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR = 1 << 1, /* Special user dir */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_HOME = 1 << 2, /* The special Home user directory */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP = 1 << 3, /* The special Desktop user directory */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_DOCUMENTS = 1 << 4, /* The special Documents user directory */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_FS = 1 << 5, /* A mount object */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT = 1 << 6, /* A mount object */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME = 1 << 7, /* A volume object */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE = 1 << 8, /* A drive object */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT = 1 << 9, /* The root file system (file:///) */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK = 1 << 10, /* A gtk bookmark */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK = 1 << 11, /* A remote gtk bookmark */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_LOCAL_BOOKMARK = 1 << 12 /* A local gtk bookmark */
+};
+
+struct _GeditFileBookmarksStore
+{
+ GtkTreeStore parent;
+
+ GeditFileBookmarksStorePrivate *priv;
+};
+
+struct _GeditFileBookmarksStoreClass
+{
+ GtkTreeStoreClass parent_class;
+};
+
+GType gedit_file_bookmarks_store_get_type (void) G_GNUC_CONST;
+
+GeditFileBookmarksStore *gedit_file_bookmarks_store_new (void);
+GFile *gedit_file_bookmarks_store_get_location (GeditFileBookmarksStore *model,
+ GtkTreeIter *iter);
+void gedit_file_bookmarks_store_refresh (GeditFileBookmarksStore *model);
+
+void _gedit_file_bookmarks_store_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BOOKMARKS_STORE_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-enum-register.c.template b/plugins/filebrowser/gedit-file-browser-enum-register.c.template
new file mode 100644
index 0000000..c97ffff
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-enum-register.c.template
@@ -0,0 +1,20 @@
+/*** BEGIN file-header ***/
+void
+gedit_file_browser_enum_and_flag_register_type (GTypeModule * module)
+{
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+ /* Enumerations from "@basename@" */
+
+/*** END file-production ***/
+
+/*** BEGIN enumeration-production ***/
+ register_@enum_name@ (module);
+
+/*** END enumeration-production ***/
+
+/*** BEGIN file-tail ***/
+}
+
+/*** END file-tail ***/
diff --git a/plugins/filebrowser/gedit-file-browser-enum-types-stage1.c.template b/plugins/filebrowser/gedit-file-browser-enum-types-stage1.c.template
new file mode 100644
index 0000000..fd58866
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-enum-types-stage1.c.template
@@ -0,0 +1,45 @@
+/*** BEGIN file-header ***/
+#include "gedit-file-browser-enum-types.h"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+#include "@basename@"
+
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+static GType @enum_name@_type = 0;
+
+static GType
+register_@enum_name@ (GTypeModule *module)
+{
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@,
+ "@VALUENAME@",
+ "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+
+ @enum_name@_type =
+ g_type_module_register_@type@ (module,
+ "@EnumName@",
+ values);
+
+ return @enum_name@_type;
+}
+
+GType
+@enum_name@_get_type (void)
+{
+ return @enum_name@_type;
+}
+
+/*** END value-tail ***/
diff --git a/plugins/filebrowser/gedit-file-browser-enum-types.h.template b/plugins/filebrowser/gedit-file-browser-enum-types.h.template
new file mode 100644
index 0000000..c13167a
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-enum-types.h.template
@@ -0,0 +1,28 @@
+/*** BEGIN file-header ***/
+#ifndef __GEDIT_FILE_BROWSER_ENUM_TYPES_H__
+#define __GEDIT_FILE_BROWSER_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* Enumerations from "@basename@" */
+
+/*** END file-production ***/
+
+/*** BEGIN enumeration-production ***/
+#define GEDIT_TYPE_@ENUMSHORT@ (@enum_name@_get_type())
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+
+/*** END enumeration-production ***/
+
+/*** BEGIN file-tail ***/
+void gedit_file_browser_enum_and_flag_register_type (GTypeModule * module);
+
+G_END_DECLS
+
+#endif /* __GEDIT_FILE_BROWSER_ENUM_TYPES_H__ */
+/*** END file-tail ***/
diff --git a/plugins/filebrowser/gedit-file-browser-error.h b/plugins/filebrowser/gedit-file-browser-error.h
new file mode 100644
index 0000000..89e6ae3
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-error.h
@@ -0,0 +1,41 @@
+/*
+ * gedit-file-browser-error.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_FILE_BROWSER_ERROR_H
+#define GEDIT_FILE_BROWSER_ERROR_H
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GEDIT_FILE_BROWSER_ERROR_NONE,
+ GEDIT_FILE_BROWSER_ERROR_RENAME,
+ GEDIT_FILE_BROWSER_ERROR_DELETE,
+ GEDIT_FILE_BROWSER_ERROR_NEW_FILE,
+ GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY,
+ GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY,
+ GEDIT_FILE_BROWSER_ERROR_SET_ROOT,
+ GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY,
+ GEDIT_FILE_BROWSER_ERROR_NUM
+} GeditFileBrowserError;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_ERROR_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-messages.c b/plugins/filebrowser/gedit-file-browser-messages.c
new file mode 100644
index 0000000..7f25424
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-messages.c
@@ -0,0 +1,1074 @@
+/*
+ * gedit-file-browser-messages.c
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gedit-file-browser-messages.h"
+#include "gedit-file-browser-store.h"
+#include "messages/messages.h"
+
+#include <gedit/gedit-message.h>
+
+#define MESSAGE_OBJECT_PATH "/plugins/filebrowser"
+#define WINDOW_DATA_KEY "GeditFileBrowserMessagesWindowData"
+
+#define BUS_CONNECT(bus, name, data) gedit_message_bus_connect(bus, MESSAGE_OBJECT_PATH, #name, (GeditMessageCallback) message_##name##_cb, data, NULL)
+#define BUS_DISCONNECT(bus, name, data) gedit_message_bus_disconnect_by_func(bus, MESSAGE_OBJECT_PATH, #name, (GeditMessageCallback) message_##name##_cb, data)
+
+typedef struct
+{
+ GeditWindow *window;
+ GeditMessage *message;
+} MessageCacheData;
+
+typedef struct
+{
+ guint row_inserted_id;
+ guint before_row_deleted_id;
+ guint root_changed_id;
+ guint begin_loading_id;
+ guint end_loading_id;
+
+ GeditMessageBus *bus;
+ GeditFileBrowserWidget *widget;
+ GHashTable *row_tracking;
+
+ GHashTable *filters;
+} WindowData;
+
+typedef struct
+{
+ gulong id;
+
+ GeditWindow *window;
+ GeditMessage *message;
+} FilterData;
+
+static WindowData *
+window_data_new (GeditWindow *window,
+ GeditFileBrowserWidget *widget)
+{
+ WindowData *data = g_slice_new (WindowData);
+
+ data->bus = gedit_window_get_message_bus (window);
+ data->widget = widget;
+ data->row_tracking = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)gtk_tree_row_reference_free);
+
+ data->filters = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ NULL);
+
+ g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, data);
+
+ return data;
+}
+
+static WindowData *
+get_window_data (GeditWindow *window)
+{
+ return (WindowData *) (g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY));
+}
+
+static void
+window_data_free (GeditWindow *window)
+{
+ WindowData *data = get_window_data (window);
+
+ g_hash_table_destroy (data->row_tracking);
+ g_hash_table_destroy (data->filters);
+
+ g_slice_free (WindowData, data);
+
+ g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL);
+}
+
+static FilterData *
+filter_data_new (GeditWindow *window,
+ GeditMessage *message)
+{
+ FilterData *data = g_slice_new (FilterData);
+ WindowData *wdata;
+
+ data->window = window;
+ data->id = 0;
+ data->message = message;
+
+ wdata = get_window_data (window);
+
+ g_hash_table_insert (wdata->filters,
+ gedit_message_type_identifier (gedit_message_get_object_path (message),
+ gedit_message_get_method (message)),
+ data);
+
+ return data;
+}
+
+static void
+filter_data_free (FilterData *data)
+{
+ WindowData *wdata = get_window_data (data->window);
+ gchar *identifier;
+
+ identifier = gedit_message_type_identifier (gedit_message_get_object_path (data->message),
+ gedit_message_get_method (data->message));
+
+ g_hash_table_remove (wdata->filters, identifier);
+ g_free (identifier);
+
+ g_object_unref (data->message);
+ g_slice_free (FilterData, data);
+}
+
+static GtkTreePath *
+track_row_lookup (WindowData *data,
+ const gchar *id)
+{
+ GtkTreeRowReference *ref;
+
+ ref = (GtkTreeRowReference *)g_hash_table_lookup (data->row_tracking, id);
+
+ if (!ref)
+ return NULL;
+
+ return gtk_tree_row_reference_get_path (ref);
+}
+
+static void
+message_cache_data_free (MessageCacheData *data)
+{
+ g_object_unref (data->message);
+ g_slice_free (MessageCacheData, data);
+}
+
+static MessageCacheData *
+message_cache_data_new (GeditWindow *window,
+ GeditMessage *message)
+{
+ MessageCacheData *data = g_slice_new (MessageCacheData);
+
+ data->window = window;
+ data->message = message;
+
+ return data;
+}
+
+static void
+message_get_root_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ GeditFileBrowserStore *store;
+ GFile *location;
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+ location = gedit_file_browser_store_get_virtual_root (store);
+
+ if (location)
+ {
+ g_object_set (message, "location", location, NULL);
+ g_object_unref (location);
+ }
+}
+
+static void
+message_set_root_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ GFile *root;
+ GFile *virtual = NULL;
+
+ g_object_get (message, "location", &root, NULL);
+
+ if (!root)
+ {
+ return;
+ }
+
+ g_object_get (message, "virtual", &virtual, NULL);
+
+ if (virtual)
+ {
+ gedit_file_browser_widget_set_root_and_virtual_root (data->widget, root, virtual);
+ }
+ else
+ {
+ gedit_file_browser_widget_set_root (data->widget, root, TRUE);
+ }
+}
+
+static void
+message_set_emblem_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gchar *id = NULL;
+ gchar *emblem = NULL;
+ GtkTreePath *path;
+ GeditFileBrowserStore *store;
+
+ g_object_get (message, "id", &id, "emblem", &emblem, NULL);
+
+ if (!id)
+ {
+ g_free (id);
+ g_free (emblem);
+
+ return;
+ }
+
+ path = track_row_lookup (data, id);
+
+ if (path != NULL)
+ {
+ GtkTreeIter iter;
+ GValue value = G_VALUE_INIT;
+ GdkPixbuf *pixbuf = NULL;
+
+ if (emblem != NULL)
+ {
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ emblem,
+ 10,
+ GTK_ICON_LOOKUP_FORCE_SIZE,
+ NULL);
+ }
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
+ {
+ g_value_init (&value, GDK_TYPE_PIXBUF);
+ g_value_set_object (&value, pixbuf);
+
+ gedit_file_browser_store_set_value (store,
+ &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM,
+ &value);
+
+ g_value_unset (&value);
+ }
+
+ if (pixbuf)
+ {
+ g_object_unref (pixbuf);
+ }
+ }
+
+ g_free (id);
+ g_free (emblem);
+}
+
+static void
+message_set_markup_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gchar *id = NULL;
+ gchar *markup = NULL;
+ GtkTreePath *path;
+ GeditFileBrowserStore *store;
+
+ g_object_get (message, "id", &id, "markup", &markup, NULL);
+
+ if (!id)
+ {
+ g_free (id);
+ g_free (markup);
+
+ return;
+ }
+
+ path = track_row_lookup (data, id);
+
+ if (path != NULL)
+ {
+ GtkTreeIter iter;
+ GValue value = G_VALUE_INIT;
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
+ {
+ if (markup == NULL)
+ {
+ gchar *name;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name,
+ -1);
+ markup = g_markup_escape_text (name, -1);
+
+ g_free (name);
+ }
+
+ g_value_init (&value, G_TYPE_STRING);
+ g_value_set_string (&value, markup);
+
+ gedit_file_browser_store_set_value (store,
+ &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP,
+ &value);
+
+ g_value_unset (&value);
+ }
+
+ gtk_tree_path_free (path);
+ }
+
+ g_free (id);
+ g_free (markup);
+}
+
+static gchar *
+item_id (const gchar *path,
+ GFile *location)
+{
+ gchar *uri;
+ gchar *id;
+
+ uri = g_file_get_uri (location);
+ id = g_strconcat (path, "::", uri, NULL);
+ g_free (uri);
+
+ return id;
+}
+
+static gchar *
+track_row (WindowData *data,
+ GeditFileBrowserStore *store,
+ GtkTreePath *path,
+ GFile *location)
+{
+ GtkTreeRowReference *ref;
+ gchar *id;
+ gchar *pathstr;
+
+ pathstr = gtk_tree_path_to_string (path);
+ id = item_id (pathstr, location);
+
+ ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store), path);
+ g_hash_table_insert (data->row_tracking, g_strdup (id), ref);
+
+ g_free (pathstr);
+
+ return id;
+}
+
+static void
+set_item_message (WindowData *data,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ GeditMessage *message)
+{
+ GeditFileBrowserStore *store;
+ gchar *name;
+ GFile *location;
+ guint flags = 0;
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (location)
+ {
+ gchar *track_id;
+
+ if (path && gtk_tree_path_get_depth (path) != 0)
+ {
+ track_id = track_row (data, store, path, location);
+ }
+ else
+ {
+ track_id = NULL;
+ }
+
+ g_object_set (message,
+ "id", track_id,
+ "location", location,
+ NULL);
+
+ if (gedit_message_has (message, "name"))
+ {
+ g_object_set (message,
+ "name", name,
+ NULL);
+ }
+
+ if (gedit_message_has (message, "is_directory"))
+ {
+ g_object_set (message,
+ "is_directory",
+ FILE_IS_DIR (flags),
+ NULL);
+ }
+
+ g_free (track_id);
+ g_object_unref (location);
+ }
+
+ g_free (name);
+}
+
+static gboolean
+custom_message_filter_func (GeditFileBrowserWidget *widget,
+ GeditFileBrowserStore *store,
+ GtkTreeIter *iter,
+ FilterData *data)
+{
+ WindowData *wdata = get_window_data (data->window);
+ GFile *location;
+ guint flags = 0;
+ gboolean filter = FALSE;
+ GtkTreePath *path;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!location || FILE_IS_DUMMY (flags))
+ return FALSE;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter);
+ set_item_message (wdata, iter, path, data->message);
+ gtk_tree_path_free (path);
+
+ g_object_set (data->message, "filter", filter, NULL);
+
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+ g_object_get (data->message, "filter", &filter, NULL);
+
+ g_object_unref (location);
+
+ return !filter;
+}
+
+static void
+message_add_filter_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ GeditWindow *window)
+{
+ const gchar *object_path = NULL;
+ const gchar *method = NULL;
+ gulong id;
+ GeditMessage *cbmessage;
+ FilterData *filter_data;
+ WindowData *data;
+ GType message_type;
+
+ data = get_window_data (window);
+
+ object_path = gedit_message_get_object_path (message);
+ method = gedit_message_get_method (message);
+
+ message_type = gedit_message_bus_lookup (bus, object_path, method);
+
+ if (message_type == G_TYPE_INVALID)
+ {
+ return;
+ }
+
+ /* Check if the message type has the correct arguments */
+ if (!gedit_message_type_check (message_type, "id", G_TYPE_STRING) ||
+ !gedit_message_type_check (message_type, "location", G_TYPE_FILE) ||
+ !gedit_message_type_check (message_type, "is-directory", G_TYPE_BOOLEAN) ||
+ !gedit_message_type_check (message_type, "filter", G_TYPE_BOOLEAN))
+ {
+ return;
+ }
+
+ cbmessage = g_object_new (message_type,
+ "object-path", object_path,
+ "method", method,
+ "id", NULL,
+ "location", NULL,
+ "is-directory", FALSE,
+ "filter", FALSE,
+ NULL);
+
+ /* Register the custom filter on the widget */
+ filter_data = filter_data_new (window, cbmessage);
+
+ id = gedit_file_browser_widget_add_filter (data->widget,
+ (GeditFileBrowserWidgetFilterFunc)custom_message_filter_func,
+ filter_data,
+ (GDestroyNotify)filter_data_free);
+
+ filter_data->id = id;
+}
+
+static void
+message_remove_filter_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gulong id = 0;
+
+ g_object_get (message, "id", &id, NULL);
+
+ if (!id)
+ return;
+
+ gedit_file_browser_widget_remove_filter (data->widget, id);
+}
+
+static void
+message_extend_context_menu_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ GeditWindow *window)
+{
+ WindowData *data;
+ GeditMenuExtension *ext;
+
+ data = get_window_data (window);
+
+ ext = gedit_file_browser_widget_extend_context_menu (data->widget);
+
+ g_object_set (message, "extension", ext, NULL);
+ g_object_unref (ext);
+}
+
+static void
+message_up_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ GeditFileBrowserStore *store = gedit_file_browser_widget_get_browser_store (data->widget);
+
+ gedit_file_browser_store_set_virtual_root_up (store);
+}
+
+static void
+message_history_back_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gedit_file_browser_widget_history_back (data->widget);
+}
+
+static void
+message_history_forward_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gedit_file_browser_widget_history_forward (data->widget);
+}
+
+static void
+message_refresh_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gedit_file_browser_widget_refresh (data->widget);
+}
+
+static void
+message_set_show_hidden_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gboolean active = FALSE;
+ GeditFileBrowserStore *store;
+ GeditFileBrowserStoreFilterMode mode;
+
+ g_object_get (message, "active", &active, NULL);
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+ mode = gedit_file_browser_store_get_filter_mode (store);
+
+ if (active)
+ mode &= ~GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
+ else
+ mode |= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
+
+ gedit_file_browser_store_set_filter_mode (store, mode);
+}
+
+static void
+message_set_show_binary_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gboolean active = FALSE;
+ GeditFileBrowserStore *store;
+ GeditFileBrowserStoreFilterMode mode;
+
+ g_object_get (message, "active", &active, NULL);
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+ mode = gedit_file_browser_store_get_filter_mode (store);
+
+ if (active)
+ mode &= ~GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY;
+ else
+ mode |= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY;
+
+ gedit_file_browser_store_set_filter_mode (store, mode);
+}
+
+static void
+message_show_bookmarks_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gedit_file_browser_widget_show_bookmarks (data->widget);
+}
+
+static void
+message_show_files_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gedit_file_browser_widget_show_files (data->widget);
+}
+
+static void
+message_get_view_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ GeditFileBrowserView *view;
+ view = gedit_file_browser_widget_get_browser_view (data->widget);
+
+ g_object_set (message, "view", view, NULL);
+}
+
+static void
+register_methods (GeditWindow *window,
+ GeditFileBrowserWidget *widget)
+{
+ GeditMessageBus *bus = gedit_window_get_message_bus (window);
+ WindowData *data = get_window_data (window);
+
+ /* Register method calls */
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,
+ MESSAGE_OBJECT_PATH,
+ "get_root");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,
+ MESSAGE_OBJECT_PATH,
+ "set_root");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,
+ MESSAGE_OBJECT_PATH,
+ "set_emblem");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,
+ MESSAGE_OBJECT_PATH,
+ "set_markup");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,
+ MESSAGE_OBJECT_PATH,
+ "add_filter");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,
+ MESSAGE_OBJECT_PATH,
+ "remove_filter");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,
+ MESSAGE_OBJECT_PATH,
+ "extend_context_menu");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_MESSAGE,
+ MESSAGE_OBJECT_PATH,
+ "up");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_MESSAGE,
+ MESSAGE_OBJECT_PATH,
+ "history_back");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_MESSAGE,
+ MESSAGE_OBJECT_PATH,
+ "history_forward");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_MESSAGE,
+ MESSAGE_OBJECT_PATH,
+ "refresh");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,
+ MESSAGE_OBJECT_PATH,
+ "set_show_hidden");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,
+ MESSAGE_OBJECT_PATH,
+ "set_show_binary");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_MESSAGE,
+ MESSAGE_OBJECT_PATH,
+ "show_bookmarks");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_MESSAGE,
+ MESSAGE_OBJECT_PATH,
+ "show_files");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,
+ MESSAGE_OBJECT_PATH,
+ "get_view");
+
+ BUS_CONNECT (bus, get_root, data);
+ BUS_CONNECT (bus, set_root, data);
+ BUS_CONNECT (bus, set_emblem, data);
+ BUS_CONNECT (bus, set_markup, data);
+ BUS_CONNECT (bus, add_filter, window);
+ BUS_CONNECT (bus, remove_filter, data);
+ BUS_CONNECT (bus, extend_context_menu, window);
+
+ BUS_CONNECT (bus, up, data);
+ BUS_CONNECT (bus, history_back, data);
+ BUS_CONNECT (bus, history_forward, data);
+
+ BUS_CONNECT (bus, refresh, data);
+
+ BUS_CONNECT (bus, set_show_hidden, data);
+ BUS_CONNECT (bus, set_show_binary, data);
+
+ BUS_CONNECT (bus, show_bookmarks, data);
+ BUS_CONNECT (bus, show_files, data);
+
+ BUS_CONNECT (bus, get_view, data);
+}
+
+static void
+store_row_inserted (GeditFileBrowserStore *store,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ MessageCacheData *data)
+{
+ guint flags = 0;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!FILE_IS_DUMMY (flags) && !FILE_IS_FILTERED (flags))
+ {
+ WindowData *wdata = get_window_data (data->window);
+
+ set_item_message (wdata, iter, path, data->message);
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+ }
+}
+
+static void
+store_before_row_deleted (GeditFileBrowserStore *store,
+ GtkTreePath *path,
+ MessageCacheData *data)
+{
+ GtkTreeIter iter;
+ guint flags = 0;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!FILE_IS_DUMMY (flags) && !FILE_IS_FILTERED (flags))
+ {
+ WindowData *wdata = get_window_data (data->window);
+ gchar *id;
+
+ set_item_message (wdata, &iter, path, data->message);
+
+ /* Must get the ID before the plugin can modify it */
+ g_object_get (data->message, "id", &id, NULL);
+
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+
+ g_hash_table_remove (wdata->row_tracking, id);
+ g_free (id);
+ }
+}
+
+static void
+store_virtual_root_changed (GeditFileBrowserStore *store,
+ GParamSpec *spec,
+ MessageCacheData *data)
+{
+ WindowData *wdata = get_window_data (data->window);
+ GFile *vroot;
+
+ vroot = gedit_file_browser_store_get_virtual_root (store);
+
+ if (!vroot)
+ {
+ return;
+ }
+
+ g_object_set (data->message,
+ "location", vroot,
+ NULL);
+
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+
+ g_object_unref (vroot);
+}
+
+static void
+store_begin_loading (GeditFileBrowserStore *store,
+ GtkTreeIter *iter,
+ MessageCacheData *data)
+{
+ GtkTreePath *path;
+ WindowData *wdata = get_window_data (data->window);
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter);
+
+ set_item_message (wdata, iter, path, data->message);
+
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+ gtk_tree_path_free (path);
+}
+
+static void
+store_end_loading (GeditFileBrowserStore *store,
+ GtkTreeIter *iter,
+ MessageCacheData *data)
+{
+ GtkTreePath *path;
+ WindowData *wdata = get_window_data (data->window);
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter);
+
+ set_item_message (wdata, iter, path, data->message);
+
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+ gtk_tree_path_free (path);
+}
+
+static void
+register_signals (GeditWindow *window,
+ GeditFileBrowserWidget *widget)
+{
+ GeditMessageBus *bus = gedit_window_get_message_bus (window);
+ GeditFileBrowserStore *store;
+
+ GeditMessage *message;
+ WindowData *data;
+
+ /* Register signals */
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,
+ MESSAGE_OBJECT_PATH,
+ "root_changed");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,
+ MESSAGE_OBJECT_PATH,
+ "begin_loading");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,
+ MESSAGE_OBJECT_PATH,
+ "end_loading");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,
+ MESSAGE_OBJECT_PATH,
+ "inserted");
+
+ gedit_message_bus_register (bus,
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,
+ MESSAGE_OBJECT_PATH,
+ "deleted");
+
+ store = gedit_file_browser_widget_get_browser_store (widget);
+
+ message = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,
+ "object-path", MESSAGE_OBJECT_PATH,
+ "method", "inserted",
+ NULL);
+
+ data = get_window_data (window);
+
+ data->row_inserted_id =
+ g_signal_connect_data (store,
+ "row-inserted",
+ G_CALLBACK (store_row_inserted),
+ message_cache_data_new (window, message),
+ (GClosureNotify)message_cache_data_free,
+ 0);
+
+ message = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,
+ "object-path", MESSAGE_OBJECT_PATH,
+ "method", "deleted",
+ NULL);
+
+ data->before_row_deleted_id =
+ g_signal_connect_data (store,
+ "before-row-deleted",
+ G_CALLBACK (store_before_row_deleted),
+ message_cache_data_new (window, message),
+ (GClosureNotify)message_cache_data_free,
+ 0);
+
+ message = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,
+ "object-path", MESSAGE_OBJECT_PATH,
+ "method", "root_changed",
+ NULL);
+
+ data->root_changed_id =
+ g_signal_connect_data (store,
+ "notify::virtual-root",
+ G_CALLBACK (store_virtual_root_changed),
+ message_cache_data_new (window, message),
+ (GClosureNotify)message_cache_data_free,
+ 0);
+
+ message = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,
+ "object-path", MESSAGE_OBJECT_PATH,
+ "method", "begin_loading",
+ NULL);
+
+ data->begin_loading_id =
+ g_signal_connect_data (store,
+ "begin_loading",
+ G_CALLBACK (store_begin_loading),
+ message_cache_data_new (window, message),
+ (GClosureNotify)message_cache_data_free,
+ 0);
+
+ message = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,
+ "object-path", MESSAGE_OBJECT_PATH,
+ "method", "end_loading",
+ NULL);
+
+ data->end_loading_id =
+ g_signal_connect_data (store,
+ "end_loading",
+ G_CALLBACK (store_end_loading),
+ message_cache_data_new (window, message),
+ (GClosureNotify)message_cache_data_free,
+ 0);
+}
+
+static void
+message_unregistered (GeditMessageBus *bus,
+ const gchar *object_path,
+ const gchar *method,
+ GeditWindow *window)
+{
+ gchar *identifier;
+ FilterData *data;
+ WindowData *wdata;
+
+ wdata = get_window_data (window);
+
+ identifier = gedit_message_type_identifier (object_path, method);
+
+ data = g_hash_table_lookup (wdata->filters, identifier);
+
+ if (data)
+ {
+ gedit_file_browser_widget_remove_filter (wdata->widget,
+ data->id);
+ }
+
+ g_free (identifier);
+}
+
+void
+gedit_file_browser_messages_register (GeditWindow *window,
+ GeditFileBrowserWidget *widget)
+{
+ window_data_new (window, widget);
+
+ register_methods (window, widget);
+ register_signals (window, widget);
+
+ g_signal_connect (gedit_window_get_message_bus (window),
+ "unregistered",
+ G_CALLBACK (message_unregistered),
+ window);
+}
+
+static void
+cleanup_signals (GeditWindow *window)
+{
+ WindowData *data = get_window_data (window);
+ GeditFileBrowserStore *store;
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+
+ g_signal_handler_disconnect (store, data->row_inserted_id);
+ g_signal_handler_disconnect (store, data->before_row_deleted_id);
+ g_signal_handler_disconnect (store, data->root_changed_id);
+ g_signal_handler_disconnect (store, data->begin_loading_id);
+ g_signal_handler_disconnect (store, data->end_loading_id);
+
+ g_signal_handlers_disconnect_by_func (data->bus, message_unregistered, window);
+}
+
+void
+gedit_file_browser_messages_unregister (GeditWindow *window)
+{
+ GeditMessageBus *bus = gedit_window_get_message_bus (window);
+ WindowData *data = get_window_data (window);
+
+ cleanup_signals (window);
+
+ BUS_DISCONNECT (bus, get_root, data);
+ BUS_DISCONNECT (bus, set_root, data);
+ BUS_DISCONNECT (bus, set_emblem, data);
+ BUS_DISCONNECT (bus, set_markup, data);
+ BUS_DISCONNECT (bus, add_filter, window);
+ BUS_DISCONNECT (bus, remove_filter, data);
+
+ BUS_DISCONNECT (bus, up, data);
+ BUS_DISCONNECT (bus, history_back, data);
+ BUS_DISCONNECT (bus, history_forward, data);
+
+ BUS_DISCONNECT (bus, refresh, data);
+
+ BUS_DISCONNECT (bus, set_show_hidden, data);
+ BUS_DISCONNECT (bus, set_show_binary, data);
+
+ BUS_DISCONNECT (bus, show_bookmarks, data);
+ BUS_DISCONNECT (bus, show_files, data);
+
+ BUS_DISCONNECT (bus, get_view, data);
+
+ gedit_message_bus_unregister_all (bus, MESSAGE_OBJECT_PATH);
+
+ window_data_free (window);
+}
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-messages.h b/plugins/filebrowser/gedit-file-browser-messages.h
new file mode 100644
index 0000000..9fcb316
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-messages.h
@@ -0,0 +1,33 @@
+/*
+ * gedit-file-browser-messages.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2008 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGES_H
+#define GEDIT_FILE_BROWSER_MESSAGES_H
+
+#include <gedit/gedit-window.h>
+#include <gedit/gedit-message-bus.h>
+#include "gedit-file-browser-widget.h"
+
+void gedit_file_browser_messages_register (GeditWindow *window,
+ GeditFileBrowserWidget *widget);
+void gedit_file_browser_messages_unregister (GeditWindow *window);
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGES_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-plugin.c b/plugins/filebrowser/gedit-file-browser-plugin.c
new file mode 100644
index 0000000..2e8e13e
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-plugin.c
@@ -0,0 +1,972 @@
+/*
+ * gedit-file-browser-plugin.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include <gedit/gedit-app.h>
+#include <gedit/gedit-commands.h>
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-window.h>
+#include <gedit/gedit-window-activatable.h>
+#include <gedit/gedit-utils.h>
+
+#include "gedit-file-browser-enum-types.h"
+#include "gedit-file-browser-plugin.h"
+#include "gedit-file-browser-utils.h"
+#include "gedit-file-browser-error.h"
+#include "gedit-file-browser-widget.h"
+#include "gedit-file-browser-messages.h"
+
+#define FILEBROWSER_BASE_SETTINGS "org.gnome.gedit.plugins.filebrowser"
+#define FILEBROWSER_TREE_VIEW "tree-view"
+#define FILEBROWSER_ROOT "root"
+#define FILEBROWSER_VIRTUAL_ROOT "virtual-root"
+#define FILEBROWSER_ENABLE_REMOTE "enable-remote"
+#define FILEBROWSER_OPEN_AT_FIRST_DOC "open-at-first-doc"
+#define FILEBROWSER_FILTER_MODE "filter-mode"
+#define FILEBROWSER_FILTER_PATTERN "filter-pattern"
+#define FILEBROWSER_BINARY_PATTERNS "binary-patterns"
+
+#define NAUTILUS_BASE_SETTINGS "org.gnome.nautilus.preferences"
+#define NAUTILUS_FALLBACK_SETTINGS "org.gnome.gedit.plugins.filebrowser.nautilus"
+#define NAUTILUS_CLICK_POLICY_KEY "click-policy"
+#define NAUTILUS_CONFIRM_TRASH_KEY "confirm-trash"
+
+#define TERMINAL_BASE_SETTINGS "org.gnome.desktop.default-applications.terminal"
+#define TERMINAL_EXEC_KEY "exec"
+
+struct _GeditFileBrowserPluginPrivate
+{
+ GSettings *settings;
+ GSettings *nautilus_settings;
+ GSettings *terminal_settings;
+
+ GeditWindow *window;
+
+ GeditFileBrowserWidget *tree_widget;
+ gboolean auto_root;
+ gulong end_loading_handle;
+ gboolean confirm_trash;
+
+ guint click_policy_handle;
+ guint confirm_trash_handle;
+};
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW
+};
+
+static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface);
+
+static void on_location_activated_cb (GeditFileBrowserWidget *widget,
+ GFile *location,
+ GeditWindow *window);
+static void on_error_cb (GeditFileBrowserWidget *widget,
+ guint code,
+ gchar const *message,
+ GeditFileBrowserPlugin *plugin);
+static void on_model_set_cb (GeditFileBrowserView *widget,
+ GParamSpec *param,
+ GeditFileBrowserPlugin *plugin);
+static void on_virtual_root_changed_cb (GeditFileBrowserStore *model,
+ GParamSpec *param,
+ GeditFileBrowserPlugin *plugin);
+static void on_rename_cb (GeditFileBrowserStore *model,
+ GFile *oldfile,
+ GFile *newfile,
+ GeditWindow *window);
+static void on_tab_added_cb (GeditWindow *window,
+ GeditTab *tab,
+ GeditFileBrowserPlugin *plugin);
+static gboolean on_confirm_delete_cb (GeditFileBrowserWidget *widget,
+ GeditFileBrowserStore *store,
+ GList *rows,
+ GeditFileBrowserPlugin *plugin);
+static gboolean on_confirm_no_trash_cb (GeditFileBrowserWidget *widget,
+ GList *files,
+ GeditWindow *window);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserPlugin,
+ gedit_file_browser_plugin,
+ G_TYPE_OBJECT,
+ 0,
+ G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserPlugin)
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE,
+ gedit_window_activatable_iface_init) \
+ \
+ gedit_file_browser_enum_and_flag_register_type (type_module); \
+ _gedit_file_bookmarks_store_register_type (type_module); \
+ _gedit_file_browser_store_register_type (type_module); \
+ _gedit_file_browser_view_register_type (type_module); \
+ _gedit_file_browser_widget_register_type (type_module); \
+)
+
+static GSettings *
+settings_try_new (const gchar *schema_id)
+{
+ GSettings *settings = NULL;
+ GSettingsSchemaSource *source;
+ GSettingsSchema *schema;
+
+ source = g_settings_schema_source_get_default ();
+
+ schema = g_settings_schema_source_lookup (source, schema_id, TRUE);
+
+ if (schema != NULL)
+ {
+ settings = g_settings_new_full (schema, NULL, NULL);
+ g_settings_schema_unref (schema);
+ }
+
+ return settings;
+}
+
+static void
+gedit_file_browser_plugin_init (GeditFileBrowserPlugin *plugin)
+{
+ plugin->priv = gedit_file_browser_plugin_get_instance_private (plugin);
+
+ plugin->priv->settings = g_settings_new (FILEBROWSER_BASE_SETTINGS);
+ plugin->priv->terminal_settings = g_settings_new (TERMINAL_BASE_SETTINGS);
+ plugin->priv->nautilus_settings = settings_try_new (NAUTILUS_BASE_SETTINGS);
+
+ if (plugin->priv->nautilus_settings == NULL)
+ {
+ plugin->priv->nautilus_settings = g_settings_new (NAUTILUS_FALLBACK_SETTINGS);
+ }
+}
+
+static void
+gedit_file_browser_plugin_dispose (GObject *object)
+{
+ GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (object);
+
+ g_clear_object (&plugin->priv->settings);
+ g_clear_object (&plugin->priv->nautilus_settings);
+ g_clear_object (&plugin->priv->terminal_settings);
+ g_clear_object (&plugin->priv->window);
+
+ G_OBJECT_CLASS (gedit_file_browser_plugin_parent_class)->dispose (object);
+}
+
+static void
+gedit_file_browser_plugin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_plugin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value, plugin->priv->window);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+on_end_loading_cb (GeditFileBrowserStore *store,
+ GtkTreeIter *iter,
+ GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+
+ /* Disconnect the signal */
+ g_signal_handler_disconnect (store, priv->end_loading_handle);
+ priv->end_loading_handle = 0;
+ priv->auto_root = FALSE;
+}
+
+static void
+prepare_auto_root (GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ GeditFileBrowserStore *store;
+
+ priv->auto_root = TRUE;
+
+ store = gedit_file_browser_widget_get_browser_store (priv->tree_widget);
+
+ if (priv->end_loading_handle != 0)
+ {
+ g_signal_handler_disconnect (store, priv->end_loading_handle);
+ priv->end_loading_handle = 0;
+ }
+
+ priv->end_loading_handle = g_signal_connect (store,
+ "end-loading",
+ G_CALLBACK (on_end_loading_cb),
+ plugin);
+}
+
+static void
+restore_default_location (GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ gchar *root;
+ gchar *virtual_root;
+ gboolean bookmarks;
+ gboolean remote;
+
+ bookmarks = !g_settings_get_boolean (priv->settings,
+ FILEBROWSER_TREE_VIEW);
+
+ if (bookmarks)
+ {
+ gedit_file_browser_widget_show_bookmarks (priv->tree_widget);
+ return;
+ }
+
+ root = g_settings_get_string (priv->settings,
+ FILEBROWSER_ROOT);
+ virtual_root = g_settings_get_string (priv->settings,
+ FILEBROWSER_VIRTUAL_ROOT);
+
+ remote = g_settings_get_boolean (priv->settings,
+ FILEBROWSER_ENABLE_REMOTE);
+
+ if (root != NULL && *root != '\0')
+ {
+ GFile *rootfile;
+ GFile *vrootfile;
+
+ rootfile = g_file_new_for_uri (root);
+ vrootfile = g_file_new_for_uri (virtual_root);
+
+ if (remote || g_file_is_native (rootfile))
+ {
+ if (virtual_root != NULL && *virtual_root != '\0')
+ {
+ prepare_auto_root (plugin);
+ gedit_file_browser_widget_set_root_and_virtual_root (priv->tree_widget,
+ rootfile,
+ vrootfile);
+ }
+ else
+ {
+ prepare_auto_root (plugin);
+ gedit_file_browser_widget_set_root (priv->tree_widget,
+ rootfile,
+ TRUE);
+ }
+ }
+
+ g_object_unref (rootfile);
+ g_object_unref (vrootfile);
+ }
+
+ g_free (root);
+ g_free (virtual_root);
+}
+
+static void
+on_click_policy_changed (GSettings *settings,
+ const gchar *key,
+ GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ GeditFileBrowserViewClickPolicy policy;
+ GeditFileBrowserView *view;
+
+ policy = g_settings_get_enum (settings, key);
+
+ view = gedit_file_browser_widget_get_browser_view (priv->tree_widget);
+ gedit_file_browser_view_set_click_policy (view, policy);
+}
+
+static void
+on_confirm_trash_changed (GSettings *settings,
+ const gchar *key,
+ GeditFileBrowserPlugin *plugin)
+{
+ plugin->priv->confirm_trash = g_settings_get_boolean (settings, key);
+}
+
+static void
+install_nautilus_prefs (GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ gboolean prefb;
+ GeditFileBrowserViewClickPolicy policy;
+ GeditFileBrowserView *view;
+
+ /* Get click_policy */
+ policy = g_settings_get_enum (priv->nautilus_settings,
+ NAUTILUS_CLICK_POLICY_KEY);
+
+ view = gedit_file_browser_widget_get_browser_view (priv->tree_widget);
+ gedit_file_browser_view_set_click_policy (view, policy);
+
+ priv->click_policy_handle =
+ g_signal_connect (priv->nautilus_settings,
+ "changed::" NAUTILUS_CLICK_POLICY_KEY,
+ G_CALLBACK (on_click_policy_changed),
+ plugin);
+
+ /* Get confirm_trash */
+ prefb = g_settings_get_boolean (priv->nautilus_settings,
+ NAUTILUS_CONFIRM_TRASH_KEY);
+
+ priv->confirm_trash = prefb;
+
+ priv->confirm_trash_handle =
+ g_signal_connect (priv->nautilus_settings,
+ "changed::" NAUTILUS_CONFIRM_TRASH_KEY,
+ G_CALLBACK (on_confirm_trash_changed),
+ plugin);
+}
+
+static void
+set_root_from_doc (GeditFileBrowserPlugin *plugin,
+ GeditDocument *doc)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ GtkSourceFile *file;
+ GFile *location;
+ GFile *parent;
+
+ if (doc == NULL)
+ {
+ return;
+ }
+
+ file = gedit_document_get_file (doc);
+ location = gtk_source_file_get_location (file);
+ if (location == NULL)
+ {
+ return;
+ }
+
+ parent = g_file_get_parent (location);
+
+ if (parent != NULL)
+ {
+ gedit_file_browser_widget_set_root (priv->tree_widget,
+ parent,
+ TRUE);
+
+ g_object_unref (parent);
+ }
+}
+
+static void
+set_active_root (GeditFileBrowserWidget *widget,
+ GeditFileBrowserPlugin *plugin)
+{
+ set_root_from_doc (plugin,
+ gedit_window_get_active_document (plugin->priv->window));
+}
+
+static gchar *
+get_terminal (GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ gchar *terminal;
+
+ terminal = g_settings_get_string (priv->terminal_settings,
+ TERMINAL_EXEC_KEY);
+
+ if (terminal == NULL)
+ {
+ const gchar *term = g_getenv ("TERM");
+
+ if (term != NULL)
+ terminal = g_strdup (term);
+ else
+ terminal = g_strdup ("xterm");
+ }
+
+ return terminal;
+}
+
+static void
+open_in_terminal (GeditFileBrowserWidget *widget,
+ GFile *location,
+ GeditFileBrowserPlugin *plugin)
+{
+ if (location)
+ {
+ gchar *terminal;
+ gchar *local;
+ gchar *argv[2];
+
+ terminal = get_terminal (plugin);
+ local = g_file_get_path (location);
+
+ argv[0] = terminal;
+ argv[1] = NULL;
+
+ g_spawn_async (local,
+ argv,
+ NULL,
+ G_SPAWN_SEARCH_PATH,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+
+ g_free (terminal);
+ g_free (local);
+ }
+}
+
+static void
+gedit_file_browser_plugin_update_state (GeditWindowActivatable *activatable)
+{
+ GeditFileBrowserPluginPrivate *priv = GEDIT_FILE_BROWSER_PLUGIN (activatable)->priv;
+ GeditDocument *doc;
+
+ doc = gedit_window_get_active_document (priv->window);
+ gedit_file_browser_widget_set_active_root_enabled (priv->tree_widget,
+ doc != NULL && !gedit_document_is_untitled (doc));
+}
+
+static void
+gedit_file_browser_plugin_activate (GeditWindowActivatable *activatable)
+{
+ GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (activatable);
+ GeditFileBrowserPluginPrivate *priv;
+ GtkWidget *panel;
+ GeditFileBrowserStore *store;
+
+ priv = plugin->priv;
+
+ priv->tree_widget = GEDIT_FILE_BROWSER_WIDGET (gedit_file_browser_widget_new ());
+
+ g_signal_connect (priv->tree_widget,
+ "location-activated",
+ G_CALLBACK (on_location_activated_cb), priv->window);
+
+ g_signal_connect (priv->tree_widget,
+ "error", G_CALLBACK (on_error_cb), plugin);
+
+ g_signal_connect (priv->tree_widget,
+ "confirm-delete",
+ G_CALLBACK (on_confirm_delete_cb),
+ plugin);
+
+ g_signal_connect (priv->tree_widget,
+ "confirm-no-trash",
+ G_CALLBACK (on_confirm_no_trash_cb),
+ priv->window);
+
+ g_signal_connect (priv->tree_widget,
+ "open-in-terminal",
+ G_CALLBACK (open_in_terminal),
+ plugin);
+
+ g_signal_connect (priv->tree_widget,
+ "set-active-root",
+ G_CALLBACK (set_active_root),
+ plugin);
+
+ g_settings_bind (priv->settings,
+ FILEBROWSER_FILTER_PATTERN,
+ priv->tree_widget,
+ FILEBROWSER_FILTER_PATTERN,
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+
+ panel = gedit_window_get_side_panel (priv->window);
+
+ gtk_stack_add_titled (GTK_STACK (panel),
+ GTK_WIDGET (priv->tree_widget),
+ "GeditFileBrowserPanel",
+ _("File Browser"));
+
+ gtk_widget_show (GTK_WIDGET (priv->tree_widget));
+
+ /* Install nautilus preferences */
+ install_nautilus_prefs (plugin);
+
+ /* Connect signals to store the last visited location */
+ g_signal_connect (gedit_file_browser_widget_get_browser_view (priv->tree_widget),
+ "notify::model",
+ G_CALLBACK (on_model_set_cb),
+ plugin);
+
+ store = gedit_file_browser_widget_get_browser_store (priv->tree_widget);
+
+ g_settings_bind (priv->settings,
+ FILEBROWSER_FILTER_MODE,
+ store,
+ FILEBROWSER_FILTER_MODE,
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+
+ g_settings_bind (priv->settings,
+ FILEBROWSER_BINARY_PATTERNS,
+ store,
+ FILEBROWSER_BINARY_PATTERNS,
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+
+ g_signal_connect (store,
+ "notify::virtual-root",
+ G_CALLBACK (on_virtual_root_changed_cb),
+ plugin);
+
+ g_signal_connect (store,
+ "rename",
+ G_CALLBACK (on_rename_cb),
+ priv->window);
+
+ g_signal_connect (priv->window,
+ "tab-added",
+ G_CALLBACK (on_tab_added_cb),
+ plugin);
+
+ /* Register messages on the bus */
+ gedit_file_browser_messages_register (priv->window, priv->tree_widget);
+
+ gedit_file_browser_plugin_update_state (activatable);
+}
+
+static void
+gedit_file_browser_plugin_deactivate (GeditWindowActivatable *activatable)
+{
+ GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (activatable);
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ GtkWidget *panel;
+
+
+ /* Unregister messages from the bus */
+ gedit_file_browser_messages_unregister (priv->window);
+
+ /* Disconnect signals */
+ g_signal_handlers_disconnect_by_func (priv->window,
+ G_CALLBACK (on_tab_added_cb),
+ plugin);
+
+ if (priv->click_policy_handle)
+ {
+ g_signal_handler_disconnect (priv->nautilus_settings,
+ priv->click_policy_handle);
+ }
+
+ if (priv->confirm_trash_handle)
+ {
+ g_signal_handler_disconnect (priv->nautilus_settings,
+ priv->confirm_trash_handle);
+ }
+
+ panel = gedit_window_get_side_panel (priv->window);
+ gtk_container_remove (GTK_CONTAINER (panel), GTK_WIDGET (priv->tree_widget));
+}
+
+static void
+gedit_file_browser_plugin_class_init (GeditFileBrowserPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_file_browser_plugin_dispose;
+ object_class->set_property = gedit_file_browser_plugin_set_property;
+ object_class->get_property = gedit_file_browser_plugin_get_property;
+
+ g_object_class_override_property (object_class, PROP_WINDOW, "window");
+}
+
+static void
+gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface)
+{
+ iface->activate = gedit_file_browser_plugin_activate;
+ iface->deactivate = gedit_file_browser_plugin_deactivate;
+ iface->update_state = gedit_file_browser_plugin_update_state;
+}
+
+static void
+gedit_file_browser_plugin_class_finalize (GeditFileBrowserPluginClass *klass)
+{
+}
+
+/* Callbacks */
+static void
+on_location_activated_cb (GeditFileBrowserWidget *tree_widget,
+ GFile *location,
+ GeditWindow *window)
+{
+ gedit_commands_load_location (window, location, NULL, 0, 0);
+}
+
+static void
+on_error_cb (GeditFileBrowserWidget *tree_widget,
+ guint code,
+ gchar const *message,
+ GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ gchar *title;
+ GtkWidget *dlg;
+
+ /* Do not show the error when the root has been set automatically */
+ if (priv->auto_root && (code == GEDIT_FILE_BROWSER_ERROR_SET_ROOT ||
+ code == GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY))
+ {
+ /* Show bookmarks */
+ gedit_file_browser_widget_show_bookmarks (priv->tree_widget);
+ return;
+ }
+
+ switch (code)
+ {
+ case GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY:
+ title = _("An error occurred while creating a new directory");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_NEW_FILE:
+ title = _("An error occurred while creating a new file");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_RENAME:
+ title = _("An error occurred while renaming a file or directory");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_DELETE:
+ title = _("An error occurred while deleting a file or directory");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY:
+ title = _("An error occurred while opening a directory in the file manager");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_SET_ROOT:
+ title = _("An error occurred while setting a root directory");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY:
+ title = _("An error occurred while loading a directory");
+ break;
+ default:
+ title = _("An error occurred");
+ break;
+ }
+
+ dlg = gtk_message_dialog_new (GTK_WINDOW (priv->window),
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "%s", title);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg),
+ "%s", message);
+
+ gtk_dialog_run (GTK_DIALOG (dlg));
+ gtk_widget_destroy (dlg);
+}
+
+static void
+on_model_set_cb (GeditFileBrowserView *widget,
+ GParamSpec *param,
+ GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ GtkTreeModel *model;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (gedit_file_browser_widget_get_browser_view (priv->tree_widget)));
+
+ if (model == NULL)
+ return;
+
+ g_settings_set_boolean (priv->settings,
+ FILEBROWSER_TREE_VIEW,
+ GEDIT_IS_FILE_BROWSER_STORE (model));
+}
+
+static void
+on_rename_cb (GeditFileBrowserStore *store,
+ GFile *oldfile,
+ GFile *newfile,
+ GeditWindow *window)
+{
+ GList *documents;
+ GList *item;
+
+ /* Find all documents and set its uri to newuri where it matches olduri */
+ documents = gedit_app_get_documents (GEDIT_APP (g_application_get_default ()));
+
+ for (item = documents; item; item = item->next)
+ {
+ GeditDocument *doc;
+ GtkSourceFile *source_file;
+ GFile *docfile;
+
+ doc = GEDIT_DOCUMENT (item->data);
+ source_file = gedit_document_get_file (doc);
+ docfile = gtk_source_file_get_location (source_file);
+
+ if (docfile == NULL)
+ {
+ continue;
+ }
+
+ if (g_file_equal (docfile, oldfile))
+ {
+ gtk_source_file_set_location (source_file, newfile);
+ }
+ else
+ {
+ gchar *relative;
+
+ relative = g_file_get_relative_path (oldfile, docfile);
+
+ if (relative != NULL)
+ {
+ /* Relative now contains the part in docfile without
+ the prefix oldfile */
+
+ docfile = g_file_get_child (newfile, relative);
+
+ gtk_source_file_set_location (source_file, docfile);
+
+ g_object_unref (docfile);
+ }
+
+ g_free (relative);
+ }
+ }
+
+ g_list_free (documents);
+}
+
+static void
+on_virtual_root_changed_cb (GeditFileBrowserStore *store,
+ GParamSpec *param,
+ GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ GFile *root;
+ GFile *virtual_root;
+ gchar *uri_root = NULL;
+
+ root = gedit_file_browser_store_get_root (store);
+
+ if (!root)
+ {
+ return;
+ }
+ else
+ {
+ uri_root = g_file_get_uri (root);
+ g_object_unref (root);
+ }
+
+ g_settings_set_string (priv->settings,
+ FILEBROWSER_ROOT,
+ uri_root);
+
+ virtual_root = gedit_file_browser_store_get_virtual_root (store);
+
+ if (!virtual_root)
+ {
+ /* Set virtual to same as root then */
+ g_settings_set_string (priv->settings,
+ FILEBROWSER_VIRTUAL_ROOT,
+ uri_root);
+ }
+ else
+ {
+ gchar *uri_vroot;
+
+ uri_vroot = g_file_get_uri (virtual_root);
+
+ g_settings_set_string (priv->settings,
+ FILEBROWSER_VIRTUAL_ROOT,
+ uri_vroot);
+ g_free (uri_vroot);
+ g_object_unref (virtual_root);
+ }
+
+ g_signal_handlers_disconnect_by_func (priv->window,
+ G_CALLBACK (on_tab_added_cb),
+ plugin);
+ g_free (uri_root);
+}
+
+static void
+on_tab_added_cb (GeditWindow *window,
+ GeditTab *tab,
+ GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ gboolean open;
+ gboolean load_default = TRUE;
+
+ open = g_settings_get_boolean (priv->settings,
+ FILEBROWSER_OPEN_AT_FIRST_DOC);
+
+ if (open)
+ {
+ GeditDocument *doc;
+ GtkSourceFile *file;
+ GFile *location;
+
+ doc = gedit_tab_get_document (tab);
+ file = gedit_document_get_file (doc);
+ location = gtk_source_file_get_location (file);
+
+ if (location != NULL)
+ {
+ if (g_file_has_uri_scheme (location, "file"))
+ {
+ prepare_auto_root (plugin);
+ set_root_from_doc (plugin, doc);
+ load_default = FALSE;
+ }
+ }
+ }
+
+ if (load_default)
+ restore_default_location (plugin);
+
+ /* Disconnect this signal, it's only called once */
+ g_signal_handlers_disconnect_by_func (window,
+ G_CALLBACK (on_tab_added_cb),
+ plugin);
+}
+
+static gchar *
+get_filename_from_path (GtkTreeModel *model,
+ GtkTreePath *path)
+{
+ GtkTreeIter iter;
+ GFile *location;
+ gchar *ret = NULL;
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ return NULL;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ -1);
+
+ if (location)
+ {
+ ret = gedit_file_browser_utils_file_basename (location);
+ g_object_unref (location);
+ }
+
+ return ret;
+}
+
+static gboolean
+on_confirm_no_trash_cb (GeditFileBrowserWidget *widget,
+ GList *files,
+ GeditWindow *window)
+{
+ gchar *normal;
+ gchar *message;
+ gchar *secondary;
+ gboolean result;
+
+ message = _("Cannot move file to trash, do you\nwant to delete permanently?");
+
+ if (files->next == NULL)
+ {
+ normal = gedit_file_browser_utils_file_basename (G_FILE (files->data));
+ secondary = g_strdup_printf (_("The file “%s” cannot be moved to the trash."), normal);
+ g_free (normal);
+ }
+ else
+ {
+ secondary = g_strdup (_("The selected files cannot be moved to the trash."));
+ }
+
+ result = gedit_file_browser_utils_confirmation_dialog (window,
+ GTK_MESSAGE_QUESTION,
+ message,
+ secondary,
+ _("_Delete"));
+ g_free (secondary);
+
+ return result;
+}
+
+static gboolean
+on_confirm_delete_cb (GeditFileBrowserWidget *widget,
+ GeditFileBrowserStore *store,
+ GList *paths,
+ GeditFileBrowserPlugin *plugin)
+{
+ GeditFileBrowserPluginPrivate *priv = plugin->priv;
+ gchar *normal;
+ gchar *message;
+ gchar *secondary;
+ gboolean result;
+
+ if (!priv->confirm_trash)
+ return TRUE;
+
+ if (paths->next == NULL)
+ {
+ normal = get_filename_from_path (GTK_TREE_MODEL (store), (GtkTreePath *)(paths->data));
+ message = g_strdup_printf (_("Are you sure you want to permanently delete “%s”?"), normal);
+ g_free (normal);
+ }
+ else
+ {
+ message = g_strdup (_("Are you sure you want to permanently delete the selected files?"));
+ }
+
+ secondary = _("If you delete an item, it is permanently lost.");
+
+ result = gedit_file_browser_utils_confirmation_dialog (priv->window,
+ GTK_MESSAGE_QUESTION,
+ message,
+ secondary,
+ _("_Delete"));
+
+ g_free (message);
+
+ return result;
+}
+
+G_MODULE_EXPORT void
+peas_register_types (PeasObjectModule *module)
+{
+ gedit_file_browser_plugin_register_type (G_TYPE_MODULE (module));
+
+ peas_object_module_register_extension_type (module,
+ GEDIT_TYPE_WINDOW_ACTIVATABLE,
+ GEDIT_TYPE_FILE_BROWSER_PLUGIN);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-plugin.h b/plugins/filebrowser/gedit-file-browser-plugin.h
new file mode 100644
index 0000000..27fe706
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-plugin.h
@@ -0,0 +1,70 @@
+/*
+ * gedit-file-browser-plugin.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_FILE_BROWSER_PLUGIN_H
+#define GEDIT_FILE_BROWSER_PLUGIN_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libpeas/peas-extension-base.h>
+#include <libpeas/peas-object-module.h>
+
+G_BEGIN_DECLS
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_FILE_BROWSER_PLUGIN (gedit_file_browser_plugin_get_type ())
+#define GEDIT_FILE_BROWSER_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPlugin))
+#define GEDIT_FILE_BROWSER_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginClass))
+#define GEDIT_IS_FILE_BROWSER_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN))
+#define GEDIT_IS_FILE_BROWSER_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_FILE_BROWSER_PLUGIN))
+#define GEDIT_FILE_BROWSER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginClass))
+
+/* Private structure type */
+typedef struct _GeditFileBrowserPluginPrivate GeditFileBrowserPluginPrivate;
+typedef struct _GeditFileBrowserPlugin GeditFileBrowserPlugin;
+typedef struct _GeditFileBrowserPluginClass GeditFileBrowserPluginClass;
+
+struct _GeditFileBrowserPlugin
+{
+ GObject parent_instance;
+
+ /* < private > */
+ GeditFileBrowserPluginPrivate *priv;
+};
+
+struct _GeditFileBrowserPluginClass
+{
+ GObjectClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType gedit_file_browser_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_PLUGIN_H */
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-store.c b/plugins/filebrowser/gedit-file-browser-store.c
new file mode 100644
index 0000000..6a5ccaf
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-store.c
@@ -0,0 +1,3672 @@
+/*
+ * gedit-file-browser-store.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include <gedit/gedit-utils.h>
+
+#include "gedit-file-browser-store.h"
+#include "gedit-file-browser-enum-types.h"
+#include "gedit-file-browser-error.h"
+#include "gedit-file-browser-utils.h"
+
+#define NODE_IS_DIR(node) (FILE_IS_DIR((node)->flags))
+#define NODE_IS_HIDDEN(node) (FILE_IS_HIDDEN((node)->flags))
+#define NODE_IS_TEXT(node) (FILE_IS_TEXT((node)->flags))
+#define NODE_LOADED(node) (FILE_LOADED((node)->flags))
+#define NODE_IS_FILTERED(node) (FILE_IS_FILTERED((node)->flags))
+#define NODE_IS_DUMMY(node) (FILE_IS_DUMMY((node)->flags))
+
+#define FILE_BROWSER_NODE_DIR(node) ((FileBrowserNodeDir *)(node))
+
+#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100
+#define STANDARD_ATTRIBUTE_TYPES G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \
+ G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," \
+ G_FILE_ATTRIBUTE_STANDARD_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+ G_FILE_ATTRIBUTE_STANDARD_ICON
+
+typedef struct _FileBrowserNode FileBrowserNode;
+typedef struct _FileBrowserNodeDir FileBrowserNodeDir;
+typedef struct _AsyncData AsyncData;
+typedef struct _AsyncNode AsyncNode;
+
+typedef gint (*SortFunc) (FileBrowserNode *node1,
+ FileBrowserNode *node2);
+
+struct _AsyncData
+{
+ GeditFileBrowserStore *model;
+ GCancellable *cancellable;
+ gboolean trash;
+ GList *files;
+ GList *iter;
+ gboolean removed;
+};
+
+struct _AsyncNode
+{
+ FileBrowserNodeDir *dir;
+ GCancellable *cancellable;
+ GSList *original_children;
+};
+
+typedef struct {
+ GeditFileBrowserStore *model;
+ GFile *virtual_root;
+ GMountOperation *operation;
+ GCancellable *cancellable;
+} MountInfo;
+
+struct _FileBrowserNode
+{
+ GFile *file;
+ guint flags;
+ gchar *icon_name;
+ gchar *name;
+ gchar *markup;
+
+ GdkPixbuf *icon;
+ GdkPixbuf *emblem;
+
+ FileBrowserNode *parent;
+ gint pos;
+ gboolean inserted;
+};
+
+struct _FileBrowserNodeDir
+{
+ FileBrowserNode node;
+ GSList *children;
+
+ GCancellable *cancellable;
+ GFileMonitor *monitor;
+ GeditFileBrowserStore *model;
+};
+
+struct _GeditFileBrowserStorePrivate
+{
+ FileBrowserNode *root;
+ FileBrowserNode *virtual_root;
+ GType column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_NUM];
+
+ GeditFileBrowserStoreFilterMode filter_mode;
+ GeditFileBrowserStoreFilterFunc filter_func;
+ gpointer filter_user_data;
+
+ gchar **binary_patterns;
+ GPtrArray *binary_pattern_specs;
+
+ SortFunc sort_func;
+
+ GSList *async_handles;
+ MountInfo *mount_info;
+};
+
+static FileBrowserNode *model_find_node (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ GFile *uri);
+static void model_remove_node (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ GtkTreePath *path,
+ gboolean free_nodes);
+
+static void set_virtual_root_from_node (GeditFileBrowserStore *model,
+ FileBrowserNode *node);
+
+static void gedit_file_browser_store_iface_init (GtkTreeModelIface *iface);
+static GtkTreeModelFlags gedit_file_browser_store_get_flags (GtkTreeModel *tree_model);
+static gint gedit_file_browser_store_get_n_columns (GtkTreeModel *tree_model);
+static GType gedit_file_browser_store_get_column_type (GtkTreeModel *tree_model,
+ gint index);
+static gboolean gedit_file_browser_store_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path);
+static GtkTreePath *gedit_file_browser_store_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static void gedit_file_browser_store_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value);
+static gboolean gedit_file_browser_store_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gboolean gedit_file_browser_store_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent);
+static gboolean gedit_file_browser_store_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gint gedit_file_browser_store_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter);
+static gboolean gedit_file_browser_store_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n);
+static gboolean gedit_file_browser_store_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child);
+static void gedit_file_browser_store_row_inserted (GtkTreeModel *tree_model,
+ GtkTreePath *path,
+ GtkTreeIter *iter);
+
+static void gedit_file_browser_store_drag_source_init (GtkTreeDragSourceIface *iface);
+static gboolean gedit_file_browser_store_row_draggable (GtkTreeDragSource *drag_source,
+ GtkTreePath *path);
+static gboolean gedit_file_browser_store_drag_data_delete (GtkTreeDragSource *drag_source,
+ GtkTreePath *path);
+static gboolean gedit_file_browser_store_drag_data_get (GtkTreeDragSource *drag_source,
+ GtkTreePath *path,
+ GtkSelectionData *selection_data);
+
+static void file_browser_node_free (GeditFileBrowserStore *model,
+ FileBrowserNode *node);
+static void model_add_node (GeditFileBrowserStore *model,
+ FileBrowserNode *child,
+ FileBrowserNode *parent);
+static void model_clear (GeditFileBrowserStore *model,
+ gboolean free_nodes);
+static gint model_sort_default (FileBrowserNode *node1,
+ FileBrowserNode *node2);
+static void model_check_dummy (GeditFileBrowserStore *model,
+ FileBrowserNode *node);
+static void next_files_async (GFileEnumerator *enumerator,
+ AsyncNode *async);
+
+static void delete_files (AsyncData *data);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserStore, gedit_file_browser_store,
+ G_TYPE_OBJECT,
+ 0,
+ G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserStore)
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GTK_TYPE_TREE_MODEL,
+ gedit_file_browser_store_iface_init)
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GTK_TYPE_TREE_DRAG_SOURCE,
+ gedit_file_browser_store_drag_source_init))
+
+/* Properties */
+enum {
+ PROP_0,
+
+ PROP_ROOT,
+ PROP_VIRTUAL_ROOT,
+ PROP_FILTER_MODE,
+ PROP_BINARY_PATTERNS
+};
+
+/* Signals */
+enum
+{
+ BEGIN_LOADING,
+ END_LOADING,
+ ERROR,
+ NO_TRASH,
+ RENAME,
+ BEGIN_REFRESH,
+ END_REFRESH,
+ UNLOAD,
+ BEFORE_ROW_DELETED,
+ NUM_SIGNALS
+};
+
+static guint model_signals[NUM_SIGNALS] = { 0 };
+
+static void
+cancel_mount_operation (GeditFileBrowserStore *obj)
+{
+ if (obj->priv->mount_info != NULL)
+ {
+ obj->priv->mount_info->model = NULL;
+ g_cancellable_cancel (obj->priv->mount_info->cancellable);
+ obj->priv->mount_info = NULL;
+ }
+}
+
+static void
+gedit_file_browser_store_finalize (GObject *object)
+{
+ GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object);
+
+ /* Free all the nodes */
+ file_browser_node_free (obj, obj->priv->root);
+
+ if (obj->priv->binary_patterns != NULL)
+ {
+ g_strfreev (obj->priv->binary_patterns);
+ g_ptr_array_unref (obj->priv->binary_pattern_specs);
+ }
+
+ /* Cancel any asynchronous operations */
+ for (GSList *item = obj->priv->async_handles; item; item = item->next)
+ {
+ AsyncData *data = (AsyncData *)(item->data);
+ g_cancellable_cancel (data->cancellable);
+
+ data->removed = TRUE;
+ }
+
+ cancel_mount_operation (obj);
+
+ g_slist_free (obj->priv->async_handles);
+ G_OBJECT_CLASS (gedit_file_browser_store_parent_class)->finalize (object);
+}
+
+static void
+set_gvalue_from_node (GValue *value,
+ FileBrowserNode *node)
+{
+ if (node == NULL)
+ g_value_set_object (value, NULL);
+ else
+ g_value_set_object (value, node->file);
+}
+
+static void
+gedit_file_browser_store_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object);
+
+ switch (prop_id)
+ {
+ case PROP_ROOT:
+ set_gvalue_from_node (value, obj->priv->root);
+ break;
+ case PROP_VIRTUAL_ROOT:
+ set_gvalue_from_node (value, obj->priv->virtual_root);
+ break;
+ case PROP_FILTER_MODE:
+ g_value_set_flags (value, obj->priv->filter_mode);
+ break;
+ case PROP_BINARY_PATTERNS:
+ g_value_set_boxed (value, obj->priv->binary_patterns);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_store_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object);
+
+ switch (prop_id)
+ {
+ case PROP_ROOT:
+ gedit_file_browser_store_set_root (obj, G_FILE (g_value_get_object (value)));
+ break;
+ case PROP_FILTER_MODE:
+ gedit_file_browser_store_set_filter_mode (obj, g_value_get_flags (value));
+ break;
+ case PROP_BINARY_PATTERNS:
+ gedit_file_browser_store_set_binary_patterns (obj, g_value_get_boxed (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_store_class_init (GeditFileBrowserStoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gedit_file_browser_store_finalize;
+ object_class->get_property = gedit_file_browser_store_get_property;
+ object_class->set_property = gedit_file_browser_store_set_property;
+
+ g_object_class_install_property (object_class, PROP_ROOT,
+ g_param_spec_object ("root",
+ "Root",
+ "The root location",
+ G_TYPE_FILE,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_VIRTUAL_ROOT,
+ g_param_spec_object ("virtual-root",
+ "Virtual Root",
+ "The virtual root location",
+ G_TYPE_FILE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_FILTER_MODE,
+ g_param_spec_flags ("filter-mode",
+ "Filter Mode",
+ "The filter mode",
+ GEDIT_TYPE_FILE_BROWSER_STORE_FILTER_MODE,
+ gedit_file_browser_store_filter_mode_get_default (),
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_BINARY_PATTERNS,
+ g_param_spec_boxed ("binary-patterns",
+ "Binary Patterns",
+ "The binary patterns",
+ G_TYPE_STRV,
+ G_PARAM_READWRITE));
+
+ model_signals[BEGIN_LOADING] =
+ g_signal_new ("begin-loading",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass, begin_loading),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
+ model_signals[END_LOADING] =
+ g_signal_new ("end-loading",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass, end_loading),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
+ model_signals[ERROR] =
+ g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass, error),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
+ model_signals[NO_TRASH] =
+ g_signal_new ("no-trash", G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass, no_trash),
+ g_signal_accumulator_true_handled, NULL, NULL,
+ G_TYPE_BOOLEAN, 1, G_TYPE_POINTER);
+ model_signals[RENAME] =
+ g_signal_new ("rename",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass, rename),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_FILE, G_TYPE_FILE);
+ model_signals[BEGIN_REFRESH] =
+ g_signal_new ("begin-refresh",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass, begin_refresh),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+ model_signals[END_REFRESH] =
+ g_signal_new ("end-refresh",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass, end_refresh),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+ model_signals[UNLOAD] =
+ g_signal_new ("unload",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass, unload),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_FILE);
+ model_signals[BEFORE_ROW_DELETED] =
+ g_signal_new ("before-row-deleted",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass, before_row_deleted),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ GTK_TYPE_TREE_PATH | G_SIGNAL_TYPE_STATIC_SCOPE);
+}
+
+static void
+gedit_file_browser_store_class_finalize (GeditFileBrowserStoreClass *klass)
+{
+}
+
+static void
+gedit_file_browser_store_iface_init (GtkTreeModelIface *iface)
+{
+ iface->get_flags = gedit_file_browser_store_get_flags;
+ iface->get_n_columns = gedit_file_browser_store_get_n_columns;
+ iface->get_column_type = gedit_file_browser_store_get_column_type;
+ iface->get_iter = gedit_file_browser_store_get_iter;
+ iface->get_path = gedit_file_browser_store_get_path;
+ iface->get_value = gedit_file_browser_store_get_value;
+ iface->iter_next = gedit_file_browser_store_iter_next;
+ iface->iter_children = gedit_file_browser_store_iter_children;
+ iface->iter_has_child = gedit_file_browser_store_iter_has_child;
+ iface->iter_n_children = gedit_file_browser_store_iter_n_children;
+ iface->iter_nth_child = gedit_file_browser_store_iter_nth_child;
+ iface->iter_parent = gedit_file_browser_store_iter_parent;
+ iface->row_inserted = gedit_file_browser_store_row_inserted;
+}
+
+static void
+gedit_file_browser_store_drag_source_init (GtkTreeDragSourceIface *iface)
+{
+ iface->row_draggable = gedit_file_browser_store_row_draggable;
+ iface->drag_data_delete = gedit_file_browser_store_drag_data_delete;
+ iface->drag_data_get = gedit_file_browser_store_drag_data_get;
+}
+
+static void
+gedit_file_browser_store_init (GeditFileBrowserStore *obj)
+{
+ obj->priv = gedit_file_browser_store_get_instance_private (obj);
+
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION] = G_TYPE_FILE;
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP] = G_TYPE_STRING;
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS] = G_TYPE_UINT;
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_ICON] = GDK_TYPE_PIXBUF;
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_ICON_NAME] = G_TYPE_STRING;
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_NAME] = G_TYPE_STRING;
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM] = GDK_TYPE_PIXBUF;
+
+ /* Default filter mode is hiding the hidden files */
+ obj->priv->filter_mode = gedit_file_browser_store_filter_mode_get_default ();
+ obj->priv->sort_func = model_sort_default;
+}
+
+static gboolean
+node_has_parent (FileBrowserNode *node,
+ FileBrowserNode *parent)
+{
+ if (node->parent == NULL)
+ return FALSE;
+
+ if (node->parent == parent)
+ return TRUE;
+
+ return node_has_parent (node->parent, parent);
+}
+
+static gboolean
+node_in_tree (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ return node_has_parent (node, model->priv->virtual_root);
+}
+
+static gboolean
+model_node_visibility (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ if (node == NULL)
+ return FALSE;
+
+ if (NODE_IS_DUMMY (node))
+ return !NODE_IS_HIDDEN (node);
+
+ if (node == model->priv->virtual_root)
+ return TRUE;
+
+ if (!node_has_parent (node, model->priv->virtual_root))
+ return FALSE;
+
+ return !NODE_IS_FILTERED (node);
+}
+
+static gboolean
+model_node_inserted (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ return node == model->priv->virtual_root ||
+ (model_node_visibility (model, node) && node->inserted);
+}
+
+/* Interface implementation */
+
+static GtkTreeModelFlags
+gedit_file_browser_store_get_flags (GtkTreeModel *tree_model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), (GtkTreeModelFlags) 0);
+
+ return GTK_TREE_MODEL_ITERS_PERSIST;
+}
+
+static gint
+gedit_file_browser_store_get_n_columns (GtkTreeModel *tree_model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), 0);
+
+ return GEDIT_FILE_BROWSER_STORE_COLUMN_NUM;
+}
+
+static GType
+gedit_file_browser_store_get_column_type (GtkTreeModel *tree_model,
+ gint idx)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), G_TYPE_INVALID);
+ g_return_val_if_fail (idx < GEDIT_FILE_BROWSER_STORE_COLUMN_NUM && idx >= 0, G_TYPE_INVALID);
+
+ return GEDIT_FILE_BROWSER_STORE (tree_model)->priv->column_types[idx];
+}
+
+static gboolean
+gedit_file_browser_store_get_iter (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ GeditFileBrowserStore *model;
+ FileBrowserNode *node;
+ gint *indices, depth;
+
+ g_assert (GEDIT_IS_FILE_BROWSER_STORE (tree_model));
+ g_assert (path != NULL);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+ indices = gtk_tree_path_get_indices (path);
+ depth = gtk_tree_path_get_depth (path);
+ node = model->priv->virtual_root;
+
+ for (guint i = 0; i < depth; ++i)
+ {
+ GSList *item;
+ gint num = 0;
+
+ if (node == NULL)
+ return FALSE;
+
+ if (!NODE_IS_DIR (node))
+ return FALSE;
+
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next)
+ {
+ FileBrowserNode *child = (FileBrowserNode *)(item->data);
+
+ if (model_node_inserted (model, child))
+ {
+ if (num == indices[i])
+ break;
+
+ num++;
+ }
+ }
+
+ if (item == NULL)
+ return FALSE;
+
+ node = (FileBrowserNode *)(item->data);
+ }
+
+ iter->user_data = node;
+ iter->user_data2 = NULL;
+ iter->user_data3 = NULL;
+
+ return node != NULL;
+}
+
+static GtkTreePath *
+gedit_file_browser_store_get_path_real (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ GtkTreePath *path = gtk_tree_path_new ();
+ gint num = 0;
+
+ while (node != model->priv->virtual_root)
+ {
+ if (node->parent == NULL)
+ {
+ gtk_tree_path_free (path);
+ return NULL;
+ }
+
+ num = 0;
+
+ for (GSList *item = FILE_BROWSER_NODE_DIR (node->parent)->children; item; item = item->next)
+ {
+ FileBrowserNode *check = (FileBrowserNode *)(item->data);
+
+ if (model_node_visibility (model, check) && (check == node || check->inserted))
+ {
+ if (check == node)
+ {
+ gtk_tree_path_prepend_index (path, num);
+ break;
+ }
+
+ ++num;
+ }
+ else if (check == node)
+ {
+ if (NODE_IS_DUMMY (node))
+ g_warning ("Dummy not visible???");
+
+ gtk_tree_path_free (path);
+ return NULL;
+ }
+ }
+
+ node = node->parent;
+ }
+
+ return path;
+}
+
+static GtkTreePath *
+gedit_file_browser_store_get_path (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), NULL);
+ g_return_val_if_fail (iter != NULL, NULL);
+ g_return_val_if_fail (iter->user_data != NULL, NULL);
+
+ return gedit_file_browser_store_get_path_real (GEDIT_FILE_BROWSER_STORE (tree_model),
+ (FileBrowserNode *)(iter->user_data));
+}
+
+static void
+gedit_file_browser_store_get_value (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value)
+{
+ FileBrowserNode *node;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->user_data != NULL);
+
+ node = (FileBrowserNode *)(iter->user_data);
+
+ g_value_init (value, GEDIT_FILE_BROWSER_STORE (tree_model)->priv->column_types[column]);
+
+ switch (column)
+ {
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION:
+ set_gvalue_from_node (value, node);
+ break;
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP:
+ g_value_set_string (value, node->markup);
+ break;
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS:
+ g_value_set_uint (value, node->flags);
+ break;
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_ICON:
+ g_value_set_object (value, node->icon);
+ break;
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_ICON_NAME:
+ g_value_set_string (value, node->icon_name);
+ break;
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_NAME:
+ g_value_set_string (value, node->name);
+ break;
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM:
+ g_value_set_object (value, node->emblem);
+ break;
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static gboolean
+gedit_file_browser_store_iter_next (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ GeditFileBrowserStore *model;
+ FileBrowserNode *node;
+ GSList *first;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (iter->user_data != NULL, FALSE);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+ node = (FileBrowserNode *)(iter->user_data);
+
+ if (node->parent == NULL)
+ return FALSE;
+
+ first = g_slist_next (g_slist_find (FILE_BROWSER_NODE_DIR (node->parent)->children, node));
+
+ for (GSList *item = first; item; item = item->next)
+ {
+ if (model_node_inserted (model, (FileBrowserNode *)(item->data)))
+ {
+ iter->user_data = item->data;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gedit_file_browser_store_iter_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent)
+{
+ FileBrowserNode *node;
+ GeditFileBrowserStore *model;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE);
+ g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+
+ if (parent == NULL)
+ node = model->priv->virtual_root;
+ else
+ node = (FileBrowserNode *)(parent->user_data);
+
+ if (node == NULL)
+ return FALSE;
+
+ if (!NODE_IS_DIR (node))
+ return FALSE;
+
+ for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next)
+ {
+ if (model_node_inserted (model, (FileBrowserNode *)(item->data)))
+ {
+ iter->user_data = item->data;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+filter_tree_model_iter_has_child_real (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ if (!NODE_IS_DIR (node))
+ return FALSE;
+
+ for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next)
+ {
+ if (model_node_inserted (model, (FileBrowserNode *)(item->data)))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gedit_file_browser_store_iter_has_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ FileBrowserNode *node;
+ GeditFileBrowserStore *model;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE);
+ g_return_val_if_fail (iter == NULL || iter->user_data != NULL, FALSE);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+
+ if (iter == NULL)
+ node = model->priv->virtual_root;
+ else
+ node = (FileBrowserNode *)(iter->user_data);
+
+ return filter_tree_model_iter_has_child_real (model, node);
+}
+
+static gint
+gedit_file_browser_store_iter_n_children (GtkTreeModel *tree_model,
+ GtkTreeIter *iter)
+{
+ FileBrowserNode *node;
+ GeditFileBrowserStore *model;
+ gint num = 0;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE);
+ g_return_val_if_fail (iter == NULL || iter->user_data != NULL, FALSE);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+
+ if (iter == NULL)
+ node = model->priv->virtual_root;
+ else
+ node = (FileBrowserNode *)(iter->user_data);
+
+ if (!NODE_IS_DIR (node))
+ return 0;
+
+ for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next)
+ {
+ if (model_node_inserted (model, (FileBrowserNode *)(item->data)))
+ ++num;
+ }
+
+ return num;
+}
+
+static gboolean
+gedit_file_browser_store_iter_nth_child (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *parent,
+ gint n)
+{
+ FileBrowserNode *node;
+ GeditFileBrowserStore *model;
+ gint num = 0;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE);
+ g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+
+ if (parent == NULL)
+ node = model->priv->virtual_root;
+ else
+ node = (FileBrowserNode *)(parent->user_data);
+
+ if (!NODE_IS_DIR (node))
+ return FALSE;
+
+ for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next)
+ {
+ if (model_node_inserted (model, (FileBrowserNode *)(item->data)))
+ {
+ if (num == n)
+ {
+ iter->user_data = item->data;
+ return TRUE;
+ }
+
+ ++num;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gedit_file_browser_store_iter_parent (GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GtkTreeIter *child)
+{
+ FileBrowserNode *node;
+ GeditFileBrowserStore *model;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE);
+ g_return_val_if_fail (child != NULL, FALSE);
+ g_return_val_if_fail (child->user_data != NULL, FALSE);
+
+ node = (FileBrowserNode *)(child->user_data);
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+
+ if (!node_in_tree (model, node))
+ return FALSE;
+
+ if (node->parent == NULL)
+ return FALSE;
+
+ iter->user_data = node->parent;
+ return TRUE;
+}
+
+static void
+gedit_file_browser_store_row_inserted (GtkTreeModel *tree_model,
+ GtkTreePath *path,
+ GtkTreeIter *iter)
+{
+ FileBrowserNode *node = (FileBrowserNode *)(iter->user_data);
+
+ node->inserted = TRUE;
+}
+
+static gboolean
+gedit_file_browser_store_row_draggable (GtkTreeDragSource *drag_source,
+ GtkTreePath *path)
+{
+ GtkTreeIter iter;
+ GeditFileBrowserStoreFlag flags;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path))
+ return FALSE;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ return !FILE_IS_DUMMY (flags);
+}
+
+static gboolean
+gedit_file_browser_store_drag_data_delete (GtkTreeDragSource *drag_source,
+ GtkTreePath *path)
+{
+ return FALSE;
+}
+
+static gboolean
+gedit_file_browser_store_drag_data_get (GtkTreeDragSource *drag_source,
+ GtkTreePath *path,
+ GtkSelectionData *selection_data)
+{
+ GtkTreeIter iter;
+ GFile *location;
+ gchar *uris[2] = {0, };
+ gboolean ret;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path))
+ return FALSE;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ -1);
+
+ g_assert (location);
+
+ uris[0] = g_file_get_uri (location);
+ ret = gtk_selection_data_set_uris (selection_data, uris);
+
+ g_free (uris[0]);
+ g_object_unref (location);
+
+ return ret;
+}
+
+#define FILTER_HIDDEN(mode) (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN)
+#define FILTER_BINARY(mode) (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY)
+
+/* Private */
+static void
+model_begin_loading (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ GtkTreeIter iter;
+
+ iter.user_data = node;
+ g_signal_emit (model, model_signals[BEGIN_LOADING], 0, &iter);
+}
+
+static void
+model_end_loading (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ GtkTreeIter iter;
+
+ iter.user_data = node;
+ g_signal_emit (model, model_signals[END_LOADING], 0, &iter);
+}
+
+static void
+model_node_update_visibility (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ GtkTreeIter iter;
+
+ node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED;
+
+ if (FILTER_HIDDEN (model->priv->filter_mode) &&
+ NODE_IS_HIDDEN (node))
+ {
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED;
+ return;
+ }
+
+ if (FILTER_BINARY (model->priv->filter_mode) && !NODE_IS_DIR (node))
+ {
+ if (!NODE_IS_TEXT (node))
+ {
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED;
+ return;
+ }
+ else if (model->priv->binary_patterns != NULL)
+ {
+ gssize name_length = strlen (node->name);
+ gchar *name_reversed = g_utf8_strreverse (node->name, name_length);
+
+ for (guint i = 0; i < model->priv->binary_pattern_specs->len; ++i)
+ {
+ GPatternSpec *spec = g_ptr_array_index (model->priv->binary_pattern_specs, i);
+
+ if (g_pattern_match (spec, name_length, node->name, name_reversed))
+ {
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED;
+ g_free (name_reversed);
+ return;
+ }
+ }
+
+ g_free (name_reversed);
+ }
+ }
+
+ if (model->priv->filter_func)
+ {
+ iter.user_data = node;
+
+ if (!model->priv->filter_func (model, &iter, model->priv->filter_user_data))
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED;
+ }
+}
+
+static gint
+collate_nodes (FileBrowserNode *node1,
+ FileBrowserNode *node2)
+{
+ if (node1->name == NULL)
+ {
+ return -1;
+ }
+ else if (node2->name == NULL)
+ {
+ return 1;
+ }
+ else
+ {
+ gchar *k1 = g_utf8_collate_key_for_filename (node1->name, -1);
+ gchar *k2 = g_utf8_collate_key_for_filename (node2->name, -1);
+ gint result = strcmp (k1, k2);
+
+ g_free (k1);
+ g_free (k2);
+
+ return result;
+ }
+}
+
+static gint
+model_sort_default (FileBrowserNode *node1,
+ FileBrowserNode *node2)
+{
+ gint f1 = NODE_IS_DUMMY (node1);
+ gint f2 = NODE_IS_DUMMY (node2);
+
+ if (f1 && f2)
+ return 0;
+ else if (f1 || f2)
+ return f1 ? -1 : 1;
+
+ f1 = NODE_IS_DIR (node1);
+ f2 = NODE_IS_DIR (node2);
+
+ if (f1 != f2)
+ return f1 ? -1 : 1;
+
+ return collate_nodes (node1, node2);
+}
+
+static void
+model_resort_node (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node->parent);
+
+ if (!model_node_visibility (model, node->parent))
+ {
+ /* Just sort the children of the parent */
+ dir->children = g_slist_sort (dir->children, (GCompareFunc)(model->priv->sort_func));
+ }
+ else
+ {
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ gint *neworder;
+ gint pos = 0;
+
+ /* Store current positions */
+ for (GSList *item = dir->children; item; item = item->next)
+ {
+ FileBrowserNode *child = (FileBrowserNode *)(item->data);
+
+ if (model_node_visibility (model, child))
+ child->pos = pos++;
+ }
+
+ dir->children = g_slist_sort (dir->children, (GCompareFunc)(model->priv->sort_func));
+ neworder = g_new (gint, pos);
+ pos = 0;
+
+ /* Store the new positions */
+ for (GSList *item = dir->children; item; item = item->next)
+ {
+ FileBrowserNode *child = (FileBrowserNode *)(item->data);
+
+ if (model_node_visibility (model, child))
+ neworder[pos++] = child->pos;
+ }
+
+ iter.user_data = node->parent;
+ path = gedit_file_browser_store_get_path_real (model, node->parent);
+
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), path, &iter, neworder);
+
+ g_free (neworder);
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+row_changed (GeditFileBrowserStore *model,
+ GtkTreePath **path,
+ GtkTreeIter *iter)
+{
+ GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), *path);
+
+ /* Insert a copy of the actual path here because the row-inserted
+ signal may alter the path */
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (model), *path, iter);
+ gtk_tree_path_free (*path);
+
+ *path = gtk_tree_row_reference_get_path (ref);
+ gtk_tree_row_reference_free (ref);
+}
+
+static void
+row_inserted (GeditFileBrowserStore *model,
+ GtkTreePath **path,
+ GtkTreeIter *iter)
+{
+ /* This function creates a row reference for the path because it's
+ uncertain what might change the actual model/view when we insert
+ a node, maybe another directory load is triggered for example.
+ Because functions that use this function rely on the notion that
+ the path remains pointed towards the inserted node, we use the
+ reference to keep track. */
+ GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), *path);
+ GtkTreePath *copy = gtk_tree_path_copy (*path);
+
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), copy, iter);
+ gtk_tree_path_free (copy);
+
+ if (ref)
+ {
+ gtk_tree_path_free (*path);
+
+ /* To restore the path, we get the path from the reference. But, since
+ we inserted a row, the path will be one index further than the
+ actual path of our node. We therefore call gtk_tree_path_prev */
+ *path = gtk_tree_row_reference_get_path (ref);
+ gtk_tree_path_prev (*path);
+ }
+
+ gtk_tree_row_reference_free (ref);
+}
+
+static void
+row_deleted (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ const GtkTreePath *path)
+{
+ gboolean hidden;
+ GtkTreePath *copy;
+
+ /* We should always be called when the row is still inserted */
+ g_return_if_fail (node->inserted == TRUE || NODE_IS_DUMMY (node));
+
+ hidden = FILE_IS_HIDDEN (node->flags);
+ node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ /* Create temporary copies of the path as the signals may alter it */
+
+ copy = gtk_tree_path_copy (path);
+ g_signal_emit (model, model_signals[BEFORE_ROW_DELETED], 0, copy);
+ gtk_tree_path_free (copy);
+
+ node->inserted = FALSE;
+
+ if (hidden)
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ copy = gtk_tree_path_copy (path);
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), copy);
+ gtk_tree_path_free (copy);
+}
+
+static void
+model_refilter_node (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ GtkTreePath **path)
+{
+ gboolean old_visible;
+ gboolean new_visible;
+ FileBrowserNodeDir *dir;
+ GSList *item;
+ GtkTreeIter iter;
+ GtkTreePath *tmppath = NULL;
+ gboolean in_tree;
+
+ if (node == NULL)
+ return;
+
+ old_visible = model_node_visibility (model, node);
+ model_node_update_visibility (model, node);
+
+ in_tree = node_in_tree (model, node);
+
+ if (path == NULL)
+ {
+ if (in_tree)
+ tmppath = gedit_file_browser_store_get_path_real (model, node);
+ else
+ tmppath = gtk_tree_path_new_first ();
+
+ path = &tmppath;
+ }
+
+ if (NODE_IS_DIR (node))
+ {
+ if (in_tree)
+ gtk_tree_path_down (*path);
+
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ for (item = dir->children; item; item = item->next)
+ model_refilter_node (model, (FileBrowserNode *)(item->data), path);
+
+ if (in_tree)
+ gtk_tree_path_up (*path);
+ }
+
+ if (in_tree)
+ {
+ new_visible = model_node_visibility (model, node);
+
+ if (old_visible != new_visible)
+ {
+ if (old_visible)
+ {
+ row_deleted (model, node, *path);
+ }
+ else
+ {
+ iter.user_data = node;
+ row_inserted (model, path, &iter);
+ gtk_tree_path_next (*path);
+ }
+ }
+ else if (old_visible)
+ {
+ gtk_tree_path_next (*path);
+ }
+ }
+
+ model_check_dummy (model, node);
+
+ if (tmppath)
+ gtk_tree_path_free (tmppath);
+}
+
+static void
+model_refilter (GeditFileBrowserStore *model)
+{
+ model_refilter_node (model, model->priv->root, NULL);
+}
+
+static void
+file_browser_node_set_name (FileBrowserNode *node)
+{
+ g_free (node->name);
+ g_free (node->markup);
+
+ if (node->file)
+ node->name = gedit_file_browser_utils_file_basename (node->file);
+ else
+ node->name = NULL;
+
+ if (node->name)
+ node->markup = g_markup_escape_text (node->name, -1);
+ else
+ node->markup = NULL;
+}
+
+static void
+file_browser_node_init (FileBrowserNode *node,
+ GFile *file,
+ FileBrowserNode *parent)
+{
+ if (file != NULL)
+ {
+ node->file = g_object_ref (file);
+ file_browser_node_set_name (node);
+ }
+
+ node->parent = parent;
+}
+
+static FileBrowserNode *
+file_browser_node_new (GFile *file,
+ FileBrowserNode *parent)
+{
+ FileBrowserNode *node = g_slice_new0 (FileBrowserNode);
+
+ file_browser_node_init (node, file, parent);
+ return node;
+}
+
+static FileBrowserNode *
+file_browser_node_dir_new (GeditFileBrowserStore *model,
+ GFile *file,
+ FileBrowserNode *parent)
+{
+ FileBrowserNode *node = (FileBrowserNode *)g_slice_new0 (FileBrowserNodeDir);
+
+ file_browser_node_init (node, file, parent);
+
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY;
+
+ FILE_BROWSER_NODE_DIR (node)->model = model;
+
+ return node;
+}
+
+static void
+file_browser_node_free_children (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ if (node == NULL || !NODE_IS_DIR (node))
+ return;
+
+ for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next)
+ file_browser_node_free (model, (FileBrowserNode *)(item->data));
+
+ g_slist_free (FILE_BROWSER_NODE_DIR (node)->children);
+ FILE_BROWSER_NODE_DIR (node)->children = NULL;
+
+ /* This node is no longer loaded */
+ node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_LOADED;
+}
+
+static void
+file_browser_node_free (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ if (node == NULL)
+ return;
+
+ if (NODE_IS_DIR (node))
+ {
+ FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node);
+
+ if (dir->cancellable)
+ {
+ g_cancellable_cancel (dir->cancellable);
+ g_object_unref (dir->cancellable);
+
+ model_end_loading (model, node);
+ }
+
+ file_browser_node_free_children (model, node);
+
+ if (dir->monitor)
+ {
+ g_file_monitor_cancel (dir->monitor);
+ g_object_unref (dir->monitor);
+ }
+ }
+
+ if (node->file)
+ {
+ g_signal_emit (model, model_signals[UNLOAD], 0, node->file);
+ g_object_unref (node->file);
+ }
+
+ if (node->icon)
+ g_object_unref (node->icon);
+
+ if (node->emblem)
+ g_object_unref (node->emblem);
+
+ g_free (node->icon_name);
+ g_free (node->name);
+ g_free (node->markup);
+
+ if (NODE_IS_DIR (node))
+ g_slice_free (FileBrowserNodeDir, (FileBrowserNodeDir *)node);
+ else
+ g_slice_free (FileBrowserNode, (FileBrowserNode *)node);
+}
+
+/**
+ * model_remove_node_children:
+ * @model: the #GeditFileBrowserStore
+ * @node: the FileBrowserNode to remove
+ * @path: the path of the node, or NULL to let the path be calculated
+ * @free_nodes: whether to also remove the nodes from memory
+ *
+ * Removes all the children of node from the model. This function is used
+ * to remove the child nodes from the _model_. Don't use it to just free
+ * a node.
+ */
+static void
+model_remove_node_children (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ GtkTreePath *path,
+ gboolean free_nodes)
+{
+ FileBrowserNodeDir *dir;
+ GtkTreePath *path_child;
+ GSList *list;
+
+ if (node == NULL || !NODE_IS_DIR (node))
+ return;
+
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ if (dir->children == NULL)
+ return;
+
+ if (!model_node_visibility (model, node))
+ {
+ /* Node is invisible and therefore the children can just be freed */
+ if (free_nodes)
+ file_browser_node_free_children (model, node);
+
+ return;
+ }
+
+ if (path == NULL)
+ path_child = gedit_file_browser_store_get_path_real (model, node);
+ else
+ path_child = gtk_tree_path_copy (path);
+
+ gtk_tree_path_down (path_child);
+
+ list = g_slist_copy (dir->children);
+
+ for (GSList *item = list; item; item = item->next)
+ model_remove_node (model, (FileBrowserNode *)(item->data), path_child, free_nodes);
+
+ g_slist_free (list);
+ gtk_tree_path_free (path_child);
+}
+
+/**
+ * model_remove_node:
+ * @model: the #GeditFileBrowserStore
+ * @node: the FileBrowserNode to remove
+ * @path: the path to use to remove this node, or NULL to use the path
+ * calculated from the node itself
+ * @free_nodes: whether to also remove the nodes from memory
+ *
+ * Removes this node and all its children from the model. This function is used
+ * to remove the node from the _model_. Don't use it to just free
+ * a node.
+ */
+static void
+model_remove_node (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ GtkTreePath *path,
+ gboolean free_nodes)
+{
+ gboolean free_path = FALSE;
+ FileBrowserNode *parent;
+
+ if (path == NULL)
+ {
+ path = gedit_file_browser_store_get_path_real (model, node);
+ free_path = TRUE;
+ }
+
+ model_remove_node_children (model, node, path, free_nodes);
+
+ /* Only delete if the node is visible in the tree (but only when it's not the virtual root) */
+ if (model_node_visibility (model, node) && node != model->priv->virtual_root)
+ row_deleted (model, node, path);
+
+ if (free_path)
+ gtk_tree_path_free (path);
+
+ parent = node->parent;
+
+ /* Remove the node from the parents children list */
+ if (free_nodes && parent)
+ FILE_BROWSER_NODE_DIR (node->parent)->children =
+ g_slist_remove (FILE_BROWSER_NODE_DIR (node->parent)->children, node);
+
+ /* If this is the virtual root, than set the parent as the virtual root */
+ if (node == model->priv->virtual_root)
+ set_virtual_root_from_node (model, parent);
+ else if (parent && model_node_visibility (model, parent) && !(free_nodes && NODE_IS_DUMMY(node)))
+ model_check_dummy (model, parent);
+
+ /* Now free the node if necessary */
+ if (free_nodes)
+ file_browser_node_free (model, node);
+}
+
+/**
+ * model_clear:
+ * @model: the #GeditFileBrowserStore
+ * @free_nodes: whether to also remove the nodes from memory
+ *
+ * Removes all nodes from the model. This function is used
+ * to remove all the nodes from the _model_. Don't use it to just free the
+ * nodes in the model.
+ */
+static void
+model_clear (GeditFileBrowserStore *model,
+ gboolean free_nodes)
+{
+ GtkTreePath *path = gtk_tree_path_new ();
+
+ model_remove_node_children (model, model->priv->virtual_root, path, free_nodes);
+ gtk_tree_path_free (path);
+
+ /* Remove the dummy if there is one */
+ if (model->priv->virtual_root)
+ {
+ FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (model->priv->virtual_root);
+
+ if (dir->children != NULL)
+ {
+ FileBrowserNode *dummy = (FileBrowserNode *)(dir->children->data);
+
+ if (NODE_IS_DUMMY (dummy) && model_node_visibility (model, dummy))
+ {
+ path = gtk_tree_path_new_first ();
+ row_deleted (model, dummy, path);
+ gtk_tree_path_free (path);
+ }
+ }
+ }
+}
+
+static void
+file_browser_node_unload (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ gboolean remove_children)
+{
+ FileBrowserNodeDir *dir;
+
+ if (node == NULL)
+ return;
+
+ if (!NODE_IS_DIR (node) || !NODE_LOADED (node))
+ return;
+
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ if (remove_children)
+ model_remove_node_children (model, node, NULL, TRUE);
+
+ if (dir->cancellable)
+ {
+ g_cancellable_cancel (dir->cancellable);
+ g_object_unref (dir->cancellable);
+
+ model_end_loading (model, node);
+ dir->cancellable = NULL;
+ }
+
+ if (dir->monitor)
+ {
+ g_file_monitor_cancel (dir->monitor);
+ g_object_unref (dir->monitor);
+
+ dir->monitor = NULL;
+ }
+
+ node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_LOADED;
+}
+
+static void
+model_recomposite_icon_real (GeditFileBrowserStore *tree_model,
+ FileBrowserNode *node,
+ GFileInfo *info)
+{
+ GdkPixbuf *icon;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model));
+ g_return_if_fail (node != NULL);
+
+ if (node->file == NULL)
+ return;
+
+ if (info)
+ {
+ GIcon *gicon = g_file_info_get_icon (info);
+
+ if (gicon != NULL)
+ icon = gedit_file_browser_utils_pixbuf_from_icon (gicon, GTK_ICON_SIZE_MENU);
+ else
+ icon = NULL;
+ }
+ else
+ {
+ icon = gedit_file_browser_utils_pixbuf_from_file (node->file, GTK_ICON_SIZE_MENU, FALSE);
+ }
+
+ /* Fallback to the same icon as the file browser */
+ if (!icon)
+ icon = gedit_file_browser_utils_pixbuf_from_theme ("text-x-generic", GTK_ICON_SIZE_MENU);
+
+ if (node->icon)
+ g_object_unref (node->icon);
+
+ if (node->emblem)
+ {
+ gint icon_size;
+
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, NULL, &icon_size);
+
+ if (icon == NULL)
+ {
+ node->icon = gdk_pixbuf_new (gdk_pixbuf_get_colorspace (node->emblem),
+ gdk_pixbuf_get_has_alpha (node->emblem),
+ gdk_pixbuf_get_bits_per_sample (node->emblem),
+ icon_size,
+ icon_size);
+ }
+ else
+ {
+ node->icon = gdk_pixbuf_copy (icon);
+ g_object_unref (icon);
+ }
+
+ gdk_pixbuf_composite (node->emblem, node->icon,
+ icon_size - 10, icon_size - 10, 10,
+ 10, icon_size - 10, icon_size - 10,
+ 1, 1, GDK_INTERP_NEAREST, 255);
+ }
+ else
+ {
+ node->icon = icon;
+ }
+}
+
+static void
+model_recomposite_icon (GeditFileBrowserStore *tree_model,
+ GtkTreeIter *iter)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->user_data != NULL);
+
+ model_recomposite_icon_real (tree_model,
+ (FileBrowserNode *)(iter->user_data),
+ NULL);
+}
+
+static FileBrowserNode *
+model_create_dummy_node (GeditFileBrowserStore *model,
+ FileBrowserNode *parent)
+{
+ FileBrowserNode *dummy;
+
+ dummy = file_browser_node_new (NULL, parent);
+ dummy->name = g_strdup (_("(Empty)"));
+ dummy->markup = g_markup_escape_text (dummy->name, -1);
+
+ dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY;
+ dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ return dummy;
+}
+
+static FileBrowserNode *
+model_add_dummy_node (GeditFileBrowserStore *model,
+ FileBrowserNode *parent)
+{
+ FileBrowserNode *dummy;
+
+ dummy = model_create_dummy_node (model, parent);
+
+ if (model_node_visibility (model, parent))
+ dummy->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ model_add_node (model, dummy, parent);
+
+ return dummy;
+}
+
+static void
+model_check_dummy (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ /* Hide the dummy child if needed */
+ if (NODE_IS_DIR (node))
+ {
+ FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node);
+ FileBrowserNode *dummy;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ guint flags;
+
+ if (dir->children == NULL)
+ {
+ model_add_dummy_node (model, node);
+ return;
+ }
+
+ dummy = (FileBrowserNode *)(dir->children->data);
+
+ if (!NODE_IS_DUMMY (dummy))
+ {
+ dummy = model_create_dummy_node (model, node);
+ dir->children = g_slist_prepend (dir->children, dummy);
+ }
+
+ if (!model_node_visibility (model, node))
+ {
+ dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+ return;
+ }
+
+ /* Temporarily set the node to invisible to check
+ for real children */
+ flags = dummy->flags;
+ dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ if (!filter_tree_model_iter_has_child_real (model, node))
+ {
+ dummy->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ if (FILE_IS_HIDDEN (flags))
+ {
+ /* Was hidden, needs to be inserted */
+ iter.user_data = dummy;
+ path = gedit_file_browser_store_get_path_real (model, dummy);
+
+ row_inserted (model, &path, &iter);
+ gtk_tree_path_free (path);
+ }
+ }
+ else if (!FILE_IS_HIDDEN (flags))
+ {
+ /* Was shown, needs to be removed */
+
+ /* To get the path we need to set it to visible temporarily */
+ dummy->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+ path = gedit_file_browser_store_get_path_real (model, dummy);
+ dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ row_deleted (model, dummy, path);
+ gtk_tree_path_free (path);
+ }
+ }
+}
+
+static void
+insert_node_sorted (GeditFileBrowserStore *model,
+ FileBrowserNode *child,
+ FileBrowserNode *parent)
+{
+ FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent);
+
+ if (model->priv->sort_func == NULL)
+ dir->children = g_slist_append (dir->children, child);
+ else
+ dir->children = g_slist_insert_sorted (dir->children, child, (GCompareFunc)(model->priv->sort_func));
+}
+
+static void
+model_add_node (GeditFileBrowserStore *model,
+ FileBrowserNode *child,
+ FileBrowserNode *parent)
+{
+ /* Add child to parents children */
+ insert_node_sorted (model, child, parent);
+
+ if (model_node_visibility (model, parent) &&
+ model_node_visibility (model, child))
+ {
+ GtkTreePath *path = gedit_file_browser_store_get_path_real (model, child);
+ GtkTreeIter iter;
+
+ iter.user_data = child;
+
+ /* Emit row inserted */
+ row_inserted (model, &path, &iter);
+ gtk_tree_path_free (path);
+ }
+
+ model_check_dummy (model, parent);
+ model_check_dummy (model, child);
+}
+
+static void
+model_add_nodes_batch (GeditFileBrowserStore *model,
+ GSList *children,
+ FileBrowserNode *parent)
+{
+ FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent);
+ GSList *sorted_children = g_slist_sort (children, (GCompareFunc)model->priv->sort_func);
+ GSList *child = sorted_children;
+ GSList *prev = NULL;
+ GSList *l = dir->children;
+
+ model_check_dummy (model, parent);
+
+ while (child)
+ {
+ FileBrowserNode *node = child->data;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ /* Reached the end of the first list, just append the second */
+ if (l == NULL)
+ {
+ dir->children = g_slist_concat (dir->children, child);
+
+ for (l = child; l; l = l->next)
+ {
+ if (model_node_visibility (model, parent) &&
+ model_node_visibility (model, l->data))
+ {
+ iter.user_data = l->data;
+ path = gedit_file_browser_store_get_path_real (model, l->data);
+
+ /* Emit row inserted */
+ row_inserted (model, &path, &iter);
+ gtk_tree_path_free (path);
+ }
+
+ model_check_dummy (model, l->data);
+ }
+
+ break;
+ }
+
+ if (model->priv->sort_func (l->data, node) > 0)
+ {
+ GSList *next_child;
+
+ if (prev == NULL)
+ {
+ /* Prepend to the list */
+ dir->children = g_slist_prepend (dir->children, child);
+ }
+ else
+ {
+ prev->next = child;
+ }
+
+ next_child = child->next;
+ prev = child;
+ child->next = l;
+ child = next_child;
+
+ if (model_node_visibility (model, parent) &&
+ model_node_visibility (model, node))
+ {
+ iter.user_data = node;
+ path = gedit_file_browser_store_get_path_real (model, node);
+
+ /* Emit row inserted */
+ row_inserted (model, &path, &iter);
+ gtk_tree_path_free (path);
+ }
+
+ model_check_dummy (model, node);
+
+ /* Try again at the same l position with the next child */
+ }
+ else
+ {
+ /* Move to the next item in the list */
+ prev = l;
+ l = l->next;
+ }
+ }
+}
+
+static gchar const *
+backup_content_type (GFileInfo *info)
+{
+ gchar const *content;
+
+ if (!g_file_info_get_is_backup (info))
+ return NULL;
+
+ content = g_file_info_get_content_type (info);
+
+ if (!content || g_content_type_equals (content, "application/x-trash"))
+ return "text/plain";
+
+ return content;
+}
+
+static gboolean
+content_type_is_text (gchar const *content_type)
+{
+#ifdef G_OS_WIN32
+ gchar *mime;
+ gboolean ret;
+#endif
+
+ if (!content_type || g_content_type_is_unknown (content_type))
+ return TRUE;
+
+#ifndef G_OS_WIN32
+ return g_content_type_is_a (content_type, "text/plain");
+#else
+ if (g_content_type_is_a (content_type, "text"))
+ return TRUE;
+
+ /* This covers a rare case in which on Windows the PerceivedType is
+ not set to "text" but the Content Type is set to text/plain */
+ mime = g_content_type_get_mime_type (content_type);
+ ret = g_strcmp0 (mime, "text/plain");
+
+ g_free (mime);
+
+ return ret;
+#endif
+}
+
+static void
+file_browser_node_set_from_info (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ GFileInfo *info,
+ gboolean isadded)
+{
+ gchar const *content;
+ gboolean free_info = FALSE;
+ GtkTreePath *path;
+ gchar *uri;
+ GError *error = NULL;
+
+ if (info == NULL)
+ {
+ info = g_file_query_info (node->file,
+ STANDARD_ATTRIBUTE_TYPES,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ if (!info)
+ {
+ if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND))
+ {
+ uri = g_file_get_uri (node->file);
+ g_warning ("Could not get info for %s: %s", uri, error->message);
+ g_free (uri);
+ }
+
+ g_error_free (error);
+ return;
+ }
+
+ free_info = TRUE;
+ }
+
+ if (g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info))
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY;
+ }
+ else
+ {
+ if (!(content = backup_content_type (info)))
+ content = g_file_info_get_content_type (info);
+
+ if (content_type_is_text (content))
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT;
+ }
+
+ model_recomposite_icon_real (model, node, info);
+
+ if (free_info)
+ g_object_unref (info);
+
+ if (isadded)
+ {
+ path = gedit_file_browser_store_get_path_real (model, node);
+ model_refilter_node (model, node, &path);
+ gtk_tree_path_free (path);
+
+ model_check_dummy (model, node->parent);
+ }
+ else
+ {
+ model_node_update_visibility (model, node);
+ }
+}
+
+static FileBrowserNode *
+node_list_contains_file (GSList *children,
+ GFile *file)
+{
+ for (GSList *item = children; item; item = item->next)
+ {
+ FileBrowserNode *node = (FileBrowserNode *)(item->data);
+
+ if (node->file != NULL && g_file_equal (node->file, file))
+ return node;
+ }
+
+ return NULL;
+}
+
+static FileBrowserNode *
+model_add_node_from_file (GeditFileBrowserStore *model,
+ FileBrowserNode *parent,
+ GFile *file,
+ GFileInfo *info)
+{
+ FileBrowserNode *node;
+ gboolean free_info = FALSE;
+ GError *error = NULL;
+
+ if ((node = node_list_contains_file (FILE_BROWSER_NODE_DIR (parent)->children, file)) == NULL)
+ {
+ if (info == NULL)
+ {
+ info = g_file_query_info (file,
+ STANDARD_ATTRIBUTE_TYPES,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+ free_info = TRUE;
+ }
+
+ if (!info)
+ {
+ g_warning ("Error querying file info: %s", error->message);
+ g_error_free (error);
+
+ /* FIXME: What to do now then... */
+ node = file_browser_node_new (file, parent);
+ }
+ else if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ node = file_browser_node_dir_new (model, file, parent);
+ }
+ else
+ {
+ node = file_browser_node_new (file, parent);
+ }
+
+ file_browser_node_set_from_info (model, node, info, FALSE);
+ model_add_node (model, node, parent);
+
+ if (info && free_info)
+ g_object_unref (info);
+ }
+
+ return node;
+}
+
+/* We pass in a copy of the list of parent->children so that we do
+ * not have to check if a file already exists among the ones we just
+ * added */
+static void
+model_add_nodes_from_files (GeditFileBrowserStore *model,
+ FileBrowserNode *parent,
+ GSList *original_children,
+ GList *files)
+{
+ GSList *nodes = NULL;
+
+ for (GList *item = files; item; item = item->next)
+ {
+ GFileInfo *info = G_FILE_INFO (item->data);
+ GFileType type = g_file_info_get_file_type (info);
+ gchar const *name;
+ GFile *file;
+ FileBrowserNode *node;
+
+ /* Skip all non regular, non directory files */
+ if (type != G_FILE_TYPE_REGULAR &&
+ type != G_FILE_TYPE_DIRECTORY &&
+ type != G_FILE_TYPE_SYMBOLIC_LINK)
+ {
+ g_object_unref (info);
+ continue;
+ }
+
+ name = g_file_info_get_name (info);
+
+ /* Skip '.' and '..' directories */
+ if (type == G_FILE_TYPE_DIRECTORY &&
+ (strcmp (name, ".") == 0 ||
+ strcmp (name, "..") == 0))
+ {
+ g_object_unref (info);
+ continue;
+ }
+
+ file = g_file_get_child (parent->file, name);
+ if (!(node = node_list_contains_file (original_children, file)))
+ {
+ if (type == G_FILE_TYPE_DIRECTORY)
+ node = file_browser_node_dir_new (model, file, parent);
+ else
+ node = file_browser_node_new (file, parent);
+
+ file_browser_node_set_from_info (model, node, info, FALSE);
+
+ nodes = g_slist_prepend (nodes, node);
+ }
+
+ g_object_unref (file);
+ g_object_unref (info);
+ }
+
+ if (nodes)
+ model_add_nodes_batch (model, nodes, parent);
+}
+
+static FileBrowserNode *
+model_add_node_from_dir (GeditFileBrowserStore *model,
+ FileBrowserNode *parent,
+ GFile *file)
+{
+ FileBrowserNode *node;
+
+ /* Check if it already exists */
+ if ((node = node_list_contains_file (FILE_BROWSER_NODE_DIR (parent)->children, file)) == NULL)
+ {
+ node = file_browser_node_dir_new (model, file, parent);
+ file_browser_node_set_from_info (model, node, NULL, FALSE);
+
+ if (node->name == NULL)
+ file_browser_node_set_name (node);
+
+ node->icon_name = g_strdup ("folder-symbolic");
+
+ model_add_node (model, node, parent);
+ }
+
+ return node;
+}
+
+static void
+on_directory_monitor_event (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event_type,
+ FileBrowserNode *parent)
+{
+ FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent);
+ FileBrowserNode *node;
+
+ switch (event_type)
+ {
+ case G_FILE_MONITOR_EVENT_DELETED:
+ node = node_list_contains_file (dir->children, file);
+
+ if (node != NULL)
+ model_remove_node (dir->model, node, NULL, TRUE);
+ break;
+ case G_FILE_MONITOR_EVENT_CREATED:
+ if (g_file_query_exists (file, NULL))
+ model_add_node_from_file (dir->model, parent, file, NULL);
+
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+async_node_free (AsyncNode *async)
+{
+ g_object_unref (async->cancellable);
+ g_slist_free (async->original_children);
+ g_slice_free (AsyncNode, async);
+}
+
+static void
+model_iterate_next_files_cb (GFileEnumerator *enumerator,
+ GAsyncResult *result,
+ AsyncNode *async)
+{
+ GError *error = NULL;
+ GList *files = g_file_enumerator_next_files_finish (enumerator, result, &error);
+ FileBrowserNodeDir *dir = async->dir;
+ FileBrowserNode *parent = (FileBrowserNode *)dir;
+
+ if (files == NULL)
+ {
+ g_file_enumerator_close (enumerator, NULL, NULL);
+ g_object_unref (enumerator);
+ async_node_free (async);
+
+ if (!error)
+ {
+ /* We're done loading */
+ g_object_unref (dir->cancellable);
+ dir->cancellable = NULL;
+
+/*
+ * FIXME: This is temporarly, it is a bug in gio:
+ * http://bugzilla.gnome.org/show_bug.cgi?id=565924
+ */
+#ifndef G_OS_WIN32
+ if (g_file_is_native (parent->file) && dir->monitor == NULL)
+ {
+ dir->monitor = g_file_monitor_directory (parent->file,
+ G_FILE_MONITOR_NONE,
+ NULL,
+ NULL);
+ if (dir->monitor != NULL)
+ {
+ g_signal_connect (dir->monitor,
+ "changed",
+ G_CALLBACK (on_directory_monitor_event),
+ parent);
+ }
+ }
+#endif
+
+ model_check_dummy (dir->model, parent);
+ model_end_loading (dir->model, parent);
+ }
+ else
+ {
+ /* Simply return if we were cancelled */
+ if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)
+ return;
+
+ /* Otherwise handle the error appropriately */
+ g_signal_emit (dir->model,
+ model_signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY,
+ error->message);
+
+ file_browser_node_unload (dir->model, (FileBrowserNode *)parent, TRUE);
+ g_error_free (error);
+ }
+ }
+ else if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ /* Check cancel state manually */
+ g_file_enumerator_close (enumerator, NULL, NULL);
+ g_object_unref (enumerator);
+ async_node_free (async);
+ }
+ else
+ {
+ model_add_nodes_from_files (dir->model, parent, async->original_children, files);
+
+ g_list_free (files);
+ next_files_async (enumerator, async);
+ }
+}
+
+static void
+next_files_async (GFileEnumerator *enumerator,
+ AsyncNode *async)
+{
+ g_file_enumerator_next_files_async (enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_DEFAULT,
+ async->cancellable,
+ (GAsyncReadyCallback)model_iterate_next_files_cb,
+ async);
+}
+
+static void
+model_iterate_children_cb (GFile *file,
+ GAsyncResult *result,
+ AsyncNode *async)
+{
+ GError *error = NULL;
+ GFileEnumerator *enumerator;
+
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_node_free (async);
+ return;
+ }
+
+ if (!(enumerator = g_file_enumerate_children_finish (file, result, &error)))
+ {
+ /* Simply return if we were cancelled or if the dir is not there */
+ FileBrowserNodeDir *dir = async->dir;
+
+ /* Otherwise handle the error appropriately */
+ g_signal_emit (dir->model,
+ model_signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY,
+ error->message);
+
+ file_browser_node_unload (dir->model, (FileBrowserNode *)dir, TRUE);
+ g_error_free (error);
+ async_node_free (async);
+ }
+ else
+ {
+ next_files_async (enumerator, async);
+ }
+}
+
+static void
+model_load_directory (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ FileBrowserNodeDir *dir;
+ AsyncNode *async;
+
+ g_return_if_fail (NODE_IS_DIR (node));
+
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ /* Cancel a previous load */
+ if (dir->cancellable != NULL)
+ file_browser_node_unload (dir->model, node, TRUE);
+
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_LOADED;
+ model_begin_loading (model, node);
+
+ dir->cancellable = g_cancellable_new ();
+
+ async = g_slice_new (AsyncNode);
+ async->dir = dir;
+ async->cancellable = g_object_ref (dir->cancellable);
+ async->original_children = g_slist_copy (dir->children);
+
+ /* Start loading async */
+ g_file_enumerate_children_async (node->file,
+ STANDARD_ATTRIBUTE_TYPES,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ async->cancellable,
+ (GAsyncReadyCallback)model_iterate_children_cb,
+ async);
+}
+
+static GList *
+get_parent_files (GeditFileBrowserStore *model,
+ GFile *file)
+{
+ GList *result = NULL;
+
+ result = g_list_prepend (result, g_object_ref (file));
+
+ while ((file = g_file_get_parent (file)))
+ {
+ if (g_file_equal (file, model->priv->root->file))
+ {
+ g_object_unref (file);
+ break;
+ }
+
+ result = g_list_prepend (result, file);
+ }
+
+ return result;
+}
+
+static void
+model_fill (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ GtkTreePath **path)
+{
+ gboolean free_path = FALSE;
+ GtkTreeIter iter = {0,};
+ GSList *item;
+ FileBrowserNode *child;
+
+ if (node == NULL)
+ {
+ node = model->priv->virtual_root;
+ *path = gtk_tree_path_new ();
+ free_path = TRUE;
+ }
+
+ if (*path == NULL)
+ {
+ *path = gedit_file_browser_store_get_path_real (model, node);
+ free_path = TRUE;
+ }
+
+ if (!model_node_visibility (model, node))
+ {
+ if (free_path)
+ gtk_tree_path_free (*path);
+
+ return;
+ }
+
+ if (node != model->priv->virtual_root)
+ {
+ /* Insert node */
+ iter.user_data = node;
+ row_inserted(model, path, &iter);
+ }
+
+ if (NODE_IS_DIR (node))
+ {
+ /* Go to the first child */
+ gtk_tree_path_down (*path);
+
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next)
+ {
+ child = (FileBrowserNode *) (item->data);
+
+ if (model_node_visibility (model, child))
+ {
+ model_fill (model, child, path);
+
+ /* Increase path for next child */
+ gtk_tree_path_next (*path);
+ }
+ }
+
+ /* Move back up to node path */
+ gtk_tree_path_up (*path);
+ }
+
+ model_check_dummy (model, node);
+
+ if (free_path)
+ gtk_tree_path_free (*path);
+}
+
+static void
+set_virtual_root_from_node (GeditFileBrowserStore *model,
+ FileBrowserNode *node)
+{
+ FileBrowserNode *prev = node;
+ FileBrowserNode *next = prev->parent;
+ FileBrowserNode *check;
+ FileBrowserNodeDir *dir;
+ GSList *copy;
+ GtkTreePath *empty = NULL;
+
+ /* Free all the nodes below that we don't need in cache */
+ while (prev != model->priv->root)
+ {
+ dir = FILE_BROWSER_NODE_DIR (next);
+ copy = g_slist_copy (dir->children);
+
+ for (GSList *item = copy; item; item = item->next)
+ {
+ check = (FileBrowserNode *)(item->data);
+
+ if (prev == node)
+ {
+ /* Only free the children, keeping this depth in cache */
+ if (check != node)
+ {
+ file_browser_node_free_children (model, check);
+ file_browser_node_unload (model, check, FALSE);
+ }
+ }
+ else if (check != prev)
+ {
+ /* Only free when the node is not in the chain */
+ dir->children = g_slist_remove (dir->children, check);
+ file_browser_node_free (model, check);
+ }
+ }
+
+ if (prev != node)
+ file_browser_node_unload (model, next, FALSE);
+
+ g_slist_free (copy);
+ prev = next;
+ next = prev->parent;
+ }
+
+ /* Free all the nodes up that we don't need in cache */
+ for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next)
+ {
+ check = (FileBrowserNode *)(item->data);
+
+ if (NODE_IS_DIR (check))
+ {
+ for (copy = FILE_BROWSER_NODE_DIR (check)->children; copy; copy = copy->next)
+ {
+ file_browser_node_free_children (model, (FileBrowserNode*) (copy->data));
+ file_browser_node_unload (model, (FileBrowserNode*) (copy->data), FALSE);
+ }
+ }
+ else if (NODE_IS_DUMMY (check))
+ {
+ check->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+ }
+ }
+
+ /* Now finally, set the virtual root, and load it up! */
+ model->priv->virtual_root = node;
+
+ /* Notify that the virtual-root has changed before loading up new nodes so that the
+ "root_changed" signal can be emitted before any "inserted" signals */
+ g_object_notify (G_OBJECT (model), "virtual-root");
+
+ model_fill (model, NULL, &empty);
+
+ if (!NODE_LOADED (node))
+ model_load_directory (model, node);
+}
+
+static void
+set_virtual_root_from_file (GeditFileBrowserStore *model,
+ GFile *file)
+{
+ GList *files;
+ FileBrowserNode *parent;
+ GFile *check;
+
+ /* Always clear the model before altering the nodes */
+ model_clear (model, FALSE);
+
+ /* Create the node path, get all the uri's */
+ files = get_parent_files (model, file);
+ parent = model->priv->root;
+
+ for (GList *item = files; item; item = item->next)
+ {
+ check = G_FILE (item->data);
+
+ parent = model_add_node_from_dir (model, parent, check);
+ g_object_unref (check);
+ }
+
+ g_list_free (files);
+ set_virtual_root_from_node (model, parent);
+}
+
+static FileBrowserNode *
+model_find_node_children (GeditFileBrowserStore *model,
+ FileBrowserNode *parent,
+ GFile *file)
+{
+ FileBrowserNodeDir *dir;
+ FileBrowserNode *child;
+ FileBrowserNode *result;
+
+ if (!NODE_IS_DIR (parent))
+ return NULL;
+
+ dir = FILE_BROWSER_NODE_DIR (parent);
+
+ for (GSList *children = dir->children; children; children = children->next)
+ {
+ child = (FileBrowserNode *)(children->data);
+
+ result = model_find_node (model, child, file);
+
+ if (result)
+ return result;
+ }
+
+ return NULL;
+}
+
+static FileBrowserNode *
+model_find_node (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ GFile *file)
+{
+ if (node == NULL)
+ node = model->priv->root;
+
+ if (node->file && g_file_equal (node->file, file))
+ return node;
+
+ if (NODE_IS_DIR (node) && g_file_has_prefix (file, node->file))
+ return model_find_node_children (model, node, file);
+
+ return NULL;
+}
+
+static GQuark
+gedit_file_browser_store_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (G_UNLIKELY (quark == 0))
+ quark = g_quark_from_string ("gedit_file_browser_store_error");
+
+ return quark;
+}
+
+static GFile *
+unique_new_name (GFile *directory,
+ gchar const *name)
+{
+ GFile *newuri = NULL;
+ guint num = 0;
+ gchar *newname;
+
+ while (newuri == NULL || g_file_query_exists (newuri, NULL))
+ {
+ if (newuri != NULL)
+ g_object_unref (newuri);
+
+ if (num == 0)
+ newname = g_strdup (name);
+ else
+ newname = g_strdup_printf ("%s(%d)", name, num);
+
+ newuri = g_file_get_child (directory, newname);
+ g_free (newname);
+
+ ++num;
+ }
+
+ return newuri;
+}
+
+static GeditFileBrowserStoreResult
+model_root_mounted (GeditFileBrowserStore *model,
+ GFile *virtual_root)
+{
+ model_check_dummy (model, model->priv->root);
+ g_object_notify (G_OBJECT (model), "root");
+
+ if (virtual_root != NULL)
+ {
+ return gedit_file_browser_store_set_virtual_root_from_location
+ (model, virtual_root);
+ }
+ else
+ {
+ set_virtual_root_from_node (model, model->priv->root);
+ }
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+static void
+handle_root_error (GeditFileBrowserStore *model,
+ GError *error)
+{
+ FileBrowserNode *root;
+
+ g_signal_emit (model,
+ model_signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_SET_ROOT,
+ error->message);
+
+ /* Set the virtual root to the root */
+ root = model->priv->root;
+ model->priv->virtual_root = root;
+
+ /* Set the root to be loaded */
+ root->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_LOADED;
+
+ /* Check the dummy */
+ model_check_dummy (model, root);
+
+ g_object_notify (G_OBJECT (model), "root");
+ g_object_notify (G_OBJECT (model), "virtual-root");
+}
+
+static void
+mount_cb (GFile *file,
+ GAsyncResult *res,
+ MountInfo *mount_info)
+{
+ gboolean mounted;
+ GError *error = NULL;
+ GeditFileBrowserStore *model = mount_info->model;
+
+ mounted = g_file_mount_enclosing_volume_finish (file, res, &error);
+
+ if (mount_info->model)
+ {
+ model->priv->mount_info = NULL;
+ model_end_loading (model, model->priv->root);
+ }
+
+ if (!mount_info->model || g_cancellable_is_cancelled (mount_info->cancellable))
+ {
+ /* Reset because it might be reused? */
+ g_cancellable_reset (mount_info->cancellable);
+ }
+ else if (mounted)
+ {
+ model_root_mounted (model, mount_info->virtual_root);
+ }
+ else if (error->code != G_IO_ERROR_CANCELLED)
+ {
+ handle_root_error (model, error);
+ }
+
+ if (error)
+ g_error_free (error);
+
+ g_object_unref (mount_info->operation);
+ g_object_unref (mount_info->cancellable);
+
+ if (mount_info->virtual_root)
+ g_object_unref (mount_info->virtual_root);
+
+ g_slice_free (MountInfo, mount_info);
+}
+
+static GeditFileBrowserStoreResult
+model_mount_root (GeditFileBrowserStore *model,
+ GFile *virtual_root)
+{
+ GFileInfo *info;
+ GError *error = NULL;
+ MountInfo *mount_info;
+
+ info = g_file_query_info (model->priv->root->file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ if (!info)
+ {
+ if (error->code == G_IO_ERROR_NOT_MOUNTED)
+ {
+ /* Try to mount it */
+ FILE_BROWSER_NODE_DIR (model->priv->root)->cancellable = g_cancellable_new ();
+
+ mount_info = g_slice_new (MountInfo);
+ mount_info->model = model;
+ mount_info->virtual_root = g_file_dup (virtual_root);
+
+ /* FIXME: we should be setting the correct window */
+ mount_info->operation = gtk_mount_operation_new (NULL);
+ mount_info->cancellable = g_object_ref (FILE_BROWSER_NODE_DIR (model->priv->root)->cancellable);
+
+ model_begin_loading (model, model->priv->root);
+ g_file_mount_enclosing_volume (model->priv->root->file,
+ G_MOUNT_MOUNT_NONE,
+ mount_info->operation,
+ mount_info->cancellable,
+ (GAsyncReadyCallback)mount_cb,
+ mount_info);
+
+ model->priv->mount_info = mount_info;
+ return GEDIT_FILE_BROWSER_STORE_RESULT_MOUNTING;
+ }
+ else
+ {
+ handle_root_error (model, error);
+ }
+
+ g_error_free (error);
+ }
+ else
+ {
+ g_object_unref (info);
+
+ return model_root_mounted (model, virtual_root);
+ }
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+/* Public */
+GeditFileBrowserStore *
+gedit_file_browser_store_new (GFile *root)
+{
+ return GEDIT_FILE_BROWSER_STORE (g_object_new (GEDIT_TYPE_FILE_BROWSER_STORE,
+ "root", root,
+ NULL));
+}
+
+void
+gedit_file_browser_store_set_value (GeditFileBrowserStore *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value)
+{
+ gpointer data;
+ FileBrowserNode *node;
+ GtkTreePath *path;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->user_data != NULL);
+
+ node = (FileBrowserNode *)(iter->user_data);
+
+ if (column == GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP)
+ {
+ g_return_if_fail (G_VALUE_HOLDS_STRING (value));
+
+ data = g_value_dup_string (value);
+
+ if (!data)
+ data = g_strdup (node->name);
+
+ g_free (node->markup);
+ node->markup = data;
+ }
+ else if (column == GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM)
+ {
+ g_return_if_fail (G_VALUE_HOLDS_OBJECT (value));
+
+ data = g_value_get_object (value);
+
+ g_return_if_fail (GDK_IS_PIXBUF (data) || data == NULL);
+
+ if (node->emblem)
+ g_object_unref (node->emblem);
+
+ if (data)
+ node->emblem = g_object_ref (GDK_PIXBUF (data));
+ else
+ node->emblem = NULL;
+
+ model_recomposite_icon (tree_model, iter);
+ }
+ else
+ {
+ g_return_if_fail (column == GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP ||
+ column == GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM);
+ }
+
+ if (model_node_visibility (tree_model, node))
+ {
+ path = gedit_file_browser_store_get_path (GTK_TREE_MODEL (tree_model), iter);
+ row_changed (tree_model, &path, iter);
+ gtk_tree_path_free (path);
+ }
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore *model,
+ GtkTreeIter *iter)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+ g_return_val_if_fail (iter != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+ g_return_val_if_fail (iter->user_data != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ model_clear (model, FALSE);
+ set_virtual_root_from_node (model, (FileBrowserNode *)(iter->user_data));
+
+ return TRUE;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root_from_location (GeditFileBrowserStore *model,
+ GFile *root)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ if (root == NULL)
+ {
+ gchar *uri = g_file_get_uri (root);
+
+ g_warning ("Invalid uri (%s)", uri);
+ g_free (uri);
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+ }
+
+ /* Check if uri is already the virtual root */
+ if (model->priv->virtual_root && g_file_equal (model->priv->virtual_root->file, root))
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ /* Check if uri is the root itself */
+ if (g_file_equal (model->priv->root->file, root))
+ {
+ /* Always clear the model before altering the nodes */
+ model_clear (model, FALSE);
+ set_virtual_root_from_node (model, model->priv->root);
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+ }
+
+ if (!g_file_has_prefix (root, model->priv->root->file))
+ {
+ gchar *str = g_file_get_parse_name (model->priv->root->file);
+ gchar *str1 = g_file_get_parse_name (root);
+
+ g_warning ("Virtual root (%s) is not below actual root (%s)", str1, str);
+
+ g_free (str);
+ g_free (str1);
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_ERROR;
+ }
+
+ set_virtual_root_from_file (model, root);
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root_top (GeditFileBrowserStore *model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ if (model->priv->virtual_root == model->priv->root)
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ model_clear (model, FALSE);
+ set_virtual_root_from_node (model, model->priv->root);
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root_up (GeditFileBrowserStore *model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ if (model->priv->virtual_root == model->priv->root)
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ model_clear (model, FALSE);
+ set_virtual_root_from_node (model, model->priv->virtual_root->parent);
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+gboolean
+gedit_file_browser_store_get_iter_virtual_root (GeditFileBrowserStore *model,
+ GtkTreeIter *iter)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ if (model->priv->virtual_root == NULL)
+ return FALSE;
+
+ iter->user_data = model->priv->virtual_root;
+ return TRUE;
+}
+
+gboolean
+gedit_file_browser_store_get_iter_root (GeditFileBrowserStore *model,
+ GtkTreeIter *iter)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ if (model->priv->root == NULL)
+ return FALSE;
+
+ iter->user_data = model->priv->root;
+ return TRUE;
+}
+
+gboolean
+gedit_file_browser_store_iter_equal (GeditFileBrowserStore *model,
+ GtkTreeIter *iter1,
+ GtkTreeIter *iter2)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (iter1 != NULL, FALSE);
+ g_return_val_if_fail (iter2 != NULL, FALSE);
+ g_return_val_if_fail (iter1->user_data != NULL, FALSE);
+ g_return_val_if_fail (iter2->user_data != NULL, FALSE);
+
+ return (iter1->user_data == iter2->user_data);
+}
+
+void
+gedit_file_browser_store_cancel_mount_operation (GeditFileBrowserStore *store)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (store));
+
+ cancel_mount_operation (store);
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore *model,
+ GFile *root,
+ GFile *virtual_root)
+{
+ FileBrowserNode *node;
+ gboolean equal = FALSE;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ if (root == NULL && model->priv->root == NULL)
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ if (root != NULL && model->priv->root != NULL)
+ {
+ equal = g_file_equal (root, model->priv->root->file);
+
+ if (equal && virtual_root == NULL)
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+ }
+
+ if (virtual_root)
+ {
+ if (equal && g_file_equal (virtual_root, model->priv->virtual_root->file))
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+ }
+
+ /* Make sure to cancel any previous mount operations */
+ cancel_mount_operation (model);
+
+ /* Always clear the model before altering the nodes */
+ model_clear (model, TRUE);
+ file_browser_node_free (model, model->priv->root);
+
+ model->priv->root = NULL;
+ model->priv->virtual_root = NULL;
+
+ if (root != NULL)
+ {
+ /* Create the root node */
+ node = file_browser_node_dir_new (model, root, NULL);
+
+ model->priv->root = node;
+ return model_mount_root (model, virtual_root);
+ }
+ else
+ {
+ g_object_notify (G_OBJECT (model), "root");
+ g_object_notify (G_OBJECT (model), "virtual-root");
+ }
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_root (GeditFileBrowserStore *model,
+ GFile *root)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ return gedit_file_browser_store_set_root_and_virtual_root (model, root, NULL);
+}
+
+GFile *
+gedit_file_browser_store_get_root (GeditFileBrowserStore *model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), NULL);
+
+ if (model->priv->root == NULL || model->priv->root->file == NULL)
+ return NULL;
+ else
+ return g_file_dup (model->priv->root->file);
+}
+
+GFile *
+gedit_file_browser_store_get_virtual_root (GeditFileBrowserStore *model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), NULL);
+
+ if (model->priv->virtual_root == NULL || model->priv->virtual_root->file == NULL)
+ return NULL;
+ else
+ return g_file_dup (model->priv->virtual_root->file);
+}
+
+void
+_gedit_file_browser_store_iter_expanded (GeditFileBrowserStore *model,
+ GtkTreeIter *iter)
+{
+ FileBrowserNode *node;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->user_data != NULL);
+
+ node = (FileBrowserNode *)(iter->user_data);
+
+ if (NODE_IS_DIR (node) && !NODE_LOADED (node))
+ {
+ /* Load it now */
+ model_load_directory (model, node);
+ }
+}
+
+void
+_gedit_file_browser_store_iter_collapsed (GeditFileBrowserStore *model,
+ GtkTreeIter *iter)
+{
+ FileBrowserNode *node;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->user_data != NULL);
+
+ node = (FileBrowserNode *)(iter->user_data);
+
+ if (NODE_IS_DIR (node) && NODE_LOADED (node))
+ {
+ /* Unload children of the children, keeping 1 depth in cache */
+
+ for (GSList *item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next)
+ {
+ node = (FileBrowserNode *)(item->data);
+
+ if (NODE_IS_DIR (node) && NODE_LOADED (node))
+ {
+ file_browser_node_unload (model, node, TRUE);
+ model_check_dummy (model, node);
+ }
+ }
+ }
+}
+
+GeditFileBrowserStoreFilterMode
+gedit_file_browser_store_get_filter_mode (GeditFileBrowserStore *model)
+{
+ return model->priv->filter_mode;
+}
+
+void
+gedit_file_browser_store_set_filter_mode (GeditFileBrowserStore *model,
+ GeditFileBrowserStoreFilterMode mode)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+
+ if (model->priv->filter_mode == mode)
+ return;
+
+ model->priv->filter_mode = mode;
+ model_refilter (model);
+
+ g_object_notify (G_OBJECT (model), "filter-mode");
+}
+
+void
+gedit_file_browser_store_set_filter_func (GeditFileBrowserStore *model,
+ GeditFileBrowserStoreFilterFunc func,
+ gpointer user_data)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+
+ model->priv->filter_func = func;
+ model->priv->filter_user_data = user_data;
+ model_refilter (model);
+}
+
+const gchar * const *
+gedit_file_browser_store_get_binary_patterns (GeditFileBrowserStore *model)
+{
+ return (const gchar * const *)model->priv->binary_patterns;
+}
+
+void
+gedit_file_browser_store_set_binary_patterns (GeditFileBrowserStore *model,
+ const gchar **binary_patterns)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+
+ if (model->priv->binary_patterns != NULL)
+ {
+ g_strfreev (model->priv->binary_patterns);
+ g_ptr_array_unref (model->priv->binary_pattern_specs);
+ }
+
+ model->priv->binary_patterns = g_strdupv ((gchar **)binary_patterns);
+
+ if (binary_patterns == NULL)
+ {
+ model->priv->binary_pattern_specs = NULL;
+ }
+ else
+ {
+ gssize n_patterns = g_strv_length ((gchar **) binary_patterns);
+
+ model->priv->binary_pattern_specs = g_ptr_array_sized_new (n_patterns);
+ g_ptr_array_set_free_func (model->priv->binary_pattern_specs, (GDestroyNotify) g_pattern_spec_free);
+
+ for (guint i = 0; binary_patterns[i] != NULL; ++i)
+ g_ptr_array_add (model->priv->binary_pattern_specs, g_pattern_spec_new (binary_patterns[i]));
+ }
+
+ model_refilter (model);
+
+ g_object_notify (G_OBJECT (model), "binary-patterns");
+}
+
+void
+gedit_file_browser_store_refilter (GeditFileBrowserStore *model)
+{
+ model_refilter (model);
+}
+
+GeditFileBrowserStoreFilterMode
+gedit_file_browser_store_filter_mode_get_default (void)
+{
+ return GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
+}
+
+void
+gedit_file_browser_store_refresh (GeditFileBrowserStore *model)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+
+ if (model->priv->root == NULL || model->priv->virtual_root == NULL)
+ return;
+
+ /* Clear the model */
+ g_signal_emit (model, model_signals[BEGIN_REFRESH], 0);
+ file_browser_node_unload (model, model->priv->virtual_root, TRUE);
+ model_load_directory (model, model->priv->virtual_root);
+ g_signal_emit (model, model_signals[END_REFRESH], 0);
+}
+
+static void
+reparent_node (FileBrowserNode *node,
+ gboolean reparent)
+{
+ if (!node->file)
+ return;
+
+ if (reparent)
+ {
+ GFile *parent = node->parent->file;
+ gchar *base = g_file_get_basename (node->file);
+
+ g_object_unref (node->file);
+
+ node->file = g_file_get_child (parent, base);
+ g_free (base);
+ }
+
+ if (NODE_IS_DIR (node))
+ {
+ FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node);
+
+ for (GSList *child = dir->children; child; child = child->next)
+ reparent_node ((FileBrowserNode *)child->data, TRUE);
+ }
+}
+
+gboolean
+gedit_file_browser_store_rename (GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ const gchar *new_name,
+ GError **error)
+{
+ FileBrowserNode *node;
+ GFile *file;
+ GFile *parent;
+ GFile *previous;
+ GError *err = NULL;
+ GtkTreePath *path;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (iter->user_data != NULL, FALSE);
+
+ node = (FileBrowserNode *)(iter->user_data);
+
+ parent = g_file_get_parent (node->file);
+ g_return_val_if_fail (parent != NULL, FALSE);
+
+ file = g_file_get_child (parent, new_name);
+ g_object_unref (parent);
+
+ if (g_file_equal (node->file, file))
+ {
+ g_object_unref (file);
+ return TRUE;
+ }
+
+ if (g_file_move (node->file, file, G_FILE_COPY_NONE, NULL, NULL, NULL, &err))
+ {
+ previous = node->file;
+ node->file = file;
+
+ /* This makes sure the actual info for the node is requeried */
+ file_browser_node_set_name (node);
+ file_browser_node_set_from_info (model, node, NULL, TRUE);
+
+ reparent_node (node, FALSE);
+
+ if (model_node_visibility (model, node))
+ {
+ path = gedit_file_browser_store_get_path_real (model, node);
+ row_changed (model, &path, iter);
+ gtk_tree_path_free (path);
+
+ /* Reorder this item */
+ model_resort_node (model, node);
+ }
+ else
+ {
+ g_object_unref (previous);
+
+ if (error != NULL)
+ {
+ *error = g_error_new_literal (gedit_file_browser_store_error_quark (),
+ GEDIT_FILE_BROWSER_ERROR_RENAME,
+ _("The renamed file is currently filtered out. "
+ "You need to adjust your filter settings to "
+ "make the file visible"));
+ }
+
+ return FALSE;
+ }
+
+ g_signal_emit (model, model_signals[RENAME], 0, previous, node->file);
+
+ g_object_unref (previous);
+
+ return TRUE;
+ }
+ else
+ {
+ g_object_unref (file);
+
+ if (err)
+ {
+ if (error != NULL)
+ {
+ *error = g_error_new_literal (gedit_file_browser_store_error_quark (),
+ GEDIT_FILE_BROWSER_ERROR_RENAME,
+ err->message);
+ }
+
+ g_error_free (err);
+ }
+
+ return FALSE;
+ }
+}
+
+static void
+async_data_free (AsyncData *data)
+{
+ g_object_unref (data->cancellable);
+ g_list_free_full (data->files, g_object_unref);
+
+ if (!data->removed)
+ data->model->priv->async_handles = g_slist_remove (data->model->priv->async_handles, data);
+
+ g_slice_free (AsyncData, data);
+}
+
+static gboolean
+emit_no_trash (AsyncData *data)
+{
+ /* Emit the no trash error */
+ gboolean ret;
+
+ g_signal_emit (data->model, model_signals[NO_TRASH], 0, data->files, &ret);
+
+ return ret;
+}
+
+static void
+delete_file_finished (GFile *file,
+ GAsyncResult *res,
+ AsyncData *data)
+{
+ GError *error = NULL;
+ gboolean ok;
+
+ if (data->trash)
+ ok = g_file_trash_finish (file, res, &error);
+ else
+ ok = g_file_delete_finish (file, res, &error);
+
+ if (ok)
+ {
+ /* Remove the file from the model */
+ FileBrowserNode *node = model_find_node (data->model, NULL, file);
+
+ if (node != NULL)
+ model_remove_node (data->model, node, NULL, TRUE);
+
+ /* Process the next file */
+ data->iter = data->iter->next;
+ }
+ else if (!ok && error != NULL)
+ {
+ gint code = error->code;
+ g_error_free (error);
+
+ if (data->trash && code == G_IO_ERROR_NOT_SUPPORTED)
+ {
+ /* Trash is not supported on this system. Ask the user
+ * if he wants to delete completely the files instead.
+ */
+ if (emit_no_trash (data))
+ {
+ /* Changes this into a delete job */
+ data->trash = FALSE;
+ data->iter = data->files;
+ }
+ else
+ {
+ /* End the job */
+ async_data_free (data);
+ return;
+ }
+ }
+ else if (code == G_IO_ERROR_CANCELLED)
+ {
+ /* Job has been cancelled, end the job */
+ async_data_free (data);
+ return;
+ }
+ }
+
+ /* Continue the job */
+ delete_files (data);
+}
+
+static void
+delete_files (AsyncData *data)
+{
+ GFile *file;
+
+ /* Check if our job is done */
+ if (data->iter == NULL)
+ {
+ async_data_free (data);
+ return;
+ }
+
+ file = G_FILE (data->iter->data);
+
+ if (data->trash)
+ {
+ g_file_trash_async (file,
+ G_PRIORITY_DEFAULT,
+ data->cancellable,
+ (GAsyncReadyCallback)delete_file_finished,
+ data);
+ }
+ else
+ {
+ g_file_delete_async (file,
+ G_PRIORITY_DEFAULT,
+ data->cancellable,
+ (GAsyncReadyCallback)delete_file_finished,
+ data);
+ }
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_delete_all (GeditFileBrowserStore *model,
+ GList *rows,
+ gboolean trash)
+{
+ FileBrowserNode *node;
+ AsyncData *data;
+ GList *files = NULL;
+ GList *row;
+ GtkTreeIter iter;
+ GtkTreePath *prev = NULL;
+ GtkTreePath *path;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ if (rows == NULL)
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ /* First we sort the paths so that we can later on remove any
+ files/directories that are actually subfiles/directories of
+ a directory that's also deleted */
+ rows = g_list_sort (g_list_copy (rows), (GCompareFunc)gtk_tree_path_compare);
+
+ for (row = rows; row; row = row->next)
+ {
+ path = (GtkTreePath *)(row->data);
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
+ continue;
+
+ /* Skip if the current path is actually a descendant of the
+ previous path */
+ if (prev != NULL && gtk_tree_path_is_descendant (path, prev))
+ continue;
+
+ prev = path;
+ node = (FileBrowserNode *)(iter.user_data);
+ files = g_list_prepend (files, g_object_ref (node->file));
+ }
+
+ data = g_slice_new (AsyncData);
+
+ data->model = model;
+ data->cancellable = g_cancellable_new ();
+ data->files = files;
+ data->trash = trash;
+ data->iter = files;
+ data->removed = FALSE;
+
+ model->priv->async_handles = g_slist_prepend (model->priv->async_handles, data);
+
+ delete_files (data);
+ g_list_free (rows);
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_delete (GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ gboolean trash)
+{
+ FileBrowserNode *node;
+ GList *rows = NULL;
+ GeditFileBrowserStoreResult result;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+ g_return_val_if_fail (iter != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+ g_return_val_if_fail (iter->user_data != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ node = (FileBrowserNode *)(iter->user_data);
+
+ if (NODE_IS_DUMMY (node))
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ rows = g_list_append(NULL, gedit_file_browser_store_get_path_real (model, node));
+ result = gedit_file_browser_store_delete_all (model, rows, trash);
+
+ g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free);
+
+ return result;
+}
+
+gboolean
+gedit_file_browser_store_new_file (GeditFileBrowserStore *model,
+ GtkTreeIter *parent,
+ GtkTreeIter *iter)
+{
+ GFile *file;
+ GFileOutputStream *stream;
+ FileBrowserNodeDir *parent_node;
+ gboolean result = FALSE;
+ FileBrowserNode *node;
+ GError *error = NULL;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (parent != NULL, FALSE);
+ g_return_val_if_fail (parent->user_data != NULL, FALSE);
+ g_return_val_if_fail (NODE_IS_DIR ((FileBrowserNode *) (parent->user_data)), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ parent_node = FILE_BROWSER_NODE_DIR (parent->user_data);
+ /* Translators: This is the default name of new files created by the file browser pane. */
+ file = unique_new_name (((FileBrowserNode *) parent_node)->file, _("Untitled File"));
+
+ stream = g_file_create (file, G_FILE_CREATE_NONE, NULL, &error);
+
+ if (!stream)
+ {
+ g_signal_emit (model, model_signals[ERROR], 0,
+ GEDIT_FILE_BROWSER_ERROR_NEW_FILE,
+ error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ g_object_unref (stream);
+ node = model_add_node_from_file (model,
+ (FileBrowserNode *)parent_node,
+ file,
+ NULL);
+
+ if (model_node_visibility (model, node))
+ {
+ iter->user_data = node;
+ result = TRUE;
+ }
+ else
+ {
+ g_signal_emit (model, model_signals[ERROR], 0,
+ GEDIT_FILE_BROWSER_ERROR_NEW_FILE,
+ _("The new file is currently filtered out. "
+ "You need to adjust your filter "
+ "settings to make the file visible"));
+ }
+ }
+
+ g_object_unref (file);
+ return result;
+}
+
+gboolean
+gedit_file_browser_store_new_directory (GeditFileBrowserStore *model,
+ GtkTreeIter *parent,
+ GtkTreeIter *iter)
+{
+ GFile *file;
+ FileBrowserNodeDir *parent_node;
+ GError *error = NULL;
+ FileBrowserNode *node;
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (parent != NULL, FALSE);
+ g_return_val_if_fail (parent->user_data != NULL, FALSE);
+ g_return_val_if_fail (NODE_IS_DIR ((FileBrowserNode *)(parent->user_data)), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ parent_node = FILE_BROWSER_NODE_DIR (parent->user_data);
+ /* Translators: This is the default name of new directories created by the file browser pane. */
+ file = unique_new_name (((FileBrowserNode *) parent_node)->file, _("Untitled Folder"));
+
+ if (!g_file_make_directory (file, NULL, &error))
+ {
+ g_signal_emit (model, model_signals[ERROR], 0, GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY, error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ node = model_add_node_from_file (model,
+ (FileBrowserNode *)parent_node,
+ file,
+ NULL);
+
+ if (model_node_visibility (model, node))
+ {
+ iter->user_data = node;
+ result = TRUE;
+ }
+ else
+ {
+ g_signal_emit (model, model_signals[ERROR], 0,
+ GEDIT_FILE_BROWSER_ERROR_NEW_FILE,
+ _("The new directory is currently filtered "
+ "out. You need to adjust your filter "
+ "settings to make the directory visible"));
+ }
+ }
+
+ g_object_unref (file);
+ return result;
+}
+
+void
+_gedit_file_browser_store_register_type (GTypeModule *type_module)
+{
+ gedit_file_browser_store_register_type (type_module);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-store.h b/plugins/filebrowser/gedit-file-browser-store.h
new file mode 100644
index 0000000..02df0cb
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-store.h
@@ -0,0 +1,188 @@
+/*
+ * gedit-file-browser-store.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_FILE_BROWSER_STORE_H
+#define GEDIT_FILE_BROWSER_STORE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define GEDIT_TYPE_FILE_BROWSER_STORE (gedit_file_browser_store_get_type ())
+#define GEDIT_FILE_BROWSER_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStore))
+#define GEDIT_FILE_BROWSER_STORE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStore const))
+#define GEDIT_FILE_BROWSER_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStoreClass))
+#define GEDIT_IS_FILE_BROWSER_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_STORE))
+#define GEDIT_IS_FILE_BROWSER_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_STORE))
+#define GEDIT_FILE_BROWSER_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStoreClass))
+
+typedef enum
+{
+ GEDIT_FILE_BROWSER_STORE_COLUMN_ICON = 0,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_ICON_NAME,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS,
+
+ /* Columns not in common with GeditFileBookmarksStore */
+ GEDIT_FILE_BROWSER_STORE_COLUMN_NAME,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_NUM
+} GeditFileBrowserStoreColumn;
+
+typedef enum
+{
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY = 1 << 0,
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN = 1 << 1,
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT = 1 << 2,
+ GEDIT_FILE_BROWSER_STORE_FLAG_LOADED = 1 << 3,
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED = 1 << 4,
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY = 1 << 5
+} GeditFileBrowserStoreFlag;
+
+typedef enum
+{
+ GEDIT_FILE_BROWSER_STORE_RESULT_OK,
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE,
+ GEDIT_FILE_BROWSER_STORE_RESULT_ERROR,
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_TRASH,
+ GEDIT_FILE_BROWSER_STORE_RESULT_MOUNTING,
+ GEDIT_FILE_BROWSER_STORE_RESULT_NUM
+} GeditFileBrowserStoreResult;
+
+typedef enum
+{
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_NONE = 0,
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN = 1 << 0,
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY = 1 << 1
+} GeditFileBrowserStoreFilterMode;
+
+#define FILE_IS_DIR(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY)
+#define FILE_IS_HIDDEN(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN)
+#define FILE_IS_TEXT(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT)
+#define FILE_LOADED(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_LOADED)
+#define FILE_IS_FILTERED(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED)
+#define FILE_IS_DUMMY(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY)
+
+typedef struct _GeditFileBrowserStore GeditFileBrowserStore;
+typedef struct _GeditFileBrowserStoreClass GeditFileBrowserStoreClass;
+typedef struct _GeditFileBrowserStorePrivate GeditFileBrowserStorePrivate;
+
+typedef gboolean (*GeditFileBrowserStoreFilterFunc) (GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ gpointer user_data);
+
+struct _GeditFileBrowserStore
+{
+ GObject parent;
+
+ GeditFileBrowserStorePrivate *priv;
+};
+
+struct _GeditFileBrowserStoreClass {
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (* begin_loading) (GeditFileBrowserStore *model,
+ GtkTreeIter *iter);
+ void (* end_loading) (GeditFileBrowserStore *model,
+ GtkTreeIter *iter);
+ void (* error) (GeditFileBrowserStore *model,
+ guint code,
+ gchar *message);
+ gboolean (* no_trash) (GeditFileBrowserStore *model,
+ GList *files);
+ void (* rename) (GeditFileBrowserStore *model,
+ GFile *oldfile,
+ GFile *newfile);
+ void (* begin_refresh) (GeditFileBrowserStore *model);
+ void (* end_refresh) (GeditFileBrowserStore *model);
+ void (* unload) (GeditFileBrowserStore *model,
+ GFile *location);
+ void (* before_row_deleted) (GeditFileBrowserStore *model,
+ GtkTreePath *path);
+};
+
+GType gedit_file_browser_store_get_type (void) G_GNUC_CONST;
+
+GeditFileBrowserStore *gedit_file_browser_store_new (GFile *root);
+GeditFileBrowserStoreResult gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore *model,
+ GFile *root,
+ GFile *virtual_root);
+GeditFileBrowserStoreResult gedit_file_browser_store_set_root (GeditFileBrowserStore *model,
+ GFile *root);
+GeditFileBrowserStoreResult gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore *model,
+ GtkTreeIter *iter);
+GeditFileBrowserStoreResult gedit_file_browser_store_set_virtual_root_from_location (GeditFileBrowserStore *model,
+ GFile *root);
+GeditFileBrowserStoreResult gedit_file_browser_store_set_virtual_root_up (GeditFileBrowserStore *model);
+GeditFileBrowserStoreResult gedit_file_browser_store_set_virtual_root_top (GeditFileBrowserStore *model);
+gboolean gedit_file_browser_store_get_iter_virtual_root (GeditFileBrowserStore *model,
+ GtkTreeIter *iter);
+gboolean gedit_file_browser_store_get_iter_root (GeditFileBrowserStore *model,
+ GtkTreeIter *iter);
+GFile *gedit_file_browser_store_get_root (GeditFileBrowserStore *model);
+GFile *gedit_file_browser_store_get_virtual_root (GeditFileBrowserStore *model);
+gboolean gedit_file_browser_store_iter_equal (GeditFileBrowserStore *model,
+ GtkTreeIter *iter1,
+ GtkTreeIter *iter2);
+void gedit_file_browser_store_set_value (GeditFileBrowserStore *tree_model,
+ GtkTreeIter *iter,
+ gint column,
+ GValue *value);
+void _gedit_file_browser_store_iter_expanded (GeditFileBrowserStore *model,
+ GtkTreeIter *iter);
+void _gedit_file_browser_store_iter_collapsed (GeditFileBrowserStore *model,
+ GtkTreeIter *iter);
+GeditFileBrowserStoreFilterMode gedit_file_browser_store_get_filter_mode (GeditFileBrowserStore *model);
+void gedit_file_browser_store_set_filter_mode (GeditFileBrowserStore *model,
+ GeditFileBrowserStoreFilterMode mode);
+void gedit_file_browser_store_set_filter_func (GeditFileBrowserStore *model,
+ GeditFileBrowserStoreFilterFunc func,
+ gpointer user_data);
+const gchar * const *gedit_file_browser_store_get_binary_patterns (GeditFileBrowserStore *model);
+void gedit_file_browser_store_set_binary_patterns (GeditFileBrowserStore *model,
+ const gchar **binary_patterns);
+void gedit_file_browser_store_refilter (GeditFileBrowserStore *model);
+GeditFileBrowserStoreFilterMode gedit_file_browser_store_filter_mode_get_default (void);
+void gedit_file_browser_store_refresh (GeditFileBrowserStore *model);
+gboolean gedit_file_browser_store_rename (GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ gchar const *new_name,
+ GError **error);
+GeditFileBrowserStoreResult gedit_file_browser_store_delete (GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ gboolean trash);
+GeditFileBrowserStoreResult gedit_file_browser_store_delete_all (GeditFileBrowserStore *model,
+ GList *rows,
+ gboolean trash);
+gboolean gedit_file_browser_store_new_file (GeditFileBrowserStore *model,
+ GtkTreeIter *parent,
+ GtkTreeIter *iter);
+gboolean gedit_file_browser_store_new_directory (GeditFileBrowserStore *model,
+ GtkTreeIter *parent,
+ GtkTreeIter *iter);
+void gedit_file_browser_store_cancel_mount_operation (GeditFileBrowserStore *store);
+
+void _gedit_file_browser_store_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_STORE_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-utils.c b/plugins/filebrowser/gedit-file-browser-utils.c
new file mode 100644
index 0000000..dbca26a
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-utils.c
@@ -0,0 +1,223 @@
+/*
+ * gedit-file-bookmarks-utils.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gedit/gedit-utils.h>
+
+#include "gedit-file-browser-utils.h"
+
+static GdkPixbuf *
+process_icon_pixbuf (GdkPixbuf *pixbuf,
+ gchar const *name,
+ gint size,
+ GError *error)
+{
+ GdkPixbuf *scale;
+
+ if (error != NULL)
+ {
+ g_warning ("Could not load theme icon %s: %s",
+ name,
+ error->message);
+ g_error_free (error);
+ }
+
+ if (pixbuf && gdk_pixbuf_get_width (pixbuf) > size)
+ {
+ scale = gdk_pixbuf_scale_simple (pixbuf,
+ size,
+ size,
+ GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ pixbuf = scale;
+ }
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+gedit_file_browser_utils_pixbuf_from_theme (gchar const *name,
+ GtkIconSize size)
+{
+ gint width;
+ GError *error = NULL;
+ GdkPixbuf *pixbuf;
+
+ gtk_icon_size_lookup (size, &width, NULL);
+
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ name,
+ width,
+ 0,
+ &error);
+
+ pixbuf = process_icon_pixbuf (pixbuf, name, width, error);
+
+ return pixbuf;
+}
+
+GdkPixbuf *
+gedit_file_browser_utils_pixbuf_from_icon (GIcon *icon,
+ GtkIconSize size)
+{
+ GdkPixbuf *ret = NULL;
+ GtkIconTheme *theme;
+ GtkIconInfo *info;
+ gint width;
+
+ if (!icon)
+ return NULL;
+
+ theme = gtk_icon_theme_get_default ();
+ gtk_icon_size_lookup (size, &width, NULL);
+
+ info = gtk_icon_theme_lookup_by_gicon (theme,
+ icon,
+ width,
+ GTK_ICON_LOOKUP_USE_BUILTIN);
+
+ if (!info)
+ return NULL;
+
+ ret = gtk_icon_info_load_icon (info, NULL);
+ g_object_unref (info);
+
+ return ret;
+}
+
+GdkPixbuf *
+gedit_file_browser_utils_pixbuf_from_file (GFile *file,
+ GtkIconSize size,
+ gboolean use_symbolic)
+{
+ GIcon *icon;
+ GFileInfo *info;
+ GdkPixbuf *ret = NULL;
+ const char *attribute;
+
+ attribute = use_symbolic ? G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON :
+ G_FILE_ATTRIBUTE_STANDARD_ICON;
+
+ info = g_file_query_info (file,
+ attribute,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (!info)
+ return NULL;
+
+ icon = use_symbolic ? g_file_info_get_symbolic_icon (info) :
+ g_file_info_get_icon (info);
+ if (icon != NULL)
+ ret = gedit_file_browser_utils_pixbuf_from_icon (icon, size);
+
+ g_object_unref (info);
+
+ return ret;
+}
+
+gchar *
+gedit_file_browser_utils_symbolic_icon_name_from_file (GFile *file)
+{
+ GFileInfo *info;
+ GIcon *icon;
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (!info)
+ return NULL;
+
+ if ((icon = g_file_info_get_symbolic_icon (info)) && G_IS_THEMED_ICON (icon))
+ {
+ const gchar * const *names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+ return g_strdup (names[0]);
+ }
+
+ g_object_unref (info);
+ return NULL;
+}
+
+gchar *
+gedit_file_browser_utils_name_from_themed_icon (GIcon *icon)
+{
+ GtkIconTheme *theme;
+ const gchar * const *names;
+
+ if (!G_IS_THEMED_ICON (icon))
+ return NULL;
+
+ theme = gtk_icon_theme_get_default ();
+ names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+
+ if (gtk_icon_theme_has_icon (theme, names[0]))
+ return g_strdup (names[0]);
+
+ return NULL;
+}
+
+gchar *
+gedit_file_browser_utils_file_basename (GFile *file)
+{
+ return gedit_utils_basename_for_display (file);
+}
+
+gboolean
+gedit_file_browser_utils_confirmation_dialog (GeditWindow *window,
+ GtkMessageType type,
+ gchar const *message,
+ gchar const *secondary,
+ gchar const *button_label)
+{
+ GtkWidget *dlg;
+ gint ret;
+
+ dlg = gtk_message_dialog_new (GTK_WINDOW (window),
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ type,
+ GTK_BUTTONS_NONE, "%s", message);
+
+ if (secondary)
+ {
+ gtk_message_dialog_format_secondary_text
+ (GTK_MESSAGE_DIALOG (dlg), "%s", secondary);
+ }
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dlg),
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ button_label, GTK_RESPONSE_OK,
+ NULL);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_CANCEL);
+
+ ret = gtk_dialog_run (GTK_DIALOG (dlg));
+ gtk_widget_destroy (dlg);
+
+ return (ret == GTK_RESPONSE_OK);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-utils.h b/plugins/filebrowser/gedit-file-browser-utils.h
new file mode 100644
index 0000000..165f513
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-utils.h
@@ -0,0 +1,46 @@
+/*
+ * gedit-file-browser-utils.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_FILE_BROWSER_UTILS_H
+#define GEDIT_FILE_BROWSER_UTILS_H
+
+#include <gedit/gedit-window.h>
+#include <gio/gio.h>
+
+gchar *gedit_file_browser_utils_name_from_themed_icon (GIcon *icon);
+GdkPixbuf *gedit_file_browser_utils_pixbuf_from_theme (gchar const *name,
+ GtkIconSize size);
+
+GdkPixbuf *gedit_file_browser_utils_pixbuf_from_icon (GIcon *icon,
+ GtkIconSize size);
+GdkPixbuf *gedit_file_browser_utils_pixbuf_from_file (GFile *file,
+ GtkIconSize size,
+ gboolean use_symbolic);
+gchar *gedit_file_browser_utils_symbolic_icon_name_from_file (GFile *file);
+gchar *gedit_file_browser_utils_file_basename (GFile *file);
+
+gboolean gedit_file_browser_utils_confirmation_dialog (GeditWindow *window,
+ GtkMessageType type,
+ gchar const *message,
+ gchar const *secondary,
+ gchar const *button_label);
+
+#endif /* GEDIT_FILE_BROWSER_UTILS_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-view.c b/plugins/filebrowser/gedit-file-browser-view.c
new file mode 100644
index 0000000..37a0a23
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-view.c
@@ -0,0 +1,1323 @@
+/*
+ * gedit-file-browser-view.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "gedit-file-browser-store.h"
+#include "gedit-file-bookmarks-store.h"
+#include "gedit-file-browser-view.h"
+#include "gedit-file-browser-enum-types.h"
+
+struct _GeditFileBrowserViewPrivate
+{
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *pixbuf_renderer;
+ GtkCellRenderer *text_renderer;
+
+ GtkTreeModel *model;
+
+ /* Used when renaming */
+ gchar *orig_markup;
+ GtkTreeRowReference *editable;
+
+ /* Click policy */
+ GeditFileBrowserViewClickPolicy click_policy;
+ /* Both clicks in a double click need to be on the same row */
+ GtkTreePath *double_click_path[2];
+ GtkTreePath *hover_path;
+ GdkCursor *hand_cursor;
+ gboolean ignore_release;
+ gboolean selected_on_button_down;
+ gint drag_button;
+ gboolean drag_started;
+
+ gboolean restore_expand_state;
+ gboolean is_refresh;
+ GHashTable *expand_state;
+};
+
+/* Properties */
+enum
+{
+ PROP_0,
+
+ PROP_CLICK_POLICY,
+ PROP_RESTORE_EXPAND_STATE
+};
+
+/* Signals */
+enum
+{
+ ERROR,
+ FILE_ACTIVATED,
+ DIRECTORY_ACTIVATED,
+ BOOKMARK_ACTIVATED,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0 };
+
+static const GtkTargetEntry drag_source_targets[] = {
+ { "text/uri-list", 0, 0 }
+};
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserView,
+ gedit_file_browser_view,
+ GTK_TYPE_TREE_VIEW,
+ 0,
+ G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserView))
+
+static void on_cell_edited (GtkCellRendererText *cell,
+ gchar *path,
+ gchar *new_text,
+ GeditFileBrowserView *tree_view);
+
+static void on_begin_refresh (GeditFileBrowserStore *model,
+ GeditFileBrowserView *view);
+static void on_end_refresh (GeditFileBrowserStore *model,
+ GeditFileBrowserView *view);
+
+static void on_unload (GeditFileBrowserStore *model,
+ GFile *location,
+ GeditFileBrowserView *view);
+
+static void on_row_inserted (GeditFileBrowserStore *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GeditFileBrowserView *view);
+
+static void
+gedit_file_browser_view_finalize (GObject *object)
+{
+ GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW (object);
+
+ if (obj->priv->hand_cursor)
+ g_object_unref (obj->priv->hand_cursor);
+
+ if (obj->priv->hover_path)
+ gtk_tree_path_free (obj->priv->hover_path);
+
+ if (obj->priv->expand_state)
+ {
+ g_hash_table_destroy (obj->priv->expand_state);
+ obj->priv->expand_state = NULL;
+ }
+
+ G_OBJECT_CLASS (gedit_file_browser_view_parent_class)->finalize (object);
+}
+
+static void
+add_expand_state (GeditFileBrowserView *view,
+ GFile *location)
+{
+ if (!location)
+ return;
+
+ if (view->priv->expand_state)
+ g_hash_table_insert (view->priv->expand_state, location, g_object_ref (location));
+}
+
+static void
+remove_expand_state (GeditFileBrowserView *view,
+ GFile *location)
+{
+ if (!location)
+ return;
+
+ if (view->priv->expand_state)
+ g_hash_table_remove (view->priv->expand_state, location);
+}
+
+static void
+row_expanded (GtkTreeView *tree_view,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (tree_view);
+
+ if (GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_expanded)
+ GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_expanded (tree_view, iter, path);
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ return;
+
+ if (view->priv->restore_expand_state)
+ {
+ GFile *location;
+
+ gtk_tree_model_get (view->priv->model,
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ -1);
+
+ add_expand_state (view, location);
+
+ if (location)
+ g_object_unref (location);
+ }
+
+ _gedit_file_browser_store_iter_expanded (GEDIT_FILE_BROWSER_STORE (view->priv->model),
+ iter);
+}
+
+static void
+row_collapsed (GtkTreeView *tree_view,
+ GtkTreeIter *iter,
+ GtkTreePath *path)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (tree_view);
+
+ if (GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_collapsed)
+ GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_collapsed (tree_view, iter, path);
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ return;
+
+ if (view->priv->restore_expand_state)
+ {
+ GFile *location;
+
+ gtk_tree_model_get (view->priv->model,
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ -1);
+
+ remove_expand_state (view, location);
+
+ if (location)
+ g_object_unref (location);
+ }
+
+ _gedit_file_browser_store_iter_collapsed (GEDIT_FILE_BROWSER_STORE (view->priv->model),
+ iter);
+}
+
+static gboolean
+leave_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+
+ if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE &&
+ view->priv->hover_path != NULL)
+ {
+ gtk_tree_path_free (view->priv->hover_path);
+ view->priv->hover_path = NULL;
+ }
+
+ /* Chainup */
+ return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->leave_notify_event (widget, event);
+}
+
+static gboolean
+enter_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+
+ if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE)
+ {
+ if (view->priv->hover_path != NULL)
+ gtk_tree_path_free (view->priv->hover_path);
+
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ event->x, event->y,
+ &view->priv->hover_path,
+ NULL, NULL, NULL);
+
+ if (view->priv->hover_path != NULL)
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget),
+ view->priv->hand_cursor);
+ }
+ }
+
+ /* Chainup */
+ return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->enter_notify_event (widget, event);
+}
+
+static gboolean
+motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ GtkTreePath *old_hover_path;
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+
+ if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE)
+ {
+ old_hover_path = view->priv->hover_path;
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ event->x, event->y,
+ &view->priv->hover_path,
+ NULL, NULL, NULL);
+
+ if ((old_hover_path != NULL) != (view->priv->hover_path != NULL))
+ {
+ if (view->priv->hover_path != NULL)
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget),
+ view->priv->hand_cursor);
+ }
+ else
+ {
+ gdk_window_set_cursor (gtk_widget_get_window (widget),
+ NULL);
+ }
+ }
+
+ if (old_hover_path != NULL)
+ gtk_tree_path_free (old_hover_path);
+ }
+
+ /* Chainup */
+ return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->motion_notify_event (widget, event);
+}
+
+static void
+set_click_policy_property (GeditFileBrowserView *obj,
+ GeditFileBrowserViewClickPolicy click_policy)
+{
+ GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (obj));
+
+ obj->priv->click_policy = click_policy;
+
+ if (click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE)
+ {
+ if (obj->priv->hand_cursor == NULL)
+ obj->priv->hand_cursor = gdk_cursor_new_from_name (display, "pointer");
+ }
+ else if (click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE)
+ {
+ if (obj->priv->hover_path != NULL)
+ {
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (obj->priv->model),
+ &iter, obj->priv->hover_path))
+ {
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (obj->priv->model),
+ obj->priv->hover_path, &iter);
+ }
+
+ gtk_tree_path_free (obj->priv->hover_path);
+ obj->priv->hover_path = NULL;
+ }
+
+ if (gtk_widget_get_realized (GTK_WIDGET (obj)))
+ {
+ GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (obj));
+
+ gdk_window_set_cursor (win, NULL);
+
+ if (display != NULL)
+ gdk_display_flush (display);
+ }
+
+ if (obj->priv->hand_cursor)
+ {
+ g_object_unref (obj->priv->hand_cursor);
+ obj->priv->hand_cursor = NULL;
+ }
+ }
+}
+
+static void
+directory_activated (GeditFileBrowserView *view,
+ GtkTreeIter *iter)
+{
+ gedit_file_browser_store_set_virtual_root (GEDIT_FILE_BROWSER_STORE (view->priv->model), iter);
+}
+
+static void
+activate_selected_files (GeditFileBrowserView *view)
+{
+ GtkTreeView *tree_view = GTK_TREE_VIEW (view);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
+ GList *rows = gtk_tree_selection_get_selected_rows (selection, &view->priv->model);
+ GList *row;
+ GtkTreePath *directory = NULL;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GeditFileBrowserStoreFlag flags;
+
+ for (row = rows; row; row = row->next)
+ {
+ path = (GtkTreePath *)(row->data);
+
+ /* Get iter from path */
+ if (!gtk_tree_model_get_iter (view->priv->model, &iter, path))
+ continue;
+
+ gtk_tree_model_get (view->priv->model, &iter, GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, -1);
+
+ if (FILE_IS_DIR (flags) && directory == NULL)
+ directory = path;
+ else if (!FILE_IS_DUMMY (flags))
+ g_signal_emit (view, signals[FILE_ACTIVATED], 0, &iter);
+ }
+
+ if (directory != NULL &&
+ gtk_tree_model_get_iter (view->priv->model, &iter, directory))
+ {
+ g_signal_emit (view, signals[DIRECTORY_ACTIVATED], 0, &iter);
+ }
+
+ g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free);
+}
+
+static void
+activate_selected_bookmark (GeditFileBrowserView *view)
+{
+ GtkTreeView *tree_view = GTK_TREE_VIEW (view);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
+ GtkTreeIter iter;
+
+ if (gtk_tree_selection_get_selected (selection, &view->priv->model, &iter))
+ g_signal_emit (view, signals[BOOKMARK_ACTIVATED], 0, &iter);
+}
+
+static void
+activate_selected_items (GeditFileBrowserView *view)
+{
+ if (GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ activate_selected_files (view);
+ else if (GEDIT_IS_FILE_BOOKMARKS_STORE (view->priv->model))
+ activate_selected_bookmark (view);
+}
+
+static void
+row_activated (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column)
+{
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
+
+ /* Make sure the activated row is the only one selected */
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+
+ activate_selected_items (GEDIT_FILE_BROWSER_VIEW (tree_view));
+}
+
+static void
+toggle_hidden_filter (GeditFileBrowserView *view)
+{
+ GeditFileBrowserStoreFilterMode mode;
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ {
+ mode = gedit_file_browser_store_get_filter_mode (GEDIT_FILE_BROWSER_STORE (view->priv->model));
+ mode ^= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
+ gedit_file_browser_store_set_filter_mode (GEDIT_FILE_BROWSER_STORE (view->priv->model), mode);
+ }
+}
+
+static gboolean
+button_event_modifies_selection (GdkEventButton *event)
+{
+ return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
+}
+
+static void
+drag_begin (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+
+ view->priv->drag_button = 0;
+ view->priv->drag_started = TRUE;
+
+ /* Chain up */
+ GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->drag_begin (widget, context);
+}
+
+static void
+did_not_drag (GeditFileBrowserView *view,
+ GdkEventButton *event)
+{
+ GtkTreeView *tree_view = GTK_TREE_VIEW (view);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
+ GtkTreePath *path;
+
+ if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, NULL, NULL, NULL))
+ {
+ if ((view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) &&
+ !button_event_modifies_selection (event) &&
+ (event->button == 1 || event->button == 2))
+ {
+ /* Activate all selected items, and leave them selected */
+ activate_selected_items (view);
+ }
+ else if ((event->button == 1 || event->button == 2) &&
+ ((event->state & GDK_CONTROL_MASK) != 0 || (event->state & GDK_SHIFT_MASK) == 0) &&
+ view->priv->selected_on_button_down)
+ {
+ if (!button_event_modifies_selection (event))
+ {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+ }
+ else
+ {
+ gtk_tree_selection_unselect_path (selection, path);
+ }
+ }
+
+ gtk_tree_path_free (path);
+ }
+}
+
+static gboolean
+button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+
+ if (event->button == view->priv->drag_button)
+ {
+ view->priv->drag_button = 0;
+
+ if (!view->priv->drag_started && !view->priv->ignore_release)
+ did_not_drag (view, event);
+ }
+
+ /* Chain up */
+ return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->button_release_event (widget, event);
+}
+
+static gboolean
+button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkWidgetClass *widget_parent = GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class);
+ GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
+ int double_click_time;
+ static int click_count = 0;
+ static guint32 last_click_time = 0;
+ GtkTreePath *path;
+ int expander_size;
+ int horizontal_separator;
+ gboolean on_expander;
+ gboolean call_parent;
+ gboolean selected;
+
+ /* Get double click time */
+ g_object_get (G_OBJECT (gtk_widget_get_settings (widget)),
+ "gtk-double-click-time", &double_click_time,
+ NULL);
+
+ /* Determine click count */
+ if (event->time - last_click_time < double_click_time)
+ click_count++;
+ else
+ click_count = 0;
+
+ last_click_time = event->time;
+
+ /* Ignore double click if we are in single click mode */
+ if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE &&
+ click_count >= 2)
+ {
+ return TRUE;
+ }
+
+ view->priv->ignore_release = FALSE;
+ call_parent = TRUE;
+
+ if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, NULL, NULL, NULL))
+ {
+ /* Keep track of path of last click so double clicks only happen
+ * on the same item */
+ if ((event->button == 1 || event->button == 2) &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ if (view->priv->double_click_path[1])
+ gtk_tree_path_free (view->priv->double_click_path[1]);
+
+ view->priv->double_click_path[1] = view->priv->double_click_path[0];
+ view->priv->double_click_path[0] = gtk_tree_path_copy (path);
+ }
+
+ if (event->type == GDK_2BUTTON_PRESS)
+ {
+ /* Do not chain up. The row-activated signal is normally
+ * already sent, which will activate the selected item
+ * and open the file.
+ */
+ }
+ else
+ {
+ /* We're going to filter out some situations where
+ * we can't let the default code run because all
+ * but one row would be deselected. We don't
+ * want that; we want the right click menu or single
+ * click to apply to everything that's currently selected. */
+ selected = gtk_tree_selection_path_is_selected (selection, path);
+
+ if (event->button == GDK_BUTTON_SECONDARY && selected)
+ call_parent = FALSE;
+
+ if ((event->button == 1 || event->button == 2) &&
+ ((event->state & GDK_CONTROL_MASK) != 0 ||
+ (event->state & GDK_SHIFT_MASK) == 0))
+ {
+ gtk_widget_style_get (widget,
+ "expander-size", &expander_size,
+ "horizontal-separator", &horizontal_separator,
+ NULL);
+ on_expander = (event->x <= horizontal_separator / 2 +
+ gtk_tree_path_get_depth (path) * expander_size);
+
+ view->priv->selected_on_button_down = selected;
+
+ if (selected)
+ {
+ call_parent = on_expander || gtk_tree_selection_count_selected_rows (selection) == 1;
+ view->priv->ignore_release = call_parent && view->priv->click_policy != GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE;
+ }
+ else if ((event->state & GDK_CONTROL_MASK) != 0)
+ {
+ call_parent = FALSE;
+ gtk_tree_selection_select_path (selection, path);
+ }
+ else
+ {
+ view->priv->ignore_release = on_expander;
+ }
+ }
+
+ if (call_parent)
+ {
+ /* Chain up */
+ widget_parent->button_press_event (widget, event);
+ }
+ else if (selected)
+ {
+ gtk_widget_grab_focus (widget);
+ }
+
+ if ((event->button == 1 || event->button == 2) &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ view->priv->drag_started = FALSE;
+ view->priv->drag_button = event->button;
+ }
+ }
+
+ gtk_tree_path_free (path);
+ }
+ else
+ {
+ if ((event->button == 1 || event->button == 2) &&
+ event->type == GDK_BUTTON_PRESS)
+ {
+ if (view->priv->double_click_path[1])
+ gtk_tree_path_free (view->priv->double_click_path[1]);
+
+ view->priv->double_click_path[1] = view->priv->double_click_path[0];
+ view->priv->double_click_path[0] = NULL;
+ }
+
+ gtk_tree_selection_unselect_all (selection);
+ /* Chain up */
+ widget_parent->button_press_event (widget, event);
+ }
+
+ /* We already chained up if nescessary, so just return TRUE */
+ return TRUE;
+}
+
+static gboolean
+key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+ guint modifiers = gtk_accelerator_get_default_mod_mask ();
+ gboolean handled = FALSE;
+
+ switch (event->keyval)
+ {
+ case GDK_KEY_space:
+ if (event->state & GDK_CONTROL_MASK)
+ {
+ handled = FALSE;
+ break;
+ }
+ if (!gtk_widget_has_focus (widget))
+ {
+ handled = FALSE;
+ break;
+ }
+
+ activate_selected_items (view);
+ handled = TRUE;
+ break;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ activate_selected_items (view);
+ handled = TRUE;
+ break;
+
+ case GDK_KEY_h:
+ if ((event->state & modifiers) == GDK_CONTROL_MASK)
+ {
+ toggle_hidden_filter (view);
+ handled = TRUE;
+ break;
+ }
+
+ default:
+ handled = FALSE;
+ break;
+ }
+
+ /* Chain up */
+ if (!handled)
+ return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->key_press_event (widget, event);
+
+ return TRUE;
+}
+
+static void
+fill_expand_state (GeditFileBrowserView *view,
+ GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+ GtkTreeIter child;
+
+ if (!gtk_tree_model_iter_has_child (view->priv->model, iter))
+ return;
+
+ path = gtk_tree_model_get_path (view->priv->model, iter);
+
+ if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (view), path))
+ {
+ GFile *location;
+
+ gtk_tree_model_get (view->priv->model,
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ -1);
+
+ add_expand_state (view, location);
+
+ if (location)
+ g_object_unref (location);
+ }
+
+ if (gtk_tree_model_iter_children (view->priv->model, &child, iter))
+ {
+ do
+ {
+ fill_expand_state (view, &child);
+ }
+ while (gtk_tree_model_iter_next (view->priv->model, &child));
+ }
+
+ gtk_tree_path_free (path);
+}
+
+static void
+uninstall_restore_signals (GeditFileBrowserView *tree_view,
+ GtkTreeModel *model)
+{
+ g_signal_handlers_disconnect_by_func (model, on_begin_refresh, tree_view);
+ g_signal_handlers_disconnect_by_func (model, on_end_refresh, tree_view);
+ g_signal_handlers_disconnect_by_func (model, on_unload, tree_view);
+ g_signal_handlers_disconnect_by_func (model, on_row_inserted, tree_view);
+}
+
+static void
+install_restore_signals (GeditFileBrowserView *tree_view,
+ GtkTreeModel *model)
+{
+ g_signal_connect (model, "begin-refresh", G_CALLBACK (on_begin_refresh), tree_view);
+ g_signal_connect (model, "end-refresh", G_CALLBACK (on_end_refresh), tree_view);
+ g_signal_connect (model, "unload", G_CALLBACK (on_unload), tree_view);
+ g_signal_connect_after (model, "row-inserted", G_CALLBACK (on_row_inserted), tree_view);
+}
+
+static void
+set_restore_expand_state (GeditFileBrowserView *view,
+ gboolean state)
+{
+ if (state == view->priv->restore_expand_state)
+ return;
+
+ if (view->priv->expand_state)
+ {
+ g_hash_table_destroy (view->priv->expand_state);
+ view->priv->expand_state = NULL;
+ }
+
+ if (state)
+ {
+ view->priv->expand_state = g_hash_table_new_full (g_file_hash,
+ (GEqualFunc)g_file_equal,
+ g_object_unref,
+ NULL);
+
+ if (view->priv->model && GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ {
+ fill_expand_state (view, NULL);
+ install_restore_signals (view, view->priv->model);
+ }
+ }
+ else if (view->priv->model && GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ {
+ uninstall_restore_signals (view, view->priv->model);
+ }
+
+ view->priv->restore_expand_state = state;
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLICK_POLICY:
+ g_value_set_enum (value, obj->priv->click_policy);
+ break;
+ case PROP_RESTORE_EXPAND_STATE:
+ g_value_set_boolean (value, obj->priv->restore_expand_state);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLICK_POLICY:
+ set_click_policy_property (obj, g_value_get_enum (value));
+ break;
+ case PROP_RESTORE_EXPAND_STATE:
+ set_restore_expand_state (obj, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_view_class_init (GeditFileBrowserViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gedit_file_browser_view_finalize;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ /* Event handlers */
+ widget_class->motion_notify_event = motion_notify_event;
+ widget_class->enter_notify_event = enter_notify_event;
+ widget_class->leave_notify_event = leave_notify_event;
+ widget_class->button_press_event = button_press_event;
+ widget_class->button_release_event = button_release_event;
+ widget_class->drag_begin = drag_begin;
+ widget_class->key_press_event = key_press_event;
+
+ /* Tree view handlers */
+ tree_view_class->row_activated = row_activated;
+ tree_view_class->row_expanded = row_expanded;
+ tree_view_class->row_collapsed = row_collapsed;
+
+ /* Default handlers */
+ klass->directory_activated = directory_activated;
+
+ g_object_class_install_property (object_class, PROP_CLICK_POLICY,
+ g_param_spec_enum ("click-policy",
+ "Click Policy",
+ "The click policy",
+ GEDIT_TYPE_FILE_BROWSER_VIEW_CLICK_POLICY,
+ GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_RESTORE_EXPAND_STATE,
+ g_param_spec_boolean ("restore-expand-state",
+ "Restore Expand State",
+ "Restore expanded state of loaded directories",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ signals[ERROR] =
+ g_signal_new ("error",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserViewClass, error),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
+ signals[FILE_ACTIVATED] =
+ g_signal_new ("file-activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserViewClass, file_activated),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
+ signals[DIRECTORY_ACTIVATED] =
+ g_signal_new ("directory-activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserViewClass, directory_activated),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
+ signals[BOOKMARK_ACTIVATED] =
+ g_signal_new ("bookmark-activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserViewClass, bookmark_activated),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
+}
+
+static void
+gedit_file_browser_view_class_finalize (GeditFileBrowserViewClass *klass)
+{
+}
+
+static void
+cell_data_cb (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GeditFileBrowserView *obj)
+{
+ GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter);
+ PangoUnderline underline = PANGO_UNDERLINE_NONE;
+ gboolean editable = FALSE;
+
+ if (obj->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE &&
+ obj->priv->hover_path != NULL &&
+ gtk_tree_path_compare (path, obj->priv->hover_path) == 0)
+ {
+ underline = PANGO_UNDERLINE_SINGLE;
+ }
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (tree_model) &&
+ obj->priv->editable != NULL &&
+ gtk_tree_row_reference_valid (obj->priv->editable))
+ {
+ GtkTreePath *edpath = gtk_tree_row_reference_get_path (obj->priv->editable);
+
+ editable = edpath && gtk_tree_path_compare (path, edpath) == 0;
+
+ gtk_tree_path_free (edpath);
+ }
+
+ gtk_tree_path_free (path);
+ g_object_set (cell, "editable", editable, "underline", underline, NULL);
+}
+
+static void
+icon_renderer_cb (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GeditFileBrowserView *obj)
+{
+ GdkPixbuf *pixbuf;
+ gchar *icon_name;
+ gboolean set_pixbuf = FALSE;
+
+ gtk_tree_model_get (tree_model,
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_ICON_NAME, &icon_name,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_ICON, &pixbuf,
+ -1);
+
+ if (pixbuf != NULL && (GEDIT_IS_FILE_BROWSER_STORE (tree_model) || icon_name == NULL))
+ set_pixbuf = TRUE;
+
+ if (set_pixbuf)
+ g_object_set (cell, "pixbuf", pixbuf, NULL);
+ else
+ g_object_set (cell, "icon-name", icon_name, NULL);
+
+ g_clear_object (&pixbuf);
+ g_free (icon_name);
+}
+
+static void
+gedit_file_browser_view_init (GeditFileBrowserView *obj)
+{
+ obj->priv = gedit_file_browser_view_get_instance_private (obj);
+
+ obj->priv->column = gtk_tree_view_column_new ();
+
+ obj->priv->pixbuf_renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (obj->priv->column,
+ obj->priv->pixbuf_renderer,
+ FALSE);
+
+ gtk_tree_view_column_set_cell_data_func (obj->priv->column,
+ obj->priv->pixbuf_renderer,
+ (GtkTreeCellDataFunc)icon_renderer_cb,
+ obj,
+ NULL);
+
+ obj->priv->text_renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (obj->priv->column,
+ obj->priv->text_renderer, TRUE);
+ gtk_tree_view_column_add_attribute (obj->priv->column,
+ obj->priv->text_renderer,
+ "markup",
+ GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP);
+
+ g_signal_connect (obj->priv->text_renderer, "edited",
+ G_CALLBACK (on_cell_edited), obj);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (obj),
+ obj->priv->column);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (obj), FALSE);
+
+ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (obj),
+ GDK_BUTTON1_MASK,
+ drag_source_targets,
+ G_N_ELEMENTS (drag_source_targets),
+ GDK_ACTION_COPY);
+}
+
+static gboolean
+bookmarks_separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ guint flags;
+
+ gtk_tree_model_get (model,
+ iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ return (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR);
+}
+
+/* Public */
+GtkWidget *
+gedit_file_browser_view_new (void)
+{
+ GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW (g_object_new (GEDIT_TYPE_FILE_BROWSER_VIEW, NULL));
+
+ return GTK_WIDGET (obj);
+}
+
+void
+gedit_file_browser_view_set_model (GeditFileBrowserView *tree_view,
+ GtkTreeModel *model)
+{
+ GtkTreeSelection *selection;
+ gint search_column;
+
+ if (tree_view->priv->model == model)
+ return;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+ if (GEDIT_IS_FILE_BOOKMARKS_STORE (model))
+ {
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+ gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (tree_view), bookmarks_separator_func, NULL, NULL);
+ gtk_tree_view_column_set_cell_data_func (tree_view->priv->column,
+ tree_view->priv->text_renderer,
+ (GtkTreeCellDataFunc)cell_data_cb,
+ tree_view, NULL);
+ search_column = GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME;
+ }
+ else
+ {
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+ gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (tree_view), NULL, NULL, NULL);
+ gtk_tree_view_column_set_cell_data_func (tree_view->priv->column,
+ tree_view->priv->text_renderer,
+ (GtkTreeCellDataFunc)cell_data_cb,
+ tree_view, NULL);
+ search_column = GEDIT_FILE_BROWSER_STORE_COLUMN_NAME;
+
+ if (tree_view->priv->restore_expand_state)
+ install_restore_signals (tree_view, model);
+
+ }
+
+ if (tree_view->priv->hover_path != NULL)
+ {
+ gtk_tree_path_free (tree_view->priv->hover_path);
+ tree_view->priv->hover_path = NULL;
+ }
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (tree_view->priv->model) &&
+ tree_view->priv->restore_expand_state)
+ {
+ uninstall_restore_signals (tree_view, tree_view->priv->model);
+ }
+
+ tree_view->priv->model = model;
+ gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model);
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (tree_view), search_column);
+}
+
+void
+gedit_file_browser_view_start_rename (GeditFileBrowserView *tree_view,
+ GtkTreeIter *iter)
+{
+ gchar *name;
+ gchar *markup;
+ guint flags;
+ GValue name_escaped = G_VALUE_INIT;
+ GtkTreeRowReference *rowref;
+ GtkTreePath *path;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view));
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_view->priv->model));
+ g_return_if_fail (iter != NULL);
+
+ gtk_tree_model_get (tree_view->priv->model,
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, &markup,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!(FILE_IS_DIR (flags) || !FILE_IS_DUMMY (flags)))
+ {
+ g_free (name);
+ g_free (markup);
+ return;
+ }
+
+ /* Restore the markup to the original
+ * name, a plugin might have changed the markup.
+ */
+ g_value_init (&name_escaped, G_TYPE_STRING);
+ g_value_take_string (&name_escaped, g_markup_escape_text (name, -1));
+ gedit_file_browser_store_set_value (GEDIT_FILE_BROWSER_STORE (tree_view->priv->model),
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, &name_escaped);
+
+ path = gtk_tree_model_get_path (tree_view->priv->model, iter);
+ rowref = gtk_tree_row_reference_new (tree_view->priv->model, path);
+
+ /* Start editing */
+ gtk_widget_grab_focus (GTK_WIDGET (tree_view));
+
+ if (gtk_tree_path_up (path))
+ gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree_view), path);
+
+ gtk_tree_path_free (path);
+
+ tree_view->priv->orig_markup = markup;
+ tree_view->priv->editable = rowref;
+
+ /* grab focus on the text cell which is editable */
+ gtk_tree_view_column_focus_cell (tree_view->priv->column, tree_view->priv->text_renderer);
+
+ path = gtk_tree_row_reference_get_path (tree_view->priv->editable),
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view), path, tree_view->priv->column, TRUE);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view),
+ path, tree_view->priv->column,
+ FALSE, 0.0, 0.0);
+
+ gtk_tree_path_free (path);
+ g_value_unset (&name_escaped);
+ g_free (name);
+}
+
+void
+gedit_file_browser_view_set_click_policy (GeditFileBrowserView *tree_view,
+ GeditFileBrowserViewClickPolicy policy)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view));
+
+ set_click_policy_property (tree_view, policy);
+
+ g_object_notify (G_OBJECT (tree_view), "click-policy");
+}
+
+void
+gedit_file_browser_view_set_restore_expand_state (GeditFileBrowserView *tree_view,
+ gboolean restore_expand_state)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view));
+
+ set_restore_expand_state (tree_view, restore_expand_state);
+ g_object_notify (G_OBJECT (tree_view), "restore-expand-state");
+}
+
+/* Signal handlers */
+static void
+on_cell_edited (GtkCellRendererText *cell,
+ gchar *path,
+ gchar *new_text,
+ GeditFileBrowserView *tree_view)
+{
+ GtkTreePath *treepath = gtk_tree_path_new_from_string (path);
+ GtkTreeIter iter;
+ gboolean ret;
+ GValue orig_markup = G_VALUE_INIT;
+ GError *error = NULL;
+
+ ret = gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->priv->model), &iter, treepath);
+ gtk_tree_path_free (treepath);
+
+ if (ret)
+ {
+ /* Restore the original markup */
+ g_value_init (&orig_markup, G_TYPE_STRING);
+ g_value_set_string (&orig_markup, tree_view->priv->orig_markup);
+ gedit_file_browser_store_set_value (GEDIT_FILE_BROWSER_STORE (tree_view->priv->model), &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, &orig_markup);
+
+ if (new_text != NULL && *new_text != '\0' &&
+ gedit_file_browser_store_rename (GEDIT_FILE_BROWSER_STORE (tree_view->priv->model),
+ &iter,
+ new_text,
+ &error))
+ {
+ treepath = gtk_tree_model_get_path (GTK_TREE_MODEL (tree_view->priv->model), &iter);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view),
+ treepath, NULL,
+ FALSE, 0.0, 0.0);
+ gtk_tree_path_free (treepath);
+ }
+ else if (error)
+ {
+ g_signal_emit (tree_view, signals[ERROR], 0, error->code, error->message);
+ g_error_free (error);
+ }
+
+ g_value_unset (&orig_markup);
+ }
+
+ g_free (tree_view->priv->orig_markup);
+ tree_view->priv->orig_markup = NULL;
+
+ gtk_tree_row_reference_free (tree_view->priv->editable);
+ tree_view->priv->editable = NULL;
+}
+
+static void
+on_begin_refresh (GeditFileBrowserStore *model,
+ GeditFileBrowserView *view)
+{
+ /* Store the refresh state, so we can handle unloading of nodes while
+ refreshing properly */
+ view->priv->is_refresh = TRUE;
+}
+
+static void
+on_end_refresh (GeditFileBrowserStore *model,
+ GeditFileBrowserView *view)
+{
+ /* Store the refresh state, so we can handle unloading of nodes while
+ refreshing properly */
+ view->priv->is_refresh = FALSE;
+}
+
+static void
+on_unload (GeditFileBrowserStore *model,
+ GFile *location,
+ GeditFileBrowserView *view)
+{
+ /* Don't remove the expand state if we are refreshing */
+ if (!view->priv->restore_expand_state || view->priv->is_refresh)
+ return;
+
+ remove_expand_state (view, location);
+}
+
+static void
+restore_expand_state (GeditFileBrowserView *view,
+ GeditFileBrowserStore *model,
+ GtkTreeIter *iter)
+{
+ GFile *location;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ -1);
+
+ if (location)
+ {
+ GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+
+ if (g_hash_table_lookup (view->priv->expand_state, location))
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, FALSE);
+
+ gtk_tree_path_free (path);
+ g_object_unref (location);
+ }
+}
+
+static void
+on_row_inserted (GeditFileBrowserStore *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GeditFileBrowserView *view)
+{
+ GtkTreeIter parent;
+ GtkTreePath *copy;
+
+ if (gtk_tree_model_iter_has_child (GTK_TREE_MODEL (model), iter))
+ restore_expand_state (view, model, iter);
+
+ copy = gtk_tree_path_copy (path);
+
+ if (gtk_tree_path_up (copy) &&
+ (gtk_tree_path_get_depth (copy) != 0) &&
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &parent, copy))
+ {
+ restore_expand_state (view, model, &parent);
+ }
+
+ gtk_tree_path_free (copy);
+}
+
+void
+_gedit_file_browser_view_register_type (GTypeModule *type_module)
+{
+ gedit_file_browser_view_register_type (type_module);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-view.h b/plugins/filebrowser/gedit-file-browser-view.h
new file mode 100644
index 0000000..8c2efc8
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-view.h
@@ -0,0 +1,84 @@
+/*
+ * gedit-file-browser-view.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_FILE_BROWSER_VIEW_H
+#define GEDIT_FILE_BROWSER_VIEW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define GEDIT_TYPE_FILE_BROWSER_VIEW (gedit_file_browser_view_get_type ())
+#define GEDIT_FILE_BROWSER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserView))
+#define GEDIT_FILE_BROWSER_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserView const))
+#define GEDIT_FILE_BROWSER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewClass))
+#define GEDIT_IS_FILE_BROWSER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW))
+#define GEDIT_IS_FILE_BROWSER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_VIEW))
+#define GEDIT_FILE_BROWSER_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewClass))
+
+typedef struct _GeditFileBrowserView GeditFileBrowserView;
+typedef struct _GeditFileBrowserViewClass GeditFileBrowserViewClass;
+typedef struct _GeditFileBrowserViewPrivate GeditFileBrowserViewPrivate;
+
+typedef enum {
+ GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE,
+ GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE
+} GeditFileBrowserViewClickPolicy;
+
+struct _GeditFileBrowserView
+{
+ GtkTreeView parent;
+
+ GeditFileBrowserViewPrivate *priv;
+};
+
+struct _GeditFileBrowserViewClass
+{
+ GtkTreeViewClass parent_class;
+
+ /* Signals */
+ void (* error) (GeditFileBrowserView *filetree,
+ guint code,
+ gchar const *message);
+ void (* file_activated) (GeditFileBrowserView *filetree,
+ GtkTreeIter *iter);
+ void (* directory_activated) (GeditFileBrowserView *filetree,
+ GtkTreeIter *iter);
+ void (* bookmark_activated) (GeditFileBrowserView *filetree,
+ GtkTreeIter *iter);
+};
+
+GType gedit_file_browser_view_get_type (void) G_GNUC_CONST;
+
+GtkWidget *gedit_file_browser_view_new (void);
+void gedit_file_browser_view_set_model (GeditFileBrowserView *tree_view,
+ GtkTreeModel *model);
+void gedit_file_browser_view_start_rename (GeditFileBrowserView *tree_view,
+ GtkTreeIter *iter);
+void gedit_file_browser_view_set_click_policy (GeditFileBrowserView *tree_view,
+ GeditFileBrowserViewClickPolicy policy);
+void gedit_file_browser_view_set_restore_expand_state (GeditFileBrowserView *tree_view,
+ gboolean restore_expand_state);
+
+void _gedit_file_browser_view_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_VIEW_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-widget.c b/plugins/filebrowser/gedit-file-browser-widget.c
new file mode 100644
index 0000000..eab78ed
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-widget.c
@@ -0,0 +1,3150 @@
+/*
+ * gedit-file-browser-widget.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gedit/gedit-utils.h>
+
+#include "gedit-file-browser-utils.h"
+#include "gedit-file-browser-error.h"
+#include "gedit-file-browser-widget.h"
+#include "gedit-file-browser-view.h"
+#include "gedit-file-browser-store.h"
+#include "gedit-file-bookmarks-store.h"
+#include "gedit-file-browser-enum-types.h"
+
+#define LOCATION_DATA_KEY "gedit-file-browser-widget-location"
+
+enum
+{
+ BOOKMARKS_ID,
+ SEPARATOR_CUSTOM_ID,
+ SEPARATOR_ID,
+ PATH_ID,
+ NUM_DEFAULT_IDS
+};
+
+enum
+{
+ COLUMN_ICON,
+ COLUMN_ICON_NAME,
+ COLUMN_NAME,
+ COLUMN_FILE,
+ COLUMN_ID,
+ N_COLUMNS
+};
+
+/* Properties */
+enum
+{
+ PROP_0,
+
+ PROP_FILTER_PATTERN,
+};
+
+/* Signals */
+enum
+{
+ LOCATION_ACTIVATED,
+ ERROR,
+ CONFIRM_DELETE,
+ CONFIRM_NO_TRASH,
+ OPEN_IN_TERMINAL,
+ SET_ACTIVE_ROOT,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0 };
+
+typedef struct _SignalNode
+{
+ GObject *object;
+ gulong id;
+} SignalNode;
+
+typedef struct
+{
+ gulong id;
+ GeditFileBrowserWidgetFilterFunc func;
+ gpointer user_data;
+ GDestroyNotify destroy_notify;
+} FilterFunc;
+
+typedef struct
+{
+ GFile *root;
+ GFile *virtual_root;
+} Location;
+
+typedef struct
+{
+ gchar *name;
+ gchar *icon_name;
+ GdkPixbuf *icon;
+} NameIcon;
+
+struct _GeditFileBrowserWidgetPrivate
+{
+ GeditFileBrowserView *treeview;
+ GeditFileBrowserStore *file_store;
+ GeditFileBookmarksStore *bookmarks_store;
+
+ GHashTable *bookmarks_hash;
+
+ GMenuModel *dir_menu;
+ GMenuModel *bookmarks_menu;
+
+ GtkWidget *previous_button;
+ GtkWidget *next_button;
+
+ GtkWidget *locations_button;
+ GtkWidget *locations_popover;
+ GtkWidget *locations_treeview;
+ GtkTreeViewColumn *treeview_icon_column;
+ GtkCellRenderer *treeview_icon_renderer;
+ GtkTreeSelection *locations_treeview_selection;
+ GtkWidget *locations_button_arrow;
+ GtkWidget *locations_cellview;
+ GtkListStore *locations_model;
+
+ GtkWidget *location_entry;
+
+ GtkWidget *filter_entry_revealer;
+ GtkWidget *filter_entry;
+
+ GSimpleActionGroup *action_group;
+
+ GSList *signal_pool;
+
+ GSList *filter_funcs;
+ gulong filter_id;
+ gulong glob_filter_id;
+ GPatternSpec *filter_pattern;
+ gchar *filter_pattern_str;
+
+ GList *locations;
+ GList *current_location;
+ gboolean changing_location;
+ GtkWidget *location_previous_menu;
+ GtkWidget *location_next_menu;
+ GtkWidget *current_location_menu_item;
+
+ GCancellable *cancellable;
+
+ GdkCursor *busy_cursor;
+};
+
+static void on_model_set (GObject *gobject,
+ GParamSpec *arg1,
+ GeditFileBrowserWidget *obj);
+static void on_treeview_error (GeditFileBrowserView *tree_view,
+ guint code,
+ gchar *message,
+ GeditFileBrowserWidget *obj);
+static void on_file_store_error (GeditFileBrowserStore *store,
+ guint code,
+ gchar *message,
+ GeditFileBrowserWidget *obj);
+static gboolean on_file_store_no_trash (GeditFileBrowserStore *store,
+ GList *files,
+ GeditFileBrowserWidget *obj);
+static gboolean on_location_button_press_event (GtkWidget *button,
+ GdkEventButton *event,
+ GeditFileBrowserWidget *obj);
+static void on_location_entry_activate (GtkEntry *entry,
+ GeditFileBrowserWidget *obj);
+static gboolean
+ on_location_entry_focus_out_event (GtkWidget *entry,
+ GdkEvent *event,
+ GeditFileBrowserWidget *obj);
+static gboolean
+ on_location_entry_key_press_event (GtkWidget *entry,
+ GdkEventKey *event,
+ GeditFileBrowserWidget *obj);
+static gboolean on_treeview_popup_menu (GeditFileBrowserView *treeview,
+ GeditFileBrowserWidget *obj);
+static gboolean on_treeview_button_press_event (GeditFileBrowserView *treeview,
+ GdkEventButton *event,
+ GeditFileBrowserWidget *obj);
+static gboolean on_treeview_key_press_event (GeditFileBrowserView *treeview,
+ GdkEventKey *event,
+ GeditFileBrowserWidget *obj);
+static void on_selection_changed (GtkTreeSelection *selection,
+ GeditFileBrowserWidget *obj);
+
+static void on_virtual_root_changed (GeditFileBrowserStore *model,
+ GParamSpec *param,
+ GeditFileBrowserWidget *obj);
+
+static gboolean on_entry_filter_activate (GeditFileBrowserWidget *obj);
+static void on_location_jump_activate (GtkMenuItem *item,
+ GeditFileBrowserWidget *obj);
+static void on_bookmarks_row_changed (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj);
+static void on_bookmarks_row_deleted (GtkTreeModel *model,
+ GtkTreePath *path,
+ GeditFileBrowserWidget *obj);
+static void on_filter_mode_changed (GeditFileBrowserStore *model,
+ GParamSpec *param,
+ GeditFileBrowserWidget *obj);
+static void previous_location_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void next_location_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void up_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void home_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void new_folder_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void open_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void new_file_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void rename_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void delete_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void move_to_trash_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void refresh_view_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void view_folder_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void change_show_hidden_state (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data);
+static void change_show_binary_state (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data);
+static void change_show_match_filename (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data);
+static void open_in_terminal_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void set_active_root_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+static void on_locations_treeview_selection_changed (GtkTreeSelection *treeselection,
+ GeditFileBrowserWidget *obj);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserWidget,
+ gedit_file_browser_widget,
+ GTK_TYPE_GRID,
+ 0,
+ G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserWidget))
+
+static void
+free_name_icon (gpointer data)
+{
+ NameIcon *item = (NameIcon *)(data);
+
+ if (item == NULL)
+ return;
+
+ g_free (item->icon_name);
+ g_free (item->name);
+
+ if (item->icon)
+ g_object_unref (item->icon);
+
+ g_slice_free (NameIcon, item);
+}
+
+static FilterFunc *
+filter_func_new (GeditFileBrowserWidget *obj,
+ GeditFileBrowserWidgetFilterFunc func,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ FilterFunc *result = g_slice_new (FilterFunc);
+
+ result->id = ++obj->priv->filter_id;
+ result->func = func;
+ result->user_data = user_data;
+ result->destroy_notify = notify;
+ return result;
+}
+
+static void
+location_free (Location *loc)
+{
+ if (loc->root)
+ g_object_unref (loc->root);
+
+ if (loc->virtual_root)
+ g_object_unref (loc->virtual_root);
+
+ g_slice_free (Location, loc);
+}
+
+static gboolean
+locations_find_by_id (GeditFileBrowserWidget *obj,
+ guint id,
+ GtkTreeIter *iter)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (obj->priv->locations_model);
+ guint checkid;
+
+ if (iter == NULL)
+ return FALSE;
+
+ if (gtk_tree_model_get_iter_first (model, iter))
+ {
+ do
+ {
+ gtk_tree_model_get (model,
+ iter,
+ COLUMN_ID, &checkid,
+ -1);
+
+ if (checkid == id)
+ return TRUE;
+ }
+ while (gtk_tree_model_iter_next (model, iter));
+ }
+
+ return FALSE;
+}
+
+static void
+cancel_async_operation (GeditFileBrowserWidget *widget)
+{
+ if (!widget->priv->cancellable)
+ return;
+
+ g_cancellable_cancel (widget->priv->cancellable);
+ g_object_unref (widget->priv->cancellable);
+
+ widget->priv->cancellable = NULL;
+}
+
+static void
+filter_func_free (FilterFunc *func)
+{
+ g_slice_free (FilterFunc, func);
+}
+
+static void
+gedit_file_browser_widget_finalize (GObject *object)
+{
+ GeditFileBrowserWidgetPrivate *priv = GEDIT_FILE_BROWSER_WIDGET (object)->priv;
+
+ g_free (priv->filter_pattern_str);
+
+ G_OBJECT_CLASS (gedit_file_browser_widget_parent_class)->finalize (object);
+}
+
+static void
+clear_signals (GeditFileBrowserWidget *obj)
+{
+ GSList *item = obj->priv->signal_pool;
+ SignalNode *node;
+
+ while (item != NULL)
+ {
+ node = (SignalNode *) (item->data);
+ item = g_slist_delete_link (item, item);
+
+ g_signal_handler_disconnect (node->object, node->id);
+ g_slice_free (SignalNode, node);
+ }
+
+ obj->priv->signal_pool = NULL;
+}
+
+static void
+gedit_file_browser_widget_dispose (GObject *object)
+{
+ GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object);
+ GeditFileBrowserWidgetPrivate *priv = obj->priv;
+
+ clear_signals (obj);
+ g_clear_object (&priv->file_store);
+ g_clear_object (&priv->bookmarks_store);
+
+ g_slist_free_full (priv->filter_funcs, (GDestroyNotify)filter_func_free);
+ g_list_free_full (priv->locations, (GDestroyNotify)location_free);
+
+ if (priv->bookmarks_hash != NULL)
+ {
+ g_hash_table_unref (priv->bookmarks_hash);
+ priv->bookmarks_hash = NULL;
+ }
+
+ cancel_async_operation (obj);
+
+ g_clear_object (&obj->priv->current_location_menu_item);
+ g_clear_object (&priv->busy_cursor);
+ g_clear_object (&priv->dir_menu);
+ g_clear_object (&priv->bookmarks_menu);
+
+ G_OBJECT_CLASS (gedit_file_browser_widget_parent_class)->dispose (object);
+}
+
+static void
+gedit_file_browser_widget_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILTER_PATTERN:
+ g_value_set_string (value, obj->priv->filter_pattern_str);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_widget_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILTER_PATTERN:
+ gedit_file_browser_widget_set_filter_pattern (obj,
+ g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_widget_class_init (GeditFileBrowserWidgetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gedit_file_browser_widget_finalize;
+ object_class->dispose = gedit_file_browser_widget_dispose;
+
+ object_class->get_property = gedit_file_browser_widget_get_property;
+ object_class->set_property = gedit_file_browser_widget_set_property;
+
+ g_object_class_install_property (object_class, PROP_FILTER_PATTERN,
+ g_param_spec_string ("filter-pattern",
+ "Filter Pattern",
+ "The filter pattern",
+ "",
+ G_PARAM_READWRITE));
+
+ signals[LOCATION_ACTIVATED] =
+ g_signal_new ("location-activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, location_activated),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_FILE);
+
+ signals[ERROR] =
+ g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, error),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
+
+ signals[CONFIRM_DELETE] =
+ g_signal_new ("confirm-delete", G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, confirm_delete),
+ g_signal_accumulator_true_handled, NULL, NULL,
+ G_TYPE_BOOLEAN, 2, G_TYPE_OBJECT, G_TYPE_POINTER);
+
+ signals[CONFIRM_NO_TRASH] =
+ g_signal_new ("confirm-no-trash", G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, confirm_no_trash),
+ g_signal_accumulator_true_handled, NULL, NULL,
+ G_TYPE_BOOLEAN, 1, G_TYPE_POINTER);
+
+ signals[OPEN_IN_TERMINAL] =
+ g_signal_new ("open-in-terminal",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, open_in_terminal),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_FILE);
+
+ signals[SET_ACTIVE_ROOT] =
+ g_signal_new ("set-active-root",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, set_active_root),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ /* Bind class to template */
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/gedit/plugins/file-browser/ui/gedit-file-browser-widget.ui");
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, previous_button);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, next_button);
+
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_button);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_popover);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_treeview);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_treeview_selection);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, treeview_icon_column);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, treeview_icon_renderer);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_cellview);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_button_arrow);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_model);
+
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, location_entry);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, treeview);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, filter_entry_revealer);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, filter_entry);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, location_previous_menu);
+ gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, location_next_menu);
+}
+
+static void
+gedit_file_browser_widget_class_finalize (GeditFileBrowserWidgetClass *klass)
+{
+}
+
+static void
+add_signal (GeditFileBrowserWidget *obj,
+ gpointer object,
+ gulong id)
+{
+ SignalNode *node = g_slice_new (SignalNode);
+
+ node->object = G_OBJECT (object);
+ node->id = id;
+
+ obj->priv->signal_pool = g_slist_prepend (obj->priv->signal_pool, node);
+}
+
+static gboolean
+separator_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ guint id;
+
+ gtk_tree_model_get (model, iter, COLUMN_ID, &id, -1);
+
+ return (id == SEPARATOR_ID);
+}
+
+static gboolean
+get_from_bookmark_file (GeditFileBrowserWidget *obj,
+ GFile *file,
+ gchar **name,
+ gchar **icon_name,
+ GdkPixbuf **icon)
+{
+ NameIcon *item = (NameIcon *)g_hash_table_lookup (obj->priv->bookmarks_hash, file);
+
+ if (item == NULL)
+ return FALSE;
+
+ *name = g_strdup (item->name);
+ *icon_name = g_strdup (item->icon_name);
+
+ if (icon != NULL && item->icon != NULL)
+ {
+ *icon = g_object_ref (item->icon);
+ }
+
+ return TRUE;
+}
+
+static void
+insert_path_item (GeditFileBrowserWidget *obj,
+ GFile *file,
+ GtkTreeIter *after,
+ GtkTreeIter *iter)
+{
+ gchar *unescape = NULL;
+ gchar *icon_name = NULL;
+ GdkPixbuf *icon = NULL;
+
+ /* Try to get the icon and name from the bookmarks hash */
+ if (!get_from_bookmark_file (obj, file, &unescape, &icon_name, &icon))
+ {
+ /* It's not a bookmark, fetch the name and the icon ourselves */
+ unescape = gedit_file_browser_utils_file_basename (file);
+
+ /* Get the icon */
+ icon_name = gedit_file_browser_utils_symbolic_icon_name_from_file (file);
+ }
+
+ gtk_list_store_insert_after (obj->priv->locations_model, iter, after);
+
+ gtk_list_store_set (obj->priv->locations_model,
+ iter,
+ COLUMN_ICON, icon,
+ COLUMN_ICON_NAME, icon_name,
+ COLUMN_NAME, unescape,
+ COLUMN_FILE, file,
+ COLUMN_ID, PATH_ID,
+ -1);
+
+ if (icon)
+ g_object_unref (icon);
+
+ g_free (icon_name);
+ g_free (unescape);
+}
+
+static void
+insert_separator_item (GeditFileBrowserWidget *obj)
+{
+ GtkTreeIter iter;
+
+ gtk_list_store_insert (obj->priv->locations_model, &iter, 1);
+ gtk_list_store_set (obj->priv->locations_model, &iter,
+ COLUMN_ICON, NULL,
+ COLUMN_ICON_NAME, NULL,
+ COLUMN_NAME, NULL,
+ COLUMN_ID, SEPARATOR_ID, -1);
+}
+
+static void
+insert_location_path (GeditFileBrowserWidget *obj)
+{
+ GeditFileBrowserWidgetPrivate *priv = obj->priv;
+ Location *loc;
+ GFile *current = NULL;
+ GFile *tmp;
+ GtkTreeIter separator;
+ GtkTreeIter iter;
+
+ if (!priv->current_location)
+ {
+ g_message ("insert_location_path: no current location");
+ return;
+ }
+
+ loc = (Location *)(priv->current_location->data);
+
+ current = loc->virtual_root;
+ locations_find_by_id (obj, SEPARATOR_ID, &separator);
+
+ while (current != NULL)
+ {
+ insert_path_item (obj, current, &separator, &iter);
+
+ if (current == loc->virtual_root)
+ {
+
+ g_signal_handlers_block_by_func (priv->locations_treeview,
+ on_locations_treeview_selection_changed,
+ obj);
+
+ gtk_tree_selection_select_iter (priv->locations_treeview_selection, &iter);
+
+ g_signal_handlers_unblock_by_func (priv->locations_treeview,
+ on_locations_treeview_selection_changed,
+ obj);
+ }
+
+ if (g_file_equal (current, loc->root) ||
+ !g_file_has_parent (current, NULL))
+ {
+ if (current != loc->virtual_root)
+ g_object_unref (current);
+ break;
+ }
+
+ tmp = g_file_get_parent (current);
+
+ if (current != loc->virtual_root)
+ g_object_unref (current);
+
+ current = tmp;
+ }
+}
+
+static void
+remove_path_items (GeditFileBrowserWidget *obj)
+{
+ GtkTreeIter iter;
+
+ while (locations_find_by_id (obj, PATH_ID, &iter))
+ {
+ gtk_list_store_remove (obj->priv->locations_model, &iter);
+ }
+}
+
+static void
+check_current_item (GeditFileBrowserWidget *obj,
+ gboolean show_path)
+{
+ GtkTreeIter separator;
+ gboolean has_sep;
+
+ remove_path_items (obj);
+ has_sep = locations_find_by_id (obj, SEPARATOR_ID, &separator);
+
+ if (show_path)
+ {
+ if (!has_sep)
+ insert_separator_item (obj);
+
+ insert_location_path (obj);
+ }
+ else if (has_sep)
+ {
+ gtk_list_store_remove (obj->priv->locations_model, &separator);
+ }
+}
+
+static void
+fill_locations_model (GeditFileBrowserWidget *obj)
+{
+ GeditFileBrowserWidgetPrivate *priv = obj->priv;
+ GtkTreeIter iter;
+
+ gtk_list_store_append (priv->locations_model, &iter);
+ gtk_list_store_set (priv->locations_model,
+ &iter,
+ COLUMN_ICON, NULL,
+ COLUMN_ICON_NAME, "user-bookmarks-symbolic",
+ COLUMN_NAME, _("Bookmarks"),
+ COLUMN_ID, BOOKMARKS_ID, -1);
+
+ gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (priv->locations_treeview),
+ separator_func,
+ obj,
+ NULL);
+
+ gtk_tree_selection_select_iter (priv->locations_treeview_selection, &iter);
+
+ on_locations_treeview_selection_changed (priv->locations_treeview_selection, obj);
+ gedit_file_browser_widget_show_bookmarks (obj);
+}
+
+static gboolean
+filter_real (GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ GSList *item;
+
+ for (item = obj->priv->filter_funcs; item; item = item->next)
+ {
+ FilterFunc *func = (FilterFunc *)(item->data);
+
+ if (!func->func (obj, model, iter, func->user_data))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+add_bookmark_hash (GeditFileBrowserWidget *obj,
+ GtkTreeIter *iter)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (obj->priv->bookmarks_store);
+ GdkPixbuf *pixbuf;
+ gchar *name;
+ gchar *icon_name;
+ GFile *location;
+ NameIcon *item;
+
+ if (!(location = gedit_file_bookmarks_store_get_location (obj->priv->bookmarks_store, iter)))
+ return;
+
+ gtk_tree_model_get (model,
+ iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, &pixbuf,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON_NAME, &icon_name,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &name,
+ -1);
+
+ item = g_slice_new (NameIcon);
+ item->name = name;
+ item->icon_name = icon_name;
+ item->icon = pixbuf;
+
+ g_hash_table_insert (obj->priv->bookmarks_hash,
+ location,
+ item);
+}
+
+static void
+init_bookmarks_hash (GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model = GTK_TREE_MODEL (obj->priv->bookmarks_store);
+ GtkTreeIter iter;
+
+ if (!gtk_tree_model_get_iter_first (model, &iter))
+ return;
+
+ do
+ {
+ add_bookmark_hash (obj, &iter);
+ }
+ while (gtk_tree_model_iter_next (model, &iter));
+
+ g_signal_connect (obj->priv->bookmarks_store,
+ "row-changed",
+ G_CALLBACK (on_bookmarks_row_changed),
+ obj);
+
+ g_signal_connect (obj->priv->bookmarks_store,
+ "row-deleted",
+ G_CALLBACK (on_bookmarks_row_deleted),
+ obj);
+}
+
+static void
+on_begin_loading (GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ if (!GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview))))
+ return;
+
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (obj)),
+ obj->priv->busy_cursor);
+}
+
+static void
+on_end_loading (GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ if (!GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview))))
+ return;
+
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (obj)), NULL);
+}
+
+static void
+on_locations_treeview_row_activated (GtkTreeView *locations_treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GeditFileBrowserWidget *obj)
+{
+ GeditFileBrowserWidgetPrivate *priv = obj->priv;
+ GtkTreeIter iter;
+ guint id = G_MAXUINT;
+ GFile *file;
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->locations_model), &iter, path))
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->locations_model), &iter, COLUMN_ID, &id, -1);
+ }
+
+ if (id == BOOKMARKS_ID)
+ {
+ gedit_file_browser_widget_show_bookmarks (obj);
+ }
+ else if (id == PATH_ID)
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (priv->locations_model),
+ &iter,
+ COLUMN_FILE, &file,
+ -1);
+
+ gedit_file_browser_store_set_virtual_root_from_location (priv->file_store, file);
+
+ g_object_unref (file);
+ gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->locations_cellview), path);
+ }
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->locations_button), FALSE);
+}
+
+static GActionEntry browser_entries[] = {
+ { "open", open_activated },
+ { "set_active_root", set_active_root_activated },
+ { "new_folder", new_folder_activated },
+ { "new_file", new_file_activated },
+ { "rename", rename_activated },
+ { "move_to_trash", move_to_trash_activated },
+ { "delete", delete_activated },
+ { "refresh_view", refresh_view_activated },
+ { "view_folder", view_folder_activated },
+ { "open_in_terminal", open_in_terminal_activated },
+ { "show_hidden", NULL, NULL, "false", change_show_hidden_state },
+ { "show_binary", NULL, NULL, "false", change_show_binary_state },
+ { "show_match_filename", NULL, NULL, "false", change_show_match_filename },
+ { "previous_location", previous_location_activated },
+ { "next_location", next_location_activated },
+ { "up", up_activated },
+ { "home", home_activated }
+};
+
+static void
+locations_icon_renderer_cb (GtkTreeViewColumn *tree_column,
+ GtkCellRenderer *cell,
+ GtkTreeModel *tree_model,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ GdkPixbuf *pixbuf;
+ gchar *icon_name;
+
+ gtk_tree_model_get (tree_model,
+ iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON_NAME, &icon_name,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, &pixbuf,
+ -1);
+
+ if (icon_name != NULL)
+ g_object_set (cell, "icon-name", icon_name, NULL);
+ else
+ g_object_set (cell, "pixbuf", pixbuf, NULL);
+
+ g_clear_object (&pixbuf);
+ g_free (icon_name);
+}
+
+static void
+gedit_file_browser_widget_init (GeditFileBrowserWidget *obj)
+{
+ GtkBuilder *builder;
+ GdkDisplay *display;
+ GAction *action;
+ GError *error = NULL;
+
+ obj->priv = gedit_file_browser_widget_get_instance_private (obj);
+
+ obj->priv->filter_pattern_str = g_strdup ("");
+ obj->priv->bookmarks_hash = g_hash_table_new_full (g_file_hash,
+ (GEqualFunc)g_file_equal,
+ g_object_unref,
+ free_name_icon);
+
+ display = gtk_widget_get_display (GTK_WIDGET (obj));
+ obj->priv->busy_cursor = gdk_cursor_new_from_name (display, "progress");
+
+ builder = gtk_builder_new ();
+ if (!gtk_builder_add_from_resource (builder,
+ "/org/gnome/gedit/plugins/file-browser/ui/gedit-file-browser-menus.ui",
+ &error))
+ {
+ g_warning ("loading menu builder file: %s", error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ obj->priv->dir_menu = G_MENU_MODEL (g_object_ref (gtk_builder_get_object (builder, "dir-menu")));
+ obj->priv->bookmarks_menu = G_MENU_MODEL (g_object_ref (gtk_builder_get_object (builder, "bookmarks-menu")));
+ }
+
+ g_object_unref (builder);
+
+ obj->priv->action_group = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (obj->priv->action_group),
+ browser_entries,
+ G_N_ELEMENTS (browser_entries),
+ obj);
+
+ /* set initial sensitivity */
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "previous_location");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "next_location");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+ gtk_widget_insert_action_group (GTK_WIDGET (obj),
+ "browser",
+ G_ACTION_GROUP (obj->priv->action_group));
+
+ gtk_widget_init_template (GTK_WIDGET (obj));
+
+ g_signal_connect (obj->priv->previous_button, "button-press-event",
+ G_CALLBACK (on_location_button_press_event), obj);
+ g_signal_connect (obj->priv->next_button, "button-press-event",
+ G_CALLBACK (on_location_button_press_event), obj);
+
+ /* locations popover */
+ gtk_tree_selection_set_mode (obj->priv->locations_treeview_selection, GTK_SELECTION_SINGLE);
+ gtk_tree_view_column_set_cell_data_func (obj->priv->treeview_icon_column,
+ obj->priv->treeview_icon_renderer,
+ (GtkTreeCellDataFunc)locations_icon_renderer_cb,
+ obj,
+ NULL);
+ fill_locations_model (obj);
+
+ g_signal_connect (obj->priv->locations_treeview_selection, "changed",
+ G_CALLBACK (on_locations_treeview_selection_changed), obj);
+ g_signal_connect (obj->priv->locations_treeview, "row-activated",
+ G_CALLBACK (on_locations_treeview_row_activated), obj);
+
+ g_signal_connect (obj->priv->location_entry, "activate",
+ G_CALLBACK (on_location_entry_activate), obj);
+ g_signal_connect (obj->priv->location_entry, "focus-out-event",
+ G_CALLBACK (on_location_entry_focus_out_event), obj);
+ g_signal_connect (obj->priv->location_entry, "key-press-event",
+ G_CALLBACK (on_location_entry_key_press_event), obj);
+
+ /* tree view */
+ obj->priv->file_store = gedit_file_browser_store_new (NULL);
+ obj->priv->bookmarks_store = gedit_file_bookmarks_store_new ();
+
+ gedit_file_browser_view_set_restore_expand_state (obj->priv->treeview, TRUE);
+
+ gedit_file_browser_store_set_filter_mode (obj->priv->file_store,
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN |
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY);
+ gedit_file_browser_store_set_filter_func (obj->priv->file_store,
+ (GeditFileBrowserStoreFilterFunc) filter_real,
+ obj);
+
+ g_signal_connect (obj->priv->treeview, "notify::model",
+ G_CALLBACK (on_model_set), obj);
+ g_signal_connect (obj->priv->treeview, "error",
+ G_CALLBACK (on_treeview_error), obj);
+ g_signal_connect (obj->priv->treeview, "popup-menu",
+ G_CALLBACK (on_treeview_popup_menu), obj);
+ g_signal_connect (obj->priv->treeview, "button-press-event",
+ G_CALLBACK (on_treeview_button_press_event),
+ obj);
+ g_signal_connect (obj->priv->treeview, "key-press-event",
+ G_CALLBACK (on_treeview_key_press_event), obj);
+
+ g_signal_connect (gtk_tree_view_get_selection
+ (GTK_TREE_VIEW (obj->priv->treeview)), "changed",
+ G_CALLBACK (on_selection_changed), obj);
+ g_signal_connect (obj->priv->file_store, "notify::filter-mode",
+ G_CALLBACK (on_filter_mode_changed), obj);
+
+ g_signal_connect (obj->priv->file_store, "notify::virtual-root",
+ G_CALLBACK (on_virtual_root_changed), obj);
+
+ g_signal_connect (obj->priv->file_store, "begin-loading",
+ G_CALLBACK (on_begin_loading), obj);
+
+ g_signal_connect (obj->priv->file_store, "end-loading",
+ G_CALLBACK (on_end_loading), obj);
+
+ g_signal_connect (obj->priv->file_store, "error",
+ G_CALLBACK (on_file_store_error), obj);
+
+ init_bookmarks_hash (obj);
+
+ /* filter */
+ g_signal_connect_swapped (obj->priv->filter_entry, "activate",
+ G_CALLBACK (on_entry_filter_activate),
+ obj);
+ g_signal_connect_swapped (obj->priv->filter_entry, "focus_out_event",
+ G_CALLBACK (on_entry_filter_activate),
+ obj);
+}
+
+/* Private */
+
+static void
+update_sensitivity (GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ GAction *action;
+ gint mode;
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ {
+ mode = gedit_file_browser_store_get_filter_mode (GEDIT_FILE_BROWSER_STORE (model));
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "show_hidden");
+
+ g_action_change_state (action,
+ g_variant_new_boolean (!(mode &
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN)));
+
+ /* sensitivity */
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "up");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "home");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "show_hidden");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "show_binary");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "show_match_filename");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+ }
+ else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model))
+ {
+ /* Set the filter toggle to normal up state, just for visual pleasure */
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "show_hidden");
+
+ g_action_change_state (action, g_variant_new_boolean (FALSE));
+
+ /* sensitivity */
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "up");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "home");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "show_hidden");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "show_binary");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group),
+ "show_match_filename");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+ }
+
+ on_selection_changed (gtk_tree_view_get_selection
+ GTK_TREE_VIEW (obj->priv->treeview),
+ obj);
+}
+
+static gboolean
+gedit_file_browser_widget_get_first_selected (GeditFileBrowserWidget *obj,
+ GtkTreeIter *iter)
+{
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview));
+ GtkTreeModel *model;
+ GList *rows = gtk_tree_selection_get_selected_rows (selection, &model);
+ gboolean result;
+
+ if (!rows)
+ return FALSE;
+
+ result = gtk_tree_model_get_iter (model, iter, (GtkTreePath *)(rows->data));
+
+ g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free);
+
+ return result;
+}
+
+static gboolean
+popup_menu (GeditFileBrowserWidget *obj,
+ GtkTreeView *treeview,
+ GdkEventButton *event,
+ GtkTreeModel *model)
+{
+ GtkWidget *menu;
+ GMenuModel *menu_model;
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ menu_model = obj->priv->dir_menu;
+ else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model))
+ menu_model = obj->priv->bookmarks_menu;
+ else
+ return FALSE;
+
+ menu = gtk_menu_new_from_model (menu_model);
+ gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (obj), NULL);
+
+ if (event != NULL)
+ {
+ GtkTreeSelection *selection;
+ selection = gtk_tree_view_get_selection (treeview);
+
+ if (gtk_tree_selection_count_selected_rows (selection) <= 1)
+ {
+ GtkTreePath *path;
+
+ if (gtk_tree_view_get_path_at_pos (treeview,
+ (gint)event->x, (gint)event->y,
+ &path, NULL, NULL, NULL))
+ {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+ gtk_tree_path_free (path);
+ }
+ }
+
+ gtk_menu_popup_at_pointer (GTK_MENU (menu), (GdkEvent *)event);
+ }
+ else
+ {
+ GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (treeview));
+ GdkGravity rect_gravity = GDK_GRAVITY_EAST;
+ GdkGravity menu_gravity = GDK_GRAVITY_NORTH_WEST;
+ GdkRectangle rect;
+
+ if (gedit_utils_menu_position_under_tree_view (treeview, &rect))
+ {
+ if (gtk_widget_get_direction (GTK_WIDGET (treeview)) == GTK_TEXT_DIR_RTL)
+ {
+ rect_gravity = GDK_GRAVITY_WEST;
+ menu_gravity = GDK_GRAVITY_NORTH_EAST;
+ }
+
+ gtk_menu_popup_at_rect (GTK_MENU (menu), window, &rect, rect_gravity, menu_gravity, NULL);
+ gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+filter_glob (GeditFileBrowserWidget *obj,
+ GeditFileBrowserStore *store,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ gchar *name;
+ gboolean result;
+ guint flags;
+
+ if (obj->priv->filter_pattern == NULL)
+ return TRUE;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (FILE_IS_DIR (flags) || FILE_IS_DUMMY (flags))
+ result = TRUE;
+ else
+ result = g_pattern_match_string (obj->priv->filter_pattern, name);
+
+ g_free (name);
+ return result;
+}
+
+static void
+rename_selected_file (GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ GtkTreeIter iter;
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ if (gedit_file_browser_widget_get_first_selected (obj, &iter))
+ gedit_file_browser_view_start_rename (obj->priv->treeview, &iter);
+}
+
+static GList *
+get_deletable_files (GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview));
+ GList *rows = gtk_tree_selection_get_selected_rows (selection, &model);
+ GList *row;
+ GList *paths = NULL;
+
+ for (row = rows; row; row = row->next)
+ {
+ GtkTreePath *path = (GtkTreePath *)(row->data);
+ GtkTreeIter iter;
+ guint flags;
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ continue;
+
+ gtk_tree_model_get (model,
+ &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (FILE_IS_DUMMY (flags))
+ continue;
+
+ paths = g_list_append (paths, gtk_tree_path_copy (path));
+ }
+
+ g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free);
+
+ return paths;
+}
+
+static gboolean
+delete_selected_files (GeditFileBrowserWidget *obj,
+ gboolean trash)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ gboolean confirm;
+ GeditFileBrowserStoreResult result;
+ GList *rows;
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return FALSE;
+
+ if (!(rows = get_deletable_files (obj)))
+ return FALSE;
+
+ if (!trash)
+ {
+ g_signal_emit (obj, signals[CONFIRM_DELETE], 0, model, rows, &confirm);
+
+ if (!confirm)
+ return FALSE;
+ }
+
+ result = gedit_file_browser_store_delete_all (GEDIT_FILE_BROWSER_STORE (model),
+ rows,
+ trash);
+
+ g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free);
+
+ return result == GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+static void
+show_location_entry (GeditFileBrowserWidget *obj,
+ const gchar *location)
+{
+ g_warn_if_fail (location != NULL);
+
+ gtk_entry_set_text (GTK_ENTRY (obj->priv->location_entry), location);
+
+ gtk_widget_show (obj->priv->location_entry);
+ gtk_widget_grab_focus (obj->priv->location_entry);
+
+ /* grab_focus() causes the entry's text to become
+ * selected so, unselect it and move the cursor to the end.
+ */
+ gtk_editable_set_position (GTK_EDITABLE (obj->priv->location_entry), -1);
+}
+
+static gboolean
+on_file_store_no_trash (GeditFileBrowserStore *store,
+ GList *files,
+ GeditFileBrowserWidget *obj)
+{
+ gboolean confirm = FALSE;
+
+ g_signal_emit (obj, signals[CONFIRM_NO_TRASH], 0, files, &confirm);
+
+ return confirm;
+}
+
+static GFile *
+get_topmost_file (GFile *file)
+{
+ GFile *current = g_object_ref (file);
+ GFile *tmp;
+
+ while ((tmp = g_file_get_parent (current)) != NULL)
+ {
+ g_object_unref (current);
+ current = tmp;
+ }
+
+ return current;
+}
+
+static GtkWidget *
+create_goto_menu_item (GeditFileBrowserWidget *obj,
+ GList *item)
+{
+ Location *loc = (Location *) (item->data);
+ GtkWidget *result;
+ gchar *icon_name = NULL;
+ gchar *unescape = NULL;
+
+ if (!get_from_bookmark_file (obj, loc->virtual_root, &unescape, &icon_name, NULL))
+ unescape = gedit_file_browser_utils_file_basename (loc->virtual_root);
+
+ result = gtk_menu_item_new_with_label (unescape);
+
+ g_object_set_data (G_OBJECT (result), LOCATION_DATA_KEY, item);
+ g_signal_connect (result, "activate",
+ G_CALLBACK (on_location_jump_activate), obj);
+
+ gtk_widget_show (result);
+
+ g_free (icon_name);
+ g_free (unescape);
+
+ return result;
+}
+
+static GList *
+list_next_iterator (GList *list)
+{
+ if (!list)
+ return NULL;
+
+ return list->next;
+}
+
+static GList *
+list_prev_iterator (GList *list)
+{
+ if (!list)
+ return NULL;
+
+ return list->prev;
+}
+
+static void
+jump_to_location (GeditFileBrowserWidget *obj,
+ GList *item,
+ gboolean previous)
+{
+ Location *loc;
+ GtkWidget *widget;
+ GList *children;
+ GList *child;
+ GList *(*iter_func) (GList *);
+ GtkWidget *menu_from;
+ GtkWidget *menu_to;
+
+ if (!obj->priv->locations)
+ return;
+
+ if (previous)
+ {
+ iter_func = list_next_iterator;
+ menu_from = obj->priv->location_previous_menu;
+ menu_to = obj->priv->location_next_menu;
+ }
+ else
+ {
+ iter_func = list_prev_iterator;
+ menu_from = obj->priv->location_next_menu;
+ menu_to = obj->priv->location_previous_menu;
+ }
+
+ children = gtk_container_get_children (GTK_CONTAINER (menu_from));
+ child = children;
+
+ /* This is the menuitem for the current location, which is the first
+ to be added to the menu */
+ widget = obj->priv->current_location_menu_item;
+
+ while (obj->priv->current_location != item)
+ {
+ if (widget)
+ {
+ /* Prepend the menu item to the menu */
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu_to), widget);
+ g_object_unref (widget);
+ }
+
+ widget = GTK_WIDGET (child->data);
+
+ /* Make sure the widget isn't destroyed when removed */
+ g_object_ref (widget);
+ gtk_container_remove (GTK_CONTAINER (menu_from), widget);
+
+ obj->priv->current_location_menu_item = widget;
+
+ if (obj->priv->current_location == NULL)
+ {
+ obj->priv->current_location = obj->priv->locations;
+
+ if (obj->priv->current_location == item)
+ break;
+ }
+ else
+ {
+ obj->priv->current_location = iter_func (obj->priv->current_location);
+ }
+
+ child = child->next;
+ }
+
+ g_list_free (children);
+
+ obj->priv->changing_location = TRUE;
+
+ loc = (Location *) (obj->priv->current_location->data);
+
+ /* Set the new root + virtual root */
+ gedit_file_browser_widget_set_root_and_virtual_root (obj,
+ loc->root,
+ loc->virtual_root);
+
+ obj->priv->changing_location = FALSE;
+}
+
+static void
+clear_next_locations (GeditFileBrowserWidget *obj)
+{
+ GAction *action;
+ GList *children;
+ GList *item;
+
+ if (obj->priv->current_location == NULL)
+ return;
+
+ while (obj->priv->current_location->prev)
+ {
+ location_free ((Location *) (obj->priv->current_location->prev->data));
+ obj->priv->locations = g_list_remove_link (obj->priv->locations,
+ obj->priv->current_location->prev);
+ }
+
+ children = gtk_container_get_children (GTK_CONTAINER (obj->priv->location_next_menu));
+
+ for (item = children; item; item = item->next)
+ {
+ gtk_container_remove (GTK_CONTAINER
+ (obj->priv->location_next_menu),
+ GTK_WIDGET (item->data));
+ }
+
+ g_list_free (children);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "next_location");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+}
+
+static void
+update_filter_mode (GeditFileBrowserWidget *obj,
+ GSimpleAction *action,
+ GVariant *state,
+ GeditFileBrowserStoreFilterMode mode)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ {
+ gint now = gedit_file_browser_store_get_filter_mode (GEDIT_FILE_BROWSER_STORE (model));
+
+ if (g_variant_get_boolean(state))
+ now &= ~mode;
+ else
+ now |= mode;
+
+ gedit_file_browser_store_set_filter_mode (GEDIT_FILE_BROWSER_STORE (model), now);
+
+ }
+
+ g_simple_action_set_state (action, state);
+}
+
+static void
+set_filter_pattern_real (GeditFileBrowserWidget *obj,
+ gchar const *pattern,
+ gboolean update_entry)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (pattern != NULL && *pattern == '\0')
+ pattern = NULL;
+
+ if (pattern == NULL && *obj->priv->filter_pattern_str == '\0')
+ return;
+
+ if (pattern != NULL && strcmp (pattern, obj->priv->filter_pattern_str) == 0)
+ return;
+
+ /* Free the old pattern */
+ g_free (obj->priv->filter_pattern_str);
+
+ if (pattern == NULL)
+ obj->priv->filter_pattern_str = g_strdup ("");
+ else
+ obj->priv->filter_pattern_str = g_strdup (pattern);
+
+ if (obj->priv->filter_pattern)
+ {
+ g_pattern_spec_free (obj->priv->filter_pattern);
+ obj->priv->filter_pattern = NULL;
+ }
+
+ if (pattern == NULL)
+ {
+ if (obj->priv->glob_filter_id != 0)
+ {
+ gedit_file_browser_widget_remove_filter (obj, obj->priv->glob_filter_id);
+ obj->priv->glob_filter_id = 0;
+ }
+ }
+ else
+ {
+ obj->priv->filter_pattern = g_pattern_spec_new (pattern);
+
+ if (obj->priv->glob_filter_id == 0)
+ obj->priv->glob_filter_id = gedit_file_browser_widget_add_filter (obj,
+ filter_glob,
+ NULL,
+ NULL);
+ }
+
+ if (update_entry)
+ gtk_entry_set_text (GTK_ENTRY (obj->priv->filter_entry), obj->priv->filter_pattern_str);
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ gedit_file_browser_store_refilter (GEDIT_FILE_BROWSER_STORE (model));
+
+ g_object_notify (G_OBJECT (obj), "filter-pattern");
+}
+
+
+/* Public */
+
+GtkWidget *
+gedit_file_browser_widget_new (void)
+{
+ GeditFileBrowserWidget *obj = g_object_new (GEDIT_TYPE_FILE_BROWSER_WIDGET, NULL);
+
+ gedit_file_browser_widget_show_bookmarks (obj);
+
+ return GTK_WIDGET (obj);
+}
+
+void
+gedit_file_browser_widget_show_bookmarks (GeditFileBrowserWidget *obj)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ gtk_widget_set_sensitive (obj->priv->locations_button, FALSE);
+ gtk_widget_hide (obj->priv->locations_button_arrow);
+ locations_find_by_id (obj, BOOKMARKS_ID, &iter);
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (obj->priv->locations_model), &iter);
+ gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (obj->priv->locations_cellview), path);
+ gtk_tree_path_free (path);
+
+ gedit_file_browser_view_set_model (obj->priv->treeview,
+ GTK_TREE_MODEL (obj->priv->bookmarks_store));
+}
+
+static void
+show_files_real (GeditFileBrowserWidget *obj,
+ gboolean do_root_changed)
+{
+ gtk_widget_set_sensitive (obj->priv->locations_button, TRUE);
+ gtk_widget_show (obj->priv->locations_button_arrow);
+
+ gedit_file_browser_view_set_model (obj->priv->treeview,
+ GTK_TREE_MODEL (obj->priv->file_store));
+
+ if (do_root_changed)
+ on_virtual_root_changed (obj->priv->file_store, NULL, obj);
+}
+
+void
+gedit_file_browser_widget_show_files (GeditFileBrowserWidget *obj)
+{
+ show_files_real (obj, TRUE);
+}
+
+void
+gedit_file_browser_widget_set_root_and_virtual_root (GeditFileBrowserWidget *obj,
+ GFile *root,
+ GFile *virtual_root)
+{
+ GeditFileBrowserStoreResult result;
+
+ if (!virtual_root)
+ result = gedit_file_browser_store_set_root_and_virtual_root (obj->priv->file_store,
+ root,
+ root);
+ else
+ result = gedit_file_browser_store_set_root_and_virtual_root (obj->priv->file_store,
+ root,
+ virtual_root);
+
+ if (result == GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE)
+ show_files_real (obj, TRUE);
+}
+
+void
+gedit_file_browser_widget_set_root (GeditFileBrowserWidget *obj,
+ GFile *root,
+ gboolean virtual_root)
+{
+ GFile *parent;
+
+ if (!virtual_root)
+ {
+ gedit_file_browser_widget_set_root_and_virtual_root (obj,
+ root,
+ NULL);
+ return;
+ }
+
+ if (!root)
+ return;
+
+ parent = get_topmost_file (root);
+ gedit_file_browser_widget_set_root_and_virtual_root (obj, parent, root);
+ g_object_unref (parent);
+}
+
+GeditFileBrowserStore *
+gedit_file_browser_widget_get_browser_store (GeditFileBrowserWidget *obj)
+{
+ return obj->priv->file_store;
+}
+
+GeditFileBookmarksStore *
+gedit_file_browser_widget_get_bookmarks_store (GeditFileBrowserWidget *obj)
+{
+ return obj->priv->bookmarks_store;
+}
+
+GeditFileBrowserView *
+gedit_file_browser_widget_get_browser_view (GeditFileBrowserWidget *obj)
+{
+ return obj->priv->treeview;
+}
+
+GtkWidget *
+gedit_file_browser_widget_get_filter_entry (GeditFileBrowserWidget *obj)
+{
+ return obj->priv->filter_entry;
+}
+
+gulong
+gedit_file_browser_widget_add_filter (GeditFileBrowserWidget *obj,
+ GeditFileBrowserWidgetFilterFunc func,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ FilterFunc *f = filter_func_new (obj, func, user_data, notify);;
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ obj->priv->filter_funcs = g_slist_append (obj->priv->filter_funcs, f);
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ gedit_file_browser_store_refilter (GEDIT_FILE_BROWSER_STORE (model));
+
+ return f->id;
+}
+
+void
+gedit_file_browser_widget_remove_filter (GeditFileBrowserWidget *obj,
+ gulong id)
+{
+ GSList *item;
+
+ for (item = obj->priv->filter_funcs; item; item = item->next)
+ {
+ FilterFunc *func = (FilterFunc *) (item->data);
+
+ if (func->id == id)
+ {
+ if (func->destroy_notify)
+ func->destroy_notify (func->user_data);
+
+ obj->priv->filter_funcs = g_slist_remove_link (obj->priv->filter_funcs, item);
+
+ filter_func_free (func);
+ break;
+ }
+ }
+}
+
+void
+gedit_file_browser_widget_set_filter_pattern (GeditFileBrowserWidget *obj,
+ gchar const *pattern)
+{
+ gboolean show;
+ GAction *action;
+
+ /* if pattern is not null, reveal the entry */
+ show = pattern != NULL && *pattern != '\0';
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "show_match_filename");
+ g_action_change_state (action, g_variant_new_boolean (show));
+
+ set_filter_pattern_real (obj, pattern, TRUE);
+}
+
+gboolean
+gedit_file_browser_widget_get_selected_directory (GeditFileBrowserWidget *obj,
+ GtkTreeIter *iter)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ GtkTreeIter parent;
+ guint flags;
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return FALSE;
+
+ if (!gedit_file_browser_widget_get_first_selected (obj, iter) &&
+ !gedit_file_browser_store_get_iter_virtual_root
+ (GEDIT_FILE_BROWSER_STORE (model), iter))
+ {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!FILE_IS_DIR (flags))
+ {
+ /* Get the parent, because the selection is a file */
+ gtk_tree_model_iter_parent (model, &parent, iter);
+ *iter = parent;
+ }
+
+ return TRUE;
+}
+
+void
+gedit_file_browser_widget_set_active_root_enabled (GeditFileBrowserWidget *widget,
+ gboolean enabled)
+{
+ GAction *action;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_WIDGET (widget));
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (widget->priv->action_group), "set_active_root");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+}
+
+static guint
+gedit_file_browser_widget_get_num_selected_files_or_directories (GeditFileBrowserWidget *obj,
+ guint *files,
+ guint *dirs)
+{
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview));
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ GList *rows, *row;
+ guint result = 0;
+
+ if (GEDIT_IS_FILE_BOOKMARKS_STORE (model))
+ return 0;
+
+ rows = gtk_tree_selection_get_selected_rows (selection, &model);
+
+ for (row = rows; row; row = row->next)
+ {
+ GtkTreePath *path = (GtkTreePath *)(row->data);
+ GeditFileBrowserStoreFlag flags;
+ GtkTreeIter iter;
+
+ /* Get iter from path */
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ continue;
+
+ gtk_tree_model_get (model, &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!FILE_IS_DUMMY (flags))
+ {
+ if (!FILE_IS_DIR (flags))
+ ++(*files);
+ else
+ ++(*dirs);
+
+ ++result;
+ }
+ }
+
+ g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free);
+
+ return result;
+}
+
+typedef struct
+{
+ GeditFileBrowserWidget *widget;
+ GCancellable *cancellable;
+} AsyncData;
+
+static AsyncData *
+async_data_new (GeditFileBrowserWidget *widget)
+{
+ AsyncData *ret = g_slice_new (AsyncData);
+
+ ret->widget = widget;
+
+ cancel_async_operation (widget);
+ widget->priv->cancellable = g_cancellable_new ();
+
+ ret->cancellable = g_object_ref (widget->priv->cancellable);
+
+ return ret;
+}
+
+static void
+async_free (AsyncData *async)
+{
+ g_object_unref (async->cancellable);
+ g_slice_free (AsyncData, async);
+}
+
+static void
+set_busy (GeditFileBrowserWidget *obj,
+ gboolean busy)
+{
+ GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview));
+
+ if (!GDK_IS_WINDOW (window))
+ return;
+
+ if (busy)
+ {
+ GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (obj));
+ GdkCursor *cursor= gdk_cursor_new_from_name (display, "progress");
+
+ gdk_window_set_cursor (window, cursor);
+ g_clear_object (&cursor);
+ }
+ else
+ {
+ gdk_window_set_cursor (window, NULL);
+ }
+}
+
+static void try_mount_volume (GeditFileBrowserWidget *widget, GVolume *volume);
+
+static void
+activate_mount (GeditFileBrowserWidget *widget,
+ GVolume *volume,
+ GMount *mount)
+{
+ GFile *root;
+
+ if (!mount)
+ {
+ gchar *name = g_volume_get_name (volume);
+ gchar *message = g_strdup_printf (_("No mount object for mounted volume: %s"), name);
+
+ g_signal_emit (widget,
+ signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_SET_ROOT,
+ message);
+
+ g_free (name);
+ g_free (message);
+ return;
+ }
+
+ root = g_mount_get_root (mount);
+
+ gedit_file_browser_widget_set_root (widget, root, FALSE);
+
+ g_object_unref (root);
+}
+
+static void
+try_activate_drive (GeditFileBrowserWidget *widget,
+ GDrive *drive)
+{
+ GList *volumes = g_drive_get_volumes (drive);
+ GVolume *volume = G_VOLUME (volumes->data);
+ GMount *mount = g_volume_get_mount (volume);
+
+ if (mount)
+ {
+ /* try set the root of the mount */
+ activate_mount (widget, volume, mount);
+ g_object_unref (mount);
+ }
+ else
+ {
+ /* try to mount it then? */
+ try_mount_volume (widget, volume);
+ }
+
+ g_list_free_full (volumes, g_object_unref);
+}
+
+static void
+poll_for_media_cb (GDrive *drive,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+
+ /* check for cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_free (async);
+ return;
+ }
+
+ /* finish poll operation */
+ set_busy (async->widget, FALSE);
+
+ if (g_drive_poll_for_media_finish (drive, res, &error) &&
+ g_drive_has_media (drive) &&
+ g_drive_has_volumes (drive))
+ {
+ try_activate_drive (async->widget, drive);
+ }
+ else
+ {
+ gchar *name = g_drive_get_name (drive);
+ gchar *message = g_strdup_printf (_("Could not open media: %s"), name);
+
+ g_signal_emit (async->widget,
+ signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_SET_ROOT,
+ message);
+
+ g_free (name);
+ g_free (message);
+
+ g_error_free (error);
+ }
+
+ async_free (async);
+}
+
+static void
+mount_volume_cb (GVolume *volume,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+
+ /* check for cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_free (async);
+ return;
+ }
+
+ if (g_volume_mount_finish (volume, res, &error))
+ {
+ GMount *mount = g_volume_get_mount (volume);
+
+ activate_mount (async->widget, volume, mount);
+
+ if (mount)
+ g_object_unref (mount);
+ }
+ else
+ {
+ gchar *name = g_volume_get_name (volume);
+ gchar *message = g_strdup_printf (_("Could not mount volume: %s"), name);
+
+ g_signal_emit (async->widget,
+ signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_SET_ROOT,
+ message);
+
+ g_free (name);
+ g_free (message);
+
+ g_error_free (error);
+ }
+
+ set_busy (async->widget, FALSE);
+ async_free (async);
+}
+
+static void
+activate_drive (GeditFileBrowserWidget *obj,
+ GtkTreeIter *iter)
+{
+ GDrive *drive;
+ AsyncData *async;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->bookmarks_store),
+ iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, &drive,
+ -1);
+
+ /* most common use case is a floppy drive, we'll poll for media and
+ go from there */
+ async = async_data_new (obj);
+ g_drive_poll_for_media (drive,
+ async->cancellable,
+ (GAsyncReadyCallback)poll_for_media_cb,
+ async);
+
+ g_object_unref (drive);
+ set_busy (obj, TRUE);
+}
+
+static void
+try_mount_volume (GeditFileBrowserWidget *widget,
+ GVolume *volume)
+{
+ GMountOperation *operation = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (widget))));
+ AsyncData *async = async_data_new (widget);
+
+ g_volume_mount (volume,
+ G_MOUNT_MOUNT_NONE,
+ operation,
+ async->cancellable,
+ (GAsyncReadyCallback)mount_volume_cb,
+ async);
+
+ g_object_unref (operation);
+ set_busy (widget, TRUE);
+}
+
+static void
+activate_volume (GeditFileBrowserWidget *obj,
+ GtkTreeIter *iter)
+{
+ GVolume *volume;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->bookmarks_store),
+ iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, &volume,
+ -1);
+
+ /* see if we can mount the volume */
+ try_mount_volume (obj, volume);
+ g_object_unref (volume);
+}
+
+void
+gedit_file_browser_widget_refresh (GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ {
+ gedit_file_browser_store_refresh (GEDIT_FILE_BROWSER_STORE (model));
+ }
+ else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model))
+ {
+ g_hash_table_ref (obj->priv->bookmarks_hash);
+ g_hash_table_destroy (obj->priv->bookmarks_hash);
+
+ gedit_file_bookmarks_store_refresh (GEDIT_FILE_BOOKMARKS_STORE (model));
+ }
+}
+
+GeditMenuExtension *
+gedit_file_browser_widget_extend_context_menu (GeditFileBrowserWidget *obj)
+{
+ guint i, n_items;
+ GMenuModel *section = NULL;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_WIDGET (obj), NULL);
+
+ n_items = g_menu_model_get_n_items (obj->priv->dir_menu);
+
+ for (i = 0; i < n_items && !section; i++)
+ {
+ gchar *id = NULL;
+
+ if (g_menu_model_get_item_attribute (obj->priv->dir_menu, i, "id", "s", &id) &&
+ strcmp (id, "extension-section") == 0)
+ {
+ section = g_menu_model_get_item_link (obj->priv->dir_menu, i, G_MENU_LINK_SECTION);
+ }
+
+ g_free (id);
+ }
+
+ return section != NULL ? gedit_menu_extension_new (G_MENU (section)) : NULL;
+}
+
+void
+gedit_file_browser_widget_history_back (GeditFileBrowserWidget *obj)
+{
+ if (obj->priv->locations)
+ {
+ if (obj->priv->current_location)
+ jump_to_location (obj, obj->priv->current_location->next, TRUE);
+ else
+ jump_to_location (obj, obj->priv->locations, TRUE);
+ }
+}
+
+void
+gedit_file_browser_widget_history_forward (GeditFileBrowserWidget *obj)
+{
+ if (obj->priv->locations)
+ jump_to_location (obj, obj->priv->current_location->prev, FALSE);
+}
+
+static void
+bookmark_open (GeditFileBrowserWidget *obj,
+ GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ GFile *location;
+ gint flags;
+
+ gtk_tree_model_get (model,
+ iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE)
+ {
+ /* handle a drive node */
+ gedit_file_browser_store_cancel_mount_operation (obj->priv->file_store);
+ activate_drive (obj, iter);
+ return;
+ }
+ else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME)
+ {
+ /* handle a volume node */
+ gedit_file_browser_store_cancel_mount_operation (obj->priv->file_store);
+ activate_volume (obj, iter);
+ return;
+ }
+
+ if ((location = gedit_file_bookmarks_store_get_location (GEDIT_FILE_BOOKMARKS_STORE (model), iter)))
+ {
+ /* here we check if the bookmark is a mount point, or if it
+ is a remote bookmark. If that's the case, we will set the
+ root to the uri of the bookmark and not try to set the
+ topmost parent as root (since that may as well not be the
+ mount point anymore) */
+ if ((flags & GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT) ||
+ (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK))
+ {
+ gedit_file_browser_widget_set_root (obj, location, FALSE);
+ }
+ else
+ {
+ gedit_file_browser_widget_set_root (obj, location, TRUE);
+ }
+
+ g_object_unref (location);
+ }
+ else
+ {
+ g_warning ("No uri!");
+ }
+}
+
+static void
+file_open (GeditFileBrowserWidget *obj,
+ GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ GFile *location;
+ gint flags;
+
+ gtk_tree_model_get (model, iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ -1);
+
+ if (!FILE_IS_DIR (flags) && !FILE_IS_DUMMY (flags))
+ g_signal_emit (obj, signals[LOCATION_ACTIVATED], 0, location);
+
+ if (location)
+ g_object_unref (location);
+}
+
+static gboolean
+directory_open (GeditFileBrowserWidget *obj,
+ GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ gboolean result = FALSE;
+ GError *error = NULL;
+ GFile *location;
+ GeditFileBrowserStoreFlag flags;
+
+ gtk_tree_model_get (model, iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ -1);
+
+ if (FILE_IS_DIR (flags) && location)
+ {
+ gchar *uri = g_file_get_uri (location);
+ result = TRUE;
+
+ if (!gtk_show_uri_on_window (GTK_WINDOW (obj), uri, GDK_CURRENT_TIME, &error))
+ {
+ g_signal_emit (obj, signals[ERROR], 0,
+ GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY,
+ error->message);
+
+ g_error_free (error);
+ error = NULL;
+ }
+
+ g_free (uri);
+ g_object_unref (location);
+ }
+
+ return result;
+}
+
+static void
+on_bookmark_activated (GeditFileBrowserView *tree_view,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
+
+ bookmark_open (obj, model, iter);
+}
+
+static void
+on_file_activated (GeditFileBrowserView *tree_view,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
+
+ file_open (obj, model, iter);
+}
+
+static gboolean
+virtual_root_is_root (GeditFileBrowserWidget *obj,
+ GeditFileBrowserStore *model)
+{
+ GtkTreeIter root;
+ GtkTreeIter virtual_root;
+
+ if (!gedit_file_browser_store_get_iter_root (model, &root))
+ return TRUE;
+
+ if (!gedit_file_browser_store_get_iter_virtual_root (model, &virtual_root))
+ return TRUE;
+
+ return gedit_file_browser_store_iter_equal (model, &root, &virtual_root);
+}
+
+static void
+on_virtual_root_changed (GeditFileBrowserStore *model,
+ GParamSpec *param,
+ GeditFileBrowserWidget *obj)
+{
+ GtkTreeIter iter;
+
+ if (gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)) !=
+ GTK_TREE_MODEL (obj->priv->file_store))
+ {
+ show_files_real (obj, FALSE);
+ }
+
+ if (gedit_file_browser_store_get_iter_virtual_root (model, &iter))
+ {
+ GFile *location;
+ GtkTreeIter root;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location,
+ -1);
+
+ if (gedit_file_browser_store_get_iter_root (model, &root))
+ {
+ GAction *action;
+
+ if (!obj->priv->changing_location)
+ {
+ Location *loc;
+
+ /* Remove all items from obj->priv->current_location on */
+ if (obj->priv->current_location)
+ clear_next_locations (obj);
+
+ loc = g_slice_new (Location);
+ loc->root = gedit_file_browser_store_get_root (model);
+ loc->virtual_root = g_object_ref (location);
+
+ if (obj->priv->current_location)
+ {
+ /* Add current location to the menu so we can go back
+ to it later */
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (obj->priv->location_previous_menu),
+ obj->priv->current_location_menu_item);
+ }
+
+ obj->priv->locations = g_list_prepend (obj->priv->locations, loc);
+
+ obj->priv->current_location = obj->priv->locations;
+ obj->priv->current_location_menu_item = create_goto_menu_item (obj,
+ obj->priv->current_location);
+
+ g_object_ref_sink (obj->priv->current_location_menu_item);
+ }
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "up");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !virtual_root_is_root (obj, model));
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ obj->priv->current_location != NULL &&
+ obj->priv->current_location->next != NULL);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "next_location");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ obj->priv->current_location != NULL &&
+ obj->priv->current_location->prev != NULL);
+ }
+
+ check_current_item (obj, TRUE);
+
+ if (location)
+ g_object_unref (location);
+ }
+ else
+ {
+ g_message ("NO!");
+ }
+}
+
+static void
+on_model_set (GObject *gobject,
+ GParamSpec *arg1,
+ GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (gobject));
+
+ clear_signals (obj);
+
+ if (GEDIT_IS_FILE_BOOKMARKS_STORE (model))
+ {
+ clear_next_locations (obj);
+
+ /* Add the current location to the back menu */
+ if (obj->priv->current_location)
+ {
+ GAction *action;
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (obj->priv->location_previous_menu),
+ obj->priv->current_location_menu_item);
+
+ g_object_unref (obj->priv->current_location_menu_item);
+ obj->priv->current_location = NULL;
+ obj->priv->current_location_menu_item = NULL;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+ }
+
+ gtk_widget_hide (obj->priv->filter_entry_revealer);
+
+ add_signal (obj,
+ gobject,
+ g_signal_connect (gobject, "bookmark-activated",
+ G_CALLBACK (on_bookmark_activated),
+ obj));
+ }
+ else if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ {
+ /* make sure any async operation is cancelled */
+ cancel_async_operation (obj);
+
+ add_signal (obj,
+ gobject,
+ g_signal_connect (gobject, "file-activated",
+ G_CALLBACK (on_file_activated),
+ obj));
+
+ add_signal (obj,
+ model,
+ g_signal_connect (model, "no-trash",
+ G_CALLBACK (on_file_store_no_trash),
+ obj));
+
+ gtk_widget_show (obj->priv->filter_entry_revealer);
+ }
+
+ update_sensitivity (obj);
+}
+
+static void
+on_file_store_error (GeditFileBrowserStore *store,
+ guint code,
+ gchar *message,
+ GeditFileBrowserWidget *obj)
+{
+ g_signal_emit (obj, signals[ERROR], 0, code, message);
+}
+
+static void
+on_treeview_error (GeditFileBrowserView *tree_view,
+ guint code,
+ gchar *message,
+ GeditFileBrowserWidget *obj)
+{
+ g_signal_emit (obj, signals[ERROR], 0, code, message);
+}
+
+static gboolean
+on_location_button_press_event (GtkWidget *button,
+ GdkEventButton *event,
+ GeditFileBrowserWidget *obj)
+{
+ GtkWidget *menu;
+
+ if (event->button != GDK_BUTTON_SECONDARY)
+ return FALSE;
+
+ if (button == obj->priv->previous_button)
+ menu = obj->priv->location_previous_menu;
+ else
+ menu = obj->priv->location_next_menu;
+
+ gtk_menu_popup_at_pointer (GTK_MENU (menu), (GdkEvent *)event);
+
+ return TRUE;
+}
+
+static void
+on_locations_treeview_selection_changed (GtkTreeSelection *treeview_selection,
+ GeditFileBrowserWidget *obj)
+{
+ GeditFileBrowserWidgetPrivate *priv = obj->priv;
+ GtkTreeModel *model = GTK_TREE_MODEL (priv->locations_model);
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ if (!gtk_tree_selection_get_selected (treeview_selection, &model, &iter))
+ return;
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (obj->priv->locations_model), &iter);
+ gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (obj->priv->locations_cellview), path);
+ gtk_tree_path_free (path);
+}
+
+static void
+on_location_entry_activate (GtkEntry *entry,
+ GeditFileBrowserWidget *obj)
+{
+ gchar *location = g_strdup (gtk_entry_get_text (entry));
+ GFile *root;
+ gchar *cwd;
+ GFile *new_root;
+
+ if (g_str_has_prefix (location, "~/"))
+ {
+ gchar *tmp = location;
+
+ location = g_strdup_printf ("%s/%s", g_get_home_dir (), tmp + strlen ("~/"));
+
+ g_free (tmp);
+ }
+
+ root = gedit_file_browser_store_get_virtual_root (obj->priv->file_store);
+ cwd = g_file_get_path (root);
+
+ if (cwd == NULL)
+ cwd = g_file_get_uri (root);
+
+ new_root = g_file_new_for_commandline_arg_and_cwd (location, cwd);
+
+ if (g_file_query_file_type (new_root, G_FILE_QUERY_INFO_NONE, NULL) != G_FILE_TYPE_DIRECTORY)
+ {
+ gchar *display_name = g_file_get_parse_name (new_root);
+ gchar *msg = g_strdup_printf (_("Error when loading “%s”: No such directory"), display_name);
+
+ g_signal_emit (obj, signals[ERROR], 0, GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, msg);
+
+ g_free (msg);
+ g_free (display_name);
+ }
+ else
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (obj->priv->treeview));
+ gtk_widget_hide (obj->priv->location_entry);
+
+ gedit_file_browser_widget_set_root (obj, new_root, TRUE);
+ }
+
+ g_object_unref (new_root);
+ g_free (cwd);
+ g_object_unref (root);
+ g_free (location);
+}
+
+static gboolean
+on_location_entry_focus_out_event (GtkWidget *entry,
+ GdkEvent *event,
+ GeditFileBrowserWidget *obj)
+{
+ gtk_widget_hide (entry);
+ return FALSE;
+}
+
+static gboolean
+on_location_entry_key_press_event (GtkWidget *entry,
+ GdkEventKey *event,
+ GeditFileBrowserWidget *obj)
+{
+ guint modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ if (event->keyval == GDK_KEY_Escape &&
+ (event->state & modifiers) == 0)
+ {
+ gtk_widget_grab_focus (GTK_WIDGET (obj->priv->treeview));
+ gtk_widget_hide (entry);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+on_treeview_popup_menu (GeditFileBrowserView *treeview,
+ GeditFileBrowserWidget *obj)
+{
+ return popup_menu (obj, GTK_TREE_VIEW (treeview), NULL, gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)));
+}
+
+static gboolean
+on_treeview_button_press_event (GeditFileBrowserView *treeview,
+ GdkEventButton *event,
+ GeditFileBrowserWidget *obj)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_SECONDARY)
+ return popup_menu (obj,
+ GTK_TREE_VIEW (treeview),
+ event,
+ gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)));
+
+ return FALSE;
+}
+
+static gboolean
+do_change_directory (GeditFileBrowserWidget *obj,
+ GdkEventKey *event)
+{
+ GAction *action = NULL;
+
+ if ((event->state &
+ (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK & ~GDK_MOD1_MASK)) ==
+ event->state && event->keyval == GDK_KEY_BackSpace)
+ {
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location");
+ }
+ else if (!((event->state & GDK_MOD1_MASK) &&
+ (event->state & (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK)) == event->state))
+ {
+ return FALSE;
+ }
+
+ switch (event->keyval)
+ {
+ case GDK_KEY_Home:
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "home");
+ break;
+ case GDK_KEY_Left:
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location");
+ break;
+ case GDK_KEY_Right:
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "next_location");
+ break;
+ case GDK_KEY_Up:
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "up");
+ break;
+ default:
+ break;
+ }
+
+ if (action != NULL)
+ {
+ g_action_activate (action, NULL);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+on_treeview_key_press_event (GeditFileBrowserView *treeview,
+ GdkEventKey *event,
+ GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model;
+ guint modifiers;
+
+ if (do_change_directory (obj, event))
+ return TRUE;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return FALSE;
+
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ if (event->keyval == GDK_KEY_Delete ||
+ event->keyval == GDK_KEY_KP_Delete)
+ {
+
+ if ((event->state & modifiers) == GDK_SHIFT_MASK)
+ {
+ delete_selected_files (obj, FALSE);
+ return TRUE;
+ }
+ else if ((event->state & modifiers) == 0)
+ {
+ delete_selected_files (obj, TRUE);
+ return TRUE;
+ }
+ }
+
+ if ((event->keyval == GDK_KEY_F2) && (event->state & modifiers) == 0)
+ {
+ rename_selected_file (obj);
+ return TRUE;
+ }
+
+ if (event->keyval == GDK_KEY_l &&
+ (event->state & modifiers) == GDK_CONTROL_MASK)
+ {
+ show_location_entry (obj, "");
+ return TRUE;
+ }
+
+ if (event->keyval == GDK_KEY_slash ||
+ event->keyval == GDK_KEY_KP_Divide ||
+#ifdef G_OS_WIN32
+ event->keyval == GDK_KEY_backslash ||
+#endif
+ event->keyval == GDK_KEY_asciitilde)
+ {
+ gchar location[2] = {'\0', '\0'};
+
+ location[0] = gdk_keyval_to_unicode (event->keyval);
+
+ show_location_entry (obj, location);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+on_selection_changed (GtkTreeSelection *selection,
+ GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ GAction *action;
+ guint selected = 0;
+ guint files = 0;
+ guint dirs = 0;
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ selected = gedit_file_browser_widget_get_num_selected_files_or_directories (obj, &files, &dirs);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "move_to_trash");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected > 0);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "delete");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected > 0);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "open");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (selected > 0) && (selected == files));
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "rename");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected == 1);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "open_in_terminal");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected == 1);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "new_folder");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected <= 1);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "new_file");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected <= 1);
+}
+
+static gboolean
+on_entry_filter_activate (GeditFileBrowserWidget *obj)
+{
+ gchar const *text = gtk_entry_get_text (GTK_ENTRY (obj->priv->filter_entry));
+ set_filter_pattern_real (obj, text, FALSE);
+
+ return FALSE;
+}
+
+static void
+on_location_jump_activate (GtkMenuItem *item,
+ GeditFileBrowserWidget *obj)
+{
+ GList *location = g_object_get_data (G_OBJECT (item), LOCATION_DATA_KEY);
+
+ if (obj->priv->current_location)
+ {
+ jump_to_location (obj, location,
+ g_list_position (obj->priv->locations, location) >
+ g_list_position (obj->priv->locations, obj->priv->current_location));
+ }
+ else
+ {
+ jump_to_location (obj, location, TRUE);
+ }
+}
+
+static void
+on_bookmarks_row_changed (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ add_bookmark_hash (obj, iter);
+}
+
+static void
+on_bookmarks_row_deleted (GtkTreeModel *model,
+ GtkTreePath *path,
+ GeditFileBrowserWidget *obj)
+{
+ GtkTreeIter iter;
+ GFile *location;
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ return;
+
+ if (!(location = gedit_file_bookmarks_store_get_location (obj->priv->bookmarks_store, &iter)))
+ return;
+
+ g_hash_table_remove (obj->priv->bookmarks_hash, location);
+
+ g_object_unref (location);
+}
+
+static void
+on_filter_mode_changed (GeditFileBrowserStore *model,
+ GParamSpec *param,
+ GeditFileBrowserWidget *obj)
+{
+ gint mode = gedit_file_browser_store_get_filter_mode (model);
+ GAction *action;
+ GVariant *variant;
+ gboolean active;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "show_hidden");
+ active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN);
+ variant = g_action_get_state (action);
+
+ if (active != g_variant_get_boolean (variant))
+ g_action_change_state (action, g_variant_new_boolean (active));
+
+ g_variant_unref (variant);
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "show_binary");
+ active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY);
+ variant = g_action_get_state (action);
+
+ if (active != g_variant_get_boolean (variant))
+ g_action_change_state (action, g_variant_new_boolean (active));
+
+ g_variant_unref (variant);
+}
+
+static void
+next_location_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ gedit_file_browser_widget_history_forward (GEDIT_FILE_BROWSER_WIDGET (user_data));
+}
+
+static void
+previous_location_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ gedit_file_browser_widget_history_back (GEDIT_FILE_BROWSER_WIDGET (user_data));
+}
+
+static void
+up_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview));
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ gedit_file_browser_store_set_virtual_root_up (GEDIT_FILE_BROWSER_STORE (model));
+}
+
+static void
+home_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview));
+ GFile *home_location;
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ home_location = g_file_new_for_path (g_get_home_dir ());
+ gedit_file_browser_widget_set_root (widget, home_location, TRUE);
+
+ g_object_unref (home_location);
+}
+
+static void
+new_folder_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview));
+ GtkTreeIter parent;
+ GtkTreeIter iter;
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ if (!gedit_file_browser_widget_get_selected_directory (widget, &parent))
+ return;
+
+ if (gedit_file_browser_store_new_directory (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter))
+ gedit_file_browser_view_start_rename (widget->priv->treeview, &iter);
+}
+
+static void
+open_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview));
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->treeview));
+ GList *rows;
+ GList *row;
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ rows = gtk_tree_selection_get_selected_rows (selection, &model);
+ for (row = rows; row; row = row->next)
+ {
+ GtkTreePath *path = (GtkTreePath *)(row->data);
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter (model, &iter, path))
+ file_open (widget, model, &iter);
+
+ gtk_tree_path_free (path);
+ }
+
+ g_list_free (rows);
+}
+
+static void
+new_file_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview));
+ GtkTreeIter parent;
+ GtkTreeIter iter;
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ if (!gedit_file_browser_widget_get_selected_directory (widget, &parent))
+ return;
+
+ if (gedit_file_browser_store_new_file (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter))
+ gedit_file_browser_view_start_rename (widget->priv->treeview, &iter);
+}
+
+static void
+rename_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+
+ rename_selected_file (widget);
+}
+
+static void
+delete_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+
+ delete_selected_files (widget, FALSE);
+}
+
+static void
+move_to_trash_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+
+ delete_selected_files (widget, TRUE);
+}
+
+static void
+refresh_view_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+
+ gedit_file_browser_widget_refresh (widget);
+}
+
+static void
+view_folder_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview));
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->treeview));
+ GtkTreeIter iter;
+ GList *rows;
+ GList *row;
+ gboolean directory_opened = FALSE;
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ rows = gtk_tree_selection_get_selected_rows (selection, &model);
+ for (row = rows; row; row = row->next)
+ {
+ GtkTreePath *path = (GtkTreePath *)(row->data);
+
+ if (gtk_tree_model_get_iter (model, &iter, path))
+ directory_opened |= directory_open (widget, model, &iter);
+
+ gtk_tree_path_free (path);
+ }
+
+ if (!directory_opened && gedit_file_browser_widget_get_selected_directory (widget, &iter))
+ directory_open (widget, model, &iter);
+
+ g_list_free (rows);
+}
+
+static void
+change_show_hidden_state (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+
+ update_filter_mode (widget,
+ action,
+ state,
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN);
+}
+
+static void
+change_show_binary_state (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+
+ update_filter_mode (widget,
+ action,
+ state,
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY);
+}
+
+static void
+change_show_match_filename (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+ gboolean visible = g_variant_get_boolean (state);
+
+ gtk_revealer_set_reveal_child (GTK_REVEALER (widget->priv->filter_entry_revealer), visible);
+
+ if (visible)
+ gtk_widget_grab_focus (widget->priv->filter_entry);
+ else
+ /* clear the filter */
+ set_filter_pattern_real (widget, NULL, TRUE);
+
+ g_simple_action_set_state (action, state);
+}
+
+static void
+open_in_terminal_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+ GtkTreeIter iter;
+ GFile *file;
+
+ /* Get the current directory */
+ if (!gedit_file_browser_widget_get_selected_directory (widget, &iter))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->file_store),
+ &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &file,
+ -1);
+
+ g_signal_emit (widget, signals[OPEN_IN_TERMINAL], 0, file);
+
+ g_object_unref (file);
+}
+
+static void
+set_active_root_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data);
+
+ g_signal_emit (widget, signals[SET_ACTIVE_ROOT], 0);
+}
+
+void
+_gedit_file_browser_widget_register_type (GTypeModule *type_module)
+{
+ gedit_file_browser_widget_register_type (type_module);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/gedit-file-browser-widget.h b/plugins/filebrowser/gedit-file-browser-widget.h
new file mode 100644
index 0000000..8da8312
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-widget.h
@@ -0,0 +1,126 @@
+/*
+ * gedit-file-browser-widget.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_FILE_BROWSER_WIDGET_H
+#define GEDIT_FILE_BROWSER_WIDGET_H
+
+#include <gtk/gtk.h>
+#include <gedit/gedit-menu-extension.h>
+#include "gedit-file-browser-store.h"
+#include "gedit-file-bookmarks-store.h"
+#include "gedit-file-browser-view.h"
+
+G_BEGIN_DECLS
+#define GEDIT_TYPE_FILE_BROWSER_WIDGET (gedit_file_browser_widget_get_type ())
+#define GEDIT_FILE_BROWSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidget))
+#define GEDIT_FILE_BROWSER_WIDGET_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidget const))
+#define GEDIT_FILE_BROWSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidgetClass))
+#define GEDIT_IS_FILE_BROWSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET))
+#define GEDIT_IS_FILE_BROWSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_WIDGET))
+#define GEDIT_FILE_BROWSER_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidgetClass))
+
+typedef struct _GeditFileBrowserWidget GeditFileBrowserWidget;
+typedef struct _GeditFileBrowserWidgetClass GeditFileBrowserWidgetClass;
+typedef struct _GeditFileBrowserWidgetPrivate GeditFileBrowserWidgetPrivate;
+
+typedef
+gboolean (*GeditFileBrowserWidgetFilterFunc) (GeditFileBrowserWidget *obj,
+ GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ gpointer user_data);
+
+struct _GeditFileBrowserWidget
+{
+ GtkBox parent;
+
+ GeditFileBrowserWidgetPrivate *priv;
+};
+
+struct _GeditFileBrowserWidgetClass
+{
+ GtkBoxClass parent_class;
+
+ /* Signals */
+ void (* location_activated) (GeditFileBrowserWidget *widget,
+ GFile *location);
+ void (* error) (GeditFileBrowserWidget *widget,
+ guint code,
+ gchar const *message);
+ gboolean (* confirm_delete) (GeditFileBrowserWidget *widget,
+ GeditFileBrowserStore *model,
+ GList *list);
+ gboolean (* confirm_no_trash) (GeditFileBrowserWidget *widget,
+ GList *list);
+ void (* open_in_terminal) (GeditFileBrowserWidget *widget,
+ GFile *location);
+ void (* set_active_root) (GeditFileBrowserWidget *widget);
+};
+
+GType gedit_file_browser_widget_get_type (void) G_GNUC_CONST;
+
+GtkWidget *gedit_file_browser_widget_new (void);
+
+void gedit_file_browser_widget_show_bookmarks (GeditFileBrowserWidget *obj);
+void gedit_file_browser_widget_show_files (GeditFileBrowserWidget *obj);
+
+void gedit_file_browser_widget_set_root (GeditFileBrowserWidget *obj,
+ GFile *root,
+ gboolean virtual_root);
+void gedit_file_browser_widget_set_root_and_virtual_root
+ (GeditFileBrowserWidget *obj,
+ GFile *root,
+ GFile *virtual_root);
+
+gboolean gedit_file_browser_widget_get_selected_directory
+ (GeditFileBrowserWidget *obj,
+ GtkTreeIter *iter);
+
+void gedit_file_browser_widget_set_active_root_enabled (GeditFileBrowserWidget *widget,
+ gboolean enabled);
+
+GeditFileBrowserStore *
+gedit_file_browser_widget_get_browser_store (GeditFileBrowserWidget *obj);
+GeditFileBookmarksStore *
+gedit_file_browser_widget_get_bookmarks_store (GeditFileBrowserWidget *obj);
+GeditFileBrowserView *
+gedit_file_browser_widget_get_browser_view (GeditFileBrowserWidget *obj);
+GtkWidget *
+gedit_file_browser_widget_get_filter_entry (GeditFileBrowserWidget *obj);
+
+gulong gedit_file_browser_widget_add_filter (GeditFileBrowserWidget *obj,
+ GeditFileBrowserWidgetFilterFunc func,
+ gpointer user_data,
+ GDestroyNotify notify);
+void gedit_file_browser_widget_remove_filter (GeditFileBrowserWidget *obj,
+ gulong id);
+void gedit_file_browser_widget_set_filter_pattern (GeditFileBrowserWidget *obj,
+ gchar const *pattern);
+GeditMenuExtension *
+ gedit_file_browser_widget_extend_context_menu (GeditFileBrowserWidget *obj);
+void gedit_file_browser_widget_refresh (GeditFileBrowserWidget *obj);
+void gedit_file_browser_widget_history_back (GeditFileBrowserWidget *obj);
+void gedit_file_browser_widget_history_forward (GeditFileBrowserWidget *obj);
+
+void _gedit_file_browser_widget_register_type (GTypeModule *type_module);
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_WIDGET_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/filebrowser/meson.build b/plugins/filebrowser/meson.build
new file mode 100644
index 0000000..ba66652
--- /dev/null
+++ b/plugins/filebrowser/meson.build
@@ -0,0 +1,127 @@
+libfilebrowser_public_h = files(
+ 'gedit-file-bookmarks-store.h',
+ 'gedit-file-browser-error.h',
+ 'gedit-file-browser-store.h',
+ 'gedit-file-browser-view.h',
+ 'gedit-file-browser-widget.h',
+ 'gedit-file-browser-utils.h',
+ 'gedit-file-browser-plugin.h',
+ 'gedit-file-browser-messages.h',
+)
+
+libfilebrowser_sources = files(
+ 'gedit-file-bookmarks-store.c',
+ 'gedit-file-browser-messages.c',
+ 'gedit-file-browser-plugin.c',
+ 'gedit-file-browser-store.c',
+ 'gedit-file-browser-utils.c',
+ 'gedit-file-browser-view.c',
+ 'gedit-file-browser-widget.c',
+)
+
+libfilebrowser_deps = [
+ libgedit_dep,
+]
+
+subdir('messages')
+
+libfilebrowser_register_enums = gnome.mkenums(
+ 'gedit-file-browser-enum-register',
+ sources: libfilebrowser_public_h,
+ c_template: 'gedit-file-browser-enum-register.c.template',
+)
+
+libfilebrowser_type_enums = gnome.mkenums(
+ 'gedit-file-browser-enum-types',
+ depends : [libfilebrowser_register_enums],
+ sources: libfilebrowser_public_h,
+ h_template: 'gedit-file-browser-enum-types.h.template',
+ c_template: 'gedit-file-browser-enum-types-stage1.c.template',
+)
+
+# cat won't work on Windows so this
+# will need to be reimplemented as a script
+cat = find_program('cat')
+
+# Combine the 2 C mkenums templates together before compiling
+libfilebrowser_enums_c = custom_target('libfilebrowser_enums_c',
+ input: [libfilebrowser_type_enums.get(0),
+ libfilebrowser_register_enums],
+ output: 'gedit-file-browser-enum-types.c',
+ command: [cat, '@INPUT0@', '@INPUT1@'],
+ # redirects the command output since we can't use >> here
+ capture: true,
+)
+
+libfilebrowser_sources += [
+ libfilebrowser_enums_c,
+ libfilebrowser_type_enums.get(1),
+]
+
+subdir('resources')
+
+libfilebrowser_sha = shared_module(
+ 'filebrowser',
+ sources: libfilebrowser_sources,
+ include_directories: root_include_dir,
+ dependencies: libfilebrowser_deps,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ ),
+ name_suffix: module_suffix,
+)
+
+# FIXME: https://github.com/mesonbuild/meson/issues/1687
+custom_target(
+ 'org.gnome.gedit.plugins.filebrowser.enums.xml',
+ input : libfilebrowser_sources + libfilebrowser_public_h,
+ output: 'org.gnome.gedit.plugins.filebrowser.enums.xml',
+ capture: true,
+ command: [
+ 'glib-mkenums',
+ '--comments', '<!-- @comment@ -->',
+ '--fhead', '<schemalist>',
+ '--vhead', ' <@type@ id="org.gnome.gedit.plugins.filebrowser.@EnumName@">',
+ '--vprod', ' <value nick="@valuenick@" value="@valuenum@"/>',
+ '--vtail', ' </@type@>',
+ '--ftail', '</schemalist>',
+ '@INPUT@'
+ ],
+ install: true,
+ install_dir: join_paths(
+ glibdir,
+ 'schemas',
+ )
+)
+
+filebrowser_gschema_file = files('org.gnome.gedit.plugins.filebrowser.gschema.xml')
+install_data(
+ filebrowser_gschema_file,
+ install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas')
+)
+
+if xmllint.found()
+ test(
+ 'validate-filebrowser-gschema',
+ xmllint,
+ args: [
+ '--noout',
+ '--dtdvalid', gschema_dtd,
+ filebrowser_gschema_file,
+ ]
+ )
+endif
+
+custom_target(
+ 'filebrowser.plugin',
+ input: 'filebrowser.plugin.desktop.in',
+ output: 'filebrowser.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
diff --git a/plugins/filebrowser/messages.xml b/plugins/filebrowser/messages.xml
new file mode 100644
index 0000000..e2b7137
--- /dev/null
+++ b/plugins/filebrowser/messages.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<messages>
+ <message namespace="Gedit" name="FileBrowserMessageGetRoot">
+ <include>gio/gio.h</include>
+ <property name="location" type="object" gtype="G_TYPE_FILE"/>
+ </message>
+ <message namespace="Gedit" name="FileBrowserMessageSetRoot">
+ <include>gio/gio.h</include>
+ <property name="location" type="object" gtype="G_TYPE_FILE"/>
+ <property name="virtual" type="string"/>
+ </message>
+ <message namespace="Gedit" name="FileBrowserMessageSetEmblem">
+ <property name="id" type="string"/>
+ <property name="emblem" type="string"/>
+ </message>
+ <message namespace="Gedit" name="FileBrowserMessageSetMarkup">
+ <property name="id" type="string"/>
+ <property name="markup" type="string"/>
+ </message>
+ <message namespace="Gedit" name="FileBrowserMessageAddFilter">
+ <property name="object_path" type="string"/>
+ <property name="method" type="string"/>
+ <property name="id" type="uint"/>
+ </message>
+ <message namespace="Gedit" name="FileBrowserMessageId">
+ <property name="id" type="uint"/>
+ </message>
+ <message namespace="Gedit" name="FileBrowserMessageExtendContextMenu">
+ <include system="yes">gedit/gedit-menu-extension.h</include>
+ <property name="extension" type="object" gtype="GEDIT_TYPE_MENU_EXTENSION"/>
+ </message>
+ <message namespace="Gedit" name="FileBrowserMessageActivation">
+ <property name="active" type="boolean"/>
+ </message>
+ <message namespace="Gedit" name="FileBrowserMessageGetView">
+ <include>plugins/filebrowser/gedit-file-browser-view.h</include>
+ <property name="view" type="object" gtype="GEDIT_TYPE_FILE_BROWSER_VIEW"/>
+ </message>
+ <message namespace="Gedit" name="FileBrowserMessageIdLocation">
+ <include>gio/gio.h</include>
+ <property name="id" type="string"/>
+ <property name="name" type="string"/>
+ <property name="location" type="object" gtype="G_TYPE_FILE"/>
+ <property name="is-directory" type="boolean"/>
+ </message>
+</messages>
+<!-- vi:ex:ts=2:et -->
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-activation.c b/plugins/filebrowser/messages/gedit-file-browser-message-activation.c
new file mode 100644
index 0000000..0138e0d
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-activation.c
@@ -0,0 +1,105 @@
+
+/*
+ * gedit-file-browser-message-activation.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "gedit-file-browser-message-activation.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_ACTIVE,
+};
+
+struct _GeditFileBrowserMessageActivationPrivate
+{
+ gboolean active;
+};
+
+G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageActivation,
+ gedit_file_browser_message_activation,
+ GEDIT_TYPE_MESSAGE,
+ 0,
+ G_ADD_PRIVATE (GeditFileBrowserMessageActivation))
+
+static void
+gedit_file_browser_message_activation_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageActivation *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION (obj);
+
+ switch (prop_id)
+ {
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, msg->priv->active);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_activation_set_property (GObject *obj,
+ guint prop_id,
+ GValue const *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageActivation *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION (obj);
+
+ switch (prop_id)
+ {
+ case PROP_ACTIVE:
+ msg->priv->active = g_value_get_boolean (value);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_activation_class_init (GeditFileBrowserMessageActivationClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = gedit_file_browser_message_activation_get_property;
+ object_class->set_property = gedit_file_browser_message_activation_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ACTIVE,
+ g_param_spec_boolean ("active",
+ "Active",
+ "Active",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gedit_file_browser_message_activation_init (GeditFileBrowserMessageActivation *message)
+{
+ message->priv = gedit_file_browser_message_activation_get_instance_private (message);
+}
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-activation.h b/plugins/filebrowser/messages/gedit-file-browser-message-activation.h
new file mode 100644
index 0000000..bd3b1ed
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-activation.h
@@ -0,0 +1,69 @@
+
+/*
+ * gedit-file-browser-message-activation.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_H
+#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_H
+
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION (gedit_file_browser_message_activation_get_type ())
+#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\
+ GeditFileBrowserMessageActivation))
+#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\
+ GeditFileBrowserMessageActivation const))
+#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\
+ GeditFileBrowserMessageActivationClass))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_ACTIVATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_ACTIVATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION))
+#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\
+ GeditFileBrowserMessageActivationClass))
+
+typedef struct _GeditFileBrowserMessageActivation GeditFileBrowserMessageActivation;
+typedef struct _GeditFileBrowserMessageActivationClass GeditFileBrowserMessageActivationClass;
+typedef struct _GeditFileBrowserMessageActivationPrivate GeditFileBrowserMessageActivationPrivate;
+
+struct _GeditFileBrowserMessageActivation
+{
+ GeditMessage parent;
+
+ GeditFileBrowserMessageActivationPrivate *priv;
+};
+
+struct _GeditFileBrowserMessageActivationClass
+{
+ GeditMessageClass parent_class;
+};
+
+GType gedit_file_browser_message_activation_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_H */
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c
new file mode 100644
index 0000000..f66a32d
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c
@@ -0,0 +1,162 @@
+
+/*
+ * gedit-file-browser-message-add-filter.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "gedit-file-browser-message-add-filter.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_OBJECT_PATH,
+ PROP_METHOD,
+ PROP_ID,
+};
+
+struct _GeditFileBrowserMessageAddFilterPrivate
+{
+ gchar *object_path;
+ gchar *method;
+ guint id;
+};
+
+G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageAddFilter,
+ gedit_file_browser_message_add_filter,
+ GEDIT_TYPE_MESSAGE,
+ 0,
+ G_ADD_PRIVATE (GeditFileBrowserMessageAddFilter))
+
+static void
+gedit_file_browser_message_add_filter_finalize (GObject *obj)
+{
+ GeditFileBrowserMessageAddFilter *msg = GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER (obj);
+
+ g_free (msg->priv->object_path);
+ g_free (msg->priv->method);
+
+ G_OBJECT_CLASS (gedit_file_browser_message_add_filter_parent_class)->finalize (obj);
+}
+
+static void
+gedit_file_browser_message_add_filter_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageAddFilter *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER (obj);
+
+ switch (prop_id)
+ {
+ case PROP_OBJECT_PATH:
+ g_value_set_string (value, msg->priv->object_path);
+ break;
+ case PROP_METHOD:
+ g_value_set_string (value, msg->priv->method);
+ break;
+ case PROP_ID:
+ g_value_set_uint (value, msg->priv->id);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_add_filter_set_property (GObject *obj,
+ guint prop_id,
+ GValue const *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageAddFilter *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER (obj);
+
+ switch (prop_id)
+ {
+ case PROP_OBJECT_PATH:
+ {
+ g_free (msg->priv->object_path);
+ msg->priv->object_path = g_value_dup_string (value);
+ break;
+ }
+ case PROP_METHOD:
+ {
+ g_free (msg->priv->method);
+ msg->priv->method = g_value_dup_string (value);
+ break;
+ }
+ case PROP_ID:
+ msg->priv->id = g_value_get_uint (value);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_add_filter_class_init (GeditFileBrowserMessageAddFilterClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = gedit_file_browser_message_add_filter_finalize;
+
+ object_class->get_property = gedit_file_browser_message_add_filter_get_property;
+ object_class->set_property = gedit_file_browser_message_add_filter_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_OBJECT_PATH,
+ g_param_spec_string ("object-path",
+ "Object Path",
+ "Object Path",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_METHOD,
+ g_param_spec_string ("method",
+ "Method",
+ "Method",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_uint ("id",
+ "Id",
+ "Id",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gedit_file_browser_message_add_filter_init (GeditFileBrowserMessageAddFilter *message)
+{
+ message->priv = gedit_file_browser_message_add_filter_get_instance_private (message);
+}
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h
new file mode 100644
index 0000000..4a5b35c
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h
@@ -0,0 +1,69 @@
+
+/*
+ * gedit-file-browser-message-add-filter.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_H
+#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_H
+
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER (gedit_file_browser_message_add_filter_get_type ())
+#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\
+ GeditFileBrowserMessageAddFilter))
+#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\
+ GeditFileBrowserMessageAddFilter const))
+#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\
+ GeditFileBrowserMessageAddFilterClass))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_ADD_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_ADD_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER))
+#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\
+ GeditFileBrowserMessageAddFilterClass))
+
+typedef struct _GeditFileBrowserMessageAddFilter GeditFileBrowserMessageAddFilter;
+typedef struct _GeditFileBrowserMessageAddFilterClass GeditFileBrowserMessageAddFilterClass;
+typedef struct _GeditFileBrowserMessageAddFilterPrivate GeditFileBrowserMessageAddFilterPrivate;
+
+struct _GeditFileBrowserMessageAddFilter
+{
+ GeditMessage parent;
+
+ GeditFileBrowserMessageAddFilterPrivate *priv;
+};
+
+struct _GeditFileBrowserMessageAddFilterClass
+{
+ GeditMessageClass parent_class;
+};
+
+GType gedit_file_browser_message_add_filter_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_H */
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c
new file mode 100644
index 0000000..4bb8682
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c
@@ -0,0 +1,128 @@
+
+/*
+ * gedit-file-browser-message-extend-context-menu.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Paolo Borelli
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "gedit-file-browser-message-extend-context-menu.h"
+#include <gedit/gedit-menu-extension.h>
+
+enum
+{
+ PROP_0,
+
+ PROP_EXTENSION,
+};
+
+struct _GeditFileBrowserMessageExtendContextMenuPrivate
+{
+ GeditMenuExtension *extension;
+};
+
+G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageExtendContextMenu,
+ gedit_file_browser_message_extend_context_menu,
+ GEDIT_TYPE_MESSAGE,
+ 0,
+ G_ADD_PRIVATE (GeditFileBrowserMessageExtendContextMenu))
+
+static void
+gedit_file_browser_message_extend_context_menu_finalize (GObject *obj)
+{
+ GeditFileBrowserMessageExtendContextMenu *msg = GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (obj);
+
+ if (msg->priv->extension)
+ {
+ g_object_unref (msg->priv->extension);
+ }
+
+ G_OBJECT_CLASS (gedit_file_browser_message_extend_context_menu_parent_class)->finalize (obj);
+}
+
+static void
+gedit_file_browser_message_extend_context_menu_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageExtendContextMenu *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (obj);
+
+ switch (prop_id)
+ {
+ case PROP_EXTENSION:
+ g_value_set_object (value, msg->priv->extension);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_extend_context_menu_set_property (GObject *obj,
+ guint prop_id,
+ GValue const *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageExtendContextMenu *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (obj);
+
+ switch (prop_id)
+ {
+ case PROP_EXTENSION:
+ {
+ if (msg->priv->extension)
+ {
+ g_object_unref (msg->priv->extension);
+ }
+ msg->priv->extension = g_value_dup_object (value);
+ break;
+ }
+ }
+}
+
+static void
+gedit_file_browser_message_extend_context_menu_class_init (GeditFileBrowserMessageExtendContextMenuClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = gedit_file_browser_message_extend_context_menu_finalize;
+
+ object_class->get_property = gedit_file_browser_message_extend_context_menu_get_property;
+ object_class->set_property = gedit_file_browser_message_extend_context_menu_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_EXTENSION,
+ g_param_spec_object ("extension",
+ "Extension",
+ "Extension",
+ GEDIT_TYPE_MENU_EXTENSION,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gedit_file_browser_message_extend_context_menu_init (GeditFileBrowserMessageExtendContextMenu *message)
+{
+ message->priv = gedit_file_browser_message_extend_context_menu_get_instance_private (message);
+}
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h
new file mode 100644
index 0000000..643485d
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h
@@ -0,0 +1,70 @@
+
+/*
+ * gedit-file-browser-message-extend-context-menu.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Paolo Borelli
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_H
+#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_H
+
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (gedit_file_browser_message_extend_context_menu_get_type ())
+#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\
+ GeditFileBrowserMessageExtendContextMenu))
+#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\
+ GeditFileBrowserMessageExtendContextMenu const))
+#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\
+ GeditFileBrowserMessageExtendContextMenuClass))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU))
+#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\
+ GeditFileBrowserMessageExtendContextMenuClass))
+
+typedef struct _GeditFileBrowserMessageExtendContextMenu GeditFileBrowserMessageExtendContextMenu;
+typedef struct _GeditFileBrowserMessageExtendContextMenuClass GeditFileBrowserMessageExtendContextMenuClass;
+typedef struct _GeditFileBrowserMessageExtendContextMenuPrivate GeditFileBrowserMessageExtendContextMenuPrivate;
+
+struct _GeditFileBrowserMessageExtendContextMenu
+{
+ GeditMessage parent;
+
+ GeditFileBrowserMessageExtendContextMenuPrivate *priv;
+};
+
+struct _GeditFileBrowserMessageExtendContextMenuClass
+{
+ GeditMessageClass parent_class;
+};
+
+GType gedit_file_browser_message_extend_context_menu_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_H */
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-root.c b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.c
new file mode 100644
index 0000000..b766ac1
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.c
@@ -0,0 +1,127 @@
+
+/*
+ * gedit-file-browser-message-get-root.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "gedit-file-browser-message-get-root.h"
+#include "gio/gio.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_LOCATION,
+};
+
+struct _GeditFileBrowserMessageGetRootPrivate
+{
+ GFile *location;
+};
+
+G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageGetRoot,
+ gedit_file_browser_message_get_root,
+ GEDIT_TYPE_MESSAGE,
+ 0,
+ G_ADD_PRIVATE (GeditFileBrowserMessageGetRoot))
+
+static void
+gedit_file_browser_message_get_root_finalize (GObject *obj)
+{
+ GeditFileBrowserMessageGetRoot *msg = GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT (obj);
+
+ if (msg->priv->location)
+ {
+ g_object_unref (msg->priv->location);
+ }
+
+ G_OBJECT_CLASS (gedit_file_browser_message_get_root_parent_class)->finalize (obj);
+}
+
+static void
+gedit_file_browser_message_get_root_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageGetRoot *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT (obj);
+
+ switch (prop_id)
+ {
+ case PROP_LOCATION:
+ g_value_set_object (value, msg->priv->location);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_get_root_set_property (GObject *obj,
+ guint prop_id,
+ GValue const *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageGetRoot *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT (obj);
+
+ switch (prop_id)
+ {
+ case PROP_LOCATION:
+ {
+ if (msg->priv->location)
+ {
+ g_object_unref (msg->priv->location);
+ }
+ msg->priv->location = g_value_dup_object (value);
+ break;
+ }
+ }
+}
+
+static void
+gedit_file_browser_message_get_root_class_init (GeditFileBrowserMessageGetRootClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = gedit_file_browser_message_get_root_finalize;
+
+ object_class->get_property = gedit_file_browser_message_get_root_get_property;
+ object_class->set_property = gedit_file_browser_message_get_root_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_LOCATION,
+ g_param_spec_object ("location",
+ "Location",
+ "Location",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gedit_file_browser_message_get_root_init (GeditFileBrowserMessageGetRoot *message)
+{
+ message->priv = gedit_file_browser_message_get_root_get_instance_private (message);
+}
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-root.h b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.h
new file mode 100644
index 0000000..8cc114d
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.h
@@ -0,0 +1,69 @@
+
+/*
+ * gedit-file-browser-message-get-root.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_H
+#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_H
+
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT (gedit_file_browser_message_get_root_get_type ())
+#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\
+ GeditFileBrowserMessageGetRoot))
+#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\
+ GeditFileBrowserMessageGetRoot const))
+#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\
+ GeditFileBrowserMessageGetRootClass))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT))
+#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\
+ GeditFileBrowserMessageGetRootClass))
+
+typedef struct _GeditFileBrowserMessageGetRoot GeditFileBrowserMessageGetRoot;
+typedef struct _GeditFileBrowserMessageGetRootClass GeditFileBrowserMessageGetRootClass;
+typedef struct _GeditFileBrowserMessageGetRootPrivate GeditFileBrowserMessageGetRootPrivate;
+
+struct _GeditFileBrowserMessageGetRoot
+{
+ GeditMessage parent;
+
+ GeditFileBrowserMessageGetRootPrivate *priv;
+};
+
+struct _GeditFileBrowserMessageGetRootClass
+{
+ GeditMessageClass parent_class;
+};
+
+GType gedit_file_browser_message_get_root_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_H */
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-view.c b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.c
new file mode 100644
index 0000000..17fe757
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.c
@@ -0,0 +1,127 @@
+
+/*
+ * gedit-file-browser-message-get-view.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "gedit-file-browser-message-get-view.h"
+#include "plugins/filebrowser/gedit-file-browser-view.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_VIEW,
+};
+
+struct _GeditFileBrowserMessageGetViewPrivate
+{
+ GeditFileBrowserView *view;
+};
+
+G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageGetView,
+ gedit_file_browser_message_get_view,
+ GEDIT_TYPE_MESSAGE,
+ 0,
+ G_ADD_PRIVATE (GeditFileBrowserMessageGetView))
+
+static void
+gedit_file_browser_message_get_view_finalize (GObject *obj)
+{
+ GeditFileBrowserMessageGetView *msg = GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW (obj);
+
+ if (msg->priv->view)
+ {
+ g_object_unref (msg->priv->view);
+ }
+
+ G_OBJECT_CLASS (gedit_file_browser_message_get_view_parent_class)->finalize (obj);
+}
+
+static void
+gedit_file_browser_message_get_view_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageGetView *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW (obj);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ g_value_set_object (value, msg->priv->view);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_get_view_set_property (GObject *obj,
+ guint prop_id,
+ GValue const *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageGetView *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW (obj);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ {
+ if (msg->priv->view)
+ {
+ g_object_unref (msg->priv->view);
+ }
+ msg->priv->view = g_value_dup_object (value);
+ break;
+ }
+ }
+}
+
+static void
+gedit_file_browser_message_get_view_class_init (GeditFileBrowserMessageGetViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = gedit_file_browser_message_get_view_finalize;
+
+ object_class->get_property = gedit_file_browser_message_get_view_get_property;
+ object_class->set_property = gedit_file_browser_message_get_view_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_VIEW,
+ g_param_spec_object ("view",
+ "View",
+ "View",
+ GEDIT_TYPE_FILE_BROWSER_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gedit_file_browser_message_get_view_init (GeditFileBrowserMessageGetView *message)
+{
+ message->priv = gedit_file_browser_message_get_view_get_instance_private (message);
+}
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-view.h b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.h
new file mode 100644
index 0000000..452abbc
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.h
@@ -0,0 +1,69 @@
+
+/*
+ * gedit-file-browser-message-get-view.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_H
+#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_H
+
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW (gedit_file_browser_message_get_view_get_type ())
+#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\
+ GeditFileBrowserMessageGetView))
+#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\
+ GeditFileBrowserMessageGetView const))
+#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\
+ GeditFileBrowserMessageGetViewClass))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW))
+#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\
+ GeditFileBrowserMessageGetViewClass))
+
+typedef struct _GeditFileBrowserMessageGetView GeditFileBrowserMessageGetView;
+typedef struct _GeditFileBrowserMessageGetViewClass GeditFileBrowserMessageGetViewClass;
+typedef struct _GeditFileBrowserMessageGetViewPrivate GeditFileBrowserMessageGetViewPrivate;
+
+struct _GeditFileBrowserMessageGetView
+{
+ GeditMessage parent;
+
+ GeditFileBrowserMessageGetViewPrivate *priv;
+};
+
+struct _GeditFileBrowserMessageGetViewClass
+{
+ GeditMessageClass parent_class;
+};
+
+GType gedit_file_browser_message_get_view_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_H */
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id-location.c b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.c
new file mode 100644
index 0000000..36c33c5
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.c
@@ -0,0 +1,190 @@
+
+/*
+ * gedit-file-browser-message-id-location.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2013 - Garrett Regier
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "gedit-file-browser-message-id-location.h"
+#include "gio/gio.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_ID,
+ PROP_NAME,
+ PROP_LOCATION,
+ PROP_IS_DIRECTORY,
+};
+
+struct _GeditFileBrowserMessageIdLocationPrivate
+{
+ gchar *id;
+ gchar *name;
+ GFile *location;
+ gboolean is_directory;
+};
+
+G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageIdLocation,
+ gedit_file_browser_message_id_location,
+ GEDIT_TYPE_MESSAGE,
+ 0,
+ G_ADD_PRIVATE (GeditFileBrowserMessageIdLocation))
+
+static void
+gedit_file_browser_message_id_location_finalize (GObject *obj)
+{
+ GeditFileBrowserMessageIdLocation *msg = GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION (obj);
+
+ g_free (msg->priv->id);
+ g_free (msg->priv->name);
+ if (msg->priv->location)
+ {
+ g_object_unref (msg->priv->location);
+ }
+
+ G_OBJECT_CLASS (gedit_file_browser_message_id_location_parent_class)->finalize (obj);
+}
+
+static void
+gedit_file_browser_message_id_location_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageIdLocation *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION (obj);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ g_value_set_string (value, msg->priv->id);
+ break;
+ case PROP_NAME:
+ g_value_set_string (value, msg->priv->name);
+ break;
+ case PROP_LOCATION:
+ g_value_set_object (value, msg->priv->location);
+ break;
+ case PROP_IS_DIRECTORY:
+ g_value_set_boolean (value, msg->priv->is_directory);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_id_location_set_property (GObject *obj,
+ guint prop_id,
+ GValue const *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageIdLocation *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION (obj);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ {
+ g_free (msg->priv->id);
+ msg->priv->id = g_value_dup_string (value);
+ break;
+ }
+ case PROP_NAME:
+ {
+ g_free (msg->priv->name);
+ msg->priv->name = g_value_dup_string (value);
+ break;
+ }
+ case PROP_LOCATION:
+ {
+ if (msg->priv->location)
+ {
+ g_object_unref (msg->priv->location);
+ }
+ msg->priv->location = g_value_dup_object (value);
+ break;
+ }
+ case PROP_IS_DIRECTORY:
+ msg->priv->is_directory = g_value_get_boolean (value);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_id_location_class_init (GeditFileBrowserMessageIdLocationClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = gedit_file_browser_message_id_location_finalize;
+
+ object_class->get_property = gedit_file_browser_message_id_location_get_property;
+ object_class->set_property = gedit_file_browser_message_id_location_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_string ("id",
+ "Id",
+ "Id",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_NAME,
+ g_param_spec_string ("name",
+ "Name",
+ "Name",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_LOCATION,
+ g_param_spec_object ("location",
+ "Location",
+ "Location",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_IS_DIRECTORY,
+ g_param_spec_boolean ("is-directory",
+ "Is Directory",
+ "Is Directory",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gedit_file_browser_message_id_location_init (GeditFileBrowserMessageIdLocation *message)
+{
+ message->priv = gedit_file_browser_message_id_location_get_instance_private (message);
+}
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id-location.h b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.h
new file mode 100644
index 0000000..0af4c77
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.h
@@ -0,0 +1,70 @@
+
+/*
+ * gedit-file-browser-message-id-location.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2013 - Garrett Regier
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_H
+#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_H
+
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION (gedit_file_browser_message_id_location_get_type ())
+#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\
+ GeditFileBrowserMessageIdLocation))
+#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\
+ GeditFileBrowserMessageIdLocation const))
+#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\
+ GeditFileBrowserMessageIdLocationClass))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID_LOCATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION))
+#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\
+ GeditFileBrowserMessageIdLocationClass))
+
+typedef struct _GeditFileBrowserMessageIdLocation GeditFileBrowserMessageIdLocation;
+typedef struct _GeditFileBrowserMessageIdLocationClass GeditFileBrowserMessageIdLocationClass;
+typedef struct _GeditFileBrowserMessageIdLocationPrivate GeditFileBrowserMessageIdLocationPrivate;
+
+struct _GeditFileBrowserMessageIdLocation
+{
+ GeditMessage parent;
+
+ GeditFileBrowserMessageIdLocationPrivate *priv;
+};
+
+struct _GeditFileBrowserMessageIdLocationClass
+{
+ GeditMessageClass parent_class;
+};
+
+GType gedit_file_browser_message_id_location_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_H */
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id.c b/plugins/filebrowser/messages/gedit-file-browser-message-id.c
new file mode 100644
index 0000000..fec490f
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-id.c
@@ -0,0 +1,107 @@
+
+/*
+ * gedit-file-browser-message-id.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "gedit-file-browser-message-id.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_ID,
+};
+
+struct _GeditFileBrowserMessageIdPrivate
+{
+ guint id;
+};
+
+G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageId,
+ gedit_file_browser_message_id,
+ GEDIT_TYPE_MESSAGE,
+ 0,
+ G_ADD_PRIVATE (GeditFileBrowserMessageId))
+
+static void
+gedit_file_browser_message_id_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageId *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_ID (obj);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ g_value_set_uint (value, msg->priv->id);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_id_set_property (GObject *obj,
+ guint prop_id,
+ GValue const *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageId *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_ID (obj);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ msg->priv->id = g_value_get_uint (value);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_id_class_init (GeditFileBrowserMessageIdClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = gedit_file_browser_message_id_get_property;
+ object_class->set_property = gedit_file_browser_message_id_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_uint ("id",
+ "Id",
+ "Id",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gedit_file_browser_message_id_init (GeditFileBrowserMessageId *message)
+{
+ message->priv = gedit_file_browser_message_id_get_instance_private (message);
+}
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id.h b/plugins/filebrowser/messages/gedit-file-browser-message-id.h
new file mode 100644
index 0000000..8351c3b
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-id.h
@@ -0,0 +1,69 @@
+
+/*
+ * gedit-file-browser-message-id.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGE_ID_H
+#define GEDIT_FILE_BROWSER_MESSAGE_ID_H
+
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID (gedit_file_browser_message_id_get_type ())
+#define GEDIT_FILE_BROWSER_MESSAGE_ID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\
+ GeditFileBrowserMessageId))
+#define GEDIT_FILE_BROWSER_MESSAGE_ID_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\
+ GeditFileBrowserMessageId const))
+#define GEDIT_FILE_BROWSER_MESSAGE_ID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\
+ GeditFileBrowserMessageIdClass))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID))
+#define GEDIT_FILE_BROWSER_MESSAGE_ID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\
+ GeditFileBrowserMessageIdClass))
+
+typedef struct _GeditFileBrowserMessageId GeditFileBrowserMessageId;
+typedef struct _GeditFileBrowserMessageIdClass GeditFileBrowserMessageIdClass;
+typedef struct _GeditFileBrowserMessageIdPrivate GeditFileBrowserMessageIdPrivate;
+
+struct _GeditFileBrowserMessageId
+{
+ GeditMessage parent;
+
+ GeditFileBrowserMessageIdPrivate *priv;
+};
+
+struct _GeditFileBrowserMessageIdClass
+{
+ GeditMessageClass parent_class;
+};
+
+GType gedit_file_browser_message_id_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGE_ID_H */
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c
new file mode 100644
index 0000000..8e63042
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c
@@ -0,0 +1,142 @@
+
+/*
+ * gedit-file-browser-message-set-emblem.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "gedit-file-browser-message-set-emblem.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_ID,
+ PROP_EMBLEM,
+};
+
+struct _GeditFileBrowserMessageSetEmblemPrivate
+{
+ gchar *id;
+ gchar *emblem;
+};
+
+G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageSetEmblem,
+ gedit_file_browser_message_set_emblem,
+ GEDIT_TYPE_MESSAGE,
+ 0,
+ G_ADD_PRIVATE (GeditFileBrowserMessageSetEmblem))
+
+static void
+gedit_file_browser_message_set_emblem_finalize (GObject *obj)
+{
+ GeditFileBrowserMessageSetEmblem *msg = GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM (obj);
+
+ g_free (msg->priv->id);
+ g_free (msg->priv->emblem);
+
+ G_OBJECT_CLASS (gedit_file_browser_message_set_emblem_parent_class)->finalize (obj);
+}
+
+static void
+gedit_file_browser_message_set_emblem_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageSetEmblem *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM (obj);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ g_value_set_string (value, msg->priv->id);
+ break;
+ case PROP_EMBLEM:
+ g_value_set_string (value, msg->priv->emblem);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_set_emblem_set_property (GObject *obj,
+ guint prop_id,
+ GValue const *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageSetEmblem *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM (obj);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ {
+ g_free (msg->priv->id);
+ msg->priv->id = g_value_dup_string (value);
+ break;
+ }
+ case PROP_EMBLEM:
+ {
+ g_free (msg->priv->emblem);
+ msg->priv->emblem = g_value_dup_string (value);
+ break;
+ }
+ }
+}
+
+static void
+gedit_file_browser_message_set_emblem_class_init (GeditFileBrowserMessageSetEmblemClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = gedit_file_browser_message_set_emblem_finalize;
+
+ object_class->get_property = gedit_file_browser_message_set_emblem_get_property;
+ object_class->set_property = gedit_file_browser_message_set_emblem_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_string ("id",
+ "Id",
+ "Id",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_EMBLEM,
+ g_param_spec_string ("emblem",
+ "Emblem",
+ "Emblem",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gedit_file_browser_message_set_emblem_init (GeditFileBrowserMessageSetEmblem *message)
+{
+ message->priv = gedit_file_browser_message_set_emblem_get_instance_private (message);
+}
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h
new file mode 100644
index 0000000..6daa795
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h
@@ -0,0 +1,69 @@
+
+/*
+ * gedit-file-browser-message-set-emblem.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_H
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_H
+
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM (gedit_file_browser_message_set_emblem_get_type ())
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\
+ GeditFileBrowserMessageSetEmblem))
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\
+ GeditFileBrowserMessageSetEmblem const))
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\
+ GeditFileBrowserMessageSetEmblemClass))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_EMBLEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_EMBLEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM))
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\
+ GeditFileBrowserMessageSetEmblemClass))
+
+typedef struct _GeditFileBrowserMessageSetEmblem GeditFileBrowserMessageSetEmblem;
+typedef struct _GeditFileBrowserMessageSetEmblemClass GeditFileBrowserMessageSetEmblemClass;
+typedef struct _GeditFileBrowserMessageSetEmblemPrivate GeditFileBrowserMessageSetEmblemPrivate;
+
+struct _GeditFileBrowserMessageSetEmblem
+{
+ GeditMessage parent;
+
+ GeditFileBrowserMessageSetEmblemPrivate *priv;
+};
+
+struct _GeditFileBrowserMessageSetEmblemClass
+{
+ GeditMessageClass parent_class;
+};
+
+GType gedit_file_browser_message_set_emblem_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_H */
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c
new file mode 100644
index 0000000..3b14d16
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c
@@ -0,0 +1,143 @@
+
+/*
+ * gedit-file-browser-message-set-markup.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2013 - Garrett Regier
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "gedit-file-browser-message-set-markup.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_ID,
+ PROP_MARKUP,
+};
+
+struct _GeditFileBrowserMessageSetMarkupPrivate
+{
+ gchar *id;
+ gchar *markup;
+};
+
+G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageSetMarkup,
+ gedit_file_browser_message_set_markup,
+ GEDIT_TYPE_MESSAGE,
+ 0,
+ G_ADD_PRIVATE (GeditFileBrowserMessageSetMarkup))
+
+static void
+gedit_file_browser_message_set_markup_finalize (GObject *obj)
+{
+ GeditFileBrowserMessageSetMarkup *msg = GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP (obj);
+
+ g_free (msg->priv->id);
+ g_free (msg->priv->markup);
+
+ G_OBJECT_CLASS (gedit_file_browser_message_set_markup_parent_class)->finalize (obj);
+}
+
+static void
+gedit_file_browser_message_set_markup_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageSetMarkup *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP (obj);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ g_value_set_string (value, msg->priv->id);
+ break;
+ case PROP_MARKUP:
+ g_value_set_string (value, msg->priv->markup);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_set_markup_set_property (GObject *obj,
+ guint prop_id,
+ GValue const *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageSetMarkup *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP (obj);
+
+ switch (prop_id)
+ {
+ case PROP_ID:
+ {
+ g_free (msg->priv->id);
+ msg->priv->id = g_value_dup_string (value);
+ break;
+ }
+ case PROP_MARKUP:
+ {
+ g_free (msg->priv->markup);
+ msg->priv->markup = g_value_dup_string (value);
+ break;
+ }
+ }
+}
+
+static void
+gedit_file_browser_message_set_markup_class_init (GeditFileBrowserMessageSetMarkupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = gedit_file_browser_message_set_markup_finalize;
+
+ object_class->get_property = gedit_file_browser_message_set_markup_get_property;
+ object_class->set_property = gedit_file_browser_message_set_markup_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_ID,
+ g_param_spec_string ("id",
+ "Id",
+ "Id",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_MARKUP,
+ g_param_spec_string ("markup",
+ "Markup",
+ "Markup",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gedit_file_browser_message_set_markup_init (GeditFileBrowserMessageSetMarkup *message)
+{
+ message->priv = gedit_file_browser_message_set_markup_get_instance_private (message);
+}
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h
new file mode 100644
index 0000000..ab9a1dc
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h
@@ -0,0 +1,70 @@
+
+/*
+ * gedit-file-browser-message-set-markup.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2013 - Garrett Regier
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_H
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_H
+
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP (gedit_file_browser_message_set_markup_get_type ())
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\
+ GeditFileBrowserMessageSetMarkup))
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\
+ GeditFileBrowserMessageSetMarkup const))
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\
+ GeditFileBrowserMessageSetMarkupClass))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_MARKUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_MARKUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP))
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\
+ GeditFileBrowserMessageSetMarkupClass))
+
+typedef struct _GeditFileBrowserMessageSetMarkup GeditFileBrowserMessageSetMarkup;
+typedef struct _GeditFileBrowserMessageSetMarkupClass GeditFileBrowserMessageSetMarkupClass;
+typedef struct _GeditFileBrowserMessageSetMarkupPrivate GeditFileBrowserMessageSetMarkupPrivate;
+
+struct _GeditFileBrowserMessageSetMarkup
+{
+ GeditMessage parent;
+
+ GeditFileBrowserMessageSetMarkupPrivate *priv;
+};
+
+struct _GeditFileBrowserMessageSetMarkupClass
+{
+ GeditMessageClass parent_class;
+};
+
+GType gedit_file_browser_message_set_markup_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_H */
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-root.c b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.c
new file mode 100644
index 0000000..cb3891b
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.c
@@ -0,0 +1,149 @@
+
+/*
+ * gedit-file-browser-message-set-root.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+
+#include "gedit-file-browser-message-set-root.h"
+#include "gio/gio.h"
+
+enum
+{
+ PROP_0,
+
+ PROP_LOCATION,
+ PROP_VIRTUAL,
+};
+
+struct _GeditFileBrowserMessageSetRootPrivate
+{
+ GFile *location;
+ gchar *virtual;
+};
+
+G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageSetRoot,
+ gedit_file_browser_message_set_root,
+ GEDIT_TYPE_MESSAGE,
+ 0,
+ G_ADD_PRIVATE (GeditFileBrowserMessageSetRoot))
+
+static void
+gedit_file_browser_message_set_root_finalize (GObject *obj)
+{
+ GeditFileBrowserMessageSetRoot *msg = GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT (obj);
+
+ if (msg->priv->location)
+ {
+ g_object_unref (msg->priv->location);
+ }
+ g_free (msg->priv->virtual);
+
+ G_OBJECT_CLASS (gedit_file_browser_message_set_root_parent_class)->finalize (obj);
+}
+
+static void
+gedit_file_browser_message_set_root_get_property (GObject *obj,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageSetRoot *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT (obj);
+
+ switch (prop_id)
+ {
+ case PROP_LOCATION:
+ g_value_set_object (value, msg->priv->location);
+ break;
+ case PROP_VIRTUAL:
+ g_value_set_string (value, msg->priv->virtual);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_message_set_root_set_property (GObject *obj,
+ guint prop_id,
+ GValue const *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserMessageSetRoot *msg;
+
+ msg = GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT (obj);
+
+ switch (prop_id)
+ {
+ case PROP_LOCATION:
+ {
+ if (msg->priv->location)
+ {
+ g_object_unref (msg->priv->location);
+ }
+ msg->priv->location = g_value_dup_object (value);
+ break;
+ }
+ case PROP_VIRTUAL:
+ {
+ g_free (msg->priv->virtual);
+ msg->priv->virtual = g_value_dup_string (value);
+ break;
+ }
+ }
+}
+
+static void
+gedit_file_browser_message_set_root_class_init (GeditFileBrowserMessageSetRootClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->finalize = gedit_file_browser_message_set_root_finalize;
+
+ object_class->get_property = gedit_file_browser_message_set_root_get_property;
+ object_class->set_property = gedit_file_browser_message_set_root_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_LOCATION,
+ g_param_spec_object ("location",
+ "Location",
+ "Location",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (object_class,
+ PROP_VIRTUAL,
+ g_param_spec_string ("virtual",
+ "Virtual",
+ "Virtual",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gedit_file_browser_message_set_root_init (GeditFileBrowserMessageSetRoot *message)
+{
+ message->priv = gedit_file_browser_message_set_root_get_instance_private (message);
+}
diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-root.h b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.h
new file mode 100644
index 0000000..8b61c50
--- /dev/null
+++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.h
@@ -0,0 +1,69 @@
+
+/*
+ * gedit-file-browser-message-set-root.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Jesse van den Kieboom
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_H
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_H
+
+#include <gedit/gedit-message.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT (gedit_file_browser_message_set_root_get_type ())
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\
+ GeditFileBrowserMessageSetRoot))
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\
+ GeditFileBrowserMessageSetRoot const))
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\
+ GeditFileBrowserMessageSetRootClass))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT))
+#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT))
+#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
+ GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\
+ GeditFileBrowserMessageSetRootClass))
+
+typedef struct _GeditFileBrowserMessageSetRoot GeditFileBrowserMessageSetRoot;
+typedef struct _GeditFileBrowserMessageSetRootClass GeditFileBrowserMessageSetRootClass;
+typedef struct _GeditFileBrowserMessageSetRootPrivate GeditFileBrowserMessageSetRootPrivate;
+
+struct _GeditFileBrowserMessageSetRoot
+{
+ GeditMessage parent;
+
+ GeditFileBrowserMessageSetRootPrivate *priv;
+};
+
+struct _GeditFileBrowserMessageSetRootClass
+{
+ GeditMessageClass parent_class;
+};
+
+GType gedit_file_browser_message_set_root_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_H */
diff --git a/plugins/filebrowser/messages/meson.build b/plugins/filebrowser/messages/meson.build
new file mode 100644
index 0000000..ec4fb84
--- /dev/null
+++ b/plugins/filebrowser/messages/meson.build
@@ -0,0 +1,25 @@
+libfilebrowser_public_h += files(
+ 'gedit-file-browser-message-activation.h',
+ 'gedit-file-browser-message-add-filter.h',
+ 'gedit-file-browser-message-extend-context-menu.h',
+ 'gedit-file-browser-message-get-root.h',
+ 'gedit-file-browser-message-get-view.h',
+ 'gedit-file-browser-message-id.h',
+ 'gedit-file-browser-message-id-location.h',
+ 'gedit-file-browser-message-set-emblem.h',
+ 'gedit-file-browser-message-set-markup.h',
+ 'gedit-file-browser-message-set-root.h',
+)
+
+libfilebrowser_sources += files(
+ 'gedit-file-browser-message-activation.c',
+ 'gedit-file-browser-message-add-filter.c',
+ 'gedit-file-browser-message-extend-context-menu.c',
+ 'gedit-file-browser-message-get-root.c',
+ 'gedit-file-browser-message-get-view.c',
+ 'gedit-file-browser-message-id.c',
+ 'gedit-file-browser-message-id-location.c',
+ 'gedit-file-browser-message-set-emblem.c',
+ 'gedit-file-browser-message-set-markup.c',
+ 'gedit-file-browser-message-set-root.c',
+)
diff --git a/plugins/filebrowser/messages/messages.h b/plugins/filebrowser/messages/messages.h
new file mode 100644
index 0000000..4dc0f51
--- /dev/null
+++ b/plugins/filebrowser/messages/messages.h
@@ -0,0 +1,16 @@
+#ifndef GEDIT_FILE_BROWER_MESSAGES_MESSAGES_H
+#define GEDIT_FILE_BROWER_MESSAGES_MESSAGES_H
+
+#include "gedit-file-browser-message-activation.h"
+#include "gedit-file-browser-message-add-filter.h"
+#include "gedit-file-browser-message-extend-context-menu.h"
+#include "gedit-file-browser-message-get-root.h"
+#include "gedit-file-browser-message-get-view.h"
+#include "gedit-file-browser-message-id.h"
+#include "gedit-file-browser-message-id-location.h"
+#include "gedit-file-browser-message-set-emblem.h"
+#include "gedit-file-browser-message-set-markup.h"
+#include "gedit-file-browser-message-set-root.h"
+
+#endif /* GEDIT_FILE_BROWER_MESSAGES_MESSAGES_H */
+
diff --git a/plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml b/plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml
new file mode 100644
index 0000000..9e9d856
--- /dev/null
+++ b/plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml
@@ -0,0 +1,58 @@
+<schemalist gettext-domain="gedit">
+ <schema id="org.gnome.gedit.plugins.filebrowser" path="/org/gnome/gedit/plugins/filebrowser/">
+ <key name="tree-view" type="b">
+ <default>true</default>
+ <summary>Open With Tree View</summary>
+ <description>Open the tree view when the file browser plugin gets loaded instead of the bookmarks view</description>
+ </key>
+ <key name="root" type="s">
+ <default>''</default>
+ <summary>File Browser Root Directory</summary>
+ <description>The file browser root directory to use when loading the file browser plugin and onload/tree_view is TRUE.</description>
+ </key>
+ <key name="virtual-root" type="s">
+ <default>''</default>
+ <summary>File Browser Virtual Root Directory</summary>
+ <description>The file browser virtual root directory to use when loading the file browser plugin when onload/tree_view is TRUE. The virtual root must always be below the actual root.</description>
+ </key>
+ <key name="enable-remote" type="b">
+ <default>false</default>
+ <summary>Enable Restore of Remote Locations</summary>
+ <description>Sets whether to enable restoring of remote locations.</description>
+ </key>
+ <key name="open-at-first-doc" type="b">
+ <default>true</default>
+ <summary>Set Location to First Document</summary>
+ <description>If TRUE the file browser plugin will view the directory of the first opened document given that the file browser hasn’t been used yet. (Thus this generally applies to opening a document from the command line or opening it with Nautilus, etc.)</description>
+ </key>
+ <key name="filter-mode" flags="org.gnome.gedit.plugins.filebrowser.GeditFileBrowserStoreFilterMode">
+ <default>['hide-hidden', 'hide-binary']</default>
+ <summary>File Browser Filter Mode</summary>
+ <description>This value determines what files get filtered from the file browser. Valid values are: none (filter nothing), hide-hidden (filter hidden files) and hide-binary (filter binary files).</description>
+ </key>
+ <key name="filter-pattern" type="s">
+ <default>''</default>
+ <summary>File Browser Filter Pattern</summary>
+ <description>The filter pattern to filter the file browser with. This filter works on top of the filter_mode.</description>
+ </key>
+ <key name="binary-patterns" type="as">
+ <default>['*.la', '*.lo']</default>
+ <summary>File Browser Binary Patterns</summary>
+ <description>The supplemental patterns to use when filtering binary files.</description>
+ </key>
+ </schema>
+
+ <enum id="org.gnome.gedit.plugins.filebrowser.nautilus.ClickPolicy">
+ <value value="0" nick="single"/>
+ <value value="1" nick="double"/>
+ </enum>
+
+ <schema id="org.gnome.gedit.plugins.filebrowser.nautilus" path="/org/gnome/gedit/plugins/filebrowser/nautilus/">
+ <key name="click-policy" enum="org.gnome.gedit.plugins.filebrowser.nautilus.ClickPolicy">
+ <default>'double'</default>
+ </key>
+ <key name="confirm-trash" type="b">
+ <default>true</default>
+ </key>
+ </schema>
+</schemalist>
diff --git a/plugins/filebrowser/resources/gedit-file-browser.gresource.xml b/plugins/filebrowser/resources/gedit-file-browser.gresource.xml
new file mode 100644
index 0000000..273bd26
--- /dev/null
+++ b/plugins/filebrowser/resources/gedit-file-browser.gresource.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/gedit/plugins/file-browser">
+ <file preprocess="xml-stripblanks">ui/gedit-file-browser-menus.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-file-browser-widget.ui</file>
+ </gresource>
+</gresources>
diff --git a/plugins/filebrowser/resources/meson.build b/plugins/filebrowser/resources/meson.build
new file mode 100644
index 0000000..da2b577
--- /dev/null
+++ b/plugins/filebrowser/resources/meson.build
@@ -0,0 +1,8 @@
+libfilebrowser_res = gnome.compile_resources(
+ 'gedit-file-browser-resources',
+ 'gedit-file-browser.gresource.xml',
+)
+
+libfilebrowser_sources += [
+ libfilebrowser_res.get(0),
+]
diff --git a/plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui b/plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui
new file mode 100644
index 0000000..9134268
--- /dev/null
+++ b/plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <menu id="dir-menu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Open</attribute>
+ <attribute name="action">browser.open</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Set Root to Active Document</attribute>
+ <attribute name="action">browser.set_active_root</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_New Folder</attribute>
+ <attribute name="action">browser.new_folder</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">New F_ile</attribute>
+ <attribute name="action">browser.new_file</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Rename…</attribute>
+ <attribute name="action">browser.rename</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Move to Trash</attribute>
+ <attribute name="action">browser.move_to_trash</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Delete</attribute>
+ <attribute name="action">browser.delete</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Re_fresh View</attribute>
+ <attribute name="action">browser.refresh_view</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_View Folder</attribute>
+ <attribute name="action">browser.view_folder</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">_Open in Terminal</attribute>
+ <attribute name="action">browser.open_in_terminal</attribute>
+ </item>
+ </section>
+ <submenu>
+ <attribute name="label" translatable="yes">_Filter</attribute>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Show _Hidden</attribute>
+ <attribute name="action">browser.show_hidden</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Show _Binary</attribute>
+ <attribute name="action">browser.show_binary</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Match Filename</attribute>
+ <attribute name="action">browser.show_match_filename</attribute>
+ </item>
+ </section>
+ </submenu>
+ <section>
+ <attribute name="id">extension-section</attribute>
+ </section>
+ </menu>
+ <menu id="bookmarks-menu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">_Set Root to Active Document</attribute>
+ <attribute name="action">browser.set_active_root</attribute>
+ </item>
+ </section>
+ </menu>
+</interface>
diff --git a/plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui b/plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui
new file mode 100644
index 0000000..e4fb300
--- /dev/null
+++ b/plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.6 -->
+ <object class="GtkListStore" id="locations_model">
+ <columns>
+ <!-- column-name icon -->
+ <column type="GdkPixbuf"/>
+ <!-- column-name icon name -->
+ <column type="gchararray"/>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name file -->
+ <column type="GFile"/>
+ <!-- column-name id -->
+ <column type="guint"/>
+ </columns>
+ </object>
+ <object class="GtkPopover" id="locations_popover">
+ <property name="can_focus">True</property>
+ <property name="visible">True</property>
+ <property name="width-request">300</property>
+ <property name="height-request">300</property>
+ <child>
+ <object class="GtkScrolledWindow" id="locations_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <property name="margin">6</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkTreeView" id="locations_treeview">
+ <property name="visible">True</property>
+ <property name="headers_visible">False</property>
+ <property name="can_focus">True</property>
+ <property name="model">locations_model</property>
+ <property name="activate-on-single-click">True</property>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeview_icon_column">
+ <child>
+ <object class="GtkCellRendererPixbuf" id="treeview_icon_renderer"/>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeview_name_column">
+ <child>
+ <object class="GtkCellRendererText" id="treeview_name_renderer">
+ <property name="ellipsize">end</property>
+ </object>
+ <attributes>
+ <attribute name="text">2</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="locations_treeview_selection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkMenu" id="location_previous_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <object class="GtkMenu" id="location_next_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <template class="GeditFileBrowserWidget" parent="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="toolbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin">3</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="previous_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="action_name">browser.previous_location</property>
+ <property name="image">previous_image</property>
+ <style>
+ <class name="small-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="next_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="action_name">browser.next_location</property>
+ <property name="image">next_image</property>
+ <style>
+ <class name="small-button"/>
+ </style>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="action_name">browser.up</property>
+ <property name="image">up_image</property>
+ <style>
+ <class name="small-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkMenuButton" id="locations_button">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <property name="use_popover">True</property>
+ <property name="popover">locations_popover</property>
+ <style>
+ <class name="text-button"/>
+ <class name="image-button"/>
+ <class name="small-button"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="locations_button_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_focus">False</property>
+ <property name="is_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCellView" id="locations_cellview">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">locations_model</property>
+ <child>
+ <object class="GtkCellRendererPixbuf" id="cellview_icon_renderer"/>
+ </child>
+ <child>
+ <object class="GtkCellRendererText" id="cellview_name_renderer">
+ <property name="ellipsize">end</property>
+ </object>
+ <attributes>
+ <attribute name="text">2</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="locations_button_arrow">
+ <property name="visible">True</property>
+ <property name="valign">baseline</property>
+ <property name="icon_name">pan-down-symbolic</property>
+ </object>
+ <packing>
+ <property name="pack-type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="locations_button_a11y">
+ <property name="accessible-name" translatable="yes">History</property>
+ <property name="accessible-description" translatable="yes">Open history menu</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSearchEntry" id="location_entry">
+ <property name="visible">False</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_name">folder-symbolic</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GeditFileBrowserView" id="treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection1"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRevealer" id="filter_entry_revealer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="reveal_child">False</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkEntry" id="filter_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="placeholder_text">Match Filename</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </template>
+ <object class="GtkImage" id="previous_image">
+ <property name="visible">True</property>
+ <property name="icon_name">go-previous-symbolic</property>
+ <property name="icon-size">2</property>
+ </object>
+ <object class="GtkImage" id="next_image">
+ <property name="visible">True</property>
+ <property name="icon_name">go-next-symbolic</property>
+ <property name="icon-size">2</property>
+ </object>
+ <object class="GtkImage" id="up_image">
+ <property name="visible">True</property>
+ <property name="icon_name">go-up-symbolic</property>
+ <property name="icon-size">2</property>
+ </object>
+</interface>
diff --git a/plugins/generate-list.sh b/plugins/generate-list.sh
new file mode 100755
index 0000000..c77cab0
--- /dev/null
+++ b/plugins/generate-list.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+# SPDX-FileCopyrightText: 2020 Sébastien Wilmet <swilmet@gnome.org>
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# This script generates a Markdown file with the names and descriptions of all
+# official gedit plugins.
+
+write_list_for_plugins_dir() {
+ plugins_dir=$1
+
+ for plugin_desktop_file in `find "$plugins_dir" -name '*.plugin.desktop*'`
+ do
+ name=`grep -P '^Name=' "$plugin_desktop_file" | cut -d'=' -f2`
+ echo -n "- **$name** - "
+
+ desc=`grep -P '^Description=' "$plugin_desktop_file" | cut -d'=' -f2`
+ echo "*$desc*"
+ done | sort
+}
+
+write_content() {
+ echo 'gedit plugins'
+ echo '============='
+ echo
+ echo 'Core plugins'
+ echo '------------'
+ echo
+ echo 'Plugins that are distributed with gedit itself.'
+ echo
+
+ write_list_for_plugins_dir '.'
+
+ echo
+ echo 'gedit-plugins package'
+ echo '---------------------'
+ echo
+ echo 'The gedit-plugins package contains useful plugins that are (most'
+ echo 'of the time) too specific to be distributed with gedit itself.'
+ echo
+
+ write_list_for_plugins_dir '../../gedit-plugins/plugins'
+}
+
+write_content > list-of-gedit-plugins.md
diff --git a/plugins/list-of-gedit-plugins.md b/plugins/list-of-gedit-plugins.md
new file mode 100644
index 0000000..3afea6c
--- /dev/null
+++ b/plugins/list-of-gedit-plugins.md
@@ -0,0 +1,45 @@
+gedit plugins
+=============
+
+Core plugins
+------------
+
+Plugins that are distributed with gedit itself.
+
+- **Document Statistics** - *Report the number of words, lines and characters in a document.*
+- **External Tools** - *Execute external commands and shell scripts.*
+- **File Browser Panel** - *Easy file access from the side panel.*
+- **Insert Date/Time** - *Inserts current date and time at the cursor position.*
+- **Modelines** - *Emacs, Kate and Vim-style modelines support for gedit.*
+- **Python Console** - *Interactive Python console standing in the bottom panel.*
+- **Quick Highlight** - *Highlights every occurrences of selected text.*
+- **Quick Open** - *Quickly open files.*
+- **Snippets** - *Insert often-used pieces of text in a fast way.*
+- **Sort** - *Sorts a document or selected text.*
+- **Spell Checker** - *Checks the spelling of the current document.*
+
+gedit-plugins package
+---------------------
+
+The gedit-plugins package contains useful plugins that are (most
+of the time) too specific to be distributed with gedit itself.
+
+- **Bookmarks** - *Easy document navigation with bookmarks*
+- **Bracket Completion** - *Automatically adds closing brackets.*
+- **Character Map** - *Insert special characters just by clicking on them.*
+- **Code Comment** - *Comment out or uncomment a selected block of code.*
+- **Color Picker** - *Pick a color from a dialog and insert its hexadecimal representation.*
+- **Color Scheme Editor** - *Source code color scheme editor*
+- **Commander** - *Command line interface for advanced editing*
+- **Draw Spaces** - *Draw spaces and tabs*
+- **Embedded Terminal** - *Embed a terminal in the bottom pane.*
+- **Find in Files** - *Find text in all files of a folder.*
+- **Git** - *Highlight lines that have been changed since the last commit*
+- **Join/Split Lines** - *Join several lines or split long ones*
+- **Multi Edit** - *Edit document in multiple places at once*
+- **Session Saver** - *Save and restore your working sessions*
+- **Smart Spaces** - *Forget you’re not using tabulations.*
+- **SyncTeX** - *Synchronize between LaTeX and PDF with gedit and evince.*
+- **Text Size** - *Easily increase and decrease the text size*
+- **Translate** - *Translates text into different languages*
+- **Word Completion** - *Word completion using the completion framework*
diff --git a/plugins/meson.build b/plugins/meson.build
new file mode 100644
index 0000000..50bc5d3
--- /dev/null
+++ b/plugins/meson.build
@@ -0,0 +1,31 @@
+# Keep the autotools convention for shared module suffix because GModule
+# depends on it: https://gitlab.gnome.org/GNOME/glib/issues/520
+module_suffix = []
+if host_machine.system() == 'darwin'
+ module_suffix = 'so'
+endif
+
+msgfmt_plugin_cmd = [
+ find_program('msgfmt'),
+ '--desktop',
+ '--keyword=Name',
+ '--keyword=Description',
+ '--template=@INPUT@',
+ '-d', join_paths(srcdir, 'po'),
+ '--output=@OUTPUT@'
+]
+
+subdir('docinfo')
+subdir('filebrowser')
+subdir('modelines')
+subdir('pythonconsole')
+subdir('quickhighlight')
+subdir('quickopen')
+subdir('snippets')
+subdir('sort')
+subdir('spell')
+subdir('time')
+
+if get_option('plugin_externaltools')
+ subdir('externaltools')
+endif
diff --git a/plugins/modelines/gedit-modeline-plugin.c b/plugins/modelines/gedit-modeline-plugin.c
new file mode 100644
index 0000000..376d996
--- /dev/null
+++ b/plugins/modelines/gedit-modeline-plugin.c
@@ -0,0 +1,225 @@
+/*
+ * gedit-modeline-plugin.c
+ * Emacs, Kate and Vim-style modelines support for gedit.
+ *
+ * Copyright (C) 2005-2010 - Steve Frécinaux <code@istique.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include "gedit-modeline-plugin.h"
+#include "modeline-parser.h"
+
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-view-activatable.h>
+#include <gedit/gedit-view.h>
+
+struct _GeditModelinePluginPrivate
+{
+ GeditView *view;
+
+ gulong document_loaded_handler_id;
+ gulong document_saved_handler_id;
+};
+
+enum
+{
+ PROP_0,
+ PROP_VIEW
+};
+
+static void gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditModelinePlugin,
+ gedit_modeline_plugin,
+ PEAS_TYPE_EXTENSION_BASE,
+ 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_VIEW_ACTIVATABLE,
+ gedit_view_activatable_iface_init)
+ G_ADD_PRIVATE_DYNAMIC (GeditModelinePlugin))
+
+static void
+gedit_modeline_plugin_constructed (GObject *object)
+{
+ gchar *data_dir;
+
+ data_dir = peas_extension_base_get_data_dir (PEAS_EXTENSION_BASE (object));
+
+ modeline_parser_init (data_dir);
+
+ g_free (data_dir);
+
+ G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->constructed (object);
+}
+
+static void
+gedit_modeline_plugin_init (GeditModelinePlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin initializing");
+
+ plugin->priv = gedit_modeline_plugin_get_instance_private (plugin);
+
+}
+
+static void
+gedit_modeline_plugin_dispose (GObject *object)
+{
+ GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object);
+
+ gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin disposing");
+
+ g_clear_object (&plugin->priv->view);
+
+ G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->dispose (object);
+}
+
+static void
+gedit_modeline_plugin_finalize (GObject *object)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin finalizing");
+
+ modeline_parser_shutdown ();
+
+ G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->finalize (object);
+}
+
+static void
+gedit_modeline_plugin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ plugin->priv->view = GEDIT_VIEW (g_value_dup_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_modeline_plugin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ g_value_set_object (value, plugin->priv->view);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+on_document_loaded_or_saved (GeditDocument *document,
+ GtkSourceView *view)
+{
+ modeline_parser_apply_modeline (view);
+}
+
+static void
+gedit_modeline_plugin_activate (GeditViewActivatable *activatable)
+{
+ GeditModelinePlugin *plugin;
+ GtkTextBuffer *doc;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ plugin = GEDIT_MODELINE_PLUGIN (activatable);
+
+ modeline_parser_apply_modeline (GTK_SOURCE_VIEW (plugin->priv->view));
+
+ doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view));
+
+ plugin->priv->document_loaded_handler_id =
+ g_signal_connect (doc, "loaded",
+ G_CALLBACK (on_document_loaded_or_saved),
+ plugin->priv->view);
+ plugin->priv->document_saved_handler_id =
+ g_signal_connect (doc, "saved",
+ G_CALLBACK (on_document_loaded_or_saved),
+ plugin->priv->view);
+}
+
+static void
+gedit_modeline_plugin_deactivate (GeditViewActivatable *activatable)
+{
+ GeditModelinePlugin *plugin;
+ GtkTextBuffer *doc;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ plugin = GEDIT_MODELINE_PLUGIN (activatable);
+
+ doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view));
+
+ g_signal_handler_disconnect (doc, plugin->priv->document_loaded_handler_id);
+ g_signal_handler_disconnect (doc, plugin->priv->document_saved_handler_id);
+}
+
+static void
+gedit_modeline_plugin_class_init (GeditModelinePluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = gedit_modeline_plugin_constructed;
+ object_class->dispose = gedit_modeline_plugin_dispose;
+ object_class->finalize = gedit_modeline_plugin_finalize;
+ object_class->set_property = gedit_modeline_plugin_set_property;
+ object_class->get_property = gedit_modeline_plugin_get_property;
+
+ g_object_class_override_property (object_class, PROP_VIEW, "view");
+}
+
+static void
+gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface)
+{
+ iface->activate = gedit_modeline_plugin_activate;
+ iface->deactivate = gedit_modeline_plugin_deactivate;
+}
+
+static void
+gedit_modeline_plugin_class_finalize (GeditModelinePluginClass *klass)
+{
+}
+
+
+G_MODULE_EXPORT void
+peas_register_types (PeasObjectModule *module)
+{
+ gedit_modeline_plugin_register_type (G_TYPE_MODULE (module));
+
+ peas_object_module_register_extension_type (module,
+ GEDIT_TYPE_VIEW_ACTIVATABLE,
+ GEDIT_TYPE_MODELINE_PLUGIN);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/modelines/gedit-modeline-plugin.h b/plugins/modelines/gedit-modeline-plugin.h
new file mode 100644
index 0000000..d3557e4
--- /dev/null
+++ b/plugins/modelines/gedit-modeline-plugin.h
@@ -0,0 +1,60 @@
+/*
+ * gedit-modeline-plugin.h
+ * Emacs, Kate and Vim-style modelines support for gedit.
+ *
+ * Copyright (C) 2005-2007 - Steve Frécinaux <code@istique.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_MODELINE_PLUGIN_H
+#define GEDIT_MODELINE_PLUGIN_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libpeas/peas-extension-base.h>
+#include <libpeas/peas-object-module.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_MODELINE_PLUGIN (gedit_modeline_plugin_get_type ())
+#define GEDIT_MODELINE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePlugin))
+#define GEDIT_MODELINE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePluginClass))
+#define GEDIT_IS_MODELINE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_MODELINE_PLUGIN))
+#define GEDIT_IS_MODELINE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_MODELINE_PLUGIN))
+#define GEDIT_MODELINE_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePluginClass))
+
+typedef struct _GeditModelinePlugin GeditModelinePlugin;
+typedef struct _GeditModelinePluginPrivate GeditModelinePluginPrivate;
+typedef struct _GeditModelinePluginClass GeditModelinePluginClass;
+
+struct _GeditModelinePlugin {
+ PeasExtensionBase parent;
+
+ /*< private >*/
+ GeditModelinePluginPrivate *priv;
+};
+
+struct _GeditModelinePluginClass {
+ PeasExtensionBaseClass parent_class;
+};
+
+GType gedit_modeline_plugin_get_type (void) G_GNUC_CONST;
+
+G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
+
+G_END_DECLS
+
+#endif /* GEDIT_MODELINE_PLUGIN_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/modelines/language-mappings b/plugins/modelines/language-mappings
new file mode 100644
index 0000000..47d0029
--- /dev/null
+++ b/plugins/modelines/language-mappings
@@ -0,0 +1,14 @@
+[vim]
+cs=c-sharp
+docbk=docbook
+javascript=js
+lhaskell=haskell-literate
+spec=rpmspec
+tex=latex
+xhtml=html
+
+[emacs]
+c++=cpp
+
+[kate]
+
diff --git a/plugins/modelines/meson.build b/plugins/modelines/meson.build
new file mode 100644
index 0000000..3ff0e84
--- /dev/null
+++ b/plugins/modelines/meson.build
@@ -0,0 +1,42 @@
+libmodelines_sources = files(
+ 'gedit-modeline-plugin.c',
+ 'modeline-parser.c',
+)
+
+libmodelines_deps = [
+ libgedit_dep,
+]
+
+libmodelines_sha = shared_module(
+ 'modelines',
+ sources: libmodelines_sources,
+ include_directories: root_include_dir,
+ dependencies: libmodelines_deps,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ ),
+ name_suffix: module_suffix,
+)
+
+custom_target(
+ 'modelines.plugin',
+ input: 'modelines.plugin.desktop.in',
+ output: 'modelines.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
+
+install_data(
+ 'language-mappings',
+ install_dir: join_paths(
+ pkgdatadir,
+ 'plugins',
+ 'modelines',
+ )
+)
diff --git a/plugins/modelines/modeline-parser.c b/plugins/modelines/modeline-parser.c
new file mode 100644
index 0000000..989e111
--- /dev/null
+++ b/plugins/modelines/modeline-parser.c
@@ -0,0 +1,899 @@
+/*
+ * modeline-parser.c
+ * Emacs, Kate and Vim-style modelines support for gedit.
+ *
+ * Copyright (C) 2005-2007 - Steve Frécinaux <code@istique.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <gtksourceview/gtksource.h>
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-document.h>
+#include <gedit/gedit-settings.h>
+#include <gedit/gedit-utils.h>
+#include "modeline-parser.h"
+
+#define MODELINES_LANGUAGE_MAPPINGS_FILE "language-mappings"
+
+/* base dir to lookup configuration files */
+static gchar *modelines_data_dir;
+
+/* Mappings: language name -> Gedit language ID */
+static GHashTable *vim_languages = NULL;
+static GHashTable *emacs_languages = NULL;
+static GHashTable *kate_languages = NULL;
+
+typedef enum
+{
+ MODELINE_SET_NONE = 0,
+ MODELINE_SET_TAB_WIDTH = 1 << 0,
+ MODELINE_SET_INDENT_WIDTH = 1 << 1,
+ MODELINE_SET_WRAP_MODE = 1 << 2,
+ MODELINE_SET_SHOW_RIGHT_MARGIN = 1 << 3,
+ MODELINE_SET_RIGHT_MARGIN_POSITION = 1 << 4,
+ MODELINE_SET_LANGUAGE = 1 << 5,
+ MODELINE_SET_INSERT_SPACES = 1 << 6
+} ModelineSet;
+
+typedef struct _ModelineOptions
+{
+ gchar *language_id;
+
+ /* these options are similar to the GtkSourceView properties of the
+ * same names.
+ */
+ gboolean insert_spaces;
+ guint tab_width;
+ guint indent_width;
+ GtkWrapMode wrap_mode;
+ gboolean display_right_margin;
+ guint right_margin_position;
+
+ ModelineSet set;
+} ModelineOptions;
+
+#define MODELINE_OPTIONS_DATA_KEY "ModelineOptionsDataKey"
+
+static gboolean
+has_option (ModelineOptions *options,
+ ModelineSet set)
+{
+ return options->set & set;
+}
+
+void
+modeline_parser_init (const gchar *data_dir)
+{
+ if (modelines_data_dir == NULL)
+ {
+ modelines_data_dir = g_strdup (data_dir);
+ }
+}
+
+void
+modeline_parser_shutdown ()
+{
+ if (vim_languages != NULL)
+ g_hash_table_unref (vim_languages);
+
+ if (emacs_languages != NULL)
+ g_hash_table_unref (emacs_languages);
+
+ if (kate_languages != NULL)
+ g_hash_table_unref (kate_languages);
+
+ vim_languages = NULL;
+ emacs_languages = NULL;
+ kate_languages = NULL;
+
+ g_free (modelines_data_dir);
+ modelines_data_dir = NULL;
+}
+
+static GHashTable *
+load_language_mappings_group (GKeyFile *key_file, const gchar *group)
+{
+ GHashTable *table;
+ gchar **keys;
+ gsize length = 0;
+ int i;
+
+ table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ keys = g_key_file_get_keys (key_file, group, &length, NULL);
+
+ gedit_debug_message (DEBUG_PLUGINS,
+ "%" G_GSIZE_FORMAT " mappings in group %s",
+ length, group);
+
+ for (i = 0; i < length; i++)
+ {
+ /* steal the name string */
+ gchar *name = keys[i];
+ gchar *id = g_key_file_get_string (key_file, group, name, NULL);
+ g_hash_table_insert (table, name, id);
+ }
+ g_free (keys);
+
+ return table;
+}
+
+/* lazy loading of language mappings */
+static void
+load_language_mappings (void)
+{
+ gchar *fname;
+ GKeyFile *mappings;
+ GError *error = NULL;
+
+ fname = g_build_filename (modelines_data_dir,
+ MODELINES_LANGUAGE_MAPPINGS_FILE,
+ NULL);
+
+ mappings = g_key_file_new ();
+
+ if (g_key_file_load_from_file (mappings, fname, 0, &error))
+ {
+ gedit_debug_message (DEBUG_PLUGINS,
+ "Loaded language mappings from %s",
+ fname);
+
+ vim_languages = load_language_mappings_group (mappings, "vim");
+ emacs_languages = load_language_mappings_group (mappings, "emacs");
+ kate_languages = load_language_mappings_group (mappings, "kate");
+ }
+ else
+ {
+ gedit_debug_message (DEBUG_PLUGINS,
+ "Failed to loaded language mappings from %s: %s",
+ fname, error->message);
+
+ g_error_free (error);
+ }
+
+ g_key_file_free (mappings);
+ g_free (fname);
+}
+
+static gchar *
+get_language_id (const gchar *language_name, GHashTable *mapping)
+{
+ gchar *name;
+ gchar *language_id = NULL;
+
+ name = g_ascii_strdown (language_name, -1);
+
+ if (mapping != NULL)
+ {
+ language_id = g_hash_table_lookup (mapping, name);
+
+ if (language_id != NULL)
+ {
+ g_free (name);
+ return g_strdup (language_id);
+ }
+ }
+
+ /* by default assume that the gtksourcevuew id is the same */
+ return name;
+}
+
+static gchar *
+get_language_id_vim (const gchar *language_name)
+{
+ if (vim_languages == NULL)
+ load_language_mappings ();
+
+ return get_language_id (language_name, vim_languages);
+}
+
+static gchar *
+get_language_id_emacs (const gchar *language_name)
+{
+ if (emacs_languages == NULL)
+ load_language_mappings ();
+
+ return get_language_id (language_name, emacs_languages);
+}
+
+static gchar *
+get_language_id_kate (const gchar *language_name)
+{
+ if (kate_languages == NULL)
+ load_language_mappings ();
+
+ return get_language_id (language_name, kate_languages);
+}
+
+static gboolean
+skip_whitespaces (gchar **s)
+{
+ while (**s != '\0' && g_ascii_isspace (**s))
+ (*s)++;
+ return **s != '\0';
+}
+
+/* Parse vi(m) modelines.
+ * Vi(m) modelines looks like this:
+ * - first form: [text]{white}{vi:|vim:|ex:}[white]{options}
+ * - second form: [text]{white}{vi:|vim:|ex:}[white]se[t] {options}:[text]
+ * They can happen on the three first or last lines.
+ */
+static gchar *
+parse_vim_modeline (gchar *s,
+ ModelineOptions *options)
+{
+ gboolean in_set = FALSE;
+ gboolean neg;
+ guint intval;
+ GString *key, *value;
+
+ key = g_string_sized_new (8);
+ value = g_string_sized_new (8);
+
+ while (*s != '\0' && !(in_set && *s == ':'))
+ {
+ while (*s != '\0' && (*s == ':' || g_ascii_isspace (*s)))
+ s++;
+
+ if (*s == '\0')
+ break;
+
+ if (strncmp (s, "set ", 4) == 0 ||
+ strncmp (s, "se ", 3) == 0)
+ {
+ s = strchr(s, ' ') + 1;
+ in_set = TRUE;
+ }
+
+ neg = FALSE;
+ if (strncmp (s, "no", 2) == 0)
+ {
+ neg = TRUE;
+ s += 2;
+ }
+
+ g_string_assign (key, "");
+ g_string_assign (value, "");
+
+ while (*s != '\0' && *s != ':' && *s != '=' &&
+ !g_ascii_isspace (*s))
+ {
+ g_string_append_c (key, *s);
+ s++;
+ }
+
+ if (*s == '=')
+ {
+ s++;
+ while (*s != '\0' && *s != ':' &&
+ !g_ascii_isspace (*s))
+ {
+ g_string_append_c (value, *s);
+ s++;
+ }
+ }
+
+ if (strcmp (key->str, "ft") == 0 ||
+ strcmp (key->str, "filetype") == 0)
+ {
+ g_free (options->language_id);
+ options->language_id = get_language_id_vim (value->str);
+
+ options->set |= MODELINE_SET_LANGUAGE;
+ }
+ else if (strcmp (key->str, "et") == 0 ||
+ strcmp (key->str, "expandtab") == 0)
+ {
+ options->insert_spaces = !neg;
+ options->set |= MODELINE_SET_INSERT_SPACES;
+ }
+ else if (strcmp (key->str, "ts") == 0 ||
+ strcmp (key->str, "tabstop") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->tab_width = intval;
+ options->set |= MODELINE_SET_TAB_WIDTH;
+ }
+ }
+ else if (strcmp (key->str, "sw") == 0 ||
+ strcmp (key->str, "shiftwidth") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->indent_width = intval;
+ options->set |= MODELINE_SET_INDENT_WIDTH;
+ }
+ }
+ else if (strcmp (key->str, "wrap") == 0)
+ {
+ options->wrap_mode = neg ? GTK_WRAP_NONE : GTK_WRAP_WORD;
+
+ options->set |= MODELINE_SET_WRAP_MODE;
+ }
+ else if (strcmp (key->str, "textwidth") == 0 ||
+ strcmp (key->str, "tw") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->right_margin_position = intval;
+ options->display_right_margin = TRUE;
+
+ options->set |= MODELINE_SET_SHOW_RIGHT_MARGIN |
+ MODELINE_SET_RIGHT_MARGIN_POSITION;
+
+ }
+ }
+ }
+
+ g_string_free (key, TRUE);
+ g_string_free (value, TRUE);
+
+ return s;
+}
+
+/* Parse emacs modelines.
+ * Emacs modelines looks like this: "-*- key1: value1; key2: value2 -*-"
+ * They can happen on the first line, or on the second one if the first line is
+ * a shebang (#!)
+ * See http://www.delorie.com/gnu/docs/emacs/emacs_486.html
+ */
+static gchar *
+parse_emacs_modeline (gchar *s,
+ ModelineOptions *options)
+{
+ guint intval;
+ GString *key, *value;
+
+ key = g_string_sized_new (8);
+ value = g_string_sized_new (8);
+
+ while (*s != '\0')
+ {
+ while (*s != '\0' && (*s == ';' || g_ascii_isspace (*s)))
+ s++;
+ if (*s == '\0' || strncmp (s, "-*-", 3) == 0)
+ break;
+
+ g_string_assign (key, "");
+ g_string_assign (value, "");
+
+ while (*s != '\0' && *s != ':' && *s != ';' &&
+ !g_ascii_isspace (*s))
+ {
+ g_string_append_c (key, *s);
+ s++;
+ }
+
+ if (!skip_whitespaces (&s))
+ break;
+
+ if (*s != ':')
+ continue;
+ s++;
+
+ if (!skip_whitespaces (&s))
+ break;
+
+ while (*s != '\0' && *s != ';' && !g_ascii_isspace (*s))
+ {
+ g_string_append_c (value, *s);
+ s++;
+ }
+
+ gedit_debug_message (DEBUG_PLUGINS,
+ "Emacs modeline bit: %s = %s",
+ key->str, value->str);
+
+ /* "Mode" key is case insenstive */
+ if (g_ascii_strcasecmp (key->str, "Mode") == 0)
+ {
+ g_free (options->language_id);
+ options->language_id = get_language_id_emacs (value->str);
+
+ options->set |= MODELINE_SET_LANGUAGE;
+ }
+ else if (strcmp (key->str, "tab-width") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->tab_width = intval;
+ options->set |= MODELINE_SET_TAB_WIDTH;
+ }
+ }
+ else if (strcmp (key->str, "indent-offset") == 0 ||
+ strcmp (key->str, "c-basic-offset") == 0 ||
+ strcmp (key->str, "js-indent-level") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->indent_width = intval;
+ options->set |= MODELINE_SET_INDENT_WIDTH;
+ }
+ }
+ else if (strcmp (key->str, "indent-tabs-mode") == 0)
+ {
+ intval = strcmp (value->str, "nil") == 0;
+ options->insert_spaces = intval;
+
+ options->set |= MODELINE_SET_INSERT_SPACES;
+ }
+ else if (strcmp (key->str, "autowrap") == 0)
+ {
+ intval = strcmp (value->str, "nil") != 0;
+ options->wrap_mode = intval ? GTK_WRAP_WORD : GTK_WRAP_NONE;
+
+ options->set |= MODELINE_SET_WRAP_MODE;
+ }
+ }
+
+ g_string_free (key, TRUE);
+ g_string_free (value, TRUE);
+
+ return *s == '\0' ? s : s + 2;
+}
+
+/*
+ * Parse kate modelines.
+ * Kate modelines are of the form "kate: key1 value1; key2 value2;"
+ * These can happen on the 10 first or 10 last lines of the buffer.
+ * See http://wiki.kate-editor.org/index.php/Modelines
+ */
+static gchar *
+parse_kate_modeline (gchar *s,
+ ModelineOptions *options)
+{
+ guint intval;
+ GString *key, *value;
+
+ key = g_string_sized_new (8);
+ value = g_string_sized_new (8);
+
+ while (*s != '\0')
+ {
+ while (*s != '\0' && (*s == ';' || g_ascii_isspace (*s)))
+ s++;
+ if (*s == '\0')
+ break;
+
+ g_string_assign (key, "");
+ g_string_assign (value, "");
+
+ while (*s != '\0' && *s != ';' && !g_ascii_isspace (*s))
+ {
+ g_string_append_c (key, *s);
+ s++;
+ }
+
+ if (!skip_whitespaces (&s))
+ break;
+ if (*s == ';')
+ continue;
+
+ while (*s != '\0' && *s != ';' &&
+ !g_ascii_isspace (*s))
+ {
+ g_string_append_c (value, *s);
+ s++;
+ }
+
+ gedit_debug_message (DEBUG_PLUGINS,
+ "Kate modeline bit: %s = %s",
+ key->str, value->str);
+
+ if (strcmp (key->str, "hl") == 0 ||
+ strcmp (key->str, "syntax") == 0)
+ {
+ g_free (options->language_id);
+ options->language_id = get_language_id_kate (value->str);
+
+ options->set |= MODELINE_SET_LANGUAGE;
+ }
+ else if (strcmp (key->str, "tab-width") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->tab_width = intval;
+ options->set |= MODELINE_SET_TAB_WIDTH;
+ }
+ }
+ else if (strcmp (key->str, "indent-width") == 0)
+ {
+ intval = atoi (value->str);
+ if (intval) options->indent_width = intval;
+ }
+ else if (strcmp (key->str, "space-indent") == 0)
+ {
+ intval = strcmp (value->str, "on") == 0 ||
+ strcmp (value->str, "true") == 0 ||
+ strcmp (value->str, "1") == 0;
+
+ options->insert_spaces = intval;
+ options->set |= MODELINE_SET_INSERT_SPACES;
+ }
+ else if (strcmp (key->str, "word-wrap") == 0)
+ {
+ intval = strcmp (value->str, "on") == 0 ||
+ strcmp (value->str, "true") == 0 ||
+ strcmp (value->str, "1") == 0;
+
+ options->wrap_mode = intval ? GTK_WRAP_WORD : GTK_WRAP_NONE;
+
+ options->set |= MODELINE_SET_WRAP_MODE;
+ }
+ else if (strcmp (key->str, "word-wrap-column") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->right_margin_position = intval;
+ options->display_right_margin = TRUE;
+
+ options->set |= MODELINE_SET_RIGHT_MARGIN_POSITION |
+ MODELINE_SET_SHOW_RIGHT_MARGIN;
+ }
+ }
+ }
+
+ g_string_free (key, TRUE);
+ g_string_free (value, TRUE);
+
+ return s;
+}
+
+/* Scan a line for vi(m)/emacs/kate modelines.
+ * Line numbers are counted starting at one.
+ */
+static void
+parse_modeline (gchar *line,
+ gint line_number,
+ gint line_count,
+ ModelineOptions *options)
+{
+ gchar *s = line;
+
+ /* look for the beginning of a modeline */
+ while (s != NULL && *s != '\0')
+ {
+ if (s > line && !g_ascii_isspace (*(s - 1)))
+ {
+ s++;
+ continue;
+ }
+
+ if ((line_number <= 3 || line_number > line_count - 3) &&
+ (strncmp (s, "ex:", 3) == 0 ||
+ strncmp (s, "vi:", 3) == 0 ||
+ strncmp (s, "vim:", 4) == 0))
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "Vim modeline on line %d", line_number);
+
+ while (*s != ':')
+ {
+ s++;
+ }
+
+ s = parse_vim_modeline (s + 1, options);
+ }
+ else if (line_number <= 2 && strncmp (s, "-*-", 3) == 0)
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "Emacs modeline on line %d", line_number);
+
+ s = parse_emacs_modeline (s + 3, options);
+ }
+ else if ((line_number <= 10 || line_number > line_count - 10) &&
+ strncmp (s, "kate:", 5) == 0)
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "Kate modeline on line %d", line_number);
+
+ s = parse_kate_modeline (s + 5, options);
+ }
+ else
+ {
+ s++;
+ }
+ }
+}
+
+static gboolean
+check_previous (GtkSourceView *view,
+ ModelineOptions *previous,
+ ModelineSet set)
+{
+ GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+
+ /* Do not restore default when this is the first time */
+ if (!previous)
+ return FALSE;
+
+ /* Do not restore default when previous was not set */
+ if (!(previous->set & set))
+ return FALSE;
+
+ /* Only restore default when setting has not changed */
+ switch (set)
+ {
+ case MODELINE_SET_INSERT_SPACES:
+ return gtk_source_view_get_insert_spaces_instead_of_tabs (view) ==
+ previous->insert_spaces;
+ break;
+ case MODELINE_SET_TAB_WIDTH:
+ return gtk_source_view_get_tab_width (view) == previous->tab_width;
+ break;
+ case MODELINE_SET_INDENT_WIDTH:
+ return gtk_source_view_get_indent_width (view) == previous->indent_width;
+ break;
+ case MODELINE_SET_WRAP_MODE:
+ return gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view)) ==
+ previous->wrap_mode;
+ break;
+ case MODELINE_SET_RIGHT_MARGIN_POSITION:
+ return gtk_source_view_get_right_margin_position (view) ==
+ previous->right_margin_position;
+ break;
+ case MODELINE_SET_SHOW_RIGHT_MARGIN:
+ return gtk_source_view_get_show_right_margin (view) ==
+ previous->display_right_margin;
+ break;
+ case MODELINE_SET_LANGUAGE:
+ {
+ GtkSourceLanguage *language = gtk_source_buffer_get_language (buffer);
+
+ return (language == NULL && previous->language_id == NULL) ||
+ (language != NULL && g_strcmp0 (gtk_source_language_get_id (language),
+ previous->language_id) == 0);
+ }
+ break;
+ default:
+ return FALSE;
+ break;
+ }
+}
+
+static void
+free_modeline_options (ModelineOptions *options)
+{
+ g_free (options->language_id);
+ g_slice_free (ModelineOptions, options);
+}
+
+void
+modeline_parser_apply_modeline (GtkSourceView *view)
+{
+ ModelineOptions options;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter, liter;
+ gint line_count;
+ GSettings *settings;
+
+ options.language_id = NULL;
+ options.set = MODELINE_SET_NONE;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ gtk_text_buffer_get_start_iter (buffer, &iter);
+
+ line_count = gtk_text_buffer_get_line_count (buffer);
+
+ /* Parse the modelines on the 10 first lines... */
+ while ((gtk_text_iter_get_line (&iter) < 10) &&
+ !gtk_text_iter_is_end (&iter))
+ {
+ gchar *line;
+
+ liter = iter;
+ gtk_text_iter_forward_to_line_end (&iter);
+ line = gtk_text_buffer_get_text (buffer, &liter, &iter, TRUE);
+
+ parse_modeline (line,
+ 1 + gtk_text_iter_get_line (&iter),
+ line_count,
+ &options);
+
+ gtk_text_iter_forward_line (&iter);
+
+ g_free (line);
+ }
+
+ /* ...and on the 10 last ones (modelines are not allowed in between) */
+ if (!gtk_text_iter_is_end (&iter))
+ {
+ gint cur_line;
+ guint remaining_lines;
+
+ /* we are on the 11th line (count from 0) */
+ cur_line = gtk_text_iter_get_line (&iter);
+ /* g_assert (10 == cur_line); */
+
+ remaining_lines = line_count - cur_line - 1;
+
+ if (remaining_lines > 10)
+ {
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ gtk_text_iter_backward_lines (&iter, 9);
+ }
+ }
+
+ while (!gtk_text_iter_is_end (&iter))
+ {
+ gchar *line;
+
+ liter = iter;
+ gtk_text_iter_forward_to_line_end (&iter);
+ line = gtk_text_buffer_get_text (buffer, &liter, &iter, TRUE);
+
+ parse_modeline (line,
+ 1 + gtk_text_iter_get_line (&iter),
+ line_count,
+ &options);
+
+ gtk_text_iter_forward_line (&iter);
+
+ g_free (line);
+ }
+
+ /* Try to set language */
+ if (has_option (&options, MODELINE_SET_LANGUAGE) && options.language_id)
+ {
+ if (g_ascii_strcasecmp (options.language_id, "text") == 0)
+ {
+ gedit_document_set_language (GEDIT_DOCUMENT (buffer),
+ NULL);
+ }
+ else
+ {
+ GtkSourceLanguageManager *manager;
+ GtkSourceLanguage *language;
+
+ manager = gtk_source_language_manager_get_default ();
+
+ language = gtk_source_language_manager_get_language
+ (manager, options.language_id);
+ if (language != NULL)
+ {
+ gedit_document_set_language (GEDIT_DOCUMENT (buffer),
+ language);
+ }
+ else
+ {
+ gedit_debug_message (DEBUG_PLUGINS,
+ "Unknown language `%s'",
+ options.language_id);
+ }
+ }
+ }
+
+ ModelineOptions *previous = g_object_get_data (G_OBJECT (buffer),
+ MODELINE_OPTIONS_DATA_KEY);
+
+ settings = g_settings_new ("org.gnome.gedit.preferences.editor");
+
+ /* Apply the options we got from modelines and restore defaults if
+ we set them before */
+ if (has_option (&options, MODELINE_SET_INSERT_SPACES))
+ {
+ gtk_source_view_set_insert_spaces_instead_of_tabs
+ (view, options.insert_spaces);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_INSERT_SPACES))
+ {
+ gboolean insert_spaces;
+
+ insert_spaces = g_settings_get_boolean (settings, GEDIT_SETTINGS_INSERT_SPACES);
+
+ gtk_source_view_set_insert_spaces_instead_of_tabs (view, insert_spaces);
+ }
+
+ if (has_option (&options, MODELINE_SET_TAB_WIDTH))
+ {
+ gtk_source_view_set_tab_width (view, options.tab_width);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_TAB_WIDTH))
+ {
+ guint tab_width;
+
+ g_settings_get (settings, GEDIT_SETTINGS_TABS_SIZE, "u", &tab_width);
+
+ gtk_source_view_set_tab_width (view, tab_width);
+ }
+
+ if (has_option (&options, MODELINE_SET_INDENT_WIDTH))
+ {
+ gtk_source_view_set_indent_width (view, options.indent_width);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_INDENT_WIDTH))
+ {
+ gtk_source_view_set_indent_width (view, -1);
+ }
+
+ if (has_option (&options, MODELINE_SET_WRAP_MODE))
+ {
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), options.wrap_mode);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_WRAP_MODE))
+ {
+ GtkWrapMode mode;
+
+ mode = g_settings_get_enum (settings,
+ GEDIT_SETTINGS_WRAP_MODE);
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), mode);
+ }
+
+ if (has_option (&options, MODELINE_SET_RIGHT_MARGIN_POSITION))
+ {
+ gtk_source_view_set_right_margin_position (view, options.right_margin_position);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_RIGHT_MARGIN_POSITION))
+ {
+ guint right_margin_pos;
+
+ g_settings_get (settings, GEDIT_SETTINGS_RIGHT_MARGIN_POSITION, "u",
+ &right_margin_pos);
+ gtk_source_view_set_right_margin_position (view,
+ right_margin_pos);
+ }
+
+ if (has_option (&options, MODELINE_SET_SHOW_RIGHT_MARGIN))
+ {
+ gtk_source_view_set_show_right_margin (view, options.display_right_margin);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_SHOW_RIGHT_MARGIN))
+ {
+ gboolean display_right_margin;
+
+ display_right_margin = g_settings_get_boolean (settings,
+ GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN);
+ gtk_source_view_set_show_right_margin (view, display_right_margin);
+ }
+
+ if (previous)
+ {
+ g_free (previous->language_id);
+ *previous = options;
+ previous->language_id = g_strdup (options.language_id);
+ }
+ else
+ {
+ previous = g_slice_new (ModelineOptions);
+ *previous = options;
+ previous->language_id = g_strdup (options.language_id);
+
+ g_object_set_data_full (G_OBJECT (buffer),
+ MODELINE_OPTIONS_DATA_KEY,
+ previous,
+ (GDestroyNotify)free_modeline_options);
+ }
+
+ g_object_unref (settings);
+ g_free (options.language_id);
+}
+
+/* vi:ts=8 */
diff --git a/plugins/modelines/modeline-parser.h b/plugins/modelines/modeline-parser.h
new file mode 100644
index 0000000..0fbf9a1
--- /dev/null
+++ b/plugins/modelines/modeline-parser.h
@@ -0,0 +1,36 @@
+/*
+ * modelie-parser.h
+ * Emacs, Kate and Vim-style modelines support for gedit.
+ *
+ * Copyright (C) 2005-2007 - Steve Frécinaux <code@istique.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MODELINE_PARSER_H
+#define MODELINE_PARSER_H
+
+#include <glib.h>
+#include <gtksourceview/gtksource.h>
+
+G_BEGIN_DECLS
+
+void modeline_parser_init (const gchar *data_dir);
+void modeline_parser_shutdown (void);
+void modeline_parser_apply_modeline (GtkSourceView *view);
+
+G_END_DECLS
+
+#endif /* MODELINE_PARSER_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/modelines/modelines.plugin.desktop.in b/plugins/modelines/modelines.plugin.desktop.in
new file mode 100644
index 0000000..d334c55
--- /dev/null
+++ b/plugins/modelines/modelines.plugin.desktop.in
@@ -0,0 +1,8 @@
+[Plugin]
+Module=modelines
+IAge=3
+Name=Modelines
+Description=Emacs, Kate and Vim-style modelines support for gedit.
+Authors=Steve Frécinaux <steve@istique.net>
+Copyright=Copyright © 2005 Steve Frécinaux
+Website=http://www.gedit.org
diff --git a/plugins/pythonconsole/meson.build b/plugins/pythonconsole/meson.build
new file mode 100644
index 0000000..1aecada
--- /dev/null
+++ b/plugins/pythonconsole/meson.build
@@ -0,0 +1,31 @@
+subdir('pythonconsole')
+
+pythonconsole_gschema_file = files('org.gnome.gedit.plugins.pythonconsole.gschema.xml')
+install_data(
+ pythonconsole_gschema_file,
+ install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas')
+)
+
+if xmllint.found()
+ test(
+ 'validate-pythonconsole-gschema',
+ xmllint,
+ args: [
+ '--noout',
+ '--dtdvalid', gschema_dtd,
+ pythonconsole_gschema_file,
+ ]
+ )
+endif
+
+custom_target(
+ 'pythonconsole.plugin',
+ input: 'pythonconsole.plugin.desktop.in',
+ output: 'pythonconsole.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
diff --git a/plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml b/plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml
new file mode 100644
index 0000000..0a29031
--- /dev/null
+++ b/plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml
@@ -0,0 +1,30 @@
+<schemalist gettext-domain="gedit">
+ <schema id="org.gnome.gedit.plugins.pythonconsole" path="/org/gnome/gedit/plugins/pythonconsole/">
+ <key name="command-color" type="s">
+ <default>'#314e6c'</default>
+ <summary>Command Color Text</summary>
+ <description>The command color text</description>
+ </key>
+ <key name="error-color" type="s">
+ <default>'#990000'</default>
+ <summary>Error Color Text</summary>
+ <description>The error color text</description>
+ </key>
+ <key name="use-system-font" type="b">
+ <default>true</default>
+ <summary>Whether to use the system font</summary>
+ <description>
+ If true, the terminal will use the desktop-global standard
+ font if it’s monospace (and the most similar font it can
+ come up with otherwise).
+ </description>
+ </key>
+ <key name="font" type="s">
+ <default>'Monospace 10'</default>
+ <summary>Font</summary>
+ <description>
+ A Pango font name. Examples are “Sans 12” or “Monospace Bold 14”.
+ </description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/plugins/pythonconsole/pythonconsole.plugin.desktop.in b/plugins/pythonconsole/pythonconsole.plugin.desktop.in
new file mode 100644
index 0000000..2b4456c
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole.plugin.desktop.in
@@ -0,0 +1,12 @@
+[Plugin]
+Loader=python3
+Module=pythonconsole
+IAge=3
+Name=Python Console
+Description=Interactive Python console standing in the bottom panel.
+# TRANSLATORS: Do NOT translate or transliterate this text!
+# This is an icon file name.
+Icon=text-x-script
+Authors=Steve Frécinaux <steve@istique.net>
+Copyright=Copyright © 2006 Steve Frécinaux
+Website=http://www.gedit.org
diff --git a/plugins/pythonconsole/pythonconsole/__init__.py b/plugins/pythonconsole/pythonconsole/__init__.py
new file mode 100644
index 0000000..6405596
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole/__init__.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+# __init__.py -- plugin object
+#
+# Copyright (C) 2006 - Steve Frécinaux
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py)
+# Copyright (C), 1998 James Henstridge <james@daa.com.au>
+# Copyright (C), 2005 Adam Hooper <adamh@densi.com>
+# Bits from gedit Python Console Plugin
+# Copyrignt (C), 2005 Raphaël Slinckx
+
+import gi
+gi.require_version('Gedit', '3.0')
+gi.require_version('Peas', '1.0')
+gi.require_version('PeasGtk', '1.0')
+gi.require_version('Gtk', '3.0')
+
+from gi.repository import GObject, Gtk, Gedit, Peas, PeasGtk
+from .console import PythonConsole
+from .config import PythonConsoleConfigWidget
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class PythonConsolePlugin(GObject.Object, Gedit.WindowActivatable, PeasGtk.Configurable):
+ __gtype_name__ = "PythonConsolePlugin"
+
+ window = GObject.Property(type=Gedit.Window)
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+
+ def do_activate(self):
+ self._console = PythonConsole(namespace = {'__builtins__' : __builtins__,
+ 'gedit' : Gedit,
+ 'window' : self.window})
+ self._console.eval('print("You can access the main window through ' \
+ '\'window\' :\\n%s" % window)', False)
+ bottom = self.window.get_bottom_panel()
+ self._console.show_all()
+ bottom.add_titled(self._console, "GeditPythonConsolePanel", _('Python Console'))
+
+ def do_deactivate(self):
+ self._console.stop()
+ bottom = self.window.get_bottom_panel()
+ bottom.remove(self._console)
+
+ def do_update_state(self):
+ pass
+
+ def do_create_configure_widget(self):
+ config_widget = PythonConsoleConfigWidget(self.plugin_info.get_data_dir())
+
+ return config_widget.configure_widget()
+
+# ex:et:ts=4:
diff --git a/plugins/pythonconsole/pythonconsole/config.py b/plugins/pythonconsole/pythonconsole/config.py
new file mode 100644
index 0000000..8c609af
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole/config.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+# config.py -- Config dialog
+#
+# Copyright (C) 2008 - B. Clausius
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py)
+# Copyright (C), 1998 James Henstridge <james@daa.com.au>
+# Copyright (C), 2005 Adam Hooper <adamh@densi.com>
+# Bits from gedit Python Console Plugin
+# Copyrignt (C), 2005 Raphaël Slinckx
+
+import os
+from gi.repository import Gio, Gtk, Gdk
+
+__all__ = ('PythonConsoleConfigWidget')
+
+class PythonConsoleConfigWidget(object):
+
+ CONSOLE_KEY_BASE = 'org.gnome.gedit.plugins.pythonconsole'
+ CONSOLE_KEY_COMMAND_COLOR = 'command-color'
+ CONSOLE_KEY_ERROR_COLOR = 'error-color'
+
+ def __init__(self, datadir):
+ object.__init__(self)
+
+ self._ui_path = os.path.join(datadir, 'ui', 'config.ui')
+ self._settings = Gio.Settings.new(self.CONSOLE_KEY_BASE)
+ self._ui = Gtk.Builder()
+
+ def configure_widget(self):
+ self._ui.add_from_file(self._ui_path)
+
+ self.set_colorbutton_color(self._ui.get_object('colorbutton-command'),
+ self._settings.get_string(self.CONSOLE_KEY_COMMAND_COLOR))
+ self.set_colorbutton_color(self._ui.get_object('colorbutton-error'),
+ self._settings.get_string(self.CONSOLE_KEY_ERROR_COLOR))
+
+ self._ui.connect_signals(self)
+
+ widget = self._ui.get_object('grid')
+
+ return widget
+
+ @staticmethod
+ def set_colorbutton_color(colorbutton, value):
+ rgba = Gdk.RGBA()
+ parsed = rgba.parse(value)
+
+ if parsed:
+ colorbutton.set_rgba(rgba)
+
+ def on_colorbutton_command_color_set(self, colorbutton):
+ self._settings.set_string(self.CONSOLE_KEY_COMMAND_COLOR,
+ colorbutton.get_color().to_string())
+
+ def on_colorbutton_error_color_set(self, colorbutton):
+ self._settings.set_string(self.CONSOLE_KEY_ERROR_COLOR,
+ colorbutton.get_color().to_string())
+
+# ex:et:ts=4:
diff --git a/plugins/pythonconsole/pythonconsole/config.ui b/plugins/pythonconsole/pythonconsole/config.ui
new file mode 100644
index 0000000..573be34
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole/config.ui
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkGrid" id="grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="margin_end">12</property>
+ <property name="margin_top">12</property>
+ <property name="margin_bottom">12</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label-command">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">C_ommand color:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">colorbutton-command</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label-error">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Error color:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">colorbutton-error</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="colorbutton-command">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="hexpand">True</property>
+ <property name="rgba">#31314e4e6c6c</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="colorbutton-error">
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="hexpand">True</property>
+ <property name="rgba">#999900000000</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/plugins/pythonconsole/pythonconsole/console.py b/plugins/pythonconsole/pythonconsole/console.py
new file mode 100644
index 0000000..b81e8a7
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole/console.py
@@ -0,0 +1,415 @@
+# -*- coding: utf-8 -*-
+
+# pythonconsole.py -- Console widget
+#
+# Copyright (C) 2006 - Steve Frécinaux
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py)
+# Copyright (C), 1998 James Henstridge <james@daa.com.au>
+# Copyright (C), 2005 Adam Hooper <adamh@densi.com>
+# Bits from gedit Python Console Plugin
+# Copyrignt (C), 2005 Raphaël Slinckx
+
+import string
+import sys
+import re
+import traceback
+
+from gi.repository import GLib, Gio, Gtk, Gdk, Pango
+
+__all__ = ('PythonConsole', 'OutFile')
+
+class PythonConsole(Gtk.ScrolledWindow):
+
+ __gsignals__ = {
+ 'grab-focus' : 'override',
+ }
+
+ DEFAULT_FONT = "Monospace 10"
+
+ CONSOLE_KEY_BASE = 'org.gnome.gedit.plugins.pythonconsole'
+ SETTINGS_INTERFACE_DIR = "org.gnome.desktop.interface"
+ SETTINGS_PROFILE_DIR = "org.gnome.GnomeTerminal.profiles.Default"
+
+ CONSOLE_KEY_COMMAND_COLOR = 'command-color'
+ CONSOLE_KEY_ERROR_COLOR = 'error-color'
+
+ def __init__(self, namespace = {}):
+ Gtk.ScrolledWindow.__init__(self)
+
+ self._settings = Gio.Settings.new(self.CONSOLE_KEY_BASE)
+ self._settings.connect("changed", self.on_color_settings_changed)
+
+ self._interface_settings = Gio.Settings.new(self.SETTINGS_INTERFACE_DIR)
+ self._interface_settings.connect("changed", self.on_settings_changed)
+
+ self._profile_settings = self.get_profile_settings()
+ self._profile_settings.connect("changed", self.on_settings_changed)
+
+ self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
+ self.set_shadow_type(Gtk.ShadowType.NONE)
+ self.view = Gtk.TextView()
+ self.reconfigure()
+ self.view.set_editable(True)
+ self.view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
+ self.add(self.view)
+ self.view.show()
+
+ buf = self.view.get_buffer()
+ self.normal = buf.create_tag("normal")
+ self.error = buf.create_tag("error")
+ self.command = buf.create_tag("command")
+
+ # Load the default settings
+ self.on_color_settings_changed(self._settings, None)
+
+ self.__spaces_pattern = re.compile(r'^\s+')
+ self.namespace = namespace
+
+ self.block_command = False
+
+ # Init first line
+ buf.create_mark("input-line", buf.get_end_iter(), True)
+ buf.insert(buf.get_end_iter(), ">>> ")
+ buf.create_mark("input", buf.get_end_iter(), True)
+
+ # Init history
+ self.history = ['']
+ self.history_pos = 0
+ self.current_command = ''
+ self.namespace['__history__'] = self.history
+
+ # Set up hooks for standard output.
+ self.stdout = OutFile(self, sys.stdout.fileno(), self.normal)
+ self.stderr = OutFile(self, sys.stderr.fileno(), self.error)
+
+ # Signals
+ self.view.connect("key-press-event", self.__key_press_event_cb)
+ buf.connect("mark-set", self.__mark_set_cb)
+
+ def get_profile_settings(self):
+ #FIXME return either the gnome-terminal settings or the gedit one
+ return Gio.Settings.new(self.CONSOLE_KEY_BASE)
+
+ def do_grab_focus(self):
+ self.view.grab_focus()
+
+ def reconfigure(self):
+ # Font
+ font_desc = None
+ system_font = self._interface_settings.get_string("monospace-font-name")
+
+ if self._profile_settings.get_boolean("use-system-font"):
+ font_name = system_font
+ else:
+ font_name = self._profile_settings.get_string("font")
+
+ try:
+ font_desc = Pango.FontDescription(font_name)
+ except:
+ if font_name != self.DEFAULT_FONT:
+ if font_name != system_font:
+ try:
+ font_desc = Pango.FontDescription(system_font)
+ except:
+ pass
+
+ if font_desc is None:
+ try:
+ font_desc = Pango.FontDescription(self.DEFAULT_FONT)
+ except:
+ pass
+
+ if font_desc != None:
+ self.view.modify_font(font_desc)
+
+ def on_settings_changed(self, settings, key):
+ self.reconfigure()
+
+ def on_color_settings_changed(self, settings, key):
+ self.error.set_property("foreground", settings.get_string(self.CONSOLE_KEY_ERROR_COLOR))
+ self.command.set_property("foreground", settings.get_string(self.CONSOLE_KEY_COMMAND_COLOR))
+
+ def stop(self):
+ self.namespace = None
+
+ def __key_press_event_cb(self, view, event):
+ modifier_mask = Gtk.accelerator_get_default_mod_mask()
+ event_state = event.state & modifier_mask
+
+ if event.keyval == Gdk.KEY_D and event_state == Gdk.ModifierType.CONTROL_MASK:
+ self.destroy()
+
+ elif event.keyval == Gdk.KEY_Return and event_state == Gdk.ModifierType.CONTROL_MASK:
+ # Get the command
+ buf = view.get_buffer()
+ inp_mark = buf.get_mark("input")
+ inp = buf.get_iter_at_mark(inp_mark)
+ cur = buf.get_end_iter()
+ line = buf.get_text(inp, cur, False)
+ self.current_command = self.current_command + line + "\n"
+ self.history_add(line)
+
+ # Prepare the new line
+ cur = buf.get_end_iter()
+ buf.insert(cur, "\n... ")
+ cur = buf.get_end_iter()
+ buf.move_mark(inp_mark, cur)
+
+ # Keep indentation of precendent line
+ spaces = re.match(self.__spaces_pattern, line)
+ if spaces is not None:
+ buf.insert(cur, line[spaces.start() : spaces.end()])
+ cur = buf.get_end_iter()
+
+ buf.place_cursor(cur)
+ GLib.idle_add(self.scroll_to_end)
+ return True
+
+ elif event.keyval == Gdk.KEY_Return:
+ # Get the marks
+ buf = view.get_buffer()
+ lin_mark = buf.get_mark("input-line")
+ inp_mark = buf.get_mark("input")
+
+ # Get the command line
+ inp = buf.get_iter_at_mark(inp_mark)
+ cur = buf.get_end_iter()
+ line = buf.get_text(inp, cur, False)
+ self.current_command = self.current_command + line + "\n"
+ self.history_add(line)
+
+ # Make the line blue
+ lin = buf.get_iter_at_mark(lin_mark)
+ buf.apply_tag(self.command, lin, cur)
+ buf.insert(cur, "\n")
+
+ cur_strip = self.current_command.rstrip()
+
+ if cur_strip.endswith(":") \
+ or (self.current_command[-2:] != "\n\n" and self.block_command):
+ # Unfinished block command
+ self.block_command = True
+ com_mark = "... "
+ elif cur_strip.endswith("\\"):
+ com_mark = "... "
+ else:
+ # Eval the command
+ self.__run(self.current_command)
+ self.current_command = ''
+ self.block_command = False
+ com_mark = ">>> "
+
+ # Prepare the new line
+ cur = buf.get_end_iter()
+ buf.move_mark(lin_mark, cur)
+ buf.insert(cur, com_mark)
+ cur = buf.get_end_iter()
+ buf.move_mark(inp_mark, cur)
+ buf.place_cursor(cur)
+ GLib.idle_add(self.scroll_to_end)
+ return True
+
+ elif event.keyval == Gdk.KEY_KP_Down or event.keyval == Gdk.KEY_Down:
+ # Next entry from history
+ view.stop_emission_by_name("key_press_event")
+ self.history_down()
+ GLib.idle_add(self.scroll_to_end)
+ return True
+
+ elif event.keyval == Gdk.KEY_KP_Up or event.keyval == Gdk.KEY_Up:
+ # Previous entry from history
+ view.stop_emission_by_name("key_press_event")
+ self.history_up()
+ GLib.idle_add(self.scroll_to_end)
+ return True
+
+ elif event.keyval == Gdk.KEY_KP_Left or event.keyval == Gdk.KEY_Left or \
+ event.keyval == Gdk.KEY_BackSpace:
+ buf = view.get_buffer()
+ inp = buf.get_iter_at_mark(buf.get_mark("input"))
+ cur = buf.get_iter_at_mark(buf.get_insert())
+ if inp.compare(cur) == 0:
+ if not event_state:
+ buf.place_cursor(inp)
+ return True
+ return False
+
+ # For the console we enable smart/home end behavior incoditionally
+ # since it is useful when editing python
+
+ elif (event.keyval == Gdk.KEY_KP_Home or event.keyval == Gdk.KEY_Home) and \
+ event_state == event_state & (Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK):
+ # Go to the begin of the command instead of the begin of the line
+ buf = view.get_buffer()
+ it = buf.get_iter_at_mark(buf.get_mark("input"))
+ ins = buf.get_iter_at_mark(buf.get_insert())
+
+ while it.get_char().isspace():
+ it.forward_char()
+
+ if it.equal(ins):
+ it = buf.get_iter_at_mark(buf.get_mark("input"))
+
+ if event_state & Gdk.ModifierType.SHIFT_MASK:
+ buf.move_mark_by_name("insert", it)
+ else:
+ buf.place_cursor(it)
+ return True
+
+ elif (event.keyval == Gdk.KEY_KP_End or event.keyval == Gdk.KEY_End) and \
+ event_state == event_state & (Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK):
+
+ buf = view.get_buffer()
+ it = buf.get_end_iter()
+ ins = buf.get_iter_at_mark(buf.get_insert())
+
+ it.backward_char()
+
+ while it.get_char().isspace():
+ it.backward_char()
+
+ it.forward_char()
+
+ if it.equal(ins):
+ it = buf.get_end_iter()
+
+ if event_state & Gdk.ModifierType.SHIFT_MASK:
+ buf.move_mark_by_name("insert", it)
+ else:
+ buf.place_cursor(it)
+ return True
+
+ def __mark_set_cb(self, buf, it, name):
+ input = buf.get_iter_at_mark(buf.get_mark("input"))
+ pos = buf.get_iter_at_mark(buf.get_insert())
+ self.view.set_editable(pos.compare(input) != -1)
+
+ def get_command_line(self):
+ buf = self.view.get_buffer()
+ inp = buf.get_iter_at_mark(buf.get_mark("input"))
+ cur = buf.get_end_iter()
+ return buf.get_text(inp, cur, False)
+
+ def set_command_line(self, command):
+ buf = self.view.get_buffer()
+ mark = buf.get_mark("input")
+ inp = buf.get_iter_at_mark(mark)
+ cur = buf.get_end_iter()
+ buf.delete(inp, cur)
+ buf.insert(inp, command)
+ self.view.grab_focus()
+
+ def history_add(self, line):
+ if line.strip() != '':
+ self.history_pos = len(self.history)
+ self.history[self.history_pos - 1] = line
+ self.history.append('')
+
+ def history_up(self):
+ if self.history_pos > 0:
+ self.history[self.history_pos] = self.get_command_line()
+ self.history_pos = self.history_pos - 1
+ self.set_command_line(self.history[self.history_pos])
+
+ def history_down(self):
+ if self.history_pos < len(self.history) - 1:
+ self.history[self.history_pos] = self.get_command_line()
+ self.history_pos = self.history_pos + 1
+ self.set_command_line(self.history[self.history_pos])
+
+ def scroll_to_end(self):
+ i = self.view.get_buffer().get_end_iter()
+ self.view.scroll_to_iter(i, 0.0, False, 0.5, 0.5)
+ return False
+
+ def write(self, text, tag = None):
+ buf = self.view.get_buffer()
+ if tag is None:
+ buf.insert(buf.get_end_iter(), text)
+ else:
+ buf.insert_with_tags(buf.get_end_iter(), text, tag)
+
+ GLib.idle_add(self.scroll_to_end)
+
+ def eval(self, command, display_command = False):
+ buf = self.view.get_buffer()
+ lin = buf.get_mark("input-line")
+ buf.delete(buf.get_iter_at_mark(lin),
+ buf.get_end_iter())
+
+ if isinstance(command, list) or isinstance(command, tuple):
+ for c in command:
+ if display_command:
+ self.write(">>> " + c + "\n", self.command)
+ self.__run(c)
+ else:
+ if display_command:
+ self.write(">>> " + c + "\n", self.command)
+ self.__run(command)
+
+ cur = buf.get_end_iter()
+ buf.move_mark_by_name("input-line", cur)
+ buf.insert(cur, ">>> ")
+ cur = buf.get_end_iter()
+ buf.move_mark_by_name("input", cur)
+ self.view.scroll_to_iter(buf.get_end_iter(), 0.0, False, 0.5, 0.5)
+
+ def __run(self, command):
+ sys.stdout, self.stdout = self.stdout, sys.stdout
+ sys.stderr, self.stderr = self.stderr, sys.stderr
+
+ try:
+ try:
+ r = eval(command, self.namespace, self.namespace)
+ if r is not None:
+ print(r)
+ except SyntaxError:
+ exec(command, self.namespace)
+ except:
+ if hasattr(sys, 'last_type') and sys.last_type == SystemExit:
+ self.destroy()
+ else:
+ traceback.print_exc()
+
+ sys.stdout, self.stdout = self.stdout, sys.stdout
+ sys.stderr, self.stderr = self.stderr, sys.stderr
+
+ def destroy(self):
+ pass
+ #gtk.ScrolledWindow.destroy(self)
+
+class OutFile:
+ """A fake output file object. It sends output to a TK test widget,
+ and if asked for a file number, returns one set on instance creation"""
+ def __init__(self, console, fn, tag):
+ self.fn = fn
+ self.console = console
+ self.tag = tag
+ def close(self): pass
+ def flush(self): pass
+ def fileno(self): return self.fn
+ def isatty(self): return 0
+ def read(self, a): return ''
+ def readline(self): return ''
+ def readlines(self): return []
+ def write(self, s): self.console.write(s, self.tag)
+ def writelines(self, l): self.console.write(l, self.tag)
+ def seek(self, a): raise IOError((29, 'Illegal seek'))
+ def tell(self): raise IOError((29, 'Illegal seek'))
+ truncate = tell
+
+# ex:et:ts=4:
diff --git a/plugins/pythonconsole/pythonconsole/meson.build b/plugins/pythonconsole/pythonconsole/meson.build
new file mode 100644
index 0000000..6bae388
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole/meson.build
@@ -0,0 +1,24 @@
+pythonconsole_sources = files(
+ '__init__.py',
+ 'config.py',
+ 'console.py',
+)
+
+install_data(
+ pythonconsole_sources,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ 'pythonconsole',
+ )
+)
+
+install_data(
+ 'config.ui',
+ install_dir: join_paths(
+ pkgdatadir,
+ 'plugins',
+ 'pythonconsole',
+ 'ui',
+ )
+)
diff --git a/plugins/quickhighlight/gedit-quick-highlight-plugin.c b/plugins/quickhighlight/gedit-quick-highlight-plugin.c
new file mode 100644
index 0000000..7b4289d
--- /dev/null
+++ b/plugins/quickhighlight/gedit-quick-highlight-plugin.c
@@ -0,0 +1,483 @@
+/*
+ * gedit-quick-highlight-plugin.c
+ *
+ * Copyright (C) 2018 Martin Blanchard
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-document.h>
+#include <gedit/gedit-view-activatable.h>
+#include <gedit/gedit-view.h>
+
+#include "gedit-quick-highlight-plugin.h"
+
+struct _GeditQuickHighlightPluginPrivate
+{
+ GeditView *view;
+
+ GeditDocument *buffer;
+ GtkTextMark *insert_mark;
+
+ GtkSourceSearchContext *search_context;
+ GtkSourceStyle *style;
+
+ gulong buffer_handler_id;
+ gulong mark_set_handler_id;
+ gulong delete_range_handler_id;
+ gulong style_scheme_handler_id;
+
+ guint queued_highlight;
+};
+
+enum
+{
+ PROP_0,
+ PROP_VIEW
+};
+
+static void gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditQuickHighlightPlugin,
+ gedit_quick_highlight_plugin,
+ PEAS_TYPE_EXTENSION_BASE,
+ 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_VIEW_ACTIVATABLE,
+ gedit_view_activatable_iface_init)
+ G_ADD_PRIVATE_DYNAMIC (GeditQuickHighlightPlugin))
+
+static void gedit_quick_highlight_plugin_notify_buffer_cb (GObject *object, GParamSpec *pspec, gpointer user_data);
+static void gedit_quick_highlight_plugin_mark_set_cb (GtkTextBuffer *textbuffer, GtkTextIter *location, GtkTextMark *mark, gpointer user_data);
+static void gedit_quick_highlight_plugin_delete_range_cb (GtkTextBuffer *textbuffer, GtkTextIter *start, GtkTextIter *end, gpointer user_data);
+static void gedit_quick_highlight_plugin_notify_style_scheme_cb (GObject *object, GParamSpec *pspec, gpointer user_data);
+
+static void
+gedit_quick_highlight_plugin_load_style (GeditQuickHighlightPlugin *plugin)
+{
+ GtkSourceStyleScheme *style_scheme;
+ GtkSourceStyle *style = NULL;
+
+ g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin));
+
+ if (plugin->priv->buffer == NULL)
+ {
+ return;
+ }
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_clear_object (&plugin->priv->style);
+
+ style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (plugin->priv->buffer));
+
+ if (style_scheme != NULL)
+ {
+ style = gtk_source_style_scheme_get_style (style_scheme, "quick-highlight-match");
+
+ if (style != NULL)
+ {
+ plugin->priv->style = gtk_source_style_copy (style);
+ }
+ }
+}
+
+static gboolean
+gedit_quick_highlight_plugin_highlight_worker (gpointer user_data)
+{
+ GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data);
+ GtkSourceSearchSettings *search_settings;
+ GtkTextIter start, end;
+ g_autofree gchar *text = NULL;
+
+ g_assert (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin));
+
+ plugin->priv->queued_highlight = 0;
+
+ if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (plugin->priv->buffer), &start, &end))
+ {
+ g_clear_object (&plugin->priv->search_context);
+ return G_SOURCE_REMOVE;
+ }
+
+ if (gtk_text_iter_get_line (&start) != gtk_text_iter_get_line (&end))
+ {
+ g_clear_object (&plugin->priv->search_context);
+ return G_SOURCE_REMOVE;
+ }
+
+ if (plugin->priv->search_context == NULL)
+ {
+ search_settings =
+ g_object_new (GTK_SOURCE_TYPE_SEARCH_SETTINGS,
+ "at-word-boundaries", FALSE,
+ "case-sensitive", TRUE,
+ "regex-enabled", FALSE,
+ NULL);
+
+ plugin->priv->search_context =
+ g_object_new (GTK_SOURCE_TYPE_SEARCH_CONTEXT,
+ "buffer", plugin->priv->buffer,
+ "highlight", FALSE,
+ "match-style", plugin->priv->style,
+ "settings", search_settings,
+ NULL);
+
+ g_object_unref (search_settings);
+ }
+ else
+ {
+ search_settings =
+ gtk_source_search_context_get_settings (plugin->priv->search_context);
+ }
+
+ text = gtk_text_iter_get_slice (&start, &end);
+
+ gtk_source_search_settings_set_search_text (search_settings, text);
+
+ gtk_source_search_context_set_highlight (plugin->priv->search_context, TRUE);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gedit_quick_highlight_plugin_queue_update (GeditQuickHighlightPlugin *plugin)
+{
+ g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin));
+
+ if (plugin->priv->queued_highlight != 0)
+ {
+ return;
+ }
+
+ plugin->priv->queued_highlight =
+ gdk_threads_add_idle_full (G_PRIORITY_LOW,
+ gedit_quick_highlight_plugin_highlight_worker,
+ g_object_ref (plugin),
+ g_object_unref);
+}
+
+static void
+gedit_quick_highlight_plugin_notify_weak_buffer_cb (gpointer data,
+ GObject *where_the_object_was)
+{
+ GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (data);
+
+ g_assert (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin));
+
+ plugin->priv->style_scheme_handler_id = 0;
+ plugin->priv->buffer = NULL;
+}
+
+static void
+gedit_quick_highlight_plugin_unref_weak_buffer (GeditQuickHighlightPlugin *plugin)
+{
+ g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin));
+
+ if (plugin->priv->buffer == NULL)
+ {
+ return;
+ }
+
+ if (plugin->priv->delete_range_handler_id > 0)
+ {
+ g_signal_handler_disconnect (plugin->priv->buffer,
+ plugin->priv->delete_range_handler_id);
+ plugin->priv->delete_range_handler_id = 0;
+ }
+
+ if (plugin->priv->mark_set_handler_id > 0)
+ {
+ g_signal_handler_disconnect (plugin->priv->buffer,
+ plugin->priv->mark_set_handler_id);
+ plugin->priv->mark_set_handler_id = 0;
+ }
+
+ if (plugin->priv->style_scheme_handler_id > 0)
+ {
+ g_signal_handler_disconnect (plugin->priv->buffer,
+ plugin->priv->style_scheme_handler_id);
+ plugin->priv->style_scheme_handler_id = 0;
+ }
+
+ g_object_weak_unref (G_OBJECT (plugin->priv->buffer),
+ gedit_quick_highlight_plugin_notify_weak_buffer_cb,
+ plugin);
+
+ plugin->priv->buffer = NULL;
+}
+
+static void
+gedit_quick_highlight_plugin_set_buffer (GeditQuickHighlightPlugin *plugin,
+ GeditDocument *buffer)
+{
+ g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin));
+ g_return_if_fail (GEDIT_IS_DOCUMENT (buffer));
+
+ if (plugin->priv->buffer == buffer)
+ {
+ return;
+ }
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ gedit_quick_highlight_plugin_unref_weak_buffer (plugin);
+
+ plugin->priv->buffer = buffer;
+
+ if (plugin->priv->buffer != NULL)
+ {
+ g_object_weak_ref (G_OBJECT (plugin->priv->buffer),
+ gedit_quick_highlight_plugin_notify_weak_buffer_cb,
+ plugin);
+
+ plugin->priv->style_scheme_handler_id =
+ g_signal_connect (plugin->priv->buffer,
+ "notify::style-scheme",
+ G_CALLBACK (gedit_quick_highlight_plugin_notify_style_scheme_cb),
+ plugin);
+
+ plugin->priv->mark_set_handler_id =
+ g_signal_connect (plugin->priv->buffer,
+ "mark-set",
+ G_CALLBACK (gedit_quick_highlight_plugin_mark_set_cb),
+ plugin);
+
+ plugin->priv->delete_range_handler_id =
+ g_signal_connect (plugin->priv->buffer,
+ "delete-range",
+ G_CALLBACK (gedit_quick_highlight_plugin_delete_range_cb),
+ plugin);
+
+ plugin->priv->insert_mark =
+ gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (plugin->priv->buffer));
+
+ gedit_quick_highlight_plugin_load_style (plugin);
+
+ gedit_quick_highlight_plugin_queue_update (plugin);
+ }
+}
+
+static void
+gedit_quick_highlight_plugin_mark_set_cb (GtkTextBuffer *textbuffer,
+ GtkTextIter *location,
+ GtkTextMark *mark,
+ gpointer user_data)
+{
+ GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data);
+
+ g_assert (GEDIT_QUICK_HIGHLIGHT_PLUGIN (plugin));
+
+ if G_LIKELY (mark != plugin->priv->insert_mark)
+ {
+ return;
+ }
+
+ gedit_quick_highlight_plugin_queue_update (plugin);
+}
+
+static void
+gedit_quick_highlight_plugin_delete_range_cb (GtkTextBuffer *textbuffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gpointer user_data)
+{
+ GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data);
+
+ g_assert (GEDIT_QUICK_HIGHLIGHT_PLUGIN (plugin));
+
+ gedit_quick_highlight_plugin_queue_update (plugin);
+}
+
+static void
+gedit_quick_highlight_plugin_notify_style_scheme_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data);
+
+ g_assert (GEDIT_QUICK_HIGHLIGHT_PLUGIN (plugin));
+
+ gedit_quick_highlight_plugin_load_style (plugin);
+}
+
+static void
+gedit_quick_highlight_plugin_dispose (GObject *object)
+{
+ GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object);
+
+ g_clear_object (&plugin->priv->search_context);
+
+ gedit_quick_highlight_plugin_unref_weak_buffer (plugin);
+
+ g_clear_object (&plugin->priv->view);
+
+ G_OBJECT_CLASS (gedit_quick_highlight_plugin_parent_class)->dispose (object);
+}
+
+static void
+gedit_quick_highlight_plugin_finalize (GObject *object)
+{
+ GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object);
+
+ g_clear_object (&plugin->priv->style);
+
+ G_OBJECT_CLASS (gedit_quick_highlight_plugin_parent_class)->finalize (object);
+}
+
+static void
+gedit_quick_highlight_plugin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ g_value_set_object (value, plugin->priv->view);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_quick_highlight_plugin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ plugin->priv->view = GEDIT_VIEW (g_value_dup_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_quick_highlight_plugin_class_init (GeditQuickHighlightPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_quick_highlight_plugin_dispose;
+ object_class->finalize = gedit_quick_highlight_plugin_finalize;
+ object_class->set_property = gedit_quick_highlight_plugin_set_property;
+ object_class->get_property = gedit_quick_highlight_plugin_get_property;
+
+ g_object_class_override_property (object_class, PROP_VIEW, "view");
+}
+
+static void
+gedit_quick_highlight_plugin_class_finalize (GeditQuickHighlightPluginClass *klass)
+{
+}
+
+static void
+gedit_quick_highlight_plugin_init (GeditQuickHighlightPlugin *plugin)
+{
+ plugin->priv = gedit_quick_highlight_plugin_get_instance_private (plugin);
+}
+
+static void
+gedit_quick_highlight_plugin_activate (GeditViewActivatable *activatable)
+{
+ GeditQuickHighlightPlugin *plugin;
+ GtkTextBuffer *buffer;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (activatable);
+
+ plugin->priv->buffer_handler_id =
+ g_signal_connect (plugin->priv->view,
+ "notify::buffer",
+ G_CALLBACK (gedit_quick_highlight_plugin_notify_buffer_cb),
+ plugin);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view));
+
+ gedit_quick_highlight_plugin_set_buffer (plugin, GEDIT_DOCUMENT (buffer));
+}
+
+static void
+gedit_quick_highlight_plugin_deactivate (GeditViewActivatable *activatable)
+{
+ GeditQuickHighlightPlugin *plugin;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (activatable);
+
+ g_clear_object (&plugin->priv->style);
+ g_clear_object (&plugin->priv->search_context);
+
+ gedit_quick_highlight_plugin_unref_weak_buffer (plugin);
+
+ if (plugin->priv->view != NULL && plugin->priv->buffer_handler_id > 0)
+ {
+ g_signal_handler_disconnect (plugin->priv->view,
+ plugin->priv->buffer_handler_id);
+ plugin->priv->buffer_handler_id = 0;
+ }
+}
+
+static void
+gedit_quick_highlight_plugin_notify_buffer_cb (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data);
+ GtkTextBuffer *buffer;
+
+ g_assert (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view));
+
+ gedit_quick_highlight_plugin_set_buffer (plugin, GEDIT_DOCUMENT (buffer));
+}
+
+static void
+gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface)
+{
+ iface->activate = gedit_quick_highlight_plugin_activate;
+ iface->deactivate = gedit_quick_highlight_plugin_deactivate;
+}
+
+G_MODULE_EXPORT void
+peas_register_types (PeasObjectModule *module)
+{
+ gedit_quick_highlight_plugin_register_type (G_TYPE_MODULE (module));
+
+ peas_object_module_register_extension_type (module,
+ GEDIT_TYPE_VIEW_ACTIVATABLE,
+ GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/quickhighlight/gedit-quick-highlight-plugin.h b/plugins/quickhighlight/gedit-quick-highlight-plugin.h
new file mode 100644
index 0000000..3e668d0
--- /dev/null
+++ b/plugins/quickhighlight/gedit-quick-highlight-plugin.h
@@ -0,0 +1,63 @@
+/*
+ * gedit-quick-highlight-plugin.h
+ *
+ * Copyright (C) 2018 Martin Blanchard
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef GEDIT_QUICK_HIGHLIGHT_PLUGIN_H
+#define GEDIT_QUICK_HIGHLIGHT_PLUGIN_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libpeas/peas-extension-base.h>
+#include <libpeas/peas-object-module.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN (gedit_quick_highlight_plugin_get_type ())
+#define GEDIT_QUICK_HIGHLIGHT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN, GeditQuickHighlightPlugin))
+#define GEDIT_QUICK_HIGHLIGHT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN, GeditQuickHighlightPluginClass))
+#define GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN))
+#define GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN))
+#define GEDIT_QUICK_HIGHLIGHT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN, GeditQuickHighlightPluginClass))
+
+typedef struct _GeditQuickHighlightPlugin GeditQuickHighlightPlugin;
+typedef struct _GeditQuickHighlightPluginPrivate GeditQuickHighlightPluginPrivate;
+typedef struct _GeditQuickHighlightPluginClass GeditQuickHighlightPluginClass;
+
+struct _GeditQuickHighlightPlugin
+{
+ PeasExtensionBase parent_instance;
+
+ /* < private > */
+ GeditQuickHighlightPluginPrivate *priv;
+};
+
+struct _GeditQuickHighlightPluginClass
+{
+ PeasExtensionBaseClass parent_class;
+};
+
+GType gedit_quick_highlight_plugin_get_type (void) G_GNUC_CONST;
+
+G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
+
+G_END_DECLS
+
+#endif /* GEDIT_QUICK_HIGHLIGHT_PLUGIN_H */
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/quickhighlight/meson.build b/plugins/quickhighlight/meson.build
new file mode 100644
index 0000000..18fb417
--- /dev/null
+++ b/plugins/quickhighlight/meson.build
@@ -0,0 +1,32 @@
+libquickhighlight_sources = files(
+ 'gedit-quick-highlight-plugin.c',
+)
+
+libquickhighlight_deps = [
+ libgedit_dep,
+]
+
+libquickhighlight_sha = shared_module(
+ 'quickhighlight',
+ sources: libquickhighlight_sources,
+ include_directories: root_include_dir,
+ dependencies: libquickhighlight_deps,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ ),
+ name_suffix: module_suffix,
+)
+
+custom_target(
+ 'quickhighlight.plugin',
+ input: 'quickhighlight.plugin.desktop.in',
+ output: 'quickhighlight.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
diff --git a/plugins/quickhighlight/quickhighlight.plugin.desktop.in b/plugins/quickhighlight/quickhighlight.plugin.desktop.in
new file mode 100644
index 0000000..abb1baf
--- /dev/null
+++ b/plugins/quickhighlight/quickhighlight.plugin.desktop.in
@@ -0,0 +1,8 @@
+[Plugin]
+Module=quickhighlight
+IAge=3
+Name=Quick Highlight
+Description=Highlights every occurrences of selected text.
+Authors=Martin Blanchard <tchaik@gmx.com>
+Copyright=Copyright © 2016 Martin Blanchard
+Website=http://www.gedit.org
diff --git a/plugins/quickopen/meson.build b/plugins/quickopen/meson.build
new file mode 100644
index 0000000..c5a2680
--- /dev/null
+++ b/plugins/quickopen/meson.build
@@ -0,0 +1,19 @@
+install_subdir(
+ 'quickopen',
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
+
+custom_target(
+ 'quickopen.plugin',
+ input: 'quickopen.plugin.desktop.in',
+ output: 'quickopen.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
diff --git a/plugins/quickopen/quickopen.plugin.desktop.in b/plugins/quickopen/quickopen.plugin.desktop.in
new file mode 100644
index 0000000..80fa03b
--- /dev/null
+++ b/plugins/quickopen/quickopen.plugin.desktop.in
@@ -0,0 +1,12 @@
+[Plugin]
+Loader=python3
+Module=quickopen
+IAge=3
+Name=Quick Open
+Description=Quickly open files.
+# TRANSLATORS: Do NOT translate or transliterate this text!
+# This is an icon file name.
+Icon=document-open
+Authors=Jesse van den Kieboom <jessevdk@gnome.org>
+Copyright=Copyright © 2009 Jesse van den Kieboom
+Website=http://www.gedit.org
diff --git a/plugins/quickopen/quickopen/__init__.py b/plugins/quickopen/quickopen/__init__.py
new file mode 100644
index 0000000..3f708a3
--- /dev/null
+++ b/plugins/quickopen/quickopen/__init__.py
@@ -0,0 +1,193 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2009 - Jesse van den Kieboom
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+import gi
+gi.require_version('Gedit', '3.0')
+gi.require_version('Gtk', '3.0')
+from gi.repository import GObject, Gio, GLib, Gtk, Gedit
+
+from .popup import Popup
+from .virtualdirs import RecentDocumentsDirectory
+from .virtualdirs import CurrentDocumentsDirectory
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class QuickOpenAppActivatable(GObject.Object, Gedit.AppActivatable):
+ app = GObject.Property(type=Gedit.App)
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+
+ def do_activate(self):
+ self.app.add_accelerator("<Primary><Alt>O", "win.quickopen", None)
+
+ self.menu_ext = self.extend_menu("file-section")
+ item = Gio.MenuItem.new(_("Quick Open…"), "win.quickopen")
+ self.menu_ext.prepend_menu_item(item)
+
+ def do_deactivate(self):
+ self.app.remove_accelerator("win.quickopen", None)
+
+
+class QuickOpenPlugin(GObject.Object, Gedit.WindowActivatable):
+ __gtype_name__ = "QuickOpenPlugin"
+
+ window = GObject.Property(type=Gedit.Window)
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+
+ def do_activate(self):
+ self._popup_size = (450, 300)
+ self._popup = None
+
+ action = Gio.SimpleAction(name="quickopen")
+ action.connect('activate', self.on_quick_open_activate)
+ self.window.add_action(action)
+
+ def do_deactivate(self):
+ self.window.remove_action("quickopen")
+
+ def get_popup_size(self):
+ return self._popup_size
+
+ def set_popup_size(self, size):
+ self._popup_size = size
+
+ def _create_popup(self):
+ paths = []
+
+ # Open documents
+ paths.append(CurrentDocumentsDirectory(self.window))
+
+ doc = self.window.get_active_document()
+
+ # Current document directory
+ if doc and doc.get_file().is_local():
+ gfile = doc.get_file().get_location()
+ paths.append(gfile.get_parent())
+
+ # File browser root directory
+ bus = self.window.get_message_bus()
+
+ if bus.is_registered('/plugins/filebrowser', 'get_root'):
+ msg = bus.send_sync('/plugins/filebrowser', 'get_root')
+
+ if msg:
+ gfile = msg.props.location
+
+ if gfile and gfile.is_native():
+ paths.append(gfile)
+
+ # Recent documents
+ paths.append(RecentDocumentsDirectory())
+
+ # Local bookmarks
+ for path in self._local_bookmarks():
+ paths.append(path)
+
+ # Desktop directory
+ desktopdir = self._desktop_dir()
+
+ if desktopdir:
+ paths.append(Gio.file_new_for_path(desktopdir))
+
+ # Home directory
+ paths.append(Gio.file_new_for_path(os.path.expanduser('~')))
+
+ self._popup = Popup(self.window, paths, self.on_activated)
+ self.window.get_group().add_window(self._popup)
+
+ self._popup.set_default_size(*self.get_popup_size())
+ self._popup.set_transient_for(self.window)
+ self._popup.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
+ self._popup.connect('destroy', self.on_popup_destroy)
+
+ def _local_bookmarks(self):
+ filename = os.path.expanduser('~/.config/gtk-3.0/bookmarks')
+
+ if not os.path.isfile(filename):
+ return []
+
+ paths = []
+
+ for line in open(filename, 'r', encoding='utf-8'):
+ uri = line.strip().split(" ")[0]
+ f = Gio.file_new_for_uri(uri)
+
+ if f.is_native():
+ try:
+ info = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE,
+ Gio.FileQueryInfoFlags.NONE,
+ None)
+
+ if info and info.get_file_type() == Gio.FileType.DIRECTORY:
+ paths.append(f)
+ except:
+ pass
+
+ return paths
+
+ def _desktop_dir(self):
+ config = os.getenv('XDG_CONFIG_HOME')
+
+ if not config:
+ config = os.path.expanduser('~/.config')
+
+ config = os.path.join(config, 'user-dirs.dirs')
+ desktopdir = None
+
+ if os.path.isfile(config):
+ for line in open(config, 'r', encoding='utf-8'):
+ line = line.strip()
+
+ if line.startswith('XDG_DESKTOP_DIR'):
+ parts = line.split('=', 1)
+ desktopdir = parts[1].strip('"').strip("'")
+ desktopdir = os.path.expandvars(desktopdir)
+ break
+
+ if not desktopdir:
+ desktopdir = os.path.expanduser('~/Desktop')
+
+ return desktopdir
+
+ # Callbacks
+ def on_quick_open_activate(self, action, parameter, user_data=None):
+ if not self._popup:
+ self._create_popup()
+
+ self._popup.show()
+
+ def on_popup_destroy(self, popup, user_data=None):
+ self.set_popup_size(popup.get_final_size())
+
+ self._popup = None
+
+ def on_activated(self, gfile, user_data=None):
+ Gedit.commands_load_location(self.window, gfile, None, -1, -1)
+ return True
+
+# ex:ts=4:et:
diff --git a/plugins/quickopen/quickopen/popup.py b/plugins/quickopen/quickopen/popup.py
new file mode 100644
index 0000000..50d957e
--- /dev/null
+++ b/plugins/quickopen/quickopen/popup.py
@@ -0,0 +1,617 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2009 - Jesse van den Kieboom
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import os
+import platform
+import functools
+import fnmatch
+
+from gi.repository import GLib, Gio, GObject, Pango, Gtk, Gdk, Gedit
+import xml.sax.saxutils
+from .virtualdirs import VirtualDirectory
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class Popup(Gtk.Dialog):
+ __gtype_name__ = "QuickOpenPopup"
+
+ def __init__(self, window, paths, handler):
+ Gtk.Dialog.__init__(self,
+ title=_('Quick Open'),
+ transient_for=window,
+ modal=True,
+ destroy_with_parent=True)
+
+ self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL)
+ self._open_button = self.add_button(_("_Open"),
+ Gtk.ResponseType.ACCEPT)
+
+ self._handler = handler
+ self._build_ui()
+
+ self._size = (0, 0)
+ self._dirs = []
+ self._cache = {}
+ self._theme = None
+ self._cursor = None
+ self._shift_start = None
+
+ self._busy_cursor = Gdk.Cursor(Gdk.CursorType.WATCH)
+
+ accel_group = Gtk.AccelGroup()
+ accel_group.connect(Gdk.KEY_l,
+ Gdk.ModifierType.CONTROL_MASK,
+ 0,
+ self.on_focus_entry)
+
+ self.add_accel_group(accel_group)
+
+ unique = []
+
+ for path in paths:
+ if not path.get_uri() in unique:
+ self._dirs.append(path)
+ unique.append(path.get_uri())
+
+ self.connect('show', self.on_show)
+
+ def get_final_size(self):
+ return self._size
+
+ def _build_ui(self):
+ self.set_border_width(5)
+ vbox = self.get_content_area()
+ vbox.set_spacing(2)
+ action_area = self.get_action_area()
+ action_area.set_border_width(5)
+ action_area.set_spacing(6)
+
+ self._entry = Gtk.SearchEntry()
+ self._entry.set_placeholder_text(_('Type to search…'))
+
+ self._entry.connect('changed', self.on_changed)
+ self._entry.connect('key-press-event', self.on_key_press_event)
+
+ sw = Gtk.ScrolledWindow()
+ sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+ sw.set_shadow_type(Gtk.ShadowType.OUT)
+
+ tv = Gtk.TreeView()
+ tv.set_headers_visible(False)
+
+ self._store = Gtk.ListStore(Gio.Icon,
+ str,
+ GObject.Object,
+ Gio.FileType)
+ tv.set_model(self._store)
+
+ self._treeview = tv
+ tv.connect('row-activated', self.on_row_activated)
+
+ column = Gtk.TreeViewColumn()
+
+ renderer = Gtk.CellRendererPixbuf()
+ column.pack_start(renderer, False)
+ column.add_attribute(renderer, "gicon", 0)
+
+ renderer = Gtk.CellRendererText()
+ column.pack_start(renderer, True)
+ column.add_attribute(renderer, "markup", 1)
+
+ column.set_cell_data_func(renderer, self.on_cell_data_cb, None)
+
+ tv.append_column(column)
+ sw.add(tv)
+
+ selection = tv.get_selection()
+ selection.connect('changed', self.on_selection_changed)
+ selection.set_mode(Gtk.SelectionMode.MULTIPLE)
+
+ vbox.pack_start(self._entry, False, False, 0)
+ vbox.pack_start(sw, True, True, 0)
+
+ lbl = Gtk.Label()
+ lbl.set_halign(Gtk.Align.START)
+ lbl.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
+ self._info_label = lbl
+
+ vbox.pack_start(lbl, False, False, 0)
+
+ # Initial selection
+ self.on_selection_changed(tv.get_selection())
+ vbox.show_all()
+
+ def on_cell_data_cb(self, column, cell, model, piter, user_data):
+ path = model.get_path(piter)
+
+ if self._cursor and path == self._cursor.get_path():
+ style = self._treeview.get_style()
+ bg = style.bg[Gtk.StateType.PRELIGHT]
+
+ cell.set_property('cell-background-gdk', bg)
+ cell.set_property('style', Pango.Style.ITALIC)
+ else:
+ cell.set_property('cell-background-set', False)
+ cell.set_property('style-set', False)
+
+ def _is_text(self, entry):
+ content_type = entry.get_content_type()
+
+ if content_type is None or Gio.content_type_is_unknown(content_type):
+ return True
+
+ if platform.system() != 'Windows':
+ if Gio.content_type_is_a(content_type, 'text/plain'):
+ return True
+ else:
+ if Gio.content_type_is_a(content_type, 'text'):
+ return True
+
+ # This covers a rare case in which on Windows the PerceivedType
+ # is not set to "text" but the Content Type is set to text/plain
+ if Gio.content_type_get_mime_type(content_type) == 'text/plain':
+ return True
+
+ return False
+
+ def _list_dir(self, gfile):
+ entries = []
+
+ try:
+ ret = gfile.enumerate_children("standard::*",
+ Gio.FileQueryInfoFlags.NONE,
+ None)
+ except GLib.Error as e:
+ pass
+
+ if isinstance(ret, Gio.FileEnumerator):
+ while True:
+ entry = ret.next_file(None)
+
+ if not entry:
+ break
+
+ if not entry.get_is_backup():
+ entries.append((gfile.get_child(entry.get_name()), entry))
+ else:
+ entries = ret
+
+ children = []
+
+ for entry in entries:
+ file_type = entry[1].get_file_type()
+
+ if file_type == Gio.FileType.REGULAR:
+ if not self._is_text(entry[1]):
+ continue
+
+ children.append((entry[0],
+ entry[1].get_name(),
+ file_type,
+ entry[1].get_icon()))
+
+ return children
+
+ def _compare_entries(self, a, b, lpart):
+ if lpart in a:
+ if lpart in b:
+ if a.index(lpart) < b.index(lpart):
+ return -1
+ elif a.index(lpart) > b.index(lpart):
+ return 1
+ else:
+ return 0
+ else:
+ return -1
+ elif lpart in b:
+ return 1
+ else:
+ return 0
+
+ def _match_glob(self, s, glob):
+ if glob:
+ glob += '*'
+
+ return fnmatch.fnmatch(s, glob)
+
+ def do_search_dir(self, parts, d):
+ if not parts or not d:
+ return []
+
+ if d in self._cache:
+ entries = self._cache[d]
+ else:
+ entries = self._list_dir(d)
+ entries.sort(key=lambda x: x[1].lower())
+ self._cache[d] = entries
+
+ found = []
+ newdirs = []
+
+ lpart = parts[0].lower()
+
+ for entry in entries:
+ if not entry:
+ continue
+
+ lentry = entry[1].lower()
+
+ if not lpart or lpart in lentry or self._match_glob(lentry, lpart):
+ if entry[2] == Gio.FileType.DIRECTORY:
+ if len(parts) > 1:
+ newdirs.append(entry[0])
+ else:
+ found.append(entry)
+ elif entry[2] == Gio.FileType.REGULAR and \
+ (not lpart or len(parts) == 1):
+ found.append(entry)
+
+ found.sort(key=functools.cmp_to_key(lambda a, b: self._compare_entries(a[1].lower(), b[1].lower(), lpart)))
+
+ if lpart == '..':
+ newdirs.append(d.get_parent())
+
+ for dd in newdirs:
+ found.extend(self.do_search_dir(parts[1:], dd))
+
+ return found
+
+ def _replace_insensitive(self, s, find, rep):
+ out = ''
+ l = s.lower()
+ find = find.lower()
+ last = 0
+
+ if len(find) == 0:
+ return xml.sax.saxutils.escape(s)
+
+ while True:
+ m = l.find(find, last)
+
+ if m == -1:
+ break
+ else:
+ out += xml.sax.saxutils.escape(s[last:m]) + rep % (xml.sax.saxutils.escape(s[m:m + len(find)]),)
+ last = m + len(find)
+
+ return out + xml.sax.saxutils.escape(s[last:])
+
+ def make_markup(self, parts, path):
+ out = []
+
+ for i in range(0, len(parts)):
+ out.append(self._replace_insensitive(path[i], parts[i], "<b>%s</b>"))
+
+ return os.sep.join(out)
+
+ def _get_icon(self, f):
+ query = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_ICON,
+ Gio.FileQueryInfoFlags.NONE,
+ None)
+
+ if not query:
+ return None
+ else:
+ return query.get_icon()
+
+ def _make_parts(self, parent, child, pp):
+ parts = []
+
+ # We went from parent, to child, using pp
+ idx = len(pp) - 1
+
+ while idx >= 0:
+ if pp[idx] == '..':
+ parts.insert(0, '..')
+ else:
+ parts.insert(0, child.get_basename())
+ child = child.get_parent()
+
+ idx -= 1
+
+ return parts
+
+ def normalize_relative(self, parts):
+ if not parts:
+ return []
+
+ out = self.normalize_relative(parts[:-1])
+
+ if parts[-1] == '..':
+ if not out or (out[-1] == '..') or len(out) == 1:
+ out.append('..')
+ else:
+ del out[-1]
+ else:
+ out.append(parts[-1])
+
+ return out
+
+ def _append_to_store(self, item):
+ uri = item[2].get_uri()
+
+ if uri not in self._stored_items:
+ self._store.append(item)
+ self._stored_items.add(uri)
+
+ def _clear_store(self):
+ self._store.clear()
+ self._stored_items = set()
+
+ def _show_virtuals(self):
+ for d in self._dirs:
+ if isinstance(d, VirtualDirectory):
+ for entry in d.enumerate_children("standard::*", 0, None):
+ self._append_to_store((entry[1].get_icon(),
+ xml.sax.saxutils.escape(entry[1].get_name()),
+ entry[0],
+ entry[1].get_file_type()))
+
+ def _set_busy(self, busy):
+ if busy:
+ self.get_window().set_cursor(self._busy_cursor)
+ else:
+ self.get_window().set_cursor(None)
+ Gdk.flush()
+
+ def _remove_cursor(self):
+ if self._cursor:
+ path = self._cursor.get_path()
+ self._cursor = None
+
+ self._store.row_changed(path, self._store.get_iter(path))
+
+ def do_search(self):
+ self._set_busy(True)
+ self._remove_cursor()
+
+ text = self._entry.get_text().strip()
+ self._clear_store()
+
+ if text == '':
+ self._show_virtuals()
+ else:
+ parts = self.normalize_relative(text.split(os.sep))
+ files = []
+
+ for d in self._dirs:
+ for entry in self.do_search_dir(parts, d):
+ pathparts = self._make_parts(d, entry[0], parts)
+ self._append_to_store((entry[3],
+ self.make_markup(parts, pathparts),
+ entry[0],
+ entry[2]))
+
+ piter = self._store.get_iter_first()
+ if piter:
+ path = self._store.get_path(piter)
+ self._treeview.get_selection().select_path(path)
+
+ self._set_busy(False)
+
+ # FIXME: override doesn't work anymore for some reason, if we override
+ # the widget is not realized
+ def on_show(self, data=None):
+ # Gtk.Window.do_show(self)
+
+ self._entry.grab_focus()
+ self._entry.set_text("")
+
+ self.do_search()
+
+ def on_changed(self, editable):
+ self.do_search()
+ self.on_selection_changed(self._treeview.get_selection())
+
+ def _shift_extend(self, towhere):
+ selection = self._treeview.get_selection()
+
+ if not self._shift_start:
+ model, rows = selection.get_selected_rows()
+ start = rows[0]
+
+ self._shift_start = Gtk.TreeRowReference.new(self._store, start)
+ else:
+ start = self._shift_start.get_path()
+
+ selection.unselect_all()
+ selection.select_range(start, towhere)
+
+ def _select_index(self, idx, hasctrl, hasshift):
+ path = (idx,)
+
+ if not (hasctrl or hasshift):
+ self._treeview.get_selection().unselect_all()
+
+ if hasshift:
+ self._shift_extend(path)
+ else:
+ self._shift_start = None
+
+ if not hasctrl:
+ self._treeview.get_selection().select_path(path)
+
+ self._treeview.scroll_to_cell(path, None, True, 0.5, 0)
+ self._remove_cursor()
+
+ if hasctrl or hasshift:
+ self._cursor = Gtk.TreeRowReference(self._store, path)
+
+ piter = self._store.get_iter(path)
+ self._store.row_changed(path, piter)
+
+ def _move_selection(self, howmany, hasctrl, hasshift):
+ num = self._store.iter_n_children(None)
+
+ if num == 0:
+ return True
+
+ # Test for cursor
+ path = None
+
+ if self._cursor:
+ path = self._cursor.get_path()
+ else:
+ model, rows = self._treeview.get_selection().get_selected_rows()
+
+ if len(rows) == 1:
+ path = rows[0]
+
+ if not path:
+ if howmany > 0:
+ self._select_index(0, hasctrl, hasshift)
+ else:
+ self._select_index(num - 1, hasctrl, hasshift)
+ else:
+ idx = path.get_indices()[0]
+
+ if idx + howmany < 0:
+ self._select_index(0, hasctrl, hasshift)
+ elif idx + howmany >= num:
+ self._select_index(num - 1, hasctrl, hasshift)
+ else:
+ self._select_index(idx + howmany, hasctrl, hasshift)
+
+ return True
+
+ def _direct_file(self):
+ uri = self._entry.get_text()
+ gfile = Gio.file_new_for_uri(uri)
+
+ if Gedit.utils_is_valid_location(gfile) or \
+ (os.path.isabs(uri) and gfile.query_exists()):
+ return gfile
+ else:
+ return None
+
+ def _activate(self):
+ model, rows = self._treeview.get_selection().get_selected_rows()
+ ret = True
+
+ for row in rows:
+ s = model.get_iter(row)
+ info = model.get(s, 2, 3)
+
+ if info[1] != Gio.FileType.DIRECTORY:
+ ret = ret and self._handler(info[0])
+ else:
+ text = self._entry.get_text()
+
+ for i in range(len(text) - 1, -1, -1):
+ if text[i] == os.sep:
+ break
+
+ self._entry.set_text(os.path.join(text[:i], os.path.basename(info[0].get_uri())) + os.sep)
+ self._entry.set_position(-1)
+ self._entry.grab_focus()
+ return True
+
+ if rows and ret:
+ # We destroy the popup in an idle callback to work around a crash that happens with
+ # GTK_IM_MODULE=xim. See https://bugzilla.gnome.org/show_bug.cgi?id=737711 .
+ GLib.idle_add(self.destroy)
+
+ if not rows:
+ gfile = self._direct_file()
+
+ if gfile and self._handler(gfile):
+ GLib.idle_add(self.destroy)
+ else:
+ ret = False
+ else:
+ ret = False
+
+ return ret
+
+ def toggle_cursor(self):
+ if not self._cursor:
+ return
+
+ path = self._cursor.get_path()
+ selection = self._treeview.get_selection()
+
+ if selection.path_is_selected(path):
+ selection.unselect_path(path)
+ else:
+ selection.select_path(path)
+
+ def on_key_press_event(self, widget, event):
+ move_mapping = {
+ Gdk.KEY_Down: 1,
+ Gdk.KEY_Up: -1,
+ Gdk.KEY_Page_Down: 5,
+ Gdk.KEY_Page_Up: -5
+ }
+
+ if event.keyval == Gdk.KEY_Escape:
+ self.destroy()
+ return True
+ elif event.keyval in move_mapping:
+ return self._move_selection(move_mapping[event.keyval], event.state & Gdk.ModifierType.CONTROL_MASK, event.state & Gdk.ModifierType.SHIFT_MASK)
+ elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter, Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]:
+ return self._activate()
+ elif event.keyval == Gdk.KEY_space and event.state & Gdk.ModifierType.CONTROL_MASK:
+ self.toggle_cursor()
+
+ return False
+
+ def on_row_activated(self, view, path, column):
+ self._activate()
+
+ def do_response(self, response):
+ if response != Gtk.ResponseType.ACCEPT or not self._activate():
+ self.destroy()
+
+ def do_configure_event(self, event):
+ if self.get_realized():
+ alloc = self.get_allocation()
+ self._size = (alloc.width, alloc.height)
+
+ return Gtk.Dialog.do_configure_event(self, event)
+
+ def on_selection_changed(self, selection):
+ model, rows = selection.get_selected_rows()
+
+ gfile = None
+ fname = None
+
+ if not rows:
+ gfile = self._direct_file()
+ elif len(rows) == 1:
+ gfile = model.get(model.get_iter(rows[0]), 2)[0]
+ else:
+ fname = ''
+
+ if gfile:
+ if gfile.is_native():
+ fname = xml.sax.saxutils.escape(gfile.get_path())
+ else:
+ fname = xml.sax.saxutils.escape(gfile.get_uri())
+
+ self._open_button.set_sensitive(fname is not None)
+ self._info_label.set_markup(fname or '')
+
+ def on_focus_entry(self, group, accel, keyval, modifier):
+ self._entry.grab_focus()
+
+# ex:ts=4:et:
diff --git a/plugins/quickopen/quickopen/virtualdirs.py b/plugins/quickopen/quickopen/virtualdirs.py
new file mode 100644
index 0000000..6792f9b
--- /dev/null
+++ b/plugins/quickopen/quickopen/virtualdirs.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2009 - Jesse van den Kieboom
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+from gi.repository import Gio, Gtk
+
+
+class VirtualDirectory(object):
+ def __init__(self, name):
+ self._name = name
+ self._children = []
+
+ def get_uri(self):
+ return 'virtual://' + self._name
+
+ def get_parent(self):
+ return None
+
+ def enumerate_children(self, attr, flags, callback):
+ return self._children
+
+ def append(self, child):
+ if not child.is_native():
+ return
+
+ try:
+ info = child.query_info("standard::*",
+ Gio.FileQueryInfoFlags.NONE,
+ None)
+
+ if info:
+ self._children.append((child, info))
+ except Exception as e:
+ pass
+
+
+class RecentDocumentsDirectory(VirtualDirectory):
+ def __init__(self, maxitems=200):
+ VirtualDirectory.__init__(self, 'recent')
+
+ self._maxitems = maxitems
+ self.fill()
+
+ def fill(self):
+ manager = Gtk.RecentManager.get_default()
+
+ items = manager.get_items()
+ items.sort(key=lambda a: a.get_visited(), reverse=True)
+
+ added = 0
+
+ for item in items:
+ if item.has_group('gedit'):
+ self.append(Gio.file_new_for_uri(item.get_uri()))
+ added += 1
+
+ if added >= self._maxitems:
+ break
+
+
+class CurrentDocumentsDirectory(VirtualDirectory):
+ def __init__(self, window):
+ VirtualDirectory.__init__(self, 'documents')
+
+ self.fill(window)
+
+ def fill(self, window):
+ for doc in window.get_documents():
+ location = doc.get_file().get_location()
+
+ if location:
+ self.append(location)
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/data/c.xml b/plugins/snippets/data/c.xml
new file mode 100644
index 0000000..f46ceed
--- /dev/null
+++ b/plugins/snippets/data/c.xml
@@ -0,0 +1,281 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="C">
+ <snippet id="gpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<program name>}
+ *
+ * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4:
+import pwd, os
+try:
+ return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0]
+except KeyError:
+ return '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with ${2}. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$0]]></text>
+ <tag>gpl</tag>
+ <description>GPL License</description>
+ </snippet>
+ <snippet id="lgpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<library name>}
+ *
+ * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4:
+import pwd, os
+try:
+ return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0]
+except KeyError:
+ return '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with ${2}. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$0]]></text>
+ <tag>lgpl</tag>
+ <description>LGPL License</description>
+ </snippet>
+ <snippet id="do">
+ <text><![CDATA[do
+{
+ $0
+} while ($1);]]></text>
+ <tag>do</tag>
+ <description>do .. while</description>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for (${1:i} = ${2:0}; ${1:i} < ${3:count}; ${1:i} += ${4:1})
+{
+ $0
+}]]></text>
+ <tag>for</tag>
+ <description>for loop</description>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while (${1:condition})
+{
+ $0
+}]]></text>
+ <tag>while</tag>
+ <description>while loop</description>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if (${1:condition})
+{
+ $0
+}]]></text>
+ <tag>if</tag>
+ <description>if</description>
+ </snippet>
+ <snippet id="elif">
+ <text><![CDATA[else if (${1:condition})
+{
+ $0
+}]]></text>
+ <tag>elif</tag>
+ <description>else if</description>
+ </snippet>
+ <snippet id="else">
+ <text><![CDATA[else
+{
+ $0
+}]]></text>
+ <tag>else</tag>
+ <description>else</description>
+ </snippet>
+ <snippet id="Inc">
+ <text><![CDATA[#include <${1:file}.h>
+$0]]></text>
+ <tag>Inc</tag>
+ <description>#include &lt;..&gt;</description>
+ </snippet>
+ <snippet id="inc">
+ <text><![CDATA[#include "${1:file}.h"
+$0]]></text>
+ <tag>inc</tag>
+ <description>#include ".."</description>
+ </snippet>
+ <snippet id="main">
+ <text><![CDATA[int
+main (int argc, char *argv[])
+{
+ $0
+ return 0;
+}]]></text>
+ <tag>main</tag>
+ <description>main</description>
+ </snippet>
+ <snippet id="struct">
+ <text><![CDATA[struct ${1:name}
+{
+ ${0:/* data */}
+};]]></text>
+ <tag>struct</tag>
+ <description>struct</description>
+ </snippet>
+ <snippet id="endif">
+ <text><![CDATA[#endif
+$0]]></text>
+ <description>#endif</description>
+ <accelerator><![CDATA[<Control><Alt>period]]></accelerator>
+ </snippet>
+ <snippet id="td">
+ <text><![CDATA[typedef ${1:newtype} ${2:type};
+$0]]></text>
+ <tag>td</tag>
+ <description>typedef</description>
+ </snippet>
+ <snippet id="gobject">
+ <text><![CDATA[#include "$1.h"
+$<
+global camel_str,low_str, type_str, is_str, up_str
+components = $1.split('-')
+low_str = '_'.join(components).lower()
+up_str = '_'.join(components).upper()
+type_str = '_'.join([components[0], 'TYPE'] + components[1:]).upper()
+is_str = '_'.join([components[0], 'IS'] + components[1:]).upper()
+camel_str = ''
+
+for t in components:
+ camel_str += t.capitalize()
+>
+
+typedef struct _$<[1]: return camel_str >Private
+{
+} $<[1]: return camel_str >Private;
+
+G_DEFINE_TYPE_WITH_PRIVATE ($<[1]: return camel_str >, $<[1]: return low_str >, ${2:G_TYPE_OBJECT})
+
+static void
+$<[1]: return low_str>_finalize (GObject *object)
+{
+ G_OBJECT_CLASS ($<[1]: return low_str >_parent_class)->finalize (object);
+}
+
+static void
+$<[1]: return low_str >_class_init ($<[1]: return camel_str >Class *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = $<[1]: return low_str >_finalize;
+}
+
+static void
+$<[1]: return low_str >_init ($<[1]: return camel_str> *self)
+{
+}
+
+$<[1]: return camel_str > *
+$<[1]: return low_str >_new ()
+{
+ return g_object_new ($<[1]: return type_str >, NULL);
+}]]></text>
+ <tag>gobject</tag>
+ <description>GObject template</description>
+ </snippet>
+ <snippet id="ginterface">
+ <text><![CDATA[#include "$1.h"
+$<
+global camel_str,low_str,up_str
+components = $1.split('-')
+low_str = '_'.join(components).lower()
+up_str = '_'.join(components).upper()
+camel_str = ''
+
+for t in components:
+ camel_str += t.capitalize()
+>
+G_DEFINE_INTERFACE ($<[1]: return camel_str >, $<[1]: return low_str >, ${2:G_TYPE_OBJECT})
+
+/* Default implementation */
+static const gchar *
+$<[1]: return low_str>_example_method_default ($<[1]: return camel_str > *self)
+{
+ g_return_val_if_reached (NULL);
+}
+
+static void
+$<[1]: return low_str>_init ($<[1]: return camel_str >Iface *iface)
+{
+ static gboolean initialized = FALSE;
+
+ iface->example_method = $<[1]: return low_str>_example_method_default;
+
+ if (!initialized)
+ {
+ initialized = TRUE;
+ }
+}
+
+/*
+ * This is an method example for an interface
+ */
+const gchar *
+$<[1]: return low_str>_example_method ($<[1]: return camel_str > *self)
+{
+ g_return_val_if_fail ($<[1]: return up_str> (self), NULL);
+ return $<[1]: return up_str>_GET_INTERFACE (self)->example_method (self);
+}]]></text>
+ <tag>ginterface</tag>
+ <description>GObject interface</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[#include "$1.h"
+$<
+global camel_str,low_str, type_str, is_str, up_str
+components = $1.split('-')
+low_str = '_'.join(components).lower()
+up_str = '_'.join(components).upper()
+type_str = '_'.join([components[0], 'TYPE'] + components[1:]).upper()
+camel_str = ''
+
+for t in components:
+ camel_str += t.capitalize()
+>
+
+struct _$<[1]: return camel_str >
+{
+};
+
+G_DEFINE_BOXED_TYPE ($<[1]: return camel_str >, $<[1]: return low_str >, $<[1]: return low_str >_${2:copy}, $<[1]: return low_str >_${3:free})
+
+$<[1]: return camel_str > *
+$<[1]: return low_str >_${2:copy} ($<[1]: return camel_str > *${4:boxed_name})
+{
+
+}
+
+void
+$<[1]: return low_str >_${3:free} ($<[1]: return camel_str > *${4:boxed_name})
+{
+
+}]]></text>
+ <tag>gboxed</tag>
+ <description>GBoxed template</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/chdr.xml b/plugins/snippets/data/chdr.xml
new file mode 100644
index 0000000..2ce94d7
--- /dev/null
+++ b/plugins/snippets/data/chdr.xml
@@ -0,0 +1,258 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="chdr">
+ <snippet id="once">
+ <text><![CDATA[#ifndef ${1:NAME}_H
+#define $1_H
+
+$0
+
+#endif /* $1_H */
+]]></text>
+ <description>Header Include-Guard</description>
+ <tag>once</tag>
+ </snippet>
+ <snippet id="inc">
+ <text><![CDATA[#include "${1:file}"
+$0]]></text>
+ <description>#include ".."</description>
+ <tag>inc</tag>
+ </snippet>
+ <snippet id="Inc">
+ <text><![CDATA[#include <${1:file}>
+$0]]></text>
+ <description>#include &lt;..&gt;</description>
+ <tag>Inc</tag>
+ </snippet>
+ <snippet id="namespace">
+ <text><![CDATA[namespace ${1:ns}
+{
+ $0
+};
+]]></text>
+ <description>namespace ..</description>
+ <tag>namespace</tag>
+ </snippet>
+ <snippet id="gpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<program name>}
+ *
+ * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4:
+import pwd, os
+try:
+ return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0]
+except KeyError:
+ return '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with ${2}. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$0]]></text>
+ <tag>gpl</tag>
+ <description>GPL License</description>
+ </snippet>
+ <snippet id="lgpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<library name>}
+ *
+ * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4:
+import pwd, os
+try:
+ return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0]
+except KeyError:
+ return '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with ${2}. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$0]]></text>
+ <tag>lgpl</tag>
+ <description>LGPL License</description>
+ </snippet>
+ <snippet id="td">
+ <text><![CDATA[typedef ${1:newtype} ${2:type};
+$0]]></text>
+ <tag>td</tag>
+ <description>typedef</description>
+ </snippet>
+ <snippet id="class">
+ <text><![CDATA[class ${1:name}
+{
+ public:
+ ${1:name} (${2:arguments});
+ virtual ~${1:name} ();
+
+ private:
+ ${0:/* data */}
+};]]></text>
+ <description>class ..</description>
+ <tag>class</tag>
+ </snippet>
+ <snippet id="struct">
+ <text><![CDATA[struct ${1:name}
+{
+ ${0:/* data */}
+};]]></text>
+ <tag>struct</tag>
+ <description>struct</description>
+ </snippet>
+ <snippet id="template">
+ <text><![CDATA[template <typename ${1:_InputIter}>]]></text>
+ <description>template &lt;typename ..&gt;</description>
+ <tag>template</tag>
+ </snippet>
+ <snippet id="gobject">
+ <text><![CDATA[#ifndef ${1:NAME}_H
+#define $1_H
+$<
+global camel_str, module, name, type_str
+components = $1.split('_')
+module = components[0].upper()
+name = '_'.join(components[1:]).upper()
+type_str = '_'.join([components[0], 'TYPE'] + components[1:]).upper()
+camel_str = ''
+
+for t in components:
+ camel_str += t.capitalize()
+>
+#include <${2:glib-object.h}>
+
+G_BEGIN_DECLS
+
+#define $<[1]: return type_str > ($<[1]: return $1.lower() >_get_type ())
+G_DECLARE_DERIVABLE_TYPE ($<[1]: return camel_str >, $<[1]: return $1.lower() >, $<[1]: return module >, $<[1]: return name >, ${3:GObject})
+
+struct _$<[1]: return camel_str >Class
+{
+ $3Class parent_class;
+};
+
+$<[1]: return camel_str > *$< return $1.lower()>_new (void);
+
+$0
+G_END_DECLS
+
+#endif /* $1_H */]]></text>
+ <tag>gobject</tag>
+ <description>GObject template</description>
+ </snippet>
+ <snippet id="ginterface">
+ <text><![CDATA[#ifndef ${1:NAME}_H
+#define $1_H
+
+#include <${2:glib-object.h}>
+
+G_BEGIN_DECLS
+
+$<
+global camel_str
+components = $1.split('_')
+type_str = '_'.join([components[0], 'TYPE'] + components[1:])
+is_str = '_'.join([components[0], 'IS'] + components[1:])
+camel_str = ''
+
+for t in components:
+ camel_str += t.capitalize()
+
+items = [ \
+['#define ' + type_str, '(' + $1.lower() + '_get_type ())'], \
+['#define ' + $1 + '(obj)', '(G_TYPE_CHECK_INSTANCE_CAST ((obj), ' + type_str + ', ' + camel_str + '))'], \
+['#define ' + is_str + '(obj)', '(G_TYPE_CHECK_INSTANCE_TYPE ((obj), ' + type_str + '))'], \
+['#define ' + $1 + '_GET_INTERFACE(obj)', '(G_TYPE_INSTANCE_GET_INTERFACE ((obj), ' + type_str + ', ' + camel_str + 'Iface))']
+]
+
+return align(items) >
+
+$<[1]:
+items = [ \
+['typedef struct _' + camel_str, camel_str + ';'], \
+['typedef struct _' + camel_str + 'Iface', camel_str + 'Iface;'], \
+]
+
+return align(items) >
+
+struct _$<[1]: return camel_str >Iface
+{
+ ${7:GTypeInterface} parent;
+
+ const gchar * (*example_method) ($<[1]: return camel_str > *self);
+};
+
+GType $< return $1.lower() + '_get_type' > (void) G_GNUC_CONST;
+
+const gchar *$< return $1.lower()>_example_method ($<[1]: return camel_str > *self);
+$0
+G_END_DECLS
+
+#endif /* $1_H */]]></text>
+ <tag>ginterface</tag>
+ <description>GObject interface</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[#ifndef ${1:NAME}_H
+#define $1_H
+
+#include <${2:glib-object.h}>
+
+G_BEGIN_DECLS
+
+$<
+global camel_str
+components = $1.split('_')
+type_str = '_'.join([components[0], 'TYPE'] + components[1:])
+is_str = '_'.join([components[0], 'IS'] + components[1:])
+camel_str = ''
+
+for t in components:
+ camel_str += t.capitalize()
+
+items = [ \
+['#define ' + type_str, '(' + $1.lower() + '_get_type ())'], \
+['#define ' + $1 + '(obj)', '((' + camel_str + ' *)obj)'], \
+['#define ' + $1 + '_CONST(obj)', '((' + camel_str + ' const *)obj)'], \
+]
+
+return align(items) >
+
+$<[1]:
+items = [ \
+['typedef struct _' + camel_str, camel_str + ';'], \
+]
+
+return align(items) >
+
+GType $< return $1.lower() + '_get_type' > (void) G_GNUC_CONST;
+$<[1]: return camel_str > *$< return $1.lower()>_${3:copy} ($<[1]: return camel_str > *${4:boxed_name});
+void $< return $1.lower()>_${5:free} ($<[1]: return camel_str > *${4:boxed_name});
+
+$0
+G_END_DECLS
+
+#endif /* $1_H */]]></text>
+ <tag>gboxed</tag>
+ <description>GBoxed template</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/cpp.xml b/plugins/snippets/data/cpp.xml
new file mode 100644
index 0000000..1d4c31c
--- /dev/null
+++ b/plugins/snippets/data/cpp.xml
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="cpp">
+ <snippet id="main">
+ <text><![CDATA[int main (int argc, char const* argv[])
+{
+ $0
+ return 0;
+}]]></text>
+ <description>main</description>
+ <tag>main</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for (${1:unsigned int} ${2:i} = ${3:0}; ${2:i} < ${4:count}; ${2:i} += ${5:1})
+{
+ $0
+}]]></text>
+ <description>for loop</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="beginend">
+ <text><![CDATA[${1:v}.begin(), ${1:v}.end()]]></text>
+ <description>$1.begin</description>
+ <tag>beginend</tag>
+ </snippet>
+ <snippet id="do">
+ <text><![CDATA[do
+{
+ $0
+} while ($1 );]]></text>
+ <description>do .. while</description>
+ <tag>do</tag>
+ </snippet>
+ <snippet id="endif">
+ <text><![CDATA[#endif
+$0]]></text>
+ <accelerator><![CDATA[<Control><Alt>period]]></accelerator>
+ <description>#endif</description>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if (${1:condition})
+{
+ $0
+}]]></text>
+ <description>if ..</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="inc">
+ <text><![CDATA[#include "${1:file}"
+$0]]></text>
+ <description>#include ".."</description>
+ <tag>inc</tag>
+ </snippet>
+ <snippet id="Inc">
+ <text><![CDATA[#include <${1:file}>
+$0]]></text>
+ <description>#include &lt;..&gt;</description>
+ <tag>Inc</tag>
+ </snippet>
+ <snippet id="namespace">
+ <text><![CDATA[namespace ${1:ns}
+{
+ $0
+};
+]]></text>
+ <description>namespace ..</description>
+ <tag>namespace</tag>
+ </snippet>
+ <snippet id="readfile">
+ <text><![CDATA[std::vector<uint8_t> v;
+if (FILE* fp = fopen (${1:"filename"}, "r"))
+{
+ uint8_t buf[1024];
+ while (size_t len = fread (buf, 1, sizeof (buf), fp))
+ v.insert (v.end(), buf, buf + len);
+ fclose(fp);
+}
+$0]]></text>
+ <description>Read File Into Vector</description>
+ <tag>readfile</tag>
+ </snippet>
+ <snippet id="map">
+ <text><![CDATA[std::map<${1:key}, ${2:value}> ${3:map};
+$0]]></text>
+ <description>std::map</description>
+ <tag>map</tag>
+ </snippet>
+ <snippet id="vector">
+ <text><![CDATA[std::vector<${1:char}> ${2:v};
+$0]]></text>
+ <description>std::vector</description>
+ <tag>vector</tag>
+ </snippet>
+ <snippet id="struct">
+ <text><![CDATA[struct ${1:name}
+{
+ ${0:/* data */}
+};]]></text>
+ <description>struct ..</description>
+ <tag>struct</tag>
+ </snippet>
+ <snippet id="template">
+ <text><![CDATA[template <typename ${1:_InputIter}>]]></text>
+ <description>template &lt;typename ..&gt;</description>
+ <tag>template</tag>
+ </snippet>
+ <snippet id="gpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<program name>}
+ *
+ * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4:
+import pwd, os
+try:
+ return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0]
+except KeyError:
+ return '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with ${2}. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+ $0]]></text>
+ <tag>gpl</tag>
+ <description>GPL License</description>
+ </snippet>
+ <snippet id="lgpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<library name>}
+ *
+ * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4:
+import pwd, os
+try:
+ return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0]
+except KeyError:
+ return '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with ${2}. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+ $0]]></text>
+ <tag>lgpl</tag>
+ <description>LGPL License</description>
+ </snippet>
+ <snippet id="td">
+ <text><![CDATA[typedef ${1:newtype} ${2:type};
+$0]]></text>
+ <tag>td</tag>
+ <description>typedef</description>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while ($1)
+{
+ $0
+}]]></text>
+ <tag>while</tag>
+ <description>while</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/css.xml b/plugins/snippets/data/css.xml
new file mode 100644
index 0000000..babca91
--- /dev/null
+++ b/plugins/snippets/data/css.xml
@@ -0,0 +1,557 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="CSS">
+ <snippet id="background">
+ <text><![CDATA[background-attachment: ${1:scroll/fixed};
+$0]]></text>
+ <description>background-attachment: scroll/fixed</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-1">
+ <text><![CDATA[background-color: #${1:DDD};
+$0]]></text>
+ <description>background-color: color-hex</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-2">
+ <text><![CDATA[background-color: ${1:red};
+$0]]></text>
+ <description>background-color: color-name</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-3">
+ <text><![CDATA[background-color: rgb(${1:255},${2:255},${3:255});
+$0]]></text>
+ <description>background-color: color-rgb</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-4">
+ <text><![CDATA[background: #${1:DDD} url($2) ${3:repeat/repeat-x/repeat-y/no-repeat} ${4:scroll/fixed} ${5:top letft/top center/top right/center left/center center/center right/bottom left/bottom center/bottom right/x-% y-%/x-pos y-pos};
+$0]]></text>
+ <description>background: color image repeat attachment position</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-5">
+ <text><![CDATA[background-color: transparent;
+$0]]></text>
+ <description>background-color: transparent</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-6">
+ <text><![CDATA[background-image: none;
+$0]]></text>
+ <description>background-image: none</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-7">
+ <text><![CDATA[background-image: url($1);
+$0]]></text>
+ <description>background-image: url</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-8">
+ <text><![CDATA[background-position: ${1:top letft/top center/top right/center left/center center/center right/bottom left/bottom center/bottom right/x-% y-%/x-pos y-pos};
+$0]]></text>
+ <description>background-position: position</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-9">
+ <text><![CDATA[background-repeat: ${1:repeat/repeat-x/repeat-y/no-repeat};
+$0]]></text>
+ <description>background-repeat: r/r-x/r-y/n-r</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="border">
+ <text><![CDATA[border-bottom-color: #${1:999};
+$0]]></text>
+ <description>border-bottom-color: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-1">
+ <text><![CDATA[border-bottom: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-bottom: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-2">
+ <text><![CDATA[border-bottom-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset};
+$0]]></text>
+ <description>border-bottom-style: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-3">
+ <text><![CDATA[border-bottom-width: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-bottom-width: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-4">
+ <text><![CDATA[border-color: ${1:999};
+$0]]></text>
+ <description>border-color: color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-5">
+ <text><![CDATA[border-right-color: #${1:999};
+$0]]></text>
+ <description>border-left-color: color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-6">
+ <text><![CDATA[border-left: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-left: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-7">
+ <text><![CDATA[border-left-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset};
+$0]]></text>
+ <description>border-left-style: style</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-8">
+ <text><![CDATA[border-left-width: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-left-width: size</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-9">
+ <text><![CDATA[border-right-color: #${1:999};
+$0]]></text>
+ <description>border-right-color: color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-10">
+ <text><![CDATA[border-right: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-right: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-11">
+ <text><![CDATA[border-right-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset};
+$0]]></text>
+ <description>border-right-style: style</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-12">
+ <text><![CDATA[border-right-width: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-right-width: size</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-13">
+ <text><![CDATA[border: ${1:1px} ${2:solid} #${3:999};
+$0]]></text>
+ <description>border: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-14">
+ <text><![CDATA[border-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset};
+$0]]></text>
+ <description>border-style: style</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-15">
+ <text><![CDATA[border-top-color: #${1:999};
+$0]]></text>
+ <description>border-top-color: color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-16">
+ <text><![CDATA[border-top: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-top: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-17">
+ <text><![CDATA[border-top-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset};
+$0]]></text>
+ <description>border-top-style: style</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-18">
+ <text><![CDATA[border-top-width: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-top-width: size</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-19">
+ <text><![CDATA[border-color: ${1:1px};
+$0]]></text>
+ <description>border-width: width</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="clear">
+ <text><![CDATA[clear: ${1:left/right/both/none};
+$0]]></text>
+ <description>clear: value</description>
+ <tag>clear</tag>
+ </snippet>
+ <snippet id="color">
+ <text><![CDATA[color: #${1:DDD};
+$0]]></text>
+ <description>color: color-hex</description>
+ <tag>color</tag>
+ </snippet>
+ <snippet id="color-1">
+ <text><![CDATA[color: ${1:red};
+$0]]></text>
+ <description>color: color-name</description>
+ <tag>color</tag>
+ </snippet>
+ <snippet id="color-2">
+ <text><![CDATA[color: rgb(${1:255},${2:255},${3:255});
+$0]]></text>
+ <description>color: color-rgb</description>
+ <tag>color</tag>
+ </snippet>
+ <snippet id="cursor">
+ <text><![CDATA[cursor: {$1:default/auto/crosshair/pointer/move/*-resize/text/wait/help};
+$0]]></text>
+ <description>cursor: type</description>
+ <tag>cursor</tag>
+ </snippet>
+ <snippet id="clear-1">
+ <text><![CDATA[cursor: url($1);
+$0]]></text>
+ <description>cursor: url</description>
+ <tag>clear</tag>
+ </snippet>
+ <snippet id="direction">
+ <text><![CDATA[direction: ${1:ltr|rtl};
+$0]]></text>
+ <description>direction: ltr|rtl</description>
+ <tag>direction</tag>
+ </snippet>
+ <snippet id="display">
+ <text><![CDATA[display: block;
+$0]]></text>
+ <description>display: block</description>
+ <tag>display</tag>
+ </snippet>
+ <snippet id="display-1">
+ <text><![CDATA[display: ${1:none/inline/block/list-item/run-in/compact/marker};
+$0]]></text>
+ <description>display: common-types</description>
+ <tag>display</tag>
+ </snippet>
+ <snippet id="display-2">
+ <text><![CDATA[display: inline;
+$0]]></text>
+ <description>display: inline</description>
+ <tag>display</tag>
+ </snippet>
+ <snippet id="display-3">
+ <text><![CDATA[display: ${1:table/inline-table/table-row-group/table-header-group/table-footer-group/table-row/table-column-group/table-column/table-cell/table-caption};
+$0]]></text>
+ <description>display: table-types</description>
+ <tag>display</tag>
+ </snippet>
+ <snippet id="float">
+ <text><![CDATA[float: ${1:left/right/none};
+$0]]></text>
+ <description>float: left/right/none</description>
+ <tag>float</tag>
+ </snippet>
+ <snippet id="font">
+ <text><![CDATA[font-family: ${1:Arial, "MS Trebuchet"}, ${2:sans-}serif;
+$0]]></text>
+ <description>font-family: family</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-1">
+ <text><![CDATA[font: ${1:75%} ${2:"Lucida Grande", "Trebuchet MS", Verdana,} ${3:sans-}serif;
+$0]]></text>
+ <description>font: size font</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-2">
+ <text><![CDATA[font-size: ${1:100%};
+$0]]></text>
+ <description>font-size: size</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-3">
+ <text><![CDATA[font-style: ${1:normal/italic/oblique};
+$0]]></text>
+ <description>font-style: normal/italic/oblique</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-4">
+ <text><![CDATA[font: ${1:normal/italic/oblique} ${2:normal/small-caps} ${3:normal/bold} ${4:1em/1.5em} ${5:Arial}, ${6:sans-}serif;
+$0]]></text>
+ <description>font: style variant weight size/line-height font-family</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-5">
+ <text><![CDATA[font-variant: ${1:normal/small-caps};
+$0]]></text>
+ <description>font-variant: normal/small-caps</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-6">
+ <text><![CDATA[font-weight: ${1:normal/bold};
+$0]]></text>
+ <description>font-weight: weight</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="letter">
+ <text><![CDATA[letter-spacing: $1em;
+$0]]></text>
+ <description>letter-spacing: length-em</description>
+ <tag>letter</tag>
+ </snippet>
+ <snippet id="letter-1">
+ <text><![CDATA[letter-spacing: $1px;
+$0]]></text>
+ <description>letter-spacing: length-px</description>
+ <tag>letter</tag>
+ </snippet>
+ <snippet id="list">
+ <text><![CDATA[list-style-image: url($1);
+$0]]></text>
+ <description>list-style-image: url</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-1">
+ <text><![CDATA[list-style-position: ${1:inside/outside};
+$0]]></text>
+ <description>list-style-position: pos</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-2">
+ <text><![CDATA[list-style-type: ${1:cjk-ideographic/hiragana/katakana/hiragana-iroha/katakana-iroha};
+$0]]></text>
+ <description>list-style-type: asian</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-3">
+ <text><![CDATA[list-style-type: ${1:none/disc/circle/square};
+$0]]></text>
+ <description>list-style-type: marker</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-4">
+ <text><![CDATA[list-style-type: ${1:decimal/decimal-leading-zero/zero};
+$0]]></text>
+ <description>list-style-type: numeric</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-5">
+ <text><![CDATA[list-style-type: ${1:hebrew/armenian/georgian};
+$0]]></text>
+ <description>list-style-type: other</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-6">
+ <text><![CDATA[list-style: ${1:none/disc/circle/square/decimal/zero} ${2:inside/outside} url($3);
+$0]]></text>
+ <description>list-style: type position image</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-7">
+ <text><![CDATA[list-style-type: ${1:lower-roman/uppert-roman/lower-alpha/upper-alpha/lower-greek/lower-latin/upper-latin};
+$0]]></text>
+ <description>list-style-type: roman-alpha-greek</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="margin">
+ <text><![CDATA[margin: ${1:20px};
+$0]]></text>
+ <description>margin: all</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-1">
+ <text><![CDATA[margin-bottom: ${1:20px};
+$0]]></text>
+ <description>margin-bottom: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-2">
+ <text><![CDATA[margin-left: ${1:20px};
+$0]]></text>
+ <description>margin-left: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-3">
+ <text><![CDATA[margin-right: ${1:20px};
+$0]]></text>
+ <description>margin-right: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-4">
+ <text><![CDATA[margin-top: ${1:20px};
+$0]]></text>
+ <description>margin-top: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-5">
+ <text><![CDATA[margin: ${1:20px} ${2:0px} ${3:40px} ${4:0px};
+$0]]></text>
+ <description>margin: T R B L</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-6">
+ <text><![CDATA[margin: ${1:20px} ${2:0px};
+$0]]></text>
+ <description>margin: V H</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="marker">
+ <text><![CDATA[marker-offset: auto;
+$0]]></text>
+ <description>marker-offset: auto</description>
+ <tag>marker</tag>
+ </snippet>
+ <snippet id="marker-1">
+ <text><![CDATA[marker-offset: ${1:10px};
+$0]]></text>
+ <description>marker-offset: length</description>
+ <tag>marker</tag>
+ </snippet>
+ <snippet id="overflow">
+ <text><![CDATA[overflow: ${1:visible/hidden/scroll/auto};
+$0]]></text>
+ <description>overflow: type</description>
+ <tag>overflow</tag>
+ </snippet>
+ <snippet id="padding">
+ <text><![CDATA[padding: ${1:20px};
+$0]]></text>
+ <description>padding: all</description>
+ <tag>padding</tag>
+ </snippet>
+ <snippet id="margin-7">
+ <text><![CDATA[padding-bottom: ${1:20px};
+$0]]></text>
+ <description>padding-bottom: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-8">
+ <text><![CDATA[padding-left: ${1:20px};
+$0]]></text>
+ <description>padding-left: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-9">
+ <text><![CDATA[padding-right: ${1:20px};
+$0]]></text>
+ <description>padding-right: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-10">
+ <text><![CDATA[padding-top: ${1:20px};
+$0]]></text>
+ <description>padding-top: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="padding-1">
+ <text><![CDATA[padding: ${1:20px} ${2:0px} ${3:40px} ${4:0px};
+$0]]></text>
+ <description>padding: T R B L</description>
+ <tag>padding</tag>
+ </snippet>
+ <snippet id="padding-2">
+ <text><![CDATA[padding: ${1:20px} ${2:0px};
+$0]]></text>
+ <description>padding: V H</description>
+ <tag>padding</tag>
+ </snippet>
+ <snippet id="position">
+ <text><![CDATA[position: ${1:static/relative/absolute/fixed};
+$0]]></text>
+ <description>position: type</description>
+ <tag>position</tag>
+ </snippet>
+ <snippet id="{">
+ <text><![CDATA[{
+ /* $1 */
+ $0
+]]></text>
+ <description>properties { }</description>
+ <tag>{</tag>
+ </snippet>
+ <snippet id="text">
+ <text><![CDATA[text-align: ${1:left/right/center/justify};
+$0]]></text>
+ <description>text-align: left/center/right</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-1">
+ <text><![CDATA[text-decoration: ${1:none/underline/overline/line-through/blink};
+$0]]></text>
+ <description>text-decoration: none/underline/overline/line-through/blink</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-2">
+ <text><![CDATA[text-indent: ${1:10p}x;
+$0]]></text>
+ <description>text-indent: length</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-3">
+ <text><![CDATA[text-shadow: #${1:DDD} ${2:10px} ${3:10px} ${4:2px};
+$0]]></text>
+ <description>text-shadow: color-hex x y blur</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-4">
+ <text><![CDATA[text-shadow: rgb(${1:255},${2:255},${3:255}) ${4:10px} ${5:10px} ${6:2px};
+$0]]></text>
+ <description>text-shadow: color-rgb x y blur</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-5">
+ <text><![CDATA[text-shadow: none;
+$0]]></text>
+ <description>text-shadow: none</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-6">
+ <text><![CDATA[text-transform: ${1:capitalize/uppercase/lowercase};
+$0]]></text>
+ <description>text-transform: capitalize/upper/lower</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-7">
+ <text><![CDATA[text-transform: none;
+$0]]></text>
+ <description>text-transform: none</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="vertical">
+ <text><![CDATA[vertical-align: ${1:baseline/sub/super/top/text-top/middle/bottom/text-bottom/length/%};
+$0]]></text>
+ <description>vertical-align: type</description>
+ <tag>vertical</tag>
+ </snippet>
+ <snippet id="visibility">
+ <text><![CDATA[visibility: ${1:visible/hidden/collapse};
+$0]]></text>
+ <description>visibility: type</description>
+ <tag>visibility</tag>
+ </snippet>
+ <snippet id="white">
+ <text><![CDATA[white-space: ${1:normal/pre/nowrap};
+$0]]></text>
+ <description>white-space: normal/pre/nowrap</description>
+ <tag>white</tag>
+ </snippet>
+ <snippet id="word">
+ <text><![CDATA[word-spacing: ${1:10px};
+$0]]></text>
+ <description>word-spacing: length</description>
+ <tag>word</tag>
+ </snippet>
+ <snippet id="word-1">
+ <text><![CDATA[word-spacing: normal;
+$0]]></text>
+ <description>word-spacing: normal</description>
+ <tag>word</tag>
+ </snippet>
+ <snippet id="z">
+ <text><![CDATA[z-index: $1;
+$0]]></text>
+ <description>z-index: index</description>
+ <tag>z</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/docbook.xml b/plugins/snippets/data/docbook.xml
new file mode 100644
index 0000000..d2a07de
--- /dev/null
+++ b/plugins/snippets/data/docbook.xml
@@ -0,0 +1,2645 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!--
+ DocBook 4.5 snippets according to DocBook: The Definitive Guide (v2.0.17)
+ Copyright (C) 2012, 2013 Jaromir Hradilek
+
+ Home Page: https://github.com/jhradilek/gedit-snippets
+ Last Change: 16 February 2013
+-->
+<snippets language="docbook">
+ <!-- Core DocBook Snippets: -->
+ <snippet id="abbrev">
+ <text><![CDATA[<abbrev>${1}</abbrev>]]></text>
+ <tag>abbrev</tag>
+ <description>abbrev</description>
+ </snippet>
+ <snippet id="abstract">
+ <text><![CDATA[<abstract>
+ ${1}
+</abstract>]]></text>
+ <tag>abstract</tag>
+ <description>abstract</description>
+ </snippet>
+ <snippet id="accel">
+ <text><![CDATA[<accel>${1}</accel>]]></text>
+ <tag>accel</tag>
+ <description>accel</description>
+ </snippet>
+ <snippet id="ackno">
+ <text><![CDATA[<ackno>
+ ${1}
+</ackno>]]></text>
+ <tag>ackno</tag>
+ <description>ackno</description>
+ </snippet>
+ <snippet id="acronym">
+ <text><![CDATA[<acronym>${1}</acronym>]]></text>
+ <tag>acronym</tag>
+ <description>acronym</description>
+ </snippet>
+ <snippet id="action">
+ <text><![CDATA[<action>${1}</action>]]></text>
+ <tag>action</tag>
+ <description>action</description>
+ </snippet>
+ <snippet id="address">
+ <text><![CDATA[<address>
+ ${1}
+</address>]]></text>
+ <tag>address</tag>
+ <description>address</description>
+ </snippet>
+ <snippet id="affiliation">
+ <text><![CDATA[<affiliation>
+ ${1}
+</affiliation>]]></text>
+ <tag>affiliation</tag>
+ <description>affiliation</description>
+ </snippet>
+ <snippet id="alt">
+ <text><![CDATA[<alt>${1}</alt>]]></text>
+ <tag>alt</tag>
+ <description>alt</description>
+ </snippet>
+ <snippet id="anchor">
+ <text><![CDATA[<anchor id="${1}" />]]></text>
+ <tag>anchor</tag>
+ <description>anchor</description>
+ </snippet>
+ <snippet id="answer">
+ <text><![CDATA[<answer>
+ ${1}
+</answer>]]></text>
+ <tag>answer</tag>
+ <description>answer</description>
+ </snippet>
+ <snippet id="appendix">
+ <text><![CDATA[<appendix id="${1}">
+ ${2}
+</appendix>]]></text>
+ <tag>appendix</tag>
+ <description>appendix</description>
+ </snippet>
+ <snippet id="appendixinfo">
+ <text><![CDATA[<appendixinfo>
+ ${1}
+</appendixinfo>]]></text>
+ <tag>appendixinfo</tag>
+ <description>appendixinfo</description>
+ </snippet>
+ <snippet id="application">
+ <text><![CDATA[<application>${1}</application>]]></text>
+ <tag>application</tag>
+ <description>application</description>
+ </snippet>
+ <snippet id="area">
+ <text><![CDATA[<area id="${1}" coords="${2}" />]]></text>
+ <tag>area</tag>
+ <description>area</description>
+ </snippet>
+ <snippet id="areaset">
+ <text><![CDATA[<areaset id="${1}" coords="${2}">
+ ${3}
+</areaset>]]></text>
+ <tag>areaset</tag>
+ <description>areaset</description>
+ </snippet>
+ <snippet id="areaspec">
+ <text><![CDATA[<areaspec units="${1}">
+ ${2}
+</areaspec>]]></text>
+ <tag>areaspec</tag>
+ <description>areaspec</description>
+ </snippet>
+ <snippet id="arg">
+ <text><![CDATA[<arg>${1}</arg>]]></text>
+ <tag>arg</tag>
+ <description>arg</description>
+ </snippet>
+ <snippet id="article">
+ <text><![CDATA[<article id="${1}">
+ ${2}
+</article>]]></text>
+ <tag>article</tag>
+ <description>article</description>
+ </snippet>
+ <snippet id="articleinfo">
+ <text><![CDATA[<articleinfo>
+ ${1}
+</articleinfo>]]></text>
+ <tag>articleinfo</tag>
+ <description>articleinfo</description>
+ </snippet>
+ <snippet id="artpagenums">
+ <text><![CDATA[<artpagenums>${1}</artpagenums>]]></text>
+ <tag>artpagenums</tag>
+ <description>artpagenums</description>
+ </snippet>
+ <snippet id="attribution">
+ <text><![CDATA[<attribution>${1}</attribution>]]></text>
+ <tag>attribution</tag>
+ <description>attribution</description>
+ </snippet>
+ <snippet id="audiodata">
+ <text><![CDATA[<audiodata fileref="${1}" />]]></text>
+ <tag>audiodata</tag>
+ <description>audiodata</description>
+ </snippet>
+ <snippet id="audioobject">
+ <text><![CDATA[<audioobject>
+ ${1}
+</audioobject>]]></text>
+ <tag>audioobject</tag>
+ <description>audioobject</description>
+ </snippet>
+ <snippet id="author">
+ <text><![CDATA[<author>
+ ${1}
+</author>]]></text>
+ <tag>author</tag>
+ <description>author</description>
+ </snippet>
+ <snippet id="authorblurb">
+ <text><![CDATA[<authorblurb>
+ ${1}
+</authorblurb>]]></text>
+ <tag>authorblurb</tag>
+ <description>authorblurb</description>
+ </snippet>
+ <snippet id="authorgroup">
+ <text><![CDATA[<authorgroup>
+ ${1}
+</authorgroup>]]></text>
+ <tag>authorgroup</tag>
+ <description>authorgroup</description>
+ </snippet>
+ <snippet id="authorinitials">
+ <text><![CDATA[<authorinitials>${1}</authorinitials>]]></text>
+ <tag>authorinitials</tag>
+ <description>authorinitials</description>
+ </snippet>
+ <snippet id="beginpage">
+ <text><![CDATA[<beginpage pagenum="${1}" />]]></text>
+ <tag>beginpage</tag>
+ <description>beginpage</description>
+ </snippet>
+ <snippet id="bibliocoverage">
+ <text><![CDATA[<bibliocoverage>
+ ${1}
+</bibliocoverage>]]></text>
+ <tag>bibliocoverage</tag>
+ <description>bibliocoverage</description>
+ </snippet>
+ <snippet id="bibliodiv">
+ <text><![CDATA[<bibliodiv>
+ ${1}
+</bibliodiv>]]></text>
+ <tag>bibliodiv</tag>
+ <description>bibliodiv</description>
+ </snippet>
+ <snippet id="biblioentry">
+ <text><![CDATA[<biblioentry>
+ ${1}
+</biblioentry>]]></text>
+ <tag>biblioentry</tag>
+ <description>biblioentry</description>
+ </snippet>
+ <snippet id="bibliography">
+ <text><![CDATA[<bibliography>
+ ${1}
+</bibliography>]]></text>
+ <tag>bibliography</tag>
+ <description>bibliography</description>
+ </snippet>
+ <snippet id="bibliographyinfo">
+ <text><![CDATA[<bibliographyinfo>
+ ${1}
+</bibliographyinfo>]]></text>
+ <tag>bibliographyinfo</tag>
+ <description>bibliographyinfo</description>
+ </snippet>
+ <snippet id="biblioid">
+ <text><![CDATA[<biblioid class="${1:isbn}">${2}</biblioid>]]></text>
+ <tag>biblioid</tag>
+ <description>biblioid</description>
+ </snippet>
+ <snippet id="bibliolist">
+ <text><![CDATA[<bibliolist>
+ ${1}
+</bibliolist>]]></text>
+ <tag>bibliolist</tag>
+ <description>bibliolist</description>
+ </snippet>
+ <snippet id="bibliomisc">
+ <text><![CDATA[<bibliomisc>
+ ${1}
+</bibliomisc>]]></text>
+ <tag>bibliomisc</tag>
+ <description>bibliomisc</description>
+ </snippet>
+ <snippet id="bibliomixed">
+ <text><![CDATA[<bibliomixed>
+ ${1}
+</bibliomixed>]]></text>
+ <tag>bibliomixed</tag>
+ <description>bibliomixed</description>
+ </snippet>
+ <snippet id="bibliomset">
+ <text><![CDATA[<bibliomset relation="${1}">
+ ${2}
+</bibliomset>]]></text>
+ <tag>bibliomset</tag>
+ <description>bibliomset</description>
+ </snippet>
+ <snippet id="biblioref">
+ <text><![CDATA[<biblioref linkend="${1}" />]]></text>
+ <tag>biblioref</tag>
+ <description>biblioref</description>
+ </snippet>
+ <snippet id="bibliorelation">
+ <text><![CDATA[<bibliorelation type="${1}" class="${2}">${3}</bibliorelation>]]></text>
+ <tag>bibliorelation</tag>
+ <description>bibliorelation</description>
+ </snippet>
+ <snippet id="biblioset">
+ <text><![CDATA[<biblioset relation="${1}">
+ ${2}
+</biblioset>]]></text>
+ <tag>biblioset</tag>
+ <description>biblioset</description>
+ </snippet>
+ <snippet id="bibliosource">
+ <text><![CDATA[<bibliosource class="${1:isbn}">${2}</bibliosource>]]></text>
+ <tag>bibliosource</tag>
+ <description>bibliosource</description>
+ </snippet>
+ <snippet id="blockinfo">
+ <text><![CDATA[<blockinfo>
+ ${1}
+</blockinfo>]]></text>
+ <tag>blockinfo</tag>
+ <description>blockinfo</description>
+ </snippet>
+ <snippet id="blockquote">
+ <text><![CDATA[<blockquote>
+ ${1}
+</blockquote>]]></text>
+ <tag>blockquote</tag>
+ <description>blockquote</description>
+ </snippet>
+ <snippet id="book">
+ <text><![CDATA[<book id="${1}">
+ ${2}
+</book>]]></text>
+ <tag>book</tag>
+ <description>book</description>
+ </snippet>
+ <snippet id="bookinfo">
+ <text><![CDATA[<bookinfo>
+ ${1}
+</bookinfo>]]></text>
+ <tag>bookinfo</tag>
+ <description>bookinfo</description>
+ </snippet>
+ <snippet id="bridgehead">
+ <text><![CDATA[<bridgehead id="${1}">${2}</bridgehead>]]></text>
+ <tag>bridgehead</tag>
+ <description>bridgehead</description>
+ </snippet>
+ <snippet id="callout">
+ <text><![CDATA[<callout arearefs="${1}">
+ ${2}
+</callout>]]></text>
+ <tag>callout</tag>
+ <description>callout</description>
+ </snippet>
+ <snippet id="calloutlist">
+ <text><![CDATA[<calloutlist>
+ ${1}
+</calloutlist>]]></text>
+ <tag>calloutlist</tag>
+ <description>calloutlist</description>
+ </snippet>
+ <snippet id="caption">
+ <text><![CDATA[<caption>
+ ${1}
+</caption>]]></text>
+ <tag>caption</tag>
+ <description>caption</description>
+ </snippet>
+ <snippet id="caution">
+ <text><![CDATA[<caution>
+ ${1}
+</caution>]]></text>
+ <tag>caution</tag>
+ <description>caution</description>
+ </snippet>
+ <snippet id="chapter">
+ <text><![CDATA[<chapter id="${1}">
+ ${2}
+</chapter>]]></text>
+ <tag>chapter</tag>
+ <description>chapter</description>
+ </snippet>
+ <snippet id="chapterinfo">
+ <text><![CDATA[<chapterinfo>
+ ${1}
+</chapterinfo>]]></text>
+ <tag>chapterinfo</tag>
+ <description>chapterinfo</description>
+ </snippet>
+ <snippet id="citation">
+ <text><![CDATA[<citation>${1}</citation>]]></text>
+ <tag>citation</tag>
+ <description>citation</description>
+ </snippet>
+ <snippet id="citebiblioid">
+ <text><![CDATA[<citebiblioid class="${1:isbn}">${2}</citebiblioid>]]></text>
+ <tag>citebiblioid</tag>
+ <description>citebiblioid</description>
+ </snippet>
+ <snippet id="citerefentry">
+ <text><![CDATA[<citerefentry>${1}</citerefentry>]]></text>
+ <tag>citerefentry</tag>
+ <description>citerefentry</description>
+ </snippet>
+ <snippet id="citetitle">
+ <text><![CDATA[<citetitle pubwork="${1:book}">${2}</citetitle>]]></text>
+ <tag>citetitle</tag>
+ <description>citetitle</description>
+ </snippet>
+ <snippet id="city">
+ <text><![CDATA[<city>${1}</city>]]></text>
+ <tag>city</tag>
+ <description>city</description>
+ </snippet>
+ <snippet id="classname">
+ <text><![CDATA[<classname>${1}</classname>]]></text>
+ <tag>classname</tag>
+ <description>classname</description>
+ </snippet>
+ <snippet id="classsynopsis">
+ <text><![CDATA[<classsynopsis class="${1}" language="${2}">
+ ${3}
+</classsynopsis>]]></text>
+ <tag>classsynopsis</tag>
+ <description>classsynopsis</description>
+ </snippet>
+ <snippet id="classsynopsisinfo">
+ <text><![CDATA[<classsynopsisinfo>
+ ${1}
+</classsynopsisinfo>]]></text>
+ <tag>classsynopsisinfo</tag>
+ <description>classsynopsisinfo</description>
+ </snippet>
+ <snippet id="cmdsynopsis">
+ <text><![CDATA[<cmdsynopsis>
+ ${1}
+</cmdsynopsis>]]></text>
+ <tag>cmdsynopsis</tag>
+ <description>cmdsynopsis</description>
+ </snippet>
+ <snippet id="co">
+ <text><![CDATA[<co label="${1}" linkends="${2}" />]]></text>
+ <tag>co</tag>
+ <description>co</description>
+ </snippet>
+ <snippet id="code">
+ <text><![CDATA[<code language="${1}">${2}</code>]]></text>
+ <tag>code</tag>
+ <description>code</description>
+ </snippet>
+ <snippet id="col">
+ <text><![CDATA[<col>
+ ${1}
+</col>]]></text>
+ <tag>col</tag>
+ <description>col</description>
+ </snippet>
+ <snippet id="colgroup">
+ <text><![CDATA[<colgroup>
+ ${1}
+</colgroup>]]></text>
+ <tag>colgroup</tag>
+ <description>colgroup</description>
+ </snippet>
+ <snippet id="collab">
+ <text><![CDATA[<collab>
+ ${1}
+</collab>]]></text>
+ <tag>collab</tag>
+ <description>collab</description>
+ </snippet>
+ <snippet id="collabname">
+ <text><![CDATA[<collabname>${1}</collabname>]]></text>
+ <tag>collabname</tag>
+ <description>collabname</description>
+ </snippet>
+ <snippet id="colophon">
+ <text><![CDATA[<colophon>
+ ${1}
+</colophon>]]></text>
+ <tag>colophon</tag>
+ <description>colophon</description>
+ </snippet>
+ <snippet id="colspec">
+ <text><![CDATA[<colspec colname="${1}" colnum="${2}" colwidth="${3}" />]]></text>
+ <tag>colspec</tag>
+ <description>colspec</description>
+ </snippet>
+ <snippet id="command">
+ <text><![CDATA[<command>${1}</command>]]></text>
+ <tag>command</tag>
+ <description>command</description>
+ </snippet>
+ <snippet id="computeroutput">
+ <text><![CDATA[<computeroutput>${1}</computeroutput>]]></text>
+ <tag>computeroutput</tag>
+ <description>computeroutput</description>
+ </snippet>
+ <snippet id="confdates">
+ <text><![CDATA[<confdates>${1}</confdates>]]></text>
+ <tag>confdates</tag>
+ <description>confdates</description>
+ </snippet>
+ <snippet id="confgroup">
+ <text><![CDATA[<confgroup>
+ ${1}
+</confgroup>]]></text>
+ <tag>confgroup</tag>
+ <description>confgroup</description>
+ </snippet>
+ <snippet id="confnum">
+ <text><![CDATA[<confnum>${1}</confnum>]]></text>
+ <tag>confnum</tag>
+ <description>confnum</description>
+ </snippet>
+ <snippet id="confsponsor">
+ <text><![CDATA[<confsponsor>${1}</confsponsor>]]></text>
+ <tag>confsponsor</tag>
+ <description>confsponsor</description>
+ </snippet>
+ <snippet id="conftitle">
+ <text><![CDATA[<conftitle>${1}</conftitle>]]></text>
+ <tag>conftitle</tag>
+ <description>conftitle</description>
+ </snippet>
+ <snippet id="constant">
+ <text><![CDATA[<constant>${1}</constant>]]></text>
+ <tag>constant</tag>
+ <description>constant</description>
+ </snippet>
+ <snippet id="constructorsynopsis">
+ <text><![CDATA[<constructorsynopsis language="${1}">
+ ${2}
+</constructorsynopsis>]]></text>
+ <tag>constructorsynopsis</tag>
+ <description>constructorsynopsis</description>
+ </snippet>
+ <snippet id="contractnum">
+ <text><![CDATA[<contractnum>${1}</contractnum>]]></text>
+ <tag>contractnum</tag>
+ <description>contractnum</description>
+ </snippet>
+ <snippet id="contractsponsor">
+ <text><![CDATA[<contractsponsor>${1}</contractsponsor>]]></text>
+ <tag>contractsponsor</tag>
+ <description>contractsponsor</description>
+ </snippet>
+ <snippet id="contrib">
+ <text><![CDATA[<contrib>${1}</contrib>]]></text>
+ <tag>contrib</tag>
+ <description>contrib</description>
+ </snippet>
+ <snippet id="copyright">
+ <text><![CDATA[<copyright>
+ ${1}
+</copyright>]]></text>
+ <tag>copyright</tag>
+ <description>copyright</description>
+ </snippet>
+ <snippet id="coref">
+ <text><![CDATA[<coref label="${1}" linkend="${1}" />]]></text>
+ <tag>coref</tag>
+ <description>coref</description>
+ </snippet>
+ <snippet id="corpauthor">
+ <text><![CDATA[<corpauthor>${1}</corpauthor>]]></text>
+ <tag>corpauthor</tag>
+ <description>corpauthor</description>
+ </snippet>
+ <snippet id="corpcredit">
+ <text><![CDATA[<corpcredit>${1}</corpcredit>]]></text>
+ <tag>corpcredit</tag>
+ <description>corpcredit</description>
+ </snippet>
+ <snippet id="corpname">
+ <text><![CDATA[<corpname>${1}</corpname>]]></text>
+ <tag>corpname</tag>
+ <description>corpname</description>
+ </snippet>
+ <snippet id="country">
+ <text><![CDATA[<country>${1}</country>]]></text>
+ <tag>country</tag>
+ <description>country</description>
+ </snippet>
+ <snippet id="database">
+ <text><![CDATA[<database class="${1}">${2}</database>]]></text>
+ <tag>database</tag>
+ <description>database</description>
+ </snippet>
+ <snippet id="date">
+ <text><![CDATA[<date>${1}</date>]]></text>
+ <tag>date</tag>
+ <description>date</description>
+ </snippet>
+ <snippet id="dedication">
+ <text><![CDATA[<dedication>
+ ${1}
+</dedication>]]></text>
+ <tag>dedication</tag>
+ <description>dedication</description>
+ </snippet>
+ <snippet id="destructorsynopsis">
+ <text><![CDATA[<destructorsynopsis language="${1}">
+ ${2}
+</destructorsynopsis>]]></text>
+ <tag>destructorsynopsis</tag>
+ <description>destructorsynopsis</description>
+ </snippet>
+ <snippet id="edition">
+ <text><![CDATA[<edition>${1}</edition>]]></text>
+ <tag>edition</tag>
+ <description>edition</description>
+ </snippet>
+ <snippet id="editor">
+ <text><![CDATA[<editor>
+ ${1}
+</editor>]]></text>
+ <tag>editor</tag>
+ <description>editor</description>
+ </snippet>
+ <snippet id="email">
+ <text><![CDATA[<email>${1}</email>]]></text>
+ <tag>email</tag>
+ <description>email</description>
+ </snippet>
+ <snippet id="emphasis">
+ <text><![CDATA[<emphasis>${1}</emphasis>]]></text>
+ <tag>emphasis</tag>
+ <description>emphasis</description>
+ </snippet>
+ <snippet id="entry">
+ <text><![CDATA[<entry>${1}</entry>]]></text>
+ <tag>entry</tag>
+ <description>entry</description>
+ </snippet>
+ <snippet id="entrytbl">
+ <text><![CDATA[<entrytbl cols="${1}">
+ ${2}
+</entrytbl>]]></text>
+ <tag>entrytbl</tag>
+ <description>entrytbl</description>
+ </snippet>
+ <snippet id="envar">
+ <text><![CDATA[<envar>${1}</envar>]]></text>
+ <tag>envar</tag>
+ <description>envar</description>
+ </snippet>
+ <snippet id="epigraph">
+ <text><![CDATA[<epigraph>
+ ${1}
+</epigraph>]]></text>
+ <tag>epigraph</tag>
+ <description>epigraph</description>
+ </snippet>
+ <snippet id="equation">
+ <text><![CDATA[<equation>
+ ${1}
+</equation>]]></text>
+ <tag>equation</tag>
+ <description>equation</description>
+ </snippet>
+ <snippet id="errorcode">
+ <text><![CDATA[<errorcode>${1}</errorcode>]]></text>
+ <tag>errorcode</tag>
+ <description>errorcode</description>
+ </snippet>
+ <snippet id="errorname">
+ <text><![CDATA[<errorname>${1}</errorname>]]></text>
+ <tag>errorname</tag>
+ <description>errorname</description>
+ </snippet>
+ <snippet id="errortext">
+ <text><![CDATA[<errortext>${1}</errortext>]]></text>
+ <tag>errortext</tag>
+ <description>errortext</description>
+ </snippet>
+ <snippet id="errortype">
+ <text><![CDATA[<errortype>${1}</errortype>]]></text>
+ <tag>errortype</tag>
+ <description>errortype</description>
+ </snippet>
+ <snippet id="example">
+ <text><![CDATA[<example id="${1}">
+ ${2}
+</example>]]></text>
+ <tag>example</tag>
+ <description>example</description>
+ </snippet>
+ <snippet id="exceptionname">
+ <text><![CDATA[<exceptionname>${1}</exceptionname>]]></text>
+ <tag>exceptionname</tag>
+ <description>exceptionname</description>
+ </snippet>
+ <snippet id="fax">
+ <text><![CDATA[<fax>${1}</fax>]]></text>
+ <tag>fax</tag>
+ <description>fax</description>
+ </snippet>
+ <snippet id="fieldsynopsis">
+ <text><![CDATA[<fieldsynopsis language="${1}">
+ ${2}
+</fieldsynopsis>]]></text>
+ <tag>fieldsynopsis</tag>
+ <description>fieldsynopsis</description>
+ </snippet>
+ <snippet id="figure">
+ <text><![CDATA[<figure id="${1}">
+ ${2}
+</figure>]]></text>
+ <tag>figure</tag>
+ <description>figure</description>
+ </snippet>
+ <snippet id="filename">
+ <text><![CDATA[<filename>${1}</filename>]]></text>
+ <tag>filename</tag>
+ <description>filename</description>
+ </snippet>
+ <snippet id="firstname">
+ <text><![CDATA[<firstname>${1}</firstname>]]></text>
+ <tag>firstname</tag>
+ <description>firstname</description>
+ </snippet>
+ <snippet id="firstterm">
+ <text><![CDATA[<firstterm>${1}</firstterm>]]></text>
+ <tag>firstterm</tag>
+ <description>firstterm</description>
+ </snippet>
+ <snippet id="footnote">
+ <text><![CDATA[<footnote>
+ ${1}
+</footnote>]]></text>
+ <tag>footnote</tag>
+ <description>footnote</description>
+ </snippet>
+ <snippet id="footnoteref">
+ <text><![CDATA[<footnoteref linkend="${1}" />]]></text>
+ <tag>footnoteref</tag>
+ <description>footnoteref</description>
+ </snippet>
+ <snippet id="foreignphrase">
+ <text><![CDATA[<foreignphrase>${1}</foreignphrase>]]></text>
+ <tag>foreignphrase</tag>
+ <description>foreignphrase</description>
+ </snippet>
+ <snippet id="formalpara">
+ <text><![CDATA[<formalpara>
+ ${1}
+</formalpara>]]></text>
+ <tag>formalpara</tag>
+ <description>formalpara</description>
+ </snippet>
+ <snippet id="funcdef">
+ <text><![CDATA[<funcdef>${1}</funcdef>]]></text>
+ <tag>funcdef</tag>
+ <description>funcdef</description>
+ </snippet>
+ <snippet id="funcparams">
+ <text><![CDATA[<funcparams>${1}</funcparams>]]></text>
+ <tag>funcparams</tag>
+ <description>funcparams</description>
+ </snippet>
+ <snippet id="funcprototype">
+ <text><![CDATA[<funcprototype>
+ ${1}
+</funcprototype>]]></text>
+ <tag>funcprototype</tag>
+ <description>funcprototype</description>
+ </snippet>
+ <snippet id="funcsynopsis">
+ <text><![CDATA[<funcsynopsis>
+ ${1}
+</funcsynopsis>]]></text>
+ <tag>funcsynopsis</tag>
+ <description>funcsynopsis</description>
+ </snippet>
+ <snippet id="funcsynopsisinfo">
+ <text><![CDATA[<funcsynopsisinfo>
+ ${1}
+</funcsynopsisinfo>]]></text>
+ <tag>funcsynopsisinfo</tag>
+ <description>funcsynopsisinfo</description>
+ </snippet>
+ <snippet id="function">
+ <text><![CDATA[<function>${1}</function>]]></text>
+ <tag>function</tag>
+ <description>function</description>
+ </snippet>
+ <snippet id="glossary">
+ <text><![CDATA[<glossary id="${1}">
+ ${2}
+</glossary>]]></text>
+ <tag>glossary</tag>
+ <description>glossary</description>
+ </snippet>
+ <snippet id="glossaryinfo">
+ <text><![CDATA[<glossaryinfo>
+ ${1}
+</glossaryinfo>]]></text>
+ <tag>glossaryinfo</tag>
+ <description>glossaryinfo</description>
+ </snippet>
+ <snippet id="glossdef">
+ <text><![CDATA[<glossdef>
+ ${1}
+</glossdef>]]></text>
+ <tag>glossdef</tag>
+ <description>glossdef</description>
+ </snippet>
+ <snippet id="glossdiv">
+ <text><![CDATA[<glossdiv>
+ ${1}
+</glossdiv>]]></text>
+ <tag>glossdiv</tag>
+ <description>glossdiv</description>
+ </snippet>
+ <snippet id="glossentry">
+ <text><![CDATA[<glossentry>
+ ${1}
+</glossentry>]]></text>
+ <tag>glossentry</tag>
+ <description>glossentry</description>
+ </snippet>
+ <snippet id="glosslist">
+ <text><![CDATA[<glosslist>
+ ${1}
+</glosslist>]]></text>
+ <tag>glosslist</tag>
+ <description>glosslist</description>
+ </snippet>
+ <snippet id="glosssee">
+ <text><![CDATA[<glosssee otherterm="${1}">${2}</glosssee>]]></text>
+ <tag>glosssee</tag>
+ <description>glosssee</description>
+ </snippet>
+ <snippet id="glossseealso">
+ <text><![CDATA[<glossseealso otherterm="${1}">${2}</glossseealso>]]></text>
+ <tag>glossseealso</tag>
+ <description>glossseealso</description>
+ </snippet>
+ <snippet id="glossterm">
+ <text><![CDATA[<glossterm>${1}</glossterm>]]></text>
+ <tag>glossterm</tag>
+ <description>glossterm</description>
+ </snippet>
+ <snippet id="graphic">
+ <text><![CDATA[<graphic fileref="${1}" />]]></text>
+ <tag>graphic</tag>
+ <description>graphic</description>
+ </snippet>
+ <snippet id="graphicco">
+ <text><![CDATA[<graphicco>
+ ${1}
+</graphicco>]]></text>
+ <tag>graphicco</tag>
+ <description>graphicco</description>
+ </snippet>
+ <snippet id="group">
+ <text><![CDATA[<group>
+ ${1}
+</group>]]></text>
+ <tag>group</tag>
+ <description>group</description>
+ </snippet>
+ <snippet id="guibutton">
+ <text><![CDATA[<guibutton>${1}</guibutton>]]></text>
+ <tag>guibutton</tag>
+ <description>guibutton</description>
+ </snippet>
+ <snippet id="guiicon">
+ <text><![CDATA[<guiicon>${1}</guiicon>]]></text>
+ <tag>guiicon</tag>
+ <description>guiicon</description>
+ </snippet>
+ <snippet id="guilabel">
+ <text><![CDATA[<guilabel>${1}</guilabel>]]></text>
+ <tag>guilabel</tag>
+ <description>guilabel</description>
+ </snippet>
+ <snippet id="guimenu">
+ <text><![CDATA[<guimenu>${1}</guimenu>]]></text>
+ <tag>guimenu</tag>
+ <description>guimenu</description>
+ </snippet>
+ <snippet id="guimenuitem">
+ <text><![CDATA[<guimenuitem>${1}</guimenuitem>]]></text>
+ <tag>guimenuitem</tag>
+ <description>guimenuitem</description>
+ </snippet>
+ <snippet id="guisubmenu">
+ <text><![CDATA[<guisubmenu>${1}</guisubmenu>]]></text>
+ <tag>guisubmenu</tag>
+ <description>guisubmenu</description>
+ </snippet>
+ <snippet id="hardware">
+ <text><![CDATA[<hardware>${1}</hardware>]]></text>
+ <tag>hardware</tag>
+ <description>hardware</description>
+ </snippet>
+ <snippet id="highlights">
+ <text><![CDATA[<highlights>
+ ${1}
+</highlights>]]></text>
+ <tag>highlights</tag>
+ <description>highlights</description>
+ </snippet>
+ <snippet id="holder">
+ <text><![CDATA[<holder>${1}</holder>]]></text>
+ <tag>holder</tag>
+ <description>holder</description>
+ </snippet>
+ <snippet id="honorific">
+ <text><![CDATA[<honorific>${1}</honorific>]]></text>
+ <tag>honorific</tag>
+ <description>honorific</description>
+ </snippet>
+ <snippet id="imagedata">
+ <text><![CDATA[<imagedata fileref="${1}" format="${2:PNG}" scalefit="${3:0}" />]]></text>
+ <tag>imagedata</tag>
+ <description>imagedata</description>
+ </snippet>
+ <snippet id="imageobject">
+ <text><![CDATA[<imageobject>
+ ${1}
+</imageobject>]]></text>
+ <tag>imageobject</tag>
+ <description>imageobject</description>
+ </snippet>
+ <snippet id="imageobjectco">
+ <text><![CDATA[<imageobjectco>
+ ${1}
+</imageobjectco>]]></text>
+ <tag>imageobjectco</tag>
+ <description>imageobjectco</description>
+ </snippet>
+ <snippet id="important">
+ <text><![CDATA[<important>
+ ${1}
+</important>]]></text>
+ <tag>important</tag>
+ <description>important</description>
+ </snippet>
+ <snippet id="index">
+ <text><![CDATA[<index>
+ ${1}
+</index>]]></text>
+ <tag>index</tag>
+ <description>index</description>
+ </snippet>
+ <snippet id="indexdiv">
+ <text><![CDATA[<indexdiv>
+ ${1}
+</indexdiv>]]></text>
+ <tag>indexdiv</tag>
+ <description>indexdiv</description>
+ </snippet>
+ <snippet id="indexentry">
+ <text><![CDATA[<indexentry>
+ ${1}
+</indexentry>]]></text>
+ <tag>indexentry</tag>
+ <description>indexentry</description>
+ </snippet>
+ <snippet id="indexinfo">
+ <text><![CDATA[<indexinfo>
+ ${1}
+</indexinfo>]]></text>
+ <tag>indexinfo</tag>
+ <description>indexinfo</description>
+ </snippet>
+ <snippet id="indexterm">
+ <text><![CDATA[<indexterm>
+ ${1}
+</indexterm>]]></text>
+ <tag>indexterm</tag>
+ <description>indexterm</description>
+ </snippet>
+ <snippet id="informalequation">
+ <text><![CDATA[<informalequation>
+ ${1}
+</informalequation>]]></text>
+ <tag>informalequation</tag>
+ <description>informalequation</description>
+ </snippet>
+ <snippet id="informalexample">
+ <text><![CDATA[<informalexample>
+ ${1}
+</informalexample>]]></text>
+ <tag>informalexample</tag>
+ <description>informalexample</description>
+ </snippet>
+ <snippet id="informalfigure">
+ <text><![CDATA[<informalfigure>
+ ${1}
+</informalfigure>]]></text>
+ <tag>informalfigure</tag>
+ <description>informalfigure</description>
+ </snippet>
+ <snippet id="informaltable">
+ <text><![CDATA[<informaltable>
+ ${1}
+</informaltable>]]></text>
+ <tag>informaltable</tag>
+ <description>informaltable</description>
+ </snippet>
+ <snippet id="initializer">
+ <text><![CDATA[<initializer>${1}</initializer>]]></text>
+ <tag>initializer</tag>
+ <description>initializer</description>
+ </snippet>
+ <snippet id="inlineequation">
+ <text><![CDATA[<inlineequation>
+ ${1}
+</inlineequation>]]></text>
+ <tag>inlineequation</tag>
+ <description>inlineequation</description>
+ </snippet>
+ <snippet id="inlinegraphic">
+ <text><![CDATA[<inlinegraphic fileref="${1}" format="${2:PNG}" scalefit="${3:0}" />]]></text>
+ <tag>inlinegraphic</tag>
+ <description>inlinegraphic</description>
+ </snippet>
+ <snippet id="inlinemediaobject">
+ <text><![CDATA[<inlinemediaobject>
+ ${1}
+</inlinemediaobject>]]></text>
+ <tag>inlinemediaobject</tag>
+ <description>inlinemediaobject</description>
+ </snippet>
+ <snippet id="interface">
+ <text><![CDATA[<interface>${1}</interface>]]></text>
+ <tag>interface</tag>
+ <description>interface</description>
+ </snippet>
+ <snippet id="interfacename">
+ <text><![CDATA[<interfacename>${1}</interfacename>]]></text>
+ <tag>interfacename</tag>
+ <description>interfacename</description>
+ </snippet>
+ <snippet id="invpartnumber">
+ <text><![CDATA[<invpartnumber>${1}</invpartnumber>]]></text>
+ <tag>invpartnumber</tag>
+ <description>invpartnumber</description>
+ </snippet>
+ <snippet id="isbn">
+ <text><![CDATA[<isbn>${1}</isbn>]]></text>
+ <tag>isbn</tag>
+ <description>isbn</description>
+ </snippet>
+ <snippet id="issn">
+ <text><![CDATA[<issn>${1}</issn>]]></text>
+ <tag>issn</tag>
+ <description>issn</description>
+ </snippet>
+ <snippet id="issuenum">
+ <text><![CDATA[<issuenum>${1}</issuenum>]]></text>
+ <tag>issuenum</tag>
+ <description>issuenum</description>
+ </snippet>
+ <snippet id="itemizedlist">
+ <text><![CDATA[<itemizedlist>
+ ${1}
+</itemizedlist>]]></text>
+ <tag>itemizedlist</tag>
+ <description>itemizedlist</description>
+ </snippet>
+ <snippet id="itermset">
+ <text><![CDATA[<itermset>
+ ${1}
+</itermset>]]></text>
+ <tag>itermset</tag>
+ <description>itermset</description>
+ </snippet>
+ <snippet id="jobtitle">
+ <text><![CDATA[<jobtitle>${1}</jobtitle>]]></text>
+ <tag>jobtitle</tag>
+ <description>jobtitle</description>
+ </snippet>
+ <snippet id="keycap">
+ <text><![CDATA[<keycap>${1}</keycap>]]></text>
+ <tag>keycap</tag>
+ <description>keycap</description>
+ </snippet>
+ <snippet id="keycode">
+ <text><![CDATA[<keycode>${1}</keycode>]]></text>
+ <tag>keycode</tag>
+ <description>keycode</description>
+ </snippet>
+ <snippet id="keycombo">
+ <text><![CDATA[<keycombo>${1}</keycombo>]]></text>
+ <tag>keycombo</tag>
+ <description>keycombo</description>
+ </snippet>
+ <snippet id="keysym">
+ <text><![CDATA[<keysym>${1}</keysym>]]></text>
+ <tag>keysym</tag>
+ <description>keysym</description>
+ </snippet>
+ <snippet id="keyword">
+ <text><![CDATA[<keyword>${1}</keyword>]]></text>
+ <tag>keyword</tag>
+ <description>keyword</description>
+ </snippet>
+ <snippet id="keywordset">
+ <text><![CDATA[<keywordset>
+ ${1}
+</keywordset>]]></text>
+ <tag>keywordset</tag>
+ <description>keywordset</description>
+ </snippet>
+ <snippet id="label">
+ <text><![CDATA[<label>${1}</label>]]></text>
+ <tag>label</tag>
+ <description>label</description>
+ </snippet>
+ <snippet id="legalnotice">
+ <text><![CDATA[<legalnotice>
+ ${1}
+</legalnotice>]]></text>
+ <tag>legalnotice</tag>
+ <description>legalnotice</description>
+ </snippet>
+ <snippet id="lineage">
+ <text><![CDATA[<lineage>${1}</lineage>]]></text>
+ <tag>lineage</tag>
+ <description>lineage</description>
+ </snippet>
+ <snippet id="lineannotation">
+ <text><![CDATA[<lineannotation>${1}</lineannotation>]]></text>
+ <tag>lineannotation</tag>
+ <description>lineannotation</description>
+ </snippet>
+ <snippet id="link">
+ <text><![CDATA[<link linkend="${1}">${2}</link>]]></text>
+ <tag>link</tag>
+ <description>link</description>
+ </snippet>
+ <snippet id="listitem">
+ <text><![CDATA[<listitem>
+ ${1}
+</listitem>]]></text>
+ <tag>listitem</tag>
+ <description>listitem</description>
+ </snippet>
+ <snippet id="literal">
+ <text><![CDATA[<literal>${1}</literal>]]></text>
+ <tag>literal</tag>
+ <description>literal</description>
+ </snippet>
+ <snippet id="literallayout">
+ <text><![CDATA[<literallayout>${1}</literallayout>]]></text>
+ <tag>literallayout</tag>
+ <description>literallayout</description>
+ </snippet>
+ <snippet id="lot">
+ <text><![CDATA[<lot>
+ ${1}
+</lot>]]></text>
+ <tag>lot</tag>
+ <description>lot</description>
+ </snippet>
+ <snippet id="lotentry">
+ <text><![CDATA[<lotentry linkend="${1}">
+ ${2}
+</lotentry>]]></text>
+ <tag>lotentry</tag>
+ <description>lotentry</description>
+ </snippet>
+ <snippet id="manvolnum">
+ <text><![CDATA[<manvolnum>${1}</manvolnum>]]></text>
+ <tag>manvolnum</tag>
+ <description>manvolnum</description>
+ </snippet>
+ <snippet id="markup">
+ <text><![CDATA[<markup>${1}</markup>]]></text>
+ <tag>markup</tag>
+ <description>markup</description>
+ </snippet>
+ <snippet id="mathphrase">
+ <text><![CDATA[<mathphrase>${1}</mathphrase>]]></text>
+ <tag>mathphrase</tag>
+ <description>mathphrase</description>
+ </snippet>
+ <snippet id="medialabel">
+ <text><![CDATA[<medialabel>${1}</medialabel>]]></text>
+ <tag>medialabel</tag>
+ <description>medialabel</description>
+ </snippet>
+ <snippet id="mediaobject">
+ <text><![CDATA[<mediaobject>
+ ${1}
+</mediaobject>]]></text>
+ <tag>mediaobject</tag>
+ <description>mediaobject</description>
+ </snippet>
+ <snippet id="mediaobjectco">
+ <text><![CDATA[<mediaobjectco>
+ ${1}
+</mediaobjectco>]]></text>
+ <tag>mediaobjectco</tag>
+ <description>mediaobjectco</description>
+ </snippet>
+ <snippet id="member">
+ <text><![CDATA[<member>
+ ${1}
+</member>]]></text>
+ <tag>member</tag>
+ <description>member</description>
+ </snippet>
+ <snippet id="menuchoice">
+ <text><![CDATA[<menuchoice>${1}</menuchoice>]]></text>
+ <tag>menuchoice</tag>
+ <description>menuchoice</description>
+ </snippet>
+ <snippet id="methodname">
+ <text><![CDATA[<methodname>${1}</methodname>]]></text>
+ <tag>methodname</tag>
+ <description>methodname</description>
+ </snippet>
+ <snippet id="methodparam">
+ <text><![CDATA[<methodparam>${1}</methodparam>]]></text>
+ <tag>methodparam</tag>
+ <description>methodparam</description>
+ </snippet>
+ <snippet id="methodsynopsis">
+ <text><![CDATA[<methodsynopsis language="${1}">
+ ${2}
+</methodsynopsis>]]></text>
+ <tag>methodsynopsis</tag>
+ <description>methodsynopsis</description>
+ </snippet>
+ <snippet id="modespec">
+ <text><![CDATA[<modespec application="${1}">${2}</modespec>]]></text>
+ <tag>modespec</tag>
+ <description>modespec</description>
+ </snippet>
+ <snippet id="modifier">
+ <text><![CDATA[<modifier>${1}</modifier>]]></text>
+ <tag>modifier</tag>
+ <description>modifier</description>
+ </snippet>
+ <snippet id="mousebutton">
+ <text><![CDATA[<mousebutton>${1}</mousebutton>]]></text>
+ <tag>mousebutton</tag>
+ <description>mousebutton</description>
+ </snippet>
+ <snippet id="msg">
+ <text><![CDATA[<msg>
+ ${1}
+</msg>]]></text>
+ <tag>msg</tag>
+ <description>msg</description>
+ </snippet>
+ <snippet id="msgaud">
+ <text><![CDATA[<msgaud>${1}</msgaud>]]></text>
+ <tag>msgaud</tag>
+ <description>msgaud</description>
+ </snippet>
+ <snippet id="msgentry">
+ <text><![CDATA[<msgentry>
+ ${1}
+</msgentry>]]></text>
+ <tag>msgentry</tag>
+ <description>msgentry</description>
+ </snippet>
+ <snippet id="msgexplan">
+ <text><![CDATA[<msgexplan>
+ ${1}
+</msgexplan>]]></text>
+ <tag>msgexplan</tag>
+ <description>msgexplan</description>
+ </snippet>
+ <snippet id="msginfo">
+ <text><![CDATA[<msginfo>
+ ${1}
+</msginfo>]]></text>
+ <tag>msginfo</tag>
+ <description>msginfo</description>
+ </snippet>
+ <snippet id="msglevel">
+ <text><![CDATA[<msglevel>${1}</msglevel>]]></text>
+ <tag>msglevel</tag>
+ <description>msglevel</description>
+ </snippet>
+ <snippet id="msgmain">
+ <text><![CDATA[<msgmain>
+ ${1}
+</msgmain>]]></text>
+ <tag>msgmain</tag>
+ <description>msgmain</description>
+ </snippet>
+ <snippet id="msgorig">
+ <text><![CDATA[<msgorig>${1}</msgorig>]]></text>
+ <tag>msgorig</tag>
+ <description>msgorig</description>
+ </snippet>
+ <snippet id="msgrel">
+ <text><![CDATA[<msgrel>
+ ${1}
+</msgrel>]]></text>
+ <tag>msgrel</tag>
+ <description>msgrel</description>
+ </snippet>
+ <snippet id="msgset">
+ <text><![CDATA[<msgset>
+ ${1}
+</msgset>]]></text>
+ <tag>msgset</tag>
+ <description>msgset</description>
+ </snippet>
+ <snippet id="msgsub">
+ <text><![CDATA[<msgsub>
+ ${1}
+</msgsub>]]></text>
+ <tag>msgsub</tag>
+ <description>msgsub</description>
+ </snippet>
+ <snippet id="msgtext">
+ <text><![CDATA[<msgtext>
+ ${1}
+</msgtext>]]></text>
+ <tag>msgtext</tag>
+ <description>msgtext</description>
+ </snippet>
+ <snippet id="note">
+ <text><![CDATA[<note>
+ ${1}
+</note>]]></text>
+ <tag>note</tag>
+ <description>note</description>
+ </snippet>
+ <snippet id="objectinfo">
+ <text><![CDATA[<objectinfo>
+ ${1}
+</objectinfo>]]></text>
+ <tag>objectinfo</tag>
+ <description>objectinfo</description>
+ </snippet>
+ <snippet id="olink">
+ <text><![CDATA[<olink targetdocent="${1}">${2}</olink>]]></text>
+ <tag>olink</tag>
+ <description>olink</description>
+ </snippet>
+ <snippet id="ooclass">
+ <text><![CDATA[<ooclass>${1}</ooclass>]]></text>
+ <tag>ooclass</tag>
+ <description>ooclass</description>
+ </snippet>
+ <snippet id="ooexception">
+ <text><![CDATA[<ooexception>${1}</ooexception>]]></text>
+ <tag>ooexception</tag>
+ <description>ooexception</description>
+ </snippet>
+ <snippet id="oointerface">
+ <text><![CDATA[<oointerface>${1}</oointerface>]]></text>
+ <tag>oointerface</tag>
+ <description>oointerface</description>
+ </snippet>
+ <snippet id="option">
+ <text><![CDATA[<option>${1}</option>]]></text>
+ <tag>option</tag>
+ <description>option</description>
+ </snippet>
+ <snippet id="optional">
+ <text><![CDATA[<optional>${1}</optional>]]></text>
+ <tag>optional</tag>
+ <description>optional</description>
+ </snippet>
+ <snippet id="orderedlist">
+ <text><![CDATA[<orderedlist>
+ ${1}
+</orderedlist>]]></text>
+ <tag>orderedlist</tag>
+ <description>orderedlist</description>
+ </snippet>
+ <snippet id="orgdiv">
+ <text><![CDATA[<orgdiv>${1}</orgdiv>]]></text>
+ <tag>orgdiv</tag>
+ <description>orgdiv</description>
+ </snippet>
+ <snippet id="orgname">
+ <text><![CDATA[<orgname>${1}</orgname>]]></text>
+ <tag>orgname</tag>
+ <description>orgname</description>
+ </snippet>
+ <snippet id="otheraddr">
+ <text><![CDATA[<otheraddr>${1}</otheraddr>]]></text>
+ <tag>otheraddr</tag>
+ <description>otheraddr</description>
+ </snippet>
+ <snippet id="othercredit">
+ <text><![CDATA[<othercredit>
+ ${1}
+</othercredit>]]></text>
+ <tag>othercredit</tag>
+ <description>othercredit</description>
+ </snippet>
+ <snippet id="othername">
+ <text><![CDATA[<othername>${1}</othername>]]></text>
+ <tag>othername</tag>
+ <description>othername</description>
+ </snippet>
+ <snippet id="package">
+ <text><![CDATA[<package>${1}</package>]]></text>
+ <tag>package</tag>
+ <description>package</description>
+ </snippet>
+ <snippet id="pagenums">
+ <text><![CDATA[<pagenums>${1}</pagenums>]]></text>
+ <tag>pagenums</tag>
+ <description>pagenums</description>
+ </snippet>
+ <snippet id="para">
+ <text><![CDATA[<para>
+ ${1}
+</para>]]></text>
+ <tag>para</tag>
+ <description>para</description>
+ </snippet>
+ <snippet id="paramdef">
+ <text><![CDATA[<paramdef>${1}</paramdef>]]></text>
+ <tag>paramdef</tag>
+ <description>paramdef</description>
+ </snippet>
+ <snippet id="parameter">
+ <text><![CDATA[<parameter class="${1:function}">${2}</parameter>]]></text>
+ <tag>parameter</tag>
+ <description>parameter</description>
+ </snippet>
+ <snippet id="part">
+ <text><![CDATA[<part id="${1}">
+ ${2}
+</part>]]></text>
+ <tag>part</tag>
+ <description>part</description>
+ </snippet>
+ <snippet id="partinfo">
+ <text><![CDATA[<partinfo>
+ ${1}
+</partinfo>]]></text>
+ <tag>partinfo</tag>
+ <description>partinfo</description>
+ </snippet>
+ <snippet id="partintro">
+ <text><![CDATA[<partintro>
+ ${1}
+</partintro>]]></text>
+ <tag>partintro</tag>
+ <description>partintro</description>
+ </snippet>
+ <snippet id="personblurb">
+ <text><![CDATA[<personblurb>
+ ${1}
+</personblurb>]]></text>
+ <tag>personblurb</tag>
+ <description>personblurb</description>
+ </snippet>
+ <snippet id="personname">
+ <text><![CDATA[<personname>${1}</personname>]]></text>
+ <tag>personname</tag>
+ <description>personname</description>
+ </snippet>
+ <snippet id="phone">
+ <text><![CDATA[<phone>${1}</phone>]]></text>
+ <tag>phone</tag>
+ <description>phone</description>
+ </snippet>
+ <snippet id="phrase">
+ <text><![CDATA[<phrase>${1}</phrase>]]></text>
+ <tag>phrase</tag>
+ <description>phrase</description>
+ </snippet>
+ <snippet id="pob">
+ <text><![CDATA[<pob>${1}</pob>]]></text>
+ <tag>pob</tag>
+ <description>pob</description>
+ </snippet>
+ <snippet id="postcode">
+ <text><![CDATA[<postcode>${1}</postcode>]]></text>
+ <tag>postcode</tag>
+ <description>postcode</description>
+ </snippet>
+ <snippet id="preface">
+ <text><![CDATA[<preface id="${1}">
+ ${2}
+</preface>]]></text>
+ <tag>preface</tag>
+ <description>preface</description>
+ </snippet>
+ <snippet id="prefaceinfo">
+ <text><![CDATA[<prefaceinfo>
+ ${1}
+</prefaceinfo>]]></text>
+ <tag>prefaceinfo</tag>
+ <description>prefaceinfo</description>
+ </snippet>
+ <snippet id="primary">
+ <text><![CDATA[<primary>${1}</primary>]]></text>
+ <tag>primary</tag>
+ <description>primary</description>
+ </snippet>
+ <snippet id="primaryie">
+ <text><![CDATA[<primaryie>${1}</primaryie>]]></text>
+ <tag>primaryie</tag>
+ <description>primaryie</description>
+ </snippet>
+ <snippet id="printhistory">
+ <text><![CDATA[<printhistory>
+ ${1}
+</printhistory>]]></text>
+ <tag>printhistory</tag>
+ <description>printhistory</description>
+ </snippet>
+ <snippet id="procedure">
+ <text><![CDATA[<procedure>
+ ${1}
+</procedure>]]></text>
+ <tag>procedure</tag>
+ <description>procedure</description>
+ </snippet>
+ <snippet id="productname">
+ <text><![CDATA[<productname class="${1:trade}">${2}</productname>]]></text>
+ <tag>productname</tag>
+ <description>productname</description>
+ </snippet>
+ <snippet id="productnumber">
+ <text><![CDATA[<productnumber>${1}</productnumber>]]></text>
+ <tag>productnumber</tag>
+ <description>productnumber</description>
+ </snippet>
+ <snippet id="programlisting">
+ <text><![CDATA[<programlisting language="${1}">${2}</programlisting>]]></text>
+ <tag>programlisting</tag>
+ <description>programlisting</description>
+ </snippet>
+ <snippet id="programlistingco">
+ <text><![CDATA[<programlistingco>
+ ${1}
+</programlistingco>]]></text>
+ <tag>programlistingco</tag>
+ <description>programlistingco</description>
+ </snippet>
+ <snippet id="prompt">
+ <text><![CDATA[<prompt>${1}</prompt>]]></text>
+ <tag>prompt</tag>
+ <description>prompt</description>
+ </snippet>
+ <snippet id="property">
+ <text><![CDATA[<property>${1}</property>]]></text>
+ <tag>property</tag>
+ <description>property</description>
+ </snippet>
+ <snippet id="pubdate">
+ <text><![CDATA[<pubdate>${1}</pubdate>]]></text>
+ <tag>pubdate</tag>
+ <description>pubdate</description>
+ </snippet>
+ <snippet id="publisher">
+ <text><![CDATA[<publisher>
+ ${1}
+</publisher>]]></text>
+ <tag>publisher</tag>
+ <description>publisher</description>
+ </snippet>
+ <snippet id="publishername">
+ <text><![CDATA[<publishername>${1}</publishername>]]></text>
+ <tag>publishername</tag>
+ <description>publishername</description>
+ </snippet>
+ <snippet id="pubsnumber">
+ <text><![CDATA[<pubsnumber>${1}</pubsnumber>]]></text>
+ <tag>pubsnumber</tag>
+ <description>pubsnumber</description>
+ </snippet>
+ <snippet id="qandadiv">
+ <text><![CDATA[<qandadiv>
+ ${1}
+</qandadiv>]]></text>
+ <tag>qandadiv</tag>
+ <description>qandadiv</description>
+ </snippet>
+ <snippet id="qandaentry">
+ <text><![CDATA[<qandaentry>
+ ${1}
+</qandaentry>]]></text>
+ <tag>qandaentry</tag>
+ <description>qandaentry</description>
+ </snippet>
+ <snippet id="qandaset">
+ <text><![CDATA[<qandaset>
+ ${1}
+</qandaset>]]></text>
+ <tag>qandaset</tag>
+ <description>qandaset</description>
+ </snippet>
+ <snippet id="question">
+ <text><![CDATA[<question>
+ ${1}
+</question>]]></text>
+ <tag>question</tag>
+ <description>question</description>
+ </snippet>
+ <snippet id="quote">
+ <text><![CDATA[<quote>${1}</quote>]]></text>
+ <tag>quote</tag>
+ <description>quote</description>
+ </snippet>
+ <snippet id="refclass">
+ <text><![CDATA[<refclass>${1}</refclass>]]></text>
+ <tag>refclass</tag>
+ <description>refclass</description>
+ </snippet>
+ <snippet id="refdescriptor">
+ <text><![CDATA[<refdescriptor>${1}</refdescriptor>]]></text>
+ <tag>refdescriptor</tag>
+ <description>refdescriptor</description>
+ </snippet>
+ <snippet id="refentry">
+ <text><![CDATA[<refentry>
+ ${1}
+</refentry>]]></text>
+ <tag>refentry</tag>
+ <description>refentry</description>
+ </snippet>
+ <snippet id="refentryinfo">
+ <text><![CDATA[<refentryinfo>
+ ${1}
+</refentryinfo>]]></text>
+ <tag>refentryinfo</tag>
+ <description>refentryinfo</description>
+ </snippet>
+ <snippet id="refentrytitle">
+ <text><![CDATA[<refentrytitle>${1}</refentrytitle>]]></text>
+ <tag>refentrytitle</tag>
+ <description>refentrytitle</description>
+ </snippet>
+ <snippet id="reference">
+ <text><![CDATA[<reference>
+ ${1}
+</reference>]]></text>
+ <tag>reference</tag>
+ <description>reference</description>
+ </snippet>
+ <snippet id="referenceinfo">
+ <text><![CDATA[<referenceinfo>
+ ${1}
+</referenceinfo>]]></text>
+ <tag>referenceinfo</tag>
+ <description>referenceinfo</description>
+ </snippet>
+ <snippet id="refmeta">
+ <text><![CDATA[<refmeta>
+ ${1}
+</refmeta>]]></text>
+ <tag>refmeta</tag>
+ <description>refmeta</description>
+ </snippet>
+ <snippet id="refmiscinfo">
+ <text><![CDATA[<refmiscinfo>
+ ${1}
+</refmiscinfo>]]></text>
+ <tag>refmiscinfo</tag>
+ <description>refmiscinfo</description>
+ </snippet>
+ <snippet id="refname">
+ <text><![CDATA[<refname>${1}</refname>]]></text>
+ <tag>refname</tag>
+ <description>refname</description>
+ </snippet>
+ <snippet id="refnamediv">
+ <text><![CDATA[<refnamediv>
+ ${1}
+</refnamediv>]]></text>
+ <tag>refnamediv</tag>
+ <description>refnamediv</description>
+ </snippet>
+ <snippet id="refpurpose">
+ <text><![CDATA[<refpurpose>${1}</refpurpose>]]></text>
+ <tag>refpurpose</tag>
+ <description>refpurpose</description>
+ </snippet>
+ <snippet id="refsect1">
+ <text><![CDATA[<refsect1>
+ ${1}
+</refsect1>]]></text>
+ <tag>refsect1</tag>
+ <description>refsect1</description>
+ </snippet>
+ <snippet id="refsect1info">
+ <text><![CDATA[<refsect1info>
+ ${1}
+</refsect1info>]]></text>
+ <tag>refsect1info</tag>
+ <description>refsect1info</description>
+ </snippet>
+ <snippet id="refsect2">
+ <text><![CDATA[<refsect2>
+ ${1}
+</refsect2>]]></text>
+ <tag>refsect2</tag>
+ <description>refsect2</description>
+ </snippet>
+ <snippet id="refsect2info">
+ <text><![CDATA[<refsect2info>
+ ${1}
+</refsect2info>]]></text>
+ <tag>refsect2info</tag>
+ <description>refsect2info</description>
+ </snippet>
+ <snippet id="refsect3">
+ <text><![CDATA[<refsect3>
+ ${1}
+</refsect3>]]></text>
+ <tag>refsect3</tag>
+ <description>refsect3</description>
+ </snippet>
+ <snippet id="refsect3info">
+ <text><![CDATA[<refsect3info>
+ ${1}
+</refsect3info>]]></text>
+ <tag>refsect3info</tag>
+ <description>refsect3info</description>
+ </snippet>
+ <snippet id="refsection">
+ <text><![CDATA[<refsection>
+ ${1}
+</refsection>]]></text>
+ <tag>refsection</tag>
+ <description>refsection</description>
+ </snippet>
+ <snippet id="refsectioninfo">
+ <text><![CDATA[<refsectioninfo>
+ ${1}
+</refsectioninfo>]]></text>
+ <tag>refsectioninfo</tag>
+ <description>refsectioninfo</description>
+ </snippet>
+ <snippet id="refsynopsisdiv">
+ <text><![CDATA[<refsynopsisdiv>
+ ${1}
+</refsynopsisdiv>]]></text>
+ <tag>refsynopsisdiv</tag>
+ <description>refsynopsisdiv</description>
+ </snippet>
+ <snippet id="refsynopsisdivinfo">
+ <text><![CDATA[<refsynopsisdivinfo>
+ ${1}
+</refsynopsisdivinfo>]]></text>
+ <tag>refsynopsisdivinfo</tag>
+ <description>refsynopsisdivinfo</description>
+ </snippet>
+ <snippet id="releaseinfo">
+ <text><![CDATA[<releaseinfo>${1}</releaseinfo>]]></text>
+ <tag>releaseinfo</tag>
+ <description>releaseinfo</description>
+ </snippet>
+ <snippet id="remark">
+ <text><![CDATA[<remark>${1}</remark>]]></text>
+ <tag>remark</tag>
+ <description>remark</description>
+ </snippet>
+ <snippet id="replaceable">
+ <text><![CDATA[<replaceable>${1}</replaceable>]]></text>
+ <tag>replaceable</tag>
+ <description>replaceable</description>
+ </snippet>
+ <snippet id="returnvalue">
+ <text><![CDATA[<returnvalue>${1}</returnvalue>]]></text>
+ <tag>returnvalue</tag>
+ <description>returnvalue</description>
+ </snippet>
+ <snippet id="revdescription">
+ <text><![CDATA[<revdescription>
+ ${1}
+</revdescription>]]></text>
+ <tag>revdescription</tag>
+ <description>revdescription</description>
+ </snippet>
+ <snippet id="revhistory">
+ <text><![CDATA[<revhistory>
+ ${1}
+</revhistory>]]></text>
+ <tag>revhistory</tag>
+ <description>revhistory</description>
+ </snippet>
+ <snippet id="revision">
+ <text><![CDATA[<revision>
+ ${1}
+</revision>]]></text>
+ <tag>revision</tag>
+ <description>revision</description>
+ </snippet>
+ <snippet id="revnumber">
+ <text><![CDATA[<revnumber>${1}</revnumber>]]></text>
+ <tag>revnumber</tag>
+ <description>revnumber</description>
+ </snippet>
+ <snippet id="revremark">
+ <text><![CDATA[<revremark>${1}</revremark>]]></text>
+ <tag>revremark</tag>
+ <description>revremark</description>
+ </snippet>
+ <snippet id="row">
+ <text><![CDATA[<row>
+ ${1}
+</row>]]></text>
+ <tag>row</tag>
+ <description>row</description>
+ </snippet>
+ <snippet id="sbr">
+ <text><![CDATA[<sbr />]]></text>
+ <tag>sbr</tag>
+ <description>sbr</description>
+ </snippet>
+ <snippet id="screen">
+ <text><![CDATA[<screen>${1}</screen>]]></text>
+ <tag>screen</tag>
+ <description>screen</description>
+ </snippet>
+ <snippet id="screenco">
+ <text><![CDATA[<screenco>
+ ${1}
+</screenco>]]></text>
+ <tag>screenco</tag>
+ <description>screenco</description>
+ </snippet>
+ <snippet id="screeninfo">
+ <text><![CDATA[<screeninfo>${1}</screeninfo>]]></text>
+ <tag>screeninfo</tag>
+ <description>screeninfo</description>
+ </snippet>
+ <snippet id="screenshot">
+ <text><![CDATA[<screenshot>
+ ${1}
+</screenshot>]]></text>
+ <tag>screenshot</tag>
+ <description>screenshot</description>
+ </snippet>
+ <snippet id="secondary">
+ <text><![CDATA[<secondary>${1}</secondary>]]></text>
+ <tag>secondary</tag>
+ <description>secondary</description>
+ </snippet>
+ <snippet id="secondaryie">
+ <text><![CDATA[<secondaryie>${1}</secondaryie>]]></text>
+ <tag>secondaryie</tag>
+ <description>secondaryie</description>
+ </snippet>
+ <snippet id="sect1">
+ <text><![CDATA[<sect1 id="${1}">
+ ${2}
+</sect1>]]></text>
+ <tag>sect1</tag>
+ <description>sect1</description>
+ </snippet>
+ <snippet id="sect1info">
+ <text><![CDATA[<sect1info>
+ ${1}
+</sect1info>]]></text>
+ <tag>sect1info</tag>
+ <description>sect1info</description>
+ </snippet>
+ <snippet id="sect2">
+ <text><![CDATA[<sect2 id="${1}">
+ ${2}
+</sect2>]]></text>
+ <tag>sect2</tag>
+ <description>sect2</description>
+ </snippet>
+ <snippet id="sect2info">
+ <text><![CDATA[<sect2info>
+ ${1}
+</sect2info>]]></text>
+ <tag>sect2info</tag>
+ <description>sect2info</description>
+ </snippet>
+ <snippet id="sect3">
+ <text><![CDATA[<sect3 id="${1}">
+ ${2}
+</sect3>]]></text>
+ <tag>sect3</tag>
+ <description>sect3</description>
+ </snippet>
+ <snippet id="sect3info">
+ <text><![CDATA[<sect3info>
+ ${1}
+</sect3info>]]></text>
+ <tag>sect3info</tag>
+ <description>sect3info</description>
+ </snippet>
+ <snippet id="sect4">
+ <text><![CDATA[<sect4 id="${1}">
+ ${2}
+</sect4>]]></text>
+ <tag>sect4</tag>
+ <description>sect4</description>
+ </snippet>
+ <snippet id="sect4info">
+ <text><![CDATA[<sect4info>
+ ${1}
+</sect4info>]]></text>
+ <tag>sect4info</tag>
+ <description>sect4info</description>
+ </snippet>
+ <snippet id="sect5">
+ <text><![CDATA[<sect5 id="${1}">
+ ${2}
+</sect5>]]></text>
+ <tag>sect5</tag>
+ <description>sect5</description>
+ </snippet>
+ <snippet id="sect5info">
+ <text><![CDATA[<sect5info>
+ ${1}
+</sect5info>]]></text>
+ <tag>sect5info</tag>
+ <description>sect5info</description>
+ </snippet>
+ <snippet id="section">
+ <text><![CDATA[<section id="${1}">
+ ${2}
+</section>]]></text>
+ <tag>section</tag>
+ <description>section</description>
+ </snippet>
+ <snippet id="sectioninfo">
+ <text><![CDATA[<sectioninfo>
+ ${1}
+</sectioninfo>]]></text>
+ <tag>sectioninfo</tag>
+ <description>sectioninfo</description>
+ </snippet>
+ <snippet id="see">
+ <text><![CDATA[<see>${1}</see>]]></text>
+ <tag>see</tag>
+ <description>see</description>
+ </snippet>
+ <snippet id="seealso">
+ <text><![CDATA[<seealso>${1}</seealso>]]></text>
+ <tag>seealso</tag>
+ <description>seealso</description>
+ </snippet>
+ <snippet id="seealsoie">
+ <text><![CDATA[<seealsoie>${1}</seealsoie>]]></text>
+ <tag>seealsoie</tag>
+ <description>seealsoie</description>
+ </snippet>
+ <snippet id="seeie">
+ <text><![CDATA[<seeie>${1}</seeie>]]></text>
+ <tag>seeie</tag>
+ <description>seeie</description>
+ </snippet>
+ <snippet id="seg">
+ <text><![CDATA[<seg>${1}</seg>]]></text>
+ <tag>seg</tag>
+ <description>seg</description>
+ </snippet>
+ <snippet id="seglistitem">
+ <text><![CDATA[<seglistitem>
+ ${1}
+</seglistitem>]]></text>
+ <tag>seglistitem</tag>
+ <description>seglistitem</description>
+ </snippet>
+ <snippet id="segmentedlist">
+ <text><![CDATA[<segmentedlist>
+ ${1}
+</segmentedlist>]]></text>
+ <tag>segmentedlist</tag>
+ <description>segmentedlist</description>
+ </snippet>
+ <snippet id="segtitle">
+ <text><![CDATA[<segtitle>${1}</segtitle>]]></text>
+ <tag>segtitle</tag>
+ <description>segtitle</description>
+ </snippet>
+ <snippet id="seriesvolnums">
+ <text><![CDATA[<seriesvolnums>${1}</seriesvolnums>]]></text>
+ <tag>seriesvolnums</tag>
+ <description>seriesvolnums</description>
+ </snippet>
+ <snippet id="set">
+ <text><![CDATA[<set>
+ ${1}
+</set>]]></text>
+ <tag>set</tag>
+ <description>set</description>
+ </snippet>
+ <snippet id="setindex">
+ <text><![CDATA[<setindex>
+ ${1}
+</setindex>]]></text>
+ <tag>setindex</tag>
+ <description>setindex</description>
+ </snippet>
+ <snippet id="setindexinfo">
+ <text><![CDATA[<setindexinfo>
+ ${1}
+</setindexinfo>]]></text>
+ <tag>setindexinfo</tag>
+ <description>setindexinfo</description>
+ </snippet>
+ <snippet id="setinfo">
+ <text><![CDATA[<setinfo>
+ ${1}
+</setinfo>]]></text>
+ <tag>setinfo</tag>
+ <description>setinfo</description>
+ </snippet>
+ <snippet id="sgmltag">
+ <text><![CDATA[<sgmltag>${1}</sgmltag>]]></text>
+ <tag>sgmltag</tag>
+ <description>sgmltag</description>
+ </snippet>
+ <snippet id="shortaffil">
+ <text><![CDATA[<shortaffil>${1}</shortaffil>]]></text>
+ <tag>shortaffil</tag>
+ <description>shortaffil</description>
+ </snippet>
+ <snippet id="shortcut">
+ <text><![CDATA[<shortcut>${1}</shortcut>]]></text>
+ <tag>shortcut</tag>
+ <description>shortcut</description>
+ </snippet>
+ <snippet id="sidebar">
+ <text><![CDATA[<sidebar>
+ ${1}
+</sidebar>]]></text>
+ <tag>sidebar</tag>
+ <description>sidebar</description>
+ </snippet>
+ <snippet id="sidebarinfo">
+ <text><![CDATA[<sidebarinfo>
+ ${1}
+</sidebarinfo>]]></text>
+ <tag>sidebarinfo</tag>
+ <description>sidebarinfo</description>
+ </snippet>
+ <snippet id="simpara">
+ <text><![CDATA[<simpara>
+ ${1}
+</simpara>]]></text>
+ <tag>simpara</tag>
+ <description>simpara</description>
+ </snippet>
+ <snippet id="simplelist">
+ <text><![CDATA[<simplelist>
+ ${1}
+</simplelist>]]></text>
+ <tag>simplelist</tag>
+ <description>simplelist</description>
+ </snippet>
+ <snippet id="simplemsgentry">
+ <text><![CDATA[<simplemsgentry>
+ ${1}
+</simplemsgentry>]]></text>
+ <tag>simplemsgentry</tag>
+ <description>simplemsgentry</description>
+ </snippet>
+ <snippet id="simplesect">
+ <text><![CDATA[<simplesect id="${1}">
+ ${2}
+</simplesect>]]></text>
+ <tag>simplesect</tag>
+ <description>simplesect</description>
+ </snippet>
+ <snippet id="spanspec">
+ <text><![CDATA[<spanspec spanname="${1}" namest="${2}" nameend="${3}" />]]></text>
+ <tag>spanspec</tag>
+ <description>spanspec</description>
+ </snippet>
+ <snippet id="state">
+ <text><![CDATA[<state>${1}</state>]]></text>
+ <tag>state</tag>
+ <description>state</description>
+ </snippet>
+ <snippet id="step">
+ <text><![CDATA[<step>
+ ${1}
+</step>]]></text>
+ <tag>step</tag>
+ <description>step</description>
+ </snippet>
+ <snippet id="stepalternatives">
+ <text><![CDATA[<stepalternatives>
+ ${1}
+</stepalternatives>]]></text>
+ <tag>stepalternatives</tag>
+ <description>stepalternatives</description>
+ </snippet>
+ <snippet id="street">
+ <text><![CDATA[<street>${1}</street>]]></text>
+ <tag>street</tag>
+ <description>street</description>
+ </snippet>
+ <snippet id="structfield">
+ <text><![CDATA[<structfield>${1}</structfield>]]></text>
+ <tag>structfield</tag>
+ <description>structfield</description>
+ </snippet>
+ <snippet id="structname">
+ <text><![CDATA[<structname>${1}</structname>]]></text>
+ <tag>structname</tag>
+ <description>structname</description>
+ </snippet>
+ <snippet id="subject">
+ <text><![CDATA[<subject>
+ ${1}
+</subject>]]></text>
+ <tag>subject</tag>
+ <description>subject</description>
+ </snippet>
+ <snippet id="subjectset">
+ <text><![CDATA[<subjectset>
+ ${1}
+</subjectset>]]></text>
+ <tag>subjectset</tag>
+ <description>subjectset</description>
+ </snippet>
+ <snippet id="subjectterm">
+ <text><![CDATA[<subjectterm>${1}</subjectterm>]]></text>
+ <tag>subjectterm</tag>
+ <description>subjectterm</description>
+ </snippet>
+ <snippet id="subscript">
+ <text><![CDATA[<subscript>${1}</subscript>]]></text>
+ <tag>subscript</tag>
+ <description>subscript</description>
+ </snippet>
+ <snippet id="substeps">
+ <text><![CDATA[<substeps>
+ ${1}
+</substeps>]]></text>
+ <tag>substeps</tag>
+ <description>substeps</description>
+ </snippet>
+ <snippet id="subtitle">
+ <text><![CDATA[<subtitle>${1}</subtitle>]]></text>
+ <tag>subtitle</tag>
+ <description>subtitle</description>
+ </snippet>
+ <snippet id="superscript">
+ <text><![CDATA[<superscript>${1}</superscript>]]></text>
+ <tag>superscript</tag>
+ <description>superscript</description>
+ </snippet>
+ <snippet id="surname">
+ <text><![CDATA[<surname>${1}</surname>]]></text>
+ <tag>surname</tag>
+ <description>surname</description>
+ </snippet>
+ <snippet id="symbol">
+ <text><![CDATA[<symbol>${1}</symbol>]]></text>
+ <tag>symbol</tag>
+ <description>symbol</description>
+ </snippet>
+ <snippet id="synopfragment">
+ <text><![CDATA[<synopfragment id="${1}">
+ ${2}
+</synopfragment>]]></text>
+ <tag>synopfragment</tag>
+ <description>synopfragment</description>
+ </snippet>
+ <snippet id="synopfragmentref">
+ <text><![CDATA[<synopfragmentref linkend="${1}">
+ ${2}
+</synopfragmentref>]]></text>
+ <tag>synopfragmentref</tag>
+ <description>synopfragmentref</description>
+ </snippet>
+ <snippet id="synopsis">
+ <text><![CDATA[<synopsis>${1}</synopsis>]]></text>
+ <tag>synopsis</tag>
+ <description>synopsis</description>
+ </snippet>
+ <snippet id="systemitem">
+ <text><![CDATA[<systemitem>${1}</systemitem>]]></text>
+ <tag>systemitem</tag>
+ <description>systemitem</description>
+ </snippet>
+ <snippet id="table">
+ <text><![CDATA[<table id="${1}">
+ ${2}
+</table>]]></text>
+ <tag>table</tag>
+ <description>table</description>
+ </snippet>
+ <snippet id="task">
+ <text><![CDATA[<task>
+ ${1}
+</task>]]></text>
+ <tag>task</tag>
+ <description>task</description>
+ </snippet>
+ <snippet id="taskprerequisites">
+ <text><![CDATA[<taskprerequisites>
+ ${1}
+</taskprerequisites>]]></text>
+ <tag>taskprerequisites</tag>
+ <description>taskprerequisites</description>
+ </snippet>
+ <snippet id="taskrelated">
+ <text><![CDATA[<taskrelated>
+ ${1}
+</taskrelated>]]></text>
+ <tag>taskrelated</tag>
+ <description>taskrelated</description>
+ </snippet>
+ <snippet id="tasksummary">
+ <text><![CDATA[<tasksummary>
+ ${1}
+</tasksummary>]]></text>
+ <tag>tasksummary</tag>
+ <description>tasksummary</description>
+ </snippet>
+ <snippet id="tbody">
+ <text><![CDATA[<tbody>
+ ${1}
+</tbody>]]></text>
+ <tag>tbody</tag>
+ <description>tbody</description>
+ </snippet>
+ <snippet id="td">
+ <text><![CDATA[<td>
+ ${1}
+</td>]]></text>
+ <tag>td</tag>
+ <description>td</description>
+ </snippet>
+ <snippet id="term">
+ <text><![CDATA[<term>${1}</term>]]></text>
+ <tag>term</tag>
+ <description>term</description>
+ </snippet>
+ <snippet id="termdef">
+ <text><![CDATA[<termdef>${1}</termdef>]]></text>
+ <tag>termdef</tag>
+ <description>termdef</description>
+ </snippet>
+ <snippet id="tertiary">
+ <text><![CDATA[<tertiary>${1}</tertiary>]]></text>
+ <tag>tertiary</tag>
+ <description>tertiary</description>
+ </snippet>
+ <snippet id="tertiaryie">
+ <text><![CDATA[<tertiaryie>${1}</tertiaryie>]]></text>
+ <tag>tertiaryie</tag>
+ <description>tertiaryie</description>
+ </snippet>
+ <snippet id="textdata">
+ <text><![CDATA[<textdata fileref="${1}" />]]></text>
+ <tag>textdata</tag>
+ <description>textdata</description>
+ </snippet>
+ <snippet id="textobject">
+ <text><![CDATA[<textobject>
+ ${1}
+</textobject>]]></text>
+ <tag>textobject</tag>
+ <description>textobject</description>
+ </snippet>
+ <snippet id="tfoot">
+ <text><![CDATA[<tfoot>
+ ${1}
+</tfoot>]]></text>
+ <tag>tfoot</tag>
+ <description>tfoot</description>
+ </snippet>
+ <snippet id="tgroup">
+ <text><![CDATA[<tgroup cols="${1}">
+ ${2}
+</tgroup>]]></text>
+ <tag>tgroup</tag>
+ <description>tgroup</description>
+ </snippet>
+ <snippet id="th">
+ <text><![CDATA[<th>
+ ${1}
+</th>]]></text>
+ <tag>th</tag>
+ <description>th</description>
+ </snippet>
+ <snippet id="thead">
+ <text><![CDATA[<thead>
+ ${1}
+</thead>]]></text>
+ <tag>thead</tag>
+ <description>thead</description>
+ </snippet>
+ <snippet id="tip">
+ <text><![CDATA[<tip>
+ ${1}
+</tip>]]></text>
+ <tag>tip</tag>
+ <description>tip</description>
+ </snippet>
+ <snippet id="title">
+ <text><![CDATA[<title>${1}</title>]]></text>
+ <tag>title</tag>
+ <description>title</description>
+ </snippet>
+ <snippet id="titleabbrev">
+ <text><![CDATA[<titleabbrev>${1}</titleabbrev>]]></text>
+ <tag>titleabbrev</tag>
+ <description>titleabbrev</description>
+ </snippet>
+ <snippet id="toc">
+ <text><![CDATA[<toc>
+ ${1}
+</toc>]]></text>
+ <tag>toc</tag>
+ <description>toc</description>
+ </snippet>
+ <snippet id="tocback">
+ <text><![CDATA[<tocback linkend="${1}">${2}</tocback>]]></text>
+ <tag>tocback</tag>
+ <description>tocback</description>
+ </snippet>
+ <snippet id="tocchap">
+ <text><![CDATA[<tocchap>
+ ${1}
+</tocchap>]]></text>
+ <tag>tocchap</tag>
+ <description>tocchap</description>
+ </snippet>
+ <snippet id="tocentry">
+ <text><![CDATA[<tocentry linkend="${1}">${2}</tocentry>]]></text>
+ <tag>tocentry</tag>
+ <description>tocentry</description>
+ </snippet>
+ <snippet id="tocfront">
+ <text><![CDATA[<tocfront linkend="${1}">${2}</tocfront>]]></text>
+ <tag>tocfront</tag>
+ <description>tocfront</description>
+ </snippet>
+ <snippet id="toclevel1">
+ <text><![CDATA[<toclevel1>
+ ${1}
+</toclevel1>]]></text>
+ <tag>toclevel1</tag>
+ <description>toclevel1</description>
+ </snippet>
+ <snippet id="toclevel2">
+ <text><![CDATA[<toclevel2>
+ ${1}
+</toclevel2>]]></text>
+ <tag>toclevel2</tag>
+ <description>toclevel2</description>
+ </snippet>
+ <snippet id="toclevel3">
+ <text><![CDATA[<toclevel3>
+ ${1}
+</toclevel3>]]></text>
+ <tag>toclevel3</tag>
+ <description>toclevel3</description>
+ </snippet>
+ <snippet id="toclevel4">
+ <text><![CDATA[<toclevel4>
+ ${1}
+</toclevel4>]]></text>
+ <tag>toclevel4</tag>
+ <description>toclevel4</description>
+ </snippet>
+ <snippet id="toclevel5">
+ <text><![CDATA[<toclevel5>
+ ${1}
+</toclevel5>]]></text>
+ <tag>toclevel5</tag>
+ <description>toclevel5</description>
+ </snippet>
+ <snippet id="tocpart">
+ <text><![CDATA[<tocpart>
+ ${1}
+</tocpart>]]></text>
+ <tag>tocpart</tag>
+ <description>tocpart</description>
+ </snippet>
+ <snippet id="token">
+ <text><![CDATA[<token>${1}</token>]]></text>
+ <tag>token</tag>
+ <description>token</description>
+ </snippet>
+ <snippet id="tr">
+ <text><![CDATA[<tr>
+ ${1}
+</tr>]]></text>
+ <tag>tr</tag>
+ <description>tr</description>
+ </snippet>
+ <snippet id="trademark">
+ <text><![CDATA[<trademark class="${1:trade}">${2}</trademark>]]></text>
+ <tag>trademark</tag>
+ <description>trademark</description>
+ </snippet>
+ <snippet id="type">
+ <text><![CDATA[<type>${1}</type>]]></text>
+ <tag>type</tag>
+ <description>type</description>
+ </snippet>
+ <snippet id="ulink">
+ <text><![CDATA[<ulink url="${1}">${2}</ulink>]]></text>
+ <tag>ulink</tag>
+ <description>ulink</description>
+ </snippet>
+ <snippet id="uri">
+ <text><![CDATA[<uri>${1}</uri>]]></text>
+ <tag>uri</tag>
+ <description>uri</description>
+ </snippet>
+ <snippet id="userinput">
+ <text><![CDATA[<userinput>${1}</userinput>]]></text>
+ <tag>userinput</tag>
+ <description>userinput</description>
+ </snippet>
+ <snippet id="varargs">
+ <text><![CDATA[<varargs />]]></text>
+ <tag>varargs</tag>
+ <description>varargs</description>
+ </snippet>
+ <snippet id="variablelist">
+ <text><![CDATA[<variablelist>
+ ${1}
+</variablelist>]]></text>
+ <tag>variablelist</tag>
+ <description>variablelist</description>
+ </snippet>
+ <snippet id="varlistentry">
+ <text><![CDATA[<varlistentry>
+ ${1}
+</varlistentry>]]></text>
+ <tag>varlistentry</tag>
+ <description>varlistentry</description>
+ </snippet>
+ <snippet id="varname">
+ <text><![CDATA[<varname>${1}</varname>]]></text>
+ <tag>varname</tag>
+ <description>varname</description>
+ </snippet>
+ <snippet id="videodata">
+ <text><![CDATA[<videodata fileref="${1}" scalefit="${2:0}" />]]></text>
+ <tag>videodata</tag>
+ <description>videodata</description>
+ </snippet>
+ <snippet id="videoobject">
+ <text><![CDATA[<videoobject>
+ ${1}
+</videoobject>]]></text>
+ <tag>videoobject</tag>
+ <description>videoobject</description>
+ </snippet>
+ <snippet id="void">
+ <text><![CDATA[<void />]]></text>
+ <tag>void</tag>
+ <description>void</description>
+ </snippet>
+ <snippet id="volumenum">
+ <text><![CDATA[<volumenum>${1}</volumenum>]]></text>
+ <tag>volumenum</tag>
+ <description>volumenum</description>
+ </snippet>
+ <snippet id="warning">
+ <text><![CDATA[<warning>
+ ${1}
+</warning>]]></text>
+ <tag>warning</tag>
+ <description>warning</description>
+ </snippet>
+ <snippet id="wordasword">
+ <text><![CDATA[<wordasword>${1}</wordasword>]]></text>
+ <tag>wordasword</tag>
+ <description>wordasword</description>
+ </snippet>
+ <snippet id="xref">
+ <text><![CDATA[<xref linkend="${1}" />]]></text>
+ <tag>xref</tag>
+ <description>xref</description>
+ </snippet>
+ <snippet id="year">
+ <text><![CDATA[<year>${1}</year>]]></text>
+ <tag>year</tag>
+ <description>year</description>
+ </snippet>
+ <!-- DocBook Element Aliases: -->
+ <snippet id="bold">
+ <text><![CDATA[<emphasis role="bold">${1}</emphasis>]]></text>
+ <tag>bold</tag>
+ <description>emphasis, role bold</description>
+ </snippet>
+ <snippet id="strong">
+ <text><![CDATA[<emphasis role="strong">${1}</emphasis>]]></text>
+ <tag>strong</tag>
+ <description>emphasis, role strong</description>
+ </snippet>
+ <snippet id="devicefile">
+ <text><![CDATA[<filename class="devicefile">${1}</filename>]]></text>
+ <tag>devicefile</tag>
+ <description>filename, class devicefile</description>
+ </snippet>
+ <snippet id="directory">
+ <text><![CDATA[<filename class="directory">${1}</filename>]]></text>
+ <tag>directory</tag>
+ <description>filename, class directory</description>
+ </snippet>
+ <snippet id="extension">
+ <text><![CDATA[<filename class="extension">${1}</filename>]]></text>
+ <tag>extension</tag>
+ <description>filename, class extension</description>
+ </snippet>
+ <snippet id="headerfile">
+ <text><![CDATA[<filename class="headerfile">${1}</filename>]]></text>
+ <tag>headerfile</tag>
+ <description>filename, class headerfile</description>
+ </snippet>
+ <snippet id="libraryfile">
+ <text><![CDATA[<filename class="libraryfile">${1}</filename>]]></text>
+ <tag>libraryfile</tag>
+ <description>filename, class libraryfile</description>
+ </snippet>
+ <snippet id="partition">
+ <text><![CDATA[<filename class="partition">${1}</filename>]]></text>
+ <tag>partition</tag>
+ <description>filename, class partition</description>
+ </snippet>
+ <snippet id="symlink">
+ <text><![CDATA[<filename class="symlink">${1}</filename>]]></text>
+ <tag>symlink</tag>
+ <description>filename, class symlink</description>
+ </snippet>
+ <snippet id="cartridge">
+ <text><![CDATA[<medialabel class="cartridge">${1}</medialabel>]]></text>
+ <tag>cartridge</tag>
+ <description>medialabel, class cartridge</description>
+ </snippet>
+ <snippet id="cdrom">
+ <text><![CDATA[<medialabel class="cdrom">${1}</medialabel>]]></text>
+ <tag>cdrom</tag>
+ <description>medialabel, class cdrom</description>
+ </snippet>
+ <snippet id="disk">
+ <text><![CDATA[<medialabel class="disk">${1}</medialabel>]]></text>
+ <tag>disk</tag>
+ <description>medialabel, class disk</description>
+ </snippet>
+ <snippet id="tape">
+ <text><![CDATA[<medialabel class="tape">${1}</medialabel>]]></text>
+ <tag>tape</tag>
+ <description>medialabel, class tape</description>
+ </snippet>
+ <snippet id="daemon">
+ <text><![CDATA[<systemitem class="daemon">${1}</systemitem>]]></text>
+ <tag>daemon</tag>
+ <description>systemitem, class daemon</description>
+ </snippet>
+ <snippet id="domainname">
+ <text><![CDATA[<systemitem class="domainname">${1}</systemitem>]]></text>
+ <tag>domainname</tag>
+ <description>systemitem, class domainname</description>
+ </snippet>
+ <snippet id="etheraddress">
+ <text><![CDATA[<systemitem class="etheraddress">${1}</systemitem>]]></text>
+ <tag>etheraddress</tag>
+ <description>systemitem, class etheraddress</description>
+ </snippet>
+ <snippet id="eventhandler">
+ <text><![CDATA[<systemitem class="eventhandler">${1}</systemitem>]]></text>
+ <tag>eventhandler</tag>
+ <description>systemitem, class eventhandler</description>
+ </snippet>
+ <snippet id="event">
+ <text><![CDATA[<systemitem class="event">${1}</systemitem>]]></text>
+ <tag>event</tag>
+ <description>systemitem, class event</description>
+ </snippet>
+ <snippet id="filesystem">
+ <text><![CDATA[<systemitem class="filesystem">${1}</systemitem>]]></text>
+ <tag>filesystem</tag>
+ <description>systemitem, class filesystem</description>
+ </snippet>
+ <snippet id="fqdomainname">
+ <text><![CDATA[<systemitem class="fqdomainname">${1}</systemitem>]]></text>
+ <tag>fqdomainname</tag>
+ <description>systemitem, class fqdomainname</description>
+ </snippet>
+ <snippet id="groupname">
+ <text><![CDATA[<systemitem class="groupname">${1}</systemitem>]]></text>
+ <tag>groupname</tag>
+ <description>systemitem, class groupname</description>
+ </snippet>
+ <snippet id="ipaddress">
+ <text><![CDATA[<systemitem class="ipaddress">${1}</systemitem>]]></text>
+ <tag>ipaddress</tag>
+ <description>systemitem, class ipaddress</description>
+ </snippet>
+ <snippet id="library">
+ <text><![CDATA[<systemitem class="library">${1}</systemitem>]]></text>
+ <tag>library</tag>
+ <description>systemitem, class library</description>
+ </snippet>
+ <snippet id="macro">
+ <text><![CDATA[<systemitem class="macro">${1}</systemitem>]]></text>
+ <tag>macro</tag>
+ <description>systemitem, class macro</description>
+ </snippet>
+ <snippet id="netmask">
+ <text><![CDATA[<systemitem class="netmask">${1}</systemitem>]]></text>
+ <tag>netmask</tag>
+ <description>systemitem, class netmask</description>
+ </snippet>
+ <snippet id="newsgroup">
+ <text><![CDATA[<systemitem class="newsgroup">${1}</systemitem>]]></text>
+ <tag>newsgroup</tag>
+ <description>systemitem, class newsgroup</description>
+ </snippet>
+ <snippet id="osname">
+ <text><![CDATA[<systemitem class="osname">${1}</systemitem>]]></text>
+ <tag>osname</tag>
+ <description>systemitem, class osname</description>
+ </snippet>
+ <snippet id="process">
+ <text><![CDATA[<systemitem class="process">${1}</systemitem>]]></text>
+ <tag>process</tag>
+ <description>systemitem, class process</description>
+ </snippet>
+ <snippet id="protocol">
+ <text><![CDATA[<systemitem class="protocol">${1}</systemitem>]]></text>
+ <tag>protocol</tag>
+ <description>systemitem, class protocol</description>
+ </snippet>
+ <snippet id="resource">
+ <text><![CDATA[<systemitem class="resource">${1}</systemitem>]]></text>
+ <tag>resource</tag>
+ <description>systemitem, class resource</description>
+ </snippet>
+ <snippet id="server">
+ <text><![CDATA[<systemitem class="server">${1}</systemitem>]]></text>
+ <tag>server</tag>
+ <description>systemitem, class server</description>
+ </snippet>
+ <snippet id="service">
+ <text><![CDATA[<systemitem class="service">${1}</systemitem>]]></text>
+ <tag>service</tag>
+ <description>systemitem, class service</description>
+ </snippet>
+ <snippet id="systemname">
+ <text><![CDATA[<systemitem class="systemname">${1}</systemitem>]]></text>
+ <tag>systemname</tag>
+ <description>systemitem, class systemname</description>
+ </snippet>
+ <snippet id="username">
+ <text><![CDATA[<systemitem class="username">${1}</systemitem>]]></text>
+ <tag>username</tag>
+ <description>systemitem, class username</description>
+ </snippet>
+ <!-- XML-related Snippets: -->
+ <snippet id="include">
+ <text><![CDATA[<xi:include href="${1}.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />]]></text>
+ <tag>include</tag>
+ <description>xi:include</description>
+ </snippet>
+ <snippet id="fallback">
+ <text><![CDATA[<xi:fallback xmlns:xi="http://www.w3.org/2001/XInclude">
+ ${1}
+</xi:fallback>]]></text>
+ <tag>fallback</tag>
+ <description>xi:fallback</description>
+ </snippet>
+ <snippet id="xml">
+ <text><![CDATA[<?xml version='1.0' encoding='utf-8' ?>]]></text>
+ <tag>xml</tag>
+ <description>xml</description>
+ </snippet>
+ <snippet id="doctype">
+ <text><![CDATA[<!DOCTYPE ${1:chapter} PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+<!ENTITY % BOOK_ENTITIES SYSTEM "${2}.ent">
+%BOOK_ENTITIES;
+]>]]></text>
+ <tag>doctype</tag>
+ <description>DOCTYPE</description>
+ </snippet>
+ <snippet id="entity">
+ <text><![CDATA[<!ENTITY ${1} "${2}">]]></text>
+ <tag>entity</tag>
+ <description>ENTITY</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/fortran.xml b/plugins/snippets/data/fortran.xml
new file mode 100644
index 0000000..205abe6
--- /dev/null
+++ b/plugins/snippets/data/fortran.xml
@@ -0,0 +1,164 @@
+<?xml version='1.0' encoding='utf-8'?>
+<snippets language="fortran">
+ <snippet id="c">
+ <text><![CDATA[character(len=${1:10}) :: $0]]></text>
+ <tag>c</tag>
+ <description>character</description>
+ </snippet>
+ <snippet id="cl">
+ <text><![CDATA[close(${1:unit}, status='${2:keep}')]]></text>
+ <tag>cl</tag>
+ <description>close</description>
+ </snippet>
+ <snippet id="do">
+ <text><![CDATA[do ${1:i}=$2, $3, ${4:1}
+ ${0:source}
+end do]]></text>
+ <tag>do</tag>
+ <description>do ... end do</description>
+ </snippet>
+ <snippet id="func">
+ <text><![CDATA[function ${1:name}( ${2:parameter} )
+ ${3:integer/real ::} $1
+ ${4:integer/real ::} $2
+
+ ${0:source}
+
+ $1 = !result
+end function]]></text>
+ <tag>func</tag>
+ <description>function</description>
+ </snippet>
+ <snippet id="ifel">
+ <text><![CDATA[if( $1 ) then
+ ${2:source}
+else
+ ${0:source}
+end if]]></text>
+ <tag>ifel</tag>
+ <description>if ... else ... end if</description>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if( $1 ) then
+ ${0:source}
+end if]]></text>
+ <tag>if</tag>
+ <description>if ... end if</description>
+ </snippet>
+ <snippet id="i">
+ <text><![CDATA[integer(kind=${1:4}) :: $0]]></text>
+ <tag>i</tag>
+ <description>integer</description>
+ </snippet>
+ <snippet id="ida">
+ <text><![CDATA[integer(kind=${1:4}), dimension(${2::}), allocatable :: $0]]></text>
+ <tag>ida</tag>
+ <description>integerdimalloc</description>
+ </snippet>
+ <snippet id="id">
+ <text><![CDATA[integer(kind=${1:4}), dimension(${2::}) :: $0]]></text>
+ <tag>id</tag>
+ <description>integerdim</description>
+ </snippet>
+ <snippet id="l">
+ <text><![CDATA[logical(kind=${1:1}) :: $0]]></text>
+ <tag>l</tag>
+ <description>logical</description>
+ </snippet>
+ <snippet id="mod">
+ <text><![CDATA[module ${1:name}
+ implicit none
+ ${2:integer/real ::} $3
+
+ ${4:contains}
+
+ ${0:source}
+end module]]></text>
+ <tag>mod</tag>
+ <description>module</description>
+ </snippet>
+ <snippet id="op">
+ <text><![CDATA[open(${1:unit}, file='${2:name}', status='${3:new}')]]></text>
+ <tag>op</tag>
+ <description>open</description>
+ </snippet>
+ <snippet id="prog">
+ <text><![CDATA[program ${1:name}
+ implicit none
+
+ ${0:source}
+end program]]></text>
+ <tag>prog</tag>
+ <description>program</description>
+ </snippet>
+ <snippet id="re">
+ <text><![CDATA[read(unit=${1:*},fmt=${2:*}) $0]]></text>
+ <tag>re</tag>
+ <description>read</description>
+ </snippet>
+ <snippet id="r">
+ <text><![CDATA[real(kind=${1:8}) :: $0]]></text>
+ <tag>r</tag>
+ <description>real</description>
+ </snippet>
+ <snippet id="rda">
+ <text><![CDATA[real(kind=${1:8}), dimension(${2::}), allocatable :: $0]]></text>
+ <tag>rda</tag>
+ <description>realdimalloc</description>
+ </snippet>
+ <snippet id="rd">
+ <text><![CDATA[real(kind=${1:8}), dimension(${2::}) :: $0]]></text>
+ <tag>rd</tag>
+ <description>realdim</description>
+ </snippet>
+ <snippet id="rec">
+ <text><![CDATA[recursive function ${1:name}( ${2:parameter} ) result( ${3:res} )
+ ${4:integer/real ::} $3
+ ${5:integer/real ::} $2
+
+ ${0:source}
+
+ $3 = !result
+end function]]></text>
+ <tag>rec</tag>
+ <description>recursivfunc</description>
+ </snippet>
+ <snippet id="sel">
+ <text><![CDATA[select case( $1 )
+ case( $2 )
+ ${3:source}
+ case default
+ ${0:source}
+end select]]></text>
+ <tag>sel</tag>
+ <description>select</description>
+ </snippet>
+ <snippet id="sub">
+ <text><![CDATA[subroutine ${1:name}( ${2:parameter} )
+ ${3:integer/real ::} $2
+
+ ${0:source}
+end subroutine]]></text>
+ <tag>sub</tag>
+ <description>subroutine</description>
+ </snippet>
+ <snippet id="t">
+ <text><![CDATA[type :: ${1:name}
+ ${2:integer/real ::} $0
+end type $1]]></text>
+ <tag>t</tag>
+ <description>type</description>
+ </snippet>
+ <snippet id="dow">
+ <text><![CDATA[do while( ${1} )
+ ${0:source}
+end do]]></text>
+ <tag>dow</tag>
+ <description>while</description>
+ </snippet>
+ <snippet id="wr">
+ <text><![CDATA[write(unit=${1:*},fmt=${2:*}) "$3", $0]]></text>
+ <tag>wr</tag>
+ <description>write</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/global.xml b/plugins/snippets/data/global.xml
new file mode 100644
index 0000000..afe3c0b
--- /dev/null
+++ b/plugins/snippets/data/global.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets/>
diff --git a/plugins/snippets/data/haskell.xml b/plugins/snippets/data/haskell.xml
new file mode 100644
index 0000000..54a8e7d
--- /dev/null
+++ b/plugins/snippets/data/haskell.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Haskell">
+ <snippet id="mod">
+ <text><![CDATA[module ${1:Main} where
+ $0]]></text>
+ <description>module</description>
+ <tag>mod</tag>
+ </snippet>
+ <snippet id="\">
+ <text><![CDATA[\\${1:t} -> ${1:t}]]></text>
+ <description>\t -&gt; t</description>
+ <tag>\</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/html.xml b/plugins/snippets/data/html.xml
new file mode 100644
index 0000000..dd9faea
--- /dev/null
+++ b/plugins/snippets/data/html.xml
@@ -0,0 +1,252 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="HTML">
+ <snippet id="doctype">
+ <text><![CDATA[<!DOCTYPE html>
+]]></text>
+ <description>HTML5 Doctype</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-1">
+ <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+]]></text>
+ <description>XHTML — 1.0 Frameset</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-2">
+ <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+]]></text>
+ <description>XHTML — 1.0 Strict</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-3">
+ <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+]]></text>
+ <description>XHTML — 1.0 Transitional</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-4">
+ <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+]]></text>
+ <description>XHTML — 1.1</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-5">
+ <text><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+]]></text>
+ <description>HTML — 4.0 Transitional</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-6">
+ <text><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+]]></text>
+ <description>HTML — 4.01 Strict</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="author">
+ <text><![CDATA[<meta name="author" content="${1:author}" />
+$0]]></text>
+ <tag>author</tag>
+ <description>Author</description>
+ </snippet>
+ <snippet id="date">
+ <text><![CDATA[<meta name="date" content="$<1: import time; return time.strftime("%Y-%m-%d") >" />
+$0]]></text>
+ <tag>date</tag>
+ <description>Date</description>
+ </snippet>
+ <snippet id="ref">
+ <text><![CDATA[<a href="${1:http://somesite.com/}">${2:$GEDIT_SELECTED_TEXT}</a>
+]]></text>
+ <accelerator><![CDATA[<Shift><Alt>l]]></accelerator>
+ <description>Wrap Selection as Link</description>
+ <tag>ref</tag>
+ </snippet>
+ <snippet id="open/close">
+ <text><![CDATA[<${1:p}>$GEDIT_SELECTED_TEXT</${1}>]]></text>
+ <accelerator><![CDATA[<Shift><Alt>w]]></accelerator>
+ <description>Wrap Selection in Open/Close Tag</description>
+ </snippet>
+ <snippet id="mailto">
+ <text><![CDATA[<a href="mailto:${1:joe@example.com}?subject=${2:feedback}">${3:email me}</a> $0]]></text>
+ <description>Mail Anchor</description>
+ <tag>mailto</tag>
+ </snippet>
+ <snippet id="base">
+ <text><![CDATA[<base href="$1" ${2}/>$0]]></text>
+ <description>Base</description>
+ <tag>base</tag>
+ </snippet>
+ <snippet id="body">
+ <text><![CDATA[<body id="${1:ID}">
+ $0
+</body>]]></text>
+ <description>Body</description>
+ <tag>body</tag>
+ </snippet>
+ <snippet id="br">
+ <text><![CDATA[<br />
+$0]]></text>
+ <accelerator><![CDATA[<Shift><Control>space]]></accelerator>
+ <description>Br</description>
+ </snippet>
+ <snippet id="button">
+ <text><![CDATA[<button type="button" name="${1:name}" value="${2:caption}" onclick="$3" />$4
+]]></text>
+ <tag>button</tag>
+ <description>Button</description>
+ </snippet>
+ <snippet id="div">
+ <text><![CDATA[<div ${1}>
+ ${0:$GEDIT_SELECTED_TEXT}
+</div>]]></text>
+ <description>Div</description>
+ <tag>div</tag>
+ </snippet>
+ <snippet id="file">
+ <text><![CDATA[<input type="file" name="${1:name}" size="$2" accept="$3" />$0
+]]></text>
+ <tag>file</tag>
+ <description>File</description>
+ </snippet>
+ <snippet id="form">
+ <text><![CDATA[<form action="${1}" method="${2:get}">
+ $0
+
+ <p><input type="submit" value="${3:Continue &rarr;}" /></p>
+</form>]]></text>
+ <description>Form</description>
+ <tag>form</tag>
+ </snippet>
+ <snippet id="h">
+ <text><![CDATA[<h${1:1} id="${2}">${3:$GEDIT_SELECTED_TEXT}</h${1}>
+$0]]></text>
+ <description>Heading</description>
+ <tag>h</tag>
+ </snippet>
+ <snippet id="head">
+ <text><![CDATA[<head>
+ <meta charset="utf-8">
+ <title>${1:Page Title}</title>
+ $0
+</head>]]></text>
+ <description>Head</description>
+ <tag>head</tag>
+ </snippet>
+ <snippet id="image">
+ <text><![CDATA[<img src="${1:path/to/file}" alt="${2:description}" title="${3:tool tip}" width="$4" height="$5" />$0]]></text>
+ <tag>img</tag>
+ <description>Image</description>
+ </snippet>
+ <snippet id="input">
+ <text><![CDATA[<input type="${1:[button,checkbox,color,date,datetime,datetime-local,email,file,hidden,image,month,number,password,radio,range,reset,search,submit,tel,text,url,week]}" name="${2:some_name}" value="${3:default_value}" placeholder="${4:default_placeholder}" id="$5" />]]></text>
+ <description>Input</description>
+ <tag>input</tag>
+ </snippet>
+ <snippet id="li">
+ <text><![CDATA[<li>$1</li>$0]]></text>
+ <tag>li</tag>
+ <description>List Element</description>
+ </snippet>
+ <snippet id="link">
+ <text><![CDATA[<link rel="${1:stylesheet}" href="${2:/css/master.css}">
+$0]]></text>
+ <description>Link</description>
+ <tag>link</tag>
+ </snippet>
+ <snippet id="meta">
+ <text><![CDATA[<meta name="${1:name}" content="${2:content}" />
+$0]]></text>
+ <description>Meta</description>
+ <tag>meta</tag>
+ </snippet>
+ <snippet id="nbsp">
+ <text><![CDATA[&nbsp;]]></text>
+ <accelerator><![CDATA[<Control><Alt>space]]></accelerator>
+ <description>Non-Breaking Space</description>
+ </snippet>
+ <snippet id="noscript">
+ <text><![CDATA[<noscript>$1</noscript>$0]]></text>
+ <tag>noscript</tag>
+ <description>Noscript</description>
+ </snippet>
+ <snippet id="option">
+ <text><![CDATA[<option value="${1:value}">$2</option>$0]]></text>
+ <tag>option</tag>
+ <description>Option</description>
+ </snippet>
+ <snippet id="script">
+ <text><![CDATA[<script type="text/javascript">
+ $0
+</script>]]></text>
+ <description>Script</description>
+ <tag>script</tag>
+ </snippet>
+ <snippet id="scriptsrc">
+ <text><![CDATA[<script src="$1" type="text/javascript"></script>]]></text>
+ <description>Script With External Source</description>
+ <tag>scriptsrc</tag>
+ </snippet>
+ <snippet id="select">
+ <text><![CDATA[<select name="${1:name}">
+ <option value="${2:value}">$3</option>
+ $4
+</select>$0
+]]></text>
+ <tag>select</tag>
+ <description>Select</description>
+ </snippet>
+ <snippet id="span">
+ <text><![CDATA[<span ${1}>$2</span>$0]]></text>
+ <tag>span</tag>
+ <description>Span</description>
+ </snippet>
+ <snippet id="style">
+ <text><![CDATA[<style type="text/css" media="screen">
+ $0
+</style>
+]]></text>
+ <description>Style</description>
+ <tag>style</tag>
+ </snippet>
+ <snippet id="table">
+ <text><![CDATA[<table border="${1:0}" cellspacing="${2:0}" cellpadding="${3:0}">
+ <tr><th>${4:Header}</th></tr>
+ <tr><td>${5:Data}</td></tr>
+ $0
+</table>]]></text>
+ <description>Table</description>
+ <tag>table</tag>
+ </snippet>
+ <snippet id="textarea">
+ <text><![CDATA[<textarea name="${1:Name}" rows="${2:8}" cols="${3:40}">$0</textarea>]]></text>
+ <description>Text Area</description>
+ <tag>textarea</tag>
+ </snippet>
+ <snippet id="title">
+ <text><![CDATA[<title>${1:Page Title}</title>
+$0]]></text>
+ <description>Title</description>
+ <tag>title</tag>
+ </snippet>
+ <snippet id="tr">
+ <text><![CDATA[<tr><td>$1</td></tr>
+$0]]></text>
+ <tag>tr</tag>
+ <description>Table Row</description>
+ </snippet>
+ <snippet id="ul">
+ <text><![CDATA[<ul>
+ <li>$1</li>
+ <li>$2</li>
+ $3
+</ul>
+$0]]></text>
+ <tag>ul</tag>
+ <description>Unordered List</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/idl.xml b/plugins/snippets/data/idl.xml
new file mode 100644
index 0000000..2b6ef30
--- /dev/null
+++ b/plugins/snippets/data/idl.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="IDL">
+ <snippet id="mod">
+ <text><![CDATA[module ${1:name}
+{
+ $0
+};
+]]></text>
+ <tag>mod</tag>
+ <description>Module</description>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[interface ${1:name}
+{
+ $0
+};
+]]></text>
+ <tag>if</tag>
+ <description>Interface</description>
+ </snippet>
+ <snippet id="str">
+ <text><![CDATA[struct ${1:name}
+{
+ $0
+};
+]]></text>
+ <tag>str</tag>
+ <description>Struct</description>
+ </snippet>
+ <snippet id="exc">
+ <text><![CDATA[exception ${1:name}
+{
+ $0
+};
+]]></text>
+ <tag>exc</tag>
+ <description>Exception</description>
+ </snippet>
+ <snippet id="seq">
+ <text><![CDATA[sequence<${1:type}> ]]></text>
+ <tag>seq</tag>
+ <description>Sequence</description>
+ </snippet>
+ <snippet id="tseq">
+ <text><![CDATA[typedef sequence<${1:type}> ${0:newtype};]]></text>
+ <tag>tseq</tag>
+ <description>Typedef Sequence</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/java.xml b/plugins/snippets/data/java.xml
new file mode 100644
index 0000000..f7f11c0
--- /dev/null
+++ b/plugins/snippets/data/java.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Java">
+ <snippet id="cd">
+ <text><![CDATA[private static final ${1:String} ${2:var} = "$0";]]></text>
+ <description>const def</description>
+ <tag>cd</tag>
+ </snippet>
+ <snippet id="ife">
+ <text><![CDATA[if ($1) { // $2
+
+ $0
+
+} else { // $3
+
+
+
+}
+
+]]></text>
+ <description>if .. else</description>
+ <tag>ife</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if ($1) { // $2
+ $0
+}]]></text>
+ <description>if</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="log">
+ <text><![CDATA[/** Logger for this class and subclasses. */
+protected final Log log = LogFactory.getLog(getClass());
+]]></text>
+ <description>logger</description>
+ <tag>log</tag>
+ </snippet>
+ <snippet id="tcf">
+ <text><![CDATA[try {
+ $2
+} catch (${1:Exception} e) {
+ $3
+} finally {
+ $4
+}
+$0]]></text>
+ <description>try .. catch .. finally</description>
+ <tag>tcf</tag>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while ($1) { // $2
+ $0
+}]]></text>
+ <description>while statement</description>
+ <tag>while</tag>
+ </snippet>
+ <snippet id="main">
+ <text><![CDATA[public static void main(String[] args) {
+ ${1:System.exit(0)};
+}]]></text>
+ <description>main</description>
+ <tag>main</tag>
+ </snippet>
+ <snippet id="sout">
+ <text><![CDATA[System.out.println("${1}");
+$0
+]]></text>
+ <description>System.out.println</description>
+ <tag>sout</tag>
+ </snippet>
+ <snippet id="try/catch">
+ <text><![CDATA[try {
+ $GEDIT_SELECTED_TEXT
+}
+catch (Exception e) {
+ ${1:e.printStackTrace();}
+}
+$0]]></text>
+ <accelerator><![CDATA[<Shift><Alt>t]]></accelerator>
+ <description>Wrap Selection in Try/Catch</description>
+ </snippet>
+ <snippet id="tc">
+ <text><![CDATA[try {
+ $2
+} catch (${1:Exception} e) {
+ $3
+}
+$0]]></text>
+ <tag>tc</tag>
+ <description>try .. catch</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/javascript.xml b/plugins/snippets/data/javascript.xml
new file mode 100644
index 0000000..b55c5b3
--- /dev/null
+++ b/plugins/snippets/data/javascript.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="js">
+ <snippet id="fun">
+ <text><![CDATA[function ${1:function_name}(${2:first_argument}) {
+ $0
+}]]></text>
+ <description>function</description>
+ <tag>fun</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/lang/snippets.lang b/plugins/snippets/data/lang/snippets.lang
new file mode 100644
index 0000000..7b755cd
--- /dev/null
+++ b/plugins/snippets/data/lang/snippets.lang
@@ -0,0 +1,160 @@
+<?xml version="1.0"?>
+<!--
+
+ Author: Jesse van den Kieboom <jesse@icecrew.nl>
+ Copyright (C) 2007-2008 Jesse van den Kieboom <jesse@icecrew.nl>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+-->
+<language id="snippets" name="Snippets" hidden="true" version="2.0">
+ <styles>
+ <style id="placeholder-bounds" name="Placeholder begin and end" map-to="def:function"/>
+ <style id="default-value" name="Default Value" map-to="def:string"/>
+ <style id="single-placeholder" name="Single Placeholder" map-to="def:decimal"/>
+ <style id="shell-placeholder" name="Shell Placeholder" map-to="def:preprocessor"/>
+ <style id="python-placeholder" name="Python Placeholder" map-to="def:preprocessor"/>
+ <style id="regex-placeholder" name="Regular Expression Placeholder" map-to="def:preprocessor"/>
+ <style id="tabstop" name="Tabstop" map-to="def:decimal"/>
+ <style id="placeholder-ref" name="Placeholder Reference" map-to="def:decimal"/>
+ <style id="placeholder-def" name="Placeholder Default" map-to="def:string"/>
+ <style id="escape" name="Escape" map-to="def:special-char"/>
+ <style id="environmental-var" name="Environmental Variable" map-to="def:string"/>
+ <style id="seperator" name="Seperator" map-to="def:shebang"/>
+ <style id="regex-pattern" name="Regular Expression Pattern" map-to="def:string"/>
+ <style id="replace-pattern" name="Regular Expression Replace Pattern" map-to="def:string"/>
+ <style id="modifier" name="Modifier" map-to="def:keyword"/>
+ </styles>
+
+ <definitions>
+ <define-regex id="number">[0-9]+</define-regex>
+ <define-regex id="tabstop">\s*((\%{number})(:))</define-regex>
+ <define-regex id="number-list" extended="true">\s*(\[(\%{number}(,\%{number})*)\](:))</define-regex>
+ <define-regex id="environment">\$[A-Z_]+</define-regex>
+ <define-regex id="regex-pattern">((?:\\[/]|\\}|[^/}])+)</define-regex>
+
+ <context id="escape" style-ref="escape">
+ <match>\\\$</match>
+ </context>
+ <context id="single-placeholder" style-ref="single-placeholder">
+ <match>\$\%{number}|\${\%{number}}</match>
+ </context>
+ <context id="simple-placeholder-def" style-ref="default-value">
+ <start>\${\%{tabstop}</start>
+ <end>}</end>
+ <include>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/>
+ <context sub-pattern="2" where="start" style-ref="tabstop"/>
+ <context sub-pattern="3" where="start" style-ref="seperator"/>
+ <context>
+ <match>\\}</match>
+ </context>
+ <context ref="escape"/>
+ <context ref="environmental-variable"/>
+ </include>
+ </context>
+ <context id="simple-placeholder">
+ <include>
+ <context ref="single-placeholder"/>
+ <context ref="simple-placeholder-def"/>
+ </include>
+ </context>
+ <context id="shell-placeholder-contents">
+ <include>
+ <context ref="escape"/>
+ <context ref="environmental-variable"/>
+ <context ref="single-placeholder"/>
+ </include>
+ </context>
+ <context id="shell-placeholder">
+ <include>
+ <context style-ref="shell-placeholder">
+ <start>\$\(\%{tabstop}?</start>
+ <end>\)</end>
+ <include>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/>
+ <context sub-pattern="2" where="start" style-ref="tabstop"/>
+ <context sub-pattern="3" where="start" style-ref="seperator"/>
+ <context ref="shell-placeholder-contents"/>
+ <context>
+ <match>\\\)</match>
+ </context>
+ </include>
+ </context>
+ <context style-ref="shell-placeholder">
+ <start>`\%{tabstop}?</start>
+ <end>`</end>
+ <include>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/>
+ <context sub-pattern="2" where="start" style-ref="tabstop"/>
+ <context sub-pattern="3" where="start" style-ref="seperator"/>
+ <context ref="shell-placeholder-contents"/>
+ <context>
+ <match>\\`</match>
+ </context>
+ </include>
+ </context>
+ </include>
+ </context>
+ <context id="python-placeholder">
+ <start>\$&lt;\%{tabstop}?\%{number-list}?</start>
+ <end>&gt;</end>
+ <include>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/>
+ <context sub-pattern="2" where="start" style-ref="tabstop"/>
+ <context sub-pattern="3" where="start" style-ref="seperator"/>
+ <context sub-pattern="5" where="start" style-ref="tabstop"/>
+ <context sub-pattern="7" where="start" style-ref="seperator"/>
+ <context>
+ <match>\\&gt;</match>
+ </context>
+ <context ref="escape"/>
+ <context ref="environmental-variable"/>
+ <context ref="single-placeholder"/>
+ <context ref="python:python"/>
+ </include>
+ </context>
+ <context id="regex-placeholder" style-ref="regex-placeholder">
+ <match>(\${)\%{tabstop}?(?:\s*(?:(\%{number})|(\%{environment})))/\%{regex-pattern}/\%{regex-pattern}(?:[/]([a-zA-Z]*))?(})</match>
+ <include>
+ <context sub-pattern="1" style-ref="placeholder-bounds"/>
+ <context sub-pattern="10" style-ref="placeholder-bounds"/>
+ <context sub-pattern="3" style-ref="tabstop"/>
+ <context sub-pattern="4" style-ref="seperator"/>
+ <context sub-pattern="5" style-ref="tabstop"/>
+ <context sub-pattern="6" style-ref="environmental-var"/>
+ <context sub-pattern="7" style-ref="regex-pattern"/>
+ <context sub-pattern="8" style-ref="replace-pattern"/>
+ <context sub-pattern="9" style-ref="modifier"/>
+ </include>
+ </context>
+ <context id="environmental-variable" style-ref="environmental-var">
+ <match>\%{environment}</match>
+ </context>
+ <context id="snippets">
+ <include>
+ <context ref="escape"/>
+ <context ref="regex-placeholder"/>
+ <context ref="simple-placeholder"/>
+ <context ref="shell-placeholder"/>
+ <context ref="python-placeholder"/>
+ <context ref="environmental-variable"/>
+ </include>
+ </context>
+ </definitions>
+</language>
diff --git a/plugins/snippets/data/latex.xml b/plugins/snippets/data/latex.xml
new file mode 100644
index 0000000..71672ec
--- /dev/null
+++ b/plugins/snippets/data/latex.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="LaTeX">
+ <snippet id="command">
+ <text><![CDATA[{\\${1:bf} $GEDIT_SELECTED_TEXT}]]></text>
+ <accelerator><![CDATA[<Shift><Alt>w]]></accelerator>
+ <description>Wrap Selection in Command</description>
+ </snippet>
+ <snippet id="$">
+ <text><![CDATA[\[
+ $1
+\]]]></text>
+ <description>Displaymath</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="itd">
+ <text><![CDATA[\item[${1:description}] ${0:item}]]></text>
+ <description>\item[description]</description>
+ <tag>itd</tag>
+ </snippet>
+ <snippet id="sec">
+ <text><![CDATA[\section{${1:section name}}\label{${2:label}}
+]]></text>
+ <description>Section</description>
+ <tag>sec</tag>
+ </snippet>
+ <snippet id="sub">
+ <text><![CDATA[\subsection{${1:subsection name}}\label{${2:label}}
+]]></text>
+ <description>Sub Section</description>
+ <tag>sub</tag>
+ </snippet>
+ <snippet id="ssub">
+ <text><![CDATA[\subsubsection{${1:subsubsection name}}\label{${2:label}}
+]]></text>
+ <description>Sub Sub Section</description>
+ <tag>ssub</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/mallard.xml b/plugins/snippets/data/mallard.xml
new file mode 100644
index 0000000..bb08b43
--- /dev/null
+++ b/plugins/snippets/data/mallard.xml
@@ -0,0 +1,316 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!--
+ Mallard 1.0 snippets according to Mallard 1.0 DRAFT (as of 2013-02-11)
+ Copyright (C) 2013 Jaromir Hradilek
+
+ Home Page: https://github.com/jhradilek/gedit-snippets
+ Last Change: 12 February 2013
+-->
+<snippets language="mallard">
+ <snippet id="app">
+ <text><![CDATA[<app>${1}</app>]]></text>
+ <tag>app</tag>
+ <description>app</description>
+ </snippet>
+ <snippet id="cite">
+ <text><![CDATA[<cite>${1}</cite>]]></text>
+ <tag>cite</tag>
+ <description>cite</description>
+ </snippet>
+ <snippet id="cmd">
+ <text><![CDATA[<cmd>${1}</cmd>]]></text>
+ <tag>cmd</tag>
+ <description>cmd</description>
+ </snippet>
+ <snippet id="code">
+ <text><![CDATA[<code>${1}</code>]]></text>
+ <tag>code</tag>
+ <description>code</description>
+ </snippet>
+ <snippet id="col">
+ <text><![CDATA[<col/>${1}]]></text>
+ <tag>col</tag>
+ <description>col</description>
+ </snippet>
+ <snippet id="colgroup">
+ <text><![CDATA[<colgroup>${1}</colgroup>]]></text>
+ <tag>colgroup</tag>
+ <description>colgroup</description>
+ </snippet>
+ <snippet id="comment">
+ <text><![CDATA[<comment>
+ ${1}
+</comment>]]></text>
+ <tag>comment</tag>
+ <description>comment</description>
+ </snippet>
+ <snippet id="credit">
+ <text><![CDATA[<credit type="${1:author}">
+ ${2}
+</credit>]]></text>
+ <tag>credit</tag>
+ <description>credit</description>
+ </snippet>
+ <snippet id="desc">
+ <text><![CDATA[<desc>${1}</desc>]]></text>
+ <tag>desc</tag>
+ <description>desc</description>
+ </snippet>
+ <snippet id="em">
+ <text><![CDATA[<em>${1}</em>]]></text>
+ <tag>em</tag>
+ <description>em</description>
+ </snippet>
+ <snippet id="email">
+ <text><![CDATA[<email>${1}</email>]]></text>
+ <tag>email</tag>
+ <description>email</description>
+ </snippet>
+ <snippet id="example">
+ <text><![CDATA[<example>
+ ${1}
+</example>]]></text>
+ <tag>example</tag>
+ <description>example</description>
+ </snippet>
+ <snippet id="figure">
+ <text><![CDATA[<figure>
+ ${1}
+</figure>]]></text>
+ <tag>figure</tag>
+ <description>figure</description>
+ </snippet>
+ <snippet id="file">
+ <text><![CDATA[<file>${1}</file>]]></text>
+ <tag>file</tag>
+ <description>file</description>
+ </snippet>
+ <snippet id="gui">
+ <text><![CDATA[<gui>${1}</gui>]]></text>
+ <tag>gui</tag>
+ <description>gui</description>
+ </snippet>
+ <snippet id="guiseq">
+ <text><![CDATA[<guiseq>${1}</guiseq>]]></text>
+ <tag>guiseq</tag>
+ <description>guiseq</description>
+ </snippet>
+ <snippet id="info">
+ <text><![CDATA[<info>
+ ${1}
+</info>]]></text>
+ <tag>info</tag>
+ <description>info</description>
+ </snippet>
+ <snippet id="input">
+ <text><![CDATA[<input>${1}</input>]]></text>
+ <tag>input</tag>
+ <description>input</description>
+ </snippet>
+ <snippet id="item">
+ <text><![CDATA[<item>${1}</item>]]></text>
+ <tag>item</tag>
+ <description>item</description>
+ </snippet>
+ <snippet id="key">
+ <text><![CDATA[<key>${1}</key>]]></text>
+ <tag>key</tag>
+ <description>key</description>
+ </snippet>
+ <snippet id="keyseq">
+ <text><![CDATA[<keyseq type="${1:combo}">${2}</keyseq>]]></text>
+ <tag>keyseq</tag>
+ <description>keyseq</description>
+ </snippet>
+ <snippet id="license">
+ <text><![CDATA[<license href="${1}">
+ ${2}
+</license>]]></text>
+ <tag>license</tag>
+ <description>license</description>
+ </snippet>
+ <snippet id="link">
+ <text><![CDATA[<link type="${1:guide}" xref="${2:index}" group="${3}"/>]]></text>
+ <tag>link</tag>
+ <description>link</description>
+ </snippet>
+ <snippet id="links">
+ <text><![CDATA[<links type="${1:topic}" groups="${2}">
+ ${3}
+</links>]]></text>
+ <tag>links</tag>
+ <description>links</description>
+ </snippet>
+ <snippet id="list">
+ <text><![CDATA[<list type="${1:disc}">
+ ${2}
+</list>]]></text>
+ <tag>list</tag>
+ <description>list</description>
+ </snippet>
+ <snippet id="listing">
+ <text><![CDATA[<listing>
+ ${1}
+</listing>]]></text>
+ <tag>listing</tag>
+ <description>listing</description>
+ </snippet>
+ <snippet id="media">
+ <text><![CDATA[<media type="${1:image}" mime="${2:image/png}" src="${3}">
+ ${4}
+</media>]]></text>
+ <tag>media</tag>
+ <description>media</description>
+ </snippet>
+ <snippet id="name">
+ <text><![CDATA[<name>${1}</name>]]></text>
+ <tag>name</tag>
+ <description>name</description>
+ </snippet>
+ <snippet id="note">
+ <text><![CDATA[<note style="${1:advanced}">
+ ${2}
+</note>]]></text>
+ <tag>note</tag>
+ <description>note</description>
+ </snippet>
+ <snippet id="output">
+ <text><![CDATA[<output>${1}</output>]]></text>
+ <tag>output</tag>
+ <description>output</description>
+ </snippet>
+ <snippet id="p">
+ <text><![CDATA[<p>${1}</p>]]></text>
+ <tag>p</tag>
+ <description>p</description>
+ </snippet>
+ <snippet id="page">
+ <text><![CDATA[<page xmlns="http://projectmallard.org/1.0/" type="${1:topic}" id="${2}">
+ ${3}
+</page>]]></text>
+ <tag>page</tag>
+ <description>page</description>
+ </snippet>
+ <snippet id="quote">
+ <text><![CDATA[<quote>
+ ${1}
+</quote>]]></text>
+ <tag>quote</tag>
+ <description>quote</description>
+ </snippet>
+ <snippet id="revision">
+ <text><![CDATA[<revision version="${1:0.1}" date="$(2:date +%Y-%m-%d)" status="${3:stub}"/>]]></text>
+ <tag>revision</tag>
+ <description>revision</description>
+ </snippet>
+ <snippet id="screen">
+ <text><![CDATA[<screen>${1}</screen>]]></text>
+ <tag>screen</tag>
+ <description>screen</description>
+ </snippet>
+ <snippet id="section">
+ <text><![CDATA[<section id="${1}">
+ ${2}
+</section>]]></text>
+ <tag>section</tag>
+ <description>section</description>
+ </snippet>
+ <snippet id="span">
+ <text><![CDATA[<span>${1}</span>]]></text>
+ <tag>span</tag>
+ <description>span</description>
+ </snippet>
+ <snippet id="steps">
+ <text><![CDATA[<steps>
+ ${1}
+</steps>]]></text>
+ <tag>steps</tag>
+ <description>steps</description>
+ </snippet>
+ <snippet id="subtitle">
+ <text><![CDATA[<subtitle>${1}</subtitle>]]></text>
+ <tag>subtitle</tag>
+ <description>subtitle</description>
+ </snippet>
+ <snippet id="synopsis">
+ <text><![CDATA[<synopsis>
+ ${1}
+</synopsis>]]></text>
+ <tag>synopsis</tag>
+ <description>synopsis</description>
+ </snippet>
+ <snippet id="sys">
+ <text><![CDATA[<sys>${1}</sys>]]></text>
+ <tag>sys</tag>
+ <description>sys</description>
+ </snippet>
+ <snippet id="table">
+ <text><![CDATA[<table frame="${1:all}" rules="${2:all}" shade="${3:none}">
+ ${4}
+</table>]]></text>
+ <tag>table</tag>
+ <description>table</description>
+ </snippet>
+ <snippet id="tbody">
+ <text><![CDATA[<tbody>
+ ${1}
+</tbody>]]></text>
+ <tag>tbody</tag>
+ <description>tbody</description>
+ </snippet>
+ <snippet id="td">
+ <text><![CDATA[<td>${1}</td>]]></text>
+ <tag>td</tag>
+ <description>td</description>
+ </snippet>
+ <snippet id="terms">
+ <text><![CDATA[<terms>
+ ${1}
+</terms>]]></text>
+ <tag>terms</tag>
+ <description>terms</description>
+ </snippet>
+ <snippet id="tfoot">
+ <text><![CDATA[<tfoot>
+ ${1}
+</tfoot>]]></text>
+ <tag>tfoot</tag>
+ <description>tfoot</description>
+ </snippet>
+ <snippet id="thead">
+ <text><![CDATA[<thead>
+ ${1}
+</thead>]]></text>
+ <tag>thead</tag>
+ <description>thead</description>
+ </snippet>
+ <snippet id="title">
+ <text><![CDATA[<title>${1}</title>]]></text>
+ <tag>title</tag>
+ <description>title</description>
+ </snippet>
+ <snippet id="tr">
+ <text><![CDATA[<tr>
+ ${1}
+</tr>]]></text>
+ <tag>tr</tag>
+ <description>tr</description>
+ </snippet>
+ <snippet id="tree">
+ <text><![CDATA[<tree>
+ ${1}
+</tree>]]></text>
+ <tag>tree</tag>
+ <description>tree</description>
+ </snippet>
+ <snippet id="var">
+ <text><![CDATA[<var>${1}</var>]]></text>
+ <tag>var</tag>
+ <description>var</description>
+ </snippet>
+ <snippet id="years">
+ <text><![CDATA[<years>$(1:date +%Y)</years>]]></text>
+ <tag>years</tag>
+ <description>years</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/markdown.xml b/plugins/snippets/data/markdown.xml
new file mode 100644
index 0000000..e49209b
--- /dev/null
+++ b/plugins/snippets/data/markdown.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Markdown">
+ <snippet id="atx-header">
+ <text><![CDATA[${1:#} ${2:Header} $1
+
+]]></text>
+ <tag>h</tag>
+ <description>Header (atx-style)</description>
+ </snippet>
+
+ <snippet id="setext-header-1">
+ <text><![CDATA[${1:Header}
+===============
+
+]]></text>
+ <tag>Hs</tag>
+ <description>Header 1 (setext-style)</description>
+ </snippet>
+
+ <snippet id="setext-header-2">
+ <text><![CDATA[${1:Header}
+---------------
+
+]]></text>
+ <tag>hs</tag>
+ <description>Header 2 (setext-style)</description>
+ </snippet>
+
+ <snippet id="horizontal-rule">
+ <text><![CDATA[***************
+
+$0]]></text>
+ <tag>hr</tag>
+ <description>Horizontal Rule</description>
+ </snippet>
+
+ <snippet id="unordered-list">
+ <text><![CDATA[* $1
+* $2
+* $3
+* $4
+
+]]></text>
+ <tag>ul</tag>
+ <description>Unordered List</description>
+ </snippet>
+
+ <snippet id="ordered-list">
+ <text><![CDATA[1. $1
+2. $2
+3. $3
+4. $4
+
+]]></text>
+ <tag>ol</tag>
+ <description>Ordered List</description>
+ </snippet>
+
+ <snippet id="code-span">
+ <text><![CDATA[\`${1:$GEDIT_SELECTED_TEXT}\`]]></text>
+ <tag>code</tag>
+ <description>Wrap Selection as Code Span</description>
+ <accelerator><![CDATA[<Control><Alt>c]]></accelerator>
+ </snippet>
+
+ <snippet id="inline-link">
+ <text><![CDATA[[${1:$GEDIT_SELECTED_TEXT}](${2:URL})]]></text>
+ <tag>a</tag>
+ <description>Wrap Selection as Inline Link</description>
+ <accelerator><![CDATA[<Control><Alt>a]]></accelerator>
+ </snippet>
+
+ <snippet id="reference-link">
+ <text><![CDATA[[${1:$GEDIT_SELECTED_TEXT}][${2:link label}]]]></text>
+ <tag>aref</tag>
+ <description>Wrap Selection as Reference Link</description>
+ <accelerator><![CDATA[<Control><Alt>r]]></accelerator>
+ </snippet>
+
+ <snippet id="link-definition">
+ <text><![CDATA[[${1:link label}]: ${2:URL}
+]]></text>
+ <tag>adef</tag>
+ <description>Link Definition</description>
+ </snippet>
+
+ <snippet id="inline-image">
+ <text><![CDATA[![${1:alt text}](${2:URL})]]></text>
+ <tag>img</tag>
+ <description>Inline Image</description>
+ </snippet>
+
+ <snippet id="reference-image">
+ <text><![CDATA[![${1:alt text}][${2:image label}]]]></text>
+ <tag>iref</tag>
+ <description>Reference Image</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/perl.xml b/plugins/snippets/data/perl.xml
new file mode 100644
index 0000000..add148f
--- /dev/null
+++ b/plugins/snippets/data/perl.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Perl">
+ <snippet id="perl">
+ <text><![CDATA[#!/usr/bin/perl
+$0]]></text>
+ <tag>perl</tag>
+ <description>#!/usr/bin/perl</description>
+ </snippet>
+ <snippet id="ife">
+ <text><![CDATA[if ($1) {
+ ${2:# body...}
+} else {
+ ${3:# else...}
+}
+]]></text>
+ <description>Conditional if..else</description>
+ <tag>ife</tag>
+ </snippet>
+ <snippet id="ifee">
+ <text><![CDATA[if ($1) {
+ ${2:# body...}
+} elsif ($3) {
+ ${4:# elsif...}
+} else {
+ ${5:# else...}
+}
+]]></text>
+ <description>Conditional if..elsif..else</description>
+ <tag>ifee</tag>
+ </snippet>
+ <snippet id="xunless">
+ <text><![CDATA[${1:expression} unless ${2:condition};
+]]></text>
+ <description>Conditional one-line</description>
+ <tag>xunless</tag>
+ </snippet>
+ <snippet id="xif">
+ <text><![CDATA[${1:expression} if ${2:condition};
+]]></text>
+ <description>Conditional one-line</description>
+ <tag>xif</tag>
+ </snippet>
+ <snippet id="eval">
+ <text><![CDATA[eval {
+ ${1:# do something risky...}
+};
+if ($@) {
+ ${2:# handle failure...}
+}
+]]></text>
+ <description>Try/Except</description>
+ <tag>eval</tag>
+ </snippet>
+ <snippet id="fore">
+ <text><![CDATA[foreach ${1:my $${2:x} }(@${3:array}) {
+ ${4:# body...}
+}
+]]></text>
+ <description>Loop</description>
+ <tag>fore</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for (my $${1:var} = 0; $$1 < ${2:expression}; $$1++) {
+ ${3:# body...}
+}
+]]></text>
+ <description>Loop</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="sub">
+ <text><![CDATA[sub ${1:function_name} {
+ ${2:# body...}
+}
+]]></text>
+ <description>Function</description>
+ <tag>sub</tag>
+ </snippet>
+ <snippet id="hashpointer">
+ <text><![CDATA[ => ]]></text>
+ <accelerator><![CDATA[<Shift><Alt>l]]></accelerator>
+ <description>hash pointer</description>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if ($1) {
+ ${2:# body...}
+}
+]]></text>
+ <description>Conditional</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="xfore">
+ <text><![CDATA[${1:expression} foreach @${2:array};
+]]></text>
+ <description>Loop one-line</description>
+ <tag>xfore</tag>
+ </snippet>
+ <snippet id="xwhile">
+ <text><![CDATA[${1:expression} while ${2:condition};
+]]></text>
+ <description>Loop one-line</description>
+ <tag>xwhile</tag>
+ </snippet>
+ <snippet id="slurp">
+ <text><![CDATA[my $${1:var};
+{ local $/ = undef; local *FILE; open FILE, "<${2:file}"; $$1 = <FILE>; close FILE }
+]]></text>
+ <description>Read File</description>
+ <tag>slurp</tag>
+ </snippet>
+ <snippet id="unless">
+ <text><![CDATA[unless ($1) {
+ ${2:# body...}
+}
+]]></text>
+ <description>Conditional</description>
+ <tag>unless</tag>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while ($1) {
+ ${2:# body...}
+}
+]]></text>
+ <description>Loop</description>
+ <tag>while</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/php.xml b/plugins/snippets/data/php.xml
new file mode 100644
index 0000000..89b27e7
--- /dev/null
+++ b/plugins/snippets/data/php.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="PHP">
+ <snippet id="class">
+ <text><![CDATA[class ${1:ClassName}${2: extends AnotherClass}
+{
+ function __construct(${3:argument})
+ {
+ $0
+ }
+}]]></text>
+ <description>class ..</description>
+ <tag>class</tag>
+ </snippet>
+ <snippet id="$">
+ <text><![CDATA[\$_COOKIE['${1:variable}']]]></text>
+ <description>COOKIE['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="do">
+ <text><![CDATA[do {
+ $0
+} while (${1:$a <= 10});]]></text>
+ <description>do .. while ..</description>
+ <tag>do</tag>
+ </snippet>
+ <snippet id="elseif">
+ <text><![CDATA[elseif (${1:condition}) {
+ $0
+}]]></text>
+ <description>elseif ..</description>
+ <tag>elseif</tag>
+ </snippet>
+ <snippet id="else">
+ <text><![CDATA[else {
+ $0
+}]]></text>
+ <description>else ..</description>
+ <tag>else</tag>
+ </snippet>
+ <snippet id="$-1">
+ <text><![CDATA[\$_ENV['${1:variable}']]]></text>
+ <description>ENV['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-2">
+ <text><![CDATA[\$_FILES['${1:variable}']]]></text>
+ <description>FILES['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="foreach">
+ <text><![CDATA[foreach ($${1:variable} as $${2:key} => $${3:value}) {
+ $0
+}]]></text>
+ <description>foreach ..</description>
+ <tag>foreach</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for ($${1:i} = ${2:0}; $${1:i} < $3; $${1:i}++) {
+ $0
+}]]></text>
+ <description>for ..</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="function">
+ <text><![CDATA[${1:public }function ${2:FunctionName}($3)
+{
+ ${0:# code...}
+}]]></text>
+ <description>function ..</description>
+ <tag>function</tag>
+ </snippet>
+ <snippet id="$-3">
+ <text><![CDATA[\$_GET['${1:variable}']]]></text>
+ <description>GET['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="globals">
+ <text><![CDATA[\$GLOBALS['${1:variable}']${2: =} ${3:something} ${4:;}]]></text>
+ <description>$GLOBALS['..']</description>
+ <tag>globals</tag>
+ </snippet>
+ <snippet id="if?">
+ <text><![CDATA[$${1:retVal} = (${2:condition}) ? ${3:a} : ${4:b};]]></text>
+ <description>$.. =</description>
+ <tag>iff</tag>
+ </snippet>
+ <snippet id="ifelse">
+ <text><![CDATA[if (${1:condition}) {
+ ${2}
+} else {
+ ${3}
+}
+$0]]></text>
+ <description>if .. else ..</description>
+ <tag>ifelse</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if (${1:condition}) {
+ $0
+}]]></text>
+ <description>if ..</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="incl1">
+ <text><![CDATA[include_once('${1:file}');$0]]></text>
+ <description>include_once</description>
+ <tag>inclo</tag>
+ </snippet>
+ <snippet id="incl">
+ <text><![CDATA[include('${1:file}');$0]]></text>
+ <description>include</description>
+ <tag>incl</tag>
+ </snippet>
+ <snippet id="array">
+ <text><![CDATA[$${1:arrayName} = array('$2'${3:,});]]></text>
+ <description>$.. = array</description>
+ <tag>array</tag>
+ </snippet>
+ <snippet id="php">
+ <text><![CDATA[<?php
+
+ $0
+
+?>]]></text>
+ <description>&lt;?php .. ?&gt;</description>
+ <tag>php</tag>
+ </snippet>
+ <snippet id="$-4">
+ <text><![CDATA[\$_POST['${1:variable}']]]></text>
+ <description>POST['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="print">
+ <text><![CDATA[print "${1:string}"${2: . };]]></text>
+ <description>print ".."</description>
+ <tag>print</tag>
+ </snippet>
+ <snippet id="$-5">
+ <text><![CDATA[\$_REQUEST['${1:variable}']]]></text>
+ <description>REQUEST['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="req1">
+ <text><![CDATA[require_once('${1:file}');]]></text>
+ <description>require_once</description>
+ <tag>reqo</tag>
+ </snippet>
+ <snippet id="req">
+ <text><![CDATA[require('${1:file}');]]></text>
+ <description>require</description>
+ <tag>req</tag>
+ </snippet>
+ <snippet id="$-6">
+ <text><![CDATA[\$_SERVER['${1:variable}']]]></text>
+ <description>SERVER['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-7">
+ <text><![CDATA[\$_SESSION['${1:variable}']]]></text>
+ <description>SESSION['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="case">
+ <text><![CDATA[case '${1:variable}':
+ $0
+ break;]]></text>
+ <description>case ..</description>
+ <tag>case</tag>
+ </snippet>
+ <snippet id="switch">
+ <text><![CDATA[switch (${1:variable}) {
+ case '${2:value}':
+ ${3}
+ break;
+
+ $0
+
+ default:
+ ${4}
+ break;
+}]]></text>
+ <description>switch ..</description>
+ <tag>switch</tag>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while (${1:$a <= 10}) {
+ $0
+}]]></text>
+ <description>while ..</description>
+ <tag>while</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/python.xml b/plugins/snippets/data/python.xml
new file mode 100644
index 0000000..a25617b
--- /dev/null
+++ b/plugins/snippets/data/python.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Python">
+ <snippet id="py">
+ <text><![CDATA[#!/usr/bin/env python
+#-*- coding:utf-8 -*-
+
+$0]]></text>
+ <description>#!/usr/bin/env python</description>
+ <tag>py</tag>
+ </snippet>
+ <snippet id="def">
+ <text><![CDATA[def ${1:fname}(${2:self}):
+ ${3:pass}]]></text>
+ <description>New Function</description>
+ <tag>def</tag>
+ </snippet>
+ <snippet id="doc">
+ <text><![CDATA["""
+ $1
+"""
+$0]]></text>
+ <description>doc string</description>
+ <tag>doc</tag>
+ </snippet>
+ <snippet id="get">
+ <text><![CDATA[def get$1(self): return self._$1]]></text>
+ <description>New Get Method</description>
+ <tag>get</tag>
+ </snippet>
+ <snippet id="class">
+ <text><![CDATA[class ${1:ClassName} (${2:object}):
+
+ def __init__(self${3:,}):
+ ${4:pass}
+
+$0]]></text>
+ <description>New Class</description>
+ <tag>class</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for ${1:i} in ${2:xrange}(${3:count}):
+ $0]]></text>
+ <description>for loop</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="from">
+ <text><![CDATA[from $1 import $2
+$0]]></text>
+ <description>from</description>
+ <tag>from</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if ${1:condition}:
+ $0]]></text>
+ <description>if</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="elif">
+ <text><![CDATA[elif ${1:condition}:
+ $0]]></text>
+ <description>elif</description>
+ <tag>elif</tag>
+ </snippet>
+ <snippet id="else">
+ <text><![CDATA[else:
+ $0]]></text>
+ <description>else</description>
+ <tag>else</tag>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while ${1:condition}:
+ $0]]></text>
+ <tag>while</tag>
+ <description>while loop</description>
+ </snippet>
+ <snippet id="insert">
+ <text><![CDATA["${1:$GEDIT_SELECTED_TEXT}"]]></text>
+ <accelerator><![CDATA[<Control>2]]></accelerator>
+ <description>Inside String: Insert "…"</description>
+ </snippet>
+ <snippet id="insert-1">
+ <text><![CDATA['${1:$GEDIT_SELECTED_TEXT}']]></text>
+ <accelerator><![CDATA[<Control>apostrophe]]></accelerator>
+ <description>Inside String: Insert '…'</description>
+ </snippet>
+ <snippet id=".">
+ <text><![CDATA[self.]]></text>
+ <description>self</description>
+ <tag>.</tag>
+ </snippet>
+ <snippet id="set">
+ <text><![CDATA[def set$1(self, ${2:newValue}): self._$1 = $2]]></text>
+ <description>New Set Method</description>
+ <tag>set</tag>
+ </snippet>
+ <snippet id="try">
+ <text><![CDATA[try:
+ $1
+except ${2:Error}:
+ $0]]></text>
+ <tag>try</tag>
+ <description>Try... Except</description>
+ </snippet>
+ <snippet id="main">
+ <text><![CDATA[if __name__ == '__main__':
+ ${1:sys.exit(main())}
+
+$0]]></text>
+ <description>main</description>
+ <tag>main</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/rpmspec.xml b/plugins/snippets/data/rpmspec.xml
new file mode 100644
index 0000000..eb3396d
--- /dev/null
+++ b/plugins/snippets/data/rpmspec.xml
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!-- requires rpm-python package installed -->
+<snippets language="rpmspec">
+ <snippet id="ch">
+ <text><![CDATA[$<
+import rpm
+import datetime
+
+spec = rpm.spec($GEDIT_CURRENT_DOCUMENT_PATH)
+date = datetime.date.today().strftime("%a %b %d %Y")
+headers = spec.packages[0].header
+version = headers['Version']
+release = ".".join(headers['Release'].split(".")[:-1])
+packager = headers['Packager']
+newheader = "* %s %s - %s-%s\n- " % (date, packager, version, release)
+return newheader
+>
+]]></text>
+ <tag>ch</tag>
+ <description>changelog entry</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/ruby.xml b/plugins/snippets/data/ruby.xml
new file mode 100644
index 0000000..db13e69
--- /dev/null
+++ b/plugins/snippets/data/ruby.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Ruby">
+ <snippet id="forin">
+ <text><![CDATA[for ${1:element} in ${2:collection}
+ ${1:element}.$0
+end]]></text>
+ <description>for .. in .. end</description>
+ <tag>forin</tag>
+ </snippet>
+ <snippet id="inject">
+ <text><![CDATA[inject(${1:object}) { |${2:injection}, ${3:element}| $0 }]]></text>
+ <description>inject object</description>
+ <tag>inject</tag>
+ </snippet>
+ <snippet id="reject">
+ <text><![CDATA[reject { |${1:element}| ${1:element}.$0 }]]></text>
+ <description>reject element</description>
+ <tag>reject</tag>
+ </snippet>
+ <snippet id="select">
+ <text><![CDATA[select { |${1:element}| ${1:element}.$0 }]]></text>
+ <description>select element</description>
+ <tag>select</tag>
+ </snippet>
+ <snippet id="ife">
+ <text><![CDATA[if ${1:condition}
+ $2
+else
+ $3
+end]]></text>
+ <description>if .. else .. end</description>
+ <tag>ife</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if ${1:condition}
+ $0
+end]]></text>
+ <description>if .. end</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="case">
+ <text><![CDATA[case ${1:object}
+ when ${2:condition}
+ $0
+end]]></text>
+ <description>case .. end</description>
+ <tag>case</tag>
+ </snippet>
+ <snippet id="begin">
+ <text><![CDATA[begin
+ $1
+rescue ${2:Exception} => ${3:e}
+ $0
+end]]></text>
+ <description>begin .. rescue .. end</description>
+ <tag>begin</tag>
+ </snippet>
+ <snippet id="class">
+ <text><![CDATA[class ${1:class_name}
+ $0
+end]]></text>
+ <description>class .. end</description>
+ <tag>class</tag>
+ </snippet>
+ <snippet id="collecto">
+ <text><![CDATA[collect do |${1:element}|
+ ${1:element}.$0
+end]]></text>
+ <description>collect element do</description>
+ <tag>collecto</tag>
+ </snippet>
+ <snippet id="collect">
+ <text><![CDATA[collect { |${1:element}| ${1:element}.$0 }]]></text>
+ <description>collect element</description>
+ <tag>collect</tag>
+ </snippet>
+ <snippet id="def">
+ <text><![CDATA[def ${1:method_name}
+ $0
+end]]></text>
+ <description>def .. end</description>
+ <tag>def</tag>
+ </snippet>
+ <snippet id="do">
+ <text><![CDATA[do
+ $0
+end]]></text>
+ <description>do .. end</description>
+ <tag>do</tag>
+ </snippet>
+ <snippet id="doo">
+ <text><![CDATA[do |${1:object}|
+ $0
+end]]></text>
+ <description>do |object| .. end</description>
+ <tag>doo</tag>
+ </snippet>
+ <snippet id="eacho">
+ <text><![CDATA[each do |${1:element}|
+ ${1:element}.$0
+end]]></text>
+ <description>each element do</description>
+ <tag>eacho</tag>
+ </snippet>
+ <snippet id="each">
+ <text><![CDATA[each { |${1:element}| ${1:element}.$0 }]]></text>
+ <description>each element</description>
+ <tag>each</tag>
+ </snippet>
+ <snippet id="each_with_indexo">
+ <text><![CDATA[each_with_index do |${1:element}, ${2:idx}|
+ ${1:element}.$0
+end]]></text>
+ <description>each_with_index do</description>
+ <tag>eachwithindexo</tag>
+ </snippet>
+ <snippet id="each_with_index">
+ <text><![CDATA[each_with_index { |${1:element}, ${2:idx}| ${1:element}.$0 }]]></text>
+ <description>each_with_index</description>
+ <tag>eachwithindex</tag>
+ </snippet>
+ <snippet id=":">
+ <text><![CDATA[:${1:key} => ${2:"value"}${3:, }]]></text>
+ <description>hash pair</description>
+ <tag>:</tag>
+ </snippet>
+ <snippet id="hashpointer">
+ <text><![CDATA[ => ]]></text>
+ <accelerator><![CDATA[<Shift><Alt>l]]></accelerator>
+ <description>hash pointer</description>
+ </snippet>
+ <snippet id="injecto">
+ <text><![CDATA[inject(${1:object}) do |${2:injection}, ${3:element}|
+ $0
+end]]></text>
+ <description>inject object do</description>
+ <tag>injecto</tag>
+ </snippet>
+ <snippet id="rejecto">
+ <text><![CDATA[reject do |${1:element}|
+ ${1:element}.$0
+end]]></text>
+ <description>reject element do</description>
+ <tag>rejecto</tag>
+ </snippet>
+ <snippet id="selecto">
+ <text><![CDATA[select do |${1:element}|
+ ${1:element}.$0
+end]]></text>
+ <description>select element do</description>
+ <tag>selecto</tag>
+ </snippet>
+ <snippet id="unless">
+ <text><![CDATA[unless ${1:condition}
+ $0
+end]]></text>
+ <description>unless</description>
+ <tag>unless</tag>
+ </snippet>
+ <snippet id="when">
+ <text><![CDATA[when ${1:condition}
+ $0]]></text>
+ <description>when</description>
+ <tag>when</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/sh.xml b/plugins/snippets/data/sh.xml
new file mode 100644
index 0000000..b8fc0a6
--- /dev/null
+++ b/plugins/snippets/data/sh.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="sh">
+ <snippet id="elif">
+ <text><![CDATA[elif [[ ${1:condition} ]]; then
+ $0]]></text>
+ <description>elif ..</description>
+ <tag>elif</tag>
+ </snippet>
+ <snippet id="case">
+ <text><![CDATA[case ${1:choice} in
+${2:first})
+ $3
+ ;;
+*)
+ $4
+ ;;
+esac]]></text>
+ <description>case ..</description>
+ <tag>case</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for (( ${1:i = 0}; ${2:i < 10}; ${3:i++} )); do
+ $0
+done]]></text>
+ <description>for .. done</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if [[ ${1:condition} ]]; then
+ $0
+fi]]></text>
+ <description>if .. then</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="sh">
+ <text><![CDATA[#!/bin/sh
+$0]]></text>
+ <description>#!/bin/sh</description>
+ <tag>sh</tag>
+ </snippet>
+ <snippet id="bash">
+ <text><![CDATA[#!/bin/bash
+$0]]></text>
+ <description>#!/bin/bash</description>
+ <tag>bash</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/snippets.xml b/plugins/snippets/data/snippets.xml
new file mode 100644
index 0000000..ee405e6
--- /dev/null
+++ b/plugins/snippets/data/snippets.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="snippets">
+ <snippet id="simple">
+ <text><![CDATA[\${${1:n:default}}]]></text>
+ <description>Simple Placeholder</description>
+ <tag>simple</tag>
+ </snippet>
+ <snippet id="simple-fallback">
+ <text><![CDATA[\${${1:n:}[${2:default1,default2}]}]]></text>
+ <description>Simple Fallback Placeholder</description>
+ <tag>simplef</tag>
+ </snippet>
+ <snippet id="shell">
+ <text><![CDATA[\$(${1:n:} ${2:shell code})]]></text>
+ <description>Shell Placeholder</description>
+ <tag>shell</tag>
+ </snippet>
+ <snippet id="python">
+ <text><![CDATA[\$<${1:n:} ${2:[refs]:} return 'python code' >]]></text>
+ <description>Python Placeholder</description>
+ <tag>python</tag>
+ </snippet>
+ <snippet id="regex">
+ <text><![CDATA[\${${1:n:} ${2:input}/${3:regex-pattern}/${4:replacement}/${5:modifiers}}]]></text>
+ <description>Regular Expression Placeholder</description>
+ <tag>regex</tag>
+ </snippet>
+ <snippet id="$-CURRENT_DOCUMENT_PATH">
+ <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_PATH]]></text>
+ <description>Gedit Current Document Path Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_DOCUMENT_NAME">
+ <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_NAME]]></text>
+ <description>Gedit Current Document Name Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_DOCUMENT_URI">
+ <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_URI]]></text>
+ <description>Gedit Current Document Uri Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_DOCUMENT_SCHEME">
+ <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_SCHEME]]></text>
+ <description>Gedit Current Document Scheme Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_DOCUMENT_TYPE">
+ <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_TYPE]]></text>
+ <description>Gedit Current Document Type Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-DOCUMENTS_URI">
+ <text><![CDATA[\$GEDIT_DOCUMENTS_URI]]></text>
+ <description>Gedit Documents Uri Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-DOCUMENTS_PATH">
+ <text><![CDATA[\$GEDIT_DOCUMENTS_PATH]]></text>
+ <description>Gedit Documents Path Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-SELECTED_TEXT">
+ <text><![CDATA[\$GEDIT_SELECTED_TEXT]]></text>
+ <description>Gedit Selected Text Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_WORD">
+ <text><![CDATA[\$GEDIT_CURRENT_WORD]]></text>
+ <description>Gedit Current Word Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_LINE">
+ <text><![CDATA[\$GEDIT_CURRENT_LINE]]></text>
+ <description>Gedit Current Line Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_LINE_NUMBER">
+ <text><![CDATA[\$GEDIT_CURRENT_LINE_NUMBER]]></text>
+ <description>Gedit Current Line Number Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-DROP_FILENAME">
+ <text><![CDATA[\$GEDIT_DROP_FILENAME]]></text>
+ <description>Gedit Drop Filename Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-DROP_REL_FILENAME">
+ <text><![CDATA[\$GEDIT_DROP_REL_FILENAME]]></text>
+ <description>Gedit Drop Relative Filename Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-DROP_MIME_TYPE">
+ <text><![CDATA[\$GEDIT_DROP_MIME_TYPE]]></text>
+ <description>Gedit Drop Mime Type Variable</description>
+ <tag>$</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/tcl.xml b/plugins/snippets/data/tcl.xml
new file mode 100644
index 0000000..73a50c0
--- /dev/null
+++ b/plugins/snippets/data/tcl.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Tcl">
+ <snippet id="foreach">
+ <text><![CDATA[foreach ${1:var} ${2:$list} {
+ ${3}
+}
+]]></text>
+ <description>foreach...</description>
+ <tag>foreach</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for {${1:set i 0}} {${2:$i < $n}} {${3:incr i}} {
+ ${4}
+}
+]]></text>
+ <description>for...</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if {${1:condition}} {
+ ${2}
+}
+]]></text>
+ <description>if...</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="proc">
+ <text><![CDATA[proc ${1:name} {${2:args}} \
+{
+ ${3}
+}
+]]></text>
+ <description>proc...</description>
+ <tag>proc</tag>
+ </snippet>
+ <snippet id="switch">
+ <text><![CDATA[switch ${1:-exact} -- ${2:$var} {
+ ${3:match} {
+ ${4}
+ }
+ default {${5}}
+}
+]]></text>
+ <description>switch...</description>
+ <tag>switch</tag>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while {${1:condition}} {
+ ${2}
+}
+]]></text>
+ <description>while...</description>
+ <tag>while</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/xml.xml b/plugins/snippets/data/xml.xml
new file mode 100644
index 0000000..a53d565
--- /dev/null
+++ b/plugins/snippets/data/xml.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="XML">
+ <snippet id="&quot;">
+ <text><![CDATA[<${1:name} ${2:attr}="${3:value}">$0</${1}>]]></text>
+ <description>Long Attribute Tag</description>
+ <tag>"</tag>
+ </snippet>
+ <snippet id="&lt;">
+ <text><![CDATA[<${1:name}>$0</${1}>
+
+]]></text>
+ <description>Long Tag</description>
+ <tag>&lt;</tag>
+ </snippet>
+ <snippet id="&gt;">
+ <text><![CDATA[<${1:name} />]]></text>
+ <description>Short Tag</description>
+ <tag>&gt;</tag>
+ </snippet>
+ <snippet id="cdata">
+ <text><![CDATA[<![CDATA[$0]]]]><![CDATA[>]]></text>
+ <tag>cdata</tag>
+ <description>CDATA</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/xslt.xml b/plugins/snippets/data/xslt.xml
new file mode 100644
index 0000000..0ff5cc1
--- /dev/null
+++ b/plugins/snippets/data/xslt.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="xslt">
+ <snippet id="stylesheet">
+ <text><![CDATA[<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+$0
+</xsl:stylesheet>
+]]></text>
+ <description>StyleSheet</description>
+ <tag>stylesheet</tag>
+ </snippet>
+ <snippet id="include">
+ <text><![CDATA[<xsl:include href="$1"/>
+]]></text>
+ <description>Include</description>
+ <tag>inc</tag>
+ </snippet>
+ <snippet id="import">
+ <text><![CDATA[<xsl:import href="$1"/>
+]]></text>
+ <description>Import</description>
+ <tag>imp</tag>
+ </snippet>
+ <snippet id="param">
+ <text><![CDATA[<xsl:param name="$1"/>
+]]></text>
+ <description>Parameter</description>
+ <tag>param</tag>
+ </snippet>
+ <snippet id="template">
+ <text><![CDATA[<xsl:template ${1:[match,name]}="$2" ${3:mode=""}>
+ $0
+</xsl:template>
+]]></text>
+ <description>Template</description>
+ <tag>templ</tag>
+ </snippet>
+ <snippet id="variable-1">
+ <text><![CDATA[<xsl:variable name="$1">
+ $0
+</xsl:variable>
+]]></text>
+ <description>Variable</description>
+ <tag>var</tag>
+ </snippet>
+ <snippet id="variable-2">
+ <text><![CDATA[<xsl:variable name="$1" select="$2"/>
+$0]]></text>
+ <description>Variable with Select Attribute</description>
+ <tag>var</tag>
+ </snippet>
+ <snippet id="choose">
+ <text><![CDATA[<xsl:choose>
+ <xsl:when test="$1">
+ $2
+ </xsl:when>
+ $3
+</xsl:choose>
+]]></text>
+ <description>Choose</description>
+ <tag>choose</tag>
+ </snippet>
+ <snippet id="when">
+ <text><![CDATA[<xsl:when test="$1">
+ $2
+</xsl:when>
+$0]]></text>
+ <description>When</description>
+ <tag>when</tag>
+ </snippet>
+ <snippet id="otherwise">
+ <text><![CDATA[<xsl:otherwise>
+ $1
+</xsl:otherwise>
+$0]]></text>
+ <description>Otherwise</description>
+ <tag>otherwise</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[<xsl:if test="$1">
+ $2
+</xsl:if>
+$0]]></text>
+ <description>If</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="value-of">
+ <text><![CDATA[<xsl:value-of select="$1"/>
+]]></text>
+ <description>Value of</description>
+ <tag>val</tag>
+ </snippet>
+ <snippet id="element">
+ <text><![CDATA[<xsl:element name="$1">
+</xsl:element>
+$0]]></text>
+ <description>Element</description>
+ <tag>elem</tag>
+ </snippet>
+ <snippet id="attribute">
+ <text><![CDATA[<xsl:attribute name="$1">$2</xsl:attribute>
+$0]]></text>
+ <description>Attribute</description>
+ <tag>attr</tag>
+ </snippet>
+ <snippet id="text">
+ <text><![CDATA[<xsl:text>${1:$GEDIT_SELECTED_TEXT}</xsl:text>
+]]></text>
+ <description>Text</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="comment">
+ <text><![CDATA[<xsl:comment>${1:$GEDIT_SELECTED_TEXT}</xsl:comment>
+]]></text>
+ <description>Comment</description>
+ <tag>comment</tag>
+ </snippet>
+ <snippet id="call-template">
+ <text><![CDATA[<xsl:call-template name="$1"/>
+]]></text>
+ <description>Call Template</description>
+ <tag>call</tag>
+ </snippet>
+ <snippet id="apply-templates">
+ <text><![CDATA[<xsl:apply-templates mode="$1" select="$2"/>
+$0]]></text>
+ <description>Apply Templates</description>
+ <tag>applyt</tag>
+ </snippet>
+ <snippet id="apply-imports">
+ <text><![CDATA[<xsl:apply-imports/>
+]]></text>
+ <description>Apply Imports</description>
+ <tag>applyimp</tag>
+ </snippet>
+ <snippet id="with-param">
+ <text><![CDATA[<xsl:with-param name="$1">
+ $2
+</xsl:with-param>
+$0]]></text>
+ <description>With Param</description>
+ <tag>with</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/meson.build b/plugins/snippets/meson.build
new file mode 100644
index 0000000..91401d1
--- /dev/null
+++ b/plugins/snippets/meson.build
@@ -0,0 +1,23 @@
+subdir('snippets')
+
+install_subdir(
+ 'data',
+ strip_directory : true,
+ install_dir: join_paths(
+ pkgdatadir,
+ 'plugins',
+ 'snippets',
+ )
+)
+
+custom_target(
+ 'snippets.plugin',
+ input: 'snippets.plugin.desktop.in',
+ output: 'snippets.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
diff --git a/plugins/snippets/snippets.plugin.desktop.in b/plugins/snippets/snippets.plugin.desktop.in
new file mode 100644
index 0000000..129db21
--- /dev/null
+++ b/plugins/snippets/snippets.plugin.desktop.in
@@ -0,0 +1,9 @@
+[Plugin]
+Loader=python3
+Module=snippets
+IAge=3
+Name=Snippets
+Description=Insert often-used pieces of text in a fast way.
+Authors=Jesse van den Kieboom <jesse@icecrew.nl>
+Copyright=Copyright © 2005 Jesse van den Kieboom
+Website=http://www.gedit.org
diff --git a/plugins/snippets/snippets/__init__.py b/plugins/snippets/snippets/__init__.py
new file mode 100644
index 0000000..147aa69
--- /dev/null
+++ b/plugins/snippets/snippets/__init__.py
@@ -0,0 +1,26 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gi
+gi.require_version('Gedit', '3.0')
+gi.require_version('Gtk', '3.0')
+
+from .appactivatable import AppActivatable
+from .windowactivatable import WindowActivatable
+from .document import Document
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/appactivatable.py b/plugins/snippets/snippets/appactivatable.py
new file mode 100644
index 0000000..fb56e51
--- /dev/null
+++ b/plugins/snippets/snippets/appactivatable.py
@@ -0,0 +1,133 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import platform
+from gi.repository import Gedit, Gtk, Gdk, GObject, Gio, GLib
+from .library import Library
+from .shareddata import SharedData
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class AppActivatable(GObject.Object, Gedit.AppActivatable):
+ __gtype_name__ = "GeditSnippetsAppActivatable"
+
+ app = GObject.Property(type=Gedit.App)
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+
+ def do_activate(self):
+ # Initialize snippets library
+ library = Library()
+
+ if platform.system() == 'Windows':
+ snippetsdir = os.path.expanduser('~/gedit/snippets')
+ else:
+ snippetsdir = os.path.join(GLib.get_user_config_dir(), 'gedit/snippets')
+
+ library.set_dirs(snippetsdir, self.system_dirs())
+
+ self.css = Gtk.CssProvider()
+ self.css.load_from_data("""
+.gedit-snippet-manager-paned {
+ border-style: solid;
+ border-color: @borders;
+}
+.gedit-snippet-manager-paned:dir(ltr) {
+ border-width: 0 1px 0 0;
+}
+
+.gedit-snippet-manager-paned:dir(rtl) {
+ border-width: 0 0 0 1px;
+}
+
+.gedit-snippet-manager-view {
+ border-width: 0 0 1px 0;
+}
+
+.gedit-snippet-manager-treeview {
+ border-top-width: 0;
+}
+
+.gedit-snippet-manager-treeview:dir(ltr) {
+ border-left-width: 0;
+}
+
+.gedit-snippet-manager-treeview:dir(rtl) {
+ border-right-width: 0;
+}
+""".encode('utf-8'))
+ Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(),
+ self.css, 600)
+
+ action = Gio.SimpleAction(name="snippets")
+ action.connect('activate', self.on_action_snippets_activate)
+ self.app.add_action(action)
+
+ item = Gio.MenuItem.new(_("Manage _Snippets…"), "app.snippets")
+ self.menu = self.extend_menu("preferences-section")
+ self.menu.append_menu_item(item)
+
+ def do_deactivate(self):
+ self.app.remove_action("snippets")
+ self.menu = None
+ Gtk.StyleContext.remove_provider_for_screen(Gdk.Screen.get_default(),
+ self.css)
+
+ def system_dirs(self):
+ dirs = []
+
+ if 'XDG_DATA_DIRS' in os.environ:
+ datadirs = os.environ['XDG_DATA_DIRS']
+ elif platform.system() != 'Windows':
+ datadirs = '/usr/local/share' + os.pathsep + '/usr/share'
+ else:
+ datadirs = GLib.win32_get_package_installation_directory_of_module(None)
+
+ for d in datadirs.split(os.pathsep):
+ d = os.path.join(d, 'gedit', 'plugins', 'snippets')
+
+ if os.path.isdir(d):
+ dirs.append(d)
+
+ dirs.append(self.plugin_info.get_data_dir())
+ return dirs
+
+ def accelerator_activated(self, group, obj, keyval, mod):
+ activatable = SharedData().lookup_window_activatable(obj)
+
+ ret = False
+
+ if activatable:
+ ret = activatable.accelerator_activated(keyval, mod)
+
+ return ret
+
+ def create_configure_dialog(self):
+ SharedData().show_manager(self.app.get_active_window(), self.plugin_info.get_data_dir())
+
+ def on_action_snippets_activate(self, action, parameter):
+ self.create_configure_dialog()
+
+# vi:ex:ts=4:et
diff --git a/plugins/snippets/snippets/completion.py b/plugins/snippets/snippets/completion.py
new file mode 100644
index 0000000..562b268
--- /dev/null
+++ b/plugins/snippets/snippets/completion.py
@@ -0,0 +1,187 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gi.repository import GObject, Gtk, GtkSource, Gedit
+
+from .library import Library
+from .languagemanager import get_language_manager
+from .snippet import Snippet
+
+class Proposal(GObject.Object, GtkSource.CompletionProposal):
+ __gtype_name__ = "GeditSnippetsProposal"
+
+ def __init__(self, snippet):
+ super(Proposal, self).__init__()
+ self._snippet = Snippet(snippet)
+
+ def snippet(self):
+ return self._snippet.data
+
+ # Interface implementation
+ def do_get_markup(self):
+ return self._snippet.display()
+
+ def do_get_info(self):
+ return self._snippet.data['text']
+
+class Provider(GObject.Object, GtkSource.CompletionProvider):
+ __gtype_name__ = "GeditSnippetsProvider"
+
+ def __init__(self, name, language_id, handler):
+ super(Provider, self).__init__()
+
+ self.name = name
+ self.info_widget = None
+ self.proposals = []
+ self.language_id = language_id
+ self.handler = handler
+ self.info_widget = None
+ self.mark = None
+
+ theme = Gtk.IconTheme.get_default()
+ f, w, h = Gtk.icon_size_lookup(Gtk.IconSize.MENU)
+
+ try:
+ self.icon = theme.load_icon(Gtk.STOCK_JUSTIFY_LEFT, w, 0)
+ except:
+ self.icon = None
+
+ def __del__(self):
+ if self.mark:
+ self.mark.get_buffer().delete_mark(self.mark)
+
+ def set_proposals(self, proposals):
+ self.proposals = proposals
+
+ def mark_position(self, it):
+ if not self.mark:
+ self.mark = it.get_buffer().create_mark(None, it, True)
+ else:
+ self.mark.get_buffer().move_mark(self.mark, it)
+
+ def get_word(self, context):
+ (valid_context, it) = context.get_iter()
+ if not valid_context:
+ return None
+
+ if it.starts_word() or it.starts_line() or not it.ends_word():
+ return None
+
+ start = it.copy()
+
+ if start.backward_word_start():
+ self.mark_position(start)
+ return start.get_text(it)
+ else:
+ return None
+
+ def do_get_start_iter(self, context, proposal):
+ if not self.mark or self.mark.get_deleted():
+ return (False, None)
+
+ return (True, self.mark.get_buffer().get_iter_at_mark(self.mark))
+
+ def do_match(self, context):
+ return True
+
+ def get_proposals(self, word):
+ if self.proposals:
+ proposals = self.proposals
+ else:
+ proposals = Library().get_snippets(None)
+
+ if self.language_id:
+ proposals += Library().get_snippets(self.language_id)
+
+ # Filter based on the current word
+ if word:
+ proposals = (x for x in proposals if x['tag'].startswith(word))
+
+ return [Proposal(x) for x in proposals]
+
+ def do_populate(self, context):
+ proposals = self.get_proposals(self.get_word(context))
+ context.add_proposals(self, proposals, True)
+
+ def do_get_name(self):
+ return self.name
+
+ def do_activate_proposal(self, proposal, piter):
+ return self.handler(proposal, piter)
+
+ def do_get_info_widget(self, proposal):
+ if not self.info_widget:
+ view = Gedit.View.new_with_buffer(Gedit.Document())
+ manager = get_language_manager()
+
+ lang = manager.get_language('snippets')
+ view.get_buffer().set_language(lang)
+
+ sw = Gtk.ScrolledWindow()
+ sw.add(view)
+ sw.show_all()
+
+ # Fixed size
+ sw.set_size_request(300, 200)
+
+ self.info_view = view
+ self.info_widget = sw
+
+ return self.info_widget
+
+ def do_update_info(self, proposal, info):
+ buf = self.info_view.get_buffer()
+
+ buf.set_text(proposal.get_info())
+ buf.move_mark(buf.get_insert(), buf.get_start_iter())
+ buf.move_mark(buf.get_selection_bound(), buf.get_start_iter())
+ self.info_view.scroll_to_iter(buf.get_start_iter(), 0.0, False, 0.5, 0.5)
+
+ def do_get_icon(self):
+ return self.icon
+
+ def do_get_activation(self):
+ return GtkSource.CompletionActivation.USER_REQUESTED
+
+class Defaults(GObject.Object, GtkSource.CompletionProvider):
+ __gtype_name__ = "GeditSnippetsDefaultsProvider"
+
+ def __init__(self, handler):
+ GObject.Object.__init__(self)
+
+ self.handler = handler
+ self.proposals = []
+
+ def set_defaults(self, defaults):
+ self.proposals = []
+
+ for d in defaults:
+ self.proposals.append(GtkSource.CompletionItem.new(d, d, None, None))
+
+ def do_get_name(self):
+ return ""
+
+ def do_activate_proposal(self, proposal, piter):
+ return self.handler(proposal, piter)
+
+ def do_populate(self, context):
+ context.add_proposals(self, self.proposals, True)
+
+ def do_get_activation(self):
+ return GtkSource.CompletionActivation.USER_REQUESTED
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/document.py b/plugins/snippets/snippets/document.py
new file mode 100644
index 0000000..23df280
--- /dev/null
+++ b/plugins/snippets/snippets/document.py
@@ -0,0 +1,1097 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import re
+
+from gi.repository import Gtk, Gdk, Gio, GLib, Gedit, GObject
+
+from .library import Library
+from .snippet import Snippet
+from .placeholder import PlaceholderEnd
+from . import completion
+from .signals import Signals
+from .shareddata import SharedData
+from . import helper
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class DynamicSnippet(dict):
+ def __init__(self, text):
+ self['text'] = text
+ self.valid = True
+
+class Document(GObject.Object, Gedit.ViewActivatable, Signals):
+ TAB_KEY_VAL = (Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab)
+ SPACE_KEY_VAL = (Gdk.KEY_space,)
+
+ view = GObject.Property(type=Gedit.View)
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+ Signals.__init__(self)
+
+ self.placeholders = []
+ self.active_snippets = []
+ self.active_placeholder = None
+
+ self.ordered_placeholders = []
+ self.update_placeholders = []
+ self.jump_placeholders = []
+ self.language_id = 0
+ self.timeout_update_id = 0
+
+ self.provider = completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated)
+
+ def do_activate(self):
+ # Always have a reference to the global snippets
+ Library().ref(None)
+
+ buf = self.view.get_buffer()
+
+ self.connect_signal(self.view, 'key-press-event', self.on_view_key_press)
+ self.connect_signal(buf, 'notify::language', self.on_notify_language)
+ self.connect_signal(self.view, 'drag-data-received', self.on_drag_data_received)
+
+ self.connect_signal_after(self.view, 'draw', self.on_draw)
+
+ self.update_language()
+
+ completion = self.view.get_completion()
+ completion.add_provider(self.provider)
+
+ SharedData().register_controller(self.view, self)
+
+ def do_deactivate(self):
+ if self.timeout_update_id != 0:
+ GLib.source_remove(self.timeout_update_id)
+ self.timeout_update_id = 0
+
+ del self.update_placeholders[:]
+ del self.jump_placeholders[:]
+
+ # Always release the reference to the global snippets
+ Library().unref(None)
+ self.active_placeholder = None
+
+ self.disconnect_signals(self.view)
+ self.disconnect_signals(self.view.get_buffer())
+
+ # Remove all active snippets
+ for snippet in list(self.active_snippets):
+ self.deactivate_snippet(snippet, True)
+
+ completion = self.view.get_completion()
+
+ if completion:
+ completion.remove_provider(self.provider)
+
+ if self.language_id != 0:
+ Library().unref(self.language_id)
+
+ SharedData().unregister_controller(self.view, self)
+
+ # Call this whenever the language in the view changes. This makes sure that
+ # the correct language is used when finding snippets
+ def update_language(self):
+ lang = self.view.get_buffer().get_language()
+
+ if lang is None and self.language_id is None:
+ return
+ elif lang and lang.get_id() == self.language_id:
+ return
+
+ langid = self.language_id
+
+ if lang:
+ self.language_id = lang.get_id()
+ else:
+ self.language_id = None
+
+ if langid != 0:
+ Library().unref(langid)
+
+ Library().ref(self.language_id)
+ self.provider.language_id = self.language_id
+
+ SharedData().update_state(self.view.get_toplevel())
+
+ def accelerator_activate(self, keyval, mod):
+ if not self.view or not self.view.get_editable():
+ return False
+
+ accelerator = Gtk.accelerator_name(keyval, mod)
+ snippets = Library().from_accelerator(accelerator, \
+ self.language_id)
+
+ if len(snippets) == 0:
+ return False
+ elif len(snippets) == 1:
+ self.apply_snippet(snippets[0])
+ else:
+ # Do the fancy completion dialog
+ provider = completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated)
+ provider.set_proposals(snippets)
+
+ cm = self.view.get_completion()
+ cm.show([provider], cm.create_context(None))
+
+ return True
+
+ def first_snippet_inserted(self):
+ buf = self.view.get_buffer()
+
+ self.connect_signal(buf, 'changed', self.on_buffer_changed)
+ self.connect_signal(buf, 'cursor-moved', self.on_buffer_cursor_moved)
+ self.connect_signal_after(buf, 'insert-text', self.on_buffer_insert_text)
+
+ def last_snippet_removed(self):
+ buf = self.view.get_buffer()
+ self.disconnect_signal(buf, 'changed')
+ self.disconnect_signal(buf, 'cursor-moved')
+ self.disconnect_signal(buf, 'insert-text')
+
+ def current_placeholder(self):
+ buf = self.view.get_buffer()
+
+ piter = buf.get_iter_at_mark(buf.get_insert())
+ found = []
+
+ for placeholder in self.placeholders:
+ begin = placeholder.begin_iter()
+ end = placeholder.end_iter()
+
+ if piter.compare(begin) >= 0 and piter.compare(end) <= 0:
+ found.append(placeholder)
+
+ if self.active_placeholder in found:
+ return self.active_placeholder
+ elif len(found) > 0:
+ return found[0]
+ else:
+ return None
+
+ def advance_placeholder(self, direction):
+ # Returns (CurrentPlaceholder, NextPlaceholder), depending on direction
+ buf = self.view.get_buffer()
+
+ piter = buf.get_iter_at_mark(buf.get_insert())
+ found = current = next = None
+ length = len(self.placeholders)
+
+ placeholders = list(self.placeholders)
+
+ if self.active_placeholder:
+ begin = self.active_placeholder.begin_iter()
+ end = self.active_placeholder.end_iter()
+
+ if piter.compare(begin) >= 0 and piter.compare(end) <= 0:
+ current = self.active_placeholder
+ currentIndex = placeholders.index(self.active_placeholder)
+
+ if direction == 1:
+ # w = piter, x = begin, y = end, z = found
+ nearest = lambda w, x, y, z: (w.compare(x) <= 0 and (not z or \
+ x.compare(z.begin_iter()) < 0))
+ indexer = lambda x: x < length - 1
+ else:
+ # w = piter, x = begin, y = end, z = prev
+ nearest = lambda w, x, y, z: (w.compare(x) >= 0 and (not z or \
+ x.compare(z.begin_iter()) >= 0))
+ indexer = lambda x: x > 0
+
+ for index in range(0, length):
+ placeholder = placeholders[index]
+ begin = placeholder.begin_iter()
+ end = placeholder.end_iter()
+
+ # Find the nearest placeholder
+ if nearest(piter, begin, end, found):
+ found = placeholder
+
+ # Find the current placeholder
+ if piter.compare(begin) >= 0 and \
+ piter.compare(end) <= 0 and \
+ current is None:
+ currentIndex = index
+ current = placeholder
+
+ if current and current != found and \
+ (current.begin_iter().compare(found.begin_iter()) == 0 or \
+ current.end_iter().compare(found.begin_iter()) == 0) and \
+ self.active_placeholder and \
+ current.begin_iter().compare(self.active_placeholder.begin_iter()) == 0:
+ # if current and found are at the same place, then
+ # resolve the 'hugging' problem
+ current = self.active_placeholder
+ currentIndex = placeholders.index(current)
+
+ if current:
+ if indexer(currentIndex):
+ next = placeholders[currentIndex + direction]
+ elif found:
+ next = found
+ elif length > 0:
+ next = self.placeholders[0]
+
+ return current, next
+
+ def next_placeholder(self):
+ return self.advance_placeholder(1)
+
+ def previous_placeholder(self):
+ return self.advance_placeholder(-1)
+
+ def cursor_on_screen(self):
+ buf = self.view.get_buffer()
+ self.view.scroll_mark_onscreen(buf.get_insert())
+
+ def set_active_placeholder(self, placeholder):
+ self.active_placeholder = placeholder
+
+ def goto_placeholder(self, current, next):
+ last = None
+
+ if current:
+ # Signal this placeholder to end action
+ self.view.get_completion().hide()
+ current.leave()
+
+ if current.__class__ == PlaceholderEnd:
+ last = current
+
+ self.set_active_placeholder(next)
+
+ if next:
+ next.enter()
+
+ if next.__class__ == PlaceholderEnd:
+ last = next
+ elif len(next.defaults) > 1 and next.get_text() == next.default:
+ provider = completion.Defaults(self.on_default_activated)
+ provider.set_defaults(next.defaults)
+
+ cm = self.view.get_completion()
+ cm.show([provider], cm.create_context(None))
+
+ if last:
+ # This is the end of the placeholder, remove the snippet etc
+ for snippet in list(self.active_snippets):
+ if snippet.placeholders[0] == last:
+ self.deactivate_snippet(snippet)
+ break
+
+ self.cursor_on_screen()
+
+ return next != None
+
+ def skip_to_next_placeholder(self):
+ (current, next) = self.next_placeholder()
+ return self.goto_placeholder(current, next)
+
+ def skip_to_previous_placeholder(self):
+ (current, prev) = self.previous_placeholder()
+ return self.goto_placeholder(current, prev)
+
+ def string_in_native_doc_encoding(self, buf, s):
+ enc = buf.get_file().get_encoding()
+
+ if not enc or enc.get_charset() == 'UTF-8':
+ return s
+
+ try:
+ cv = GLib.convert(s, -1, enc.get_charset(), 'UTF-8')
+ return cv[0]
+ except GLib.GError:
+ pass
+
+ return s
+
+ def env_get_selected_text(self, buf):
+ bounds = buf.get_selection_bounds()
+
+ if bounds:
+ u8 = buf.get_text(bounds[0], bounds[1], False)
+
+ return {'utf8': u8, 'noenc': self.string_in_native_doc_encoding(buf, u8)}
+ else:
+ return ''
+
+ def env_get_current_word(self, buf):
+ start, end = helper.buffer_word_boundary(buf)
+
+ u8 = buf.get_text(start, end, False)
+
+ return {'utf8': u8, 'noenc': self.string_in_native_doc_encoding(buf, u8)}
+
+ def env_get_current_line(self, buf):
+ start, end = helper.buffer_line_boundary(buf)
+
+ u8 = buf.get_text(start, end, False)
+
+ return {'utf8': u8, 'noenc': self.string_in_native_doc_encoding(buf, u8)}
+
+ def env_get_current_line_number(self, buf):
+ start, end = helper.buffer_line_boundary(buf)
+
+ return str(start.get_line() + 1)
+
+ def location_uri_for_env(self, location):
+ if not location:
+ return {'utf8': '', 'noenc': ''}
+
+ u8 = location.get_parse_name()
+
+ if location.has_uri_scheme('file'):
+ u8 = "file://" + u8
+
+ return {'utf8': u8, 'noenc': location.get_uri()}
+
+ def location_name_for_env(self, location):
+ if location:
+ try:
+ info = location.query_info("standard::display-name", 0, None)
+ display_name = info.get_display_name()
+ except:
+ display_name = ''
+
+ return {'utf8': display_name,
+ 'noenc': location.get_basename()}
+ else:
+ return ''
+
+ def location_scheme_for_env(self, location):
+ if location:
+ return location.get_uri_scheme()
+ else:
+ return ''
+
+ def location_path_for_env(self, location):
+ if location and location.has_uri_scheme('file'):
+ return {'utf8': location.get_parse_name(),
+ 'noenc': location.get_path()}
+ else:
+ return ''
+
+ def location_dir_for_env(self, location):
+ if location:
+ parent = location.get_parent()
+
+ if parent and parent.has_uri_scheme('file'):
+ return {'utf8': parent.get_parse_name(),
+ 'noenc': parent.get_path()}
+
+ return ''
+
+ def env_add_for_location(self, environ, location, prefix):
+ parts = {'URI': self.location_uri_for_env,
+ 'NAME': self.location_name_for_env,
+ 'SCHEME': self.location_scheme_for_env,
+ 'PATH': self.location_path_for_env,
+ 'DIR': self.location_dir_for_env}
+
+ for k in parts:
+ v = parts[k](location)
+ key = prefix + '_' + k
+
+ if isinstance(v, dict):
+ environ['utf8'][key] = v['utf8']
+ environ['noenc'][key] = v['noenc']
+ else:
+ environ['utf8'][key] = v
+ environ['noenc'][key] = str(v)
+
+ return environ
+
+ def env_get_document_type(self, buf):
+ typ = buf.get_mime_type()
+
+ if typ:
+ return typ
+ else:
+ return ''
+
+ def env_get_documents_uri(self, buf):
+ toplevel = self.view.get_toplevel()
+
+ documents_uri = {'utf8': [], 'noenc': []}
+
+ if isinstance(toplevel, Gedit.Window):
+ for doc in toplevel.get_documents():
+ r = self.location_uri_for_env(doc.get_file().get_location())
+
+ if isinstance(r, dict):
+ documents_uri['utf8'].append(r['utf8'])
+ documents_uri['noenc'].append(r['noenc'])
+ else:
+ documents_uri['utf8'].append(r)
+ documents_uri['noenc'].append(str(r))
+
+ return {'utf8': ' '.join(documents_uri['utf8']),
+ 'noenc': ' '.join(documents_uri['noenc'])}
+
+ def env_get_documents_path(self, buf):
+ toplevel = self.view.get_toplevel()
+
+ documents_path = {'utf8': [], 'noenc': []}
+
+ if isinstance(toplevel, Gedit.Window):
+ for doc in toplevel.get_documents():
+ r = self.location_path_for_env(doc.get_file().get_location())
+
+ if isinstance(r, dict):
+ documents_path['utf8'].append(r['utf8'])
+ documents_path['noenc'].append(r['noenc'])
+ else:
+ documents_path['utf8'].append(r)
+ documents_path['noenc'].append(str(r))
+
+ return {'utf8': ' '.join(documents_path['utf8']),
+ 'noenc': ' '.join(documents_path['noenc'])}
+
+ def get_environment(self):
+ buf = self.view.get_buffer()
+ environ = {'utf8': {}, 'noenc': {}}
+
+ for k in os.environ:
+ # Get the original environment, as utf-8
+ v = os.environ[k]
+ environ['noenc'][k] = v
+ environ['utf8'][k] = os.environ[k].encode('utf-8')
+
+ variables = {'GEDIT_SELECTED_TEXT': self.env_get_selected_text,
+ 'GEDIT_CURRENT_WORD': self.env_get_current_word,
+ 'GEDIT_CURRENT_LINE': self.env_get_current_line,
+ 'GEDIT_CURRENT_LINE_NUMBER': self.env_get_current_line_number,
+ 'GEDIT_CURRENT_DOCUMENT_TYPE': self.env_get_document_type,
+ 'GEDIT_DOCUMENTS_URI': self.env_get_documents_uri,
+ 'GEDIT_DOCUMENTS_PATH': self.env_get_documents_path}
+
+ for var in variables:
+ v = variables[var](buf)
+
+ if isinstance(v, dict):
+ environ['utf8'][var] = v['utf8']
+ environ['noenc'][var] = v['noenc']
+ else:
+ environ['utf8'][var] = v
+ environ['noenc'][var] = str(v)
+
+ self.env_add_for_location(environ, buf.get_file().get_location(), 'GEDIT_CURRENT_DOCUMENT')
+
+ return environ
+
+ def uses_current_word(self, snippet):
+ matches = re.findall('(\\\\*)\\$GEDIT_CURRENT_WORD', snippet['text'])
+
+ for match in matches:
+ if len(match) % 2 == 0:
+ return True
+
+ return False
+
+ def uses_current_line(self, snippet):
+ matches = re.findall('(\\\\*)\\$GEDIT_CURRENT_LINE', snippet['text'])
+
+ for match in matches:
+ if len(match) % 2 == 0:
+ return True
+
+ return False
+
+ def apply_snippet(self, snippet, start = None, end = None, environ = {}):
+ if not snippet.valid:
+ return False
+
+ # Set environmental variables
+ env = self.get_environment()
+
+ if environ:
+ for k in environ['utf8']:
+ env['utf8'][k] = environ['utf8'][k]
+
+ for k in environ['noenc']:
+ env['noenc'][k] = environ['noenc'][k]
+
+ buf = self.view.get_buffer()
+ s = Snippet(snippet, env)
+
+ if not start:
+ start = buf.get_iter_at_mark(buf.get_insert())
+
+ if not end:
+ end = buf.get_iter_at_mark(buf.get_selection_bound())
+
+ if start.equal(end) and self.uses_current_word(s):
+ # There is no tab trigger and no selection and the snippet uses
+ # the current word. Set start and end to the word boundary so that
+ # it will be removed
+ start, end = helper.buffer_word_boundary(buf)
+ elif start.equal(end) and self.uses_current_line(s):
+ # There is no tab trigger and no selection and the snippet uses
+ # the current line. Set start and end to the line boundary so that
+ # it will be removed
+ start, end = helper.buffer_line_boundary(buf)
+
+ # You know, we could be in an end placeholder
+ (current, next) = self.next_placeholder()
+ if current and current.__class__ == PlaceholderEnd:
+ self.goto_placeholder(current, None)
+
+ if len(self.active_snippets) > 0:
+ self.block_signal(buf, 'cursor-moved')
+
+ buf.begin_user_action()
+
+ # Remove the tag, selection or current word
+ buf.delete(start, end)
+
+ # Insert the snippet
+ if len(self.active_snippets) == 0:
+ self.first_snippet_inserted()
+ self.block_signal(buf, 'cursor-moved')
+
+ sn = s.insert_into(self, start)
+ self.active_snippets.append(sn)
+
+ # Put cursor at first tab placeholder
+ keys = [x for x in sn.placeholders.keys() if x > 0]
+
+ if len(keys) == 0:
+ if 0 in sn.placeholders:
+ self.goto_placeholder(self.active_placeholder, sn.placeholders[0])
+ else:
+ buf.place_cursor(sn.begin_iter())
+ else:
+ self.goto_placeholder(self.active_placeholder, sn.placeholders[keys[0]])
+
+ self.unblock_signal(buf, 'cursor-moved')
+
+ if sn in self.active_snippets:
+ # Check if we can get end_iter in view without moving the
+ # current cursor position out of view
+ cur = buf.get_iter_at_mark(buf.get_insert())
+ last = sn.end_iter()
+
+ curloc = self.view.get_iter_location(cur)
+ lastloc = self.view.get_iter_location(last)
+
+ if (lastloc.y + lastloc.height) - curloc.y <= \
+ self.view.get_visible_rect().height:
+ self.view.scroll_mark_onscreen(sn.end_mark)
+
+ buf.end_user_action()
+ self.view.grab_focus()
+
+ return True
+
+ def get_tab_tag(self, buf, end = None):
+ if not end:
+ end = buf.get_iter_at_mark(buf.get_insert())
+
+ start = end.copy()
+ word = None
+ first = True
+
+ # Move start backward as long as there is a valid character
+ while start.backward_char():
+ c = start.get_char()
+
+ if not helper.is_tab_trigger_character(c):
+ # Check this for a single special char
+ if first and helper.is_tab_trigger(c):
+ break
+
+ # Make sure first char is valid
+ while not start.equal(end) and \
+ not helper.is_first_tab_trigger_character(start.get_char()):
+ start.forward_char()
+
+ break
+
+ first = False
+
+ if not start.equal(end):
+ word = buf.get_text(start, end, False)
+
+ if word and word != '':
+ return (word, start, end)
+
+ return (None, None, None)
+
+ def parse_and_run_snippet(self, data, iter):
+ if not self.view.get_editable():
+ return
+
+ self.apply_snippet(DynamicSnippet(data), iter, iter)
+
+ def run_snippet_trigger(self, trigger, bounds):
+ if not self.view:
+ return False
+
+ if not self.view.get_editable():
+ return False
+
+ buf = self.view.get_buffer()
+
+ if buf.get_has_selection():
+ return False
+
+ snippets = Library().from_tag(trigger, self.language_id)
+
+ if snippets:
+ if len(snippets) == 1:
+ return self.apply_snippet(snippets[0], bounds[0], bounds[1])
+ else:
+ # Do the fancy completion dialog
+ provider = completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated)
+ provider.set_proposals(snippets)
+
+ cm = self.view.get_completion()
+ cm.show([provider], cm.create_context(None))
+
+ return True
+
+ return False
+
+ def run_snippet(self):
+ if not self.view:
+ return False
+
+ if not self.view.get_editable():
+ return False
+
+ buf = self.view.get_buffer()
+
+ # get the word preceding the current insertion position
+ (word, start, end) = self.get_tab_tag(buf)
+
+ if not word:
+ return self.skip_to_next_placeholder()
+
+ if not self.run_snippet_trigger(word, (start, end)):
+ return self.skip_to_next_placeholder()
+ else:
+ return True
+
+ def deactivate_snippet(self, snippet, force = False):
+ remove = []
+ ordered_remove = []
+
+ for tabstop in snippet.placeholders:
+ if tabstop == -1:
+ placeholders = snippet.placeholders[-1]
+ else:
+ placeholders = [snippet.placeholders[tabstop]]
+
+ for placeholder in placeholders:
+ if placeholder in self.placeholders:
+ if placeholder in self.update_placeholders:
+ placeholder.update_contents()
+
+ self.update_placeholders.remove(placeholder)
+ elif placeholder in self.jump_placeholders:
+ placeholder[0].leave()
+
+ remove.append(placeholder)
+ elif placeholder in self.ordered_placeholders:
+ ordered_remove.append(placeholder)
+
+ for placeholder in remove:
+ if placeholder == self.active_placeholder:
+ self.active_placeholder = None
+
+ self.placeholders.remove(placeholder)
+ self.ordered_placeholders.remove(placeholder)
+
+ placeholder.remove(force)
+
+ for placeholder in ordered_remove:
+ self.ordered_placeholders.remove(placeholder)
+ placeholder.remove(force)
+
+ snippet.deactivate()
+ self.active_snippets.remove(snippet)
+
+ if len(self.active_snippets) == 0:
+ self.last_snippet_removed()
+
+ self.view.queue_draw()
+
+ def update_snippet_contents(self):
+ self.timeout_update_id = 0
+
+ for placeholder in self.update_placeholders:
+ placeholder.update_contents()
+
+ for placeholder in self.jump_placeholders:
+ self.goto_placeholder(placeholder[0], placeholder[1])
+
+ del self.update_placeholders[:]
+ del self.jump_placeholders[:]
+
+ return False
+
+ def on_buffer_cursor_moved(self, buf):
+ piter = buf.get_iter_at_mark(buf.get_insert())
+
+ # Check for all snippets if the cursor is outside its scope
+ for snippet in list(self.active_snippets):
+ if snippet.begin_mark.get_deleted() or snippet.end_mark.get_deleted():
+ self.deactivate(snippet)
+ else:
+ begin = snippet.begin_iter()
+ end = snippet.end_iter()
+
+ if piter.compare(begin) < 0 or piter.compare(end) > 0:
+ # Oh no! Remove the snippet this instant!!
+ self.deactivate_snippet(snippet)
+
+ current = self.current_placeholder()
+
+ if current != self.active_placeholder:
+ self.jump_placeholders.append((self.active_placeholder, current))
+
+ if self.timeout_update_id == 0:
+ self.timeout_update_id = GLib.timeout_add(0,
+ self.update_snippet_contents)
+
+ def on_buffer_changed(self, buf):
+ for snippet in list(self.active_snippets):
+ begin = snippet.begin_iter()
+ end = snippet.end_iter()
+
+ if begin.compare(end) >= 0:
+ # Begin collapsed on end, just remove it
+ self.deactivate_snippet(snippet)
+
+ current = self.current_placeholder()
+
+ if current:
+ if not current in self.update_placeholders:
+ self.update_placeholders.append(current)
+
+ if self.timeout_update_id == 0:
+ self.timeout_update_id = GLib.timeout_add(0, \
+ self.update_snippet_contents)
+
+ def on_buffer_insert_text(self, buf, piter, text, length):
+ ctx = helper.get_buffer_context(buf)
+
+ # do nothing special if there is no context and no active
+ # placeholder
+ if (not ctx) and (not self.active_placeholder):
+ return
+
+ if not ctx:
+ ctx = self.active_placeholder
+
+ if not ctx in self.ordered_placeholders:
+ return
+
+ # move any marks that were incorrectly moved by this insertion
+ # back to where they belong
+ begin = ctx.begin_iter()
+ end = ctx.end_iter()
+ idx = self.ordered_placeholders.index(ctx)
+
+ for placeholder in self.ordered_placeholders:
+ if placeholder == ctx:
+ continue
+
+ ob = placeholder.begin_iter()
+ oe = placeholder.end_iter()
+
+ if ob.compare(begin) == 0 and ((not oe) or oe.compare(end) == 0):
+ oidx = self.ordered_placeholders.index(placeholder)
+
+ if oidx > idx and ob:
+ buf.move_mark(placeholder.begin, end)
+ elif oidx < idx and oe:
+ buf.move_mark(placeholder.end, begin)
+ elif ob.compare(begin) >= 0 and ob.compare(end) < 0 and (oe and oe.compare(end) >= 0):
+ buf.move_mark(placeholder.begin, end)
+ elif (oe and oe.compare(begin) > 0) and ob.compare(begin) <= 0:
+ buf.move_mark(placeholder.end, begin)
+
+ def on_notify_language(self, buf, spec):
+ self.update_language()
+
+ def on_view_key_press(self, view, event):
+ library = Library()
+
+ state = event.get_state()
+
+ if not self.view.get_editable():
+ return False
+
+ if not (state & Gdk.ModifierType.CONTROL_MASK) and \
+ not (state & Gdk.ModifierType.MOD1_MASK) and \
+ event.keyval in self.TAB_KEY_VAL:
+ if not state & Gdk.ModifierType.SHIFT_MASK:
+ return self.run_snippet()
+ else:
+ return self.skip_to_previous_placeholder()
+ elif not library.loaded and \
+ library.valid_accelerator(event.keyval, state):
+ library.ensure_files()
+ library.ensure(self.language_id)
+ self.accelerator_activate(event.keyval, \
+ state & Gtk.accelerator_get_default_mod_mask())
+
+ return False
+
+ def path_split(self, path, components=[]):
+ head, tail = os.path.split(path)
+
+ if not tail and head:
+ return [head] + components
+ elif tail:
+ return self.path_split(head, [tail] + components)
+ else:
+ return components
+
+ def apply_uri_snippet(self, snippet, mime, uri):
+ # Remove file scheme
+ gfile = Gio.file_new_for_uri(uri)
+
+ environ = {'utf8': {'GEDIT_DROP_DOCUMENT_TYPE': mime.encode('utf-8')},
+ 'noenc': {'GEDIT_DROP_DOCUMENT_TYPE': mime}}
+
+ self.env_add_for_location(environ, gfile, 'GEDIT_DROP_DOCUMENT')
+
+ buf = self.view.get_buffer()
+ location = buf.get_file().get_location()
+
+ relpath = location.get_relative_path(gfile)
+
+ # CHECK: what is the encoding of relpath?
+ environ['utf8']['GEDIT_DROP_DOCUMENT_RELATIVE_PATH'] = relpath.encode('utf-8')
+ environ['noenc']['GEDIT_DROP_DOCUMENT_RELATIVE_PATH'] = relpath
+
+ mark = buf.get_mark('gtk_drag_target')
+
+ if not mark:
+ mark = buf.get_insert()
+
+ piter = buf.get_iter_at_mark(mark)
+ self.apply_snippet(snippet, piter, piter, environ)
+
+ def in_bounds(self, x, y):
+ rect = self.view.get_visible_rect()
+ rect.x, rect.y = self.view.buffer_to_window_coords(Gtk.TextWindowType.WIDGET, rect.x, rect.y)
+
+ return not (x < rect.x or x > rect.x + rect.width or y < rect.y or y > rect.y + rect.height)
+
+ def on_drag_data_received(self, view, context, x, y, data, info, timestamp):
+ if not self.view.get_editable():
+ return
+
+ uris = helper.drop_get_uris(data)
+ if not uris:
+ return
+
+ if not self.in_bounds(x, y):
+ return
+
+ uris.reverse()
+ stop = False
+
+ for uri in uris:
+ try:
+ mime = Gio.content_type_guess(uri)
+ except:
+ mime = None
+
+ if not mime:
+ continue
+
+ snippets = Library().from_drop_target(mime, self.language_id)
+
+ if snippets:
+ stop = True
+ self.apply_uri_snippet(snippets[0], mime, uri)
+
+ if stop:
+ context.finish(True, False, timestamp)
+ view.stop_emission('drag-data-received')
+ view.get_toplevel().present()
+ view.grab_focus()
+
+ def find_uri_target(self, context):
+ lst = Gtk.target_list_add_uri_targets((), 0)
+
+ return self.view.drag_dest_find_target(context, lst)
+
+ def on_proposal_activated(self, proposal, piter):
+ if not self.view.get_editable():
+ return False
+
+ buf = self.view.get_buffer()
+ bounds = buf.get_selection_bounds()
+
+ if bounds:
+ self.apply_snippet(proposal.snippet(), None, None)
+ else:
+ (word, start, end) = self.get_tab_tag(buf, piter)
+ self.apply_snippet(proposal.snippet(), start, end)
+
+ return True
+
+ def on_default_activated(self, proposal, piter):
+ buf = self.view.get_buffer()
+ bounds = buf.get_selection_bounds()
+
+ if bounds:
+ buf.begin_user_action()
+ buf.delete(bounds[0], bounds[1])
+ buf.insert(bounds[0], proposal.props.label)
+ buf.end_user_action()
+
+ return True
+ else:
+ return False
+
+ def iter_coords(self, piter):
+ rect = self.view.get_iter_location(piter)
+ rect.x, rect.y = self.view.buffer_to_window_coords(Gtk.TextWindowType.TEXT, rect.x, rect.y)
+
+ return rect
+
+ def placeholder_in_area(self, placeholder, area):
+ start = placeholder.begin_iter()
+ end = placeholder.end_iter()
+
+ if not start or not end:
+ return False
+
+ # Test if start is before bottom, and end is after top
+ start_rect = self.iter_coords(start)
+ end_rect = self.iter_coords(end)
+
+ return start_rect.y <= area.y + area.height and \
+ end_rect.y + end_rect.height >= area.y
+
+ def draw_placeholder_rect(self, ctx, placeholder):
+ start = placeholder.begin_iter()
+ start_rect = self.iter_coords(start)
+ start_line = start.get_line()
+
+ end = placeholder.end_iter()
+ end_rect = self.iter_coords(end)
+ end_line = end.get_line()
+
+ line = start.copy()
+ line.set_line_offset(0)
+ geom = self.view.get_window(Gtk.TextWindowType.TEXT).get_geometry()
+
+ ctx.translate(0.5, 0.5)
+
+ while line.get_line() <= end_line:
+ ypos, height = self.view.get_line_yrange(line)
+ x_, ypos = self.view.window_to_buffer_coords(Gtk.TextWindowType.TEXT, 0, ypos)
+
+ if line.get_line() == start_line and line.get_line() == end_line:
+ # Simply draw a box, both are on the same line
+ ctx.rectangle(start_rect.x, start_rect.y, end_rect.x - start_rect.x, start_rect.height - 1)
+ ctx.stroke()
+ elif line.get_line() == start_line or line.get_line() == end_line:
+ if line.get_line() == start_line:
+ rect = start_rect
+ else:
+ rect = end_rect
+
+ ctx.move_to(0, rect.y + rect.height - 1)
+ ctx.rel_line_to(rect.x, 0)
+ ctx.rel_line_to(0, -rect.height + 1)
+ ctx.rel_line_to(geom[2], 0)
+ ctx.stroke()
+
+ if not line.forward_line():
+ break
+
+ def draw_placeholder_bar(self, ctx, placeholder):
+ start = placeholder.begin_iter()
+ start_rect = self.iter_coords(start)
+
+ ctx.translate(0.5, 0.5)
+ extend_width = 2.5
+
+ ctx.move_to(start_rect.x - extend_width, start_rect.y)
+ ctx.rel_line_to(extend_width * 2, 0)
+
+ ctx.move_to(start_rect.x, start_rect.y)
+ ctx.rel_line_to(0, start_rect.height - 1)
+
+ ctx.rel_move_to(-extend_width, 0)
+ ctx.rel_line_to(extend_width * 2, 0)
+ ctx.stroke()
+
+ def draw_placeholder(self, ctx, placeholder):
+ if isinstance(placeholder, PlaceholderEnd):
+ return
+
+ col = self.view.get_style_context().get_color(Gtk.StateFlags.INSENSITIVE)
+ col.alpha = 0.5
+ Gdk.cairo_set_source_rgba(ctx, col)
+
+ if placeholder.tabstop > 0:
+ ctx.set_dash([], 0)
+ else:
+ ctx.set_dash([2], 0)
+
+ start = placeholder.begin_iter()
+ end = placeholder.end_iter()
+
+ if start.equal(end):
+ self.draw_placeholder_bar(ctx, placeholder)
+ else:
+ self.draw_placeholder_rect(ctx, placeholder)
+
+ def on_draw(self, view, ctx):
+ window = view.get_window(Gtk.TextWindowType.TEXT)
+
+ if not Gtk.cairo_should_draw_window(ctx, window):
+ return False
+
+ # Draw something
+ ctx.set_line_width(1.0)
+
+ Gtk.cairo_transform_to_window(ctx, view, window)
+
+ clipped, clip = Gdk.cairo_get_clip_rectangle(ctx)
+
+ if not clipped:
+ return False
+
+ for placeholder in self.ordered_placeholders:
+ if not self.placeholder_in_area(placeholder, clip):
+ continue
+
+ ctx.save()
+ self.draw_placeholder(ctx, placeholder)
+ ctx.restore()
+
+ return False
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/exporter.py b/plugins/snippets/snippets/exporter.py
new file mode 100644
index 0000000..8f8249e
--- /dev/null
+++ b/plugins/snippets/snippets/exporter.py
@@ -0,0 +1,122 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import tempfile
+import shutil
+
+import xml.etree.ElementTree as et
+from . import helper
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class Exporter:
+ def __init__(self, filename, snippets):
+ self.filename = filename
+ self.set_snippets(snippets)
+
+ def set_snippets(self, snippets):
+ self.snippets = {}
+
+ for snippet in snippets:
+ lang = snippet.language()
+
+ if lang in self.snippets:
+ self.snippets[lang].append(snippet)
+ else:
+ self.snippets[lang] = [snippet]
+
+ def export_xml(self, dirname, language, snippets):
+ # Create the root snippets node
+ root = et.Element('snippets')
+
+ # Create filename based on language
+ if language:
+ filename = os.path.join(dirname, language + '.xml')
+
+ # Set the language attribute
+ root.attrib['language'] = language
+ else:
+ filename = os.path.join(dirname, 'global.xml')
+
+ # Add all snippets to the root node
+ for snippet in snippets:
+ root.append(snippet.to_xml())
+
+ # Write xml
+ helper.write_xml(root, filename, ('text', 'accelerator'))
+
+ def export_archive(self, cmd):
+ dirname = tempfile.mkdtemp()
+
+ # Save current working directory and change to temporary directory
+ curdir = os.getcwd()
+
+ try:
+ os.chdir(dirname)
+
+ # Write snippet xml files
+ for language, snippets in self.snippets.items():
+ self.export_xml(dirname, language , snippets)
+
+ # Archive files
+ status = os.system('%s "%s" *.xml' % (cmd, self.filename))
+ finally:
+ os.chdir(curdir)
+
+ if status != 0:
+ return _('The archive “%s” could not be created' % self.filename)
+
+ # Remove the temporary directory
+ shutil.rmtree(dirname)
+
+ def export_targz(self):
+ self.export_archive('tar -c --gzip -f')
+
+ def export_tarbz2(self):
+ self.export_archive('tar -c --bzip2 -f')
+
+ def export_tar(self):
+ self.export_archive('tar -cf')
+
+ def run(self):
+ dirname = os.path.dirname(self.filename)
+ if not os.path.exists(dirname):
+ return _('Target directory “%s” does not exist') % dirname
+
+ if not os.path.isdir(dirname):
+ return _('Target directory “%s” is not a valid directory') % dirname
+
+ (root, ext) = os.path.splitext(self.filename)
+
+ actions = {'.tar.gz': self.export_targz,
+ '.tar.bz2': self.export_tarbz2,
+ '.tar': self.export_tar}
+
+ for k, v in actions.items():
+ if self.filename.endswith(k):
+ return v()
+
+ return self.export_targz()
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/helper.py b/plugins/snippets/snippets/helper.py
new file mode 100644
index 0000000..2fa3b3f
--- /dev/null
+++ b/plugins/snippets/snippets/helper.py
@@ -0,0 +1,204 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from xml.sax import saxutils
+import xml.etree.ElementTree as et
+import re
+import codecs
+
+from gi.repository import Gtk
+
+def message_dialog(par, typ, msg):
+ d = Gtk.MessageDialog(par, Gtk.DialogFlags.MODAL, typ, Gtk.ButtonsType.OK, msg)
+ d.set_property('use-markup', True)
+
+ d.run()
+ d.destroy()
+
+def compute_indentation(view, piter):
+ line = piter.get_line()
+ start = view.get_buffer().get_iter_at_line(line)
+ end = start.copy()
+
+ ch = end.get_char()
+
+ while (ch.isspace() and ch != '\r' and ch != '\n' and \
+ end.compare(piter) < 0):
+ if not end.forward_char():
+ break;
+
+ ch = end.get_char()
+
+ if start.equal(end):
+ return ''
+
+ return start.get_slice(end)
+
+def markup_escape(text):
+ return saxutils.escape(text)
+
+def spaces_instead_of_tabs(view, text):
+ if not view.get_insert_spaces_instead_of_tabs():
+ return text
+
+ return text.replace("\t", view.get_tab_width() * ' ')
+
+def insert_with_indent(view, piter, text, indentfirst = True, context = None):
+ text = spaces_instead_of_tabs(view, text)
+ lines = text.split('\n')
+ buf = view.get_buffer()
+
+ buf._snippets_context = context
+
+ if len(lines) == 1:
+ view.get_buffer().insert(piter, text)
+ else:
+ # Compute indentation
+ indent = compute_indentation(view, piter)
+ text = ''
+
+ for i in range(0, len(lines)):
+ if indentfirst or i > 0:
+ text += indent + lines[i] + '\n'
+ else:
+ text += lines[i] + '\n'
+
+ buf.insert(piter, text[:-1])
+
+ buf._snippets_context = None
+
+def get_buffer_context(buf):
+ if hasattr(buf, "_snippets_context"):
+ return buf._snippets_context
+ return None
+
+def snippets_debug(*s):
+ return
+
+def write_xml(node, f, cdata_nodes=()):
+ assert node is not None
+
+ if not hasattr(f, "write"):
+ f = codecs.open(f, "wb", encoding="utf-8")
+
+ # Encoding
+ f.write("<?xml version='1.0' encoding='utf-8'?>\n")
+
+ _write_node(node, f, cdata_nodes)
+
+def _write_indent(file, text, indent):
+ file.write(' ' * indent + text)
+
+def _write_node(node, file, cdata_nodes=(), indent=0):
+ # write XML to file
+ tag = node.tag
+
+ if node is et.Comment:
+ _write_indent(file, "<!-- %s -->\n" % saxutils.escape(node.text), indent)
+ elif node is et.ProcessingInstruction:
+ _write_indent(file, "<?%s?>\n" % saxutils.escape(node.text), indent)
+ else:
+ items = node.items()
+
+ if items or node.text or len(node):
+ _write_indent(file, "<" + tag, indent)
+
+ if items:
+ items.sort() # lexical order
+ for k, v in items:
+ file.write(" %s=%s" % (k, saxutils.quoteattr(v)))
+ if node.text or len(node):
+ file.write(">")
+ if node.text and node.text.strip() != "":
+ if tag in cdata_nodes:
+ file.write(_cdata(node.text))
+ else:
+ file.write(saxutils.escape(node.text))
+ else:
+ file.write("\n")
+
+ for n in node:
+ _write_node(n, file, cdata_nodes, indent + 1)
+
+ if not len(node):
+ file.write("</" + tag + ">\n")
+ else:
+ _write_indent(file, "</" + tag + ">\n", \
+ indent)
+ else:
+ file.write(" />\n")
+
+ if node.tail and node.tail.strip() != "":
+ file.write(saxutils.escape(node.tail))
+
+def _cdata(text):
+ return '<![CDATA[' + text.replace(']]>', ']]]]><![CDATA[>') + ']]>'
+
+def is_tab_trigger(w):
+ if len(w) == 1 and not (w.isalnum() or w.isspace()):
+ return True
+
+ if not is_first_tab_trigger_character(w[0]):
+ return False
+
+ for c in w:
+ if not is_tab_trigger_character(c):
+ return False
+
+ return True
+
+def is_first_tab_trigger_character(c):
+ return c.isalpha() or c in '_:.'
+
+def is_tab_trigger_character(c):
+ return c.isalnum() or c in '_:.'
+
+def buffer_word_boundary(buf):
+ iter = buf.get_iter_at_mark(buf.get_insert())
+ start = iter.copy()
+
+ if not iter.starts_word() and (iter.inside_word() or iter.ends_word()):
+ start.backward_word_start()
+
+ if not iter.ends_word() and iter.inside_word():
+ iter.forward_word_end()
+
+ return (start, iter)
+
+def buffer_line_boundary(buf):
+ iter = buf.get_iter_at_mark(buf.get_insert())
+ start = iter.copy()
+ start.set_line_offset(0)
+
+ if not iter.ends_line():
+ iter.forward_to_line_end()
+
+ return (start, iter)
+
+def drop_get_uris(selection):
+ uris = []
+ if selection.targets_include_uri():
+ data = selection.get_data()
+ lines = re.split('\\s*[\\n\\r]+\\s*', data.strip())
+
+ for line in lines:
+ if not line.startswith('#'):
+ uris.append(line)
+
+ return uris
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/importer.py b/plugins/snippets/snippets/importer.py
new file mode 100644
index 0000000..2718596
--- /dev/null
+++ b/plugins/snippets/snippets/importer.py
@@ -0,0 +1,134 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import errno
+import tempfile
+import sys
+import shutil
+
+from .library import Library
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class Importer:
+ def __init__(self, filename):
+ self.filename = filename
+
+ def import_destination(self, filename):
+ userdir = Library().userdir
+
+ filename = os.path.basename(filename)
+ (root, ext) = os.path.splitext(filename)
+
+ filename = os.path.join(userdir, root + ext)
+ i = 1
+
+ while os.path.exists(filename):
+ filename = os.path.join(userdir, root + '_' + str(i) + ext)
+ i += 1
+
+ return (userdir, filename)
+
+ def import_file(self, filename):
+ if not os.path.exists(filename):
+ return _('File “%s” does not exist') % filename
+
+ if not os.path.isfile(filename):
+ return _('File “%s” is not a valid snippets file') % filename
+
+ # Find destination for file to copy to
+ destdir, dest = self.import_destination(filename)
+
+ # Make sure dir exists
+ try:
+ os.makedirs(destdir)
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+
+ # Copy file
+ shutil.copy(filename, dest)
+
+ # Add library
+ if not Library().add_user_library(dest):
+ return _('Imported file “%s” is not a valid snippets file') % os.path.basename(dest)
+
+ def import_xml(self):
+ return self.import_file(self.filename)
+
+ def import_archive(self, cmd):
+ dirname = tempfile.mkdtemp()
+ status = os.system('cd %s; %s "%s"' % (dirname, cmd, self.filename))
+
+ if status != 0:
+ return _('The archive “%s” could not be extracted' % self.filename)
+
+ errors = []
+
+ # Now import all the files from the archive
+ for f in os.listdir(dirname):
+ f = os.path.join(dirname, f)
+
+ if os.path.isfile(f):
+ if self.import_file(f):
+ errors.append(os.path.basename(f))
+ else:
+ sys.stderr.write('Skipping %s, not a valid snippets file' % os.path.basename(f))
+
+ # Remove the temporary directory
+ shutil.rmtree(dirname)
+
+ if len(errors) > 0:
+ return _('The following files could not be imported: %s') % ', '.join(errors)
+
+ def import_targz(self):
+ self.import_archive('tar -x --gzip -f')
+
+ def import_tarbz2(self):
+ self.import_archive('tar -x --bzip2 -f')
+
+ def import_tar(self):
+ self.import_archive('tar -xf')
+
+ def run(self):
+ if not os.path.exists(self.filename):
+ return _('File “%s” does not exist') % self.filename
+
+ if not os.path.isfile(self.filename):
+ return _('File “%s” is not a valid snippets archive') % self.filename
+
+ (root, ext) = os.path.splitext(self.filename)
+
+ actions = {'.tar.gz': self.import_targz,
+ '.tar.bz2': self.import_tarbz2,
+ '.xml': self.import_xml,
+ '.tar': self.import_tar}
+
+ for k, v in actions.items():
+ if self.filename.endswith(k):
+ return v()
+
+ return _('File “%s” is not a valid snippets archive') % self.filename
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/languagemanager.py b/plugins/snippets/snippets/languagemanager.py
new file mode 100644
index 0000000..6f74319
--- /dev/null
+++ b/plugins/snippets/snippets/languagemanager.py
@@ -0,0 +1,40 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gi.repository import GtkSource
+import os
+
+from .library import Library
+
+global manager
+manager = None
+
+def get_language_manager():
+ global manager
+
+ if not manager:
+ dirs = []
+
+ for d in Library().systemdirs:
+ dirs.append(os.path.join(d, 'lang'))
+
+ manager = GtkSource.LanguageManager()
+ manager.set_search_path(dirs + manager.get_search_path())
+
+ return manager
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/library.py b/plugins/snippets/snippets/library.py
new file mode 100644
index 0000000..7ce163b
--- /dev/null
+++ b/plugins/snippets/snippets/library.py
@@ -0,0 +1,989 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import weakref
+import sys
+import re
+
+from gi.repository import Gdk, Gtk
+
+import xml.etree.ElementTree as et
+from . import helper
+
+class NamespacedId:
+ def __init__(self, namespace, id):
+ if not id:
+ self.id = None
+ else:
+ if namespace:
+ self.id = namespace + '-'
+ else:
+ self.id = 'global-'
+
+ self.id += id
+
+class SnippetData:
+ PROPS = {'tag': '', 'text': '', 'description': 'New snippet',
+ 'accelerator': '', 'drop-targets': ''}
+
+ def __init__(self, node, library):
+ self.priv_id = node.attrib.get('id')
+
+ self.set_library(library)
+ self.valid = False
+ self.set_node(node)
+
+ def can_modify(self):
+ return (self.library and (isinstance(self.library(), SnippetsUserFile)))
+
+ def set_library(self, library):
+ if library:
+ self.library = weakref.ref(library)
+ else:
+ self.library = None
+
+ self.id = NamespacedId(self.language(), self.priv_id).id
+
+ def set_node(self, node):
+ if self.can_modify():
+ self.node = node
+ else:
+ self.node = None
+
+ self.init_snippet_data(node)
+
+ def init_snippet_data(self, node):
+ if node is None:
+ return
+
+ self.override = node.attrib.get('override')
+
+ self.properties = {}
+ props = SnippetData.PROPS.copy()
+
+ # Store all properties present
+ for child in node:
+ if child.tag in props:
+ del props[child.tag]
+
+ # Normalize accelerator
+ if child.tag == 'accelerator' and child.text != None:
+ keyval, mod = Gtk.accelerator_parse(child.text)
+
+ if Gtk.accelerator_valid(keyval, mod):
+ child.text = Gtk.accelerator_name(keyval, mod)
+ else:
+ child.text = ''
+
+ if self.can_modify():
+ self.properties[child.tag] = child
+ else:
+ self.properties[child.tag] = child.text or ''
+
+ # Create all the props that were not found so we stay consistent
+ for prop in props:
+ if self.can_modify():
+ child = et.SubElement(node, prop)
+
+ child.text = props[prop]
+ self.properties[prop] = child
+ else:
+ self.properties[prop] = props[prop]
+
+ self.check_validation()
+
+ def check_validation(self):
+ if not self['tag'] and not self['accelerator'] and not self['drop-targets']:
+ return False
+
+ library = Library()
+ keyval, mod = Gtk.accelerator_parse(self['accelerator'])
+
+ self.valid = library.valid_tab_trigger(self['tag']) and \
+ (not self['accelerator'] or library.valid_accelerator(keyval, mod))
+
+ def _format_prop(self, prop, value):
+ if prop == 'drop-targets' and value != '':
+ return re.split('\\s*[,;]\\s*', value)
+ else:
+ return value
+
+ def __getitem__(self, prop):
+ if prop in self.properties:
+ if self.can_modify():
+ return self._format_prop(prop, self.properties[prop].text or '')
+ else:
+ return self._format_prop(prop, self.properties[prop] or '')
+
+ return self._format_prop(prop, '')
+
+ def __setitem__(self, prop, value):
+ if not prop in self.properties:
+ return
+
+ if isinstance(value, list):
+ value = ','.join(value)
+
+ if not self.can_modify() and self.properties[prop] != value:
+ # ohoh, this is not can_modify, but it needs to be changed...
+ # make sure it is transfered to the changes file and set all the
+ # fields.
+ # This snippet data container will effectively become the container
+ # for the newly created node, but transparently to whoever uses
+ # it
+ self._override()
+
+ if self.can_modify() and self.properties[prop].text != value:
+ if self.library():
+ self.library().tainted = True
+
+ oldvalue = self.properties[prop].text
+ self.properties[prop].text = value
+
+ if prop == 'tag' or prop == 'accelerator' or prop == 'drop-targets':
+ container = Library().container(self.language())
+ container.prop_changed(self, prop, oldvalue)
+
+ self.check_validation()
+
+ def language(self):
+ if self.library and self.library():
+ return self.library().language
+ else:
+ return None
+
+ def is_override(self):
+ return self.override and Library().overridden[self.override]
+
+ def to_xml(self):
+ return self._create_xml()
+
+ def _create_xml(self, parent=None, update=False, attrib={}):
+ # Create a new node
+ if parent != None:
+ element = et.SubElement(parent, 'snippet', attrib)
+ else:
+ element = et.Element('snippet')
+
+ # Create all the properties
+ for p in self.properties:
+ prop = et.SubElement(element, p)
+ prop.text = self[p]
+
+ if update:
+ self.properties[p] = prop
+
+ return element
+
+ def _override(self):
+ # Find the user file
+ target = Library().get_user_library(self.language())
+
+ # Create a new node there with override
+ element = self._create_xml(target.root, True, {'override': self.id})
+
+ # Create an override snippet data, feed it element so that it stores
+ # all the values and then set the node to None so that it only contains
+ # the values in .properties
+ override = SnippetData(element, self.library())
+ override.set_node(None)
+ override.id = self.id
+
+ # Set our node to the new element
+ self.node = element
+
+ # Set the override to our id
+ self.override = self.id
+ self.id = None
+
+ # Set the new library
+ self.set_library(target)
+
+ # The library is tainted because we added this snippet
+ target.tainted = True
+
+ # Add the override
+ Library().overridden[self.override] = override
+
+ def revert(self, snippet):
+ userlib = self.library()
+ self.set_library(snippet.library())
+
+ userlib.remove(self.node)
+
+ self.set_node(None)
+
+ # Copy the properties
+ self.properties = snippet.properties
+
+ # Set the id
+ self.id = snippet.id
+
+ # Reset the override flag
+ self.override = None
+
+class SnippetsTreeBuilder(et.TreeBuilder):
+ def __init__(self, start=None, end=None):
+ et.TreeBuilder.__init__(self)
+ self.set_start(start)
+ self.set_end(end)
+
+ def set_start(self, start):
+ self._start_cb = start
+
+ def set_end(self, end):
+ self._end_cb = end
+
+ def start(self, tag, attrs):
+ result = et.TreeBuilder.start(self, tag, attrs)
+
+ if self._start_cb:
+ self._start_cb(result)
+
+ return result
+
+ def end(self, tag):
+ result = et.TreeBuilder.end(self, tag)
+
+ if self._end_cb:
+ self._end_cb(result)
+
+ return result
+
+class LanguageContainer:
+ def __init__(self, language):
+ self.language = language
+ self.snippets = []
+ self.snippets_by_prop = {'tag': {}, 'accelerator': {}, 'drop-targets': {}}
+ self.accel_group = Gtk.AccelGroup()
+ self._refs = 0
+
+ def _add_prop(self, snippet, prop, value=0):
+ if value == 0:
+ value = snippet[prop]
+
+ if not value or value == '':
+ return
+
+ helper.snippets_debug('Added ', prop ,' ', value, ' to ', str(self.language))
+
+ if prop == 'accelerator':
+ keyval, mod = Gtk.accelerator_parse(value)
+ self.accel_group.connect(keyval, mod, 0, \
+ Library().accelerator_activated)
+
+ snippets = self.snippets_by_prop[prop]
+
+ if not isinstance(value, list):
+ value = [value]
+
+ for val in value:
+ if val in snippets:
+ snippets[val].append(snippet)
+ else:
+ snippets[val] = [snippet]
+
+ def _remove_prop(self, snippet, prop, value=0):
+ if value == 0:
+ value = snippet[prop]
+
+ if not value or value == '':
+ return
+
+ helper.snippets_debug('Removed ', prop, ' ', value, ' from ', str(self.language))
+
+ if prop == 'accelerator':
+ keyval, mod = Gtk.accelerator_parse(value)
+ self.accel_group.disconnect_key(keyval, mod)
+
+ snippets = self.snippets_by_prop[prop]
+
+ if not isinstance(value, list):
+ value = [value]
+
+ for val in value:
+ try:
+ snippets[val].remove(snippet)
+ except:
+ True
+
+ def append(self, snippet):
+ self.snippets.append(snippet)
+
+ self._add_prop(snippet, 'tag')
+ self._add_prop(snippet, 'accelerator')
+ self._add_prop(snippet, 'drop-targets')
+
+ return snippet
+
+ def remove(self, snippet):
+ try:
+ self.snippets.remove(snippet)
+ except:
+ True
+
+ self._remove_prop(snippet, 'tag')
+ self._remove_prop(snippet, 'accelerator')
+ self._remove_prop(snippet, 'drop-targets')
+
+ def prop_changed(self, snippet, prop, oldvalue):
+ helper.snippets_debug('PROP CHANGED (', prop, ')', oldvalue)
+
+ self._remove_prop(snippet, prop, oldvalue)
+ self._add_prop(snippet, prop)
+
+ def from_prop(self, prop, value):
+ snippets = self.snippets_by_prop[prop]
+
+ if prop == 'drop-targets':
+ s = []
+
+ # FIXME: change this to use
+ # gnomevfs.mime_type_get_equivalence when it comes
+ # available
+ for key, val in snippets.items():
+ if not value.startswith(key):
+ continue
+
+ for snippet in snippets[key]:
+ if not snippet in s:
+ s.append(snippet)
+
+ return s
+ else:
+ if value in snippets:
+ return snippets[value]
+ else:
+ return []
+
+ def ref(self):
+ self._refs += 1
+
+ return True
+
+ def unref(self):
+ if self._refs > 0:
+ self._refs -= 1
+
+ return self._refs != 0
+
+class SnippetsSystemFile:
+ def __init__(self, path=None):
+ self.path = path
+ self.loaded = False
+ self.language = None
+ self.ok = True
+ self.need_id = True
+
+ def load_error(self, message):
+ sys.stderr.write("An error occurred loading " + self.path + ":\n")
+ sys.stderr.write(message + "\nSnippets in this file will not be " \
+ "available, please correct or remove the file.\n")
+
+ def _add_snippet(self, element):
+ if not self.need_id or element.attrib.get('id'):
+ self.loading_elements.append(element)
+
+ def set_language(self, element):
+ self.language = element.attrib.get('language')
+
+ if self.language:
+ self.language = self.language.lower()
+
+ def _set_root(self, element):
+ self.set_language(element)
+
+ def _preprocess_element(self, element):
+ if not self.loaded:
+ if not element.tag == "snippets":
+ self.load_error("Root element should be `snippets' instead " \
+ "of `%s'" % element.tag)
+ return False
+ else:
+ self._set_root(element)
+ self.loaded = True
+ elif element.tag != 'snippet' and not self.insnippet:
+ self.load_error("Element should be `snippet' instead of `%s'" \
+ % element.tag)
+ return False
+ else:
+ self.insnippet = True
+
+ return True
+
+ def _process_element(self, element):
+ if element.tag == 'snippet':
+ self._add_snippet(element)
+ self.insnippet = False
+
+ return True
+
+ def ensure(self):
+ if not self.ok or self.loaded:
+ return
+
+ self.load()
+
+ def parse_xml(self, readsize=16384):
+ if not self.path:
+ return
+
+ elements = []
+
+ builder = SnippetsTreeBuilder( \
+ lambda node: elements.append((node, True)), \
+ lambda node: elements.append((node, False)))
+
+ parser = et.XMLParser(target=builder)
+ self.insnippet = False
+
+ try:
+ f = open(self.path, "r", encoding='utf-8')
+ except IOError:
+ self.ok = False
+ return
+
+ while True:
+ try:
+ data = f.read(readsize)
+ except IOError:
+ self.ok = False
+ break
+
+ if not data:
+ break
+
+ try:
+ parser.feed(data)
+ except Exception:
+ self.ok = False
+ break
+
+ for element in elements:
+ yield element
+
+ del elements[:]
+
+ f.close()
+
+ def load(self):
+ if not self.ok:
+ return
+
+ helper.snippets_debug("Loading library (" + str(self.language) + "): " + \
+ self.path)
+
+ self.loaded = False
+ self.ok = False
+ self.loading_elements = []
+
+ for element in self.parse_xml():
+ if element[1]:
+ if not self._preprocess_element(element[0]):
+ del self.loading_elements[:]
+ return
+ else:
+ if not self._process_element(element[0]):
+ del self.loading_elements[:]
+ return
+
+ for element in self.loading_elements:
+ Library().add_snippet(self, element)
+
+ del self.loading_elements[:]
+ self.ok = True
+
+ # This function will get the language for a file by just inspecting the
+ # root element of the file. This is provided so that a cache can be built
+ # for which file contains which language.
+ # It returns the name of the language
+ def ensure_language(self):
+ if not self.loaded:
+ self.ok = False
+
+ for element in self.parse_xml(256):
+ if element[1]:
+ if element[0].tag == 'snippets':
+ self.set_language(element[0])
+ self.ok = True
+
+ break
+
+ def unload(self):
+ helper.snippets_debug("Unloading library (" + str(self.language) + "): " + \
+ self.path)
+ self.language = None
+ self.loaded = False
+ self.ok = True
+
+class SnippetsUserFile(SnippetsSystemFile):
+ def __init__(self, path=None):
+ SnippetsSystemFile.__init__(self, path)
+ self.tainted = False
+ self.need_id = False
+
+ def _set_root(self, element):
+ SnippetsSystemFile._set_root(self, element)
+ self.root = element
+
+ def add_prop(self, node, tag, data):
+ if data[tag]:
+ prop = et.SubElement(node, tag)
+ prop.text = data[tag]
+
+ return prop
+ else:
+ return None
+
+ def new_snippet(self, properties=None):
+ if (not self.ok) or self.root is None:
+ return None
+
+ element = et.SubElement(self.root, 'snippet')
+
+ if properties:
+ for prop in properties:
+ sub = et.SubElement(element, prop)
+ sub.text = properties[prop]
+
+ self.tainted = True
+
+ return Library().add_snippet(self, element)
+
+ def set_language(self, element):
+ SnippetsSystemFile.set_language(self, element)
+
+ filename = os.path.basename(self.path).lower()
+
+ if not self.language and filename == "global.xml":
+ self.modifier = True
+ elif self.language and filename == self.language + ".xml":
+ self.modifier = True
+ else:
+ self.modifier = False
+
+ def create_root(self, language):
+ if self.loaded:
+ helper.snippets_debug('Not creating root, already loaded')
+ return
+
+ if language:
+ root = et.Element('snippets', {'language': language})
+ self.path = os.path.join(Library().userdir, language.lower() + '.xml')
+ else:
+ root = et.Element('snippets')
+ self.path = os.path.join(Library().userdir, 'global.xml')
+
+ self._set_root(root)
+ self.loaded = True
+ self.ok = True
+ self.tainted = True
+ self.save()
+
+ def remove(self, element):
+ try:
+ self.root.remove(element)
+ self.tainted = True
+ except:
+ return
+
+ try:
+ self.root[0]
+ except:
+ # No more elements, this library is useless now
+ Library().remove_library(self)
+
+ def save(self):
+ if not self.ok or self.root is None or not self.tainted:
+ return
+
+ path = os.path.dirname(self.path)
+
+ try:
+ if not os.path.isdir(path):
+ os.makedirs(path, 0o755)
+ except OSError:
+ # TODO: this is bad...
+ sys.stderr.write("Error in making dirs\n")
+
+ try:
+ helper.write_xml(self.root, self.path, ('text', 'accelerator'))
+ self.tainted = False
+ except IOError:
+ # Couldn't save, what to do
+ sys.stderr.write("Could not save user snippets file to " + \
+ self.path + "\n")
+
+ def unload(self):
+ SnippetsSystemFile.unload(self)
+ self.root = None
+
+class Singleton(object):
+ _instance = None
+
+ def __new__(cls, *args, **kwargs):
+ if not cls._instance:
+ cls._instance = super(Singleton, cls).__new__(
+ cls, *args, **kwargs)
+ cls._instance.__init_once__()
+
+ return cls._instance
+
+class Library(Singleton):
+ def __init_once__(self):
+ self._accelerator_activated_cb = []
+ self.loaded = False
+ self.check_buffer = Gtk.TextBuffer()
+
+ def set_dirs(self, userdir, systemdirs):
+ self.userdir = userdir
+ self.systemdirs = systemdirs
+
+ self.libraries = {}
+ self.containers = {}
+ self.overridden = {}
+ self.loaded_ids = []
+
+ self.loaded = False
+
+ def add_accelerator_callback(self, cb):
+ self._accelerator_activated_cb.append(cb)
+
+ def remove_accelerator_callback(self, cb):
+ self._accelerator_activated_cb.remove(cb)
+
+ def accelerator_activated(self, group, obj, keyval, mod):
+ ret = False
+
+ for cb in self._accelerator_activated_cb:
+ ret = cb(group, obj, keyval, mod)
+
+ if ret:
+ break
+
+ return ret
+
+ def add_snippet(self, library, element):
+ container = self.container(library.language)
+ overrided = self.overrided(library, element)
+
+ if overrided:
+ overrided.set_library(library)
+ helper.snippets_debug('Snippet is overriden: ' + overrided['description'])
+ return None
+
+ snippet = SnippetData(element, library)
+
+ if snippet.id in self.loaded_ids:
+ helper.snippets_debug('Not added snippet ' + str(library.language) + \
+ '::' + snippet['description'] + ' (duplicate)')
+ return None
+
+ snippet = container.append(snippet)
+ helper.snippets_debug('Added snippet ' + str(library.language) + '::' + \
+ snippet['description'])
+
+ if snippet and snippet.override:
+ self.add_override(snippet)
+
+ if snippet.id:
+ self.loaded_ids.append(snippet.id)
+
+ return snippet
+
+ def container(self, language):
+ language = self.normalize_language(language)
+
+ if not language in self.containers:
+ self.containers[language] = LanguageContainer(language)
+
+ return self.containers[language]
+
+ def get_user_library(self, language):
+ target = None
+
+ if language in self.libraries:
+ for library in self.libraries[language]:
+ if isinstance(library, SnippetsUserFile) and library.modifier:
+ target = library
+ elif not isinstance(library, SnippetsUserFile):
+ break
+
+ if not target:
+ # Create a new user file then
+ helper.snippets_debug('Creating a new user file for language ' + \
+ str(language))
+ target = SnippetsUserFile()
+ target.create_root(language)
+ self.add_library(target)
+
+ return target
+
+ def new_snippet(self, language, properties=None):
+ language = self.normalize_language(language)
+ library = self.get_user_library(language)
+
+ return library.new_snippet(properties)
+
+ def revert_snippet(self, snippet):
+ # This will revert the snippet to the one it overrides
+ if not snippet.can_modify() or not snippet.override in self.overridden:
+ # It can't be reverted, shouldn't happen, but oh..
+ return
+
+ # The snippet in self.overriden only contains the property contents and
+ # the library it belongs to
+ revertto = self.overridden[snippet.override]
+ del self.overridden[snippet.override]
+
+ if revertto:
+ snippet.revert(revertto)
+
+ if revertto.id:
+ self.loaded_ids.append(revertto.id)
+
+ def remove_snippet(self, snippet):
+ if not snippet.can_modify() or snippet.is_override():
+ return
+
+ # Remove from the library
+ userlib = snippet.library()
+ userlib.remove(snippet.node)
+
+ # Remove from the container
+ container = self.containers[userlib.language]
+ container.remove(snippet)
+
+ def overrided(self, library, element):
+ id = NamespacedId(library.language, element.attrib.get('id')).id
+
+ if id in self.overridden:
+ snippet = SnippetData(element, None)
+ snippet.set_node(None)
+
+ self.overridden[id] = snippet
+ return snippet
+ else:
+ return None
+
+ def add_override(self, snippet):
+ helper.snippets_debug('Add override:', snippet.override)
+ if not snippet.override in self.overridden:
+ self.overridden[snippet.override] = None
+
+ def add_library(self, library):
+ library.ensure_language()
+
+ if not library.ok:
+ helper.snippets_debug('Library in wrong format, ignoring')
+ return False
+
+ helper.snippets_debug('Adding library (' + str(library.language) + '): ' + \
+ library.path)
+
+ if library.language in self.libraries:
+ # Make sure all the user files are before the system files
+ if isinstance(library, SnippetsUserFile):
+ self.libraries[library.language].insert(0, library)
+ else:
+ self.libraries[library.language].append(library)
+ else:
+ self.libraries[library.language] = [library]
+
+ return True
+
+ def remove_library(self, library):
+ if not library.ok:
+ return
+
+ if library.path and os.path.isfile(library.path):
+ os.unlink(library.path)
+
+ try:
+ self.libraries[library.language].remove(library)
+ except KeyError:
+ True
+
+ container = self.containers[library.language]
+
+ for snippet in list(container.snippets):
+ if snippet.library() == library:
+ container.remove(snippet)
+
+ def add_user_library(self, path):
+ library = SnippetsUserFile(path)
+ return self.add_library(library)
+
+ def add_system_library(self, path):
+ library = SnippetsSystemFile(path)
+ return self.add_library(library)
+
+ def find_libraries(self, path, searched, addcb):
+ helper.snippets_debug("Finding in: " + path)
+
+ if not os.path.isdir(path):
+ return searched
+
+ files = os.listdir(path)
+ searched.append(path)
+
+ for f in files:
+ f = os.path.realpath(os.path.join(path, f))
+
+ # Determine what language this file provides snippets for
+ if os.path.isfile(f):
+ addcb(f)
+
+ return searched
+
+ def normalize_language(self, language):
+ if language:
+ return language.lower()
+
+ return language
+
+ def remove_container(self, language):
+ for snippet in self.containers[language].snippets:
+ if snippet.id in self.loaded_ids:
+ self.loaded_ids.remove(snippet.id)
+
+ if snippet.override in self.overridden:
+ del self.overridden[snippet.override]
+
+ del self.containers[language]
+
+ def get_accel_group(self, language):
+ language = self.normalize_language(language)
+ container = self.container(language)
+
+ self.ensure(language)
+ return container.accel_group
+
+ def save(self, language):
+ language = self.normalize_language(language)
+
+ if language in self.libraries:
+ for library in self.libraries[language]:
+ if isinstance(library, SnippetsUserFile):
+ library.save()
+ else:
+ break
+
+ def ref(self, language):
+ language = self.normalize_language(language)
+
+ helper.snippets_debug('Ref:', language)
+ self.container(language).ref()
+
+ def unref(self, language):
+ language = self.normalize_language(language)
+
+ helper.snippets_debug('Unref:', language)
+
+ if language in self.containers:
+ if not self.containers[language].unref() and \
+ language in self.libraries:
+
+ for library in self.libraries[language]:
+ library.unload()
+
+ self.remove_container(language)
+
+ def ensure(self, language):
+ self.ensure_files()
+ language = self.normalize_language(language)
+
+ # Ensure language as well as the global snippets (None)
+ for lang in (None, language):
+ if lang in self.libraries:
+ # Ensure the container exists
+ self.container(lang)
+
+ for library in self.libraries[lang]:
+ library.ensure()
+
+ def ensure_files(self):
+ if self.loaded:
+ return
+
+ searched = []
+ searched = self.find_libraries(self.userdir, searched, \
+ self.add_user_library)
+
+ for d in self.systemdirs:
+ searched = self.find_libraries(d, searched, \
+ self.add_system_library)
+
+ self.loaded = True
+
+ def valid_accelerator(self, keyval, mod):
+ mod &= Gtk.accelerator_get_default_mod_mask()
+
+ return (mod and (Gdk.keyval_to_unicode(keyval) or \
+ keyval in range(Gdk.KEY_F1, Gdk.KEY_F12 + 1)))
+
+ def valid_tab_trigger(self, trigger):
+ if not trigger:
+ return True
+
+ return helper.is_tab_trigger(trigger)
+
+ # Snippet getters
+ # ===============
+ def _from_prop(self, prop, value, language=None):
+ self.ensure_files()
+
+ result = []
+ language = self.normalize_language(language)
+
+ if not language in self.containers:
+ return []
+
+ self.ensure(language)
+ result = self.containers[language].from_prop(prop, value)
+
+ if len(result) == 0 and language and None in self.containers:
+ result = self.containers[None].from_prop(prop, value)
+
+ return result
+
+ # Get snippets for a given language
+ def get_snippets(self, language=None):
+ self.ensure_files()
+ language = self.normalize_language(language)
+
+ if not language in self.libraries:
+ return []
+
+ self.ensure(language)
+
+ return list(self.containers[language].snippets)
+
+ # Get snippets for a given accelerator
+ def from_accelerator(self, accelerator, language=None):
+ return self._from_prop('accelerator', accelerator, language)
+
+ # Get snippets for a given tag
+ def from_tag(self, tag, language=None):
+ return self._from_prop('tag', tag, language)
+
+ # Get snippets for a given drop target
+ def from_drop_target(self, drop_target, language=None):
+ return self._from_prop('drop-targets', drop_target, language)
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/manager.py b/plugins/snippets/snippets/manager.py
new file mode 100644
index 0000000..ca64f18
--- /dev/null
+++ b/plugins/snippets/snippets/manager.py
@@ -0,0 +1,1143 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import tempfile
+import shutil
+
+from gi.repository import Gtk, Gio, Gdk, GObject
+
+from .snippet import Snippet
+from . import helper
+from .library import Library
+from .importer import Importer
+from .exporter import Exporter
+from .languagemanager import get_language_manager
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+class Manager(Gtk.Window, Gtk.Buildable):
+ NAME_COLUMN = 0
+ SORT_COLUMN = 1
+ LANG_COLUMN = 2
+ SNIPPET_COLUMN = 3
+ TARGET_URI = 105
+
+ __gtype_name__ = "GeditSnippetsManager"
+
+ model = None
+ drag_icons = ('gnome-mime-application-x-tarz', 'gnome-package', 'package')
+ default_export_name = _('Snippets archive') + '.tar.gz'
+ dragging = False
+
+ def __init__(self):
+ self.snippet = None
+ self._temp_export = None
+ self._size = (0, 0)
+
+ self.key_press_id = 0
+ self.dnd_target_list = Gtk.TargetList.new([])
+ self.dnd_target_list.add(Gdk.atom_intern("text/uri-list", True), 0, self.TARGET_URI)
+
+ def get_final_size(self):
+ return self._size
+
+ def get_language_snippets(self, path, name = None):
+ library = Library()
+
+ name = self.get_language(path)
+ nodes = library.get_snippets(name)
+
+ return nodes
+
+ def add_new_snippet_node(self, parent):
+ return self.model.append(parent, ('<i>' + _('Add a new snippet…') + \
+ '</i>', '', None, None))
+
+ def fill_language(self, piter, expand=True):
+ # Remove all children
+ child = self.model.iter_children(piter)
+
+ while child and self.model.remove(child):
+ True
+
+ path = self.model.get_path(piter)
+ nodes = self.get_language_snippets(path)
+ language = self.get_language(path)
+
+ Library().ref(language)
+
+ if nodes:
+ for node in nodes:
+ self.add_snippet(piter, node)
+ else:
+ # Add node that tells there are no snippets currently
+ self.add_new_snippet_node(piter)
+
+ if expand:
+ self.tree_view.expand_row(path, False)
+
+ def build_model(self, force_reload = False):
+ window = Gio.Application.get_default().get_active_window()
+ if window:
+ view = window.get_active_view()
+
+ if not view:
+ current_lang = None
+ else:
+ current_lang = view.get_buffer().get_language()
+ else:
+ current_lang = None
+
+ tree_view = self['tree_view_snippets']
+ expand = None
+
+ if not self.model or force_reload:
+ self.model = Gtk.TreeStore(str, str, GObject.Object, object)
+ self.model.set_sort_column_id(self.SORT_COLUMN, Gtk.SortType.ASCENDING)
+
+ manager = get_language_manager()
+
+ langs = [manager.get_language(x) for x in manager.get_language_ids()]
+ langs.sort(key=lambda x: x.get_name())
+
+ piter = self.model.append(None, (_('Global'), '', None, None))
+
+ # Add dummy node
+ self.model.append(piter, ('', '', None, None))
+
+ nm = None
+
+ if current_lang:
+ nm = current_lang.get_name()
+
+ for lang in langs:
+ name = lang.get_name()
+ parent = self.model.append(None, (name, name, lang, None))
+
+ # Add dummy node
+ self.model.append(parent, ('', '', None, None))
+
+ if (nm == name):
+ expand = parent
+ else:
+ if current_lang:
+ piter = self.model.get_iter_first()
+ nm = current_lang.get_name()
+
+ while piter:
+ lang = self.model.get_value(piter, \
+ self.SORT_COLUMN)
+
+ if lang == nm:
+ expand = piter
+ break;
+
+ piter = self.model.iter_next(piter)
+
+ tree_view.set_model(self.model)
+
+ if not expand:
+ expand = self.model.get_iter_first()
+
+ tree_view.expand_row(self.model.get_path(expand), False)
+ self.select_iter(expand)
+
+ def get_cell_data_pixbuf_cb(self, column, cell, model, iter, data):
+ snippet = model.get_value(iter, self.SNIPPET_COLUMN)
+
+ if snippet and not snippet.valid:
+ cell.set_property('icon-name', 'dialog-error')
+ else:
+ cell.set_property('icon-name', None)
+
+ cell.set_property('xalign', 1.0)
+
+ def get_cell_data_cb(self, column, cell, model, iter, data):
+ snippet = model.get_value(iter, self.SNIPPET_COLUMN)
+
+ cell.set_property('editable', snippet != None)
+ cell.set_property('markup', model.get_value(iter, self.NAME_COLUMN))
+
+ def on_tree_view_drag_data_get(self, widget, context, selection_data, info, time):
+ gfile = Gio.file_new_for_path(self._temp_export)
+ selection_data.set_uris([gfile.get_uri()])
+
+ def on_tree_view_drag_begin(self, widget, context):
+ self.dragging = True
+
+ if self._temp_export:
+ shutil.rmtree(os.path.dirname(self._temp_export))
+ self._temp_export = None
+
+ if self.dnd_name:
+ Gtk.drag_set_icon_name(context, self.dnd_name, 0, 0)
+
+ dirname = tempfile.mkdtemp()
+ filename = os.path.join(dirname, self.default_export_name)
+
+ # Generate temporary file name
+ self.export_snippets(filename, False)
+ self._temp_export = filename
+
+ def on_tree_view_drag_end(self, widget, context):
+ self.dragging = False
+
+ def on_tree_view_drag_data_received(self, widget, context, x, y, selection, info, timestamp):
+ uris = selection.get_uris()
+
+ files = [Gio.file_new_for_uri(u) for u in uris]
+
+ self.import_snippets(files)
+
+ def on_tree_view_drag_motion(self, widget, context, x, y, timestamp):
+ # Return False if we are dragging
+ if self.dragging:
+ return False
+
+ # Check uri target
+ if not Gtk.targets_include_uri(context.targets):
+ return False
+
+ # Check action
+ action = None
+ if context.suggested_action == Gdk.DragAction.COPY:
+ action = Gdk.DragAction.COPY
+ else:
+ for act in context.actions:
+ if act == Gdk.DragAction.COPY:
+ action = Gdk.DragAction.COPY
+ break
+
+ if action == Gdk.DragAction.COPY:
+ context.drag_status(Gdk.DragAction.COPY, timestamp)
+ return True
+ else:
+ return False
+
+ def build_dnd(self):
+ tv = self.tree_view
+
+ # Set it as a drag source for exporting snippets
+ tv.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
+ tv.drag_source_set_target_list(self.dnd_target_list)
+
+ # Set it as a drag destination for importing snippets
+ tv.drag_dest_set(Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP,
+ [], Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY)
+
+ tv.drag_dest_set_target_list(self.dnd_target_list)
+
+ tv.connect('drag_data_get', self.on_tree_view_drag_data_get)
+ tv.connect('drag_begin', self.on_tree_view_drag_begin)
+ tv.connect('drag_end', self.on_tree_view_drag_end)
+ tv.connect('drag_data_received', self.on_tree_view_drag_data_received)
+ tv.connect('drag_motion', self.on_tree_view_drag_motion)
+
+ theme = Gtk.IconTheme.get_for_screen(tv.get_screen())
+
+ self.dnd_name = None
+ for name in self.drag_icons:
+ icon = theme.lookup_icon(name, Gtk.IconSize.DND, 0)
+
+ if icon:
+ self.dnd_name = name
+ break
+
+ def build_tree_view(self):
+ self.tree_view = self['tree_view_snippets']
+
+ self.column = Gtk.TreeViewColumn(None)
+
+ self.renderer = Gtk.CellRendererText()
+ self.column.pack_start(self.renderer, False)
+ self.column.set_cell_data_func(self.renderer, self.get_cell_data_cb, None)
+
+ renderer = Gtk.CellRendererPixbuf()
+ self.column.pack_start(renderer, True)
+ self.column.set_cell_data_func(renderer, self.get_cell_data_pixbuf_cb, None)
+
+ self.tree_view.append_column(self.column)
+
+ self.renderer.connect('edited', self.on_cell_edited)
+ self.renderer.connect('editing-started', self.on_cell_editing_started)
+
+ selection = self.tree_view.get_selection()
+ selection.set_mode(Gtk.SelectionMode.MULTIPLE)
+ selection.connect('changed', self.on_tree_view_selection_changed)
+
+ self.build_dnd()
+
+ def do_parser_finished(self, builder):
+ self.builder = builder
+
+ handlers_dic = {
+ 'on_add_snippet_button_clicked': self.on_add_snippet_button_clicked,
+ 'on_remove_snippet_button_clicked': self.on_remove_snippet_button_clicked,
+ 'on_import_snippets_button_clicked': self.on_import_snippets_button_clicked,
+ 'on_export_snippets_button_clicked': self.on_export_snippets_button_clicked,
+ 'on_entry_tab_trigger_focus_out': self.on_entry_tab_trigger_focus_out,
+ 'on_entry_tab_trigger_changed': self.on_entry_tab_trigger_changed,
+ 'on_entry_accelerator_focus_out': self.on_entry_accelerator_focus_out,
+ 'on_entry_accelerator_focus_in': self.on_entry_accelerator_focus_in,
+ 'on_entry_accelerator_key_press': self.on_entry_accelerator_key_press,
+ 'on_source_view_snippet_focus_out': self.on_source_view_snippet_focus_out,
+ 'on_tree_view_snippets_row_expanded': self.on_tree_view_snippets_row_expanded,
+ 'on_tree_view_snippets_key_press': self.on_tree_view_snippets_key_press}
+
+ self.builder.connect_signals(handlers_dic)
+
+ self.build_tree_view()
+ self.build_model()
+
+ # join treeview and toolbar
+ context = self['scrolled_window_snippets'].get_style_context()
+ context.set_junction_sides(Gtk.JunctionSides.BOTTOM)
+ context = self['toolbar'].get_style_context()
+ context.set_junction_sides(Gtk.JunctionSides.TOP)
+ context.set_junction_sides(Gtk.JunctionSides.BOTTOM)
+
+ source_view = self['source_view_snippet']
+ manager = get_language_manager()
+ lang = manager.get_language('snippets')
+
+ if lang:
+ source_view.get_buffer().set_highlight_syntax(True)
+ source_view.get_buffer().set_language(lang)
+
+ combo = self['combo_drop_targets']
+
+ entry = combo.get_child()
+ entry.connect('focus-out-event', self.on_entry_drop_targets_focus_out)
+ entry.connect('drag-data-received', self.on_entry_drop_targets_drag_data_received)
+
+ lst = entry.drag_dest_get_target_list()
+ lst.add_uri_targets(self.TARGET_URI)
+
+ def do_configure_event(self, event):
+ if self.get_realized():
+ alloc = self.get_allocation()
+ self._size = (alloc.width, alloc.height)
+
+ return Gtk.Dialog.do_configure_event(self, event)
+
+ def __getitem__(self, key):
+ return self.builder.get_object(key)
+
+ def is_filled(self, piter):
+ if not self.model.iter_has_child(piter):
+ return True
+
+ child = self.model.iter_children(piter)
+ nm = self.model.get_value(child, self.NAME_COLUMN)
+ lang = self.model.get_value(child, self.LANG_COLUMN)
+ snippet = self.model.get_value(child, self.SNIPPET_COLUMN)
+
+ return (lang or snippet or nm)
+
+ def fill_if_needed(self, piter, expand=True):
+ if not self.is_filled(piter):
+ self.fill_language(piter, expand)
+
+ def find_iter(self, parent, snippet):
+ self.fill_if_needed(parent)
+ piter = self.model.iter_children(parent)
+
+ while (piter):
+ sn = self.model.get_value(piter, self.SNIPPET_COLUMN)
+
+ if sn == snippet.data:
+ return piter
+
+ piter = self.model.iter_next(piter)
+
+ return None
+
+ def selected_snippets_state(self):
+ snippets = self.selected_snippets(False)
+ override = False
+ remove = False
+ system = False
+
+ for snippet in snippets:
+ if not snippet:
+ continue
+
+ if snippet.is_override():
+ override = True
+ elif snippet.can_modify():
+ remove = True
+ else:
+ system = True
+
+ # No need to continue if both are found
+ if override and remove:
+ break
+
+ return (override, remove, system)
+
+ def update_toolbar_buttons(self):
+ button_add = self['add_snippet_button']
+ button_remove = self['remove_snippet_button']
+
+ button_add.set_sensitive(self.language_path != None)
+ override, remove, system = self.selected_snippets_state()
+
+ if not (override ^ remove) or system:
+ button_remove.set_sensitive(False)
+ button_remove.set_icon_name('list-remove-symbolic')
+ else:
+ button_remove.set_sensitive(True)
+
+ if override:
+ button_remove.set_icon_name('edit-undo-symbolic')
+ tooltip = _('Revert selected snippet')
+ else:
+ button_remove.set_icon_name('list-remove-symbolic')
+ tooltip = _('Delete selected snippet')
+
+ button_remove.set_tooltip_text(tooltip)
+
+ def snippet_changed(self, piter = None):
+ if piter:
+ node = self.model.get_value(piter, self.SNIPPET_COLUMN)
+ s = Snippet(node)
+ else:
+ s = self.snippet
+ piter = self.find_iter(self.model.get_iter(self.language_path), s)
+
+ if piter:
+ nm = s.display()
+
+ self.model.set_value(piter, self.NAME_COLUMN, nm)
+ self.model.set_value(piter, self.SORT_COLUMN, nm)
+ self.update_toolbar_buttons()
+ self.entry_tab_trigger_update_valid()
+
+ return piter
+
+ def add_snippet(self, parent, snippet):
+ piter = self.model.append(parent, ('', '', None, snippet))
+
+ return self.snippet_changed(piter)
+
+ def snippet_from_iter(self, model, piter):
+ parent = model.iter_parent(piter)
+
+ if parent:
+ return model.get_value(piter, self.SNIPPET_COLUMN)
+ else:
+ return None
+
+ def language_snippets(self, model, parent, as_path=False):
+ self.fill_if_needed(parent, False)
+ piter = model.iter_children(parent)
+ snippets = []
+
+ if not piter:
+ return snippets
+
+ while piter:
+ snippet = self.snippet_from_iter(model, piter)
+
+ if snippet:
+ if as_path:
+ snippets.append(model.get_path(piter))
+ else:
+ snippets.append(snippet)
+
+ piter = model.iter_next(piter)
+
+ return snippets
+
+ def selected_snippets(self, include_languages=True, as_path=False):
+ selection = self.tree_view.get_selection()
+ (model, paths) = selection.get_selected_rows()
+ snippets = []
+
+ if paths and len(paths) != 0:
+ for p in paths:
+ piter = model.get_iter(p)
+ parent = model.iter_parent(piter)
+
+ if not piter:
+ continue
+
+ if parent:
+ snippet = self.snippet_from_iter(model, piter)
+
+ if not snippet:
+ continue
+
+ if as_path:
+ snippets.append(p)
+ else:
+ snippets.append(snippet)
+ elif include_languages:
+ snippets += self.language_snippets(model, piter, as_path)
+
+ return snippets
+
+ def selected_snippet(self):
+ selection = self.tree_view.get_selection()
+ (model, paths) = selection.get_selected_rows()
+
+ if len(paths) == 1:
+ piter = model.get_iter(paths[0])
+ parent = model.iter_parent(piter)
+ snippet = self.snippet_from_iter(model, piter)
+
+ return parent, piter, snippet
+ else:
+ return None, None, None
+
+ def selection_changed(self):
+ if not self.snippet:
+ sens = False
+
+ self['entry_tab_trigger'].set_text('')
+ self['entry_accelerator'].set_text('')
+ buf = self['source_view_snippet'].get_buffer()
+ buf.begin_not_undoable_action()
+ buf.set_text('')
+ buf.end_not_undoable_action()
+ self['combo_drop_targets'].get_child().set_text('')
+
+ else:
+ sens = True
+
+ self['entry_tab_trigger'].set_text(self.snippet['tag'])
+ self['entry_accelerator'].set_text( \
+ self.snippet.accelerator_display())
+ self['combo_drop_targets'].get_child().set_text(', '.join(self.snippet['drop-targets']))
+
+ buf = self['source_view_snippet'].get_buffer()
+ buf.begin_not_undoable_action()
+ buf.set_text(self.snippet['text'])
+ buf.end_not_undoable_action()
+
+
+ for name in ['source_view_snippet', 'label_tab_trigger',
+ 'entry_tab_trigger', 'label_accelerator',
+ 'entry_accelerator', 'label_drop_targets',
+ 'combo_drop_targets']:
+ self[name].set_sensitive(sens)
+
+ self.update_toolbar_buttons()
+
+ def select_iter(self, piter, unselect=True):
+ selection = self.tree_view.get_selection()
+
+ if unselect:
+ selection.unselect_all()
+
+ selection.select_iter(piter)
+
+ self.tree_view.scroll_to_cell(self.model.get_path(piter), None, \
+ True, 0.5, 0.5)
+
+ def get_language(self, path):
+ if path.get_indices()[0] == 0:
+ return None
+ else:
+ return self.model.get_value(self.model.get_iter(path),
+ self.LANG_COLUMN).get_id()
+
+ def new_snippet(self, properties=None):
+ if not self.language_path:
+ return None
+
+ snippet = Library().new_snippet(self.get_language(self.language_path), properties)
+
+ return Snippet(snippet)
+
+ def get_dummy(self, parent):
+ if not self.model.iter_n_children(parent) == 1:
+ return None
+
+ dummy = self.model.iter_children(parent)
+
+ if not self.model.get_value(dummy, self.SNIPPET_COLUMN):
+ return dummy
+
+ return None
+
+ def unref_languages(self):
+ piter = self.model.get_iter_first()
+ library = Library()
+
+ while piter:
+ if self.is_filled(piter):
+ language = self.get_language(self.model.get_path(piter))
+ library.save(language)
+
+ library.unref(language)
+
+ piter = self.model.iter_next(piter)
+
+ # Callbacks
+ def do_destroy(self):
+ Gtk.Dialog.do_destroy(self)
+
+ if not self.model:
+ return
+
+ # Remove temporary drag export
+ if self._temp_export:
+ shutil.rmtree(os.path.dirname(self._temp_export))
+ self._temp_export = None
+
+ self.unref_languages()
+ self.snippet = None
+ self.model = None
+
+ def on_cell_editing_started(self, renderer, editable, path):
+ piter = self.model.get_iter(path)
+
+ if not self.model.iter_parent(piter):
+ renderer.stop_editing(True)
+ editable.remove_widget()
+ elif isinstance(editable, Gtk.Entry):
+ if self.snippet:
+ editable.set_text(self.snippet['description'])
+ else:
+ # This is the `Add a new snippet...` item
+ editable.set_text('')
+
+ editable.grab_focus()
+
+ def on_cell_edited(self, cell, path, new_text):
+ if new_text != '':
+ piter = self.model.get_iter(path)
+ node = self.model.get_value(piter, self.SNIPPET_COLUMN)
+
+ if node:
+ if node == self.snippet.data:
+ s = self.snippet
+ else:
+ s = Snippet(node)
+
+ s['description'] = new_text
+ self.snippet_changed(piter)
+ self.select_iter(piter)
+ else:
+ # This is the `Add a new snippet...` item
+ # We create a new snippet
+ snippet = self.new_snippet({'description': new_text})
+
+ if snippet:
+ self.model.set_value(piter, self.SNIPPET_COLUMN, snippet.data)
+ self.snippet_changed(piter)
+ self.snippet = snippet
+ self.selection_changed()
+
+ def on_entry_accelerator_focus_out(self, entry, event):
+ if not self.snippet:
+ return
+
+ entry.set_text(self.snippet.accelerator_display())
+
+ def entry_tab_trigger_update_valid(self):
+ entry = self['entry_tab_trigger']
+ text = entry.get_text()
+
+ if text and not Library().valid_tab_trigger(text):
+ img = self['image_tab_trigger']
+ img.set_from_icon_name('dialog-error', Gtk.IconSize.BUTTON)
+ img.show()
+
+ #self['hbox_tab_trigger'].set_spacing(3)
+ tip = _('This is not a valid Tab trigger. Triggers can either contain alphanumeric characters (or _, : and .) or a single (non-alphanumeric) character like: {, [, etc.')
+
+ entry.set_tooltip_text(tip)
+ img.set_tooltip_text(tip)
+ else:
+ self['image_tab_trigger'].hide()
+ #self['hbox_tab_trigger'].set_spacing(0)
+ entry.set_tooltip_text(_('Single word the snippet is activated with after pressing Tab'))
+
+ return False
+
+ def on_entry_tab_trigger_focus_out(self, entry, event):
+ if not self.snippet:
+ return
+
+ text = entry.get_text()
+
+ # save tag
+ self.snippet['tag'] = text
+ self.snippet_changed()
+
+ def on_entry_drop_targets_focus_out(self, entry, event):
+ if not self.snippet:
+ return
+
+ text = entry.get_text()
+
+ # save drop targets
+ self.snippet['drop-targets'] = text
+ self.snippet_changed()
+
+ def on_entry_tab_trigger_changed(self, entry):
+ self.entry_tab_trigger_update_valid()
+
+ def on_source_view_snippet_focus_out(self, source_view, event):
+ if not self.snippet:
+ return
+
+ buf = source_view.get_buffer()
+ text = buf.get_text(buf.get_start_iter(), \
+ buf.get_end_iter(), False)
+
+ self.snippet['text'] = text
+ self.snippet_changed()
+
+ def on_add_snippet_button_clicked(self, button):
+ snippet = self.new_snippet()
+
+ if not snippet:
+ return
+
+ parent = self.model.get_iter(self.language_path)
+ path = self.model.get_path(parent)
+
+ dummy = self.get_dummy(parent)
+
+ if dummy:
+ # Remove the dummy
+ self.model.remove(dummy)
+
+ # Add the snippet
+ piter = self.add_snippet(parent, snippet.data)
+ self.select_iter(piter)
+
+ if not self.tree_view.row_expanded(path):
+ self.tree_view.expand_row(path, False)
+ self.select_iter(piter)
+
+ self.tree_view.grab_focus()
+
+ path = self.model.get_path(piter)
+ self.tree_view.set_cursor(path, self.column, True)
+
+ def file_filter(self, name, pattern):
+ fil = Gtk.FileFilter()
+ fil.set_name(name)
+
+ for p in pattern:
+ fil.add_pattern(p)
+
+ return fil
+
+ def import_snippets(self, files):
+ success = True
+
+ for gfile in files:
+ if not gfile.has_uri_scheme('file'):
+ continue
+
+ # Remove file://
+ filename = gfile.get_path()
+
+ importer = Importer(filename)
+ error = importer.run()
+
+ if error:
+ message = _('The following error occurred while importing: %s') % error
+ success = False
+ helper.message_dialog(self.get_toplevel(), Gtk.MessageType.ERROR, message)
+
+ self.build_model(True)
+
+ if success:
+ message = _('Import successfully completed')
+ helper.message_dialog(self.get_toplevel(), Gtk.MessageType.INFO, message)
+
+ def on_import_response(self, dialog, response):
+ if response == Gtk.ResponseType.CANCEL or response == Gtk.ResponseType.CLOSE:
+ dialog.destroy()
+ return
+
+ f = dialog.get_files()
+ dialog.destroy()
+
+ self.import_snippets(f)
+
+ def on_import_snippets_button_clicked(self, button):
+ dlg = Gtk.FileChooserDialog(parent=self.get_toplevel(), title=_("Import snippets"),
+ action=Gtk.FileChooserAction.OPEN,
+ buttons=(_("_Cancel"), Gtk.ResponseType.CANCEL,
+ _("_Open"), Gtk.ResponseType.OK))
+
+ dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar', '*.xml')))
+ dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',)))
+ dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',)))
+ dlg.add_filter(self.file_filter(_('Single snippets file'), ('*.xml',)))
+ dlg.add_filter(self.file_filter(_('All files'), '*'))
+
+ dlg.connect('response', self.on_import_response)
+ dlg.set_local_only(True)
+
+ dlg.show()
+
+ def export_snippets_real(self, filename, snippets, show_dialogs=True):
+ export = Exporter(filename, snippets)
+ error = export.run()
+
+ if error:
+ message = _('The following error occurred while exporting: %s') % error
+ msgtype = Gtk.MessageType.ERROR
+ retval = False
+ else:
+ message = _('Export successfully completed')
+ msgtype = Gtk.MessageType.INFO
+ retval = True
+
+ if show_dialogs:
+ helper.message_dialog(self.get_toplevel(), msgtype, message)
+
+ return retval
+
+ def on_export_response(self, dialog, response):
+ filename = dialog.get_filename()
+ snippets = dialog._export_snippets
+
+ dialog.destroy()
+
+ if response != Gtk.ResponseType.OK:
+ return
+
+ self.export_snippets_real(filename, snippets);
+
+ def export_snippets(self, filename=None, show_dialogs=True):
+ snippets = self.selected_snippets()
+
+ if not snippets or len(snippets) == 0:
+ return False
+
+ usersnippets = []
+ systemsnippets = []
+
+ # Iterate through snippets and look for system snippets
+ for snippet in snippets:
+ if snippet.can_modify():
+ usersnippets.append(snippet)
+ else:
+ systemsnippets.append(snippet)
+
+ export_snippets = snippets
+
+ if len(systemsnippets) != 0 and show_dialogs:
+ # Ask if system snippets should also be exported
+ message = _('Do you want to include selected <b>system</b> snippets in your export?')
+ mes = Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL,
+ type=Gtk.MessageType.QUESTION,
+ buttons=Gtk.ButtonsType.YES_NO,
+ message_format=message)
+ mes.set_property('use-markup', True)
+ resp = mes.run()
+ mes.destroy()
+
+ if resp == Gtk.ResponseType.NO:
+ export_snippets = usersnippets
+ elif resp != Gtk.ResponseType.YES:
+ return False
+
+ if len(export_snippets) == 0 and show_dialogs:
+ message = _('There are no snippets selected to be exported')
+ helper.message_dialog(self.get_toplevel(), Gtk.MessageType.QUESTION, message)
+ return False
+
+ if not filename:
+ dlg = Gtk.FileChooserDialog(parent=self.get_toplevel(), title=_('Export snippets'),
+ action=Gtk.FileChooserAction.SAVE,
+ buttons=(_("_Cancel"), Gtk.ResponseType.CANCEL,
+ _("_Save"), Gtk.ResponseType.OK))
+
+ dlg._export_snippets = export_snippets
+ dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar')))
+ dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',)))
+ dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',)))
+
+ dlg.add_filter(self.file_filter(_('All files'), '*'))
+ dlg.set_do_overwrite_confirmation(True)
+ dlg.set_current_name(self.default_export_name)
+
+ dlg.connect('response', self.on_export_response)
+ dlg.set_local_only(True)
+
+ dlg.show()
+ return True
+ else:
+ return self.export_snippets_real(filename, export_snippets, show_dialogs)
+
+ def on_export_snippets_button_clicked(self, button):
+ snippets = self.selected_snippets()
+
+ if not snippets or len(snippets) == 0:
+ return
+
+ usersnippets = []
+ systemsnippets = []
+
+ # Iterate through snippets and look for system snippets
+ for snippet in snippets:
+ if snippet.can_modify():
+ usersnippets.append(snippet)
+ else:
+ systemsnippets.append(snippet)
+
+ dlg = Gtk.FileChooserDialog(parent=self.get_toplevel(), title=_('Export snippets'),
+ action=Gtk.FileChooserAction.SAVE,
+ buttons=(_('_Cancel'), Gtk.ResponseType.CANCEL,
+ _('_Save'), Gtk.ResponseType.OK))
+
+ dlg._export_snippets = snippets
+
+ if len(systemsnippets) != 0:
+ # Ask if system snippets should also be exported
+ message = _('Do you want to include selected <b>system</b> snippets in your export?')
+ mes = Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL,
+ type=Gtk.MessageType.QUESTION,
+ buttons=Gtk.ButtonsType.YES_NO,
+ message_format=message)
+ mes.set_property('use-markup', True)
+ resp = mes.run()
+ mes.destroy()
+
+ if resp == Gtk.ResponseType.NO:
+ dlg._export_snippets = usersnippets
+ elif resp != Gtk.ResponseType.YES:
+ dlg.destroy()
+ return
+
+ if len(dlg._export_snippets) == 0:
+ dlg.destroy()
+
+ message = _('There are no snippets selected to be exported')
+ helper.message_dialog(self.get_toplevel(), Gtk.MessageType.QUESTION, message)
+ return
+
+ dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar')))
+ dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',)))
+ dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',)))
+
+ dlg.add_filter(self.file_filter(_('All files'), '*'))
+ dlg.set_do_overwrite_confirmation(True)
+ dlg.set_current_name(self.default_export_name)
+
+ dlg.connect('response', self.on_export_response)
+ dlg.set_local_only(True)
+
+ dlg.show()
+
+ def remove_snippet_revert(self, path, piter):
+ node = self.snippet_from_iter(self.model, piter)
+ Library().revert_snippet(node)
+
+ return piter
+
+ def remove_snippet_delete(self, path, piter):
+ node = self.snippet_from_iter(self.model, piter)
+ parent = self.model.iter_parent(piter)
+
+ Library().remove_snippet(node)
+ idx = path.get_indices()
+
+ if self.model.remove(piter):
+ return piter
+ elif idx[-1] != 0:
+ self.select_iter(self.model.get_iter((idx[0], idx[1] - 1)))
+ else:
+ dummy = self.add_new_snippet_node(parent)
+ self.tree_view.expand_row(self.model.get_path(parent), False)
+ return dummy
+
+ def on_remove_snippet_button_clicked(self, button):
+ override, remove, system = self.selected_snippets_state()
+
+ if not (override ^ remove) or system:
+ return
+
+ paths = self.selected_snippets(include_languages=False, as_path=True)
+
+ if override:
+ action = self.remove_snippet_revert
+ else:
+ action = self.remove_snippet_delete
+
+ # Remove selection
+ self.tree_view.get_selection().unselect_all()
+
+ # Create tree row references
+ references = []
+ for path in paths:
+ # FIXME: this should be fixed in pygobject or something
+ references.append(Gtk.TreeRowReference.new(self.model, path))
+
+ # Remove/revert snippets
+ select = None
+ for reference in references:
+ path = reference.get_path()
+ piter = self.model.get_iter(path)
+
+ res = action(path, piter)
+
+ if res:
+ select = res
+
+ if select:
+ self.select_iter(select)
+
+ self.selection_changed()
+
+ def set_accelerator(self, keyval, mod):
+ accelerator = Gtk.accelerator_name(keyval, mod)
+ self.snippet['accelerator'] = accelerator
+
+ return True
+
+ def on_entry_accelerator_key_press(self, entry, event):
+ if event.keyval == Gdk.keyval_from_name('Escape'):
+ # Reset
+ entry.set_text(self.snippet.accelerator_display())
+ self.tree_view.grab_focus()
+
+ return True
+ elif event.keyval == Gdk.keyval_from_name('Delete') or \
+ event.keyval == Gdk.keyval_from_name('BackSpace'):
+ # Remove the accelerator
+ entry.set_text('')
+ self.snippet['accelerator'] = ''
+ self.tree_view.grab_focus()
+
+ self.snippet_changed()
+ return True
+ elif Library().valid_accelerator(event.keyval, event.get_state()):
+ # New accelerator
+ self.set_accelerator(event.keyval, \
+ event.get_state() & Gtk.accelerator_get_default_mod_mask())
+ entry.set_text(self.snippet.accelerator_display())
+ self.snippet_changed()
+ self.tree_view.grab_focus()
+
+ else:
+ return True
+
+ def on_entry_accelerator_focus_in(self, entry, event):
+ if self.snippet['accelerator']:
+ entry.set_text(_('Type a new shortcut, or press Backspace to clear'))
+ else:
+ entry.set_text(_('Type a new shortcut'))
+
+ def update_language_path(self):
+ model, paths = self.tree_view.get_selection().get_selected_rows()
+
+ # Check if all have the same language parent
+ current_parent = None
+
+ for path in paths:
+ piter = model.get_iter(path)
+ parent = model.iter_parent(piter)
+
+ if parent:
+ path = model.get_path(parent)
+
+ if current_parent != None and current_parent != path:
+ current_parent = None
+ break
+ else:
+ current_parent = path
+
+ self.language_path = current_parent
+
+ def on_tree_view_selection_changed(self, selection):
+ parent, piter, node = self.selected_snippet()
+
+ if self.snippet:
+ self.on_entry_tab_trigger_focus_out(self['entry_tab_trigger'],
+ None)
+ self.on_source_view_snippet_focus_out(self['source_view_snippet'],
+ None)
+ self.on_entry_drop_targets_focus_out(self['combo_drop_targets'].get_child(),
+ None)
+
+ self.update_language_path()
+
+ if node:
+ self.snippet = Snippet(node)
+ else:
+ self.snippet = None
+
+ self.selection_changed()
+
+ def iter_after(self, target, after):
+ if not after:
+ return True
+
+ tp = self.model.get_path(target)
+ ap = self.model.get_path(after)
+
+ if tp[0] > ap[0] or (tp[0] == ap[0] and (len(ap) == 1 or tp[1] > ap[1])):
+ return True
+
+ return False
+
+ def on_tree_view_snippets_key_press(self, treeview, event):
+ if event.keyval == Gdk.keyval_from_name('Delete'):
+ self.on_remove_snippet_button_clicked(None)
+ return True
+
+ def on_tree_view_snippets_row_expanded(self, treeview, piter, path):
+ # Check if it is already filled
+ self.fill_if_needed(piter)
+ self.select_iter(piter)
+
+ def on_entry_drop_targets_drag_data_received(self, entry, context, x, y, selection_data, info, timestamp):
+ uris = helper.drop_get_uris(selection_data)
+ if not uris:
+ return
+
+ text = entry.get_text()
+
+ if text:
+ mimes = [text]
+ else:
+ mimes = []
+
+ for uri in uris:
+ try:
+ mime = Gio.content_type_guess(uri)
+ except:
+ mime = None
+
+ if mime:
+ mimes.append(mime)
+
+ entry.set_text(', '.join(mimes))
+ self.on_entry_drop_targets_focus_out(entry, None)
+ context.finish(True, False, timestamp)
+
+ entry.stop_emission('drag_data_received')
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/meson.build b/plugins/snippets/snippets/meson.build
new file mode 100644
index 0000000..1ca34de
--- /dev/null
+++ b/plugins/snippets/snippets/meson.build
@@ -0,0 +1,39 @@
+snippets_sources = [
+ '__init__.py',
+ 'appactivatable.py',
+ 'completion.py',
+ 'document.py',
+ 'exporter.py',
+ 'helper.py',
+ 'importer.py',
+ 'languagemanager.py',
+ 'library.py',
+ 'manager.py',
+ 'parser.py',
+ 'placeholder.py',
+ 'shareddata.py',
+ 'signals.py',
+ 'singleton.py',
+ 'snippet.py',
+ 'substitutionparser.py',
+ 'windowactivatable.py',
+]
+
+install_data(
+ snippets_sources,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ 'snippets',
+ )
+)
+
+install_data(
+ 'snippets.ui',
+ install_dir: join_paths(
+ pkgdatadir,
+ 'plugins',
+ 'snippets',
+ 'ui',
+ )
+)
diff --git a/plugins/snippets/snippets/parser.py b/plugins/snippets/snippets/parser.py
new file mode 100644
index 0000000..2b6043d
--- /dev/null
+++ b/plugins/snippets/snippets/parser.py
@@ -0,0 +1,256 @@
+# Gedit snippets plugin
+# Copyright (C) 2006-2007 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import re
+
+class Token:
+ def __init__(self, klass, data):
+ self.klass = klass
+ self.data = data
+
+ def __str__(self):
+ return '%s: [%s]' % (self.klass, self.data)
+
+ def __eq__(self, other):
+ return self.klass == other.klass and self.data == other.data
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+class Parser:
+ SREG_ENV = '[A-Z_]+'
+ SREG_ID = '[0-9]+'
+
+ REG_ESCAPE = re.compile('(\\$(%s|\\(|\\{|<|%s)|`|\\\\)' % (SREG_ENV, SREG_ID))
+
+ def __init__(self, **kwargs):
+ for k, v in kwargs.items():
+ setattr(self, k, v)
+
+ self.position = 0
+ self.data_length = len(self.data)
+
+ self.RULES = (self._match_env, self._match_regex, self._match_placeholder, self._match_shell, self._match_eval, self._text)
+
+ def remains(self):
+ return self.data[self.position:]
+
+ def next_char(self):
+ if self.position + 1 >= self.data_length:
+ return ''
+ else:
+ return self.data[self.position + 1]
+
+ def char(self):
+ if self.position >= self.data_length:
+ return ''
+ else:
+ return self.data[self.position]
+
+ def token(self):
+ self.tktext = ''
+
+ while self.position < self.data_length:
+ try:
+ # Get first character
+ func = {'$': self._rule,
+ '`': self._try_match_shell}[self.char()]
+ except:
+ func = self._text
+
+ # Detect end of text token
+ if func != self._text and self.tktext != '':
+ return Token('text', self.tktext)
+
+ tk = func()
+
+ if tk:
+ return tk
+
+ if self.tktext != '':
+ return Token('text', self.tktext)
+
+ def _need_escape(self):
+ text = self.remains()[1:]
+
+ if text == '':
+ return False
+
+ return self.REG_ESCAPE.match(text)
+
+ def _escape(self):
+ if not self._need_escape():
+ return
+
+ # Increase position with 1
+ self.position += 1
+
+ def _text(self):
+ if self.char() == '\\':
+ self._escape()
+
+ self.tktext += self.char()
+ self.position += 1
+
+ def _rule(self):
+ for rule in self.RULES:
+ res = rule()
+
+ if res:
+ return res
+
+ def _match_env(self):
+ text = self.remains()
+ match = re.match('\\$(%s)' % self.SREG_ENV, text) or re.match('\\${(%s)}' % self.SREG_ENV, text)
+
+ if match:
+ self.position += len(match.group(0))
+ return Token('environment', match.group(1))
+
+ def _parse_list(self, lst):
+ pos = 0
+ length = len(lst)
+ items = []
+ last = None
+
+ while pos < length:
+ char = lst[pos]
+ next = pos < length - 1 and lst[pos + 1]
+
+ if char == '\\' and (next == ',' or next == ']'):
+ char = next
+ pos += 1
+ elif char == ',':
+ if last != None:
+ items.append(last)
+
+ last = None
+ pos += 1
+ continue
+
+ last = (last != None and last + char) or char
+ pos += 1
+
+ if last != None:
+ items.append(last)
+
+ return items
+
+ def _parse_default(self, default):
+ match = re.match('^\\s*(\\\\)?(\\[((\\\\]|[^\\]])+)\\]\\s*)$', default)
+
+ if not match:
+ return [default]
+
+ groups = match.groups()
+
+ if groups[0]:
+ return [groups[1]]
+
+ return self._parse_list(groups[2])
+
+ def _match_placeholder(self):
+ text = self.remains()
+
+ match = re.match('\\${(%s)(:((\\\\\\}|[^}])+))?}' % self.SREG_ID, text) or re.match('\\$(%s)' % self.SREG_ID, text)
+
+ if not match:
+ return None
+
+ groups = match.groups()
+ default = ''
+ tabstop = int(groups[0])
+ self.position += len(match.group(0))
+
+ if len(groups) > 1 and groups[2]:
+ default = self._parse_default(groups[2].replace('\\}', '}'))
+
+ return Token('placeholder', {'tabstop': tabstop, 'default': default})
+
+ def _match_shell(self):
+ text = self.remains()
+ match = re.match('`((%s):)?((\\\\`|[^`])+?)`' % self.SREG_ID, text) or re.match('\\$\\(((%s):)?((\\\\\\)|[^\\)])+?)\\)' % self.SREG_ID, text)
+
+ if not match:
+ return None
+
+ groups = match.groups()
+ tabstop = (groups[1] and int(groups[1])) or -1
+ self.position += len(match.group(0))
+
+ if text[0] == '`':
+ contents = groups[2].replace('\\`', '`')
+ else:
+ contents = groups[2].replace('\\)', ')')
+
+ return Token('shell', {'tabstop': tabstop, 'contents': contents})
+
+ def _try_match_shell(self):
+ return self._match_shell() or self._text()
+
+ def _eval_options(self, options):
+ reg = re.compile(self.SREG_ID)
+ tabstop = -1
+ depend = []
+
+ options = options.split(':')
+
+ for opt in options:
+ if reg.match(opt):
+ tabstop = int(opt)
+ else:
+ depend += self._parse_list(opt[1:-1])
+
+ return (tabstop, depend)
+
+ def _match_eval(self):
+ text = self.remains()
+
+ options = '((%s)|\\[([0-9, ]+)\\])' % self.SREG_ID
+ match = re.match('\\$<((%s:)*)((\\\\>|[^>])+?)>' % options, text)
+
+ if not match:
+ return None
+
+ groups = match.groups()
+ (tabstop, depend) = (groups[0] and self._eval_options(groups[0][:-1])) or (-1, [])
+ self.position += len(match.group(0))
+
+ return Token('eval', {'tabstop': tabstop, 'dependencies': depend, 'contents': groups[5].replace('\\>', '>')})
+
+ def _match_regex(self):
+ text = self.remains()
+
+ content = '((?:\\\\[/]|\\\\}|[^/}])+)'
+ match = re.match('\\${(?:(%s):)?\\s*(%s|\\$([A-Z_]+))?[/]%s[/]%s(?:[/]([a-zA-Z]*))?}' % (self.SREG_ID, self.SREG_ID, content, content), text)
+
+ if not match:
+ return None
+
+ groups = match.groups()
+ tabstop = (groups[0] and int(groups[0])) or -1
+ inp = (groups[2] or (groups[1] and int(groups[1]))) or ''
+
+ pattern = re.sub('\\\\([/}])', '\\1', groups[3])
+ substitution = re.sub('\\\\([/}])', '\\1', groups[4])
+ modifiers = groups[5] or ''
+
+ self.position += len(match.group(0))
+
+ return Token('regex', {'tabstop': tabstop, 'input': inp, 'pattern': pattern, 'substitution': substitution, 'modifiers': modifiers})
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/placeholder.py b/plugins/snippets/snippets/placeholder.py
new file mode 100644
index 0000000..e70a31e
--- /dev/null
+++ b/plugins/snippets/snippets/placeholder.py
@@ -0,0 +1,714 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import traceback
+import re
+import sys
+import signal
+import locale
+import subprocess
+from gi.repository import GObject, Gtk
+
+from . import helper
+from .substitutionparser import SubstitutionParser
+
+try:
+ import gettext
+ gettext.bindtextdomain('gedit')
+ gettext.textdomain('gedit')
+ _ = gettext.gettext
+except:
+ _ = lambda s: s
+
+# These are places in a view where the cursor can go and do things
+class Placeholder:
+ def __init__(self, view, tabstop, environ, defaults, begin):
+ self.ok = True
+ self.done = False
+ self.buf = view.get_buffer()
+ self.view = view
+ self.has_references = False
+ self.mirrors = []
+ self.leave_mirrors = []
+ self.tabstop = tabstop
+ self.environ = environ
+ self.set_default(defaults)
+ self.prev_contents = self.default
+ self.set_mark_gravity()
+
+ if begin:
+ self.begin = self.buf.create_mark(None, begin, self.mark_gravity[0])
+ else:
+ self.begin = None
+
+ self.end = None
+
+ def get_environ(self):
+ return self.environ['utf8']
+
+ def __str__(self):
+ return '%s (%s)' % (str(self.__class__), str(self.default))
+
+ def set_mark_gravity(self):
+ self.mark_gravity = [True, False]
+
+ def set_default(self, defaults):
+ self.default = None
+ self.defaults = []
+
+ if not defaults:
+ return
+
+ for d in defaults:
+ dm = self.expand_environment(d)
+
+ if dm:
+ self.defaults.append(dm)
+
+ if not self.default:
+ self.default = dm
+
+ if dm != d:
+ break
+
+ def literal(self, s):
+ return repr(s)
+
+ def format_environment(self, s):
+ return s
+
+ def re_environment(self, m):
+ env = self.get_environ()
+
+ if m.group(1) or not m.group(2) in env:
+ return '$' + m.group(2)
+ else:
+ return self.format_environment(env[m.group(2)])
+
+ def expand_environment(self, text):
+ if not text:
+ return text
+
+ return re.sub('(\\\\)?\\$([A-Z_]+)', self.re_environment, text)
+
+ def get_iter(self, mark):
+ if mark and not mark.get_deleted():
+ return self.buf.get_iter_at_mark(mark)
+ else:
+ return None
+
+ def begin_iter(self):
+ return self.get_iter(self.begin)
+
+ def end_iter(self):
+ return self.get_iter(self.end)
+
+ def run_last(self, placeholders):
+ begin = self.begin_iter()
+ self.end = self.buf.create_mark(None, begin, self.mark_gravity[1])
+
+ if self.default:
+ helper.insert_with_indent(self.view, begin, self.default, False, self)
+
+ def remove(self, force = False):
+ if self.begin and not self.begin.get_deleted():
+ self.buf.delete_mark(self.begin)
+
+ if self.end and not self.end.get_deleted():
+ self.buf.delete_mark(self.end)
+
+ # Do something on beginning this placeholder
+ def enter(self):
+ if not self.begin or self.begin.get_deleted():
+ return
+
+ self.buf.move_mark(self.buf.get_insert(), self.begin_iter())
+
+ if self.end:
+ self.buf.move_mark(self.buf.get_selection_bound(), self.end_iter())
+ else:
+ self.buf.move_mark(self.buf.get_selection_bound(), self.begin_iter())
+
+ def get_text(self):
+ if self.begin and self.end:
+ biter = self.begin_iter()
+ eiter = self.end_iter()
+
+ if biter and eiter:
+ return self.buf.get_text(self.begin_iter(), self.end_iter(), False)
+ else:
+ return ''
+ else:
+ return ''
+
+ def add_mirror(self, mirror, onleave = False):
+ mirror.has_references = True
+
+ if onleave:
+ self.leave_mirrors.append(mirror)
+ else:
+ self.mirrors.append(mirror)
+
+ def set_text(self, text):
+ if self.begin.get_deleted() or self.end.get_deleted():
+ return
+
+ # Set from self.begin to self.end to text!
+ self.buf.begin_user_action()
+ # Remove everything between self.begin and self.end
+ begin = self.begin_iter()
+ self.buf.delete(begin, self.end_iter())
+
+ # Insert the text from the mirror
+ helper.insert_with_indent(self.view, begin, text, True, self)
+ self.buf.end_user_action()
+
+ self.update_contents()
+
+ def update_contents(self):
+ prev = self.prev_contents
+ self.prev_contents = self.get_text()
+
+ if prev != self.get_text():
+ for mirror in self.mirrors:
+ if not mirror.update(self):
+ return
+
+ def update_leave_mirrors(self):
+ # Notify mirrors
+ for mirror in self.leave_mirrors:
+ if not mirror.update(self):
+ return
+
+ # Do something on ending this placeholder
+ def leave(self):
+ self.update_leave_mirrors()
+
+ def find_mirrors(self, text, placeholders):
+ mirrors = []
+
+ while (True):
+ m = re.search('(\\\\)?\\$(?:{([0-9]+)}|([0-9]+))', text)
+
+ if not m:
+ break
+
+ # Skip escaped mirrors
+ if m.group(1):
+ text = text[m.end():]
+ continue
+
+ tabstop = int(m.group(2) or m.group(3))
+
+ if tabstop in placeholders:
+ if not tabstop in mirrors:
+ mirrors.append(tabstop)
+
+ text = text[m.end():]
+ else:
+ self.ok = False
+ return None
+
+ return mirrors
+
+# This is an placeholder which inserts a mirror of another Placeholder
+class PlaceholderMirror(Placeholder):
+ def __init__(self, view, tabstop, environ, begin):
+ Placeholder.__init__(self, view, -1, environ, None, begin)
+ self.mirror_stop = tabstop
+
+ def update(self, mirror):
+ self.set_text(mirror.get_text())
+ return True
+
+ def run_last(self, placeholders):
+ Placeholder.run_last(self, placeholders)
+
+ if self.mirror_stop in placeholders:
+ mirror = placeholders[self.mirror_stop]
+
+ mirror.add_mirror(self)
+
+ if mirror.default:
+ self.set_text(mirror.default)
+ else:
+ self.ok = False
+
+# This placeholder indicates the end of a snippet
+class PlaceholderEnd(Placeholder):
+ def __init__(self, view, environ, begin, default):
+ Placeholder.__init__(self, view, 0, environ, default, begin)
+
+ def run_last(self, placeholders):
+ Placeholder.run_last(self, placeholders)
+
+ # Remove the begin mark and set the begin mark
+ # to the end mark, this is needed so the end placeholder won't contain
+ # any text
+
+ if not self.default:
+ self.mark_gravity[0] = False
+ self.buf.delete_mark(self.begin)
+ self.begin = self.buf.create_mark(None, self.end_iter(), self.mark_gravity[0])
+
+ def enter(self):
+ if self.begin and not self.begin.get_deleted():
+ self.buf.move_mark(self.buf.get_insert(), self.begin_iter())
+
+ if self.end and not self.end.get_deleted():
+ self.buf.move_mark(self.buf.get_selection_bound(), self.end_iter())
+
+ def leave(self):
+ self.enter()
+
+# This placeholder is used to expand a command with embedded mirrors
+class PlaceholderExpand(Placeholder):
+ def __init__(self, view, tabstop, environ, begin, s):
+ Placeholder.__init__(self, view, tabstop, environ, None, begin)
+
+ self.mirror_text = {0: ''}
+ self.timeout_id = None
+ self.cmd = s
+ self.instant_update = False
+
+ def __str__(self):
+ s = Placeholder.__str__(self)
+
+ return s + ' ' + self.cmd
+
+ def get_mirrors(self, placeholders):
+ return self.find_mirrors(self.cmd, placeholders)
+
+ # Check if all substitution placeholders are accounted for
+ def run_last(self, placeholders):
+ Placeholder.run_last(self, placeholders)
+
+ self.ok = True
+ mirrors = self.get_mirrors(placeholders)
+
+ if mirrors:
+ allDefault = True
+
+ for mirror in mirrors:
+ p = placeholders[mirror]
+ p.add_mirror(self, not self.instant_update)
+ self.mirror_text[p.tabstop] = p.default
+
+ if not p.default and not isinstance(p, PlaceholderExpand):
+ allDefault = False
+
+ if allDefault:
+ self.update(None)
+ self.default = self.get_text() or None
+ else:
+ self.update(None)
+ self.default = self.get_text() or None
+
+ if self.tabstop == -1:
+ self.done = True
+
+ def re_placeholder(self, m, formatter):
+ if m.group(1):
+ return '"$' + m.group(2) + '"'
+ else:
+ if m.group(3):
+ index = int(m.group(3))
+ else:
+ index = int(m.group(4))
+
+ return formatter(self.mirror_text[index])
+
+ def remove_timeout(self):
+ if self.timeout_id != None:
+ GObject.source_remove(self.timeout_id)
+ self.timeout_id = None
+
+ def install_timeout(self):
+ self.remove_timeout()
+ self.timeout_id = GObject.timeout_add(1000, self.timeout_cb)
+
+ def timeout_cb(self):
+ self.timeout_id = None
+
+ return False
+
+ def format_environment(self, text):
+ return self.literal(text)
+
+ def substitute(self, text, formatter = None):
+ formatter = formatter or self.literal
+
+ # substitute all mirrors, but also environmental variables
+ text = re.sub('(\\\\)?\\$({([0-9]+)}|([0-9]+))', lambda m: self.re_placeholder(m, formatter),
+ text)
+
+ return self.expand_environment(text)
+
+ def run_update(self):
+ text = self.substitute(self.cmd)
+
+ if text:
+ ret = self.expand(text)
+
+ if ret:
+ self.update_leave_mirrors()
+ else:
+ ret = True
+
+ return ret
+
+ def update(self, mirror):
+ if mirror:
+ self.mirror_text[mirror.tabstop] = mirror.get_text()
+
+ # Check if all substitutions have been made
+ for tabstop in self.mirror_text:
+ if tabstop == 0:
+ continue
+
+ if self.mirror_text[tabstop] is None:
+ return False
+
+ return self.run_update()
+
+ def expand(self, text):
+ return True
+
+# The shell placeholder executes commands in a subshell
+class PlaceholderShell(PlaceholderExpand):
+ def __init__(self, view, tabstop, environ, begin, s):
+ PlaceholderExpand.__init__(self, view, tabstop, environ, begin, s)
+
+ self.shell = None
+ self.remove_me = False
+
+ def get_environ(self):
+ return self.environ['noenc']
+
+ def close_shell(self):
+ self.shell.stdout.close()
+ self.shell = None
+
+ def timeout_cb(self):
+ PlaceholderExpand.timeout_cb(self)
+ self.remove_timeout()
+
+ if not self.shell:
+ return False
+
+ GObject.source_remove(self.watch_id)
+ self.close_shell()
+
+ if self.remove_me:
+ PlaceholderExpand.remove(self)
+
+ helper.message_dialog(None, Gtk.MessageType.ERROR, 'Execution of the shell ' \
+ 'command (%s) exceeded the maximum time; ' \
+ 'execution aborted.' % self.command)
+
+ return False
+
+ def process_close(self):
+ self.close_shell()
+ self.remove_timeout()
+
+ self.set_text(str.join('', self.shell_output).rstrip('\n'))
+
+ if self.default is None:
+ self.default = self.get_text()
+ self.leave()
+
+ if self.remove_me:
+ PlaceholderExpand.remove(self, True)
+
+ def process_cb(self, source, condition):
+ if condition & GObject.IO_IN:
+ line = source.readline()
+
+ if len(line) > 0:
+ try:
+ line = line.decode('utf-8')
+ except UnicodeDecodeError:
+ line = line.decode(locale.getdefaultlocale()[1], errors='replace')
+
+ self.shell_output += line
+ self.install_timeout()
+
+ return True
+
+ self.process_close()
+ return False
+
+ def literal_replace(self, match):
+ return "\\%s" % (match.group(0))
+
+ def literal(self, text):
+ return '"' + re.sub('([\\\\"])', self.literal_replace, text) + '"'
+
+ def expand(self, text):
+ self.remove_timeout()
+
+ if self.shell:
+ GObject.source_remove(self.watch_id)
+ self.close_shell()
+
+ popen_args = {
+ 'cwd': None,
+ 'shell': True,
+ 'env': self.get_environ(),
+ 'stdout': subprocess.PIPE
+ }
+
+ self.command = text
+ self.shell = subprocess.Popen(text, **popen_args)
+ self.shell_output = ''
+ self.watch_id = GObject.io_add_watch(self.shell.stdout, GObject.IO_IN | \
+ GObject.IO_HUP, self.process_cb)
+ self.install_timeout()
+
+ return True
+
+ def remove(self, force = False):
+ if not force and self.shell:
+ # Still executing shell command
+ self.remove_me = True
+ else:
+ if force:
+ self.remove_timeout()
+
+ if self.shell:
+ self.close_shell()
+
+ PlaceholderExpand.remove(self, force)
+
+class TimeoutError(Exception):
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+# The python placeholder evaluates commands in python
+class PlaceholderEval(PlaceholderExpand):
+ def __init__(self, view, tabstop, environ, refs, begin, s, namespace):
+ PlaceholderExpand.__init__(self, view, tabstop, environ, begin, s)
+
+ self.fdread = 0
+ self.remove_me = False
+ self.namespace = namespace
+
+ self.refs = []
+
+ if refs:
+ for ref in refs:
+ self.refs.append(int(ref.strip()))
+
+ def get_mirrors(self, placeholders):
+ mirrors = PlaceholderExpand.get_mirrors(self, placeholders)
+
+ if not self.ok:
+ return None
+
+ for ref in self.refs:
+ if ref in placeholders:
+ if ref not in mirrors:
+ mirrors.append(ref)
+ else:
+ self.ok = False
+ return None
+
+ return mirrors
+
+ # SIGALRM is not supported on all platforms (e.g. windows). Timeout
+ # with SIGALRM will not be used on those platforms. This will
+ # potentially block gedit if you have a placeholder which gets stuck,
+ # but it's better than not supporting them at all. At some point we
+ # might have proper thread support and we can fix this in a better way
+ def timeout_supported(self):
+ return hasattr(signal, 'SIGALRM')
+
+ def timeout_cb(self, signum = 0, frame = 0):
+ raise TimeoutError("Operation timed out (>2 seconds)")
+
+ def install_timeout(self):
+ if not self.timeout_supported():
+ return
+
+ if self.timeout_id != None:
+ self.remove_timeout()
+
+ self.timeout_id = signal.signal(signal.SIGALRM, self.timeout_cb)
+ signal.alarm(2)
+
+ def remove_timeout(self):
+ if not self.timeout_supported():
+ return
+
+ if self.timeout_id != None:
+ signal.alarm(0)
+
+ signal.signal(signal.SIGALRM, self.timeout_id)
+
+ self.timeout_id = None
+
+ def expand(self, text):
+ self.remove_timeout()
+
+ text = text.strip()
+ self.command = text
+
+ if not self.command or self.command == '':
+ self.set_text('')
+ return
+
+ text = "def process_snippet():\n\t" + "\n\t".join(text.split("\n"))
+
+ if 'process_snippet' in self.namespace:
+ del self.namespace['process_snippet']
+
+ try:
+ exec(text, self.namespace)
+ except:
+ traceback.print_exc()
+
+ if 'process_snippet' in self.namespace:
+ try:
+ # Install a sigalarm signal. This is a HACK to make sure
+ # gedit doesn't get freezed by someone creating a python
+ # placeholder which for instance loops indefinately. Since
+ # the code is executed synchronously it will hang gedit. With
+ # the alarm signal we raise an exception and catch this
+ # (see below). We show an error message and return False.
+ # ___this is a HACK___ and should be fixed properly (I just
+ # don't know how)
+ self.install_timeout()
+ result = self.namespace['process_snippet']()
+ self.remove_timeout()
+ except TimeoutError:
+ self.remove_timeout()
+
+ helper.message_dialog(None, Gtk.MessageType.ERROR, \
+ _('Execution of the Python command (%s) exceeds the maximum ' \
+ 'time, execution aborted.') % self.command)
+
+ return False
+ except Exception as detail:
+ self.remove_timeout()
+
+ helper.message_dialog(None, Gtk.MessageType.ERROR,
+ _('Execution of the Python command (%s) failed: %s') %
+ (self.command, detail))
+
+ return False
+
+ if result is None:
+ # sys.stderr.write("%s:\n>> %s\n" % (_('The following python code, run in a snippet, does not return a value'), "\n>> ".join(self.command.split("\n"))))
+ result = ''
+
+ self.set_text(str(result))
+
+ return True
+
+# Regular expression placeholder
+class PlaceholderRegex(PlaceholderExpand):
+ def __init__(self, view, tabstop, environ, begin, inp, pattern, substitution, modifiers):
+ PlaceholderExpand.__init__(self, view, tabstop, environ, begin, '')
+
+ self.instant_update = True
+ self.inp = inp
+ self.pattern = pattern
+ self.substitution = substitution
+
+ self.init_modifiers(modifiers)
+
+ def init_modifiers(self, modifiers):
+ mods = {'I': re.I,
+ 'L': re.L,
+ 'M': re.M,
+ 'S': re.S,
+ 'U': re.U,
+ 'X': re.X}
+
+ self.modifiers = 0
+
+ for modifier in modifiers:
+ if modifier in mods:
+ self.modifiers |= mods[modifier]
+
+ def get_mirrors(self, placeholders):
+ mirrors = self.find_mirrors(self.pattern, placeholders) + self.find_mirrors(self.substitution, placeholders)
+
+ if isinstance(self.inp, int):
+ if self.inp not in placeholders:
+ self.ok = False
+ return None
+ elif self.inp not in mirrors:
+ mirrors.append(self.inp)
+
+ return mirrors
+
+ def literal(self, s):
+ return re.escape(s)
+
+ def get_input(self):
+ env = self.get_environ()
+
+ if isinstance(self.inp, int):
+ return self.mirror_text[self.inp]
+ elif self.inp in env:
+ return env[self.inp]
+ else:
+ return ''
+
+ def run_update(self):
+ pattern = self.substitute(self.pattern)
+ substitution = self.substitute(self.substitution, SubstitutionParser.escape_substitution)
+
+ if pattern:
+ return self.expand(pattern, substitution)
+
+ return True
+
+ def expand(self, pattern, substitution):
+ # Try to compile pattern
+ try:
+ regex = re.compile(pattern, self.modifiers)
+ except re.error as message:
+ sys.stderr.write('Could not compile regular expression: %s\n%s\n' % (pattern, message))
+ return False
+
+ inp = self.get_input()
+ match = regex.search(inp)
+
+ if not match:
+ self.set_text(inp)
+ else:
+ groups = match.groupdict()
+
+ idx = 0
+ for group in match.groups():
+ groups[str(idx + 1)] = group
+ idx += 1
+
+ groups['0'] = match.group(0)
+
+ parser = SubstitutionParser(substitution, groups)
+ self.set_text(parser.parse())
+
+ return True
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/shareddata.py b/plugins/snippets/snippets/shareddata.py
new file mode 100644
index 0000000..be6fd14
--- /dev/null
+++ b/plugins/snippets/snippets/shareddata.py
@@ -0,0 +1,83 @@
+# Gedit snippets plugin
+# Copyright (C) 2011 Jesse van den Kieboom <jessevdk@gnome.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from .singleton import Singleton
+import os
+
+from gi.repository import Gtk
+
+# To register the GeditSnippetsManager type
+from .manager import Manager
+
+class SharedData(object, metaclass=Singleton):
+ def __init__(self):
+ self.dlg = None
+ self.dlg_default_size = None
+ self.controller_registry = {}
+ self.windows = {}
+
+ def register_controller(self, view, controller):
+ self.controller_registry[view] = controller
+
+ def unregister_controller(self, view, controller):
+ if self.controller_registry[view] == controller:
+ del self.controller_registry[view]
+
+ def register_window(self, window):
+ self.windows[window.window] = window
+
+ def unregister_window(self, window):
+ if window.window in self.windows:
+ del self.windows[window.window]
+
+ def update_state(self, window):
+ if window in self.windows:
+ self.windows[window].do_update_state()
+
+ def get_active_controller(self, window):
+ view = window.get_active_view()
+
+ if not view or not view in self.controller_registry:
+ return None
+
+ return self.controller_registry[view]
+
+ def get_controller(self, view):
+ if view in self.controller_registry:
+ return self.controller_registry[view]
+ else:
+ return None
+
+ def manager_destroyed(self, dlg):
+ self.dlg_default_size = dlg.get_final_size()
+ self.dlg = None
+
+ def show_manager(self, window, datadir):
+ if not self.dlg:
+ builder = Gtk.Builder()
+ builder.add_from_file(os.path.join(datadir, 'ui', 'snippets.ui'))
+
+ self.dlg = builder.get_object('snippets_manager')
+ self.dlg.connect('destroy', self.manager_destroyed)
+
+ if self.dlg_default_size:
+ self.dlg.set_default_size(self.dlg_default_size[0], self.dlg_default_size[1])
+
+ self.dlg.set_transient_for(window)
+ self.dlg.present()
+
+# vi:ex:ts=4:et
diff --git a/plugins/snippets/snippets/signals.py b/plugins/snippets/snippets/signals.py
new file mode 100644
index 0000000..647b616
--- /dev/null
+++ b/plugins/snippets/snippets/signals.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+#
+# signals.py
+#
+# Copyright (C) 2009 - Jesse van den Kieboom
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+class Signals:
+ def __init__(self):
+ self._signals = {}
+
+ def _connect(self, obj, name, handler, connector):
+ ret = self._signals.setdefault(obj, {})
+
+ hid = connector(name, handler)
+ ret.setdefault(name, []).append(hid)
+
+ return hid
+
+ def connect_signal(self, obj, name, handler):
+ return self._connect(obj, name, handler, obj.connect)
+
+ def connect_signal_after(self, obj, name, handler):
+ return self._connect(obj, name, handler, obj.connect_after)
+
+ def disconnect_signals(self, obj):
+ if obj not in self._signals:
+ return False
+
+ for name in self._signals[obj]:
+ for hid in self._signals[obj][name]:
+ obj.disconnect(hid)
+
+ del self._signals[obj]
+ return True
+
+ def block_signal(self, obj, name):
+ if obj not in self._signals:
+ return False
+
+ if name not in self._signals[obj]:
+ return False
+
+ for hid in self._signals[obj][name]:
+ obj.handler_block(hid)
+
+ return True
+
+ def unblock_signal(self, obj, name):
+ if obj not in self._signals:
+ return False
+
+ if name not in self._signals[obj]:
+ return False
+
+ for hid in self._signals[obj][name]:
+ obj.handler_unblock(hid)
+
+ return True
+
+ def disconnect_signal(self, obj, name):
+ if obj not in self._signals:
+ return False
+
+ if name not in self._signals[obj]:
+ return False
+
+ for hid in self._signals[obj][name]:
+ obj.disconnect(hid)
+
+ del self._signals[obj][name]
+
+ if len(self._signals[obj]) == 0:
+ del self._signals[obj]
+
+ return True
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/singleton.py b/plugins/snippets/snippets/singleton.py
new file mode 100644
index 0000000..9bc346d
--- /dev/null
+++ b/plugins/snippets/snippets/singleton.py
@@ -0,0 +1,27 @@
+# Gedit snippets plugin
+# Copyright (C) 2011 Jesse van den Kieboom <jessevdk@gnome.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+class Singleton(type):
+ def __init__(cls, name, bases, dict):
+ super(Singleton, cls).__init__(name, bases, dict)
+ cls.instance = None
+
+ def __call__(cls, *args, **kw):
+ if cls.instance is None:
+ cls.instance = super(Singleton, cls).__call__(*args, **kw)
+
+ return cls.instance
diff --git a/plugins/snippets/snippets/snippet.py b/plugins/snippets/snippets/snippet.py
new file mode 100644
index 0000000..6b18156
--- /dev/null
+++ b/plugins/snippets/snippets/snippet.py
@@ -0,0 +1,360 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gi.repository import Gio, Gtk
+
+import sys
+
+from .placeholder import PlaceholderEnd, PlaceholderMirror, Placeholder, PlaceholderShell, PlaceholderEval, PlaceholderRegex, PlaceholderExpand
+from .parser import Parser
+from . import helper
+
+class EvalUtilities:
+ def __init__(self, view=None):
+ self.view = view
+ self._init_namespace()
+
+ def _init_namespace(self):
+ self.namespace = {
+ '__builtins__': __builtins__,
+ 'align': self.util_align,
+ 'readfile': self.util_readfile,
+ 'filesize': self.util_filesize
+ }
+
+ def _real_len(self, s, tablen = 0):
+ if tablen == 0:
+ tablen = self.view.get_tab_width()
+
+ return len(s.expandtabs(tablen))
+
+ def _filename_to_uri(self, filename):
+ gfile = Gio.file_new_for_path(filename)
+
+ return gfile.get_uri()
+
+ def util_readfile(self, filename):
+ stream = Gio.file_new_for_path(filename).read()
+
+ if not stream:
+ return ''
+
+ res = stream.read()
+ stream.close()
+
+ return res
+
+ def util_filesize(self, filename):
+ gfile = Gio.file_new_for_path(filename)
+ info = gfile.query_info(Gio.FILE_ATTRIBUTE_STANDARD_SIZE)
+
+ if not info:
+ return 0
+
+ return info.get_size()
+
+ def util_align(self, items):
+ maxlen = []
+ tablen = self.view.get_tab_width()
+
+ for row in range(0, len(items)):
+ for col in range(0, len(items[row]) - 1):
+ if row == 0:
+ maxlen.append(0)
+
+ items[row][col] += "\t"
+ rl = self._real_len(items[row][col], tablen)
+
+ if (rl > maxlen[col]):
+ maxlen[col] = rl
+
+ result = ''
+
+ for row in range(0, len(items)):
+ for col in range(0, len(items[row]) - 1):
+ item = items[row][col]
+
+ result += item + ("\t" * int(((maxlen[col] - \
+ self._real_len(item, tablen)) / tablen)))
+
+ result += items[row][len(items[row]) - 1]
+
+ if row != len(items) - 1:
+ result += "\n"
+
+ return result
+
+class Snippet:
+ def __init__(self, data, environ = {}):
+ self.data = data
+ self.environ = environ
+
+ def __getitem__(self, prop):
+ return self.data[prop]
+
+ def __setitem__(self, prop, value):
+ self.data[prop] = value
+
+ def accelerator_display(self):
+ accel = self['accelerator']
+
+ if accel:
+ keyval, mod = Gtk.accelerator_parse(accel)
+ accel = Gtk.accelerator_get_label(keyval, mod)
+
+ return accel or ''
+
+ def display(self):
+ nm = helper.markup_escape(self['description'])
+
+ tag = self['tag']
+ accel = self.accelerator_display()
+ detail = []
+
+ if tag and tag != '':
+ detail.append(tag)
+
+ if accel and accel != '':
+ detail.append(accel)
+
+ if not detail:
+ return nm
+ else:
+ return nm + ' (<b>' + helper.markup_escape(', '.join(detail)) + \
+ '</b>)'
+
+ def _add_placeholder(self, placeholder):
+ if placeholder.tabstop in self.placeholders:
+ if placeholder.tabstop == -1:
+ self.placeholders[-1].append(placeholder)
+ self.plugin_data.ordered_placeholders.append(placeholder)
+ elif placeholder.tabstop == -1:
+ self.placeholders[-1] = [placeholder]
+ self.plugin_data.ordered_placeholders.append(placeholder)
+ else:
+ self.placeholders[placeholder.tabstop] = placeholder
+ self.plugin_data.ordered_placeholders.append(placeholder)
+
+ def _insert_text(self, text):
+ # Insert text keeping indentation in mind
+ indented = str.join('\n' + self._indent, helper.spaces_instead_of_tabs(self._view, text).split('\n'))
+ self._view.get_buffer().insert(self._insert_iter(), indented)
+
+ def _insert_iter(self):
+ return self._view.get_buffer().get_iter_at_mark(self._insert_mark)
+
+ def _create_environment(self, data):
+ if data in self.environ['utf8']:
+ val = self.environ['utf8'][data]
+ else:
+ val = ''
+
+ # Get all the current indentation
+ all_indent = helper.compute_indentation(self._view, self._insert_iter())
+
+ # Substract initial indentation to get the snippet indentation
+ indent = all_indent[len(self._indent):]
+
+ # Keep indentation
+ return str.join('\n' + indent, val.split('\n'))
+
+ def _create_placeholder(self, data):
+ tabstop = data['tabstop']
+ begin = self._insert_iter()
+
+ if tabstop == 0:
+ # End placeholder
+ return PlaceholderEnd(self._view, self.environ, begin, data['default'])
+ elif tabstop in self.placeholders:
+ # Mirror placeholder
+ return PlaceholderMirror(self._view, tabstop, self.environ, begin)
+ else:
+ # Default placeholder
+ return Placeholder(self._view, tabstop, self.environ, data['default'], begin)
+
+ def _create_shell(self, data):
+ begin = self._insert_iter()
+ return PlaceholderShell(self._view, data['tabstop'], self.environ, begin, data['contents'])
+
+ def _create_eval(self, data):
+ begin = self._insert_iter()
+ return PlaceholderEval(self._view, data['tabstop'], self.environ, data['dependencies'], begin, data['contents'], self._utils.namespace)
+
+ def _create_regex(self, data):
+ begin = self._insert_iter()
+ return PlaceholderRegex(self._view, data['tabstop'], self.environ, begin, data['input'], data['pattern'], data['substitution'], data['modifiers'])
+
+ def _create_text(self, data):
+ return data
+
+ def _invalid_placeholder(self, placeholder, remove):
+ buf = self._view.get_buffer()
+
+ # Remove the text because this placeholder is invalid
+ if placeholder.default and remove:
+ buf.delete(placeholder.begin_iter(), placeholder.end_iter())
+
+ placeholder.remove()
+
+ if placeholder.tabstop == -1:
+ index = self.placeholders[-1].index(placeholder)
+ del self.placeholders[-1][index]
+ else:
+ del self.placeholders[placeholder.tabstop]
+
+ self.plugin_data.ordered_placeholders.remove(placeholder)
+
+ def _parse(self, plugin_data):
+ # Initialize current variables
+ self._view = plugin_data.view
+ self._indent = helper.compute_indentation(self._view, self._view.get_buffer().get_iter_at_mark(self.begin_mark))
+ self._utils = EvalUtilities(self._view)
+ self.placeholders = {}
+ self._insert_mark = self.end_mark
+ self.plugin_data = plugin_data
+
+ # Create parser
+ parser = Parser(data=self['text'])
+
+ # Parse tokens
+ while (True):
+ token = parser.token()
+
+ if not token:
+ break
+
+ try:
+ val = {'environment': self._create_environment,
+ 'placeholder': self._create_placeholder,
+ 'shell': self._create_shell,
+ 'eval': self._create_eval,
+ 'regex': self._create_regex,
+ 'text': self._create_text}[token.klass](token.data)
+ except KeyError:
+ sys.stderr.write('Token class not supported: %s (%s)\n' % token.klass)
+ continue
+
+ if isinstance(val, str):
+ # Insert text
+ self._insert_text(val)
+ else:
+ # Insert placeholder
+ self._add_placeholder(val)
+
+ # Create end placeholder if there isn't one yet
+ if 0 not in self.placeholders:
+ self.placeholders[0] = PlaceholderEnd(self._view, self.environ, self.end_iter(), None)
+ self.plugin_data.ordered_placeholders.append(self.placeholders[0])
+
+ # Make sure run_last is ran for all placeholders and remove any
+ # non `ok` placeholders
+ for tabstop in self.placeholders.copy():
+ ph = (tabstop == -1 and list(self.placeholders[-1])) or [self.placeholders[tabstop]]
+
+ for placeholder in ph:
+ placeholder.run_last(self.placeholders)
+
+ if not placeholder.ok or placeholder.done:
+ self._invalid_placeholder(placeholder, not placeholder.ok)
+
+ # Remove all the Expand placeholders which have a tabstop because
+ # they can be used to mirror, but they shouldn't be real tabstops
+ # (if they have mirrors installed). This is problably a bit of
+ # a dirty hack :)
+ if -1 not in self.placeholders:
+ self.placeholders[-1] = []
+
+ for tabstop in self.placeholders.copy():
+ placeholder = self.placeholders[tabstop]
+
+ if tabstop != -1:
+ if isinstance(placeholder, PlaceholderExpand) and \
+ placeholder.has_references:
+ # Add to anonymous placeholders
+ self.placeholders[-1].append(placeholder)
+
+ # Remove placeholder
+ del self.placeholders[tabstop]
+
+ self.plugin_data = None
+
+ def insert_into(self, plugin_data, insert):
+ buf = plugin_data.view.get_buffer()
+ last_index = 0
+
+ # Find closest mark at current insertion, so that we may insert
+ # our marks in the correct order
+ (current, next) = plugin_data.next_placeholder()
+
+ if current:
+ # Insert AFTER current
+ last_index = plugin_data.placeholders.index(current) + 1
+ elif next:
+ # Insert BEFORE next
+ last_index = plugin_data.placeholders.index(next)
+ else:
+ # Insert at first position
+ last_index = 0
+
+ # lastIndex now contains the position of the last mark
+ # Create snippet bounding marks
+ self.begin_mark = buf.create_mark(None, insert, True)
+ self.end_mark = buf.create_mark(None, insert, False)
+
+ # Now parse the contents of this snippet, create Placeholders
+ # and insert the placholder marks in the marks array of plugin_data
+ self._parse(plugin_data)
+
+ # So now all of the snippet is in the buffer, we have all our
+ # placeholders right here, what's next, put all marks in the
+ # plugin_data.marks
+ k = sorted(self.placeholders.keys(), reverse=True)
+
+ plugin_data.placeholders.insert(last_index, self.placeholders[0])
+ last_iter = self.placeholders[0].end_iter()
+
+ for tabstop in k:
+ if tabstop != -1 and tabstop != 0:
+ placeholder = self.placeholders[tabstop]
+ end_iter = placeholder.end_iter()
+
+ if last_iter.compare(end_iter) < 0:
+ last_iter = end_iter
+
+ # Inserting placeholder
+ plugin_data.placeholders.insert(last_index, placeholder)
+
+ # Move end mark to last placeholder
+ buf.move_mark(self.end_mark, last_iter)
+
+ return self
+
+ def deactivate(self):
+ buf = self.begin_mark.get_buffer()
+
+ buf.delete_mark(self.begin_mark)
+ buf.delete_mark(self.end_mark)
+
+ self.placeholders = {}
+
+ def begin_iter(self):
+ return self.begin_mark.get_buffer().get_iter_at_mark(self.begin_mark)
+
+ def end_iter(self):
+ return self.end_mark.get_buffer().get_iter_at_mark(self.end_mark)
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/snippets.ui b/plugins/snippets/snippets/snippets.ui
new file mode 100644
index 0000000..e80d633
--- /dev/null
+++ b/plugins/snippets/snippets/snippets.ui
@@ -0,0 +1,417 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.16.0 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkListStore" id="model1">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0">text</col>
+ </row>
+ <row>
+ <col id="0">text/plain</col>
+ </row>
+ <row>
+ <col id="0">text/xml</col>
+ </row>
+ <row>
+ <col id="0">image</col>
+ </row>
+ <row>
+ <col id="0">image/png</col>
+ </row>
+ <row>
+ <col id="0">image/jpeg</col>
+ </row>
+ <row>
+ <col id="0">audio</col>
+ </row>
+ <row>
+ <col id="0">video</col>
+ </row>
+ </data>
+ </object>
+ <object class="GeditDocument" id="source_buffer"/>
+ <object class="GeditSnippetsManager" id="snippets_manager">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Manage Snippets</property>
+ <property name="default_width">800</property>
+ <property name="default_height">600</property>
+ <property name="type_hint">dialog</property>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="headerbar">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Manage Snippets</property>
+ <property name="show_close_button">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPaned" id="hpaned_paned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">275</property>
+ <style>
+ <class name="gedit-snippet-manager-paned"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window_snippets">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <style>
+ <class name="gedit-snippet-manager-treeview"/>
+ </style>
+ <child>
+ <object class="GtkTreeView" id="tree_view_snippets">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <signal name="key-press-event" handler="on_tree_view_snippets_key_press" swapped="no"/>
+ <signal name="row-expanded" handler="on_tree_view_snippets_row_expanded" swapped="no"/>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="toolbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="toolbar_style">icons</property>
+ <property name="icon_size">1</property>
+ <child>
+ <object class="GtkToolButton" id="add_snippet_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_markup" translatable="yes">Create new snippet</property>
+ <property name="tooltip_text" translatable="yes">Create new snippet</property>
+ <property name="label" translatable="yes">Add Snippet</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">list-add-symbolic</property>
+ <signal name="clicked" handler="on_add_snippet_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="remove_snippet_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_markup" translatable="yes">Delete selected snippet</property>
+ <property name="tooltip_text" translatable="yes">Delete selected snippet</property>
+ <property name="label" translatable="yes">Remove Snippet</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">list-remove-symbolic</property>
+ <signal name="clicked" handler="on_remove_snippet_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="import_snippets_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_markup" translatable="yes">Import snippets</property>
+ <property name="tooltip_text" translatable="yes">Import snippets</property>
+ <property name="label" translatable="yes">Import Snippets</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">document-open-symbolic</property>
+ <signal name="clicked" handler="on_import_snippets_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="export_snippets_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_markup" translatable="yes">Export selected snippets</property>
+ <property name="tooltip_text" translatable="yes">Export selected snippets</property>
+ <property name="label" translatable="yes">Export Snippets</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">document-save-as-symbolic</property>
+ <signal name="clicked" handler="on_export_snippets_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <style>
+ <class name="inline-toolbar"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox_snippet">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window_snippet">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <style>
+ <class name="gedit-snippet-manager-view"/>
+ </style>
+ <child>
+ <object class="GeditView" id="source_view_snippet">
+ <property name="buffer">source_buffer</property>
+ <property name="visible">True</property>
+ <signal handler="on_source_view_snippet_focus_out" name="focus_out_event"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">6</property>
+ <property name="margin_end">6</property>
+ <property name="margin_top">6</property>
+ <property name="margin_bottom">6</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Activation</property>
+ <property name="use_markup">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label_tab_trigger">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes" comments="&quot;tab&quot; here means the tab key, not the notebook tab!">_Tab trigger:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_tab_trigger</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox_tab_trigger">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkEntry" id="entry_tab_trigger">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Single word the snippet is activated with after pressing Tab</property>
+ <signal name="changed" handler="on_entry_tab_trigger_changed" swapped="no"/>
+ <signal name="focus-out-event" handler="on_entry_tab_trigger_focus_out" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image_tab_trigger">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">3</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_accelerator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">S_hortcut key:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_accelerator</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_accelerator">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Shortcut key with which the snippet is activated</property>
+ <property name="editable">False</property>
+ <signal name="focus-in-event" handler="on_entry_accelerator_focus_in" swapped="no"/>
+ <signal name="focus-out-event" handler="on_entry_accelerator_focus_out" swapped="no"/>
+ <signal name="key-press-event" handler="on_entry_accelerator_key_press" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_drop_targets">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Drop targets:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">entry_accelerator</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_drop_targets">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">model1</property>
+ <property name="has_entry">True</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ <child internal-child="entry">
+ <object class="GtkEntry" id="combobox-entry">
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/plugins/snippets/snippets/substitutionparser.py b/plugins/snippets/snippets/substitutionparser.py
new file mode 100644
index 0000000..8469dd3
--- /dev/null
+++ b/plugins/snippets/snippets/substitutionparser.py
@@ -0,0 +1,203 @@
+# Gedit snippets plugin
+# Copyright (C) 2006-2007 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import re
+
+class ParseError(Exception):
+ def __str__(self):
+ return 'Parse error, resume next'
+
+class Modifiers:
+ def _first_char(s):
+ first = (s != '' and s[0]) or ''
+ rest = (len(s) > 1 and s[1:]) or ''
+
+ return first, rest
+
+ def upper_first(s):
+ first, rest = Modifiers._first_char(s)
+
+ return '%s%s' % (first.upper(), rest)
+
+ def upper(s):
+ return s.upper()
+
+ def lower_first(s):
+ first, rest = Modifiers._first_char(s)
+
+ return '%s%s' % (first.lower(), rest)
+
+ def lower(s):
+ return s.lower()
+
+ def title(s):
+ return s.title()
+
+ upper_first = staticmethod(upper_first)
+ upper = staticmethod(upper)
+ lower_first = staticmethod(lower_first)
+ lower = staticmethod(lower)
+ title = staticmethod(title)
+ _first_char = staticmethod(_first_char)
+
+class SubstitutionParser:
+ REG_ID = '[0-9]+'
+ REG_NAME = '[a-zA-Z_]+'
+ REG_MOD = '[a-zA-Z]+'
+ REG_ESCAPE = '\\\\|\\(\\?|,|\\)'
+
+ REG_GROUP = '(?:(%s)|<(%s|%s)(?:,(%s))?>)' % (REG_ID, REG_ID, REG_NAME, REG_MOD)
+
+ def __init__(self, pattern, groups = {}, modifiers = {}):
+ self.pattern = pattern
+ self.groups = groups
+
+ self.modifiers = {'u': Modifiers.upper_first,
+ 'U': Modifiers.upper,
+ 'l': Modifiers.lower_first,
+ 'L': Modifiers.lower,
+ 't': Modifiers.title}
+
+ for k, v in modifiers.items():
+ self.modifiers[k] = v
+
+ def parse(self):
+ result, tokens = self._parse(self.pattern, None)
+
+ return result
+
+ def _parse(self, tokens, terminator):
+ result = ''
+
+ while tokens != '':
+ if self._peek(tokens) == '' or self._peek(tokens) == terminator:
+ tokens = self._remains(tokens)
+ break
+
+ try:
+ res, tokens = self._expr(tokens, terminator)
+ except ParseError:
+ res, tokens = self._text(tokens)
+
+ result += res
+
+ return result, tokens
+
+ def _peek(self, tokens, num = 0):
+ return (num < len(tokens) and tokens[num])
+
+ def _token(self, tokens):
+ if tokens == '':
+ return '', '';
+
+ return tokens[0], (len(tokens) > 1 and tokens[1:]) or ''
+
+ def _remains(self, tokens, num = 1):
+ return (num < len(tokens) and tokens[num:]) or ''
+
+ def _expr(self, tokens, terminator):
+ if tokens == '':
+ return ''
+
+ try:
+ return {'\\': self._escape,
+ '(': self._condition}[self._peek(tokens)](tokens, terminator)
+ except KeyError:
+ raise ParseError
+
+ def _text(self, tokens):
+ return self._token(tokens)
+
+ def _substitute(self, group, modifiers = ''):
+ result = (self.groups.has_key(group) and self.groups[group]) or ''
+
+ for modifier in modifiers:
+ if self.modifiers.has_key(modifier):
+ result = self.modifiers[modifier](result)
+
+ return result
+
+ def _match_group(self, tokens):
+ match = re.match('\\\\%s' % self.REG_GROUP, tokens)
+
+ if not match:
+ return None, tokens
+
+ return self._substitute(match.group(1) or match.group(2), match.group(3) or ''), tokens[match.end():]
+
+ def _escape(self, tokens, terminator):
+ # Try to match a group
+ result, tokens = self._match_group(tokens)
+
+ if result != None:
+ return result, tokens
+
+ s = self.REG_GROUP
+
+ if terminator:
+ s += '|%s' % re.escape(terminator)
+
+ match = re.match('\\\\(\\\\%s|%s)' % (s, self.REG_ESCAPE), tokens)
+
+ if not match:
+ raise ParseError
+
+ return match.group(1), tokens[match.end():]
+
+ def _condition_value(self, tokens):
+ match = re.match('\\\\?%s\s*' % self.REG_GROUP, tokens)
+
+ if not match:
+ return None, tokens
+
+ groups = match.groups()
+ name = groups[0] or groups[1]
+
+ return self.groups.has_key(name) and self.groups[name] != None, tokens[match.end():]
+
+ def _condition(self, tokens, terminator):
+ # Match ? after (
+ if self._peek(tokens, 1) != '?':
+ raise ParseError
+
+ # Remove initial (? token
+ tokens = self._remains(tokens, 2)
+ condition, tokens = self._condition_value(tokens)
+
+ if condition is None or self._peek(tokens) != ',':
+ raise ParseError
+
+ truepart, tokens = self._parse(self._remains(tokens), ',')
+
+ if truepart is None:
+ raise ParseError
+
+ falsepart, tokens = self._parse(tokens, ')')
+
+ if falsepart is None:
+ raise ParseError
+
+ if condition:
+ return truepart, tokens
+ else:
+ return falsepart, tokens
+
+ @staticmethod
+ def escape_substitution(substitution):
+ return re.sub('(%s|%s)' % (SubstitutionParser.REG_GROUP, SubstitutionParser.REG_ESCAPE), '\\\\\\1', substitution)
+
+# ex:ts=4:et:
diff --git a/plugins/snippets/snippets/windowactivatable.py b/plugins/snippets/snippets/windowactivatable.py
new file mode 100644
index 0000000..6729a69
--- /dev/null
+++ b/plugins/snippets/snippets/windowactivatable.py
@@ -0,0 +1,186 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gi.repository import Gtk, Gedit, GObject
+from .snippet import Snippet
+from .library import Library
+from .shareddata import SharedData
+from .signals import Signals
+
+
+class Message(Gedit.Message):
+ view = GObject.Property(type=Gedit.View)
+ iter = GObject.Property(type=Gtk.TextIter)
+
+class Activate(Message):
+ trigger = GObject.Property(type=str)
+
+class ParseAndActivate(Message):
+ snippet = GObject.Property(type=str)
+
+class WindowActivatable(GObject.Object, Gedit.WindowActivatable, Signals):
+ __gtype_name__ = "GeditSnippetsWindowActivatable"
+
+ window = GObject.Property(type=Gedit.Window)
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+ Signals.__init__(self)
+
+ self.current_language_accel_group = None
+
+ def do_activate(self):
+ self.register_messages()
+
+ library = Library()
+ library.add_accelerator_callback(self.accelerator_activated)
+
+ self.accel_group = Library().get_accel_group(None)
+
+ if self.accel_group:
+ self.window.add_accel_group(self.accel_group)
+
+ self.connect_signal(self.window,
+ 'active-tab-changed',
+ self.on_active_tab_changed)
+
+ self.do_update_state()
+
+ SharedData().register_window(self)
+
+ def do_deactivate(self):
+ if self.accel_group:
+ self.window.remove_accel_group(self.accel_group)
+
+ self.accel_group = None
+
+ self.unregister_messages()
+
+ library = Library()
+ library.remove_accelerator_callback(self.accelerator_activated)
+
+ self.disconnect_signals(self.window)
+
+ SharedData().unregister_window(self)
+
+ def do_update_state(self):
+ controller = SharedData().get_active_controller(self.window)
+
+ self.update_language(controller)
+
+ def register_messages(self):
+ bus = self.window.get_message_bus()
+
+ bus.register(Activate, '/plugins/snippets', 'activate')
+ bus.register(ParseAndActivate, '/plugins/snippets', 'parse-and-activate')
+
+ self.signal_ids = set()
+
+ sid = bus.connect('/plugins/snippets', 'activate', self.on_message_activate, None)
+ self.signal_ids.add(sid)
+
+ sid = bus.connect('/plugins/snippets', 'parse-and-activate', self.on_message_parse_and_activate, None)
+ self.signal_ids.add(sid)
+
+ def unregister_messages(self):
+ bus = self.window.get_message_bus()
+ for sid in self.signal_ids:
+ bus.disconnect(sid)
+ self.signal_ids = set()
+ bus.unregister_all('/plugins/snippets')
+
+ def on_message_activate(self, bus, message, userdata):
+ view = message.props.view
+
+ if not view:
+ view = self.window.get_active_view()
+
+ controller = SharedData().get_controller(view)
+
+ if not controller:
+ return
+
+ iter = message.props.iter
+
+ if not iter:
+ iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert())
+
+ controller.run_snippet_trigger(message.props.trigger, (iter, iter))
+
+ def on_message_parse_and_activate(self, bus, message, userdata):
+ view = message.props.view
+
+ if not view:
+ view = self.window.get_active_view()
+
+ controller = SharedData().get_controller(view)
+
+ if not controller:
+ return
+
+ iter = message.props.iter
+
+ if not iter:
+ iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert())
+
+ controller.parse_and_run_snippet(message.props.snippet, iter)
+
+ def find_snippet(self, snippets, tag):
+ result = []
+
+ for snippet in snippets:
+ if Snippet(snippet)['tag'] == tag:
+ result.append(snippet)
+
+ return result
+
+ def update_language(self, controller):
+ if not self.window:
+ return
+
+ if controller:
+ langid = controller.language_id
+ else:
+ langid = None
+
+ if langid != None:
+ accelgroup = Library().get_accel_group(langid)
+ else:
+ accelgroup = None
+
+ if accelgroup != self.current_language_accel_group:
+ if self.current_language_accel_group:
+ self.window.remove_accel_group(self.current_language_accel_group)
+
+ if accelgroup:
+ self.window.add_accel_group(accelgroup)
+
+ self.current_language_accel_group = accelgroup
+
+ def on_active_tab_changed(self, window, tab):
+ self.update_language(SharedData().get_controller(tab.get_view()))
+
+ def accelerator_activated(self, group, obj, keyval, mod):
+ if obj == self.window:
+ controller = SharedData().get_active_controller(self.window)
+
+ if controller:
+ return controller.accelerator_activate(keyval, mod)
+ else:
+ return False
+
+# ex:ts=4:et:
diff --git a/plugins/sort/gedit-sort-plugin.c b/plugins/sort/gedit-sort-plugin.c
new file mode 100644
index 0000000..41774fc
--- /dev/null
+++ b/plugins/sort/gedit-sort-plugin.c
@@ -0,0 +1,418 @@
+/*
+ * gedit-sort-plugin.c
+ *
+ * Original author: Carlo Borreo <borreo@softhome.net>
+ * Ported to Gedit2 by Lee Mallabone <gnome@fonicmonkey.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gedit-sort-plugin.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-utils.h>
+#include <gedit/gedit-app.h>
+#include <gedit/gedit-window.h>
+#include <gedit/gedit-app-activatable.h>
+#include <gedit/gedit-window-activatable.h>
+
+static void gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface);
+static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface);
+
+struct _GeditSortPluginPrivate
+{
+ GeditWindow *window;
+
+ GSimpleAction *action;
+ GtkWidget *dialog;
+ GtkWidget *col_num_spinbutton;
+ GtkWidget *reverse_order_checkbutton;
+ GtkWidget *case_checkbutton;
+ GtkWidget *remove_dups_checkbutton;
+
+ GeditApp *app;
+ GeditMenuExtension *menu_ext;
+
+ GtkTextIter start, end; /* selection */
+};
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW,
+ PROP_APP
+};
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditSortPlugin,
+ gedit_sort_plugin,
+ PEAS_TYPE_EXTENSION_BASE,
+ 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_APP_ACTIVATABLE,
+ gedit_app_activatable_iface_init)
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE,
+ gedit_window_activatable_iface_init)
+ G_ADD_PRIVATE_DYNAMIC (GeditSortPlugin))
+
+static void
+do_sort (GeditSortPlugin *plugin)
+{
+ GeditSortPluginPrivate *priv;
+ GeditDocument *doc;
+ GtkSourceSortFlags sort_flags = 0;
+ gint starting_column;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ doc = gedit_window_get_active_document (priv->window);
+ g_return_if_fail (doc != NULL);
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->case_checkbutton)))
+ {
+ sort_flags |= GTK_SOURCE_SORT_FLAGS_CASE_SENSITIVE;
+ }
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->reverse_order_checkbutton)))
+ {
+ sort_flags |= GTK_SOURCE_SORT_FLAGS_REVERSE_ORDER;
+ }
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->remove_dups_checkbutton)))
+ {
+ sort_flags |= GTK_SOURCE_SORT_FLAGS_REMOVE_DUPLICATES;
+ }
+
+ starting_column = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (priv->col_num_spinbutton)) - 1;
+
+ gtk_source_buffer_sort_lines (GTK_SOURCE_BUFFER (doc),
+ &priv->start,
+ &priv->end,
+ sort_flags,
+ starting_column);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Done.");
+}
+
+static void
+sort_dialog_response_handler (GtkDialog *dlg,
+ gint response,
+ GeditSortPlugin *plugin)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ if (response == GTK_RESPONSE_OK)
+ {
+ do_sort (plugin);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dlg));
+}
+
+/* NOTE: we store the current selection in the dialog since focusing
+ * the text field (like the combo box) looses the documnent selection.
+ * Storing the selection ONLY works because the dialog is modal */
+static void
+get_current_selection (GeditSortPlugin *plugin)
+{
+ GeditSortPluginPrivate *priv;
+ GeditDocument *doc;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ doc = gedit_window_get_active_document (priv->window);
+
+ if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ &priv->start,
+ &priv->end))
+ {
+ /* No selection, get the whole document. */
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc),
+ &priv->start,
+ &priv->end);
+ }
+}
+
+static void
+create_sort_dialog (GeditSortPlugin *plugin)
+{
+ GeditSortPluginPrivate *priv;
+ GtkBuilder *builder;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_resource (builder, "/org/gnome/gedit/plugins/sort/ui/gedit-sort-plugin.ui", NULL);
+ priv->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "sort_dialog"));
+ priv->reverse_order_checkbutton = GTK_WIDGET (gtk_builder_get_object (builder, "reverse_order_checkbutton"));
+ priv->col_num_spinbutton = GTK_WIDGET (gtk_builder_get_object (builder, "col_num_spinbutton"));
+ priv->case_checkbutton = GTK_WIDGET (gtk_builder_get_object (builder, "case_checkbutton"));
+ priv->remove_dups_checkbutton = GTK_WIDGET (gtk_builder_get_object (builder, "remove_dups_checkbutton"));
+ g_object_unref (builder);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
+ GTK_RESPONSE_OK);
+
+ g_signal_connect (priv->dialog,
+ "destroy",
+ G_CALLBACK (gtk_widget_destroyed),
+ &priv->dialog);
+
+ g_signal_connect (priv->dialog,
+ "response",
+ G_CALLBACK (sort_dialog_response_handler),
+ plugin);
+
+ get_current_selection (plugin);
+}
+
+static void
+sort_cb (GAction *action,
+ GVariant *parameter,
+ GeditSortPlugin *plugin)
+{
+ GeditSortPluginPrivate *priv;
+ GtkWindowGroup *wg;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ create_sort_dialog (plugin);
+
+ wg = gedit_window_get_group (priv->window);
+ gtk_window_group_add_window (wg,
+ GTK_WINDOW (priv->dialog));
+
+ gtk_window_set_transient_for (GTK_WINDOW (priv->dialog),
+ GTK_WINDOW (priv->window));
+
+ gtk_window_set_modal (GTK_WINDOW (priv->dialog),
+ TRUE);
+
+ gtk_widget_show (GTK_WIDGET (priv->dialog));
+}
+
+static void
+update_ui (GeditSortPlugin *plugin)
+{
+ GeditView *view;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ view = gedit_window_get_active_view (plugin->priv->window);
+
+ g_simple_action_set_enabled (plugin->priv->action,
+ (view != NULL) &&
+ gtk_text_view_get_editable (GTK_TEXT_VIEW (view)));
+}
+
+static void
+gedit_sort_plugin_app_activate (GeditAppActivatable *activatable)
+{
+ GeditSortPluginPrivate *priv;
+ GMenuItem *item;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_SORT_PLUGIN (activatable)->priv;
+
+ priv->menu_ext = gedit_app_activatable_extend_menu (activatable, "tools-section");
+ item = g_menu_item_new (_("S_ort…"), "win.sort");
+ gedit_menu_extension_append_menu_item (priv->menu_ext, item);
+ g_object_unref (item);
+}
+
+static void
+gedit_sort_plugin_app_deactivate (GeditAppActivatable *activatable)
+{
+ GeditSortPluginPrivate *priv;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_SORT_PLUGIN (activatable)->priv;
+
+ g_clear_object (&priv->menu_ext);
+}
+
+static void
+gedit_sort_plugin_window_activate (GeditWindowActivatable *activatable)
+{
+ GeditSortPluginPrivate *priv;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_SORT_PLUGIN (activatable)->priv;
+
+ priv->action = g_simple_action_new ("sort", NULL);
+ g_signal_connect (priv->action, "activate",
+ G_CALLBACK (sort_cb), activatable);
+ g_action_map_add_action (G_ACTION_MAP (priv->window),
+ G_ACTION (priv->action));
+
+ update_ui (GEDIT_SORT_PLUGIN (activatable));
+}
+
+static void
+gedit_sort_plugin_window_deactivate (GeditWindowActivatable *activatable)
+{
+ GeditSortPluginPrivate *priv;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_SORT_PLUGIN (activatable)->priv;
+ g_action_map_remove_action (G_ACTION_MAP (priv->window), "sort");
+}
+
+static void
+gedit_sort_plugin_window_update_state (GeditWindowActivatable *activatable)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ update_ui (GEDIT_SORT_PLUGIN (activatable));
+}
+
+static void
+gedit_sort_plugin_init (GeditSortPlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditSortPlugin initializing");
+
+ plugin->priv = gedit_sort_plugin_get_instance_private (plugin);
+}
+
+static void
+gedit_sort_plugin_dispose (GObject *object)
+{
+ GeditSortPlugin *plugin = GEDIT_SORT_PLUGIN (object);
+
+ gedit_debug_message (DEBUG_PLUGINS, "GeditSortPlugin disposing");
+
+ g_clear_object (&plugin->priv->action);
+ g_clear_object (&plugin->priv->window);
+ g_clear_object (&plugin->priv->menu_ext);
+ g_clear_object (&plugin->priv->app);
+
+ G_OBJECT_CLASS (gedit_sort_plugin_parent_class)->dispose (object);
+}
+
+
+static void
+gedit_sort_plugin_finalize (GObject *object)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditSortPlugin finalizing");
+
+ G_OBJECT_CLASS (gedit_sort_plugin_parent_class)->finalize (object);
+}
+
+static void
+gedit_sort_plugin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditSortPlugin *plugin = GEDIT_SORT_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value));
+ break;
+ case PROP_APP:
+ plugin->priv->app = GEDIT_APP (g_value_dup_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_sort_plugin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditSortPlugin *plugin = GEDIT_SORT_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value, plugin->priv->window);
+ break;
+ case PROP_APP:
+ g_value_set_object (value, plugin->priv->app);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_sort_plugin_class_init (GeditSortPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_sort_plugin_dispose;
+ object_class->finalize = gedit_sort_plugin_finalize;
+ object_class->set_property = gedit_sort_plugin_set_property;
+ object_class->get_property = gedit_sort_plugin_get_property;
+
+ g_object_class_override_property (object_class, PROP_WINDOW, "window");
+ g_object_class_override_property (object_class, PROP_APP, "app");
+}
+
+static void
+gedit_sort_plugin_class_finalize (GeditSortPluginClass *klass)
+{
+}
+
+static void
+gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface)
+{
+ iface->activate = gedit_sort_plugin_app_activate;
+ iface->deactivate = gedit_sort_plugin_app_deactivate;
+}
+
+static void
+gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface)
+{
+ iface->activate = gedit_sort_plugin_window_activate;
+ iface->deactivate = gedit_sort_plugin_window_deactivate;
+ iface->update_state = gedit_sort_plugin_window_update_state;
+}
+
+G_MODULE_EXPORT void
+peas_register_types (PeasObjectModule *module)
+{
+ gedit_sort_plugin_register_type (G_TYPE_MODULE (module));
+
+ peas_object_module_register_extension_type (module,
+ GEDIT_TYPE_APP_ACTIVATABLE,
+ GEDIT_TYPE_SORT_PLUGIN);
+ peas_object_module_register_extension_type (module,
+ GEDIT_TYPE_WINDOW_ACTIVATABLE,
+ GEDIT_TYPE_SORT_PLUGIN);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/sort/gedit-sort-plugin.h b/plugins/sort/gedit-sort-plugin.h
new file mode 100644
index 0000000..e8739e0
--- /dev/null
+++ b/plugins/sort/gedit-sort-plugin.h
@@ -0,0 +1,59 @@
+/*
+ * gedit-sort-plugin.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_SORT_PLUGIN_H
+#define GEDIT_SORT_PLUGIN_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libpeas/peas-extension-base.h>
+#include <libpeas/peas-object-module.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_SORT_PLUGIN (gedit_sort_plugin_get_type ())
+#define GEDIT_SORT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_SORT_PLUGIN, GeditSortPlugin))
+#define GEDIT_SORT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_SORT_PLUGIN, GeditSortPluginClass))
+#define GEDIT_IS_SORT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_SORT_PLUGIN))
+#define GEDIT_IS_SORT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_SORT_PLUGIN))
+#define GEDIT_SORT_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_SORT_PLUGIN, GeditSortPluginClass))
+
+typedef struct _GeditSortPlugin GeditSortPlugin;
+typedef struct _GeditSortPluginPrivate GeditSortPluginPrivate;
+typedef struct _GeditSortPluginClass GeditSortPluginClass;
+
+struct _GeditSortPlugin
+{
+ PeasExtensionBase parent;
+
+ /*< private >*/
+ GeditSortPluginPrivate *priv;
+};
+
+struct _GeditSortPluginClass
+{
+ PeasExtensionBaseClass parent_class;
+};
+
+GType gedit_sort_plugin_get_type (void) G_GNUC_CONST;
+
+G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
+
+G_END_DECLS
+
+#endif /* GEDIT_SORT_PLUGIN_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/sort/meson.build b/plugins/sort/meson.build
new file mode 100644
index 0000000..7f91440
--- /dev/null
+++ b/plugins/sort/meson.build
@@ -0,0 +1,34 @@
+libsort_sources = files(
+ 'gedit-sort-plugin.c',
+)
+
+libsort_deps = [
+ libgedit_dep,
+]
+
+subdir('resources')
+
+libsort_sha = shared_module(
+ 'sort',
+ sources: libsort_sources,
+ include_directories: root_include_dir,
+ dependencies: libsort_deps,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ ),
+ name_suffix: module_suffix,
+)
+
+custom_target(
+ 'sort.plugin',
+ input: 'sort.plugin.desktop.in',
+ output: 'sort.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
diff --git a/plugins/sort/resources/gedit-sort.gresource.xml b/plugins/sort/resources/gedit-sort.gresource.xml
new file mode 100644
index 0000000..3855c8f
--- /dev/null
+++ b/plugins/sort/resources/gedit-sort.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/gedit/plugins/sort">
+ <file preprocess="xml-stripblanks">ui/gedit-sort-plugin.ui</file>
+ </gresource>
+</gresources>
diff --git a/plugins/sort/resources/meson.build b/plugins/sort/resources/meson.build
new file mode 100644
index 0000000..7178761
--- /dev/null
+++ b/plugins/sort/resources/meson.build
@@ -0,0 +1,8 @@
+libsort_res = gnome.compile_resources(
+ 'gedit-sort-resources',
+ 'gedit-sort.gresource.xml',
+)
+
+libsort_sources += [
+ libsort_res.get(0),
+]
diff --git a/plugins/sort/resources/ui/gedit-sort-plugin.ui b/plugins/sort/resources/ui/gedit-sort-plugin.ui
new file mode 100644
index 0000000..95a0e17
--- /dev/null
+++ b/plugins/sort/resources/ui/gedit-sort-plugin.ui
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 2.12 -->
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">1</property>
+ <property name="upper">100</property>
+ <property name="value">1</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkDialog" id="sort_dialog">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Sort</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="use-header-bar">1</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">10</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="reverse_order_checkbutton">
+ <property name="label" translatable="yes">_Reverse order</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="remove_dups_checkbutton">
+ <property name="label" translatable="yes">R_emove duplicates</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="case_checkbutton">
+ <property name="label" translatable="yes">C_ase sensitive</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">S_tart at column:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">col_num_spinbutton</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="col_num_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="climb_rate">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="cancel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="sort">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Sort</property>
+ <property name="can-default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">cancel</action-widget>
+ <action-widget response="-5">sort</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/sort/sort.plugin.desktop.in b/plugins/sort/sort.plugin.desktop.in
new file mode 100644
index 0000000..f2e2c78
--- /dev/null
+++ b/plugins/sort/sort.plugin.desktop.in
@@ -0,0 +1,11 @@
+[Plugin]
+Module=sort
+IAge=3
+Name=Sort
+Description=Sorts a document or selected text.
+# TRANSLATORS: Do NOT translate or transliterate this text!
+# This is an icon file name.
+Icon=view-sort-ascending
+Authors=Carlo Borreo <borreo@softhome.net>;Lee Mallabone <gnome@fonicmonkey.net>;Paolo Maggi <paolo.maggi@polito.it>;Jorge Alberto Torres H. <jorge@deadoak.com>
+Copyright=Copyright © 2001 Carlo Borreo;Copyright © 2002-2003 Lee Mallabone, Paolo Maggi;Copyright © 2004-2005 Paolo Maggi
+Website=http://www.gedit.org
diff --git a/plugins/spell/gedit-spell-app-activatable.c b/plugins/spell/gedit-spell-app-activatable.c
new file mode 100644
index 0000000..c08b0cd
--- /dev/null
+++ b/plugins/spell/gedit-spell-app-activatable.c
@@ -0,0 +1,184 @@
+/*
+ * gedit-spell-app-activatable.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Ignacio Casal Quinteiro
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gedit-spell-app-activatable.h"
+#include <glib/gi18n.h>
+#include <libpeas/peas-object-module.h>
+#include <gedit/gedit-app-activatable.h>
+#include <gedit/gedit-app.h>
+
+typedef struct _GeditSpellAppActivatablePrivate GeditSpellAppActivatablePrivate;
+
+struct _GeditSpellAppActivatable
+{
+ GObject parent;
+};
+
+struct _GeditSpellAppActivatablePrivate
+{
+ GeditApp *app;
+ GeditMenuExtension *menu_ext;
+};
+
+enum
+{
+ PROP_0,
+ PROP_APP
+};
+
+static void gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditSpellAppActivatable,
+ gedit_spell_app_activatable,
+ G_TYPE_OBJECT,
+ 0,
+ G_ADD_PRIVATE_DYNAMIC (GeditSpellAppActivatable)
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_APP_ACTIVATABLE,
+ gedit_app_activatable_iface_init))
+
+static void
+gedit_spell_app_activatable_dispose (GObject *object)
+{
+ GeditSpellAppActivatable *activatable = GEDIT_SPELL_APP_ACTIVATABLE (object);
+ GeditSpellAppActivatablePrivate *priv = gedit_spell_app_activatable_get_instance_private (activatable);
+
+ g_clear_object (&priv->app);
+ g_clear_object (&priv->menu_ext);
+
+ G_OBJECT_CLASS (gedit_spell_app_activatable_parent_class)->dispose (object);
+}
+
+static void
+gedit_spell_app_activatable_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditSpellAppActivatable *activatable = GEDIT_SPELL_APP_ACTIVATABLE (object);
+ GeditSpellAppActivatablePrivate *priv = gedit_spell_app_activatable_get_instance_private (activatable);
+
+ switch (prop_id)
+ {
+ case PROP_APP:
+ priv->app = GEDIT_APP (g_value_dup_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_spell_app_activatable_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditSpellAppActivatable *activatable = GEDIT_SPELL_APP_ACTIVATABLE (object);
+ GeditSpellAppActivatablePrivate *priv = gedit_spell_app_activatable_get_instance_private (activatable);
+
+ switch (prop_id)
+ {
+ case PROP_APP:
+ g_value_set_object (value, priv->app);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_spell_app_activatable_class_init (GeditSpellAppActivatableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_spell_app_activatable_dispose;
+ object_class->set_property = gedit_spell_app_activatable_set_property;
+ object_class->get_property = gedit_spell_app_activatable_get_property;
+
+ g_object_class_override_property (object_class, PROP_APP, "app");
+}
+
+static void
+gedit_spell_app_activatable_class_finalize (GeditSpellAppActivatableClass *klass)
+{
+}
+
+static void
+gedit_spell_app_activatable_init (GeditSpellAppActivatable *self)
+{
+}
+
+static void
+gedit_spell_app_activatable_activate (GeditAppActivatable *activatable)
+{
+ GeditSpellAppActivatable *app_activatable = GEDIT_SPELL_APP_ACTIVATABLE (activatable);
+ GeditSpellAppActivatablePrivate *priv = gedit_spell_app_activatable_get_instance_private (app_activatable);
+ GMenuItem *item;
+
+ const gchar *accels[] = { "<Shift>F7", NULL };
+ gtk_application_set_accels_for_action (GTK_APPLICATION (priv->app),"win.check-spell", accels);
+ priv->menu_ext = gedit_app_activatable_extend_menu (activatable, "spell-section");
+
+ item = g_menu_item_new (_("_Check Spelling…"), "win.check-spell");
+ gedit_menu_extension_append_menu_item (priv->menu_ext, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("Set _Language…"), "win.config-spell");
+ gedit_menu_extension_append_menu_item (priv->menu_ext, item);
+ g_object_unref (item);
+
+ item = g_menu_item_new (_("_Highlight Misspelled Words"), "win.inline-spell-checker");
+ gedit_menu_extension_append_menu_item (priv->menu_ext, item);
+ g_object_unref (item);
+}
+
+static void
+gedit_spell_app_activatable_deactivate (GeditAppActivatable *activatable)
+{
+ GeditSpellAppActivatable *app_activatable = GEDIT_SPELL_APP_ACTIVATABLE (activatable);
+ GeditSpellAppActivatablePrivate *priv = gedit_spell_app_activatable_get_instance_private (app_activatable);
+
+ const gchar *accels[] = { NULL };
+ gtk_application_set_accels_for_action (GTK_APPLICATION (priv->app),"win.check-spell", accels);
+ g_clear_object (&priv->menu_ext);
+}
+
+static void
+gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface)
+{
+ iface->activate = gedit_spell_app_activatable_activate;
+ iface->deactivate = gedit_spell_app_activatable_deactivate;
+}
+
+void
+gedit_spell_app_activatable_register (GTypeModule *module)
+{
+ gedit_spell_app_activatable_register_type (module);
+
+ peas_object_module_register_extension_type (PEAS_OBJECT_MODULE (module),
+ GEDIT_TYPE_APP_ACTIVATABLE,
+ GEDIT_TYPE_SPELL_APP_ACTIVATABLE);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/spell/gedit-spell-app-activatable.h b/plugins/spell/gedit-spell-app-activatable.h
new file mode 100644
index 0000000..aebb5ca
--- /dev/null
+++ b/plugins/spell/gedit-spell-app-activatable.h
@@ -0,0 +1,38 @@
+/*
+ * gedit-spell-app-activatable.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2014 - Ignacio Casal Quinteiro
+ *
+ * gedit is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gedit is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gedit. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef GEDIT_SPELL_APP_ACTIVATABLE_H
+#define GEDIT_SPELL_APP_ACTIVATABLE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_SPELL_APP_ACTIVATABLE (gedit_spell_app_activatable_get_type ())
+G_DECLARE_FINAL_TYPE (GeditSpellAppActivatable, gedit_spell_app_activatable,
+ GEDIT, SPELL_APP_ACTIVATABLE,
+ GObject)
+
+void gedit_spell_app_activatable_register (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* GEDIT_SPELL_APP_ACTIVATABLE_H */
diff --git a/plugins/spell/gedit-spell-plugin.c b/plugins/spell/gedit-spell-plugin.c
new file mode 100644
index 0000000..6116778
--- /dev/null
+++ b/plugins/spell/gedit-spell-plugin.c
@@ -0,0 +1,810 @@
+/*
+ * gedit-spell-plugin.c
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ * Copyright (C) 2015-2016 Sébastien Wilmet
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gedit-spell-plugin.h"
+
+#include <glib/gi18n.h>
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-app.h>
+#include <gedit/gedit-window.h>
+#include <gedit/gedit-window-activatable.h>
+#include <gspell/gspell.h>
+#include <libpeas-gtk/peas-gtk-configurable.h>
+
+#include "gedit-spell-app-activatable.h"
+
+#define GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE "gedit-spell-language"
+#define GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED "gedit-spell-enabled"
+
+#define SPELL_ENABLED_STR "1"
+#define SPELL_BASE_SETTINGS "org.gnome.gedit.plugins.spell"
+#define SETTINGS_KEY_HIGHLIGHT_MISSPELLED "highlight-misspelled"
+
+static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface);
+static void peas_gtk_configurable_iface_init (PeasGtkConfigurableInterface *iface);
+
+struct _GeditSpellPluginPrivate
+{
+ GeditWindow *window;
+ GSettings *settings;
+};
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW
+};
+
+typedef struct _SpellConfigureWidget SpellConfigureWidget;
+
+struct _SpellConfigureWidget
+{
+ GtkWidget *content;
+ GtkWidget *highlight_button;
+
+ GSettings *settings;
+};
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditSpellPlugin,
+ gedit_spell_plugin,
+ PEAS_TYPE_EXTENSION_BASE,
+ 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE,
+ gedit_window_activatable_iface_init)
+ G_IMPLEMENT_INTERFACE_DYNAMIC (PEAS_GTK_TYPE_CONFIGURABLE,
+ peas_gtk_configurable_iface_init)
+ G_ADD_PRIVATE_DYNAMIC (GeditSpellPlugin))
+
+static void
+gedit_spell_plugin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_spell_plugin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value, plugin->priv->window);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_spell_plugin_dispose (GObject *object)
+{
+ GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (object);
+
+ gedit_debug_message (DEBUG_PLUGINS, "GeditSpellPlugin disposing");
+
+ g_clear_object (&plugin->priv->window);
+ g_clear_object (&plugin->priv->settings);
+
+ G_OBJECT_CLASS (gedit_spell_plugin_parent_class)->dispose (object);
+}
+
+static void
+gedit_spell_plugin_class_init (GeditSpellPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gedit_spell_plugin_set_property;
+ object_class->get_property = gedit_spell_plugin_get_property;
+ object_class->dispose = gedit_spell_plugin_dispose;
+
+ g_object_class_override_property (object_class, PROP_WINDOW, "window");
+}
+
+static void
+gedit_spell_plugin_class_finalize (GeditSpellPluginClass *klass)
+{
+}
+
+static void
+gedit_spell_plugin_init (GeditSpellPlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditSpellPlugin initializing");
+
+ plugin->priv = gedit_spell_plugin_get_instance_private (plugin);
+ plugin->priv->settings = g_settings_new (SPELL_BASE_SETTINGS);
+}
+
+static GspellChecker *
+get_spell_checker (GeditDocument *doc)
+{
+ GspellTextBuffer *gspell_buffer;
+
+ gspell_buffer = gspell_text_buffer_get_from_gtk_text_buffer (GTK_TEXT_BUFFER (doc));
+ return gspell_text_buffer_get_spell_checker (gspell_buffer);
+}
+
+static const GspellLanguage *
+get_language_from_metadata (GeditDocument *doc)
+{
+ const GspellLanguage *lang = NULL;
+ gchar *language_code = NULL;
+
+ language_code = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE);
+
+ if (language_code != NULL)
+ {
+ lang = gspell_language_lookup (language_code);
+ g_free (language_code);
+ }
+
+ return lang;
+}
+
+static void
+check_spell_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (data);
+ GeditSpellPluginPrivate *priv;
+ GeditView *view;
+ GspellNavigator *navigator;
+ GtkWidget *dialog;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ view = gedit_window_get_active_view (priv->window);
+ g_return_if_fail (view != NULL);
+
+ navigator = gspell_navigator_text_view_new (GTK_TEXT_VIEW (view));
+ dialog = gspell_checker_dialog_new (GTK_WINDOW (priv->window), navigator);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+language_dialog_response_cb (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ if (response_id == GTK_RESPONSE_HELP)
+ {
+ gedit_app_show_help (GEDIT_APP (g_application_get_default ()),
+ GTK_WINDOW (dialog),
+ NULL,
+ "gedit-spellcheck");
+ return;
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+set_language_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (data);
+ GeditSpellPluginPrivate *priv;
+ GeditDocument *doc;
+ GspellChecker *checker;
+ const GspellLanguage *lang;
+ GtkWidget *dialog;
+ GtkWindowGroup *window_group;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ doc = gedit_window_get_active_document (priv->window);
+ g_return_if_fail (doc != NULL);
+
+ checker = get_spell_checker (doc);
+ g_return_if_fail (checker != NULL);
+
+ lang = gspell_checker_get_language (checker);
+
+ dialog = gspell_language_chooser_dialog_new (GTK_WINDOW (priv->window),
+ lang,
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT);
+
+ g_object_bind_property (dialog, "language",
+ checker, "language",
+ G_BINDING_DEFAULT);
+
+ window_group = gedit_window_get_group (priv->window);
+
+ gtk_window_group_add_window (window_group, GTK_WINDOW (dialog));
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ _("_Help"),
+ GTK_RESPONSE_HELP);
+
+ g_signal_connect (dialog,
+ "response",
+ G_CALLBACK (language_dialog_response_cb),
+ NULL);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+inline_checker_activate_cb (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (data);
+ GeditSpellPluginPrivate *priv = plugin->priv;
+ GVariant *state;
+ gboolean active;
+ GeditView *view;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ state = g_action_get_state (G_ACTION (action));
+ g_return_if_fail (state != NULL);
+
+ active = g_variant_get_boolean (state);
+ g_variant_unref (state);
+
+ /* We must toggle ourself the value. */
+ active = !active;
+ g_action_change_state (G_ACTION (action), g_variant_new_boolean (active));
+
+ view = gedit_window_get_active_view (priv->window);
+ if (view != NULL)
+ {
+ GeditDocument *doc;
+
+ doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+
+ /* Set metadata in the "activate" handler, not in "change-state"
+ * because "change-state" is called every time the state
+ * changes, not specifically when the user has changed the state
+ * herself. For example "change-state" is called to initialize
+ * the sate to the default value specified in the GActionEntry.
+ */
+ gedit_document_set_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED,
+ active ? SPELL_ENABLED_STR : NULL,
+ NULL);
+ }
+}
+
+static void
+inline_checker_change_state_cb (GSimpleAction *action,
+ GVariant *state,
+ gpointer data)
+{
+ GeditSpellPlugin *plugin = GEDIT_SPELL_PLUGIN (data);
+ GeditSpellPluginPrivate *priv = plugin->priv;
+ GeditView *view;
+ gboolean active;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ active = g_variant_get_boolean (state);
+
+ gedit_debug_message (DEBUG_PLUGINS, active ? "Inline Checker activated" : "Inline Checker deactivated");
+
+ view = gedit_window_get_active_view (priv->window);
+ if (view != NULL)
+ {
+ GspellTextView *gspell_view;
+
+ gspell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (view));
+ gspell_text_view_set_inline_spell_checking (gspell_view, active);
+
+ g_simple_action_set_state (action, g_variant_new_boolean (active));
+ }
+}
+
+static void
+update_ui (GeditSpellPlugin *plugin)
+{
+ GeditSpellPluginPrivate *priv;
+ GeditTab *tab;
+ GeditView *view = NULL;
+ gboolean editable_view;
+ GAction *check_spell_action;
+ GAction *config_spell_action;
+ GAction *inline_checker_action;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ tab = gedit_window_get_active_tab (priv->window);
+ if (tab != NULL)
+ {
+ view = gedit_tab_get_view (tab);
+ }
+
+ editable_view = (view != NULL) && gtk_text_view_get_editable (GTK_TEXT_VIEW (view));
+
+ check_spell_action = g_action_map_lookup_action (G_ACTION_MAP (priv->window),
+ "check-spell");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (check_spell_action),
+ editable_view);
+
+ config_spell_action = g_action_map_lookup_action (G_ACTION_MAP (priv->window),
+ "config-spell");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (config_spell_action),
+ editable_view);
+
+ inline_checker_action = g_action_map_lookup_action (G_ACTION_MAP (priv->window),
+ "inline-spell-checker");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (inline_checker_action),
+ editable_view);
+
+ /* Update only on normal state to avoid garbage changes during e.g. file
+ * loading.
+ */
+ if (tab != NULL &&
+ gedit_tab_get_state (tab) == GEDIT_TAB_STATE_NORMAL)
+ {
+ GspellTextView *gspell_view;
+ gboolean inline_checking_enabled;
+
+ gspell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (view));
+ inline_checking_enabled = gspell_text_view_get_inline_spell_checking (gspell_view);
+
+ g_action_change_state (inline_checker_action,
+ g_variant_new_boolean (inline_checking_enabled));
+ }
+}
+
+static void
+setup_inline_checker_from_metadata (GeditSpellPlugin *plugin,
+ GeditView *view)
+{
+ GeditDocument *doc;
+ gboolean enabled;
+ gchar *enabled_str;
+ GspellTextView *gspell_view;
+ GeditView *active_view;
+
+ doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+
+ enabled = g_settings_get_boolean(plugin->priv->settings, SETTINGS_KEY_HIGHLIGHT_MISSPELLED);
+ enabled_str = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED);
+ if (enabled_str != NULL)
+ {
+ enabled = g_str_equal (enabled_str, SPELL_ENABLED_STR);
+ g_free (enabled_str);
+ }
+
+ gspell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (view));
+ gspell_text_view_set_inline_spell_checking (gspell_view, enabled);
+
+ /* In case that the view is the active one we mark the spell action */
+ active_view = gedit_window_get_active_view (plugin->priv->window);
+
+ if (active_view == view)
+ {
+ GAction *action;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (plugin->priv->window),
+ "inline-spell-checker");
+ g_action_change_state (action, g_variant_new_boolean (enabled));
+ }
+}
+
+static void
+language_notify_cb (GspellChecker *checker,
+ GParamSpec *pspec,
+ GeditDocument *doc)
+{
+ const GspellLanguage *lang;
+ const gchar *language_code;
+
+ g_return_if_fail (GEDIT_IS_DOCUMENT (doc));
+
+ lang = gspell_checker_get_language (checker);
+ g_return_if_fail (lang != NULL);
+
+ language_code = gspell_language_get_code (lang);
+ g_return_if_fail (language_code != NULL);
+
+ gedit_document_set_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE, language_code,
+ NULL);
+}
+
+static void
+on_document_loaded (GeditDocument *doc,
+ GeditSpellPlugin *plugin)
+{
+ GspellChecker *checker;
+ GeditTab *tab;
+ GeditView *view;
+
+ checker = get_spell_checker (doc);
+
+ if (checker != NULL)
+ {
+ const GspellLanguage *lang;
+
+ lang = get_language_from_metadata (doc);
+
+ if (lang != NULL)
+ {
+ g_signal_handlers_block_by_func (checker, language_notify_cb, doc);
+ gspell_checker_set_language (checker, lang);
+ g_signal_handlers_unblock_by_func (checker, language_notify_cb, doc);
+ }
+ }
+
+ tab = gedit_tab_get_from_document (doc);
+ view = gedit_tab_get_view (tab);
+ setup_inline_checker_from_metadata (plugin, view);
+}
+
+static void
+on_document_saved (GeditDocument *doc,
+ gpointer user_data)
+{
+ GeditTab *tab;
+ GeditView *view;
+ GspellChecker *checker;
+ const gchar *language_code = NULL;
+ GspellTextView *gspell_view;
+ gboolean inline_checking_enabled;
+
+ /* Make sure to save the metadata here too */
+
+ checker = get_spell_checker (doc);
+
+ if (checker != NULL)
+ {
+ const GspellLanguage *lang;
+
+ lang = gspell_checker_get_language (checker);
+ if (lang != NULL)
+ {
+ language_code = gspell_language_get_code (lang);
+ }
+ }
+
+ tab = gedit_tab_get_from_document (doc);
+ view = gedit_tab_get_view (tab);
+
+ gspell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (view));
+ inline_checking_enabled = gspell_text_view_get_inline_spell_checking (gspell_view);
+
+ gedit_document_set_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED,
+ inline_checking_enabled ? SPELL_ENABLED_STR : NULL,
+ GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE,
+ language_code,
+ NULL);
+}
+
+static void
+activate_spell_checking_in_view (GeditSpellPlugin *plugin,
+ GeditView *view)
+{
+ GeditDocument *doc;
+
+ doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+
+ /* It is possible that a GspellChecker has already been set, for example
+ * if a GeditTab has moved to another window.
+ */
+ if (get_spell_checker (doc) == NULL)
+ {
+ const GspellLanguage *lang;
+ GspellChecker *checker;
+ GspellTextBuffer *gspell_buffer;
+
+ lang = get_language_from_metadata (doc);
+ checker = gspell_checker_new (lang);
+
+ g_signal_connect_object (checker,
+ "notify::language",
+ G_CALLBACK (language_notify_cb),
+ doc,
+ 0);
+
+ gspell_buffer = gspell_text_buffer_get_from_gtk_text_buffer (GTK_TEXT_BUFFER (doc));
+ gspell_text_buffer_set_spell_checker (gspell_buffer, checker);
+ g_object_unref (checker);
+
+ setup_inline_checker_from_metadata (plugin, view);
+ }
+
+ g_signal_connect_object (doc,
+ "loaded",
+ G_CALLBACK (on_document_loaded),
+ plugin,
+ 0);
+
+ g_signal_connect_object (doc,
+ "saved",
+ G_CALLBACK (on_document_saved),
+ plugin,
+ 0);
+}
+
+static void
+disconnect_view (GeditSpellPlugin *plugin,
+ GeditView *view)
+{
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ /* It should still be the same buffer as the one where the signal
+ * handlers were connected. If not, we assume that the old buffer is
+ * finalized. And it is anyway safe to call
+ * g_signal_handlers_disconnect_by_func() if no signal handlers are
+ * found.
+ */
+ g_signal_handlers_disconnect_by_func (buffer, on_document_loaded, plugin);
+ g_signal_handlers_disconnect_by_func (buffer, on_document_saved, plugin);
+}
+
+static void
+deactivate_spell_checking_in_view (GeditSpellPlugin *plugin,
+ GeditView *view)
+{
+ GtkTextBuffer *gtk_buffer;
+ GspellTextBuffer *gspell_buffer;
+ GspellTextView *gspell_view;
+
+ disconnect_view (plugin, view);
+
+ gtk_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ gspell_buffer = gspell_text_buffer_get_from_gtk_text_buffer (gtk_buffer);
+ gspell_text_buffer_set_spell_checker (gspell_buffer, NULL);
+
+ gspell_view = gspell_text_view_get_from_gtk_text_view (GTK_TEXT_VIEW (view));
+ gspell_text_view_set_inline_spell_checking (gspell_view, FALSE);
+}
+
+static void
+tab_added_cb (GeditWindow *window,
+ GeditTab *tab,
+ GeditSpellPlugin *plugin)
+{
+ activate_spell_checking_in_view (plugin, gedit_tab_get_view (tab));
+}
+
+static void
+tab_removed_cb (GeditWindow *window,
+ GeditTab *tab,
+ GeditSpellPlugin *plugin)
+{
+ /* Don't deactivate completely the spell checking in @tab, since the tab
+ * can be moved to another window and we don't want to loose the spell
+ * checking settings (they are not saved in metadata for unsaved
+ * documents).
+ */
+ disconnect_view (plugin, gedit_tab_get_view (tab));
+}
+
+static void
+gedit_spell_plugin_activate (GeditWindowActivatable *activatable)
+{
+ GeditSpellPlugin *plugin;
+ GeditSpellPluginPrivate *priv;
+ GList *views;
+ GList *l;
+
+ const GActionEntry action_entries[] =
+ {
+ { "check-spell", check_spell_cb },
+ { "config-spell", set_language_cb },
+ { "inline-spell-checker",
+ inline_checker_activate_cb,
+ NULL,
+ "false",
+ inline_checker_change_state_cb }
+ };
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ plugin = GEDIT_SPELL_PLUGIN (activatable);
+ priv = plugin->priv;
+
+ g_action_map_add_action_entries (G_ACTION_MAP (priv->window),
+ action_entries,
+ G_N_ELEMENTS (action_entries),
+ activatable);
+
+ update_ui (plugin);
+
+ views = gedit_window_get_views (priv->window);
+ for (l = views; l != NULL; l = l->next)
+ {
+ activate_spell_checking_in_view (plugin, GEDIT_VIEW (l->data));
+ }
+
+ g_signal_connect (priv->window,
+ "tab-added",
+ G_CALLBACK (tab_added_cb),
+ activatable);
+
+ g_signal_connect (priv->window,
+ "tab-removed",
+ G_CALLBACK (tab_removed_cb),
+ activatable);
+}
+
+static void
+gedit_spell_plugin_deactivate (GeditWindowActivatable *activatable)
+{
+ GeditSpellPlugin *plugin;
+ GeditSpellPluginPrivate *priv;
+ GList *views;
+ GList *l;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ plugin = GEDIT_SPELL_PLUGIN (activatable);
+ priv = plugin->priv;
+
+ g_action_map_remove_action (G_ACTION_MAP (priv->window), "check-spell");
+ g_action_map_remove_action (G_ACTION_MAP (priv->window), "config-spell");
+ g_action_map_remove_action (G_ACTION_MAP (priv->window), "inline-spell-checker");
+
+ g_signal_handlers_disconnect_by_func (priv->window, tab_added_cb, activatable);
+ g_signal_handlers_disconnect_by_func (priv->window, tab_removed_cb, activatable);
+
+ views = gedit_window_get_views (priv->window);
+ for (l = views; l != NULL; l = l->next)
+ {
+ deactivate_spell_checking_in_view (plugin, GEDIT_VIEW (l->data));
+ }
+}
+
+static void
+gedit_spell_plugin_update_state (GeditWindowActivatable *activatable)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ update_ui (GEDIT_SPELL_PLUGIN (activatable));
+}
+
+static void
+gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface)
+{
+ iface->activate = gedit_spell_plugin_activate;
+ iface->deactivate = gedit_spell_plugin_deactivate;
+ iface->update_state = gedit_spell_plugin_update_state;
+}
+
+G_MODULE_EXPORT void
+peas_register_types (PeasObjectModule *module)
+{
+ gedit_spell_plugin_register_type (G_TYPE_MODULE (module));
+ gedit_spell_app_activatable_register (G_TYPE_MODULE (module));
+
+ peas_object_module_register_extension_type (module,
+ GEDIT_TYPE_WINDOW_ACTIVATABLE,
+ GEDIT_TYPE_SPELL_PLUGIN);
+ peas_object_module_register_extension_type (module,
+ PEAS_GTK_TYPE_CONFIGURABLE,
+ GEDIT_TYPE_SPELL_PLUGIN);
+}
+
+static void
+highlight_button_toggled (GtkToggleButton *button,
+ SpellConfigureWidget *widget)
+{
+ gboolean status = gtk_toggle_button_get_active (button);
+ g_settings_set_boolean(widget->settings, SETTINGS_KEY_HIGHLIGHT_MISSPELLED, status);
+}
+
+static void
+configure_widget_destroyed (GtkWidget *widget,
+ gpointer data)
+{
+ SpellConfigureWidget *conf_widget = (SpellConfigureWidget *)data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_object_unref (conf_widget->settings);
+ g_slice_free (SpellConfigureWidget, data);
+
+ gedit_debug_message (DEBUG_PLUGINS, "END");
+}
+
+static SpellConfigureWidget *
+get_configure_widget (GeditSpellPlugin *plugin)
+{
+ SpellConfigureWidget *widget;
+ GtkBuilder *builder;
+ gchar *root_objects[] = {
+ "spell_dialog_content",
+ NULL
+ };
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ widget = g_slice_new (SpellConfigureWidget);
+ widget->settings = g_object_ref (plugin->priv->settings);
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_objects_from_resource (builder, "/org/gnome/gedit/plugins/spell/ui/gedit-spell-setup-dialog.ui",
+ root_objects, NULL);
+ widget->content = GTK_WIDGET (gtk_builder_get_object (builder, "spell_dialog_content"));
+ g_object_ref (widget->content);
+
+ widget->highlight_button = GTK_WIDGET (gtk_builder_get_object (builder, "highlight_button"));
+ g_object_unref (builder);
+
+ gboolean status = g_settings_get_boolean(widget->settings, SETTINGS_KEY_HIGHLIGHT_MISSPELLED);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget->highlight_button), status);
+
+ g_signal_connect (widget->highlight_button,
+ "toggled",
+ G_CALLBACK (highlight_button_toggled),
+ widget);
+
+ g_signal_connect (widget->content,
+ "destroy",
+ G_CALLBACK (configure_widget_destroyed),
+ widget);
+
+ return widget;
+}
+
+
+static GtkWidget *
+gedit_spell_plugin_create_configure_widget (PeasGtkConfigurable *configurable)
+{
+ SpellConfigureWidget *widget;
+
+ widget = get_configure_widget (GEDIT_SPELL_PLUGIN (configurable));
+
+ return widget->content;
+}
+
+static void
+peas_gtk_configurable_iface_init (PeasGtkConfigurableInterface *iface)
+{
+ iface->create_configure_widget = gedit_spell_plugin_create_configure_widget;
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/spell/gedit-spell-plugin.h b/plugins/spell/gedit-spell-plugin.h
new file mode 100644
index 0000000..13539b4
--- /dev/null
+++ b/plugins/spell/gedit-spell-plugin.h
@@ -0,0 +1,62 @@
+/*
+ * gedit-spell-plugin.h
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_SPELL_PLUGIN_H
+#define GEDIT_SPELL_PLUGIN_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libpeas/peas-extension-base.h>
+#include <libpeas/peas-object-module.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_SPELL_PLUGIN (gedit_spell_plugin_get_type ())
+#define GEDIT_SPELL_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_SPELL_PLUGIN, GeditSpellPlugin))
+#define GEDIT_SPELL_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_SPELL_PLUGIN, GeditSpellPluginClass))
+#define GEDIT_IS_SPELL_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_SPELL_PLUGIN))
+#define GEDIT_IS_SPELL_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_SPELL_PLUGIN))
+#define GEDIT_SPELL_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_SPELL_PLUGIN, GeditSpellPluginClass))
+
+typedef struct _GeditSpellPlugin GeditSpellPlugin;
+typedef struct _GeditSpellPluginPrivate GeditSpellPluginPrivate;
+typedef struct _GeditSpellPluginClass GeditSpellPluginClass;
+
+struct _GeditSpellPlugin
+{
+ PeasExtensionBase parent_instance;
+
+ /*< private >*/
+ GeditSpellPluginPrivate *priv;
+};
+
+struct _GeditSpellPluginClass
+{
+ PeasExtensionBaseClass parent_class;
+};
+
+GType gedit_spell_plugin_get_type (void) G_GNUC_CONST;
+
+G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
+
+G_END_DECLS
+
+#endif /* GEDIT_SPELL_PLUGIN_H */
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/spell/meson.build b/plugins/spell/meson.build
new file mode 100644
index 0000000..8ce7634
--- /dev/null
+++ b/plugins/spell/meson.build
@@ -0,0 +1,54 @@
+libspell_sources = files(
+ 'gedit-spell-app-activatable.c',
+ 'gedit-spell-plugin.c',
+)
+
+libspell_deps = [
+ libgedit_dep,
+ gspell_dep,
+]
+
+subdir('resources')
+
+libspell_sha = shared_module(
+ 'spell',
+ sources: libspell_sources,
+ include_directories: root_include_dir,
+ dependencies: libspell_deps,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ ),
+ name_suffix: module_suffix,
+)
+
+spell_gschema_file = files('org.gnome.gedit.plugins.spell.gschema.xml')
+install_data(
+ spell_gschema_file,
+ install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas')
+)
+
+if xmllint.found()
+ test(
+ 'validate-spell-gschema',
+ xmllint,
+ args: [
+ '--noout',
+ '--dtdvalid', gschema_dtd,
+ spell_gschema_file,
+ ]
+ )
+endif
+
+custom_target(
+ 'spell.plugin',
+ input: 'spell.plugin.desktop.in',
+ output: 'spell.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
diff --git a/plugins/spell/org.gnome.gedit.plugins.spell.gschema.xml b/plugins/spell/org.gnome.gedit.plugins.spell.gschema.xml
new file mode 100644
index 0000000..c1dd186
--- /dev/null
+++ b/plugins/spell/org.gnome.gedit.plugins.spell.gschema.xml
@@ -0,0 +1,9 @@
+<schemalist gettext-domain="gedit">
+ <schema id="org.gnome.gedit.plugins.spell" path="/org/gnome/gedit/plugins/spell/">
+ <key name="highlight-misspelled" type="b">
+ <default>false</default>
+ <summary>Highlight misspelled words</summary>
+ <description>Default setting for highlight misspelled words.</description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/plugins/spell/resources/gedit-spell.gresource.xml b/plugins/spell/resources/gedit-spell.gresource.xml
new file mode 100644
index 0000000..861e061
--- /dev/null
+++ b/plugins/spell/resources/gedit-spell.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/gedit/plugins/spell">
+ <file preprocess="xml-stripblanks">ui/gedit-spell-setup-dialog.ui</file>
+ </gresource>
+</gresources>
diff --git a/plugins/spell/resources/meson.build b/plugins/spell/resources/meson.build
new file mode 100644
index 0000000..ad15d5e
--- /dev/null
+++ b/plugins/spell/resources/meson.build
@@ -0,0 +1,8 @@
+libspell_res = gnome.compile_resources(
+ 'gedit-spell-resources',
+ 'gedit-spell.gresource.xml',
+)
+
+libspell_sources += [
+ libspell_res.get(0),
+]
diff --git a/plugins/spell/resources/ui/gedit-spell-setup-dialog.ui b/plugins/spell/resources/ui/gedit-spell-setup-dialog.ui
new file mode 100644
index 0000000..5e922c2
--- /dev/null
+++ b/plugins/spell/resources/ui/gedit-spell-setup-dialog.ui
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="spell_dialog">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Configure spell plugin</property>
+ <property name="type_hint">normal</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label" translatable="yes">_Close</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <property name="always_show_image">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">10</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="spell_dialog_content">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">True</property>
+ <property name="border_width">10</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Defaults for new documents</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="highlight_button">
+ <property name="label" translatable="yes">Highlight misspelled words</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/plugins/spell/spell.plugin.desktop.in b/plugins/spell/spell.plugin.desktop.in
new file mode 100644
index 0000000..1741079
--- /dev/null
+++ b/plugins/spell/spell.plugin.desktop.in
@@ -0,0 +1,11 @@
+[Plugin]
+Module=spell
+IAge=3
+Name=Spell Checker
+Description=Checks the spelling of the current document.
+# TRANSLATORS: Do NOT translate or transliterate this text!
+# This is an icon file name.
+Icon=tools-check-spelling
+Authors=Paolo Maggi <paolo@gnome.org>;Sébastien Wilmet <swilmet@gnome.org>
+Copyright=Copyright © 2002-2005 Paolo Maggi;Copyright © 2015 Sébastien Wilmet
+Website=http://www.gedit.org
diff --git a/plugins/time/gedit-time-plugin.c b/plugins/time/gedit-time-plugin.c
new file mode 100644
index 0000000..c205ce8
--- /dev/null
+++ b/plugins/time/gedit-time-plugin.c
@@ -0,0 +1,1078 @@
+/*
+ * gedit-time-plugin.c
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <glib.h>
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-utils.h>
+#include <gedit/gedit-window.h>
+#include <gedit/gedit-app-activatable.h>
+#include <gedit/gedit-window-activatable.h>
+#include <libpeas-gtk/peas-gtk-configurable.h>
+#include <gedit/gedit-app.h>
+#include "gedit-time-plugin.h"
+
+/* gsettings keys */
+#define TIME_BASE_SETTINGS "org.gnome.gedit.plugins.time"
+#define PROMPT_TYPE_KEY "prompt-type"
+#define SELECTED_FORMAT_KEY "selected-format"
+#define CUSTOM_FORMAT_KEY "custom-format"
+
+#define DEFAULT_CUSTOM_FORMAT "%d/%m/%Y %H:%M:%S"
+
+static const gchar *formats[] =
+{
+ "%c",
+ "%x",
+ "%X",
+ "%x %X",
+ "%Y-%m-%d %H:%M:%S",
+ "%a %b %d %H:%M:%S %Z %Y",
+ "%a %b %d %H:%M:%S %Y",
+ "%a %d %b %Y %H:%M:%S %Z",
+ "%a %d %b %Y %H:%M:%S",
+ "%d/%m/%Y",
+ "%d/%m/%y",
+ "%A %d %B %Y",
+ "%A %B %d %Y",
+ "%Y-%m-%d",
+ "%d %B %Y",
+ "%B %d, %Y",
+ "%A %b %d",
+ "%H:%M:%S",
+ "%H:%M",
+ "%I:%M:%S %p",
+ "%I:%M %p",
+ "%H.%M.%S",
+ "%H.%M",
+ "%I.%M.%S %p",
+ "%I.%M %p",
+ "%d/%m/%Y %H:%M:%S",
+ "%d/%m/%y %H:%M:%S",
+ "%a, %d %b %Y %H:%M:%S %z",
+ NULL
+};
+
+enum
+{
+ COLUMN_FORMATS = 0,
+ COLUMN_INDEX,
+ NUM_COLUMNS
+};
+
+typedef enum
+{
+ PROMPT_SELECTED_FORMAT = 0, /* Popup dialog with list preselected */
+ PROMPT_CUSTOM_FORMAT, /* Popup dialog with entry preselected */
+ USE_SELECTED_FORMAT, /* Use selected format directly */
+ USE_CUSTOM_FORMAT /* Use custom format directly */
+} GeditTimePluginPromptType;
+
+typedef struct _TimeConfigureWidget TimeConfigureWidget;
+
+struct _TimeConfigureWidget
+{
+ GtkWidget *content;
+
+ GtkWidget *list;
+
+ /* Radio buttons to indicate what should be done */
+ GtkWidget *prompt;
+ GtkWidget *use_list;
+ GtkWidget *custom;
+
+ GtkWidget *custom_entry;
+ GtkWidget *custom_format_example;
+
+ GSettings *settings;
+};
+
+typedef struct _ChooseFormatDialog ChooseFormatDialog;
+
+struct _ChooseFormatDialog
+{
+ GtkWidget *dialog;
+
+ GtkWidget *list;
+
+ /* Radio buttons to indicate what should be done */
+ GtkWidget *use_list;
+ GtkWidget *custom;
+
+ GtkWidget *custom_entry;
+ GtkWidget *custom_format_example;
+
+ /* Info needed for the response handler */
+ GtkTextBuffer *buffer;
+
+ GSettings *settings;
+};
+
+struct _GeditTimePluginPrivate
+{
+ GSettings *settings;
+
+ GSimpleAction *action;
+ GeditWindow *window;
+
+ GeditApp *app;
+ GeditMenuExtension *menu_ext;
+};
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW,
+ PROP_APP
+};
+
+static void gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface);
+static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface);
+static void peas_gtk_configurable_iface_init (PeasGtkConfigurableInterface *iface);
+
+G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditTimePlugin,
+ gedit_time_plugin,
+ PEAS_TYPE_EXTENSION_BASE,
+ 0,
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_APP_ACTIVATABLE,
+ gedit_app_activatable_iface_init)
+ G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE,
+ gedit_window_activatable_iface_init)
+ G_IMPLEMENT_INTERFACE_DYNAMIC (PEAS_GTK_TYPE_CONFIGURABLE,
+ peas_gtk_configurable_iface_init)
+ G_ADD_PRIVATE_DYNAMIC (GeditTimePlugin))
+
+static void time_cb (GAction *action, GVariant *parameter, GeditTimePlugin *plugin);
+
+static void
+gedit_time_plugin_init (GeditTimePlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditTimePlugin initializing");
+
+ plugin->priv = gedit_time_plugin_get_instance_private (plugin);
+
+ plugin->priv->settings = g_settings_new (TIME_BASE_SETTINGS);
+}
+
+static void
+gedit_time_plugin_dispose (GObject *object)
+{
+ GeditTimePlugin *plugin = GEDIT_TIME_PLUGIN (object);
+
+ gedit_debug_message (DEBUG_PLUGINS, "GeditTimePlugin disposing");
+
+ g_clear_object (&plugin->priv->settings);
+ g_clear_object (&plugin->priv->action);
+ g_clear_object (&plugin->priv->window);
+ g_clear_object (&plugin->priv->menu_ext);
+ g_clear_object (&plugin->priv->app);
+
+ G_OBJECT_CLASS (gedit_time_plugin_parent_class)->dispose (object);
+}
+
+static void
+gedit_time_plugin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditTimePlugin *plugin = GEDIT_TIME_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value));
+ break;
+ case PROP_APP:
+ plugin->priv->app = GEDIT_APP (g_value_dup_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_time_plugin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditTimePlugin *plugin = GEDIT_TIME_PLUGIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value, plugin->priv->window);
+ break;
+ case PROP_APP:
+ g_value_set_object (value, plugin->priv->app);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+update_ui (GeditTimePlugin *plugin)
+{
+ GeditView *view;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ view = gedit_window_get_active_view (plugin->priv->window);
+
+ gedit_debug_message (DEBUG_PLUGINS, "View: %p", view);
+
+ g_simple_action_set_enabled (plugin->priv->action,
+ (view != NULL) &&
+ gtk_text_view_get_editable (GTK_TEXT_VIEW (view)));
+}
+
+static void
+gedit_time_plugin_app_activate (GeditAppActivatable *activatable)
+{
+ GeditTimePluginPrivate *priv;
+ GMenuItem *item;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_TIME_PLUGIN (activatable)->priv;
+
+ priv->menu_ext = gedit_app_activatable_extend_menu (activatable, "tools-section");
+ item = g_menu_item_new (_("In_sert Date and Time…"), "win.time");
+ gedit_menu_extension_append_menu_item (priv->menu_ext, item);
+ g_object_unref (item);
+}
+
+static void
+gedit_time_plugin_app_deactivate (GeditAppActivatable *activatable)
+{
+ GeditTimePluginPrivate *priv;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_TIME_PLUGIN (activatable)->priv;
+
+ g_clear_object (&priv->menu_ext);
+}
+
+static void
+gedit_time_plugin_window_activate (GeditWindowActivatable *activatable)
+{
+ GeditTimePluginPrivate *priv;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_TIME_PLUGIN (activatable)->priv;
+
+ priv->action = g_simple_action_new ("time", NULL);
+ g_signal_connect (priv->action, "activate",
+ G_CALLBACK (time_cb), activatable);
+ g_action_map_add_action (G_ACTION_MAP (priv->window),
+ G_ACTION (priv->action));
+
+ update_ui (GEDIT_TIME_PLUGIN (activatable));
+}
+
+static void
+gedit_time_plugin_window_deactivate (GeditWindowActivatable *activatable)
+{
+ GeditTimePluginPrivate *priv;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = GEDIT_TIME_PLUGIN (activatable)->priv;
+
+ g_action_map_remove_action (G_ACTION_MAP (priv->window), "time");
+}
+
+static void
+gedit_time_plugin_window_update_state (GeditWindowActivatable *activatable)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ update_ui (GEDIT_TIME_PLUGIN (activatable));
+}
+
+/* The selected format in the list */
+static gchar *
+get_selected_format (GeditTimePlugin *plugin)
+{
+ gchar *sel_format;
+
+ sel_format = g_settings_get_string (plugin->priv->settings,
+ SELECTED_FORMAT_KEY);
+
+ return sel_format ? sel_format : g_strdup (formats [0]);
+}
+
+/* the custom format in the entry */
+static gchar *
+get_custom_format (GeditTimePlugin *plugin)
+{
+ gchar *format;
+
+ format = g_settings_get_string (plugin->priv->settings,
+ CUSTOM_FORMAT_KEY);
+
+ return format ? format : g_strdup (DEFAULT_CUSTOM_FORMAT);
+}
+
+static gchar *
+get_time (const gchar *format)
+{
+ gchar *out;
+ GDateTime *now;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_val_if_fail (format != NULL, NULL);
+
+ if (*format == '\0')
+ return g_strdup (" ");
+
+ now = g_date_time_new_now_local ();
+ out = g_date_time_format (now, format);
+ g_date_time_unref (now);
+
+ return out;
+}
+
+static void
+configure_widget_destroyed (GtkWidget *widget,
+ gpointer data)
+{
+ TimeConfigureWidget *conf_widget = (TimeConfigureWidget *)data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_object_unref (conf_widget->settings);
+ g_slice_free (TimeConfigureWidget, data);
+
+ gedit_debug_message (DEBUG_PLUGINS, "END");
+}
+
+static void
+choose_format_dialog_destroyed (GtkWidget *widget,
+ gpointer dialog_pointer)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_slice_free (ChooseFormatDialog, dialog_pointer);
+
+ gedit_debug_message (DEBUG_PLUGINS, "END");
+}
+
+static GtkTreeModel *
+create_model (GtkWidget *listview,
+ const gchar *sel_format,
+ GeditTimePlugin *plugin)
+{
+ gint i = 0;
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ /* create list store */
+ store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT);
+
+ /* Set tree view model*/
+ gtk_tree_view_set_model (GTK_TREE_VIEW (listview),
+ GTK_TREE_MODEL (store));
+ g_object_unref (G_OBJECT (store));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (listview));
+ g_return_val_if_fail (selection != NULL, GTK_TREE_MODEL (store));
+
+ /* there should always be one line selected */
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+
+ /* add data to the list store */
+ while (formats[i] != NULL)
+ {
+ gchar *str;
+
+ str = get_time (formats[i]);
+
+ gedit_debug_message (DEBUG_PLUGINS, "%d : %s", i, str);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_FORMATS, str,
+ COLUMN_INDEX, i,
+ -1);
+ g_free (str);
+
+ if (sel_format && strcmp (formats[i], sel_format) == 0)
+ gtk_tree_selection_select_iter (selection, &iter);
+
+ ++i;
+ }
+
+ /* fall back to select the first iter */
+ if (!gtk_tree_selection_get_selected (selection, NULL, NULL) &&
+ gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
+ {
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+
+ return GTK_TREE_MODEL (store);
+}
+
+static void
+scroll_to_selected (GtkTreeView *tree_view)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ model = gtk_tree_view_get_model (tree_view);
+ g_return_if_fail (model != NULL);
+
+ /* Scroll to selected */
+ selection = gtk_tree_view_get_selection (tree_view);
+ g_return_if_fail (selection != NULL);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ GtkTreePath* path;
+
+ path = gtk_tree_model_get_path (model, &iter);
+ g_return_if_fail (path != NULL);
+
+ gtk_tree_view_scroll_to_cell (tree_view,
+ path, NULL, TRUE, 1.0, 0.0);
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+create_formats_list (GtkWidget *listview,
+ const gchar *sel_format,
+ GeditTimePlugin *plugin)
+{
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (listview != NULL);
+ g_return_if_fail (sel_format != NULL);
+
+ /* the Available formats column */
+ cell = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (
+ _("Available formats"),
+ cell,
+ "text", COLUMN_FORMATS,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (listview), column);
+
+ /* Create model, it also add model to the tree view */
+ create_model (listview, sel_format, plugin);
+
+ g_signal_connect (listview,
+ "realize",
+ G_CALLBACK (scroll_to_selected),
+ NULL);
+
+ gtk_widget_show (listview);
+}
+
+static void
+updated_custom_format_example (GtkEntry *format_entry,
+ GtkLabel *format_example)
+{
+ const gchar *format;
+ gchar *time;
+ gchar *str;
+ gchar *escaped_time;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (GTK_IS_ENTRY (format_entry));
+ g_return_if_fail (GTK_IS_LABEL (format_example));
+
+ format = gtk_entry_get_text (format_entry);
+
+ time = get_time (format);
+ escaped_time = g_markup_escape_text (time, -1);
+
+ str = g_strdup_printf ("<span size=\"small\">%s</span>", escaped_time);
+
+ gtk_label_set_markup (format_example, str);
+
+ g_free (escaped_time);
+ g_free (time);
+ g_free (str);
+}
+
+static void
+choose_format_dialog_button_toggled (GtkToggleButton *button,
+ ChooseFormatDialog *dialog)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->custom)))
+ {
+ gtk_widget_set_sensitive (dialog->list, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_entry, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, TRUE);
+ }
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->use_list)))
+ {
+ gtk_widget_set_sensitive (dialog->list, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_entry, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, FALSE);
+ }
+ else
+ {
+ g_return_if_reached ();
+ }
+}
+
+static void
+configure_widget_button_toggled (GtkToggleButton *button,
+ TimeConfigureWidget *conf_widget)
+{
+ GeditTimePluginPromptType prompt_type;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (conf_widget->custom)))
+ {
+ gtk_widget_set_sensitive (conf_widget->list, FALSE);
+ gtk_widget_set_sensitive (conf_widget->custom_entry, TRUE);
+ gtk_widget_set_sensitive (conf_widget->custom_format_example, TRUE);
+
+ prompt_type = USE_CUSTOM_FORMAT;
+ }
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (conf_widget->use_list)))
+ {
+ gtk_widget_set_sensitive (conf_widget->list, TRUE);
+ gtk_widget_set_sensitive (conf_widget->custom_entry, FALSE);
+ gtk_widget_set_sensitive (conf_widget->custom_format_example, FALSE);
+
+ prompt_type = USE_SELECTED_FORMAT;
+ }
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (conf_widget->prompt)))
+ {
+ gtk_widget_set_sensitive (conf_widget->list, FALSE);
+ gtk_widget_set_sensitive (conf_widget->custom_entry, FALSE);
+ gtk_widget_set_sensitive (conf_widget->custom_format_example, FALSE);
+
+ prompt_type = PROMPT_SELECTED_FORMAT;
+ }
+ else
+ {
+ g_return_if_reached ();
+ }
+
+ g_settings_set_enum (conf_widget->settings,
+ PROMPT_TYPE_KEY,
+ prompt_type);
+}
+
+static gint
+get_format_from_list (GtkWidget *listview)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (listview));
+ g_return_val_if_fail (model != NULL, 0);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (listview));
+ g_return_val_if_fail (selection != NULL, 0);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ gint selected_value;
+
+ gtk_tree_model_get (model, &iter, COLUMN_INDEX, &selected_value, -1);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Sel value: %d", selected_value);
+
+ return selected_value;
+ }
+
+ g_return_val_if_reached (0);
+}
+
+static void
+on_configure_widget_selection_changed (GtkTreeSelection *selection,
+ TimeConfigureWidget *conf_widget)
+{
+ gint sel_format;
+
+ sel_format = get_format_from_list (conf_widget->list);
+ g_settings_set_string (conf_widget->settings,
+ SELECTED_FORMAT_KEY,
+ formats[sel_format]);
+}
+
+static TimeConfigureWidget *
+get_configure_widget (GeditTimePlugin *plugin)
+{
+ TimeConfigureWidget *widget;
+ GtkTreeSelection *selection;
+ GtkWidget *viewport;
+ GeditTimePluginPromptType prompt_type;
+ gchar *sf;
+ GtkBuilder *builder;
+ gchar *root_objects[] = {
+ "time_dialog_content",
+ NULL
+ };
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ widget = g_slice_new (TimeConfigureWidget);
+ widget->settings = g_object_ref (plugin->priv->settings);
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_objects_from_resource (builder, "/org/gnome/gedit/plugins/time/ui/gedit-time-setup-dialog.ui",
+ root_objects, NULL);
+ widget->content = GTK_WIDGET (gtk_builder_get_object (builder, "time_dialog_content"));
+ g_object_ref (widget->content);
+ viewport = GTK_WIDGET (gtk_builder_get_object (builder, "formats_viewport"));
+ widget->list = GTK_WIDGET (gtk_builder_get_object (builder, "formats_tree"));
+ widget->prompt = GTK_WIDGET (gtk_builder_get_object (builder, "always_prompt"));
+ widget->use_list = GTK_WIDGET (gtk_builder_get_object (builder, "never_prompt"));
+ widget->custom = GTK_WIDGET (gtk_builder_get_object (builder, "use_custom"));
+ widget->custom_entry = GTK_WIDGET (gtk_builder_get_object (builder, "custom_entry"));
+ widget->custom_format_example = GTK_WIDGET (gtk_builder_get_object (builder, "custom_format_example"));
+ g_object_unref (builder);
+
+ sf = get_selected_format (plugin);
+ create_formats_list (widget->list, sf, plugin);
+ g_free (sf);
+
+ prompt_type = g_settings_get_enum (plugin->priv->settings,
+ PROMPT_TYPE_KEY);
+
+ g_settings_bind (widget->settings,
+ CUSTOM_FORMAT_KEY,
+ widget->custom_entry,
+ "text",
+ G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET);
+
+ if (prompt_type == USE_CUSTOM_FORMAT)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget->custom), TRUE);
+
+ gtk_widget_set_sensitive (widget->list, FALSE);
+ gtk_widget_set_sensitive (widget->custom_entry, TRUE);
+ gtk_widget_set_sensitive (widget->custom_format_example, TRUE);
+ }
+ else if (prompt_type == USE_SELECTED_FORMAT)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget->use_list), TRUE);
+
+ gtk_widget_set_sensitive (widget->list, TRUE);
+ gtk_widget_set_sensitive (widget->custom_entry, FALSE);
+ gtk_widget_set_sensitive (widget->custom_format_example, FALSE);
+ }
+ else
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget->prompt), TRUE);
+
+ gtk_widget_set_sensitive (widget->list, FALSE);
+ gtk_widget_set_sensitive (widget->custom_entry, FALSE);
+ gtk_widget_set_sensitive (widget->custom_format_example, FALSE);
+ }
+
+ updated_custom_format_example (GTK_ENTRY (widget->custom_entry),
+ GTK_LABEL (widget->custom_format_example));
+
+ /* setup a window of a sane size. */
+ gtk_widget_set_size_request (GTK_WIDGET (viewport), 10, 200);
+
+ g_signal_connect (widget->custom,
+ "toggled",
+ G_CALLBACK (configure_widget_button_toggled),
+ widget);
+ g_signal_connect (widget->prompt,
+ "toggled",
+ G_CALLBACK (configure_widget_button_toggled),
+ widget);
+ g_signal_connect (widget->use_list,
+ "toggled",
+ G_CALLBACK (configure_widget_button_toggled),
+ widget);
+ g_signal_connect (widget->content,
+ "destroy",
+ G_CALLBACK (configure_widget_destroyed),
+ widget);
+ g_signal_connect (widget->custom_entry,
+ "changed",
+ G_CALLBACK (updated_custom_format_example),
+ widget->custom_format_example);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->list));
+
+ g_signal_connect (selection,
+ "changed",
+ G_CALLBACK (on_configure_widget_selection_changed),
+ widget);
+
+ return widget;
+}
+
+static void
+real_insert_time (GtkTextBuffer *buffer,
+ const gchar *the_time)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "Insert: %s", the_time);
+
+ gtk_text_buffer_begin_user_action (buffer);
+
+ gtk_text_buffer_insert_at_cursor (buffer, the_time, -1);
+ gtk_text_buffer_insert_at_cursor (buffer, " ", -1);
+
+ gtk_text_buffer_end_user_action (buffer);
+}
+
+static void
+choose_format_dialog_row_activated (GtkTreeView *list,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ ChooseFormatDialog *dialog)
+{
+ gint sel_format;
+ gchar *the_time;
+
+ sel_format = get_format_from_list (dialog->list);
+ the_time = get_time (formats[sel_format]);
+
+ g_settings_set_enum (dialog->settings,
+ PROMPT_TYPE_KEY,
+ PROMPT_SELECTED_FORMAT);
+ g_settings_set_string (dialog->settings,
+ SELECTED_FORMAT_KEY,
+ formats[sel_format]);
+
+ g_return_if_fail (the_time != NULL);
+
+ real_insert_time (dialog->buffer, the_time);
+
+ g_free (the_time);
+}
+
+static ChooseFormatDialog *
+get_choose_format_dialog (GtkWindow *parent,
+ GeditTimePluginPromptType prompt_type,
+ GeditTimePlugin *plugin)
+{
+ ChooseFormatDialog *dialog;
+ GtkBuilder *builder;
+ gchar *sf, *cf;
+ GtkWindowGroup *wg = NULL;
+
+ if (parent != NULL)
+ wg = gtk_window_get_group (parent);
+
+ dialog = g_slice_new (ChooseFormatDialog);
+ dialog->settings = plugin->priv->settings;
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_resource (builder, "/org/gnome/gedit/plugins/time/ui/gedit-time-dialog.ui",
+ NULL);
+ dialog->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "choose_format_dialog"));
+ dialog->list = GTK_WIDGET (gtk_builder_get_object (builder, "choice_list"));
+ dialog->use_list = GTK_WIDGET (gtk_builder_get_object (builder, "use_sel_format_radiobutton"));
+ dialog->custom = GTK_WIDGET (gtk_builder_get_object (builder, "use_custom_radiobutton"));
+ dialog->custom_entry = GTK_WIDGET (gtk_builder_get_object (builder, "custom_entry"));
+ dialog->custom_format_example = GTK_WIDGET (gtk_builder_get_object (builder, "custom_format_example"));
+ g_object_unref (builder);
+
+ gtk_window_group_add_window (wg,
+ GTK_WINDOW (dialog->dialog));
+ gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog), parent);
+ gtk_window_set_modal (GTK_WINDOW (dialog->dialog), TRUE);
+
+ sf = get_selected_format (plugin);
+ create_formats_list (dialog->list, sf, plugin);
+ g_free (sf);
+
+ cf = get_custom_format (plugin);
+ gtk_entry_set_text (GTK_ENTRY(dialog->custom_entry), cf);
+ g_free (cf);
+
+ updated_custom_format_example (GTK_ENTRY (dialog->custom_entry),
+ GTK_LABEL (dialog->custom_format_example));
+
+ if (prompt_type == PROMPT_CUSTOM_FORMAT)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->custom), TRUE);
+
+ gtk_widget_set_sensitive (dialog->list, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_entry, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, TRUE);
+ }
+ else if (prompt_type == PROMPT_SELECTED_FORMAT)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->use_list), TRUE);
+
+ gtk_widget_set_sensitive (dialog->list, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_entry, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, FALSE);
+ }
+ else
+ {
+ g_return_val_if_reached (NULL);
+ }
+
+ /* setup a window of a sane size. */
+ gtk_widget_set_size_request (dialog->list, 10, 200);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog->dialog),
+ GTK_RESPONSE_OK);
+
+ g_signal_connect (dialog->custom,
+ "toggled",
+ G_CALLBACK (choose_format_dialog_button_toggled),
+ dialog);
+ g_signal_connect (dialog->use_list,
+ "toggled",
+ G_CALLBACK (choose_format_dialog_button_toggled),
+ dialog);
+ g_signal_connect (dialog->dialog,
+ "destroy",
+ G_CALLBACK (choose_format_dialog_destroyed),
+ dialog);
+ g_signal_connect (dialog->custom_entry,
+ "changed",
+ G_CALLBACK (updated_custom_format_example),
+ dialog->custom_format_example);
+ g_signal_connect (dialog->list,
+ "row_activated",
+ G_CALLBACK (choose_format_dialog_row_activated),
+ dialog);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog->dialog), FALSE);
+
+ return dialog;
+}
+
+static void
+choose_format_dialog_response_cb (GtkWidget *widget,
+ gint response,
+ ChooseFormatDialog *dialog)
+{
+ switch (response)
+ {
+ case GTK_RESPONSE_HELP:
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_HELP");
+ gedit_app_show_help (GEDIT_APP (g_application_get_default ()),
+ GTK_WINDOW (widget),
+ NULL,
+ "gedit-plugins-insert-date-time");
+ break;
+ }
+ case GTK_RESPONSE_OK:
+ {
+ gchar *the_time;
+
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK");
+
+ /* Get the user's chosen format */
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->use_list)))
+ {
+ gint sel_format;
+
+ sel_format = get_format_from_list (dialog->list);
+ the_time = get_time (formats[sel_format]);
+
+ g_settings_set_enum (dialog->settings,
+ PROMPT_TYPE_KEY,
+ PROMPT_SELECTED_FORMAT);
+ g_settings_set_string (dialog->settings,
+ SELECTED_FORMAT_KEY,
+ formats[sel_format]);
+ }
+ else
+ {
+ const gchar *format;
+
+ format = gtk_entry_get_text (GTK_ENTRY (dialog->custom_entry));
+ the_time = get_time (format);
+
+ g_settings_set_enum (dialog->settings,
+ PROMPT_TYPE_KEY,
+ PROMPT_CUSTOM_FORMAT);
+ g_settings_set_string (dialog->settings,
+ CUSTOM_FORMAT_KEY,
+ format);
+ }
+
+ g_return_if_fail (the_time != NULL);
+
+ real_insert_time (dialog->buffer, the_time);
+ g_free (the_time);
+
+ gtk_widget_destroy (dialog->dialog);
+ break;
+ }
+ case GTK_RESPONSE_CANCEL:
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CANCEL");
+ gtk_widget_destroy (dialog->dialog);
+ }
+}
+
+static void
+time_cb (GAction *action,
+ GVariant *parameter,
+ GeditTimePlugin *plugin)
+{
+ GeditTimePluginPrivate *priv;
+ GtkTextBuffer *buffer;
+ GeditTimePluginPromptType prompt_type;
+ gchar *the_time = NULL;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ priv = plugin->priv;
+
+ buffer = GTK_TEXT_BUFFER (gedit_window_get_active_document (priv->window));
+ g_return_if_fail (buffer != NULL);
+
+ prompt_type = g_settings_get_enum (plugin->priv->settings,
+ PROMPT_TYPE_KEY);
+
+ if (prompt_type == USE_CUSTOM_FORMAT)
+ {
+ gchar *cf = get_custom_format (plugin);
+ the_time = get_time (cf);
+ g_free (cf);
+ }
+ else if (prompt_type == USE_SELECTED_FORMAT)
+ {
+ gchar *sf = get_selected_format (plugin);
+ the_time = get_time (sf);
+ g_free (sf);
+ }
+ else
+ {
+ ChooseFormatDialog *dialog;
+
+ dialog = get_choose_format_dialog (GTK_WINDOW (priv->window),
+ prompt_type,
+ plugin);
+ if (dialog != NULL)
+ {
+ dialog->buffer = buffer;
+ dialog->settings = plugin->priv->settings;
+
+ g_signal_connect (dialog->dialog,
+ "response",
+ G_CALLBACK (choose_format_dialog_response_cb),
+ dialog);
+
+ gtk_widget_show (GTK_WIDGET (dialog->dialog));
+ }
+
+ return;
+ }
+
+ g_return_if_fail (the_time != NULL);
+
+ real_insert_time (buffer, the_time);
+
+ g_free (the_time);
+}
+
+static GtkWidget *
+gedit_time_plugin_create_configure_widget (PeasGtkConfigurable *configurable)
+{
+ TimeConfigureWidget *widget;
+
+ widget = get_configure_widget (GEDIT_TIME_PLUGIN (configurable));
+
+ return widget->content;
+}
+
+static void
+gedit_time_plugin_class_init (GeditTimePluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_time_plugin_dispose;
+ object_class->set_property = gedit_time_plugin_set_property;
+ object_class->get_property = gedit_time_plugin_get_property;
+
+ g_object_class_override_property (object_class, PROP_WINDOW, "window");
+ g_object_class_override_property (object_class, PROP_APP, "app");
+}
+
+static void
+gedit_time_plugin_class_finalize (GeditTimePluginClass *klass)
+{
+}
+
+static void
+peas_gtk_configurable_iface_init (PeasGtkConfigurableInterface *iface)
+{
+ iface->create_configure_widget = gedit_time_plugin_create_configure_widget;
+}
+
+static void
+gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface)
+{
+ iface->activate = gedit_time_plugin_app_activate;
+ iface->deactivate = gedit_time_plugin_app_deactivate;
+}
+
+static void
+gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface)
+{
+ iface->activate = gedit_time_plugin_window_activate;
+ iface->deactivate = gedit_time_plugin_window_deactivate;
+ iface->update_state = gedit_time_plugin_window_update_state;
+}
+
+G_MODULE_EXPORT void
+peas_register_types (PeasObjectModule *module)
+{
+ gedit_time_plugin_register_type (G_TYPE_MODULE (module));
+
+ peas_object_module_register_extension_type (module,
+ GEDIT_TYPE_APP_ACTIVATABLE,
+ GEDIT_TYPE_TIME_PLUGIN);
+ peas_object_module_register_extension_type (module,
+ GEDIT_TYPE_WINDOW_ACTIVATABLE,
+ GEDIT_TYPE_TIME_PLUGIN);
+ peas_object_module_register_extension_type (module,
+ PEAS_GTK_TYPE_CONFIGURABLE,
+ GEDIT_TYPE_TIME_PLUGIN);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/plugins/time/gedit-time-plugin.h b/plugins/time/gedit-time-plugin.h
new file mode 100644
index 0000000..42ad349
--- /dev/null
+++ b/plugins/time/gedit-time-plugin.h
@@ -0,0 +1,61 @@
+/*
+ * gedit-time-plugin.h
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GEDIT_TIME_PLUGIN_H
+#define GEDIT_TIME_PLUGIN_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libpeas/peas-extension-base.h>
+#include <libpeas/peas-object-module.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_TIME_PLUGIN (gedit_time_plugin_get_type ())
+#define GEDIT_TIME_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_TIME_PLUGIN, GeditTimePlugin))
+#define GEDIT_TIME_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_TIME_PLUGIN, GeditTimePluginClass))
+#define GEDIT_IS_TIME_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_TIME_PLUGIN))
+#define GEDIT_IS_TIME_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_TIME_PLUGIN))
+#define GEDIT_TIME_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_TIME_PLUGIN, GeditTimePluginClass))
+
+typedef struct _GeditTimePlugin GeditTimePlugin;
+typedef struct _GeditTimePluginPrivate GeditTimePluginPrivate;
+typedef struct _GeditTimePluginClass GeditTimePluginClass;
+
+struct _GeditTimePlugin
+{
+ PeasExtensionBase parent_instance;
+
+ /*< private >*/
+ GeditTimePluginPrivate *priv;
+};
+
+struct _GeditTimePluginClass
+{
+ PeasExtensionBaseClass parent_class;
+};
+
+GType gedit_time_plugin_get_type (void) G_GNUC_CONST;
+
+G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
+
+G_END_DECLS
+
+#endif /* GEDIT_TIME_PLUGIN_H */
+/* ex:set ts=8 noet: */
diff --git a/plugins/time/meson.build b/plugins/time/meson.build
new file mode 100644
index 0000000..f3e43a3
--- /dev/null
+++ b/plugins/time/meson.build
@@ -0,0 +1,68 @@
+libtime_sources = files(
+ 'gedit-time-plugin.c',
+)
+
+libtime_deps = [
+ libgedit_dep,
+]
+
+gnome.mkenums(
+ 'org.gnome.gedit.plugins.time.enums.xml',
+ sources: libtime_sources,
+ comments: '<!-- @comment@ -->',
+ fhead: '<schemalist>',
+ vhead: ' <@type@ id="org.gnome.gedit.plugins.time.@EnumName@">',
+ vprod: ' <value nick="@valuenick@" value="@valuenum@"/>',
+ vtail: ' </@type@>',
+ ftail: '</schemalist>',
+ install_header: true,
+ install_dir: join_paths(
+ glibdir,
+ 'schemas',
+ )
+)
+
+subdir('resources')
+
+libtime_sha = shared_module(
+ 'time',
+ sources: libtime_sources,
+ include_directories: root_include_dir,
+ dependencies: libtime_deps,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ ),
+ name_suffix: module_suffix,
+)
+
+time_gschema_file = files('org.gnome.gedit.plugins.time.gschema.xml')
+install_data(
+ time_gschema_file,
+ install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas')
+)
+
+if xmllint.found()
+ test(
+ 'validate-time-gschema',
+ xmllint,
+ args: [
+ '--noout',
+ '--dtdvalid', gschema_dtd,
+ time_gschema_file,
+ ]
+ )
+endif
+
+custom_target(
+ 'time.plugin',
+ input: 'time.plugin.desktop.in',
+ output: 'time.plugin',
+ command: msgfmt_plugin_cmd,
+ install: true,
+ install_dir: join_paths(
+ pkglibdir,
+ 'plugins',
+ )
+)
diff --git a/plugins/time/org.gnome.gedit.plugins.time.gschema.xml b/plugins/time/org.gnome.gedit.plugins.time.gschema.xml
new file mode 100644
index 0000000..d012191
--- /dev/null
+++ b/plugins/time/org.gnome.gedit.plugins.time.gschema.xml
@@ -0,0 +1,19 @@
+<schemalist gettext-domain="gedit">
+ <schema id="org.gnome.gedit.plugins.time" path="/org/gnome/gedit/plugins/time/">
+ <key name="prompt-type" enum="org.gnome.gedit.plugins.time.GeditTimePluginPromptType">
+ <default>'prompt-selected-format'</default>
+ <summary>Prompt Type</summary>
+ <description>If the user should be prompted for a format or if the selected or custom format should be used.</description>
+ </key>
+ <key name="selected-format" type="s">
+ <default>'%c'</default>
+ <summary>Selected Format</summary>
+ <description>The selected format used when inserting the date/time.</description>
+ </key>
+ <key name="custom-format" type="s">
+ <default>'%d/%m/%Y %H:%M:%S'</default>
+ <summary>Custom Format</summary>
+ <description>The custom format used when inserting the date/time.</description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/plugins/time/resources/gedit-time.gresource.xml b/plugins/time/resources/gedit-time.gresource.xml
new file mode 100644
index 0000000..c7cf490
--- /dev/null
+++ b/plugins/time/resources/gedit-time.gresource.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/gedit/plugins/time">
+ <file preprocess="xml-stripblanks">ui/gedit-time-dialog.ui</file>
+ <file preprocess="xml-stripblanks">ui/gedit-time-setup-dialog.ui</file>
+ </gresource>
+</gresources>
diff --git a/plugins/time/resources/meson.build b/plugins/time/resources/meson.build
new file mode 100644
index 0000000..bcf0372
--- /dev/null
+++ b/plugins/time/resources/meson.build
@@ -0,0 +1,8 @@
+libtime_res = gnome.compile_resources(
+ 'gedit-time-resources',
+ 'gedit-time.gresource.xml',
+)
+
+libtime_sources += [
+ libtime_res.get(0),
+]
diff --git a/plugins/time/resources/ui/gedit-time-dialog.ui b/plugins/time/resources/ui/gedit-time-dialog.ui
new file mode 100644
index 0000000..063a061
--- /dev/null
+++ b/plugins/time/resources/ui/gedit-time-dialog.ui
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkDialog" id="choose_format_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Insert Date and Time</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="border_width">10</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkRadioButton" id="use_sel_format_radiobutton">
+ <property name="label" translatable="yes">Use the _selected format</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="height_request">180</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="margin_start">12</property>
+ <property name="margin_bottom">8</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="choice_list">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="use_custom_radiobutton">
+ <property name="label" translatable="yes">_Use custom format</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">use_sel_format_radiobutton</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="custom_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="invisible_char">●</property>
+ <property name="text" translatable="yes">%d/%m/%Y %H:%M:%S</property>
+ <property name="margin_start">6</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="custom_format_example">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes">01/11/2009 17:52:00</property>
+ <property name="justify">right</property>
+ <attributes>
+ <attribute name="scale" value="0.80000000000000004"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="help_button">
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="insert_button">
+ <property name="label" translatable="yes">_Insert</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-11">help_button</action-widget>
+ <action-widget response="-6">cancel_button</action-widget>
+ <action-widget response="-5">insert_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/time/resources/ui/gedit-time-setup-dialog.ui b/plugins/time/resources/ui/gedit-time-setup-dialog.ui
new file mode 100644
index 0000000..3eff64f
--- /dev/null
+++ b/plugins/time/resources/ui/gedit-time-setup-dialog.ui
@@ -0,0 +1,214 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.0 -->
+ <object class="GtkDialog" id="time_dialog">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Configure date/time plugin</property>
+ <property name="type_hint">normal</property>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label" translatable="yes">_Close</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="time_dialog_content">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">True</property>
+ <property name="border_width">10</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">When inserting date/time…</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <child>
+ <object class="GtkRadioButton" id="always_prompt">
+ <property name="label" translatable="yes">_Prompt for a format</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="never_prompt">
+ <property name="label" translatable="yes">Use the _selected format</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">always_prompt</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="formats_viewport">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="margin_start">12</property>
+ <property name="margin_bottom">8</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="formats_tree">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="headers_visible">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ <property name="width">2</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="use_custom">
+ <property name="label" translatable="yes">_Use custom format</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">always_prompt</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="custom_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="invisible_char">●</property>
+ <property name="text" translatable="yes" comments="Translators: Use the more common date format in your locale">%d/%m/%Y %H:%M:%S</property>
+ <property name="margin_start">6</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="custom_format_example">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">1</property>
+ <property name="label" translatable="yes" comments="Translators: This example should follow the date format defined in the entry above">01/11/2009 17:52:00</property>
+ <property name="justify">right</property>
+ <attributes>
+ <attribute name="scale" value="0.80000000000000004"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="width">1</property>
+ <property name="height">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button4</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/time/time.plugin.desktop.in b/plugins/time/time.plugin.desktop.in
new file mode 100644
index 0000000..1086547
--- /dev/null
+++ b/plugins/time/time.plugin.desktop.in
@@ -0,0 +1,8 @@
+[Plugin]
+Module=time
+IAge=3
+Name=Insert Date/Time
+Description=Inserts current date and time at the cursor position.
+Authors=Paolo Maggi <paolo.maggi@polito.it>;Lee Mallabone <gnome@fonicmonkey.net>
+Copyright=Copyright © 2002-2005 Paolo Maggi
+Website=http://www.gedit.org