How to set tab behaviour in a custom major mode? - emacs

Currently in the major mode I am writing pressing tab moves the point a certain number of spaces.
What I want to happen is more like how python mode makes it so that tab moves the whole line to the correct indentation.
Does anyone know how this is done?

Set indent-line-function appropriately in the major mode function, e.g. with something like
(defun my-mode-indent-line (&optional _arg)
...)
(define-derived-mode my-mode prog-mode "MyMode"
"Have fun with My Mode."
...
(setq-local indent-line-function #'my-mode-indent-line)
...)

Related

Repeating TABs on subsequent lines in text files (but keeping TABs disabled for code)

I am editing a text file foo.txt using emacs.
I press C-q TAB to insert a TAB character at the beginning of a line and then follow with a few characters.
Once I press ENTER, emacs inserts eight spaces on the following line.
How do I specify in my .emacs that I would like TABs to be repeated on subsequent lines with TABs?
Importantly, I dislike TAB characters in program code, and so I have (setq-default indent-tabs-mode nil) to make sure that TABs are inserted only when I explicitly ask for them.
Emacs inserts SPC chars because you told it to, by setting indent-tabs-mode to nil (my preference too, BTW).
If you want Emacs to indent using TAB chars in a particular mode (buffer), but you want it to use SPC chars in general (i.e., in other modes), then set indent-tabs-mode to t in those modes where you want TABs. Just use setq when you are in the mode, since it is a buffer-local variable. For example:
(add-hook MY-mode-hook (lambda () (setq indent-tabs-mode t)))
The real answer is: No, there is no way to do this by some simple configuration setting in emacs. indent-tabs-mode is either on or off, and indentation will behave according to that.
But, just because this feature is not there, doesn't mean you can't add it!
This is actually not a simple problem from what I found. Whether or not to use tabs or spaces is determined by indent-tabs-mode in C mostly. Assuming that you are running a recent version of emacs, the auto indentation is coming from electric-indent-mode which uses indent-according-to-mode in a post-self-insert-hook to do the indentation.
What I did for this was define a buffer local minor mode, when this mode is active indent-tabs-mode will be temporarily set depending on the first character in the last line while running indent-according-to-mode.
So when smart-electric-indent-tabs-mode is active, and your last line started with the tab, the next line will indent with a tab too, else it will just use whatever indent-tabs-mode would normally be set to.
You could add the following to your config to activate it. The add-hook clause is put in there for your convenience, you can activate it on the fly like a normal minor mode if you'd like.
(define-minor-mode smart-electric-indent-tabs-mode
"When on, indenting will use tabs if the current line does,
else it will indent according to `indent-tabs-mode'."
:init-value nil
:lighter " smart-tabs"
:keymap nil
:global nil)
(defadvice indent-according-to-mode (around maybe-use-tabs activate)
"Follow `smart-electric-indent-tabs-mode'."
(let ((indent-tabs-mode
(or (and smart-electric-indent-tabs-mode
(save-excursion
(save-restriction
(widen)
(beginning-of-line 0)
(looking-at "\t"))))
indent-tabs-mode)))
ad-do-it))
;; if you want, add a text mode hook
(add-hook 'text-mode-hook 'smart-electric-indent-tabs-mode)
This has only been tested to work during electric indentation

Emacs Whitespace Mode ignores whitespace-line-column and fill-column

I have an issue using Emacs 24.1.1 on Mac OS X. I'm editing Jade and CoffeeScript files, so I've turned on whitespace-mode for those file types.
What I'm seeing is that lines longer than 70 characters are highlighted with the whitespace-line font face, regardless of the setting of whitespace-line-column.
In this shot, it is clear that I've customized whitespace-line-column to track fill-column, and I've set fill-column to 120, but much shorter lines are being highlighted.
I've glanced over the code for the Jade mode and don't see anything that would explain the behavior, but I have only a passing understanding of Emacs Lisp.
Thanks in advance for any pointers!
You have to set whitespace-line-column before you activate whitespace-mode. That is, if you want to change its value it does not take effect unless you turn whitespace-mode off and on again. Ironically, that variable is not available for M-x customize until you have activated the mode once :-(
However, you can customize the global value of this variable by putting the following line in your .emacs file:
(setq whitespace-line-column 120)
Since your .emacs is evaluated when you start Emacs, the setting will take effect before you invoke whitespace-mode for the first time and should thus do what you want. If you don't want to set the value globally, but only for Jade files, put the following in your .emacs file instead:
(set (make-local-variable 'whitespace-line-column) 80)
(add-hook 'after-change-major-mode-hook
'(lambda () (when (eq major-mode 'jade-mode)
(setq whitespace-line-column 120))))
If you never want long lines to be highlighted specially at all, there is a third option you might want to consider. You could customize the variable whitespace-style (by typing M-x customize-variable ENTER whitespace-style ENTER) and in the value list remove the entries:
lines
lines-tail
(if any). This should turn off highlighting of long lines globally independent of the value of whitespace-line-column (again, only after you de- and re-activate whitespace mode).

First elisp attempt - minor mode for tab key not being invoked on tab?

I've decided to get my toes wet with a bit of lisp, since I want to make emacs behave a little better when I hit TAB. My command works fine. It just performs indent-for-tab-command and if nothing happens, it performs tab-to-tab-stop, on the assumption that it's unlikely I was hitting TAB just to have the point refuse to budge when I'm inside a multi-line string or some such. After the first TAB press, it continues to do tab-to-tab-stop until either editing resumes, or the point is moved elsewhere. AFAIK, my logic is ok, though my lisp code probably isn't!
Originally I just hacked this into my emacs dot files by doing (local-set-key (kbd "TAB") 'tab-dwim) for major modes where I wanted this behaviour. That worked as expected.
Then I decided that what I was doing was basically a minor mode, so I tried to move the key-binding into a minor-mode. For some reason, even though the minor mode is enabled (as indicated in the mode line, and just from toggling it on and off), my tab-dwim function isn't being invoked when I hit the TAB key. I can still invoke it with M-x as expected.
What am I doing wrong with my minor mode's :keymap?
;;;
;; TAB DWIM
; buffer-local before/after point tracking
(setq point-before-tab nil)
(setq point-after-tab nil)
(make-local-variable 'point-before-tab)
(make-local-variable 'point-after-tab)
(defun tab-dwim ()
"Indents normally once, then switches to tab-to-tab-stop if invoked again.
tab-dwim will always perform tab-to-tab-stop if the first TAB press does not
cause the point to move."
(interactive)
(print "in tab-dwim now") ; THIS LINE IS NEVER INVOKED ON TAB?
(setq point-before-tab (point))
(if (eq point-before-tab point-after-tab) ; pressed TAB again
(tab-to-tab-stop)
(indent-for-tab-command))
(if (eq (point) point-before-tab) ; point didn't move
(tab-to-tab-stop))
(setq point-after-tab (point)))
(define-minor-mode tab-dwim-mode
"Toggle tab-dwim-mode.
With a non-nil argument, turns on tab-dwim-mode. With a nil argument, turns it
off.
When tab-dwim-mode is enabled, pressing the TAB key once will behave as normal,
but pressing it subsequent times, will continue to indent, using
tab-to-tab-stop.
If tab-dwim determines that the first TAB key press resulted in no movement of
the point, it will indent according to tab-to-tab-stop instead."
:init-value nil
:lighter " DWIM"
:keymap
'(([TAB] . tab-dwim)))
(provide 'tab-dwim)
Cheers,
Chris
I think you are very close.
Try this for your keymap:
'(("\t" . tab-dwim)))
Yes, use "\t" or the vector format "[(tab)]".
Some additional notes for your elisp development:
Avoid making global variables as much as possible. In this case, I think dynamically binding with let is appropriate. Also have a look at let*.
Understand the difference between make-local-variable and make-variable-buffer-local. The way you've written your code, the buffer-local variable would only exist in the buffer that loads your package.
As Nemo mentioned, it's extemely extremely extremely recommended that you use a common prefix for all variables/functions related to each package. Emacs has only one namespace, this is the "hacky" way to keep it somewhat organized.

How can I set Emacs tab settings by file type?

I need to be able to set the tab settings for the following file types:
.rb: 2 soft spaces
.css, .html.erb: 4 space tabs
I have tried the following, but none of it seems to alter my default tab settings for each of the file types.
;; js-mode-hook has also been tried
(add-hook 'javascript-mode-hook
'(lambda()
(setq tab-width 4)))
(add-hook 'css-mode-hook
'(lambda()
(setq tab-width 4)))
(add-hook 'html-mode-hook
'(lambda()
(setq tab-width 8)))
I am pretty new to emacs so my knowledge of configuration is pretty low.
In emacs each mode has it's own indentation style. The main command to indent (bound to TAB) is indent-for-tab-command.
This command calls mode specific indentation function found in the variable indent-line-function. So each mode has it's own way of doing it.
For Ruby (for my emacs 2 is a default):
(setq ruby-indent-level 2)
For CSS (again, default is 4 for me):
(setq css-indent-offset 4)
Unfortunately SGML mode (on which HTML mode is based) has a very simple indentation mechanism and apparently the level is not configurable.
See the source code of sgml-calculate-indent function.
I personally find it weird. I am not writing HTML, but you can try to modify the sgml-calculate-indent function yourself :). Learn some lisp.
I am using js2 mode, and it indents perfectly by default. For js you have to search for js-indent-level or something similar.
Cheers.
Theres a number of aspects to how Emacs does indentation. Setting the tab-width only specifics how big a tab is if a literal tab is inserted. If you don't wish to use literal tabs for indentation, then you should first disable their insertion (from the manual
):
Emacs normally uses both tabs and
spaces to indent lines. If you prefer,
all indentation can be made from
spaces only. To request this, set
indent-tabs-mode to nil. This is a
per-buffer variable, so altering the
variable affects only the current
buffer, but there is a default value
which you can change as well.which you can change as well.
However, to specify the indentation levels, you'll also need to set the c-basic-offset value variable as well:
(add-hook 'html-mode-hook
'(lambda()
(setq c-basic-offset 4)
(setq indent-tabs-mode nil))
In your case, you may only need the c-basic-offset but try a few combinations and see what works best.
js-mode uses js-indent-level so put (setq js-indent-level 4) into your ~/.emacs (shouldn't have to be in a hook, even, but if you're wondering, it's js-mode-hook, not javascript-mode-hook).
If setting tab-width doesn't change your indentation level for a certain mode, it's often simplest to just open the source for that mode. I found this variable by doing C-h f js-mode, clicking the link "js.el", then searching for "indent", second hit from the top.
However, if you collaborate a lot with other people, it's often better to put a cookie at the top of the file. I typically do // -*- tab-width: 8 -*- in the file, and then I have stuff like this in my ~/.emacs:
(defvaralias 'c-basic-offset 'tab-width)
(defvaralias 'cperl-indent-level 'tab-width)
(defvaralias 'perl-indent-level 'tab-width)
(defvaralias 'js-indent-level 'tab-width)
so that I have less variables to deal with (and don't have to get warnings about the file-local variable being unsafe or whatever if the mode-writer forgot to declare it as safe)
If you are using ELPA's css-mode.el with emacs 23.1.1, you can parametrize the global setting for tab width for CSS files for the tab width by doing the following:
1) Type M-x customize-variable ,
2) Then type css-indent-level,
3) Then after you change the variable to your liking, you do "Save for future sessions".
For HTML and erb: if you are using web-mode (the mode provided by Spacemacs) it can be as simple as:
(setq-default
web-mode-code-indent-offset 2
web-mode-markup-indent-offset 2)
where markup-indent-offset refers to the actual tags and code-indent-offset refers to embedded Ruby in ERB, etc.

