9

I have a tab-delimited data file I maintain in emacs. Much of the data has more than seven characters per field, so at the top, I have the following line:

-*- tab-width: 30 -*-

This keeps things nicely aligned; unfortunately it wastes a lot of space because most of the fields are not that long.

I'd like to specify tab stop positions for the individual fields. I tried setting tab-stop-list, but that affects what emacs does when I press the tab key (which I never do when editing this file); I want only to change the column number to which emacs moves when displaying tabs.

3
  • Check the delim-col.el file. You can do some neat stuff regarding column formatting. Jun 3, 2015 at 14:22
  • A good thought, but I am not interested in reformatting (i.e., adjusting the contents) of this file - only controlling how it is displayed.
    – cpcallen
    Jun 4, 2015 at 9:56
  • 2
    As of Emacs 24.4, the display of tab characters is hard-coded to divide by tab-width. So a solution would involve bypassing that mechanism, for example by overlaying the tabs with the right number of spaces. Jun 4, 2015 at 21:00

3 Answers 3

6

Here's some proof-of-concept code that displays tabs with a variable width. It kicks in if you turn on Font lock mode in a buffer where one of the variables variable-tabs-tab-stop-initial-list or variable-tabs-tab-stop-repeat-list is set to a non-nil value (typically through a file-local variable). These variable contain a list of column widths; first the initial list is applied, then the repeat list in a loop. For example, if the initial list is (4 6) and the repeat list is (5 3) then the tab stops are at positions 4, 10, 15, 18, 23, 26, etc.

I've only tested it cursorily. Use at your own risk.

;;; variable-tabs.el --- display tab characters with variable width

;; http://emacs.stackexchange.com/questions/12872/is-there-a-way-to-set-a-list-of-display-tab-stops

;;;###autoload
(defun variable-tabs-safe-list-p (x)
  (while (and (consp x)
              (integerp (car x))
              (> (car x) 0))
    (setq x (cdr x)))
  (null x))

;;;###autoload
(defvar variable-tabs-tab-stop-initial-list nil
  "The initial tab stop list for variable tabs.
This is a list of positive integers, which are the successive column widths.
After the columns listed here, `variable-tabs-tab-stop-repeat-list' applies.")
;;;###autoload
(defvar variable-tabs-tab-stop-repeat-list nil
  "The repeated tab stop list for variable tabs.
This is a list of positive integers, which are the successive column widths.
First the columns listed in `variable-tabs-tab-stop-initial-list' apply,
then `variable-tabs-tab-stop-repeat-list' repeats forever.
If this variable is nil, it stands for (`tab-width').")
;;;###autoload
(make-variable-buffer-local 'variable-tabs-tab-stop-initial-list)
;;;###autoload
(make-variable-buffer-local 'variable-tabs-tab-stop-repeat-list)
;;;###autoload
(put 'variable-tabs-tab-stop-initial-list 'safe-local-variable 'variable-tabs-safe-list-p)
;;;###autoload
(put 'variable-tabs-tab-stop-repeat-list 'safe-local-variable 'variable-tabs-safe-list-p)

(defun variable-tabs-next-tab (goal)
  "Return the next variable tab position after GOAL.
First the columns listed in `variable-tabs-tab-stop-initial-list' apply,
then `variable-tabs-tab-stop-repeat-list' repeats forever."
  (let ((i 0)
        (tail variable-tabs-tab-stop-initial-list))
    (while (and tail (<= i goal))
      (setq i (+ i (car tail))
            tail (cdr tail)))
    (cond
     ((> i goal) i)
     (variable-tabs-tab-stop-repeat-list
      (setq tail variable-tabs-tab-stop-repeat-list)
      (while (<= i goal)
        (setq i (+ i (car tail))
              tail (or (cdr tail) variable-tabs-tab-stop-repeat-list)))
      i)
     (t
      (+ i (* tab-width (1+ (/ (- goal i) tab-width))))))))

(defun variable-tabs-compute-alignment (pos)
  (let* ((column (save-excursion
                   (goto-char pos)
                   (current-column)))
         (goal (variable-tabs-next-tab column)))
    `(face nil display (space :align-to ,goal))))

(defun variable-tabs-font-lock ()
  (if (or variable-tabs-tab-stop-initial-list variable-tabs-tab-stop-repeat-list)
      (font-lock-add-keywords nil
                              '(("\t" (0 (variable-tabs-compute-alignment
                                          (match-beginning 0))
                                         t))))))
;;;###autoload
(add-hook 'font-lock-mode-hook 'variable-tabs-font-lock)

;;; The End.
1
  • 1
    This is very nice. Here's what I did to get it working: save code to ~/.emacs.d/variable-tabs.el; add (load "~/.emacs.d/variable-tabs" noerror nomessage) to ~/.emacs, and add -*- variable-tabs-tab-stop-initial-list: (12 40 12 12); eval: (font-lock-mode 1) -*- to the top of the text file in question. Unfortunately the eval causes emacs to query me before finishing loading the file, but without this the code didn't seem to get activated (even though font-lock-mode appeared to be enabled).
    – cpcallen
    Jun 9, 2015 at 14:24
2

Install the csv-mode package from GNU ELPA, and then use that mode with your file. e.g.:

-*- mode: csv; -*-

Then type C-cC-a whenever you want to visually re-align the data.

You will need to use M-x customize-option RET csv-separators to set the separator to a TAB.

Note that the library appears to be quite limited at present, such that you must use the customize interface to set the separator char, which consequently means it's hard to have multiple files with different delimiter chars. (If you don't use customize then there are a bunch of other derived settings which you will need to take care of as well.)

1

This is not an exact answer to the question but for easy maintainance of tables in emacs org-mode might be interesting, especially the table feature.

org-mode comes with a lot of nice features. Table columns are separated by '|' and the column widths are adjusted automatically (on pressing TAB). Tab-separated data can be imported and exported.

However if the data is processed by additional tools then it will probably want tab separated data, so this solution won't work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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