;;; TODO(plundblad):
;;; - Allow adding comments outside of the diff directly from a target
;;; file.
;;; - Hide hunks with only imports etc.
;;; - Hide whitespace-only changes.
;;; - Fix path depotization to not assume google3 or eng.
;;; This elisp file adds a mode based on diff-mode to add comments to specific
;;; lines of files in the diff. It is intended to be used with files produced
;;; by g4 diff (it requires the target files to exist). In a diff file,
;;; enable this mode with crq-mode. You can also run g4 diff
;;; and enable crq mode with the command crq-review.
;;; Then press Enter on any line in the diff
;;; to open up a comment at the end of the file annotated with filename and
;;; line number.
;;;
;;; When done, enter any overall comments right below the "=== COMMENTS ==="
;;; line. The first line can be "LGTM" or "FYI" to add a vote. Then
;;; use the command crq-publish to upload and publish your comments to
;;; critique.
(require 'diff-mode)
(require 'p4-google)
(defconst crq-comments-header "=== COMMENTS ===\n")
(defconst crq-file-separator
"========================================================================\n")
(defconst crq-file-format
(concat crq-file-separator "File %s\n"))
(defconst crq-line-format
"------------------------------------\nLine %d: %s\n\n")
(defconst mch-program
(expand-file-name "mph.py"
(file-name-directory load-file-name)))
(defconst download-program
(expand-file-name "download_issue"
(file-name-directory load-file-name)))
(define-derived-mode crq-mode diff-mode "Crq"
"Special diff mode for code reviews
\\{crq-mode-map}"
(crq-ensure-header)
(crq-install-diff-map))
(define-key crq-mode-map "\C-c\C-p" 'crq-publish)
(defun crq-review (change-list-number)
"Run download script asynchronously on CHANGE-LIST-NUMBER (a string) and
activate crq-mode in the resulting buffer."
(interactive (list (read-change-list)))
(let ((process
(start-process "Gerrit Review" "gerrit-review" download-program change-list-number)))
(set-process-sentinel process 'crq-diff-sentinel)
(message "Gerrit download...")))
; TODO(plundblad): Perhaps run the python script asynchronously.
(defun crq-publish (arg)
"Publishes the comments in the current buffer to critique."
(interactive "P")
(goto-char (point-max))
(let ((result (call-process-region (point-min) (point-max) mch-program nil
(current-buffer) t
"/dev/stdin")))
(if (not (zerop result))
(progn
(message "Error running mch"))))
t)
(defun crq-comment ()
(interactive)
(let* ((loc (diff-find-source-location nil t))
(buf (nth 0 loc))
(pos (nth 2 loc))
(src (nth 3 loc))
(offset (+ (car pos) (cdr src)))
(line
(with-current-buffer buf
(save-excursion
(goto-char offset)
(thing-at-point 'line))))
(filename (buffer-file-name buf))
(lineno
(with-current-buffer buf
(save-excursion
(goto-char offset)
; count-lines counts the current line if point is not
; at the beginning of the line.
(beginning-of-line)
(1+ (count-lines (point-min) (point)))))))
(crq-ensure-header)
(crq-add-comment
(file-relative-name filename) lineno line)))
(defun crq-ensure-header ()
(save-excursion
(goto-char (point-min))
(if (not (search-forward crq-comments-header nil t))
(progn
(goto-char (point-max))
(insert ?\n crq-comments-header
?\n crq-file-separator)))))
(defvar crq-diff-map
(let ((map (make-sparse-keymap)))
(define-key map "\r" 'crq-comment)
map))
(defun crq-install-diff-map ()
(let* ((end
(save-excursion
(goto-char (point-max))
(unless (search-backward crq-comments-header nil t)
(error "No comment header"))
(point)))
(overlay (make-overlay (point-min) end)))
(overlay-put overlay 'keymap crq-diff-map)))
(defun crq-fix-filename (fullname)
(cond
((string-match "/\\(google3\\|eng\\)/.*$" fullname)
(concat "//depot" (match-string 0 fullname)))
((string-match "/\\(a\\|b\\)/.*$" fullname)
(substring (match-string 0 fullname) 3))
(t fullname)))
(defun crq-trim-line (line)
(if (string-match "^\\s-*\\(.*?\\)\\s-*$" line)
(match-string 1 line)
line))
(defun crq-add-comment (filename lineno text)
(push-mark)
(goto-char (point-max))
(forward-line -1)
(beginning-of-line)
(unless (looking-at crq-file-separator)
(error "Garbage at the end of the file"))
(insert (format crq-file-format filename))
(insert (format crq-line-format lineno
(crq-trim-line text)))
(forward-line -1))
(defun crq-diff-sentinel (process event)
(if (string= event "finished\n")
(progn
(message "Downloaded from gerrit")
(pop-to-buffer (process-buffer process))
(crq-mode)
(goto-char (point-min))
(search-forward "\t" nil t))
(progn
(message "g4 diff %s" event)
(ding))))
(provide 'crq)