5

I'm currently writing a major mode (for interacting with a remote Grid Engine) which involves fetching data, parsing it into an alist, formating the alist into a string and then inserting the formatted string into a buffer. This is done for many rows of data.

Now I want to add a way for users to interact with the data in the buffer, e.g. to tab from one field to the next, or to press RET on a field to invoke a function on it.

It seems silly to me to "lose" the underlying data structure after rendering it to the buffer. Parsing the line at point (e.g. with (thing-at-point 'word)) to partially recover the data structure just seems like a bad idea.

Here's an example of one of these alists:

((job-id   . 123)
 (priority . 0.844)
 (name     . "some job name")
 (owner    . "rekado")
 (state    . "r")
 (time     . "2014-10-24")
 (queue    . "standard")
 (slots    . 12))

This may be rendered with a format string like this:

(defun format-job-record (job)
  (format ""%-10s %-10s %-15s %-10s\n"
          (cdr (assoc 'job-id   job))
          (cdr (assoc 'priority job))
          (cdr (assoc 'owner    job))
          (cdr (assoc 'state    job))))

Is there an alternative way (an idiom perhaps), that allows me to specify a way to display this alist while keeping the underlying data intact? I would like to be able to retrieve the whole alist when point is on the row, and any given field when point is on the rendered text for the field.

I think this might be done by storing the alist as special properties on the generated string, and by storing the field keys along with each individual field string, but I'm not sure if this is the best way to do it.

My goal is to separate representation in the buffer from the underlying data and only work on data structures instead of text.

What is the most "natural" approach to achieve this?

EDIT:

Is there a better more "disciplined" approach than what is outlined in my answer below, or are custom text properties the idiomatic way to go?

3

Here is what works in general as described above (associating text with data using the 'field property). The string for each field is augmented with a 'field text property containing the underlying data. The string for the whole row is augmented with a custom property prefixed with the name of the major mode (e.g. 'grid-engine-job) which contains the underlying data structure for the row.

(defun field-as-text (field obj &optional format-string)
  (let* ((value (cdr (assoc field obj)))
         (format-string (if format-string format-string "%s"))
         (text  (format format-string value)))
    (propertize text 'field value)))

(defun format-job-record (job)
  (let ((text (format row-pattern
                      (field-as-text 'job-id job)
                      (field-as-text 'priority job)
                      (field-as-text 'owner job)
                      (field-as-text 'state job))))
    (propertize text 'grid-engine-job job)))

To access the data one only needs to get retrieve the text property at point with (get-text-property pos prop), where pos is the position of point and prop is 'field or the custom property name (e.g. 'grid-engine-job).

  • 1
    Nice answer, I'd just use a propertize instead of the put-text-property. – Malabarba Oct 24 '14 at 14:04
  • @Malabarba Thanks! Updated the answer and my budding major-mode code. – rekado Oct 24 '14 at 14:54
3

A followup to @rekado's great answer -

I do this kind of thing all the time. I often put the entire alist on the buffer text (or on a string, depending on the use case), as a text-property value: (propertize string 'my-data the-alist).

It's worth pointing out, because it might not be obvious to everyone, that the actual data - not a copy of it, is being used as the property value (including in what @rekado described).

I mention this not because it means that there is little cost (just a pointer). It is important because it means that any changes to the object (the alist, or parts of it as in @rekado's example) are automatically reflected in any string or text that holds such a property. You always have access to the current state of the object.

And note that this trick works even when the propertized string is itself part of the object whose value is put on the string! (For example, the object is a cons whose car is the propertized string.) This kind of sharing is what nasty-old-side-effecty Lisp is about - as opposed to a functional language, for example.

2

tabulated-list-mode (used by list-packages) might be what you need.

  • Looking at the output produced by tabulated-list-mode, I see that it does indeed store information in text properties: tabulated-list-id, tabulated-list-entry, and tabulated-list-column-name. Nice. – rekado Oct 24 '14 at 13:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.