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 )