run a shell command from a specific directory in emacs - emacs

How do I execute a shell command (eg, git gui) from a specific directory? I'd prefer a cross-platform approach that doesn't depend shell operators like && or ; because I need this to run on Windows and unix. (For example, calling cd /path/to/dir && git gui won't work on Windows because && is not valid.)
I tried:
(async-shell-command (concat "cd \""
(replace-regexp-in-string
"/" "\\\\" (file-name-directory (buffer-file-name))) "\" ; git gui"))
That fails because for whatever reason the shell thinks ; git gui is part of the path, and it reports: The system cannot find the path specified.
So, I'd rather not deal with shell quirks and I'm hoping there's an elisp function that sets the directory for shell-command or async-shell-command. I tried:
(shell-process-pushd (file-name-directory (buffer-file-name)))
(shell-command "git gui")
(shell-process-popd nil)
That had no effect: git gui always opens in my home directory. (Also, shell-process-pushd/popd are not documented.)
This also doesn't work:
(start-process "gitgui" nil "git" "gui")
Neither does this:
(let '(bufdir (file-name-directory (buffer-file-name)))
(with-temp-buffer
(print bufdir)
(cd bufdir)
(shell-command "git gui")))

Are you sure you're using the trailing slash to indicate directory name in emacs-lisp?
(let ((default-directory "~/.emacs.d"))
(shell-command-to-string "echo $PWD"))
;;; ⇒ "/Users/me"
(let ((default-directory "~/.emacs.d/"))
(shell-command-to-string "echo $PWD"))
;;; ⇒ "/Users/me/.emacs.d"

(shell-command) does use the the buffer's default-directory, which means there is no reason to cd to the buffer's directory.
The problem in my case was that I was mistakenly running git gui on a buffer that didn't have an associated .git/ directory.

Try this:
(let ((default-directory "~/.emacs.d/")) (shell-command "ls"))

Related

emacs cd for all splits

In emacs you can do M-x cd to change the default directory.
I usually have 5 splits/windows, so the cd I do in the first split does't affect the others. What If I want the cd to affect all my splits/buffers.
Is there an alternative command I can use?
There's nothing built-in but it's not too hard to write the function by hand:
(defun cd-all-windows (dir)
(interactive "Ddirectory: ")
(dolist (window (window-list))
(with-current-buffer (window-buffer window)
(cd dir))))
Put that in your .emacs and you should be able to run M-x cd-all-windows to get the desired effect.

emacs shell-mode follows its default-directory to current-file

When I enter cd c:/dir/to/path in shell mode, shell mode follows its default-directory to c:/dir/to/path and that's good. But while visiting a certain file(c:/another/dir/file.ext), how can I let the existing shell directory to that one without entering cd c:/antoher/dir/file.ext ?
Is there any pre-exisiting function for that in emacs? searched quite a lot but not found unfortunately.
I'm using Emacs 24.2.1 in Win7.
EDIT:
I've written a not-good-looking function like followings. Any proposal/advice will be appreciated(I'm a novice in elisp).
(With prefixed interactive call, it will show shell buffer with current directory. I'm repeatedly thinking there might be something already invented one better than this).
(defun my-shell-with-current-directory (&optional arg)
(interactive "P")
(let* ((sp (get-process "shell"))
(spbuf (and sp (process-buffer sp)))
(dir (if buffer-file-name (file-name-directory buffer-file-name) default-directory)))
(if (and arg sp spbuf dir)
(progn
(comint-simple-send sp (concat "cd /d " dir))
(display-buffer spbuf)
(save-excursion
(set-buffer spbuf)
(cd dir)
)
)
(progn
(shell)
(comint-simple-send sp "setlocal enableextensions")
)
)
)
)
You may set default-directory using setq at any time.
You may add that code as a hook onto find-file-hooks.
Also you may advice find-file command with the setting wished.
So far just my thoughts...

Update multi-term buffer name based on PWD

If I use konsole or other terminal, the terminal tag name can change based on PWD. But in multi-term, the buffer name is *terminal<number>*. This is not very nice. Because when I switch between them, the name is not very informative. So I want to rename it based on PWD.
I find that the Enter key is bind to term-send-raw, so I write a function
(defadvice term-send-raw (around rename-term-name activate)
(progn
(rename-buffer
(concat "⇒ "
(shell-command-to-string "pwd | xargs basename | tr -d '\n'")
(format-time-string " [%M ∞ %S]")))
ad-do-it))
But the problem is pwd command return the PWD of the terminal buffer, while it is not the PWD of the SHELL in that terminal.
The PWD of the terminal buffer is set by defcustom multi-term-default-dir. And it does not change when the PWD change in the SHELL.
(defcustom multi-term-default-dir "~/"
"The default directory for terms if current directory doesn't exist."
:type 'string
:group 'multi-term)
How can I get the PWD of the SHELL in the terminal?
Regards.
AFAIK there is no easy way to retrieve information from a running process.
But if you want to get the current directory you could:
ask the shell to print it
parse and trace the command-line for functions like cd, pushd, popd…
poll /proc/PID/cwd
The first method is described in the header of term.el (M-xfind-libraryRETtermRET).
And now, thank you for your question, you gave me the opportunity to do this:
(defadvice term-send-input (after update-current-directory)
(let* ((pid (process-id (get-buffer-process (current-buffer))))
(cwd (file-truename (format "/proc/%d/cwd" pid))))
(cd cwd)))
(ad-activate 'term-send-input)
It's a naive implementation of the third method and it doesn't work if the user uses su or ssh. However, I don't know if it's possible withouth using the first or the second method.
In your case, you can just replace the cd command with whatever you want.
Building off of Daimrod's answer for polling /proc/PID/cwd, I found a way get around the problem that Reed pointed out where the advice doesn't pick up the updated CWD immediately and you have to hit Enter twice.
If you move the CWD update code to its own function and use run-at-time to call it from the advice at a later time, it will pick up the updated CWD correctly. Unfortunately I don't know enough about Emacs' scheduling to explain why this works (any enlightenment would be appreciated).
Here's my code based on Daimrod's. Note I advised term-send-input for line-mode and term-send-return for char-mode. I tested this using multi-term on Emacs 24.3.1:
(defadvice term-send-input (after update-current-directory)
(run-at-time "0.1 sec" nil 'term-update-dir)
)
(ad-activate 'term-send-input)
(defadvice term-send-return (after update-current-directory)
(run-at-time "0.1 sec" nil 'term-update-dir)
)
(ad-activate 'term-send-return)
(defun term-update-dir ()
(let* ((pid (process-id (get-buffer-process (current-buffer))))
(cwd (file-truename (format "/proc/%d/cwd" pid))))
(unless (equal (file-name-as-directory cwd) default-directory)
(message (concat "Switching dir to " cwd))
(cd cwd)))
)
Most terminals get their window name from the command echo -en. In zsh you can put this in your ~/.zshenv
precmd() { echo -en "\e]0;`basename ${PWD}`\a" }
and that will get the basename of your PWD environment variable. Ideally multi-term would do something similar and put it in multi-term-buffer-name, which is the variable which holds its buffer name.
Yes, this is not a complete solution. I'm hoping for one too!
Try this:
(defun open-or-jump-to-multi-term ()
(interactive)
(if (string-prefix-p "*terminal<" (buffer-name))
(delete-window)
(progn
(setq bufname (concat "*terminal<" (directory-file-name (file-name-directory (buffer-file-name))) ">"))
(if (get-buffer-process bufname)
(switch-to-buffer-other-window bufname)
(progn
(split-window-right)
(other-window 1)
(multi-term)
(rename-buffer bufname)
)
)))
)
(global-set-key (kbd "C-`") 'open-or-jump-to-multi-term)

How to define an alternate command for emacs eshell

I'm trying to start using eshell in place of bash within emacs, but I rely heavily on bash functions that I have written over the years. I'd like to configure eshell to invoke bash whenever a "command not found" condition occurs, in case the command in question is implemented as a bash function.
There is a variable tantalizingly named eshell-alternate-command-hook that sounds like it is made to order, but my lack of elisp skill is interfering with my success I think.
This is my best effort:
(add-hook 'eshell-alternate-command-hook 'invoke-bash t t)
(defun invoke-bash (command args)
(throw 'eshell-replace-command
(list "bash -c" command args)))
But when I test it, it doesn't work:
c:/temp $ lsd
Wrong number of arguments: (lambda (command args) (throw (quote eshell-replace-command) (list "bash -c" command args))), 1
c:/temp $
This is what I eventually came up with:
(defun invoke-bash (command)
(progn
(setq invoke-bash-cmd (concat "bash -c \"" command " " (mapconcat 'identity eshell-last-arguments " ") "\""))
(message invoke-bash-cmd)
(throw 'eshell-replace-command
(eshell-parse-command invoke-bash-cmd))))
I'm not eshell guru, but in the place where this hook is used, I see that it receives only one argument - command, that you trying to execute, so your code could look like
(add-hook 'eshell-alternate-command-hook 'invoke-bash)
(defun invoke-bash (command)
(throw 'eshell-replace-command
(list "bash -c" command)))
but it doesn't work, because you need to return elisp function, not name of command (according to documentation). If you want to run bash, then you need to return string with full path to it, but I hadn't found how to pass additional arguments to bash. Maybe you can find more in corresponding section on Emacs Wiki?

How to set the default-directory of compilation in Emacs?

I am coding OCaml under Emacs, I have one makefile in the working folder, and several sub-folders containing .ml files. If I launch M-x compile and make works fine on a buffer of makefile, but does not work on a buffer of a .ml file, it gives me an error:
-*- mode: compilation; default-directory: "..." -*-
Compilation started at Fri Jan 27 18:51:35
make -k
make: *** No targets specified and no makefile found. Stop.
Compilation exited abnormally with code 2 at Fri Jan 27 18:51:35
It is understandable because the default-directory is sub-folder which does not contain makefile. Does anyone know how to set the folder of makefile always as the default-directory of compilation?
You can call make with the right arguments:
make -C .. -k
where .. is the path to your Makefile
You can control this from within emacs by writing a function that (temporarily) sets default-directory and calls compile.
(defun compile-in-parent-directory ()
(interactive)
(let ((default-directory
(if (string= (file-name-extension buffer-file-name) "ml")
(concat default-directory "..")
default-directory))))
(call-interactively #'compile))
When using compile-in-parent-directory all ml files will be compiled in the parent directory of where they are. Of course if they are nested deeper you can change the logic to reflect that. In fact there is a version on the EmacsWiki which searches parent directories until it finds a makefile. I found this after I wrote this answer, otherwise I would have just pointed you there. sigh. The good thing about my method is that it's not specific to make so that you can use the same "trick" for other commands.
You can also change the call to compile to be non-interactive if you know exactly what you want the command to be. This would work particularly well if it's bound to a key in the appropriate mode hook.
i use a script like this which allows me to run make from any sub-directory (assuming you are in a posix-like environment). just put this script in your PATH as something like "sub_make.sh" and invoke it the same way you would invoke make:
#!/bin/bash
# search for project base
INIT_DIR=`pwd`
while [ "$PWD" != "/" ] ; do
if [ -e "makefile" ] ; then
break
fi
cd ..
done
if [ ! -e "makefile" ] ; then
echo "Couldn't find 'makefile'!"
exit 1
fi
# indicate where we are now
echo "cd "`pwd`
echo make "$#"
# now run make for real
exec make "$#"
That's what I have in some of my configs :)
(defun* get-closest-pathname (&optional (max-level 3) (file "Makefile"))
(let* ((root (expand-file-name "/"))
(level 0)
(dir (loop
for d = default-directory then (expand-file-name ".." d)
do (setq level (+ level 1))
if (file-exists-p (expand-file-name file d))
return d
if (> level max-level)
return nil
if (equal d root)
return nil)))
(if dir
(expand-file-name file dir)
nil)))
(add-hook 'c-mode-hook
(lambda ()
(unless (file-exists-p "Makefile")
(set (make-local-variable 'compile-command)
(let ((file (file-name-nondirectory buffer-file-name))
(mkfile (get-closest-pathname)))
(if mkfile
(progn (format "cd %s; make -f %s"
(file-name-directory mkfile) mkfile))
(format "%s -c -o %s.o %s %s %s"
(or (getenv "CC") "gcc")
(file-name-sans-extension file)
(or (getenv "CPPFLAGS") "-DDEBUG=9")
(or (getenv "CFLAGS") "-ansi -pedantic -Wall -g")
file)))))))
Matthias Puech has a solution involving a .dir-local file in the project root directory:
((nil . ((eval . (setq default-directory
(locate-dominating-file buffer-file-name
".dir-locals.el")
)))))
It is presumably also possible to use something like: (shell-command-to-string "git rev-parse --show-toplevel") as that innermost bit.
Not a completely general solution w.r.t makefile location, but adding this here for posterity because it solved my particular use-case.
If you use projectile and your makefile is always in the root of your project directory, then you can use projectile-compile-project.
(In my case, I wanted to lint my project, so calling (compile "flake8") would only flake from the current buffer's directory downwards, whereas what I really wanted was linting of the entire project. projectile-compile-project achieves this.)