Skip navigation

Tag Archives: indentation

Writing heredocs in the shell from emacs presented to me a little nuisance: since the RETurn key is bound to ‘comint-send-input -a function that sends the text to the shell process- I coudn’t re-edit the previous line after having pressed it to insert a simple line break. (I should add that this is actually the usual unix bash behaviour, I only happen to use bash from emacs and so wanted to overcome the problem there)

Yesterday it dawned on me to just use “C-q C-j” under that situation, (that’s how you stick in a line feed in emacs regardless of the mode you are in).

Given that the perspective of typing “C-q C-j” several times looked rather awkward I thought of binding RET with the control key for that:

(global-set-key (quote [(control return)]) (quote newline))

But then, the idea of going the other way around showed more appealing: why not to make newlines with a simple RET, and use the Control key to send the input? Besides, it makes sense to prevent hitting return by accident, I hate when my pinky lands there when searching the pipe “|” key.

While at it I decided to also rebind the TAB functionality so I could further improve the readability of my scripts by indenting them at the same time.

Implementing those changes is simple when you’ve got the luxury of a highly-configurable tool:

(defun my-shell-hook ()
  (local-set-key (quote [(return)]) (quote newline))
  (local-set-key (quote [(control return)]) (quote comint-send-input))
  (local-set-key (kbd "M-i")  'my-unindent)
  (local-set-key (kbd "C-i")  'indent-or-complete))

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

UPDATE: After a day trying this set-up I think that I’d rather stick to the normal binding of the RET key. I’m leaning toward using C-j and C-m instead, as I caught myself reaching to either of them to insert carriage returns. Here are the modified bindings in replacement:

(defun my-shell-hook ()
  (local-set-key (kbd "M-i")  'my-unindent)
  (local-set-key (kbd "C-i")  'indent-or-complete)
; (local-set-key (quote [(return)]) (quote newline))
; (local-set-key (quote [(control return)]) (quote comint-send-input))
;; I actually liked these two better for adding  newlines, leaving the return with it's default behavior
  (local-set-key (quote [(control j)]) (quote newline))
  (local-set-key (quote [(control m)]) (quote newline))
  (local-set-key (quote [(return)]) (quote comint-send-input)))

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

This was once the most important portion of elisp code I had put into my dot emacs.  Thanks to it I began to conciliate with the editor which I sort of resisted at first, by finding it foreign to my habits and liking. I was accustomed to the way Dreamveaver handled the indenting of selected text in blocks, so getting emacs to behave in such familiar and practical style was critical to ease the experience of using it in a daily basis -as I needed  a couple of years ago when entering a new position.

Pretty soon I saw why emacs was so superior in regard of its extensibility: with basic elisp knowledge, the help of this discussion and a few lines of code tweaked I was able to get indent/unindent function the way I wanted. Now to select text as a block, I not longer needed to set the mark and point exactly from the beginning to the end of a line or group of lines. The code conveniently extended the highlighted area via the tab key, turning the sub-selection into a block containing all the text of the line(s) involved.

Besides, the selection was configured so to not disappear unless you click outside it, that is, while keeping  pressed the tab (or Shift-tab) key(s) you could get the text to shift continuously right (or left).

Having bound the tab key with the indentation functions required a few adjustment to make it context-sensitive. Here is how the tab key behaves after the changes:

  • First, to insert a real tab space, (when the cursor is before a word), you need to use Ctrl -q-tab.
  • If the text point is, at least, a space away from the end of a word, a standard tab-space is inserted.
  • If the text point is exactly at the end of a word, the tab uses the hippie-expand function to auto-complete the word. If Shift+tab are used instead, words are deleted backward one at a time up to the beginning of the line.
  • When editing in org-mode an exception was set to respect its visibility cycling function (org-mode cleverly uses the tab key to rotate the current state of tree and sub-trees).

Finally, Mac and PC users would probably appreciate the added function that makes Shift + click to end a selection of text, which is the default way on both platforms (I grabbed it here, from Dave Peck).

So, as you can see, the whole point of the code below is to ease the manual indentation of code. Not being comfortable with the automatic indentation in emacs, I like to avoid it at times, or for certain modes. Check the associated hack posted on how to deactivate it.

Please, have a look at the elisp lines. Remember that you could copy and paste all into the scratch buffer and do “M-x evaluate-buffer” to get them immediately working.

;; turn on transient mark mode
;;(that is, we highlight the selected text)
(transient-mark-mode t)

(setq my-tab-width 2)

(defun indent-block()
  (shift-region my-tab-width)
  (setq deactivate-mark nil))

(defun unindent-block()
  (shift-region (- my-tab-width))
  (setq deactivate-mark nil))

(defun shift-region(numcols)
" my trick to expand the region to the beginning and end of the area selected
 much in the handy way I liked in the Dreamweaver editor."
  (if (< (point)(mark))
    (if (not(bolp))    (progn (beginning-of-line)(exchange-point-and-mark) (end-of-line)))
    (progn (end-of-line)(exchange-point-and-mark)(beginning-of-line)))
  (setq region-start (region-beginning))
  (setq region-finish (region-end))
    (if (< (point) (mark)) (exchange-point-and-mark))
    (let ((save-mark (mark)))
      (indent-rigidly region-start region-finish numcols))))

(defun indent-or-complete ()
  "Indent region selected as a block; if no selection present either indent according to mode,
or expand the word preceding point. "
  (if  mark-active
    (if (looking-at "\\>")
  (hippie-expand nil)
      (insert "\t"))))

(defun my-unindent()
  "Unindent line, or block if it's a region selected.
When pressing Shift+tab, erase words backward (one at a time) up to the beginning of line.
Now it correctly stops at the beginning of the line when the pointer is at the first char of an indented line. Before the command would (unconveniently)  kill all the white spaces, as well as the last word of the previous line."

  (if mark-active
        (if (looking-back "^[ \t]*")
              ;;"a" holds how many spaces are there to the beginning of the line
              (let ((a (length(buffer-substring-no-properties (point-at-bol) (point)))))
                  ;; delete backwards progressively in my-tab-width steps, but without going further of the beginning of line.
                  (if (> a my-tab-width)
                      (delete-backward-char my-tab-width)
                    (backward-delete-char a)))))
          ;; delete tab and spaces first, if at least 2 exist, before removing words
            (if(looking-back "[ \t]\\{2,\\}")
              (backward-kill-word 1))))))))

