5

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.

  • Check the delim-col.el file. You can do some neat stuff regarding column formatting. – fredtantini Jun 3 '15 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 '15 at 9:56
  • 1
    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. – Gilles Jun 4 '15 at 21:00
2

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.
  • 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 '15 at 14:24

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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