Emacs global configuration of tabs

I'm attempting to switch from Vim to Emacs, but I'm tearing my hair out trying to configure it to treat tabs how I wish. I require:
Inserted "tabs" to be expanded into two spaces. Emacs stubbornly sticks to eight, no matter what I do.
Tabs (i.e. real \t characters) to be represented on screen by two spaces.
Pressing TAB should insert a tab at the cursor rather than indent the entire line. Currently, I press TAB anywhere and Emacs destroys all whitespace at the start of the line; this is the most infuriating thing so far.
My current ~/.emacs reads
(setq standard-indent 2)
(setq-default indent-tabs-mode nil)
but I have tried no end of suggested configurations from the web, none of which have done what they said they would. (Does the API constantly change? I'm using GNU Emacs 23.1.1, apparently.)
Emacs has extremely flexible support for handling indentation. Generally the mode that you are in dictates how they work - so if you're working on a C file then the way that pressing tab works will be different than if you're working on a Python file.
So it does depend which mode you're working in, which will limit the answers you get. In most cases I would recommend that you don't fight against it - for me the indentation behaviour is one of the best features of emacs. However, you do need to spend the time to customize it for yourself.
To change the way that tabs are displayed you need to set tab-width to 2. If you're editing Java or C style code then it sounds like you want to turn off all the nice indentation features by these to NIL:
c-tab-always-indent
c-syntactic-indentation
indent-tabs-mode
I suggest you set these through the customization interface. If you use "M-x customize-group RET C" then you can see the various settings for C mode.
If you're editting different types of files then the instructions will be different.
Perhaps emacs is in the wrong mode for your file. You could try doing "M-x fundamental-mode" to see if you prefer the behaviour there.
;; * Inserted "tabs" to be expanded into two spaces. Emacs stubbornly
;; sticks to eight, no matter what I do.
;; * Tabs (i.e. real \t characters) to be represented on screen by two
;; spaces.
(setq-default tab-width 2)
;; * Pressing TAB should insert a tab at the cursor rather than indent
;; the entire line. Currently, I press TAB anywhere and Emacs
;; destroys all whitespace at the start of the line; this is the
;; most infuriating thing so far.
(setq-default indent-tabs-mode t)
(mapcar (lambda (hooksym)
(add-hook hooksym
(lambda ()
(kill-local-variable 'indent-tabs-mode)
(kill-local-variable 'tab-width)
(local-set-key (kbd "TAB") 'self-insert-command))))
'(
c-mode-common-hook
;; add other hook functions here, one for each mode you use :-(
))
;; How to know the name of the hook function? Well ... visit a file
;; in that mode, and then type C-h v major-mode RET. You'll see the
;; mode's name in the *Help* buffer (probably on the second line).
;; Then type (e.g.) C-h f python-mode; you'll see blather about the
;; mode, and (hopefully) somewhere in there you'll see (again e.g.)
;; "This mode runs the hook `python-mode-hook', as the final step
;; during initialization."
This should get you most of what you want. You'll probably have to customize some other programming modes you commonly use.
(defun insert-tab ()
"self-insert-command doesn't seem to work for tab"
(interactive)
(insert "\t"))
(setq indent-line-function 'insert-tab) ;# for many modes
(define-key c-mode-base-map [tab] 'insert-tab) ;# for c/c++/java/etc.
(setq-default tab-width 2)