(add-hook 'find-file-hooks (function (lambda ()
 (unless (eq major-mode 'org-mode)
(local-set-key (kbd "<tab>") 'indent-or-complete)))))

(if (not (eq  major-mode 'org-mode))
      (define-key global-map "\t" 'indent-or-complete) ;; with this you have to force tab (C-q-tab) to insert a tab after a word
      (define-key global-map [S-tab] 'my-unindent)
      (define-key global-map [C-S-tab] 'my-unindent)))

;; mac and pc users would like selecting text this way
(defun dave-shift-mouse-select (event)
 "Set the mark and then move point to the position clicked on with
 the mouse. This should be bound to a mouse click event type."
 (interactive "e")
 (mouse-minibuffer-check event)
 (if mark-active (exchange-point-and-mark))
 (set-mark-command nil)
 ;; Use event-end in case called from mouse-drag-region.
 ;; If EVENT is a click, event-end and event-start give same value.
 (posn-set-point (event-end event)))

;; be aware that this overrides the function for picking a font. you can still call the command
;; directly from the minibufer doing: "M-x mouse-set-font"
(define-key global-map [S-down-mouse-1] 'dave-shift-mouse-select)

;; to use in into emacs for  unix I  needed this instead
; define-key global-map [S-mouse-1] 'dave-shift-mouse-select)

;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; this final line is only necessary to escape the *scratch* fundamental-mode
;; and let this demonstration work

I revised the code and modified some things. Especially the function ‘my-unindent” in order to add some sophistication on how the shifting backwards now occurs. The main improvement is that deleting backwards will never lead to jumping to the previous line (and removing words from there) . Here’s how the movement to left is done when pressing Shift + tab:

  • killing word by word, if the insertion point is at the right of any text.
  • stepping back, in increments (set by the variable my-tab-width), BUT ALWAYS stopping at the beginning of the line when the cursor is at the left of any indented text.

You can see how much simpler the function looked before I decided to better it:

(defun my-unindent()
  "Unindent line, or block if it's a region selected"
  (if mark-active
    (if(not(bolp))(delete-backward-char 2 ))))

Having discovered that C-i means “tab” (remember that in this setup, tab is associated with the function ‘indent-or-complete) I’m changing “M-i”, to act the same as “Shift-tab” does.
Might come as a good alternative, one supposed to be less stressful on the wrists.
(“Meta + i” comes originally bound to ‘tab-to-tab-stop)

(global-set-key (kbd "M-i") 'my-unindent)