elisp - Get path to file relative to script - emacs

Say I am writing an emacs lisp function that interfaces with a file located relative to the file in which the function is defined.
- bin/executable
- foo.el
foo.el:
(defun foo ()
(shell-command-to-string
(format "echo '%s' | ./bin/executable"
(buffer-substring-no-properties
(point-min)
(point-max)))))
If I run this from foo.el then it works great. If I invoke the function while editing any other file it doesn't work because the path ain't right.
How can I reliably reference ./bin/executable from within foo.el no matter where the function is invoked?

Use load-file-name variable.
(defconst directory-of-foo (file-name-directory load-file-name))
(defun foo ()
(shell-command-to-string
(format "echo '%s' | %s"
(buffer-substring-no-properties
(point-min)
(point-max))
(expand-file-name "./bin/executable" directory-of-foo))))

You can use a combination of load-file-name and default-directory. If you only check the former, the file will work if you explicitly load it, but it will not work if you eval it in a buffer.
For example:
(defvar my-directory (if load-file-name
;; File is being loaded.
(file-name-directory load-file-name)
;; File is being evaluated using, for example, `eval-buffer'.
default-directory))
In addition, it might be a good idea to convert the path to an absolute path using expand-file-name.

Related

Spawing ctags from Emacs with start-process

I have a function that is supposed to create a ctags file and load it into Emacs asynchronously. ctags can take a while to run if invoked on really big files, and I don't want my function to do any blocking, thus I use start-process. This is what it all looks like:
(defun temp-tags-file-for-file (file)
"Generate a temporary tags file for FILE.
Add the file to tags list and return the name of the file."
(if (not (boundp 'ctags-command))
(setq ctags-command "/usr/bin/ctags"))
(let* ((temp-file (make-temp-file "EMACS_TAGS"))
(proc (start-process "temp-tags-proc" nil ctags-command
"-f" temp-file file)))
(set-process-sentinel proc
(lambda (proc msg)
(when (eq (process-status proc) 'exit)
(if (boundp 'temp-tags-file)
(progn
(add-to-list 'tags-table-list
temp-tags-file)
(makunbound 'temp-tags-file))))))
(setq temp-tags-file temp-file)
temp-file))
For some reason, the tags file is always blank. Calling ctags with the exact same parameters from the shell generates a non-blank, working tags file. How do I get ctags to print its output properly?
If ctags wants the shell, just give it to the shell:
(start-process "temp-tags-proc" nil shell-file-name shell-command-switch
(format "/usr/bin/ctags ~/Dropbox/source/c/*.c -f %s"
(make-temp-file "EMACS_TAGS")))

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)

Emacs: Define a function which loads the file where the function itself is defined

I'm refactoring a bit in my Emacs set up and have come to the conclusion that I want to use a different init file than the default one. So basically, in my ~/.emacs file, I have this:
(load "/some/directory/init.el")
Up until now, that's been working just fine. However, now I want to redefine an old command that I've used for ages, which opens my init file:
(defun conf ()
"Open a buffer with the user init file."
(interactive)
(find-file user-init-file))
As you can see, this will open ~/.emacs no matter what I do. I want it to open /some/directory/init.el, or wherever the conf command itself is defined.
How would I do that?
You can use find-function for this:
(defun conf ()
"Open a buffer with the user init file."
(interactive)
(find-function this-command))
You can also use a sneakier way:
(defun conf () "blabla" (interactive) (find-file #$))
Because #$ works a bit like _FILE_ in C: it's replaced by the filename when the file is read.
This works for me.
;;; mymodule.el --- does my thing
(defvar mymodule--load-path nil "load path of the module")
...
(defun mymodule-load-datafile ()
"load a data file from the directory in which the .el resides"
(let* ((dir-name (concat
(file-name-directory mymodule--load-path)))
(data-file-name (concat dir-name "datafile.csv")))
(if (file-exists-p data-file-name)
... )))
;; remember load time path
(setq mymodule--load-path load-file-name)
(provide 'mymodule)

Using Emacs, is it possible to pin the compilation command to a specific buffer/directory?

Right now I am using the following to compile, when I'm in for example main.cpp
C-x b Makefile RET M-x compile RET RET
I actually have M-x compile as a keyboard shortcut, but the problem is I would really like not having to go through all that trouble to simply run my Makefile.
I need to visit Makefile to make sure the compile command is executed using the same directory. Is there any way to pin the directory so I can simply go M-x compile RET RET?
Best regards
Use recompile instead. C-u M-x recompile will let you edit the compile command first. Either way the compile will work out of the directory the last compile was done in.
See my answer here
Directory local variables provide an easy way to trigger the compile from a parent directory of any source file in a subdirectory.
I run emacs primarily on windows.
When I have a makefile that is in a parent directory of a C module, I use this as the compile command:
cd .. && nmake <arguments here>
for example:
cd .. && nmake CONFIG=Debug PLATFORM=x64 target
Beyond that, I find that specifying the make command line that I want to run for various modules is sort of a pain. I wanted a way to attach the default compile command to the buffer being edited. So I wrote a little elisp to handle that job. I figured to insert into the header comments of each buffer a line that would stipulate my preferred compile command, like this:
compile: cd .. && nmake CONFIG=Debug PLATFORM=x64 target
And then have a piece of elisp run, before I invoke M-x compile that grabs the line and proposes it as the compile command I would like to run.
This defun pulls a line out of the header comments:
(defun cheeso-c-get-value-from-comments (marker-string line-limit)
"gets a string from the header comments in the current buffer.
This is used to extract the compile command from the comments. It
could be used for other purposes too.
It looks for \"marker-string:\" and returns the string that
follows it, or returns nil if that string is not found.
eg, when marker-string is \"compile\", and the following
string is found at the top of the buffer:
compile: cl.exe /I uthash
...then this command will return the string
\"cl.exe /I uthash\"
It's ok to have whitespace between the marker and the following
colon.
"
(let (start search-limit found)
;; determine what lines to look in
(save-excursion
(save-restriction
(widen)
(cond ((> line-limit 0)
(goto-char (setq start (point-min)))
(forward-line line-limit)
(setq search-limit (point)))
((< line-limit 0)
(goto-char (setq search-limit (point-max)))
(forward-line line-limit)
(setq start (point)))
(t ;0 => no limit (use with care!)
(setq start (point-min))
(setq search-limit (point-max))))))
;; look in those lines
(save-excursion
(save-restriction
(widen)
(let ((re-string
(concat "\\b" marker-string "[ \t]*:[ \t]*\\(.+\\)$")))
(if (and start
(< (goto-char start) search-limit)
(re-search-forward re-string search-limit 'move))
(buffer-substring-no-properties
(match-beginning 1)
(match-end 1))))))))
Ok, now I need something to invoke that before I invoke compile.
(defun cheeso-invoke-compile-interactively ()
"fn to wrap the `compile' function. This simply
checks to see if `compile-command' has been previously set, and
if not, invokes `cheeso-guess-compile-command' to set the value.
Then it invokes the `compile' function, interactively."
(interactive)
(cond
((not (boundp 'cheeso-local-compile-command-has-been-set))
(cheeso-guess-compile-command)
(set (make-local-variable 'cheeso-local-compile-command-has-been-set) t)))
;; local compile command has now been set
(call-interactively 'compile))
Then of course, the defun that guesses the compile command:
(defun cheeso-guess-compile-command ()
"set `compile-command' intelligently depending on the
current buffer, or the contents of the current directory."
(interactive)
(set (make-local-variable 'compile-command)
(cond
(buffer-file-name
(let ((filename (file-name-nondirectory buffer-file-name)))
(cond
;; editing a C-language source file - check for an
;; explicitly-specified command
((string-equal (substring buffer-file-name -2) ".c")
(let ((explicit-compile-command
(cheeso-c-get-value-from-comments "compile" 34)))
(or explicit-compile-command
(concat "nmake " ;; assume a makefile exists
(file-name-sans-extension filename)
".exe"))))
;; editing a makefile - just run nmake
((string-equal (substring buffer-file-name -8) "makefile")
"nmake ")
;; something else - do a typical .exe build
(t
(concat "nmake "
(file-name-sans-extension filename)
".exe")))))
(t
;; punt
"nmake "))))
The final bit is to bind C-x C-e , normally bound to compile, to the wrapper defun:
(global-set-key "\C-x\C-e" 'cheeso-invoke-compile-interactively)
Now, when I do C-x C-e in the buffer, it searches for the compile command, and proposes to me the command that it finds. I can edit the proposed compile command, then press ENTER and run it.

How can I access the path to the current directory in an emacs directory variable file?

According to the Emacs documentation, Directory Variables apply to all files below a directory that contains an .dir-locals.el file.
How can I, in that file, set a variable to the full path that contains the file? For example:
((nil . ((indent-tabs-mode . t)
(my-project-path **THIS_DIRECTORY**))))
I asked myself the same question and found no solution on the web, so I think this answer may help. Actually, it turns out we can reuse dir-locals-find-file to get the directory containing the .dir-locals.el file. So here's what I found for, e.g, setting up an aspell personal dictionary dedicated to a whole directory:
((nil . ((eval . (setq ispell-personal-dictionary
(expand-file-name
".aspell_words"
(file-name-directory
(let ((d (dir-locals-find-file ".")))
(if (stringp d) d (car d))))))))))
Also, it seems entries are evaluated in the order they are specified, so the following code should work:
((nil . ((eval . (set (make-local-variable 'my-project-path)
(file-name-directory
(let ((d (dir-locals-find-file ".")))
(if (stringp d) d (car d))))))
(eval . (message "Project directory set to `%s'." my-project-path)))))
Emacs will complain about unsafe local variables (due to the eval construct), yet one can still permanently mark it safe.
Update: Since Emacs ≥ 26.3 (and maybe older versions as well), it appears that one needs to use (dir-locals-find-file "./") instead of (dir-locals-find-file ".").
I think (file-name-directory (or load-file-name buffer-file-name)) should give you the directory path.
See Link
Edit: Except it won't, because any eval expressions are evaluated in the context of the buffer whose variables are being hacked.
In my case, I wanted to locate a file that was relative to my current working directory for my repository, and the .dir-locals.el file was checked into the root, so a file "local" to the .dir-locals.el was also a file "local" to the project root.
pajato0's answer above worked for some cases, but it was also breaking other modes (like magit). I got around the issue by using the projectile package's projectile-project-root function to find my the base path for me:
((nil . ((eval . (setq cmake-ide-build-dir
(concat (projectile-project-root) "/build-make"))
))))
I've found the locate-dominating-file procedure, which comes out-of-the-box with Emacs, useful to retreive the current directory of a known file. The example below sets the guix-directory variable to the topmost directory of the project containing a .dir-locals.el file.
((nil . ((eval . (setq guix-directory
(locate-dominating-file default-directory
".dir-locals.el"))))))
It's not a safe .dir-locals.el setting, due to relying on eval, but it gets the job done.
In case it still matters, to the OP or some other, I would suggest you create a function to generate the .dir-locals.el file. Then one could write something like:
(let ((path default-directory)
file)
(setq file (format "%s/.dir-locals.el" path))
(with-temp-buffer
(insert (format "((nil . ((indent-tabs-mode . t)
(my-project-path \"%s\"))))" path))
(when (file-writable-p file)
(write-region (point-min)
(point-max)
file))))
to be executed within the project home directory.
hack-local-variables is the main function for processing all local variables, and it calls hack-dir-local-variables to deal with the .dir-locals.el file (or a dir local class variable, if you're not using that file).
The code for establishing the directory is not isolated in its own function, so we'll have to copy it out into a new function (this from GNU Emacs 24.0.95.1):
(defun my-dir-locals-dir ()
"Return the directory local variables directory.
Code taken from `hack-dir-local-variables'."
(let ((variables-file (dir-locals-find-file (or (buffer-file-name) default-directory)))
(dir-name nil))
(cond
((stringp variables-file)
(setq dir-name (file-name-directory variables-file)))
((consp variables-file)
(setq dir-name (nth 0 variables-file))))
dir-name))
If you are working on *nix, you might get the work directory by the following elisp code,
(defun get-working-directory ()
(getenv "PWD))
Btw, I have to mentioned that, (shell-command "pwd") will result in the directory where file (which is corresponding to the buffer you are currently editing).