This guide was intended for people which used ido in the past and wanted helm to behave more like ido (ido + flx-ido + ido-vertical-mode + smex). Now this guide inlcudes many snippets which are useful for helm usage in general and has become more of collection of configuration tips and recommended packages to improve helms default interface.
For now the configuration snippets and packages will provide the following features for you:
- Always pop up at the bottom
- Nice search through an overview of matches with helm-swoop
- Input in header line and hide the minibuffer
- Show helm source headers only when necessary
- No mode-lines above the helm buffer
- Flx support with gc adjustment to improve speed.
- DEL and RETURN for file navigation like in ido
- Remove the dots for current and parent directory in helm file navigation in non directory selections
- Smex support for
helm-M-x
, to adjust search results for recent and most frequent used commands - Remember last candidates for more sources with
helm-adaptive
(Not recommended for now)
For more great helm-hacks have a look at helm-ext.
NOTE
This is a work in progress. If you encounter any problems let me know. Some of this is my own work, but most is based on work of others that I summarized in this tutorial. Everyone has his own opinions what is considered an improvement because of that I have splitted the guide into parts where each snippet should be independent of others, so you can just pick what you like.
Theme: hc-zenburn with some adjustments.
If you haven’t already go and install helm, helm-swoop, helm-flx, helm-fuzzier, smex and helm-smex. You can do it quickly by evaluating the following snippet.
(require 'package)
;; Note that certificate verfication in Emacs 24.4 needs some
;; manual adjustments if you want to be really secure.
;; Read this for more info on this: https://glyph.twistedmatrix.com/2015/11/editor-malware.html
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
(package-refresh-contents)
(mapcar #'(lambda (package) (unless (package-installed-p package) (package-install package)))
'(helm helm-swoop helm-flx helm-fuzzier smex helm-smex dash))
Now either follow this guide or jump to Last Steps if you want see how to activate all the snippets provided by this guide at once.
(defun helm-ido-like-activate-helm-modes ()
(require 'helm-config)
(helm-mode 1)
(helm-flx-mode 1)
(helm-fuzzier-mode 1))
Now don’t forget to bind keys for the helm-smex commands:
(global-set-key [remap execute-extended-command] #'helm-smex)
(global-set-key (kbd "M-X") #'helm-smex-major-mode-commands)
The following snippet will configure helm to always pop up at the bottom.
(defun helm-ido-like-load-ido-like-bottom-buffer ()
;; popup helm-buffer at the bottom
(setq helm-split-window-in-side-p t)
(add-to-list 'display-buffer-alist
'("\\`\\*helm.*\\*\\'"
(display-buffer-in-side-window)
(window-height . 0.4)))
(add-to-list 'display-buffer-alist
'("\\`\\*helm help\\*\\'"
(display-buffer-pop-up-window)))
;; same for helm swoop
(setq helm-swoop-split-with-multiple-windows nil
helm-swoop-split-direction 'split-window-vertically
helm-swoop-split-window-function 'helm-default-display-buffer)
;; dont display the header line
(setq helm-display-header-line nil)
;; input in header line
(setq helm-echo-input-in-header-line t)
(add-hook 'helm-minibuffer-set-up-hook 'helm-hide-minibuffer-maybe))
With newer helm versions you can also use the following instead:
(with-eval-after-load 'helm (setq helm-always-two-windows nil) (setq helm-display-buffer-default-height 15) (setq helm-default-display-buffer-functions '(display-buffer-in-side-window)))
The modelines above the helm buffer are not useful and hiding them will make helm look nicer in my opinion, too. You can do that with the following code.
(defvar helm-ido-like-bottom-buffers nil
"List of bottom buffers before helm session started.
Its element is a pair of `buffer-name' and `mode-line-format'.")
(defun helm-ido-like-bottom-buffers-init ()
(setq-local mode-line-format (default-value 'mode-line-format))
(setq helm-ido-like-bottom-buffers
(cl-loop for w in (window-list)
when (window-at-side-p w 'bottom)
collect (with-current-buffer (window-buffer w)
(cons (buffer-name) mode-line-format)))))
(defun helm-ido-like-bottom-buffers-hide-mode-line ()
(mapc (lambda (elt)
(with-current-buffer (car elt)
(setq-local mode-line-format nil)))
helm-ido-like-bottom-buffers))
(defun helm-ido-like-bottom-buffers-show-mode-line ()
(when helm-ido-like-bottom-buffers
(mapc (lambda (elt)
(with-current-buffer (car elt)
(setq-local mode-line-format (cdr elt))))
helm-ido-like-bottom-buffers)
(setq helm-ido-like-bottom-buffers nil)))
(defun helm-ido-like-helm-keyboard-quit-advice (orig-func &rest args)
(helm-ido-like-bottom-buffers-show-mode-line)
(apply orig-func args))
(defun helm-ido-like-hide-modelines ()
;; hide The Modelines while Helm is active
(add-hook 'helm-before-initialize-hook #'helm-ido-like-bottom-buffers-init)
(add-hook 'helm-after-initialize-hook #'helm-ido-like-bottom-buffers-hide-mode-line)
(add-hook 'helm-exit-minibuffer-hook #'helm-ido-like-bottom-buffers-show-mode-line)
(add-hook 'helm-cleanup-hook #'helm-ido-like-bottom-buffers-show-mode-line)
(advice-add 'helm-keyboard-quit :around #'helm-ido-like-helm-keyboard-quit-advice))
If you like you can hide helms own mode-line as well:
(defun helm-ido-like-hide-helm-modeline-1 ()
"Hide mode line in `helm-buffer'."
(with-helm-buffer
(setq-local mode-line-format nil)))
(defun helm-ido-like-hide-helm-modeline ()
(fset 'helm-display-mode-line #'ignore)
(add-hook 'helm-after-initialize-hook 'helm-ido-like-hide-helm-modeline-1))
The header lines for the sources are only useful if there are more then a single source. The following snippet will hide the header line if there is only one.
(defvar helm-ido-like-source-header-default-background nil)
(defvar helm-ido-like-source-header-default-foreground nil)
(defvar helm-ido-like-source-header-default-box nil)
(defun helm-ido-like-toggle-header-line ()
;; Only Show Source Headers If More Than One
(if (> (length helm-sources) 1)
(set-face-attribute 'helm-source-header
nil
:foreground helm-ido-like-source-header-default-foreground
:background helm-ido-like-source-header-default-background
:box helm-ido-like-source-header-default-box
:height 1.0)
(set-face-attribute 'helm-source-header
nil
:foreground (face-attribute 'helm-selection :background)
:background (face-attribute 'helm-selection :background)
:box nil
:height 0.1)))
(defun helm-ido-like-header-lines-maybe ()
(setq helm-ido-like-source-header-default-background (face-attribute 'helm-source-header :background))
(setq helm-ido-like-source-header-default-foreground (face-attribute 'helm-source-header :foreground))
(setq helm-ido-like-source-header-default-box (face-attribute 'helm-source-header :box))
(add-hook 'helm-before-initialize-hook 'helm-ido-like-toggle-header-line))
If you like you can change the background color of the helm-buffer.
(defvar helm-ido-like-bg-color (face-attribute 'default :background))
(defun helm-ido-like-setup-bg-color-1 ()
(with-helm-buffer
(make-local-variable 'face-remapping-alist)
(add-to-list 'face-remapping-alist `(default (:background ,helm-ido-like-bg-color)))))
(defun helm-ido-like-setup-bg-color ()
(add-hook 'helm-after-initialize-hook 'helm-ido-like-setup-bg-color-1))
The following snippet will reconfigure the behaviour of keys in helm file navigation buffers.
Backspace goes to the upper folder if you are not inside a filename, and Return will select a file or navigate into the directory if it is one.
(defun helm-ido-like-find-files-up-one-level-maybe ()
(interactive)
(if (looking-back "/" 1)
(call-interactively 'helm-find-files-up-one-level)
(delete-char -1)))
(defun helm-ido-like-find-files-navigate-forward (orig-fun &rest args)
"Adjust how helm-execute-persistent actions behaves, depending on context."
(let ((sel (helm-get-selection)))
(if (file-directory-p sel)
;; the current dir needs to work to
;; be able to select directories if needed
(cond ((and (stringp sel)
(string-match "\\.\\'" (helm-get-selection)))
(helm-maybe-exit-minibuffer))
(t
(apply orig-fun args)))
(helm-maybe-exit-minibuffer))))
(defun helm-ido-like-load-file-nav ()
(advice-add 'helm-execute-persistent-action :around #'helm-ido-like-find-files-navigate-forward)
;; <return> is not bound in helm-map by default
(define-key helm-map (kbd "<return>") 'helm-maybe-exit-minibuffer)
(with-eval-after-load 'helm-files
(define-key helm-read-file-map (kbd "<backspace>") 'helm-ido-like-find-files-up-one-level-maybe)
(define-key helm-read-file-map (kbd "DEL") 'helm-ido-like-find-files-up-one-level-maybe)
(define-key helm-find-files-map (kbd "<backspace>") 'helm-ido-like-find-files-up-one-level-maybe)
(define-key helm-find-files-map (kbd "DEL") 'helm-ido-like-find-files-up-one-level-maybe)
(define-key helm-find-files-map (kbd "<return>") 'helm-execute-persistent-action)
(define-key helm-read-file-map (kbd "<return>") 'helm-execute-persistent-action)
(define-key helm-find-files-map (kbd "RET") 'helm-execute-persistent-action)
(define-key helm-read-file-map (kbd "RET") 'helm-execute-persistent-action)))
And this snippet will remove the dots in helm file navigation
(defvar helm-ido-like-no-dots-whitelist
'("*Helm file completions*")
"List of helm buffers in which to show dot directories.")
(defun helm-ido-like-no-dots-display-file-p (file)
;; in a whitelisted buffer display all but the relative path to parent dir
(or (and (member helm-buffer helm-ido-like-no-dots-whitelist)
(not (string-match "\\(?:/\\|\\`\\)\\.\\{2\\}\\'" file)))
;; in all other buffers display all files but the two relative ones
(not (string-match "\\(?:/\\|\\`\\)\\.\\{1,2\\}\\'" file))))
(defun helm-ido-like-no-dots-auto-add (&rest args)
"Auto add buffers which want to read directory names to the whitelist."
(if (eq (car (last args)) 'file-directory-p)
(add-to-list 'helm-ido-like-no-dots-whitelist
(format "*helm-mode-%s*"
(helm-symbol-name
(or (helm-this-command) this-command))))))
(defun helm-ido-like-no-dots ()
(require 'cl-lib)
(advice-add 'helm-ff-filter-candidate-one-by-one
:before-while 'helm-ido-like-no-dots-display-file-p)
(advice-add 'helm--generic-read-file-name :before 'helm-ido-like-no-dots-auto-add))
And you can increase flx speed (I have not benchmarked it myself) by adjusting the garbage collection setting. In addition to that the following snippet advices the helm source function to enable the flx fuzzy match in most sources but file completions(you still have fuzzy matching from helm) and async sources.
(defvar helm-ido-like-user-gc-setting nil)
(defun helm-ido-like-higher-gc ()
(setq helm-ido-like-user-gc-setting gc-cons-threshold)
(setq gc-cons-threshold most-positive-fixnum))
(defun helm-ido-like-lower-gc ()
(setq gc-cons-threshold helm-ido-like-user-gc-setting))
(defun helm-ido-like-helm-make-source (f &rest args)
(let ((source-type (cadr args)))
(unless (or (memq source-type '(helm-source-async helm-source-ffiles))
(eq (plist-get args :filtered-candidate-transformer)
'helm-ff-sort-candidates)
(eq (plist-get args :persistent-action)
'helm-find-files-persistent-action))
(nconc args '(:fuzzy-match t))))
(apply f args))
(defun helm-ido-like-load-fuzzy-enhancements ()
(add-hook 'minibuffer-setup-hook #'helm-ido-like-higher-gc)
(add-hook 'minibuffer-exit-hook #'helm-ido-like-lower-gc)
(advice-add 'helm-make-source :around 'helm-ido-like-helm-make-source))
With recent helm version there is a problem for file navigation, when helm-fuzzier is activated. Because of that it’s better to deactivate it for file completions
(defun helm-ido-like-fuzzier-deactivate (&rest _)
(helm-fuzzier-mode -1))
(defun helm-ido-like-fuzzier-activate (&rest _)
(unless helm-fuzzier-mode
(helm-fuzzier-mode 1)))
(defun helm-ido-like-fix-fuzzy-files ()
(add-hook 'helm-find-files-before-init-hook #'helm-ido-like-fuzzier-deactivate)
(advice-add 'helm--generic-read-file-name :before #'helm-ido-like-fuzzier-deactivate)
(add-hook 'helm-exit-minibuffer-hook #'helm-ido-like-fuzzier-activate)
(add-hook 'helm-cleanup-hook #'helm-ido-like-fuzzier-activate)
(advice-add 'helm-keyboard-quit :before #'helm-ido-like-fuzzier-activate))
This will offer last choosen candidates first for more sources, with support for flx.
I only use it to remember describe-function
and describe-variable
, if you want
to use it for other sources add them like shown below.
Warning: After some usage it stopped working correctly and sorted the results badly. I can live without it, but maybe I will try to fix it later.
(with-eval-after-load 'helm-adaptive
(defcustom helm-adaptive-enabled-sources '()
"List of Helm Source names for which helm-adaptive will remember history."
:type '(repeat string)
:group 'helm-adapt)
;; Remember history for these sources add more sources here if you like
(add-to-list 'helm-adaptive-enabled-sources "describe-function")
(add-to-list 'helm-adaptive-enabled-sources "describe-variable")
;; Clobber helm's implementation
(defun helm-adapt-use-adaptive-p (&optional source-name)
"Return current source only if it use adaptive history, nil otherwise."
(when helm-adaptive-mode
(let* ((source (or source-name (helm-get-current-source)))
(adapt-source (when (listp source)
(or (assoc-default 'filtered-candidate-transformer
(assoc (assoc-default 'type source)
helm-type-attributes))
(assoc-default 'candidate-transformer
(assoc (assoc-default 'type source)
helm-type-attributes))
(assoc-default 'filtered-candidate-transformer source)
(assoc-default 'candidate-transformer source)))))
(cond
((member (cdr (assoc 'name source)) helm-adaptive-enabled-sources)
source)
((listp adapt-source)
(and (member 'helm-adaptive-sort adapt-source) source))
((eq adapt-source 'helm-adaptive-sort)
source)))))
(require 'dash)
(setq helm-fuzzy-sort-fn
(lambda (candidates source &optional use-real)
(-> candidates
(helm-flx-fuzzy-matching-sort source use-real)
(helm-adaptive-sort source)
))
helm-fuzzy-matching-highlight-fn #'helm-flx-fuzzy-highlight-match))
(helm-adaptive-mode 1)
If you want to load all configurations from this guide
require the file in your init and call (helm-ido-like)
.
;;;###autoload
(defun helm-ido-like ()
"Configure and activate `helm', `helm-fuzzier' and `helm-flx'."
(interactive)
(helm-ido-like-activate-helm-modes)
(helm-ido-like-load-ido-like-bottom-buffer)
(helm-ido-like-hide-modelines)
(helm-ido-like-hide-helm-modeline)
(helm-ido-like-header-lines-maybe)
(helm-ido-like-setup-bg-color)
(helm-ido-like-load-file-nav)
(helm-ido-like-no-dots)
(helm-ido-like-load-fuzzy-enhancements)
(helm-ido-like-fix-fuzzy-files))
(provide 'helm-ido-like)
;;; helm-ido-like.el ends here