emacs shell: change directory with ido
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 ?
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.
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
上一篇: 在Entity Framework中将复杂类型映射到结果形式SqlQuery
下一篇: emacs shell:用ido更改目录