Skip to content
Mohsin Kaleem edited this page May 25, 2020 · 2 revisions

users of hl-todo may be wanting a convenient way to jump to TODO entries in your current project.

Here's a nice way to get that working using ripgrep or ag.

(require 'hl-todo)
(require 'counsel)

(defvar hl-todo-keyword-faces)

(defgroup counsel-todo nil
  "use ivy to jump to TODOs.")

(defcustom counsel-todo-backend 'rg
  "backend to use for `counsel-todo-command'"
  :group 'counsel-todo
  :type 'symbol
  :options '(rg ag))

(defcustom counsel-todo-command
  (cl-case counsel-todo-backend
    ('rg counsel-rg-base-command)
    ('ag counsel-ag-base-command))
  "command string used for searching with `counsel-todo'.")

(defun counsel-todo-function (string &rest _)
  (let ((regexp (counsel--elisp-to-pcre
                 (cl-loop for (keyword . _) in hl-todo-keyword-faces
                          for i from 1 upto (length hl-todo-keyword-faces)
                          unless (= i 1)
                            do (setq keyword (concat "\\|" keyword))
                          end
                          concat keyword))))
    (counsel--async-command
     (format counsel-todo-command
             (shell-quote-argument regexp)))
    '()))

(cl-defun counsel-todo (arg &optional initial-input initial-directory &key caller)
  (interactive "P")
  (when (and arg (not initial-directory))
    (setq initial-directory
          (counsel-read-directory-name "TODOs in directory: ")))

  (counsel-require-program counsel-todo-command)
  (let ((default-directory (or initial-directory
                               (counsel--git-root)
                               (ivy-state-directory ivy-last)
                               default-directory)))
    (ivy-read "TODO: "
              #'counsel-todo-function
              :initial-input initial-input
              ;; :dynamic-collection t
              :keymap counsel-ag-map
              :history 'counsel-git-grep-history
              :action #'counsel-git-grep-action
              :unwind #'counsel-delete-process
              :require-match t
              :caller (or caller 'counsel-todo))))

;; inherit config from counsel-ag
(ivy-configure 'counsel-todo
  :occur #'counsel-ag-occur
  :unwind-fn #'counsel--grep-unwind
  :display-transformer-fn #'counsel-git-grep-transformer
  :grep-p t
  :exit-codes '(1 "No matches found"))

(provide '+counsel-todo)