Innehållsförteckning RecentChanges News ElispArea HowTo Problems Suggestions

SmartTabs

Last edit

Sammanfattning: Problem using smart-tabs-mode with sh-mode

Tillagd:

> == Questions ==
> [new]
> I tried to use [https://github.com/jcsalomon/smarttabs/issues/11 smart-tabs-mode.el working with sh-mode] and did not managed to, any idea? --DanielDehennin


This is about indenting with tabs and aligning with spaces. For Sébastien Rocca-Serra’s smart-tab command, see TabCompletion.

TabsSpacesBoth
Tabs and spaces are often misunderstood. (Original comic by Steve Napierski.)

There is a semantic way of using tab characters in source code: tabs for indentation, spaces for alignment. This ensures that the code is displayed correctly everywhere, regardless of the viewer’s tab size.

    // Tab size: 4                // Tab size: 2
    if(foo) {                     if(foo) {
    --->char quux[4] = {0, 1,     ->char quux[4] = {0, 1,     // "0" and "2"
    --->................2, 3};    ->................2, 3};    // remain aligned
    }                             }

The approach can be summed up in two points:

  1. Tabs are only used at the beginning of lines. Everything else, like ASCII art and tables, should be formatted with spaces.
  2. Tabs are only used for expressing the indentation level. One tab per “block” – any remaining whitespace is spaces only.

This page provides code which improves on Emacs’ auto-indenting, so that whenever you press the <tab> key, the appropriate amount of tabs and spaces is inserted automatically. You can still use all spaces if you prefer, of course; the code merely ensures that if you enable tabs (maybe when maintaining legacy code), the tab size does not matter.

Code

This has been packaged and is available as a minor mode; see below

Add the code below to .emacs to make Emacs insert the proper amount of tabs and spaces automatically, provided indent-tabs-mode is not disabled (see toggling). Then, to indent and align, just press ‘<tab>’ as usual.

You can retab the whole file by pressing ‘C-x h C-M-\’. (TabCompletion provides a way to do this with fewer keystrokes.)

C-like languages

For CC Mode (C, C++, Java et al.), add

    (setq-default tab-width 4) ; or any other preferred value
    (setq cua-auto-tabify-rectangles nil)
    (defadvice align (around smart-tabs activate)
      (let ((indent-tabs-mode nil)) ad-do-it))
    (defadvice align-regexp (around smart-tabs activate)
      (let ((indent-tabs-mode nil)) ad-do-it))
    (defadvice indent-relative (around smart-tabs activate)
      (let ((indent-tabs-mode nil)) ad-do-it))
    (defadvice indent-according-to-mode (around smart-tabs activate)
      (let ((indent-tabs-mode indent-tabs-mode))
        (if (memq indent-line-function
                  '(indent-relative
                    indent-relative-maybe))
            (setq indent-tabs-mode nil))
        ad-do-it))
    (defmacro smart-tabs-advice (function offset)
      `(progn
         (defvaralias ',offset 'tab-width)
         (defadvice ,function (around smart-tabs activate)
           (cond
            (indent-tabs-mode
             (save-excursion
               (beginning-of-line)
               (while (looking-at "\t*\\( +\\)\t+")
                 (replace-match "" nil nil nil 1)))
             (setq tab-width tab-width)
             (let ((tab-width fill-column)
                   (,offset fill-column)
                   (wstart (window-start)))
               (unwind-protect
                   (progn ad-do-it)
                 (set-window-start (selected-window) wstart))))
            (t
             ad-do-it)))))
    (smart-tabs-advice c-indent-line c-basic-offset)
    (smart-tabs-advice c-indent-region c-basic-offset)

The rest of the page assumes the above is already in place.

JavaScript

For Js2Mode, add

    (smart-tabs-advice js2-indent-line js2-basic-offset)

Perl

For CPerlMode, add

    (smart-tabs-advice cperl-indent-line cperl-indent-level)

Python

For python.el, add

    (smart-tabs-advice python-indent-line-1 python-indent)
    (add-hook 'python-mode-hook
              (lambda ()
                (setq indent-tabs-mode t)
                (setq tab-width (default-value 'tab-width))))

For python-mode.el, add

    (smart-tabs-advice py-indent-line py-indent-offset)
    (smart-tabs-advice py-newline-and-indent py-indent-offset)
    (smart-tabs-advice py-indent-region py-indent-offset)

Smart tabs are, of course, fully Python compliant, as the interpreter ignores the leading whitespace of continuation lines. However, in accordance with PEP 8, you should probably use tabs only when maintaining old code.

Ruby

For RubyMode, add

    (smart-tabs-advice ruby-indent-line ruby-indent-level)
    (setq ruby-indent-tabs-mode t)

VHDL

For VhdlMode, add

    (smart-tabs-advice vhdl-indent-line vhdl-basic-offset)
    (setq vhdl-indent-tabs-mode t)

Toggling

Smart tabs are only used when indent-tabs-mode is non-nil (default). Type ‘C-h v indent-tabs-mode RET’ to see the current, buffer-local value. To change indent-tabs-mode for the current buffer only, type ‘M-x set-variable indent-tabs-mode RET’ or customize the mode hook.

What you probably want to do: Disable tabs globally (spaces only) and reactivate them for modes with smart tabs handling. For CC Mode:

    (setq-default indent-tabs-mode nil)
    (add-hook 'c-mode-common-hook
              (lambda () (setq indent-tabs-mode t)))

See GuessStyle for a package which attempts to determine the proper values for indent-tabs-mode and the indentation offset by analysing how the file is formatted. (Of course, since smart tabs are tab size-independent, such files do not have an offset.)

Packaging

This code is now available as an Emacs minor mode on GitHub; see the file smart-tabs-mode.el. Bug reports, code contributions, etc., are all welcome. —JCSalomon

Code explanation

For those looking to implement this functionality in other modes (or even other editors), this section explains how the code above works. The method used is not specific to Emacs – any scriptable editor with syntactic auto-indentation can be made to indent with tabs and align with spaces.

The editor’s auto-indentation depends on two variables: the tab size and the indentation offset. The tab size is how many columns a tab occupies (more precisely, the distance between tab stops). The indentation offset is the standard amount of columns an indented line is shifted to the right (disregarding special cases like alignment of multi-line expressions).

If we have access to these two variables, we can separate “indentation” from “alignment” without knowing very much about the language itself. The idea is simple: temporarily set the tab size and indentation offset to a very large value before indenting. This is similar to calling GNU Indent with -i99 -ts99 -l999 (along with necessary style parameters).

For instance, say we want to indent the C function

    int f(int x,
    int y) {
    return g(x,
    y);
    }

We drastically “expand” the tab size and indentation offset (they must have the same value, so that one tab equals one indentation offset), and perform the indenting:

    int f(int x,
    ......int y) {
    ----------------------->return g(x,
    ----------------------->.........y);
    }

When we change the values back – say to 4 – we have

    int f(int x,
    ......int y) {
    --->return g(x,
    --->.........y);
    }

which is indeed tab size-independent.

This works because (1) setting the tab size and indentation offset to the same value ensures that all “instances” of the offset are encoded as tabs, (2) using a value that is larger than any amount of alignment forces spaces to be used for the rest. (A good value is probably a multiple of the maximum line length.)

Spaces before tabs

This situation could arise if the user accidently hits ‘SPC’ at the beginning of a line. Emacs’ indentation functions may not be able to detect and remove such spaces, because the perceived amount of indentation is unchanged.

    if(cond) {
    .-->return 0;

To be on the safe side, we see to that such spaces are weeded out before performing “expansion” and indentation:

    if(cond) {
    --->return 0;

Advising Emacs

In Emacs, the tab size is controlled by the buffer-local variable tab-width, while the indentation offset differs from mode to mode. The macro smart-tabs-advice constructs a wrap-around advice of the mode’s indentation function, which shadows these variables. That is, the original values of tab-width and the indentation offset are temporarily overwritten with a large value (fill-column). The advice also checks the beginning of the line for spaces preceding tabs; if any are found, they are deleted before indenting.

So, to add smart tabs support for a new language, all one has to do is investigate its major mode, identify the indentation functions and the offset variable, and pass them to smart-tabs-advice as such:

    (smart-tabs-advice language-indent-line language-basic-offset)

Also, ensure that the mode has tabs activated. Some modes, such as VhdlMode, control this with their own variable rather than through Emacs’ standard indent-tabs-mode.

The Net

The wheel appears to have been reinvented a number of times:

(As for non-Emacs implementations, Michael Geddes has written a Smart Tabs plugin for Vim. Also, IntelliJ IDEA has a “Smart Tabs” option, Anjuta’s tab handling is reportedly fixed in SVN, and the Kate editor’s indentation API distinguishes between “indentation” and “alignment”.)

Screenshot

SmartTabsScreenshot

Questions

I tried to use smart-tabs-mode.el working with sh-mode and did not managed to, any idea? --DanielDehennin


CategoryIndentation