emacs: Gerrit Summary of Comments
The "gerrit-comments" commant presents a magit-like buffer that acts
as an interface to discover Gerrit comments and quickly navigate to
them on the user's system from their emacs.
BUG=chromium:1101373
TEST=Evaluated, initialized values, and used new interface.
Change-Id: I9bf0061eaeb9f157fe1a9015488733fa7adf5d30
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/2320468
Reviewed-by: Jack Rosenthal <jrosenth@chromium.org>
Reviewed-by: Sean McAllister <smcallis@google.com>
Tested-by: Aaron Massey <aaronmassey@chromium.org>
Commit-Queue: Aaron Massey <aaronmassey@chromium.org>
diff --git a/contrib/emacs/gerrit-section.el b/contrib/emacs/gerrit-section.el
new file mode 100644
index 0000000..e881689
--- /dev/null
+++ b/contrib/emacs/gerrit-section.el
@@ -0,0 +1,211 @@
+;; -*- lexical-binding: t -*-
+
+;; copyright 2020 the chromium os authors. all rights reserved.
+;; use of this source code is governed by a bsd-style license that can be
+;; found in the license file.
+
+(require 'magit)
+(require 'magit-section)
+(require 'repo-gerrit)
+
+(defface gerrit-patch
+ `((t :inherit magit-section-heading))
+ "patch")
+
+
+(defface gerrit-filepath
+ `((t :inherit magit-branch-remote))
+ "filepath")
+
+
+(defconst gerrit-buffer-name "gerrit-comments"
+ "The name of the buffer where the gerrit summary is placed.")
+
+
+(defconst gerrit-section-mode-map magit-section-mode-map
+ "Mode map for Gerrit Summary - copies standard magit map")
+
+
+(defconst gerrit-section-type (gensym)
+ "Magit Sections need a symbol for a section type.
+We don't currently use this functionality.")
+
+
+(define-derived-mode gerrit-section-mode magit-section-mode
+ "Gerrit-Repo"
+ "Mode for displaying Gerrit comments within Emacs."
+ (when (fboundp 'evil-set-initial-state)
+ ;; Evil Mode doesn't always play nice with the keymaps.
+ (evil-set-initial-state 'gerrit-section-mode 'emacs)))
+
+
+
+(defun gerrit-refresh ()
+ "Refreshes Gerrit data from hosts."
+ (interactive)
+ (setf gerrit--refreshed t)
+ (gerrit-init)
+ (when (get-buffer gerrit-buffer-name)
+ (save-excursion
+ (switch-to-buffer gerrit-buffer-name)
+ (setf inhibit-read-only t)
+ (erase-buffer)
+ (setf inhibit-read-only t)))
+ (gerrit-comments t))
+
+
+(defun gerrit-comments (&optional refresh)
+ "Display buffer that shows comments for recent open changes.
+This comment is idempotent."
+ (interactive)
+ (when (or (not (get-buffer gerrit-buffer-name))
+ refresh)
+ (save-excursion
+ (set-buffer (get-buffer-create gerrit-buffer-name))
+ (magit-insert-section (root)
+ (magit-insert-heading "Gerrit Comments\n\n")
+ (loop for change in
+ (hash-table-keys gerrit--change-to-filepath-comments) do
+ (unless (hash-table-empty-p
+ (gethash change gerrit--change-to-filepath-comments))
+ (magit-insert-section (file)
+ (magit-insert-heading
+ (format "%s - %s"
+ (gethash "subject" change)
+ (gethash "change_id" change)))
+
+ (loop for filepath in
+ (hash-table-keys
+ (gethash
+ change
+ gerrit--change-to-filepath-comments))
+ do
+ (gerrit--insert-section-comments change
+ filepath))))))
+
+ (when (hash-table-empty-p gerrit--change-to-filepath-comments)
+ (insert "No open changes!"))
+
+ (goto-char (point-min))
+ (gerrit-section-mode)
+ (setf word-wrap t)
+ (setf truncate-lines nil)))
+
+ (if refresh
+ (switch-to-buffer gerrit-buffer-name)
+ (pop-to-buffer gerrit-buffer-name)))
+
+
+(define-button-type 'gerrit--filepath
+ 'face 'gerrit-filepath)
+
+(defun gerrit--navigate-to-comment (project-branch-pair
+ line
+ filepath-from-project-root
+ section-symbol)
+ "Navigates the user to a comment."
+
+ ;; Keep section open for user's to refer back to during edits.
+ (when (oref section-symbol hidden)
+ (magit-section-toggle section-symbol))
+
+ (switch-to-buffer-other-window
+ (find-file-noselect (gerrit--get-abs-path-to-file
+ filepath-from-project-root
+ project-branch-pair
+ test-repo-root)))
+
+ (goto-char (point-min))
+ (beginning-of-line line))
+
+
+(defun gerrit--insert-comment-header (change
+ filepath-from-project-root
+ line
+ section-symbol)
+ "Inserts comments for the given change and system filepath."
+ (let (header-text
+ button-p
+ (begin-pos (point)))
+ (cond ((equal "/PATCHSET_LEVEL" filepath-from-project-root)
+ (setf header-text
+ (propertize
+ "Patch Comment"
+ 'face 'gerrit-filepath)))
+ ((equal "/COMMIT_MSG" filepath-from-project-root)
+ ;; TODO future CL for navigating
+ ;; to commit message lines.
+ (setf header-text
+ (propertize
+ (format "Commit Message:%s\n"
+ filepath-from-project-root
+ line)
+ 'face 'gerrit-filepath)))
+ ((equal "MERGE_LIST" filepath-from-project-root)
+ (message "There are merge list comments which we don't support"))
+ ;; Default here are filepath comments
+ (t (progn
+ (setf header-text
+ (propertize
+ (format "%s:%s\n"
+ filepath-from-project-root
+ line)
+ 'face 'gerrit-filepath))
+ (setf button-p t))))
+
+ (magit-insert-heading header-text)
+ (when button-p
+ (make-button begin-pos
+ (point)
+ 'type 'gerrit--filepath
+ 'action
+ (lambda (button)
+ (gerrit--navigate-to-comment
+ change
+ line
+ filepath-from-project-root
+ section-symbol))))))
+
+
+(defun gerrit--insert-section-comments (change
+ filepath-from-project-root)
+ ;; We want the smaller lines first.
+ (sort (gethash filepath-from-project-root
+ (gethash change gerrit--change-to-filepath-comments))
+ (lambda (a b)
+ ;; If comment has no line, we don't care about ordering.
+ (< (or (gethash "line" a) 0)
+ (or (gethash "line" b) 0))))
+
+ (loop for comment-info across
+ (gethash filepath-from-project-root
+ (gethash change gerrit--change-to-filepath-comments))
+ do
+ ;; We don't care about our own comments.
+ (unless (equal
+ test-user ;; FIXME when test code is removed
+ (gethash "email" (gethash "author" comment-info)))
+ (let ((section-symbol (gensym))
+ (line (gethash "line" comment-info))
+ (begin-pos (point)))
+
+ (magit-insert-section
+ ;; We use section symbols to toggle when navigating.
+ section-symbol
+ (gerrit-section-type nil t)
+
+ (gerrit--insert-comment-header
+ change
+ filepath-from-project-root
+ line
+ section-symbol)
+ (gerrit--section-insert-comment comment-info))))))
+
+
+(defun gerrit--section-insert-comment (comment-info)
+ "Inserts the author, email, and body of a comment."
+ (magit-insert-section-body
+ (insert (format "Author: %s\nEmail: %s\nComment: %s\n"
+ (gethash "name" (gethash "author" comment-info))
+ (gethash "email" (gethash "author" comment-info))
+ (gethash "message" comment-info)))))