From f08173073a065fa21bc7a8205ed67bd61b486169 Mon Sep 17 00:00:00 2001 From: Chen Bin Date: Sat, 21 Sep 2024 19:16:04 +1000 Subject: [PATCH] upgrade git-gutter --- site-lisp/git-gutter/git-gutter.el | 539 +++++++++++++++++------------ 1 file changed, 313 insertions(+), 226 deletions(-) diff --git a/site-lisp/git-gutter/git-gutter.el b/site-lisp/git-gutter/git-gutter.el index 60bdad8fc..6e137a8fd 100644 --- a/site-lisp/git-gutter/git-gutter.el +++ b/site-lisp/git-gutter/git-gutter.el @@ -1,11 +1,15 @@ ;;; git-gutter.el --- Port of Sublime Text plugin GitGutter -*- lexical-binding: t; -*- -;; Copyright (C) 2016 by Syohei YOSHIDA +;; Copyright (C) 2016-2020 Syohei YOSHIDA +;; Copyright (C) 2020-2022 Neil Okamoto +;; Copyright (C) 2020-2024 Shen, Jen-Chieh ;; Author: Syohei YOSHIDA -;; URL: https://github.com/syohex/emacs-git-gutter -;; Version: 0.90 -;; Package-Requires: ((emacs "24.3")) +;; Maintainer: Neil Okamoto +;; Shen, Jen-Chieh +;; URL: https://github.com/emacsorphanage/git-gutter +;; Version: 0.94 +;; Package-Requires: ((emacs "25.1")) ;; 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 @@ -33,98 +37,110 @@ :prefix "git-gutter:" :group 'vc) -(defcustom git-gutter:exp-to-create-diff nil - "It could be string, expression, function. -If it's a string, it's passed to `shell-command-to-string' as parameter. -If it's an expression, it will be evaluated. -If it's a function, it is executed. -The returned string is expected to be in diff Unified Format. -When it's not nil, it will overrides all the existing backends. -It's recommended to set it up in `.dir-locals.el'. -A sample `.dir-locals.el' for perforce: -`((nil (git-gutter:exp-to-create-diff . (shell-command-to-string (format \"p4 diff -du -db %s\" (file-relative-name buffer-file-name))))))'") +(defvar git-gutter-mode nil) (defcustom git-gutter:window-width nil - "Character width of gutter window. Emacs mistakes width of some characters. + "Character width of gutter window. Emacs mistakes width of some characters. It is better to explicitly assign width to this variable, if you use full-width character for signs of changes" - :type 'integer) + :type 'integer + :group 'git-gutter) (defcustom git-gutter:diff-option "" - "Option of 'git diff'" - :type 'string) + "Option of 'git diff'." + :type 'string + :group 'git-gutter) (defcustom git-gutter:subversion-diff-option "" - "Option of 'svn diff'" - :type 'string) + "Option of 'svn diff'." + :type 'string + :group 'git-gutter) (defcustom git-gutter:mercurial-diff-option "" - "Option of 'hg diff'" - :type 'string) + "Option of 'hg diff'." + :type 'string + :group 'git-gutter) (defcustom git-gutter:bazaar-diff-option "" - "Option of 'bzr diff'" - :type 'string) + "Option of 'bzr diff'." + :type 'string + :group 'git-gutter) (defcustom git-gutter:update-commands '(ido-switch-buffer helm-buffers-list) "Each command of this list is executed, gutter information is updated." :type '(list (function :tag "Update command") - (repeat :inline t (function :tag "Update command")))) + (repeat :inline t (function :tag "Update command"))) + :group 'git-gutter) (defcustom git-gutter:update-windows-commands '(kill-buffer ido-kill-buffer) - "Each command of this list is executed, gutter information is updated and -gutter information of other windows." + "Each command of this list is executed, gutter information is +updated and gutter information of other windows." :type '(list (function :tag "Update command") - (repeat :inline t (function :tag "Update command")))) + (repeat :inline t (function :tag "Update command"))) + :group 'git-gutter) (defcustom git-gutter:update-hooks - '(after-save-hook after-revert-hook find-file-hook after-change-major-mode-hook + '(after-save-hook + after-revert-hook + find-file-hook + after-change-major-mode-hook text-scale-mode-hook) - "hook points of updating gutter" + "Hook points of updating gutter." :type '(list (hook :tag "HookPoint") - (repeat :inline t (hook :tag "HookPoint")))) + (repeat :inline t (hook :tag "HookPoint"))) + :group 'git-gutter) (defcustom git-gutter:always-show-separator nil "Show separator even if there are no changes." - :type 'boolean) + :type 'boolean + :group 'git-gutter) (defcustom git-gutter:separator-sign nil - "Separator sign" - :type 'string) + "Separator sign." + :type 'string + :group 'git-gutter) (defcustom git-gutter:modified-sign "=" - "Modified sign" - :type 'string) + "Modified sign." + :type 'string + :group 'git-gutter) (defcustom git-gutter:added-sign "+" - "Added sign" - :type 'string) + "Added sign." + :type 'string + :group 'git-gutter) (defcustom git-gutter:deleted-sign "-" - "Deleted sign" - :type 'string) + "Deleted sign." + :type 'string + :group 'git-gutter) (defcustom git-gutter:unchanged-sign nil - "Unchanged sign" - :type 'string) + "Unchanged sign." + :type 'string + :group 'git-gutter) (defcustom git-gutter:hide-gutter nil - "Hide gutter if there are no changes" - :type 'boolean) + "Hide gutter if there are no changes." + :type 'boolean + :group 'git-gutter) (defcustom git-gutter:lighter " GitGutter" - "Minor mode lighter in mode-line" - :type 'string) + "Minor mode lighter in mode-line." + :type 'string + :group 'git-gutter) (defcustom git-gutter:verbosity 0 - "Log/message level. 4 means all, 0 nothing." - :type 'integer) + "Log/message level. 4 means all, 0 nothing." + :type 'integer + :group 'git-gutter) (defcustom git-gutter:visual-line nil "Show sign at gutter by visual line." - :type 'boolean) + :type 'boolean + :group 'git-gutter) (defface git-gutter:separator '((t (:foreground "cyan" :weight bold :inherit default))) @@ -139,65 +155,79 @@ gutter information of other windows." "Face of added") (defface git-gutter:deleted - '((t (:foreground "red" :weight bold))) + '((t (:foreground "red" :weight bold :inherit default))) "Face of deleted") (defface git-gutter:unchanged - '((t (:background "yellow"))) + '((t (:background "yellow" :inherit default))) "Face of unchanged") (defcustom git-gutter:disabled-modes nil "A list of modes which `global-git-gutter-mode' should be disabled." - :type '(repeat symbol)) + :type '(repeat symbol) + :group 'git-gutter) (defcustom git-gutter:handled-backends '(git) "List of version control backends for which `git-gutter.el` will be used. `git', `svn', `hg', and `bzr' are supported." - :type '(repeat symbol)) + :type '(repeat symbol) + :group 'git-gutter) (defvar git-gutter:view-diff-function #'git-gutter:view-diff-infos - "Function of viewing changes") + "Function of viewing changes.") (defvar git-gutter:clear-function #'git-gutter:clear-diff-infos - "Function of clear changes") + "Function of clear changes.") (defvar git-gutter:init-function 'nil - "Function of initialize") + "Function of initialize.") (defcustom git-gutter-mode-on-hook nil - "Hook run when git-gutter mode enable" - :type 'hook) + "Hook run when git-gutter mode enable." + :type 'hook + :group 'git-gutter) (defcustom git-gutter-mode-off-hook nil - "Hook run when git-gutter mode disable" - :type 'hook) + "Hook run when git-gutter mode disable." + :type 'hook + :group 'git-gutter) (defcustom git-gutter:update-interval 0 "Time interval in seconds for updating diff information." - :type 'integer) + :type 'integer + :group 'git-gutter) (defcustom git-gutter:ask-p t - "Ask whether commit/revert or not" - :type 'boolean) + "Ask whether commit/revert or not." + :type 'boolean + :group 'git-gutter) (defcustom git-gutter:display-p t "Display diff information or not." - :type 'boolean) + :type 'boolean + :group 'git-gutter) + +(defvar git-gutter:start-revision nil + "Starting revision for vc diffs. +Can be a directory-local variable in your project.") + +(make-variable-buffer-local 'git-gutter:start-revision) +(put 'git-gutter:start-revision 'safe-local-variable + (lambda (x) (or (booleanp x) (stringp x)))) (cl-defstruct git-gutter-hunk type content start-line end-line) -(defvar git-gutter:enabled nil) +(defvar-local git-gutter:enabled nil) (defvar git-gutter:diffinfos nil) (defvar git-gutter:has-indirect-buffers nil) (defvar git-gutter:real-this-command nil) (defvar git-gutter:linum-enabled nil) (defvar git-gutter:linum-prev-window-margin nil) (defvar git-gutter:vcs-type nil) -(defvar git-gutter:start-revision nil) (defvar git-gutter:revision-history nil) (defvar git-gutter:update-timer nil) -(defvar git-gutter:last-sha1 nil) +(defvar-local git-gutter:last-chars-modified-tick nil) (defvar git-gutter:popup-buffer "*git-gutter:diff*") (defvar git-gutter:ignore-commands @@ -208,7 +238,8 @@ gutter information of other windows." helm-confirm-and-exit-minibuffer)) (defmacro git-gutter:awhen (test &rest body) - "Anaphoric when." + "Anaphoric when. +Argument TEST is the case before BODY execution." (declare (indent 1)) `(let ((it ,test)) (when it ,@body))) @@ -217,17 +248,20 @@ gutter information of other windows." (apply #'process-file cmd nil output nil args)) (defun git-gutter:in-git-repository-p () - (when (executable-find "git") + (when (executable-find "git" t) (with-temp-buffer - (when (zerop (git-gutter:execute-command "git" t "rev-parse" "--is-inside-work-tree")) - (goto-char (point-min)) - (looking-at-p "true"))))) + (when-let ((exec-result (git-gutter:execute-command + "git" t "rev-parse" "--is-inside-work-tree"))) + (when (zerop exec-result) + (goto-char (point-min)) + (looking-at-p "true")))))) (defun git-gutter:in-repository-common-p (cmd check-subcmd repodir) - (and (executable-find cmd) + (and (executable-find cmd t) (locate-dominating-file default-directory repodir) (zerop (apply #'git-gutter:execute-command cmd nil check-subcmd)) - (not (string-match-p (regexp-quote (concat "/" repodir "/")) default-directory)))) + (not (string-match-p (regexp-quote (concat "/" repodir "/")) + default-directory)))) (defun git-gutter:vcs-check-function (vcs) (cl-case vcs @@ -237,13 +271,9 @@ gutter information of other windows." (bzr (git-gutter:in-repository-common-p "bzr" '("root") ".bzr")))) (defun git-gutter:in-repository-p () - (cond - (git-gutter:exp-to-create-diff - (setq git-gutter:vcs-type 'unknown)) - (t - (cl-loop for vcs in git-gutter:handled-backends - when (git-gutter:vcs-check-function vcs) - return (setq-local git-gutter:vcs-type vcs))))) + (cl-loop for vcs in git-gutter:handled-backends + when (git-gutter:vcs-check-function vcs) + return (setq-local git-gutter:vcs-type vcs))) (defsubst git-gutter:changes-to-number (str) (if (string= str "") @@ -263,12 +293,15 @@ gutter information of other windows." (goto-char (point-max))) (buffer-substring curpoint (point))))) +(defvar git-gutter:diff-output-regexp + "^@@ -\\(?:[0-9]+\\),?\\([0-9]*\\) \\+\\([0-9]+\\),?\\([0-9]*\\) @@" + "Parse diff output.") + (defun git-gutter:process-diff-output (buf) (when (buffer-live-p buf) (with-current-buffer buf (goto-char (point-min)) - (cl-loop with regexp = "^@@ -\\(?:[0-9]+\\),?\\([0-9]*\\) \\+\\([0-9]+\\),?\\([0-9]*\\) @@" - while (re-search-forward regexp nil t) + (cl-loop while (re-search-forward git-gutter:diff-output-regexp nil t) for new-line = (string-to-number (match-string 2)) for orig-changes = (git-gutter:changes-to-number (match-string 1)) for new-changes = (git-gutter:changes-to-number (match-string 3)) @@ -302,6 +335,7 @@ gutter information of other windows." (setq args (nreverse (split-string git-gutter:diff-option)))) (when (git-gutter:revision-set-p) (push git-gutter:start-revision args)) + (push "--" args) (nreverse (cons file args)))) (defun git-gutter:start-git-diff-process (file proc-buf) @@ -361,29 +395,30 @@ gutter information of other windows." (bzr (git-gutter:start-bzr-diff-process file proc-buf)))) (defun git-gutter:start-diff-process (curfile proc-buf) - (git-gutter:set-window-margin (git-gutter:window-margin)) - (cond - (git-gutter:exp-to-create-diff - (git-gutter:insert-diff-content proc-buf) - (git-gutter:update-current-buffer proc-buf) - (kill-buffer proc-buf)) - (t - (let ((file (git-gutter:base-file)) ;; for tramp - (process (git-gutter:start-diff-process1 curfile proc-buf))) - (set-process-query-on-exit-flag process nil) - (set-process-sentinel - process - (lambda (proc _event) - (when (eq (process-status proc) 'exit) - (git-gutter:update-current-buffer (process-buffer proc) file) - (kill-buffer proc-buf)))))))) - -(defsubst git-gutter:gutter-sperator () + (let ((file (git-gutter:base-file)) ;; for tramp + (curbuf (current-buffer)) + (process (git-gutter:start-diff-process1 curfile proc-buf))) + (set-process-query-on-exit-flag process nil) + (set-process-sentinel + process + (lambda (proc _event) + (when (eq (process-status proc) 'exit) + (setq git-gutter:enabled nil) + (let ((diffinfos (git-gutter:process-diff-output (process-buffer proc)))) + (when (buffer-live-p curbuf) + (with-current-buffer curbuf + (git-gutter:update-diffinfo diffinfos) + (when git-gutter:has-indirect-buffers + (git-gutter:update-indirect-buffers file)) + (setq git-gutter:enabled t))) + (kill-buffer proc-buf))))))) + +(defsubst git-gutter:gutter-seperator () (when git-gutter:separator-sign (propertize git-gutter:separator-sign 'face 'git-gutter:separator))) (defun git-gutter:before-string (sign) - (let ((gutter-sep (concat sign (git-gutter:gutter-sperator)))) + (let ((gutter-sep (concat sign (git-gutter:gutter-seperator)))) (propertize " " 'display `((margin left-margin) ,gutter-sep)))) (defun git-gutter:propertized-sign (type) @@ -395,6 +430,10 @@ gutter information of other windows." face 'git-gutter:modified)) (deleted (setq sign git-gutter:deleted-sign face 'git-gutter:deleted))) + (when (get-text-property 0 'face sign) + (setq face (append + (get-text-property 0 'face sign) + `(:inherit ,face)))) (propertize sign 'face face))) (defsubst git-gutter:linum-get-overlay (pos) @@ -435,10 +474,20 @@ gutter information of other windows." (defun git-gutter:next-visual-line (arg) (let ((line-move-visual t)) - (with-no-warnings - (next-line arg)))) - -(defun git-gutter:view-for-unchanged () + (or (ignore-errors + ;; next-line raises exception at end of buffer + (with-no-warnings + (next-line arg)) + t) + (goto-char (point-max))))) + +(defun git-gutter:unchanged-line-p (line diffinfos) + (cl-loop for info in diffinfos + for start = (git-gutter-hunk-start-line info) + for end = (git-gutter-hunk-end-line info) + never (and (>= line start) (<= line end)))) + +(defun git-gutter:view-for-unchanged (diffinfos) (save-excursion (let ((sign (if git-gutter:unchanged-sign (propertize git-gutter:unchanged-sign @@ -450,7 +499,8 @@ gutter information of other windows." points) (goto-char (point-min)) (while (not (eobp)) - (push (point) points) + (when (git-gutter:unchanged-line-p (line-number-at-pos) diffinfos) + (push (point) points)) (funcall move-fn 1)) (git-gutter:put-signs sign points)))) @@ -481,7 +531,8 @@ gutter information of other windows." ((memq git-gutter:real-this-command git-gutter:update-windows-commands) (git-gutter) (unless (bound-and-true-p global-linum-mode) - (git-gutter:update-other-window-buffers (selected-window) (current-buffer)))))) + (git-gutter:update-other-window-buffers (selected-window) + (current-buffer)))))) (defsubst git-gutter:diff-process-buffer (curfile) (concat " *git-gutter-" curfile "-*")) @@ -520,19 +571,21 @@ gutter information of other windows." (setq-local git-gutter:linum-enabled t) (make-local-variable 'git-gutter:linum-prev-window-margin)) +(defun git-gutter:linum-update-window (&rest _args) + (when git-gutter:display-p + (if (and git-gutter-mode git-gutter:diffinfos) + (git-gutter:linum-update git-gutter:diffinfos) + (let ((curwin (get-buffer-window)) + (margin (or git-gutter:linum-prev-window-margin + (car (window-margins))))) + (set-window-margins curwin margin (cdr (window-margins curwin))))))) + ;;;###autoload (defun git-gutter:linum-setup () "Setup for linum-mode." (setq git-gutter:init-function 'git-gutter:linum-init git-gutter:view-diff-function nil) - (defadvice linum-update-window (after git-gutter:linum-update-window activate) - (when git-gutter:display-p - (if (and git-gutter-mode git-gutter:diffinfos) - (git-gutter:linum-update git-gutter:diffinfos) - (let ((curwin (get-buffer-window)) - (margin (or git-gutter:linum-prev-window-margin - (car (window-margins))))) - (set-window-margins curwin margin (cdr (window-margins curwin)))))))) + (advice-add 'linum-update-window :after #'git-gutter:linum-update-window)) (defun git-gutter:show-backends () (mapconcat (lambda (backend) @@ -551,24 +604,24 @@ gutter information of other windows." (progn (when git-gutter:init-function (funcall git-gutter:init-function)) - (make-local-variable 'git-gutter:enabled) - (setq-local git-gutter:has-indirect-buffers nil) (make-local-variable 'git-gutter:diffinfos) - (setq-local git-gutter:start-revision nil) + ;;(setq-local git-gutter:start-revision nil) (add-hook 'kill-buffer-hook 'git-gutter:kill-buffer-hook nil t) - (add-hook 'pre-command-hook 'git-gutter:pre-command-hook) + (add-hook 'pre-command-hook 'git-gutter:pre-command-hook t) (add-hook 'post-command-hook 'git-gutter:post-command-hook nil t) (dolist (hook git-gutter:update-hooks) (add-hook hook 'git-gutter nil t)) (git-gutter) - (when (and (not git-gutter:update-timer) (> git-gutter:update-interval 0)) + (when (and (not git-gutter:update-timer) + (> git-gutter:update-interval 0)) (setq git-gutter:update-timer - (run-with-idle-timer git-gutter:update-interval t 'git-gutter:live-update)))) + (run-with-idle-timer + git-gutter:update-interval t 'git-gutter:live-update)))) (when (> git-gutter:verbosity 2) (message "Here is not %s work tree" (git-gutter:show-backends))) (git-gutter-mode -1)) (remove-hook 'kill-buffer-hook 'git-gutter:kill-buffer-hook t) - (remove-hook 'pre-command-hook 'git-gutter:pre-command-hook) + (remove-hook 'pre-command-hook 'git-gutter:pre-command-hook t) (remove-hook 'post-command-hook 'git-gutter:post-command-hook t) (dolist (hook git-gutter:update-hooks) (remove-hook hook 'git-gutter t)) @@ -593,7 +646,7 @@ gutter information of other windows." (defun git-gutter:view-set-overlays (diffinfos) (when (or git-gutter:unchanged-sign git-gutter:separator-sign) - (git-gutter:view-for-unchanged)) + (git-gutter:view-for-unchanged diffinfos)) (save-excursion (goto-char (point-min)) (cl-loop with curline = 1 @@ -642,6 +695,7 @@ gutter information of other windows." (when git-gutter:clear-function (funcall git-gutter:clear-function))) (setq git-gutter:enabled nil + git-gutter:last-chars-modified-tick nil git-gutter:diffinfos nil)) (defun git-gutter:update-diffinfo (diffinfos) @@ -717,7 +771,8 @@ gutter information of other windows." (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))) + (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 @@ -740,19 +795,23 @@ gutter information of other windows." (forward-line 4) (buffer-substring-no-properties (point-min) (point)))))) +(defvar git-gutter:git-hunk-header-regexp + "^@@ -\\([0-9]+\\),?\\([0-9]*\\) \\+\\([0-9]+\\),?\\([0-9]*\\) @@" + "Parse git hunk header.") + (defun git-gutter:read-hunk-header (header) - (let ((header-regexp "^@@ -\\([0-9]+\\),?\\([0-9]*\\) \\+\\([0-9]+\\),?\\([0-9]*\\) @@")) - (when (string-match header-regexp header) - (list (string-to-number (match-string 1 header)) - (git-gutter:changes-to-number (match-string 2 header)) - (string-to-number (match-string 3 header)) - (git-gutter:changes-to-number (match-string 4 header)))))) + (when (string-match git-gutter:git-hunk-header-regexp header) + (list (string-to-number (match-string 1 header)) + (git-gutter:changes-to-number (match-string 2 header)) + (string-to-number (match-string 3 header)) + (git-gutter:changes-to-number (match-string 4 header))))) (defun git-gutter:convert-hunk-header (type) (let ((header (buffer-substring-no-properties (point) (line-end-position)))) (delete-region (point) (line-end-position)) (cl-destructuring-bind - (orig-line orig-changes new-line new-changes) (git-gutter:read-hunk-header header) + (orig-line orig-changes new-line new-changes) + (git-gutter:read-hunk-header header) (cl-case type (added (setq new-line (1+ orig-line))) (t (setq new-line orig-line))) @@ -825,7 +884,7 @@ gutter information of other windows." (git-gutter:awhen (or diffinfo (git-gutter:search-here-diffinfo git-gutter:diffinfos)) (save-selected-window - (pop-to-buffer (git-gutter:update-popuped-buffer it))))) + (display-buffer (git-gutter:update-popuped-buffer it))))) (defun git-gutter:next-hunk (arg) "Move to next diff hunk" @@ -833,21 +892,23 @@ gutter information of other windows." (if (not git-gutter:diffinfos) (when (> git-gutter:verbosity 3) (message "There are no changes!!")) - (let* ((is-reverse (< arg 0)) - (diffinfos git-gutter:diffinfos) - (len (length diffinfos)) - (index (git-gutter:search-near-diff-index diffinfos is-reverse)) - (real-index (if index - (let ((next (if is-reverse (1+ index) (1- index)))) - (mod (+ arg next) len)) - (if is-reverse (1- len) 0))) - (diffinfo (nth real-index diffinfos))) - (goto-char (point-min)) - (forward-line (1- (git-gutter-hunk-start-line diffinfo))) - (when (> git-gutter:verbosity 0) - (message "Move to %d/%d hunk" (1+ real-index) len)) - (when (buffer-live-p (get-buffer git-gutter:popup-buffer)) - (git-gutter:update-popuped-buffer diffinfo))))) + (save-restriction + (widen) + (let* ((is-reverse (< arg 0)) + (diffinfos git-gutter:diffinfos) + (len (length diffinfos)) + (index (git-gutter:search-near-diff-index diffinfos is-reverse)) + (real-index (if index + (let ((next (if is-reverse (1+ index) (1- index)))) + (mod (+ arg next) len)) + (if is-reverse (1- len) 0))) + (diffinfo (nth real-index diffinfos))) + (goto-char (point-min)) + (forward-line (1- (git-gutter-hunk-start-line diffinfo))) + (when (> git-gutter:verbosity 0) + (message "Move to %d/%d hunk" (1+ real-index) len)) + (when (buffer-live-p (get-buffer git-gutter:popup-buffer)) + (git-gutter:update-popuped-buffer diffinfo)))))) (defun git-gutter:previous-hunk (arg) "Move to previous diff hunk" @@ -888,30 +949,49 @@ gutter information of other windows." (when (and (called-interactively-p 'interactive) (get-buffer proc-buf)) (kill-buffer proc-buf)) (when (and file (file-exists-p file) (not (get-buffer proc-buf))) - (git-gutter:start-diff-process (file-name-nondirectory file) + (git-gutter:start-diff-process (file-name-nondirectory file) (get-buffer-create proc-buf)))))) -(defadvice make-indirect-buffer (before git-gutter:has-indirect-buffers activate) - (when (and git-gutter-mode (not (buffer-base-buffer))) - (setq git-gutter:has-indirect-buffers t))) - -(defadvice vc-revert (after git-gutter:vc-revert activate) +(defun git-gutter:kill-indirect-buffer () + (with-current-buffer (buffer-base-buffer) + (when git-gutter:has-indirect-buffers + (if (< 1 git-gutter:has-indirect-buffers) + (setq git-gutter:has-indirect-buffers (1- git-gutter:has-indirect-buffers)) + (kill-local-variable 'git-gutter:has-indirect-buffers))))) + +(defun git-gutter:make-indirect-buffer (oldfun base-buffer &rest args) + (with-current-buffer (or (buffer-base-buffer (window-normalize-buffer base-buffer)) + base-buffer) + (if git-gutter:has-indirect-buffers + (setq git-gutter:has-indirect-buffers (1+ git-gutter:has-indirect-buffers)) + (setq-local git-gutter:has-indirect-buffers 1)) + (with-current-buffer (apply oldfun base-buffer args) + (add-hook 'kill-buffer-hook #'git-gutter:kill-indirect-buffer nil t) + (current-buffer)))) +(advice-add 'make-indirect-buffer :around #'git-gutter:make-indirect-buffer) + +(defun git-gutter:vc-revert (&rest _args) (when git-gutter-mode (run-with-idle-timer 0.1 nil 'git-gutter))) +(advice-add 'vc-revert :after #'git-gutter:vc-revert) -(defadvice toggle-truncate-lines (after git-gutter:toggle-truncate-lines activate) +(defun git-gutter:toggle-truncate-lines (&rest _args) (when (and git-gutter-mode git-gutter:visual-line) (run-with-idle-timer 0.1 nil 'git-gutter))) +(advice-add 'toggle-truncate-lines :after #'git-gutter:toggle-truncate-lines) ;; `quit-window' and `switch-to-buffer' are called from other -;; commands. So we should use `defadvice' instead of `post-command-hook'. -(defadvice quit-window (after git-gutter:quit-window activate) +;; commands. So calling git-gutter from `post-command-hook' is not enough, use +;; advices instead. +(defun git-gutter:quit-window (&rest _args) (when git-gutter-mode (git-gutter))) +(advice-add 'quit-window :after #'git-gutter:quit-window) -(defadvice switch-to-buffer (after git-gutter:switch-to-buffer activate) +(defun git-gutter:switch-to-buffer (&rest _args) (when git-gutter-mode (git-gutter))) +(advice-add 'switch-to-buffer :after #'git-gutter:switch-to-buffer) (defun git-gutter:clear () "Clear diff information in gutter." @@ -954,8 +1034,8 @@ start revision." (defun git-gutter:update-all-windows () "Update git-gutter information for all visible buffers." (interactive) - (dolist (win (window-list)) - (let ((buf (window-buffer win))) + (dolist (buf (buffer-list)) + (when (get-buffer-window buf 'visible) (with-current-buffer buf (when git-gutter-mode (git-gutter)))))) @@ -979,13 +1059,19 @@ start revision." (with-temp-file tmpfile (insert content)))) -(defsubst git-gutter:original-file-content (file) +(defun git-gutter:original-file-content (file vcs) (with-temp-buffer - (when (zerop (process-file "git" nil t nil "show" (concat ":" file))) - (buffer-substring-no-properties (point-min) (point-max))))) + (cl-case vcs + (git + (when (zerop (process-file "git" nil t nil "show" (concat ":" file))) + (buffer-substring-no-properties (point-min) (point-max)))) + ((svn hg bzr) + (let ((command (symbol-name vcs))) + (when (zerop (process-file command nil t nil "cat" file)) + (buffer-substring-no-properties (point-min) (point-max)))))))) (defun git-gutter:write-original-content (tmpfile filename) - (git-gutter:awhen (git-gutter:original-file-content filename) + (git-gutter:awhen (git-gutter:original-file-content filename git-gutter:vcs-type) (with-temp-file tmpfile (insert it) t))) @@ -994,79 +1080,70 @@ start revision." (start-file-process "git-gutter:update-timer" proc-buf "diff" "-U0" original now)) -(defun git-gutter:update-current-buffer (diff-buf &optional file) - (setq git-gutter:enabled nil) - (let* ((curbuf (current-buffer)) - (diffinfos (git-gutter:process-diff-output diff-buf))) - (when (buffer-live-p curbuf) - (with-current-buffer curbuf - (git-gutter:update-diffinfo diffinfos) - (if (and file git-gutter:has-indirect-buffers) - (git-gutter:update-indirect-buffers file)) - (setq git-gutter:enabled t))))) - -(defun git-gutter:insert-diff-content (buf) - (let* ((backend git-gutter:exp-to-create-diff) - (content (cond - ;; shell command - ((stringp backend) - (shell-command-to-string backend)) - ;; function - ((functionp backend) - (funcall backend)) - ;; lisp expression - ((consp backend) - (funcall `(lambda () ,backend)))))) - - (save-current-buffer - (set-buffer buf) - (erase-buffer) - (insert content)))) - (defun git-gutter:start-live-update (file original now) - (let* (proc-buf - (proc-bufname (git-gutter:diff-process-buffer file))) - (if (get-buffer proc-bufname) - (kill-buffer proc-bufname)) - (setq proc-buf (get-buffer-create proc-bufname)) - (cond - (git-gutter:exp-to-create-diff - (git-gutter:insert-diff-content proc-buf) - (git-gutter:update-current-buffer proc-buf) - (kill-buffer proc-buf) - (delete-file original) - (delete-file now)) - (t - (let* ((process (git-gutter:start-raw-diff-process proc-buf original now))) - (set-process-query-on-exit-flag process nil) - (set-process-sentinel - process - (lambda (proc _event) - (when (eq (process-status proc) 'exit) - (git-gutter:update-current-buffer (process-buffer proc)) - (kill-buffer proc-buf) - (delete-file original) - (delete-file now))))))))) + (let ((proc-bufname (git-gutter:diff-process-buffer file))) + (when (get-buffer proc-bufname) + (kill-buffer proc-bufname)) + (let* ((curbuf (current-buffer)) + (proc-buf (get-buffer-create proc-bufname)) + (process (git-gutter:start-raw-diff-process proc-buf original now))) + (set-process-query-on-exit-flag process nil) + (set-process-sentinel + process + (lambda (proc _event) + (when (eq (process-status proc) 'exit) + (setq git-gutter:enabled nil) + (let ((diffinfos (git-gutter:process-diff-output (process-buffer proc)))) + (when (buffer-live-p curbuf) + (with-current-buffer curbuf + (git-gutter:update-diffinfo diffinfos) + (setq git-gutter:enabled t))) + (kill-buffer proc-buf) + (delete-file original) + (delete-file now)))))))) (defun git-gutter:should-update-p () - (let ((sha1 (secure-hash 'sha1 (current-buffer)))) - (unless (equal sha1 git-gutter:last-sha1) - (setq git-gutter:last-sha1 sha1)))) + (let ((chars-modified-tick (buffer-chars-modified-tick))) + (unless (equal chars-modified-tick git-gutter:last-chars-modified-tick) + (setq git-gutter:last-chars-modified-tick chars-modified-tick)))) + +(defun git-gutter:vcs-root (vcs) + (with-temp-buffer + (cl-case vcs + (git + (when (zerop (process-file "git" nil t nil "rev-parse" "--show-toplevel")) + (goto-char (point-min)) + (file-name-as-directory + (buffer-substring-no-properties (point) (line-end-position))))) + (svn + (when (zerop (process-file "svn" nil t nil "info")) + (goto-char (point-min)) + (when (re-search-forward "^Working Copy Root Path: \(.+\)$" nil t) + (file-name-as-directory (match-string-no-properties 1))))) + ((hg bzr) + (let ((command (symbol-name vcs))) + (when (zerop (process-file command nil t nil "root")) + (goto-char (point-min)) + (file-name-as-directory + (buffer-substring-no-properties (point) (line-end-position))))))))) (defun git-gutter:live-update () (git-gutter:awhen (git-gutter:base-file) (when (and git-gutter:enabled - (buffer-modified-p) (git-gutter:should-update-p)) (let ((file (file-name-nondirectory it)) + (root (file-truename (git-gutter:vcs-root git-gutter:vcs-type))) (now (make-temp-file "git-gutter-cur")) (original (make-temp-file "git-gutter-orig"))) - (when (git-gutter:write-original-content original file) - (git-gutter:write-current-content now) - (git-gutter:start-live-update file original now)))))) + (if (git-gutter:write-original-content original (file-relative-name it root)) + (progn + (git-gutter:write-current-content now) + (git-gutter:start-live-update file original now)) + (delete-file now) + (delete-file original)))))) ;; for linum-user -(when (and (bound-and-true-p global-linum-mode) (not (boundp 'git-gutter-fringe))) +(when (and (and (boundp 'global-linum-mode) global-linum-mode) (not (boundp 'git-gutter-fringe))) (git-gutter:linum-setup)) (defun git-gutter:all-hunks () @@ -1094,8 +1171,12 @@ start revision." ((looking-at-p "\\-") (cl-incf deleted))) (forward-line 1)) (cons added deleted)))) - (added (cons (- (git-gutter-hunk-end-line hunk) (git-gutter-hunk-start-line hunk)) 0)) - (deleted (cons 0 (- (git-gutter-hunk-end-line hunk) (git-gutter-hunk-start-line hunk)))))) + (added (cons (- (git-gutter-hunk-end-line hunk) + (git-gutter-hunk-start-line hunk)) + 0)) + (deleted (cons 0 + (- (git-gutter-hunk-end-line hunk) + (git-gutter-hunk-start-line hunk)))))) (defun git-gutter:statistic () "Return statistic unstaged hunks in current buffer." @@ -1113,3 +1194,9 @@ start revision." (provide 'git-gutter) ;;; git-gutter.el ends here + +;; Local Variables: +;; fill-column: 85 +;; indent-tabs-mode: nil +;; elisp-lint-indent-specs: ((git-gutter:awhen . 1)) +;; End: