7

I use the emacs shell-mode more and more, and there's something that I wish could be improved: the completion when changing directory. I'd love to use ido or projectile-find-dir for that.

My workflow

As of today I do all I can outside of emacs' shell, to use the power of emacs as much as possible (visiting files with ido, finding files in project with projectile, exploring the tree inside dired,…).

I don't cd that often. When I work in a different project I open up another shell buffer. But when I have to, I really miss ido or the fasd shell utility (which works, but without its completion interface which is great with zsh, and which isn't as powerfull as the use of ido could be https://github.com/clvv/fasd).

How to wire that in elisp ?

I know we can give a list to ido-completing-read;

In the shell, typing cd ../<TAB> opens up a new *Completions* buffer. It uses comint-dynamic-completion, but how to get that list in an elisp list, not in a buffer ?

  • is it possible to wire that completions list into ido ? (or projectile or helm or whatever)
  • I would appreciate too if you link me to accurate documentation (there's a lot, it's difficult to know what's useful for me)
  • or does a solution exist yet ?

Thanks !

edit: here is another nice way to cd to recently visited directories, with the fasd utility and ido completion: https://gitlab.com/emacs-stuff/fasd-shell/blob/master/README.org

See another SO question.

ps: eshell doesn't work well with some shell scripts, I'd like to stay in shell-mode.

1
  • In regards to your PS, if you don't have many shell scripts, like i do, i run M-& for async-shell-command. Commented Jan 7, 2014 at 15:13

1 Answer 1

4

Try this, it is a quick and dirty hack and may fail in some cases but should work generally. Also pardon my elisp

(require 'ido) (require 'cl-lib) (require 'shell) (defvar my-dir-selected nil "Flag to indicate that user has selected the directory") (defun my-filter-cd-input (current-input) "Takes current user input for `cd' the a list whose car is the 'maximum possible directory path' and cdr is remaining string. Examples: '~/.emacs.d/in => ('~./emacs.d/' 'in') '/home/gue' => ('/home/' 'gue') '~/../' => ('~/../' '')" (let* ((unquoted-input (shell-unquote-argument current-input)) (components (split-string unquoted-input "/")) (directory-parts (butlast components)) (possible-prefix (car (last components)))) (list (if (string= possible-prefix "") unquoted-input (concat (mapconcat 'identity directory-parts "/") (when directory-parts "/"))) possible-prefix))) (defun my-complete-directory-name (directory current-input) "Prompts user for directories in `directory', `current-input' is the string entered by the user till now" (let* ((filtered-input (my-filter-cd-input current-input)) (directory-path (car filtered-input)) (partial-input (cadr filtered-input)) (directory-choices (mapcar 'file-name-nondirectory (condition-case nil (cl-remove-if-not 'file-directory-p (directory-files (concat directory directory-path) t)) ('file-error (list))))) (selected-name (ido-completing-read "Directory: " directory-choices nil nil partial-input))) (comint-delete-input) (insert (concat "cd " (shell-quote-argument (concat directory-path selected-name "/")))))) (defun my-prompt-for-dir-or-fallback () "If current shell command is `cd' prompt for directory using ido otherwise fallback to normal completion" (interactive) (let* ((user-input (buffer-substring-no-properties (comint-line-beginning-position) (point-max)))) (if (and (>= (length user-input) 3) (string= (substring user-input 0 3) "cd ")) (progn (setq my-dir-selected nil) (while (not my-dir-selected) (my-complete-directory-name default-directory (buffer-substring-no-properties (+ (comint-line-beginning-position) 3) (point-max)))) (comint-send-input)) (call-interactively 'completion-at-point)))) (define-key shell-mode-map (kbd "<tab>") 'my-prompt-for-dir-or-fallback) (add-hook 'ido-setup-hook 'ido-my-keys) (defun ido-my-keys () "Add my keybindings for ido." (define-key ido-completion-map (kbd "<C-return>") (lambda () (interactive) (setq my-dir-selected t) (ido-exit-minibuffer)))) 

Hitting <tab> in shell will prompt for directories available using ido if the currently entered command is cd, otherwise it will fallback to default completion, to exit press C-RET

Sign up to request clarification or add additional context in comments.

14 Comments

Wow, this is absolutely fantastic ! Thanks, and congrats, I will study your elisp carefully. One thing though: do you think it would be possible that after selecting the first directory with RET, ido keeps asking for the following sub-directory ? We could finish the selection with C-j. It would be easier to go more than one dir deep.
You should keep working on it and make it easily available (ELPA package ?), it's definitely worth it, it is so handy.
Hi I am glad it helped you. About making ido ask about following subdirectory immediately after user selects a directory, it seems to be tricky to implement, I try and let you know I manage to achieve it
Hi @Ehvince, sorry for the delay, I have made some changes, now ido will keep asking for subdirectory after selecting directory with RET, to finish selection press C-RET.
This is astonishing. Congrats. You are pardonned for the delay :)
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.