[geany/geany-plugins] 59118d: Merge pull request #531 from techee/sidebar_undo

Frank Lanitz git-noreply at xxxxx
Sat Aug 19 13:46:12 UTC 2017


Branch:      refs/heads/master
Author:      Frank Lanitz <frank at frank.uvena.de>
Committer:   GitHub <noreply at github.com>
Date:        Sat, 19 Aug 2017 13:46:12 UTC
Commit:      59118d86ef8853943dec74c9f6d6fad9a535931a
             https://github.com/geany/geany-plugins/commit/59118d86ef8853943dec74c9f6d6fad9a535931a

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).


More information about the Plugins-Commits mailing list