I found a bug, here is the code again: 0. if the current line contains hidden text, it shows the block 1. otherwise, the first tab indents, and the second tab hides 2. afterwards, it switches hide/show (defun tab-hs-hide () (interactive) (let ((obj (car (overlays-in (save-excursion (move-beginning-of-line nil ) (point) ) (save-excursion (move-end-of-line nil) (point) ) ) ) ) ) (cond ((and (null obj) (eq last-command this-command) ) (hs-hide-block) ) ((and (overlayp obj) (eq 'hs (overlay-get obj 'invisible))) (progn (move-beginning-of-line nil) (hs-show-block) ) ) (t (funcall (lookup-key (current-global-map) (kbd "^I") ) ) ) ) ) ) (define-key hs-minor-mode-map [tab] 'tab-hs-hide )