Branch: refs/heads/master Author: Frank Lanitz frank@frank.uvena.de Committer: GitHub noreply@github.com Date: Sat, 19 Aug 2017 13:46:12 UTC Commit: 59118d86ef8853943dec74c9f6d6fad9a535931a https://github.com/geany/geany-plugins/commit/59118d86ef8853943dec74c9f6d6fa...
Log Message: ----------- Merge pull request #531 from techee/sidebar_undo
git-changebar: Add the possibility to undo hunk at cursor position
Modified Paths: -------------- git-changebar/README git-changebar/src/gcb-plugin.c
Modified: git-changebar/README 8 lines changed, 6 insertions(+), 2 deletions(-) =================================================================== @@ -8,8 +8,8 @@ Git Change Bar About =====
-This plugin highlights uncommitted changes to files tracked with Git, and -allows to navigate through the hunks. +This plugin highlights uncommitted changes to files tracked with Git, +allows to navigate through the hunks and undo them.
Requirements @@ -38,6 +38,10 @@ To navigate through the hunks of the current file, you need to configure the plugin's *Go to next hunk* and *Go to previous hunk* keybindings in Geany's preferences dialog.
+Hunks can also be undone by either setting a cursor at a line with a hunk and +invoking the *Undo hunk at the cursor position* or by right-clicking in the +editor where the hunk is located and selecting *Undo Git hunk* from the popup +menu.
License =======
Modified: git-changebar/src/gcb-plugin.c 249 lines changed, 240 insertions(+), 9 deletions(-) =================================================================== @@ -68,7 +68,13 @@ PLUGIN_SET_TRANSLATABLE_INFO (
#define RESOURCES_ALLOCATED_QTAG \ (g_quark_from_string (PLUGIN"/git-resources-allocated")) +#define UNDO_LINE_QTAG \ + (g_quark_from_string (PLUGIN"/git-undo-line")) +#define DOC_ID_QTAG \ + (g_quark_from_string (PLUGIN"/git-doc-id"))
+#define REMOVED_MARKER_POS(pos) \ + ((pos) == 0 ? 0 : (pos) - 1)
enum { MARKER_LINE_ADDED, @@ -80,6 +86,7 @@ enum { enum { KB_GOTO_PREV_HUNK, KB_GOTO_NEXT_HUNK, + KB_UNDO_HUNK, KB_COUNT };
@@ -117,6 +124,16 @@ struct GotoNextHunkData { gint next_line; };
+typedef struct UndoHunkData UndoHunkData; +struct UndoHunkData { + guint doc_id; + gint line; + gboolean found; + gint old_start; + gint old_lines; + gint new_start; + gint new_lines; +};
static void on_git_repo_changed (GFileMonitor *monitor, GFile *file, @@ -155,6 +172,7 @@ static GAsyncQueue *G_queue = NULL; static GThread *G_thread = NULL; static gulong G_source_id = 0; static gboolean G_monitoring_enabled = TRUE; +static GtkWidget *G_undo_menu_item = NULL; static struct { gint num; gint style; @@ -591,7 +609,7 @@ release_resources (ScintillaObject *sci)
/* checks whether @encoding needs to be converted to UTF-8 */ static gboolean -encoding_needs_coversion (const gchar *encoding) +encoding_needs_conversion (const gchar *encoding) { return (encoding && ! utils_str_equal (encoding, "UTF-8") && @@ -694,7 +712,7 @@ diff_buf_to_doc (const git_buf *old_buf, free_buf = add_utf8_bom (&buf, &len, free_buf); } /* convert the buffer back to in-file encoding if necessary */ - if (encoding_needs_coversion (doc->encoding)) { + if (encoding_needs_conversion (doc->encoding)) { free_buf = convert_encoding_inplace (&buf, &len, free_buf, doc->encoding, "UTF-8", NULL); } @@ -720,16 +738,17 @@ diff_hunk_cb (const git_diff_delta *delta, void *data) { ScintillaObject *sci = data; + gint line;
if (hunk->new_lines > 0) { - gint line; guint marker = hunk->old_lines > 0 ? MARKER_LINE_CHANGED : MARKER_LINE_ADDED;
for (line = hunk->new_start; line < hunk->new_start + hunk->new_lines; line++) { scintilla_send_message (sci, SCI_MARKERADD, line - 1, G_markers[marker].num); } } else { - scintilla_send_message (sci, SCI_MARKERADD, hunk->new_start - 1, + line = REMOVED_MARKER_POS (hunk->new_start); + scintilla_send_message (sci, SCI_MARKERADD, line, G_markers[MARKER_LINE_REMOVED].num); }
@@ -773,7 +792,7 @@ get_widget_for_buf_range (GeanyDocument *doc, }
/* convert the buffer to UTF-8 if necessary */ - if (encoding_needs_coversion (doc->encoding)) { + if (encoding_needs_conversion (doc->encoding)) { free_buf = convert_encoding_inplace (&buf, &buf_len, free_buf, "UTF-8", doc->encoding, NULL); } @@ -812,6 +831,12 @@ get_widget_for_buf_range (GeanyDocument *doc, return GTK_WIDGET (sci); }
+static gboolean +is_first_line_removed (gint line, gint new_hunk_start, gint new_hunk_lines) +{ + return line == 1 && new_hunk_start == 0 && new_hunk_lines == 0; +} + static int tooltip_diff_hunk_cb (const git_diff_delta *delta, const git_diff_hunk *hunk, @@ -824,8 +849,9 @@ tooltip_diff_hunk_cb (const git_diff_delta *delta, }
if (hunk->old_lines > 0 && - thd->line >= hunk->new_start && - thd->line < hunk->new_start + MAX (1, hunk->new_lines)) { + (is_first_line_removed (thd->line, hunk->new_start, hunk->new_lines) || + (thd->line >= hunk->new_start && + thd->line < hunk->new_start + MAX (1, hunk->new_lines)))) { GtkWidget *old = get_widget_for_buf_range (thd->doc, thd->buf, hunk->old_start - 1, hunk->old_lines); @@ -900,6 +926,8 @@ update_diff (const gchar *path, } }
+ gtk_widget_set_visible (G_undo_menu_item, contents != NULL); + if (contents && (allocated || allocate_resources (sci))) { diff_buf_to_doc (contents, doc, diff_hunk_cb, sci); } else if (! contents && allocated) { @@ -962,6 +990,8 @@ update_diff_push (GeanyDocument *doc, { g_return_if_fail (DOC_VALID (doc));
+ gtk_widget_hide (G_undo_menu_item); + if (G_source_id) { g_source_remove (G_source_id); G_source_id = 0; @@ -1036,13 +1066,13 @@ goto_next_hunk_diff_hunk_cb (const git_diff_delta *delta, if (data->next_line >= 0) { return 1; } else if (data->line < hunk->new_start - 1) { - data->next_line = hunk->new_start - 1; + data->next_line = REMOVED_MARKER_POS (hunk->new_start); } break;
case KB_GOTO_PREV_HUNK: if (data->line > hunk->new_start - 1 + MAX (hunk->new_lines - 1, 0)) { - data->next_line = hunk->new_start - 1; + data->next_line = REMOVED_MARKER_POS (hunk->new_start); } break; } @@ -1089,6 +1119,194 @@ on_kb_goto_next_hunk (guint kb) } }
+static void +insert_buf_range (GeanyDocument *doc, + const git_buf *old_contents, + gint pos, + gint old_start, + gint old_lines) +{ + ScintillaObject *old_sci = editor_create_widget (doc->editor); + gchar *old_buf = old_contents->ptr; + gsize old_buf_len = old_contents->size; + gboolean free_buf = FALSE; + gint old_pos_start; + gint old_pos_end; + gchar *old_range; + + /* convert the buffer to UTF-8 if necessary */ + if (encoding_needs_conversion (doc->encoding)) { + free_buf = convert_encoding_inplace (&old_buf, &old_buf_len, free_buf, + "UTF-8", doc->encoding, NULL); + } + + scintilla_send_message (old_sci, SCI_ADDTEXT, old_buf_len, (glong) old_buf); + + old_pos_start = sci_get_position_from_line (old_sci, old_start); + old_pos_end = sci_get_position_from_line (old_sci, old_start + old_lines); + old_range = sci_get_contents_range (old_sci, old_pos_start, old_pos_end); + + sci_insert_text (doc->editor->sci, pos, old_range); + + g_free (old_range); + + if (free_buf) { + g_free (old_buf); + } + + g_object_ref_sink (old_sci); + g_object_unref (old_sci); +} + +static int +undo_hunk_diff_hunk_cb (const git_diff_delta *delta, + const git_diff_hunk *hunk, + void *udata) +{ + UndoHunkData *data = udata; + + if (is_first_line_removed (data->line, hunk->new_start, hunk->new_lines) || + (data->line >= hunk->new_start && + data->line < hunk->new_start + MAX (1, hunk->new_lines))) { + data->old_start = hunk->old_start; + data->old_lines = hunk->old_lines; + data->new_start = hunk->new_start; + data->new_lines = hunk->new_lines; + data->found = TRUE; + return 1; + } + + return 0; +} + +static void +undo_hunk_cb (const gchar *path, + git_buf *contents, + gpointer udata) +{ + UndoHunkData *data = udata; + GeanyDocument *doc = document_get_current (); + + if (doc && doc->id == data->doc_id && contents) { + diff_buf_to_doc (contents, doc, undo_hunk_diff_hunk_cb, data); + + if (data->found) { + ScintillaObject *sci = doc->editor->sci; + gint line = data->new_start - (data->new_lines ? 1 : 0); + gint pos = sci_get_position_from_line (sci, line); + + sci_start_undo_action (sci); + + if (data->new_lines > 0) { + sci_set_target_start (sci, pos); + pos = sci_get_position_from_line (sci, line + data->new_lines); + sci_set_target_end (sci, pos); + sci_replace_target (sci, "", FALSE); + } + + if (data->old_lines > 0) { + pos = sci_get_position_from_line (sci, line); + insert_buf_range (doc, contents, pos, + data->old_start - 1, + data->old_lines); + + pos = sci_get_position_from_line (sci, line + data->old_lines); + sci_set_current_position (sci, pos, FALSE); + } + + scintilla_send_message (sci, SCI_SCROLLRANGE, + sci_get_position_from_line (sci, line), + pos); + + sci_end_undo_action (sci); + } + } + + g_slice_free1 (sizeof *data, data); +} + +static void +undo_hunk (GeanyDocument *doc, + gint line) +{ + UndoHunkData *data = g_slice_alloc (sizeof *data); + + data->doc_id = doc->id; + data->line = line + 1; + data->found = FALSE; + + get_cached_blob_contents_async (doc->real_path, doc->id, FALSE, + undo_hunk_cb, data); +} + +static void +on_kb_undo_hunk (guint kb) +{ + GeanyDocument *doc = document_get_current (); + + if (doc) { + undo_hunk (doc, sci_get_current_line (doc->editor->sci)); + } +} + +static void +on_undo_hunk_activate (GtkWidget *widget, + gpointer user_data) +{ + GeanyDocument *doc = document_get_current (); + gpointer doc_id = g_object_get_qdata (G_OBJECT (widget), DOC_ID_QTAG); + + if (doc && doc->id == GPOINTER_TO_UINT (doc_id) && + gtk_widget_get_sensitive (widget)) { + gpointer line = g_object_get_qdata (G_OBJECT (widget), UNDO_LINE_QTAG); + + undo_hunk (doc, GPOINTER_TO_INT (line)); + } +} + +static void +check_undo_hunk_cb (const gchar *path, + git_buf *contents, + gpointer udata) +{ + UndoHunkData *data = udata; + GeanyDocument *doc = document_get_current (); + + if (doc && doc->id == data->doc_id && contents) { + diff_buf_to_doc (contents, doc, undo_hunk_diff_hunk_cb, data); + if (data->found) { + gtk_widget_set_sensitive (G_undo_menu_item, TRUE); + g_object_set_qdata (G_OBJECT (G_undo_menu_item), UNDO_LINE_QTAG, + GINT_TO_POINTER (data->line - 1)); + g_object_set_qdata (G_OBJECT (G_undo_menu_item), DOC_ID_QTAG, + GUINT_TO_POINTER (data->doc_id)); + } + } + + g_slice_free1 (sizeof *data, data); +} + +static void +on_update_editor_menu (GObject *object, + const gchar *word, + gint pos, + GeanyDocument *doc, + gpointer user_data) +{ + gtk_widget_set_sensitive (G_undo_menu_item, FALSE); + + if (doc) { + UndoHunkData *data = g_slice_alloc (sizeof *data); + + data->doc_id = doc->id; + data->line = sci_get_line_from_position (doc->editor->sci, pos) + 1; + data->found = FALSE; + + get_cached_blob_contents_async (doc->real_path, doc->id, FALSE, + check_undo_hunk_cb, data); + } +} + /* --- configuration loading and saving --- */
static void @@ -1270,14 +1488,25 @@ plugin_init (GeanyData *data)
load_config ();
+ G_undo_menu_item = gtk_menu_item_new_with_label (_("Undo Git hunk")); + g_signal_connect (G_undo_menu_item, "activate", + G_CALLBACK (on_undo_hunk_activate), NULL); + gtk_container_add (GTK_CONTAINER (data->main_widgets->editor_menu), + G_undo_menu_item); + kb_group = plugin_set_key_group (geany_plugin, PLUGIN, KB_COUNT, NULL); keybindings_set_item (kb_group, KB_GOTO_PREV_HUNK, on_kb_goto_next_hunk, 0, 0, "goto-prev-hunk", _("Go to the previous hunk"), NULL); keybindings_set_item (kb_group, KB_GOTO_NEXT_HUNK, on_kb_goto_next_hunk, 0, 0, "goto-next-hunk", _("Go to the next hunk"), NULL); + keybindings_set_item (kb_group, KB_UNDO_HUNK, on_kb_undo_hunk, 0, 0, + "undo-hunk", _("Undo hunk at the cursor position"), + G_undo_menu_item);
plugin_signal_connect (geany_plugin, NULL, "editor-notify", TRUE, G_CALLBACK (on_editor_notify), NULL); + plugin_signal_connect (geany_plugin, NULL, "update-editor-menu", TRUE, + G_CALLBACK (on_update_editor_menu), NULL); plugin_signal_connect (geany_plugin, NULL, "document-activate", TRUE, G_CALLBACK (on_document_activate), NULL); plugin_signal_connect (geany_plugin, NULL, "document-reload", TRUE, @@ -1299,6 +1528,8 @@ plugin_cleanup (void) { guint i = 0;
+ gtk_widget_destroy (G_undo_menu_item); + if (G_source_id) { g_source_remove (G_source_id); G_source_id = 0;
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).