Reputation: 13357
I'm writing my own mode in Elisp. It's basically a simple crud application showing rows of data which can be manipulated via the minibuffer. I'd like to create a view for these rows which looks like the emacs package manager: columns of data nicely aligned. What's the best way to implement such a view?
Upvotes: 17
Views: 1549
Reputation: 30259
In addition to tabulated-list there is tblui.el, which provides tblui-define
a macro that will create a derived major mode of tabulated-list-mode
, fairly rapidly.
As well as making it easier to get the table view, it also provides a simple way to integrate with magit popup. So you can add quick shortcut keys and group them by actions. So creating a tabulated list based 'app' is simpler.
Here's a simple example...
(tblui-define ocodo/gh-run-list-tblui
"GitHub Workflow Runlist"
"Display workflow runs in a tabulated list."
ocodo/gh-run-list-entries-provider
[("startedAt" 15 nil)
("url" 1 nil)
("status" 10 nil)
("event" 10 nil)
("workflowName" 15 nil)]
((:key "w" ;; tiny popup
:name browse-row-url
:funcs ((?W "Browse URL for current run" ocodo/gh-run-list-browse-row-url)))))
The entries must be provided by a function, we're using
(ocodo/gh-run-list-entries-provider)
Which fetches and transforms a github JSON run list, and makes it usable for tabulated-list
(more on this below.)
Popup functions, we also have the tiny popup definition, which calls a function that opens a url at row.
(defun ocodo/gh-run-list-browse-row-url ()
"Open the url for the current row."
(interactive)
(shell-command-to-string
(format "open \"%s\""
(elt (tabulated-list-get-entry) 1))))
tabulated-list provides the functions you need to get the row entries or row id at point:
(tabulated-list-get-entry)
and (tabulated-list-get-id)
Use (elt n entry)
to get the content at column n (zero based)
I won't delve too deeply into transforming JSON into tblui entries.
But the tl;dr is, you'd map from (output of (json-parse-string JSON-STRING)
) a vector
of alists
, converting to a list
of lists
containing an entry id (car)
and a vector
of column values (cdr)
.
The columns we define will need to match the column positions of values in a row vector. So, in our example row entries be like this:
;;(id [vector of col values])
(list 0 ["10m ago" "https://github.com/org/repo/actions/runs/60324234205" "completed" "push" "MyFlow"])
Structurally:
(list
(list 0 [... column values ...])
(list 1 [... column values ...])
(list 2 [... column values ...])
(list 3 [... column values ...])
;; ... etc ...
)
tblui
creates.Upvotes: 0
Reputation: 15803
I use org-mode for this kind of task all the time.
This should be a starting point for your development, because you already have nice tables.
Upvotes: 0
Reputation: 13357
The answer from phils got me on track. There are no tutorials or simple examples anywhere though, so I created one. Here is an example of a tabulated-list-mode derivative which has static data and can print the ID of the current column:
(define-derived-mode mymode tabulated-list-mode "mymode" "Major mode My Mode, just a test"
(setq tabulated-list-format [("Col1" 18 t)
("Col2" 12 nil)
("Col3" 10 t)
("Col4" 0 nil)])
(setq tabulated-list-padding 2)
(setq tabulated-list-sort-key (cons "Col3" nil))
(tabulated-list-init-header))
(defun print-current-line-id ()
(interactive)
(message (concat "current line ID is: " (tabulated-list-get-id))))
(defun my-listing-command ()
(interactive)
(pop-to-buffer "*MY MODE*" nil)
(mymode)
(setq tabulated-list-entries (list
(list "1" ["1" "2" "3" "4"])
(list "2" ["a" "b" "c" "d"])))
(tabulated-list-print t))
Upvotes: 27
Reputation: 73314
If you look at the code for the package listing function you mention, you'll see that it employs package-menu-mode
which derives from tabulated-list-mode
.
find-function
RET package-menu-mode
RETtabulated-list-mode
RETUpvotes: 2