@alvaro
sign in · lmno.lol

Projectile shell dir company completion

Projectile and company are just amazing Emacs packages. Projectile gives random access to files, while company completes well… anything. For shells, Emacs has a handful of options.

Standing on the shoulders of package giants (dash and f included) and some elisp, we can bring random access to project directories from the shell.

(require 'cl-lib)
(require 'company)
(require 'dash)
(require 'f)
(require 'projectile)

(defvar-local company-projectile-cd-prefix "cd ")

(defun company-projectile-cd (command &optional arg &rest ignored)
  "Company shell completion for any projectile path."
  (interactive (list 'interactive))
  (case command
    (interactive (company-begin-backend 'company-projectile-cd))
    (prefix
     (company-grab-symbol-cons company-projectile-cd-prefix
                               (length company-projectile-cd-prefix)))
    (candidates
     (company-projectile-cd--candidates
      (company-grab-symbol-cons company-projectile-cd-prefix
                                (length company-projectile-cd-prefix))))
    (post-completion
     (company-projectile-cd--expand-inserted-path arg))))

(defun company-projectile-cd--candidates (input)
  "Return candidates for given INPUT."
  (company-projectile-cd--reset-root)
  (when (consp input)
    (let ((search-term (substring-no-properties
                        (car input) 0 (length (car input))))
          (prefix-found (cdr input)))
      (when prefix-found
        (if (projectile-project-p)
            (company-projectile-cd--projectile search-term)
          (company-projectile-cd--find-fallback search-term))))))

(defun company-projectile-cd--projectile (search-term)
  (-filter (lambda (path)
             (string-match-p (regexp-quote
                              search-term)
                             path))
           (-snoc
            (projectile-current-project-dirs)
            ;; Throw project root in there also.
            (projectile-project-root))))

(defun company-projectile-cd--find-fallback (search-term)
  (ignore-errors
    (-map (lambda (path)
            (string-remove-prefix "./" path))
          (apply #'process-lines
                 (list "find" "." "-type" "d"  "-maxdepth" "2" "-iname"
                       (format "\*%s\*" search-term))))))

(defun company-projectile-cd--expand-inserted-path (path)
  "Replace relative PATH insertion with its absolute equivalent if needed."
  (unless (f-exists-p path)
    (delete-region (point) (- (point) (length path)))
    (insert (concat (projectile-project-root) path))))

(defun company-projectile-cd--reset-root ()
  "Reset project root. Useful when cd'ing in and out of projects."
  (projectile-reset-cached-project-root)
  (when (projectile-project-p)
    (projectile-project-root)))