blob: e88168980448c4672e87cd6a91ef83cb97045fe2 [file] [log] [blame]
;; -*- 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)))))