Skip to content

Guide to get an intuitive and more ido-like helm interface in Emacs. Recommends configurations and packages to improve helms default interface.

Notifications You must be signed in to change notification settings

clemera/helm-ido-like-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Helm I do like. Yes…

./yoda.jpg

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.

Screenshots

./screenshot.png Theme: hc-zenburn with some adjustments.

Installing helm and friends

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.

Initial setup

(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)

Appearance

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.

Reference

(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.

Reference

(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))

File Navigation

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.

Reference

(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

Reference

(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))

Improve Flx support

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.

Reference Reference

(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))

Helm Adaptive

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.

Reference

(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)

Last Steps

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

About

Guide to get an intuitive and more ido-like helm interface in Emacs. Recommends configurations and packages to improve helms default interface.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •