/*
* gedit-bookmarks-plugin.c - Bookmarking for gedit
*
* Copyright (C) 2008 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, 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 Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "gedit-bookmarks-plugin.h"
#include "gedit-bookmarks-app-activatable.h"
#include "messages/messages.h"
#include <stdlib.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <gtksourceview/gtksource.h>
#include <gedit/gedit-debug.h>
#include <gedit/gedit-window.h>
#include <gedit/gedit-window-activatable.h>
#include <gedit/gedit-document.h>
#define GEDIT_BOOKMARKS_PLUGIN_GET_PRIVATE(object) \
(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
GEDIT_TYPE_BOOKMARKS_PLUGIN, \
GeditBookmarksPluginPrivate))
#define BOOKMARK_CATEGORY "GeditBookmarksPluginBookmark"
#define BOOKMARK_PRIORITY 1
#define INSERT_DATA_KEY "GeditBookmarksInsertData"
#define METADATA_ATTR "metadata::gedit-bookmarks"
#define MESSAGE_OBJECT_PATH "/plugins/bookmarks"
#define BUS_CONNECT(bus, name, data) gedit_message_bus_connect(bus, MESSAGE_OBJECT_PATH, #name, (GeditMessageCallback) message_##name##_cb, data, NULL)
typedef struct
{
GtkSourceMark *bookmark;
GtkTextMark *mark;
} InsertTracker;
typedef struct
{
GSList *trackers;
guint user_action;
} InsertData;
static void on_style_scheme_notify (GObject *object,
GParamSpec *pspec,
GeditView *view);
static void on_delete_range (GtkTextBuffer *buffer,
GtkTextIter *start,
GtkTextIter *end,
gpointer user_data);
static void on_insert_text_before (GtkTextBuffer *buffer,
GtkTextIter *location,
gchar *text,
gint len,
InsertData *data);
static void on_begin_user_action (GtkTextBuffer *buffer,
InsertData *data);
static void on_end_user_action (GtkTextBuffer *buffer,
InsertData *data);
static void on_toggle_bookmark_activate (GAction *action,
GVariant *parameter,
GeditBookmarksPlugin *plugin);
static void on_next_bookmark_activate (GAction *action,
GVariant *parameter,
GeditBookmarksPlugin *plugin);
static void on_previous_bookmark_activate (GAction *action,
GVariant *parameter,
GeditBookmarksPlugin *plugin);
static void on_tab_added (GeditWindow *window,
GeditTab *tab,
GeditBookmarksPlugin *plugin);
static void on_tab_removed (GeditWindow *window,
GeditTab *tab,
GeditBookmarksPlugin *plugin);
static void add_bookmark (GtkSourceBuffer *buffer, GtkTextIter *iter);
static void remove_bookmark (GtkSourceBuffer *buffer, GtkTextIter *iter);
static void toggle_bookmark (GtkSourceBuffer *buffer, GtkTextIter *iter);
static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface);
G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditBookmarksPlugin,
gedit_bookmarks_plugin,
PEAS_TYPE_EXTENSION_BASE,
0,
G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE,
gedit_window_activatable_iface_init))
struct _GeditBookmarksPluginPrivate
{
GeditWindow *window;
GSimpleAction *action_toggle;
GSimpleAction *action_next;
GSimpleAction *action_prev;
};
enum
{
PROP_0,
PROP_WINDOW
};
static void
gedit_bookmarks_plugin_init (GeditBookmarksPlugin *plugin)
{
gedit_debug_message (DEBUG_PLUGINS, "GeditBookmarksPlugin initializing");
plugin->priv = GEDIT_BOOKMARKS_PLUGIN_GET_PRIVATE (plugin);
}
static void
gedit_bookmarks_plugin_dispose (GObject *object)
{
GeditBookmarksPlugin *plugin = GEDIT_BOOKMARKS_PLUGIN (object);
GeditBookmarksPluginPrivate *priv = plugin->priv;
gedit_debug_message (DEBUG_PLUGINS, "GeditBookmarksPlugin disposing");
g_clear_object (&priv->action_toggle);
g_clear_object (&priv->action_next);
g_clear_object (&priv->action_prev);
g_clear_object (&priv->window);
G_OBJECT_CLASS (gedit_bookmarks_plugin_parent_class)->dispose (object);
}
static void
gedit_bookmarks_plugin_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GeditBookmarksPlugin *plugin = GEDIT_BOOKMARKS_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_bookmarks_plugin_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GeditBookmarksPlugin *plugin = GEDIT_BOOKMARKS_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
free_insert_data (InsertData *data)
{
g_slice_free (InsertData, data);
}
static void
install_actions (GeditBookmarksPlugin *plugin)
{
GeditBookmarksPluginPrivate *priv;
priv = plugin->priv;
priv->action_toggle = g_simple_action_new ("bookmark-toggle", NULL);
g_signal_connect (priv->action_toggle, "activate",
G_CALLBACK (on_toggle_bookmark_activate), plugin);
g_action_map_add_action (G_ACTION_MAP (priv->window),
G_ACTION (priv->action_toggle));
priv->action_next = g_simple_action_new ("bookmark-next", NULL);
g_signal_connect (priv->action_next, "activate",
G_CALLBACK (on_next_bookmark_activate), plugin);
g_action_map_add_action (G_ACTION_MAP (priv->window),
G_ACTION (priv->action_next));
priv->action_prev = g_simple_action_new ("bookmark-prev", NULL);
g_signal_connect (priv->action_prev, "activate",
G_CALLBACK (on_previous_bookmark_activate), plugin);
g_action_map_add_action (G_ACTION_MAP (priv->window),
G_ACTION (priv->action_prev));
}
static void
uninstall_actions (GeditBookmarksPlugin *plugin)
{
GeditBookmarksPluginPrivate *priv;
priv = plugin->priv;
g_action_map_remove_action (G_ACTION_MAP (priv->window), "bookmark-toggle");
g_action_map_remove_action (G_ACTION_MAP (priv->window), "bookmark-next");
g_action_map_remove_action (G_ACTION_MAP (priv->window), "bookmark-prev");
}
static void
remove_all_bookmarks (GtkSourceBuffer *buffer)
{
GtkTextIter start;
GtkTextIter end;
gedit_debug (DEBUG_PLUGINS);
gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end);
gtk_source_buffer_remove_source_marks (buffer,
&start,
&end,
BOOKMARK_CATEGORY);
}
static void
disable_bookmarks (GeditView *view)
{
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
gpointer data;
gtk_source_view_set_show_line_marks (GTK_SOURCE_VIEW (view), FALSE);
remove_all_bookmarks (GTK_SOURCE_BUFFER (buffer));
g_signal_handlers_disconnect_by_func (buffer, on_style_scheme_notify, view);
g_signal_handlers_disconnect_by_func (buffer, on_delete_range, NULL);
data = g_object_get_data (G_OBJECT (buffer), INSERT_DATA_KEY);
g_signal_handlers_disconnect_by_func (buffer, on_insert_text_before, data);
g_signal_handlers_disconnect_by_func (buffer, on_begin_user_action, data);
g_signal_handlers_disconnect_by_func (buffer, on_end_user_action, data);
g_object_set_data (G_OBJECT (buffer), INSERT_DATA_KEY, NULL);
}
static GdkPixbuf *
get_bookmark_pixbuf (GeditBookmarksPlugin *plugin)
{
GdkPixbuf *pixbuf;
gint width;
GError *error = NULL;
gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL);
pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
"user-bookmarks-symbolic",
(width * 2) / 3,
0,
&error);
if (error != NULL)
{
g_warning ("Could not load theme icon user-bookmarks-symbolic: %s",
error->message);
g_error_free (error);
}
return pixbuf;
}
static void
update_background_color (GtkSourceMarkAttributes *attrs, GtkSourceBuffer *buffer)
{
GtkSourceStyleScheme *scheme;
GtkSourceStyle *style;
scheme = gtk_source_buffer_get_style_scheme (buffer);
style = gtk_source_style_scheme_get_style (scheme, "search-match");
if (style)
{
gboolean bgset;
gchar *bg;
g_object_get (style, "background-set", &bgset, "background", &bg, NULL);
if (bgset)
{
GdkRGBA color;
gdk_rgba_parse (&color, bg);
gtk_source_mark_attributes_set_background (attrs, &color);
g_free (bg);
return;
}
}
gtk_source_mark_attributes_set_background (attrs, NULL);
}
static void
enable_bookmarks (GeditView *view,
GeditBookmarksPlugin *plugin)
{
GdkPixbuf *pixbuf;
pixbuf = get_bookmark_pixbuf (plugin);
/* Make sure the category pixbuf is set */
if (pixbuf)
{
GtkTextBuffer *buffer;
GtkSourceMarkAttributes *attrs;
InsertData *data;
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
attrs = gtk_source_mark_attributes_new ();
update_background_color (attrs, GTK_SOURCE_BUFFER (buffer));
gtk_source_mark_attributes_set_pixbuf (attrs, pixbuf);
g_object_unref (pixbuf);
gtk_source_view_set_mark_attributes (GTK_SOURCE_VIEW (view),
BOOKMARK_CATEGORY,
attrs,
BOOKMARK_PRIORITY);
gtk_source_view_set_show_line_marks (GTK_SOURCE_VIEW (view), TRUE);
g_signal_connect (buffer,
"notify::style-scheme",
G_CALLBACK (on_style_scheme_notify),
view);
g_signal_connect (buffer,
"delete-range",
G_CALLBACK (on_delete_range),
NULL);
data = g_slice_new0 (InsertData);
g_object_set_data_full (G_OBJECT (buffer),
INSERT_DATA_KEY,
data,
(GDestroyNotify) free_insert_data);
g_signal_connect (buffer,
"insert-text",
G_CALLBACK (on_insert_text_before),
data);
g_signal_connect (buffer,
"begin-user-action",
G_CALLBACK (on_begin_user_action),
data);
g_signal_connect (buffer,
"end-user-action",
G_CALLBACK (on_end_user_action),
data);
}
else
{
g_warning ("Could not set bookmark icon!");
}
}
static void
load_bookmarks (GeditView *view,
gchar **bookmarks)
{
GtkSourceBuffer *buf;
GtkTextIter iter;
gint tot_lines;
gint i;
gedit_debug (DEBUG_PLUGINS);
buf = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (buf), &iter);
tot_lines = gtk_text_iter_get_line (&iter);
for (i = 0; bookmarks != NULL && bookmarks[i] != NULL; i++)
{
gint line;
line = atoi (bookmarks[i]);
if (line >= 0 && line < tot_lines)
{
GSList *marks;
gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (buf),
&iter, line);
marks = gtk_source_buffer_get_source_marks_at_iter (buf, &iter,
BOOKMARK_CATEGORY);
if (marks == NULL)
{
/* Add new bookmark */
gtk_source_buffer_create_source_mark (buf,
NULL,
BOOKMARK_CATEGORY,
&iter);
}
else
{
g_slist_free (marks);
}
}
}
}
static void
load_bookmark_metadata (GeditView *view)
{
GeditDocument *doc;
gchar *bookmarks_attr;
doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
bookmarks_attr = gedit_document_get_metadata (doc, METADATA_ATTR);
if (bookmarks_attr != NULL)
{
gchar **bookmarks;
bookmarks = g_strsplit (bookmarks_attr, ",", -1);
g_free (bookmarks_attr);
load_bookmarks (view, bookmarks);
g_strfreev (bookmarks);
}
}
typedef gboolean (*IterSearchFunc)(GtkSourceBuffer *buffer, GtkTextIter *iter, const gchar *category);
typedef void (*CycleFunc)(GtkTextBuffer *buffer, GtkTextIter *iter);
static void
goto_bookmark (GeditWindow *window,
GtkSourceView *view,
GtkTextIter *iter,
IterSearchFunc func,
CycleFunc cycle_func)
{
GtkTextBuffer *buffer;
GtkTextIter at;
GtkTextIter end;
if (view == NULL)
{
view = GTK_SOURCE_VIEW (gedit_window_get_active_view (window));
}
if (view == NULL)
{
return;
}
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
if (iter == NULL)
{
gtk_text_buffer_get_iter_at_mark (buffer,
&at,
gtk_text_buffer_get_insert (buffer));
}
else
{
at = *iter;
}
/* Move the iter to the beginning of the line, where the bookmarks are */
gtk_text_iter_set_line_offset (&at, 0);
/* Try to find the next bookmark */
if (!func (GTK_SOURCE_BUFFER (buffer), &at, BOOKMARK_CATEGORY))
{
GSList *marks;
/* cycle through */
cycle_func (buffer, &at);
gtk_text_iter_set_line_offset (&at, 0);
marks = gtk_source_buffer_get_source_marks_at_iter (GTK_SOURCE_BUFFER (buffer),
&at,
BOOKMARK_CATEGORY);
if (!marks && !func (GTK_SOURCE_BUFFER (buffer), &at, BOOKMARK_CATEGORY))
{
return;
}
g_slist_free (marks);
}
end = at;
if (!gtk_text_iter_forward_visible_line (&end))
{
gtk_text_buffer_get_end_iter (buffer, &end);
}
else
{
gtk_text_iter_backward_char (&end);
}
gtk_text_buffer_select_range (buffer, &at, &end);
gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (view), &at, 0.3, FALSE, 0, 0);
}
static void
message_get_view_iter (GeditWindow *window,
GeditMessage *message,
GtkSourceView **view,
GtkTextIter *iter)
{
g_object_get (message, "view", view, NULL);
if (!*view)
{
*view = GTK_SOURCE_VIEW (gedit_window_get_active_view (window));
}
if (!*view)
{
return;
}
g_object_get (message, "iter", iter, NULL);
if (iter)
{
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (*view));
gtk_text_buffer_get_iter_at_mark (buffer,
iter,
gtk_text_buffer_get_insert (buffer));
}
}
static void
message_toggle_cb (GeditMessageBus *bus,
GeditMessage *message,
GeditWindow *window)
{
GtkSourceView *view = NULL;
GtkTextIter iter;
message_get_view_iter (window, message, &view, &iter);
if (!view)
{
return;
}
toggle_bookmark (GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))),
&iter);
}
static void
message_add_cb (GeditMessageBus *bus,
GeditMessage *message,
GeditWindow *window)
{
GtkSourceView *view = NULL;
GtkTextIter iter;
message_get_view_iter (window, message, &view, &iter);
if (!view)
{
return;
}
add_bookmark (GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))),
&iter);
}
static void
message_remove_cb (GeditMessageBus *bus,
GeditMessage *message,
GeditWindow *window)
{
GtkSourceView *view = NULL;
GtkTextIter iter;
message_get_view_iter (window, message, &view, &iter);
if (!view)
{
return;
}
remove_bookmark (GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))),
&iter);
}
static void
message_goto_next_cb (GeditMessageBus *bus,
GeditMessage *message,
GeditWindow *window)
{
GtkSourceView *view = NULL;
GtkTextIter iter;
message_get_view_iter (window, message, &view, &iter);
if (!view)
{
return;
}
goto_bookmark (window,
view,
&iter,
gtk_source_buffer_forward_iter_to_source_mark,
gtk_text_buffer_get_start_iter);
}
static void
message_goto_previous_cb (GeditMessageBus *bus,
GeditMessage *message,
GeditWindow *window)
{
GtkSourceView *view = NULL;
GtkTextIter iter;
message_get_view_iter (window, message, &view, &iter);
if (!view)
{
return;
}
goto_bookmark (window,
view,
&iter,
gtk_source_buffer_backward_iter_to_source_mark,
gtk_text_buffer_get_end_iter);
}
static void
install_messages (GeditWindow *window)
{
GeditMessageBus *bus = gedit_window_get_message_bus (window);
gedit_message_bus_register (bus,
GEDIT_TYPE_BOOKMARKS_MESSAGE_TOGGLE,
MESSAGE_OBJECT_PATH,
"toggle");
gedit_message_bus_register (bus,
GEDIT_TYPE_BOOKMARKS_MESSAGE_ADD,
MESSAGE_OBJECT_PATH,
"add");
gedit_message_bus_register (bus,
GEDIT_TYPE_BOOKMARKS_MESSAGE_REMOVE,
MESSAGE_OBJECT_PATH,
"remove");
gedit_message_bus_register (bus,
GEDIT_TYPE_BOOKMARKS_MESSAGE_GOTO_NEXT,
MESSAGE_OBJECT_PATH,
"goto-next");
gedit_message_bus_register (bus,
GEDIT_TYPE_BOOKMARKS_MESSAGE_GOTO_PREVIOUS,
MESSAGE_OBJECT_PATH,
"goto-previous");
BUS_CONNECT (bus, toggle, window);
BUS_CONNECT (bus, add, window);
BUS_CONNECT (bus, remove, window);
BUS_CONNECT (bus, goto_next, window);
BUS_CONNECT (bus, goto_previous, window);
}
static void
uninstall_messages (GeditWindow *window)
{
GeditMessageBus *bus = gedit_window_get_message_bus (window);
gedit_message_bus_unregister_all (bus, MESSAGE_OBJECT_PATH);
}
static void
gedit_bookmarks_plugin_activate (GeditWindowActivatable *activatable)
{
GeditBookmarksPluginPrivate *priv;
GList *views;
GList *item;
gedit_debug (DEBUG_PLUGINS);
priv = GEDIT_BOOKMARKS_PLUGIN (activatable)->priv;
views = gedit_window_get_views (priv->window);
for (item = views; item != NULL; item = item->next)
{
enable_bookmarks (GEDIT_VIEW (item->data),
GEDIT_BOOKMARKS_PLUGIN (activatable));
load_bookmark_metadata (GEDIT_VIEW (item->data));
}
g_list_free (views);
g_signal_connect (priv->window, "tab-added",
G_CALLBACK (on_tab_added), activatable);
g_signal_connect (priv->window, "tab-removed",
G_CALLBACK (on_tab_removed), activatable);
install_actions (GEDIT_BOOKMARKS_PLUGIN (activatable));
install_messages (priv->window);
}
static void
gedit_bookmarks_plugin_update_state (GeditWindowActivatable *activatable)
{
GeditBookmarksPluginPrivate *priv;
gboolean enabled;
priv = GEDIT_BOOKMARKS_PLUGIN (activatable)->priv;
enabled = gedit_window_get_active_view (priv->window) != NULL;
g_simple_action_set_enabled (priv->action_toggle, enabled);
g_simple_action_set_enabled (priv->action_next, enabled);
g_simple_action_set_enabled (priv->action_prev, enabled);
}
static void
save_bookmark_metadata (GeditView *view)
{
GtkTextIter iter;
GtkTextBuffer *buf;
GString *string;
gchar *val = NULL;
gboolean first = TRUE;
buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
gtk_text_buffer_get_start_iter (buf, &iter);
string = g_string_new (NULL);
while (gtk_source_buffer_forward_iter_to_source_mark (GTK_SOURCE_BUFFER (buf),
&iter,
BOOKMARK_CATEGORY))
{
gint line;
line = gtk_text_iter_get_line (&iter);
if (!first)
{
g_string_append_printf (string, ",%d", line);
}
else
{
g_string_append_printf (string, "%d", line);
first = FALSE;
}
}
if (string->len == 0)
{
val = g_string_free (string, TRUE);
val = NULL;
}
else
val = g_string_free (string, FALSE);
gedit_document_set_metadata (GEDIT_DOCUMENT (buf), METADATA_ATTR,
val, NULL);
g_free (val);
}
static void
gedit_bookmarks_plugin_deactivate (GeditWindowActivatable *activatable)
{
GeditBookmarksPluginPrivate *priv;
GList *views;
GList *item;
gedit_debug (DEBUG_PLUGINS);
priv = GEDIT_BOOKMARKS_PLUGIN (activatable)->priv;
uninstall_actions (GEDIT_BOOKMARKS_PLUGIN (activatable));
uninstall_messages (priv->window);
views = gedit_window_get_views (priv->window);
for (item = views; item != NULL; item = item->next)
{
disable_bookmarks (GEDIT_VIEW (item->data));
}
g_list_free (views);
g_signal_handlers_disconnect_by_func (priv->window, on_tab_added, activatable);
g_signal_handlers_disconnect_by_func (priv->window, on_tab_removed, activatable);
}
static void
gedit_bookmarks_plugin_class_init (GeditBookmarksPluginClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gedit_bookmarks_plugin_dispose;
object_class->set_property = gedit_bookmarks_plugin_set_property;
object_class->get_property = gedit_bookmarks_plugin_get_property;
g_object_class_override_property (object_class, PROP_WINDOW, "window");
g_type_class_add_private (klass, sizeof (GeditBookmarksPluginPrivate));
}
static void
gedit_bookmarks_plugin_class_finalize (GeditBookmarksPluginClass *klass)
{
}
static void
on_style_scheme_notify (GObject *object,
GParamSpec *pspec,
GeditView *view)
{
GtkSourceMarkAttributes *attrs;
attrs = gtk_source_view_get_mark_attributes (GTK_SOURCE_VIEW (view),
BOOKMARK_CATEGORY,
NULL);
update_background_color (attrs, GTK_SOURCE_BUFFER (object));
}
static void
on_delete_range (GtkTextBuffer *buffer,
GtkTextIter *start,
GtkTextIter *end,
gpointer user_data)
{
GtkTextIter start_iter;
GtkTextIter end_iter;
gboolean keep_bookmark;
/* Nothing to do for us here. The bookmark, if any, will stay at the
beginning of the line due to its left gravity. */
if (gtk_text_iter_get_line (start) == gtk_text_iter_get_line (end))
{
return;
}
start_iter = *start;
gtk_text_iter_set_line_offset (&start_iter, 0);
end_iter = *end;
gtk_text_iter_set_line_offset (&end_iter, 0);
keep_bookmark = ((gtk_source_buffer_get_source_marks_at_iter (GTK_SOURCE_BUFFER (buffer),
&start_iter,
BOOKMARK_CATEGORY) != NULL) ||
(gtk_source_buffer_get_source_marks_at_iter (GTK_SOURCE_BUFFER (buffer),
&end_iter,
BOOKMARK_CATEGORY) != NULL));
/* Remove all bookmarks in the range. */
gtk_source_buffer_remove_source_marks (GTK_SOURCE_BUFFER (buffer),
&start_iter,
&end_iter,
BOOKMARK_CATEGORY);
if (keep_bookmark)
{
gtk_source_buffer_create_source_mark (GTK_SOURCE_BUFFER (buffer),
NULL,
BOOKMARK_CATEGORY,
&start_iter);
}
}
static void
on_begin_user_action (GtkTextBuffer *buffer,
InsertData *data)
{
++data->user_action;
}
static void
on_end_user_action (GtkTextBuffer *buffer,
InsertData *data)
{
GSList *item;
if (--data->user_action > 0)
{
return;
}
/* Remove trackers */
for (item = data->trackers; item; item = g_slist_next (item))
{
InsertTracker *tracker = item->data;
GtkTextIter curloc;
GtkTextIter newloc;
/* Move the category to the line where the mark now is */
gtk_text_buffer_get_iter_at_mark (buffer,
&curloc,
GTK_TEXT_MARK (tracker->bookmark));
gtk_text_buffer_get_iter_at_mark (buffer,
&newloc,
tracker->mark);
if (gtk_text_iter_get_line (&curloc) != gtk_text_iter_get_line (&newloc))
{
gtk_text_iter_set_line_offset (&newloc, 0);
gtk_text_buffer_move_mark (buffer,
GTK_TEXT_MARK (tracker->bookmark),
&newloc);
}
gtk_text_buffer_delete_mark (buffer, tracker->mark);
g_slice_free (InsertTracker, tracker);
}
g_slist_free (data->trackers);
data->trackers = NULL;
}
static void
add_tracker (GtkTextBuffer *buffer,
GtkTextIter *iter,
GtkSourceMark *bookmark,
InsertData *data)
{
GSList *item;
InsertTracker *tracker;
for (item = data->trackers; item; item = g_slist_next (item))
{
tracker = item->data;
if (tracker->bookmark == bookmark)
{
return;
}
}
tracker = g_slice_new (InsertTracker);
tracker->bookmark = bookmark;
tracker->mark = gtk_text_buffer_create_mark (buffer,
NULL,
iter,
FALSE);
data->trackers = g_slist_prepend (data->trackers, tracker);
}
static void
on_insert_text_before (GtkTextBuffer *buffer,
GtkTextIter *location,
gchar *text,
gint len,
InsertData *data)
{
if (gtk_text_iter_starts_line (location))
{
GSList *marks;
marks = gtk_source_buffer_get_source_marks_at_iter (GTK_SOURCE_BUFFER (buffer),
location,
BOOKMARK_CATEGORY);
if (marks != NULL)
{
add_tracker (buffer, location, marks->data, data);
g_slist_free (marks);
}
}
}
static GtkSourceMark *
get_bookmark_and_iter (GtkSourceBuffer *buffer,
GtkTextIter *iter,
GtkTextIter *start)
{
GSList *marks;
GtkSourceMark *ret = NULL;
if (!iter)
{
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
start,
gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer)));
}
else
{
*start = *iter;
}
gtk_text_iter_set_line_offset (start, 0);
marks = gtk_source_buffer_get_source_marks_at_iter (buffer,
start,
BOOKMARK_CATEGORY);
if (marks != NULL)
{
ret = GTK_SOURCE_MARK (marks->data);
}
g_slist_free (marks);
return ret;
}
static void
remove_bookmark (GtkSourceBuffer *buffer,
GtkTextIter *iter)
{
GtkTextIter start;
GtkSourceMark *bookmark;
bookmark = get_bookmark_and_iter (buffer, iter, &start);
if (bookmark != NULL)
{
gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (buffer),
GTK_TEXT_MARK (bookmark));
}
}
static void
add_bookmark (GtkSourceBuffer *buffer,
GtkTextIter *iter)
{
GtkTextIter start;
GtkSourceMark *bookmark;
bookmark = get_bookmark_and_iter (buffer, iter, &start);
if (bookmark == NULL)
{
gtk_source_buffer_create_source_mark (GTK_SOURCE_BUFFER (buffer),
NULL,
BOOKMARK_CATEGORY,
&start);
}
}
static void
toggle_bookmark (GtkSourceBuffer *buffer,
GtkTextIter *iter)
{
GtkTextIter start;
GtkSourceMark *bookmark = NULL;
if (buffer == NULL)
{
return;
}
bookmark = get_bookmark_and_iter (buffer, iter, &start);
if (bookmark != NULL)
{
remove_bookmark (buffer, &start);
}
else
{
add_bookmark (buffer, &start);
}
}
static void
on_toggle_bookmark_activate (GAction *action,
GVariant *parameter,
GeditBookmarksPlugin *plugin)
{
GtkSourceBuffer *buffer;
buffer = GTK_SOURCE_BUFFER (gedit_window_get_active_document (plugin->priv->window));
toggle_bookmark (buffer, NULL);
}
static void
on_next_bookmark_activate (GAction *action,
GVariant *parameter,
GeditBookmarksPlugin *plugin)
{
goto_bookmark (plugin->priv->window,
NULL,
NULL,
gtk_source_buffer_forward_iter_to_source_mark,
gtk_text_buffer_get_start_iter);
}
static void
on_previous_bookmark_activate (GAction *action,
GVariant *parameter,
GeditBookmarksPlugin *plugin)
{
goto_bookmark (plugin->priv->window,
NULL,
NULL,
gtk_source_buffer_backward_iter_to_source_mark,
gtk_text_buffer_get_end_iter);
}
static void
on_document_loaded (GeditDocument *doc,
GeditView *view)
{
/* Reverting can leave one bookmark at the start, remove it. */
remove_all_bookmarks (GTK_SOURCE_BUFFER (doc));
load_bookmark_metadata (view);
}
static void
on_document_saved (GeditDocument *doc,
GeditView *view)
{
save_bookmark_metadata (view);
}
static void
on_tab_added (GeditWindow *window,
GeditTab *tab,
GeditBookmarksPlugin *plugin)
{
GeditDocument *doc;
GeditView *view;
doc = gedit_tab_get_document (tab);
view = gedit_tab_get_view (tab);
g_signal_connect (doc, "loaded",
G_CALLBACK (on_document_loaded),
view);
g_signal_connect (doc, "saved",
G_CALLBACK (on_document_saved),
view);
enable_bookmarks (view, plugin);
}
static void
on_tab_removed (GeditWindow *window,
GeditTab *tab,
GeditBookmarksPlugin *plugin)
{
GeditDocument *doc;
GeditView *view;
doc = gedit_tab_get_document (tab);
view = gedit_tab_get_view (tab);
g_signal_handlers_disconnect_by_func (doc, on_document_loaded, view);
g_signal_handlers_disconnect_by_func (doc, on_document_saved, view);
disable_bookmarks (view);
}
static void
gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface)
{
iface->activate = gedit_bookmarks_plugin_activate;
iface->deactivate = gedit_bookmarks_plugin_deactivate;
iface->update_state = gedit_bookmarks_plugin_update_state;
}
G_MODULE_EXPORT void
peas_register_types (PeasObjectModule *module)
{
gedit_bookmarks_plugin_register_type (G_TYPE_MODULE (module));
gedit_bookmarks_app_activatable_register (G_TYPE_MODULE (module));
peas_object_module_register_extension_type (module,
GEDIT_TYPE_WINDOW_ACTIVATABLE,
GEDIT_TYPE_BOOKMARKS_PLUGIN);
}