Here is an example file (/tmp/asdf.sh), for which I want dtrt-indent-mode to do the right thing.

#! /usr/bin/env bash

function asdf()
    printf "asASDFASDF"
    echo "1234"

printf "qwerqwerqwer\n"

if [ df ] ; then
    if [ er ] ; then

I have created a minimal Emacs init file (~/tmp/basic-init.el) to test this:

(require 'package)
(setq package-enable-at-startup nil)
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/"))
(add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/"))
(add-to-list 'package-archives '("melpamilk" . "http://melpa.milkbox.net/packages/"))

(unless package-archive-contents
(unless (package-installed-p 'use-package)
  (package-install 'use-package))
(require 'use-package)

(defun my-prog-mode-hook ()

(use-package dtrt-indent
  :ensure t
  :config (dtrt-indent-mode t))

(use-package blank-mode
  :ensure t)
(global-set-key (kbd "C-c b") 'blank-mode)

Starting emacs with that init file, opening that shell file:

emacs -q -l ~/tmp/basic-init.el /tmp/asdf.sh

then showing blank characters with C-c b, gives this:

enter image description here

(Don't mind the yellow highlight, blank-mode should be setup to leave that alone.)

Going to that yellow line, deleting a space char then pressing TAB gives this:

enter image description here

According to me, the right thing to do is not to put a Tab character there.

What I have found trying to understand what is going on:

Luckily, dtrt-indent has a diagnosis tool. This is the output of dtrt-indent-diagnosis, on the original file (no Tab character):

Guessing offset for /tmp/asdf.sh

Elapsed time for analysis: 0.001 seconds

Total relevant lines: 5 out of 17 (limit: 5000)


     4x   4 spaces
     1x   8 spaces


  offset 2 works for 100.00% of relevant lines, matching 2 distinct offsets - merged with offset 4 (0.00% deviation, limit 20.00%)
  offset 4 works for 100.00% of relevant lines, matching 2 distinct offsets - CONSIDERED
  offset 8 works for  20.00% of relevant lines, matching 1 distinct offsets - CONSIDERED
  offset 3 works for   0.00% of relevant lines, matching 0 distinct offsets - rejected: too few distinct matching offsets (1 required)
  offset 5 works for   0.00% of relevant lines, matching 0 distinct offsets - rejected: too few distinct matching offsets (1 required)
  offset 6 works for   0.00% of relevant lines, matching 0 distinct offsets - rejected: too few distinct matching offsets (1 required)
  offset 7 works for   0.00% of relevant lines, matching 0 distinct offsets - rejected: too few distinct matching offsets (1 required)


  Best guess is offset 4 with 100.00% matching lines (80.00% required)
  Hard tab percentage: 0.00% (0 lines), -100.00% superior to soft tabs (threshold 300.00%)
  Soft tab percentage: 100.00% (5 lines), inf% superior to hard tabs (threshold 300.00%)


  Guessed offset 4 with 100% confidence.
  Change indent-tab-setting: yes, to nil

I did not manage to find help on indent-tab-setting.

However, C-h k on TAB key press gives this:

TAB (translated from <tab>) runs the command indent-for-tab-command
(found in global-map), which is an interactive compiled Lisp function
in ‘indent.el’.

It is bound to TAB.

(indent-for-tab-command &optional ARG)

Indent the current line or region, or insert a tab, as appropriate.
This function either inserts a tab, or indents the current line,
or performs symbol completion, depending on ‘tab-always-indent’.
The function called to actually indent the line or insert a tab
is given by the variable ‘indent-line-function’.

and C-h v on indent-line-function:

indent-line-function is a variable defined in ‘indent.el’.
Its value is ‘smie-indent-line’
Local in buffer asdf.sh; global value is indent-relative

Apparently, smie- is a default naive indentation function.

I was expecting to find a dtrt- function here.

How can I make dtrt-indent-mode do the right thing?

GNU Emacs 26.1.

  • 1
    "Change indent-tab-setting: yes, to nil" is poorly worded. dtrt-indent is recommending that you change the value of indent-tabs-mode to nil for that file/buffer, to prevent the insertion of tabs.
    – nega
    May 26, 2021 at 14:34
  • @nega: Please consider posting that as an answer.
    – Drew
    May 26, 2021 at 16:35
  • Please post some info (e.g. a link) about what/where dtrt-indent-mode is. It doesn't seem to be in vanilla Emacs.
    – Drew
    May 26, 2021 at 16:37

2 Answers 2


dtrt-indent is a minor mode that will guess the indentation offset of a file and adjusts those local settings accordingly. It hooks in to several major modes, and works reasonably well.

@Gauthier is wondering why dtrt-indent is inserting a TAB character to perform indentation after guessing, with 100% confidence, that the indentation offset is 4 SPACE characters. I have always taken the line in the dtrt-indent-diagnosis report to be a recommendation to the user to change the "indent settings" (indent-tabs-mode). How the user chooses to do that is up to the user. (File local variables, directory local variables, global configuration, etc). In this case Change indent-tab-setting: yes, to nil is saying "Hey, maybe you want to disable indent-tabs-mode here. I dunno. You do you."

I believe the reason why @Guathier was able to delete 2 indentation levels of 4 spaces, and insert a single tab character to match those 2 indentation levels is because indent-tabs-mode was enabled (t). This short example file didn't meet the heuristics required for dtrt-indent to change indent-tabs-mode. The code in fact states that it may have trouble with small files.

Note that in a comment I stated:

Oh, also, dtrt-indent only adjusts indent level, it won't change what the indentation is (tabs/spaces). Hence the recommendation on whether or not to change the "indent-tab-settings". – nega 5 hours ago

That comment is incorrect, and I've deleted it. I confirmed I was wrong when I globally set my indent-tabs-mode to nil and opened a rather large C++ file that was indented with tabs, and dtrt-indent locally changed indent-tabs-mode to t. By default dtrt-indent will tell you what it changes in the mini-buffer and *Messages* buffer. You can change the verbosity of these messages by customizing dtrt-indent-verbosity. Said C++ example reported:

Note: c-basic-offset adjusted to 8
Note: indent-tabs-mode adjusted to t

I haven't studied the code in depth, but I suspect that this test case, and maybe other examples that @Gauthier has seen just didn't have enough data for dtrt-indent to make a decision. Personally, I think that conservatism is a good thing, as I think it would be more jarring to have my indent "things" changed, than just my indent "sizes".

  • Could "Change indent-tab-setting" be more clear? Maybe, but that's what github issues are for :)
    – nega
    May 26, 2021 at 21:13
  • If you're discovering dtrt-indent for the first time because of this question and you use use-package, note that the package is dtrt-indent and the function you want to add to your hook is dtrt-indent-mode.
    – nega
    May 26, 2021 at 21:17
  • Thanks for the insight, I just noticed the diagnostics says "Change", not "Changed". The documentation says this: "For determining hard vs. soft tabs, dtrt-indent counts the number of lines out of the eligible lines in the fixed segment that are indented using hard tabs, and the number of lines indented using spaces. If either count is significantly higher than the other count, `indent-tabs-mode' will be modified." Why wouldn't this give me soft tabs?
    – Gauthier
    May 27, 2021 at 5:56
  • 1
    Again, maybe because the example was too small for dtrt-indent to generate the statistics to make a decision. I don't know. I've studied dtrt-indent more to answer your question than I have in the 10+ years of using it. :)
    – nega
    May 27, 2021 at 15:16
  • It shows in the example code, but I noticed it first in much larger files. I'll try and post an issue in github.
    – Gauthier
    May 28, 2021 at 11:21

I received help from the devs at github.

Basically, at its default, dtrt-indent-mode does nothing if SMIE also is active. Even if dtrt-indent-mode is active. Activating it (even buffer-locally) always defers to SMIE if active, which it is in many programming language modes.

This deferring can be overridden with dtrt-indent-run-after-smie.

Here is the relevant section of the basic init file in the question, modified to activate dtrt always:

(defun my-prog-mode-hook ()

(add-hook 'prog-mode-hook 'my-prog-mode-hook)

(use-package dtrt-indent
  :ensure t
  (setq dtrt-indent-run-after-smie t) ;; Run even if SMIE is active

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.