init-helm.el 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. ;;; Helm
  2. ;;; TODO: helm-ff should allow opening several marks externally, e.g. sxiv for
  3. ;;; pics. See
  4. ;;; https://github.com/emacs-helm/helm/wiki/Find-Files#open-files-externally.
  5. ;;; What about the default program? It currently defaults to ~/.mailcap, which is
  6. ;;; not so customizable. Would ranger's rifle be useful here? See
  7. ;;; https://github.com/emacs-helm/helm/issues/1796. There is the `openwith' package.
  8. ;;; TODO: Batch-open torrent files automatically. Add to mailcap? Same as
  9. ;;; above, C-c C-x does not allow for opening several files at once.
  10. ;;; TODO: helm-find in big folders sometimes leads bad results, like exact match
  11. ;;; not appearing first. Better sorting?
  12. ;;; TODO: Implement alternating-color multiline lists.
  13. ;;; See https://github.com/emacs-helm/helm/issues/1790.
  14. (when (require 'helm-descbinds nil t)
  15. (helm-descbinds-mode))
  16. (when (require 'wgrep-helm nil t)
  17. (setq wgrep-auto-save-buffer t
  18. wgrep-enable-key (kbd "C-x C-q"))
  19. (add-hook 'wgrep-setup-hook #'wgrep-change-to-wgrep-mode))
  20. (when (require 'helm-ls-git nil t)
  21. (setq helm-ls-git-fuzzy-match t)
  22. ;; `helm-source-ls-git' must be defined manually.
  23. ;; See https://github.com/emacs-helm/helm-ls-git/issues/34.
  24. (setq helm-source-ls-git
  25. (and (memq 'helm-source-ls-git helm-ls-git-default-sources)
  26. (helm-make-source "Git files" 'helm-ls-git-source
  27. :fuzzy-match helm-ls-git-fuzzy-match))))
  28. (helm-mode 1)
  29. ;; (helm-autoresize-mode 1)
  30. (add-to-list 'helm-sources-using-default-as-input 'helm-source-man-pages)
  31. ;;; This makes the copy and rename operations asynchronous.
  32. (dired-async-mode)
  33. ;;; Generic configuration.
  34. (setq
  35. helm-follow-mode-persistent t
  36. helm-reuse-last-window-split-state t
  37. helm-display-header-line nil
  38. helm-findutils-search-full-path t
  39. helm-show-completion-display-function nil
  40. helm-completion-mode-string ""
  41. helm-dwim-target 'completion
  42. helm-echo-input-in-header-line t
  43. helm-use-frame-when-more-than-two-windows nil
  44. helm-grep-save-buffer-name-no-confirm t
  45. helm-M-x-fuzzy-match t
  46. helm-apropos-fuzzy-match t
  47. helm-buffers-fuzzy-matching t
  48. helm-completion-in-region-fuzzy-match t
  49. helm-eshell-fuzzy-match t
  50. helm-imenu-fuzzy-match t
  51. helm-locate-library-fuzzy-match t
  52. helm-recentf-fuzzy-match t
  53. ;; To prevent M-s f from directly going to symbol at point if in same buffer.
  54. helm-imenu-execute-action-at-once-if-one nil
  55. ;; Use woman instead of man.
  56. ;; helm-man-or-woman-function nil
  57. ;; https://github.com/emacs-helm/helm/issues/1910
  58. helm-buffers-end-truncated-string "…"
  59. helm-buffer-max-length 22
  60. ;; Default needs special font
  61. helm-ff-cache-mode-lighter " ⚒"
  62. helm-ff-keep-cached-candidates nil ; 'all tends to run out-of-sync to frequently.
  63. helm-window-show-buffers-function 'helm-window-mosaic-fn
  64. helm-window-prefer-horizontal-split t)
  65. (defun ambrevar/helm-split-window-combined-fn (window)
  66. "Helm window splitting that combined most standard features.
  67. - With C-u, split inside. With C-u C-u, use same window.
  68. - Else use biggest other window when available.
  69. - Else split horizontally if width>height, vertically otherwise."
  70. (cond
  71. ((or (minibufferp helm-current-buffer)
  72. (and
  73. (not (one-window-p t))
  74. (not (equal current-prefix-arg '(4)))
  75. (not (equal current-prefix-arg '(16)))))
  76. ;; Find biggest window.
  77. (let (biggest (maxarea 0))
  78. (dolist (w (window-list))
  79. (unless (eq w (selected-window))
  80. (let ((area (* (window-pixel-width w) (window-pixel-height w))))
  81. (when (> area maxarea)
  82. (setq maxarea area
  83. biggest w)))))
  84. biggest))
  85. ((equal current-prefix-arg '(16))
  86. ;; Same window.
  87. (selected-window))
  88. (t
  89. ;; If split inside or if unique window.
  90. (split-window (selected-window) nil
  91. (if (> (window-pixel-width) (window-pixel-height))
  92. 'right
  93. 'below)))))
  94. (setq helm-split-window-preferred-function 'ambrevar/helm-split-window-combined-fn)
  95. ;;; Add bindings to `helm-apropos`. See
  96. ;;; https://github.com/emacs-helm/helm/issues/1140.
  97. (defun ambrevar/helm-def-source--emacs-commands (&optional default)
  98. (helm-build-in-buffer-source "Commands"
  99. :init `(lambda ()
  100. (helm-apropos-init 'commandp ,default))
  101. :fuzzy-match helm-apropos-fuzzy-match
  102. :filtered-candidate-transformer (and (null helm-apropos-fuzzy-match)
  103. 'helm-apropos-default-sort-fn)
  104. :candidate-transformer 'helm-M-x-transformer-1
  105. :nomark t
  106. :persistent-action (lambda (candidate)
  107. (helm-elisp--persistent-help
  108. candidate 'helm-describe-function))
  109. :persistent-help "Toggle describe command"
  110. :action '(("Describe function" . helm-describe-function)
  111. ("Find function" . helm-find-function)
  112. ("Info lookup" . helm-info-lookup-symbol))))
  113. (advice-add 'helm-def-source--emacs-commands :override 'ambrevar/helm-def-source--emacs-commands)
  114. ;;; Make `helm-mini' almighty.
  115. (require 'helm-bookmark)
  116. (setq helm-mini-default-sources `(helm-source-buffers-list
  117. helm-source-recentf
  118. ,(when (boundp 'helm-source-ls-git) 'helm-source-ls-git)
  119. helm-source-bookmarks
  120. helm-source-bookmark-set
  121. helm-source-buffer-not-found))
  122. ;;; Eshell
  123. (defun ambrevar/helm/eshell-set-keys ()
  124. (define-key eshell-mode-map [remap eshell-pcomplete] 'helm-esh-pcomplete)
  125. (define-key eshell-mode-map (kbd "M-p") 'helm-eshell-history)
  126. (define-key eshell-mode-map (kbd "M-s") nil) ; Useless when we have 'helm-eshell-history.
  127. (define-key eshell-mode-map (kbd "M-s f") 'helm-eshell-prompts-all))
  128. (add-hook 'eshell-mode-hook 'ambrevar/helm/eshell-set-keys)
  129. ;;; Comint
  130. (defun ambrevar/helm/comint-set-keys ()
  131. (define-key comint-mode-map (kbd "M-s f") 'helm-comint-prompts-all)
  132. (define-key comint-mode-map (kbd "M-p") 'helm-comint-input-ring))
  133. (add-hook 'comint-mode-hook 'ambrevar/helm/comint-set-keys)
  134. ;;; Geiser
  135. (with-eval-after-load 'geiser-repl
  136. (defun ambrevar/helm/geiser-set-keys ()
  137. (define-key geiser-repl-mode-map (kbd "M-p") 'helm-comint-input-ring))
  138. (add-hook 'geiser-repl-mode-hook 'ambrevar/helm/geiser-set-keys))
  139. ;;; SLY
  140. (with-eval-after-load 'sly
  141. (with-eval-after-load 'sly-mrepl
  142. (define-key sly-mrepl-mode-map (kbd "M-p") 'helm-comint-input-ring)
  143. (define-key sly-mrepl-mode-map (kbd "M-s f") 'helm-comint-prompts-all)
  144. (define-key sly-autodoc-mode-map (kbd "C-c C-d C-a") 'helm-sly-apropos)
  145. (define-key sly-mrepl-mode-map (kbd "C-c C-x c") 'helm-sly-list-connections)))
  146. ;;; SLIME
  147. (with-eval-after-load 'slime
  148. (when (require 'helm-slime nil t)
  149. (with-eval-after-load 'slime-repl
  150. (defun ambrevar/helm/slime-set-keys ()
  151. (define-key slime-repl-mode-map (kbd "M-p") 'helm-slime-repl-history)
  152. (define-key slime-repl-mode-map (kbd "M-s") nil)
  153. (define-key slime-repl-mode-map (kbd "M-s f") 'helm-comint-prompts-all)
  154. (define-key slime-autodoc-mode-map (kbd "C-c C-d C-a") 'helm-slime-apropos)
  155. (define-key slime-repl-mode-map (kbd "C-c C-x c") 'helm-slime-list-connections)
  156. ;; REVIEW: Seems that helm-company is less useful than helm-slime-complete.
  157. (define-key slime-mode-map (kbd "M-<tab>") 'helm-slime-complete)
  158. (dolist (key '("M-<tab>" "<tab>"))
  159. (define-key slime-repl-mode-map (kbd key) 'helm-slime-complete)))
  160. (add-hook 'slime-repl-mode-hook 'ambrevar/helm/slime-set-keys))))
  161. ;;; TODO: Use helm-ff history in helm file completion.
  162. ;;; https://github.com/emacs-helm/helm/issues/1118
  163. ;; (define-key helm-read-file-map (kbd "M-p") 'helm-ff-run-switch-to-history)
  164. (defun ambrevar/helm-grep-git-or-ag (arg)
  165. "Run `helm-grep-do-git-grep' if possible; fallback to `helm-do-grep-ag' otherwise.
  166. Requires `call-process-to-string' from `functions'."
  167. (interactive "P")
  168. (require 'vc)
  169. (require 'functions)
  170. (if (and (vc-find-root default-directory ".git")
  171. (or arg (split-string (ambrevar/call-process-to-string "git" "ls-files" "-z") "\0" t)))
  172. (helm-grep-do-git-grep arg)
  173. (helm-do-grep-ag nil)))
  174. (defun ambrevar/helm-grep-git-all-or-ag ()
  175. "Run `helm-grep-do-git-grep' over all git files."
  176. (interactive)
  177. (helm-grep-do-git-grep t))
  178. (defun ambrevar/helm-mark-or-exchange-rect ()
  179. "Run `helm-all-mark-rings-before-mark-point' or `rectangle-exchange-point-and-mark' if in rectangle-mark-mode."
  180. (interactive)
  181. (if rectangle-mark-mode
  182. (rectangle-exchange-point-and-mark)
  183. (helm-all-mark-rings)))
  184. (global-set-key [remap execute-extended-command] 'helm-M-x)
  185. (global-set-key [remap find-file] 'helm-find-files)
  186. (global-set-key [remap occur] 'helm-occur)
  187. (global-set-key [remap list-buffers] 'helm-mini)
  188. ;; (global-set-key [remap dabbrev-expand] 'helm-dabbrev)
  189. (global-set-key [remap yank-pop] 'helm-show-kill-ring)
  190. ;;; Do not remap 'exchange-point-and-mark, Evil needs it in visual mode.
  191. (global-set-key (kbd "C-x C-x") 'ambrevar/helm-mark-or-exchange-rect)
  192. (global-set-key [remap apropos-command] 'helm-apropos)
  193. (global-set-key [remap query-replace-regexp] 'helm-regexp)
  194. (unless (boundp 'completion-in-region-function)
  195. (define-key lisp-interaction-mode-map [remap completion-at-point] 'helm-lisp-completion-at-point) ; TODO: Used?
  196. (define-key emacs-lisp-mode-map [remap completion-at-point] 'helm-lisp-completion-at-point))
  197. (ambrevar/global-set-keys
  198. "C-x M-g" 'ambrevar/helm-grep-git-or-ag
  199. "C-x M-G" 'helm-do-grep-ag)
  200. ;;; Use the M-s prefix just like `occur'.
  201. (define-key prog-mode-map (kbd "M-s f") 'helm-imenu-in-all-buffers)
  202. ;;; The text-mode-map binding targets structured text modes like Markdown.
  203. (define-key text-mode-map (kbd "M-s f") 'helm-imenu-in-all-buffers)
  204. (with-eval-after-load 'org
  205. (require 'helm-org-contacts nil t)
  206. (define-key org-mode-map (kbd "M-s f") 'helm-org-in-buffer-headings))
  207. (with-eval-after-load 'woman
  208. (define-key woman-mode-map (kbd "M-s f") 'helm-imenu))
  209. (with-eval-after-load 'man
  210. (define-key Man-mode-map (kbd "M-s f") 'helm-imenu))
  211. (setq helm-source-names-using-follow '("Occur" "Git-Grep" "AG" "mark-ring" "Org Headings"
  212. "Imenu" "Imenu in all buffers"
  213. "All Eshell prompts" "All comint prompts"))
  214. ;;; From https://www.reddit.com/r/emacs/comments/5q922h/removing_dot_files_in_helmfindfiles_menu/.
  215. (defun ambrevar/helm-skip-dots (old-func &rest args)
  216. "Skip . and .. initially in helm-find-files. First call OLD-FUNC with ARGS."
  217. (apply old-func args)
  218. (let ((sel (helm-get-selection)))
  219. (if (and (stringp sel) (string-match "/\\.$" sel))
  220. (helm-next-line 2)))
  221. (let ((sel (helm-get-selection))) ; if we reached .. move back
  222. (if (and (stringp sel) (string-match "/\\.\\.$" sel))
  223. (helm-previous-line 1))))
  224. (advice-add #'helm-preselect :around #'ambrevar/helm-skip-dots)
  225. (advice-add #'helm-ff-move-to-first-real-candidate :around #'ambrevar/helm-skip-dots)
  226. (with-eval-after-load 'desktop
  227. (add-to-list 'desktop-globals-to-save 'kmacro-ring)
  228. (add-to-list 'desktop-globals-to-save 'last-kbd-macro)
  229. (add-to-list 'desktop-globals-to-save 'kmacro-counter)
  230. (add-to-list 'desktop-globals-to-save 'kmacro-counter-format)
  231. (add-to-list 'desktop-globals-to-save 'helm-ff-history)
  232. (add-to-list 'desktop-globals-to-save 'comint-input-ring))
  233. (helm-top-poll-mode)
  234. ;;; Column indices might need some customizing. See `helm-top-command' and
  235. ;;; https://github.com/emacs-helm/helm/issues/1586 and
  236. ;;; https://github.com/emacs-helm/helm/issues/1909.
  237. ;;; Fallback on 'find' if 'locate' is not available.
  238. (unless (executable-find "locate")
  239. (setq helm-locate-recursive-dirs-command "find %s -type d -regex .*%s.*$"))
  240. ;; See https://github.com/emacs-helm/helm/issues/1962.
  241. (defun ambrevar/helm-locate-meta (&optional update)
  242. "Like `helm-locate' but also use the databases found in /media and /run/media.
  243. With prefix argument, UPDATE the databases with custom uptions thanks to the
  244. 'updatedb-local' script."
  245. (interactive "P")
  246. (let ((user-db (expand-file-name "~/.cache/locate.db"))
  247. (media-dbs (apply 'append
  248. (mapcar
  249. (lambda (root)
  250. (append (ignore-errors (file-expand-wildcards (concat root "/*/locate.db")))
  251. ;; Also lookup subfolders; useful when root has snapshots (.e.g Btrfs).
  252. (ignore-errors (file-expand-wildcards (concat root "/*/*/locate.db")))))
  253. (list (concat "/run/media/" (user-login-name))
  254. (concat "/media/" (user-login-name))
  255. "/media")))))
  256. (when update
  257. (with-temp-buffer
  258. (if (= (shell-command "updatedb-local" (current-buffer)) 0)
  259. (message "%s" (buffer-string))
  260. (error "%s" (current-buffer)))))
  261. (helm-locate-with-db
  262. (mapconcat 'identity
  263. (cons user-db media-dbs)
  264. ":")
  265. nil (thing-at-point 'filename))))
  266. ;;; Convenience.
  267. (defun ambrevar/helm-toggle-visible-mark-backwards (arg)
  268. (interactive "p")
  269. (helm-toggle-visible-mark (- arg)))
  270. (define-key helm-map (kbd "S-SPC") 'ambrevar/helm-toggle-visible-mark-backwards)
  271. (global-set-key (kbd "C-<f4>") 'helm-execute-kmacro)
  272. ;; From https://github.com/thierryvolpiatto/emacs-tv-config/blob/master/init-helm.el:
  273. (defmethod helm-setup-user-source ((source helm-source-ffiles))
  274. (helm-source-add-action-to-source-if
  275. "Magit status"
  276. (lambda (_candidate)
  277. (magit-status helm-ff-default-directory))
  278. source
  279. (lambda (candidate)
  280. (and (not (string-match-p ffap-url-regexp candidate))
  281. helm-ff-default-directory
  282. (locate-dominating-file helm-ff-default-directory ".git")))
  283. 1)
  284. (helm-source-add-action-to-source-if
  285. "Add to EMMS playlist"
  286. (lambda (_candidate)
  287. (when (require 'emms nil 'noerror)
  288. (dolist (c (helm-marked-candidates))
  289. (emms-add-directory-tree c))))
  290. source
  291. (lambda (candidate)
  292. (or (file-directory-p candidate)
  293. (and (file-name-extension candidate)
  294. (string-match-p (concat (regexp-opt '("mp3" "mp4" "m4a" "ogg" "flac" "spx" "wma" "wv")) "$")
  295. (file-name-extension candidate)))))
  296. 1))
  297. (defun ambrevar/helm-external-command-cleanup-dotted (old-function &optional args)
  298. "Remove dotted programs from `helm-run-external-command' list.
  299. Useful for Guix."
  300. (funcall old-function args)
  301. (setq helm-external-commands-list
  302. (cl-delete-if (lambda (p) (string-prefix-p "." p))
  303. helm-external-commands-list)))
  304. (advice-add 'helm-external-commands-list-1
  305. :around #'ambrevar/helm-external-command-cleanup-dotted)
  306. ;; From https://github.com/emacs-helm/helm/issues/2149:
  307. (defun helm-occur-extract-urls-from-line (line)
  308. (with-temp-buffer
  309. (save-excursion (insert "\n" line))
  310. (cl-loop while (re-search-forward "\\(https?\\|ftp\\)://[^ >]*" nil t)
  311. collect (match-string 0))))
  312. (defun helm-occur-browse-urls (_candidate)
  313. (let ((urls (helm-occur-extract-urls-from-line (helm-get-selection nil t))))
  314. (browse-url (helm-comp-read "Url: " urls :exec-when-only-one t))))
  315. (defun helm-occur-action-transformer (actions _candidate)
  316. (cond ((string-match "\\(https?\\|ftp\\)://[^ >]*" (helm-get-selection nil t))
  317. (helm-append-at-nth actions
  318. '(("Browse urls in line" . helm-occur-browse-urls))
  319. 1))
  320. (t actions)))
  321. (defmethod helm-setup-user-source ((source helm-moccur-class))
  322. (setf (slot-value source 'action-transformer) 'helm-occur-action-transformer))
  323. (when (require 'helm-switch-to-repl nil :noerror)
  324. (helm-switch-to-repl-setup))
  325. (provide 'init-helm)