List issues from various issue trackers in a tabulated buffer in Emacs and act on them.
It is based on Tabulated List mode. The scope of this is to give a quick view of issues from various issue trackers. And do some basic actions on them.
This is the core package and does not come with any issue tracker backend. See the project page for backends.
In your init file you need to add the backends you want to the
issue-backends variable. See the Create backend section for
In emacs run
M-x list-issues and it should open a tabulated buffer,
listing all the issues from the various issue tracker backends that
The basic keybindings are as follows:
|Mark issue that the cursor is on.
|Mark all issues.
|Unmark issue that the cursor.
|Unmark all issues.
|Open marked issue(s) in browser
|Bring up view menu
m) or unmark (
u) will move the cursor to the next line.
Making it easier to mark/unmark multiple.
If no issue is marked, an action such as open in browser (
use the issue the cursor is on.
All keybindings can be viewed by pressing
This section describes the steps needed to create a backend for
issue.el. The steps will use a fictious issue tracker called
the examples to hopefully make the steps a bit easier to understand.
To be able to do that, each backend need to define their own issue
struct that inherit from the
issue struct defined in
(cl-defstruct (issue-foo (:include issue))
"Structure containing information about an individual foo issue.")
The issue struct contains metadata about each issue (e.g. id, title
and status of the issue) and it is this data that gets rendered in the
tabulated list buffer. The
issue struct defines all the slots that
it issue.el needs for this. Extra data can be added to the child
struct which can be accessed in the specialized action functions.
For issue.el to know about the issues from an issue tracker, the
backend needs to add the issues to the
For that issue.el calls the functions
issue-backends to populate the
list. The format of
issue-backends is an alist where the key is a
symbol that identifies the backend and the value is the function to
call to populate
tabulated-list-entries and call
tabulated-list-printer when done. For now the function does not take
any arguments, but in the future this will most likely change to allow
for filtering of issues.
foo this will look something like:
(defun issue-foo-process-issues (&optional filter)
"Return a list of `issue-foo`.
Where FILTER is the query to get the issues. Default is to get
all unresolved issues assigned to the current login user."
(issue-foo--query (or filter "magic query")))))
(defun issue-foo--convert-to-issue-foo (raw-issue)
"Convert RAW-ISSUE to `issue-foo'.
Where RAW-ISSUE is expected to be an alist containing one
issue from the issues list in the data `issue-foo--query'
(let ((fields (alist-get 'fields raw-issue)))
(make-issue-foo :id (alist-get 'key raw-issue)
:title (alist-get 'title fields)
:priority (alist-get 'priority fields)
:status (alist-get 'status fields)
:type (alist-get 'issuetype fields)
:project (alist-get 'project fields)
:tools (alist-get 'tools fields)
:assignee (alist-get 'assignee fields))))
(defun issue-foo--query (query)
"Call the foo issue tracker with QUERY.
Return a list containing the issues matching the query. Where
each entry is an alist with the data associated with an issue."
The format for
tabulated-list-entries is a bit special as it needs
to know what slot goes to what column. It is therefore recommended to
use the helper function
issue.el to append the issue list to
The user is then expected to add
issue-backends like so:
(setf (alist-get 'foo issue-backends) #'issue-foo-process-issues)
Actions are generic functions that are applied to a subset of the issues listed in the tabulated list buffer. Namely the issues that are marked or if no issue is marked the issue the cursor is on.
They need to be specialized on the issue struct the backend defines for issue.el, for them to work properly for the issues the backend supports.
This action should open the issue in the browser.
foo this would be:
(cl-defmethod issue-open-in-browser ((issue issue-foo))
"Open foo ISSUE in the browser."
(browse-url (concat "https://foo/browse/" (issue-foo-id issue))))
If your local clone of this repo is named
guix build -f guix.scm will not work. Workaround is to rename it to e.g.
issue-el. Reason for it is that when the directory is named
issue.el, guix will only copy the file
issue.el when building and
testing the repo. Which causes
ert-runner to error out as the
directory does not exist.