emacsorphanage/git-gutter

[FR] stage selected region

bricewge opened this issue · 6 comments

I wish it would be possible to stage selected region of a buffer, like magit does. ATM git-gutter:stage-hunk can only stage a whole hunk.

Sorry this package is no longer maintained. And it is difficult such feature correctly.

Maybe misunderstood but I thought the members of @emacsorphanage were now maintaining emacs-git-gutter, especially @gonewest818. That's why I have opened this feature request. Can you keep it open, to track the possible enhancement of this package and let the new active maintainers choose if this FR is relevant?
Thank you greatly for all your work @syohex and for handing over the reins to the community!

Yes, let’s keep the issues open so that new maintainers can continue working on them.

Patch for staging region.

diff --git a/git-gutter.el b/git-gutter.el
index 01db6b2..1676b79 100644
--- a/git-gutter.el
+++ b/git-gutter.el
@@ -749,16 +749,17 @@ Can be a directory-local variable in your project.")
 
 (defun git-gutter:query-action (action action-fn update-fn)
   (git-gutter:awhen (git-gutter:search-here-diffinfo git-gutter:diffinfos)
-    (save-window-excursion
-      (when git-gutter:ask-p
-        (git-gutter:popup-hunk it))
-      (when (or (not git-gutter:ask-p)
-                (yes-or-no-p (format "%s current hunk ? " action)))
-        (funcall action-fn it)
-        (funcall update-fn))
-      (if git-gutter:ask-p
-          (delete-window (git-gutter:popup-buffer-window))
-        (message "%s current hunk." action)))))
+    (let ((diff-info (git-gutter:adjust-diff-info it)))
+      (save-window-excursion
+        (when git-gutter:ask-p
+          (git-gutter:popup-hunk diff-info))
+        (when (or (not git-gutter:ask-p)
+                  (yes-or-no-p (format "%s current hunk ? " action)))
+          (funcall action-fn diff-info)
+          (funcall update-fn))
+        (if git-gutter:ask-p
+            (delete-window (git-gutter:popup-buffer-window))
+          (message "%s current hunk." action))))))
 
 (defun git-gutter:revert-hunk ()
   "Revert current hunk."
@@ -809,6 +810,67 @@ Can be a directory-local variable in your project.")
   (let ((root (locate-dominating-file default-directory ".git")))
     (file-name-directory (file-relative-name (git-gutter:base-file) root))))
 
+(defun git-gutter:adjust-added-hunk (diff-info start-line end-line)
+  (unless (= (git-gutter-hunk-start-line diff-info) start-line)
+    (error "Invalid region. Staging region for added hunk must start from first line of this hunk"))
+  (let ((region-changes (1+ (- end-line start-line))))
+    (with-temp-buffer
+      (insert (git-gutter-hunk-content diff-info))
+      (goto-char (point-min))
+      ;; re-write header
+      (re-search-forward "\\+\\([0-9]+\\),\\([0-9]+\\)" nil t)
+      (let ((base-line (string-to-number (match-string-no-properties 1))))
+        (replace-match (number-to-string region-changes) t t nil 2)
+        (let ((end-offset (1+ (- end-line base-line))))
+          (forward-line (1+ end-offset))
+          (delete-region (point) (point-max))
+          (buffer-string))))))
+
+(defun git-gutter:adjust-diff-by-region (pre-keep-lines keep-lines post-keep-lines)
+  (let ((delete-fn (lambda (lines)
+                     (dotimes (_i lines)
+                       (let ((pos (point)))
+                         (forward-line 1)
+                         (delete-region pos (point)))))))
+    (funcall delete-fn pre-keep-lines)
+    (forward-line keep-lines)
+    (funcall delete-fn post-keep-lines)))
+
+(defun git-gutter:adjust-modified-hunk (diff-info start-line end-line)
+  (with-temp-buffer
+    (insert (git-gutter-hunk-content diff-info))
+    (goto-char (point-min))
+    (re-search-forward ",[0-9]+ \\+\\([0-9]+\\),[0-9]+" nil t)
+    (let* ((base-line (string-to-number (match-string-no-properties 1)))
+           (pre-keep-lines (- start-line base-line))
+           (keep-lines (1+ (- end-line start-line)))
+           (post-keep-lines (- (git-gutter-hunk-end-line diff-info) end-line))
+           (new-header (format ",%d +%d,%d" keep-lines base-line keep-lines)))
+      (replace-match new-header)
+      (forward-line 1)
+      ;; adjust '-' part
+      (git-gutter:adjust-diff-by-region pre-keep-lines keep-lines post-keep-lines)
+      (re-search-forward "^\\+" nil t)
+      (goto-char (match-beginning 0))
+      ;; adjust '+' part
+      (git-gutter:adjust-diff-by-region pre-keep-lines keep-lines post-keep-lines)
+      (buffer-string))))
+
+(defun git-gutter:adjust-diff-info (diff-info)
+  (let ((hunk-type (git-gutter-hunk-type diff-info)))
+    (if (or (not (use-region-p)) (not (memq hunk-type '(added modified))))
+        diff-info
+      (let ((start-line (max (line-number-at-pos (region-beginning))
+                             (git-gutter-hunk-start-line diff-info)))
+            (end-line (min (line-number-at-pos (region-end))
+                           (git-gutter-hunk-end-line diff-info))))
+        (let ((new-hunk (cl-case hunk-type
+                          (added (git-gutter:adjust-added-hunk diff-info start-line end-line))
+                          (modified (git-gutter:adjust-modified-hunk diff-info start-line end-line))))
+              (adjusted-hunk (copy-git-gutter-hunk diff-info)))
+          (setf (git-gutter-hunk-content adjusted-hunk) new-hunk)
+          adjusted-hunk)))))
+
 (defun git-gutter:do-stage-hunk (diff-info)
   (let ((content (git-gutter-hunk-content diff-info))
         (type (git-gutter-hunk-type diff-info))

Would a PR for this patch be merged?

I'm not sure that the above patch works well. I think I doesn't work well in some situations