auramo
auramo

Reputation: 13357

How to create a column view in Emacs Lisp?

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

Answers (4)

ocodo
ocodo

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)

Transforming data

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 ... 
)

Upvotes: 0

alinsoar
alinsoar

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

auramo
auramo

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

phils
phils

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.

  • M-x find-function RET package-menu-mode RET
  • C-hf tabulated-list-mode RET

Upvotes: 2

Related Questions