reddit / emacs
Creating an elisp function that executes code
Purple_Worry_8600 · 2022-11-29 18:55
I'm a Vim user who is trying to migrate to emacs. On Vim, I've been using the following function to execute code:
```
function! ExecuteOnTerminal(type) range
if (&ft=='bash' || &ft=='sh')
if a:type == "V"
:vert botright '<,'> %terminal bash
else
:vert botright %terminal bash
endif
elseif (&ft=='python')
if a:type == "V"
:vert botright '<,'> %terminal python3
else
:vert botright %terminal python3
endif
elseif (&ft=='awk')
if a:type == "V"
'<,'>w! /tmp/file.awk
else
:w! /tmp/file.awk
endif
:vert botright terminal awk -f /tmp/file.awk /home/user/sampleFile
endif
endfunction
vnoremap <silent> <F10> :call ExecuteOnTerminal("V")<CR>
nnoremap <silent> <F10> :call ExecuteOnTerminal("I")<CR>
```
Basically, it detects if I'm selecting lines or not. If I press F10, it'll execute the selected lines or the entire file depending on the type of the file. I'd like to create a similar function on emacs. I know that for that I'd need:
1. A function that detects the language of the file like `&ft` does on vimscript.
2. A way of detecting if I'm selecting lines or not on the current file and getting the content of it.
3. Something like the `terminal` command from vim, it executes the code on a split window.
I'd like to create my own personalized function because I end up having more control of what's happening, like the last `elseif` that I'm executing `awk` code with the data of a hardcoded file. This way I can handle multiple types of files with a single shortcut... But I'm having trouble finding equivalent elisp functions for the three points that I've listed here. Can anyone help me on that? Or give me some advice for executing code on emacs (not org files, just plain scripts .py, .sh, .bash, .js and so on)?
life-exp · 2022-11-29 19:28
You checked out evil mode or doom emacs? I feel that gets you part of the way there. You have to adjust your workflow slightly.
Purple_Worry_8600 · 2022-11-29 19:53
I'm using Spacemacs with the evil mode, but the commands that I'm using on that vimscript function don't exist in evil mode too... The only one that works is `!w` or `'<,'>w!` for writing the content of the file in a new file.
ieure · 2022-11-29 20:25
> A function that detects the language of the file like &ft does on vimscript.
The `major-mode` variable has this information.
> A way of detecting if I'm selecting lines or not on the current file and getting the content of it.
`(region-active-p)` tells you if there's a selected region, `(buffer-substring-no-properties (region-beginning) (region-end))` will return the selected part of the buffer.
> Something like the terminal command from vim, it executes the code on a split window.
There's probably a better way, but this ought to do what you want:
(with-current-buffer (pop-to-buffer "*output*")
(erase-buffer)
(insert (shell-command-to-string "whatever you want to run")))
ieure · 2022-11-29 20:27
Also noting, some language modes (like Python and SQL) have built-in support for this sort of thing. You can run the interpreter / client in a split window, and `C-c C-r` will send the region to it. You don't specify which language you're working with, but I'd check to see if it has something like that before rolling your own.
Imaltont · 2022-11-29 22:15
A lot of modes, including some of those you mention, already come with the things you ask for. Look into the various "comint" modes for the relevant languages [mastering emacs has a good section on them].(https://www.masteringemacs.org/article/comint-writing-command-interpreter) Things to look for in your modes would be functions like send/eval-defun/region/buffer and similar. The lisp modes has this to a great degree, python comes with this, the rest I don't know, but assume they either are in the default modes or are easily accessible in extensions.
If you have never done something like it before, just open up a python file, M-x run-python, and C-M-x inside some expression. It is really fun to work with.
deaddyfreddy · 2022-11-29 22:18
> Or give me some advice for executing code on emacs (not org files, just plain scripts .py, .sh, .bash, .js and so on)?
take a look at Menu, at least there are options for shell and python
Purple_Worry_8600 · 2022-12-05 02:19
Thanks, I've tried to create the function and I've arrived in the following:
```
(global-set-key (kbd "C-c e") 'execute-code)
(defun execute-code ()
"Function that executes code"
(interactive)
(write-region (point-min) (point-max) "/tmp/file")
(case major-mode
('sh-mode (setq code (concat "bash /tmp/file")))
('python-mode (setq code (concat "python3 /tmp/file")))
('js2-mode (setq code (concat "node /tmp/file")))
(t (print major-mode)))
(print code)
(with-current-buffer (pop-to-buffer "*output*")
(erase-buffer)
(insert (shell-command-to-string code))
)
)
```
I'm not sure why but I don't have the function `(region-active-p)` in Spacemacs... So I didn't manage to check if the text is selected or not, the solution above executes the whole file.
But the main problem that I have with this implementation is that it waits for the whole code to execute to start displaying the results. For example, let's say I have the following bash code:
```bash
echo "test"
sleep 2
echo "test"
sleep 2
echo "test"
sleep 2
echo "test"
sleep 2
echo "test"
sleep 2
echo "test"
```
In this case, it doesn't work like the `terminal` Vim command that displays results on the run. I have to wait 10 seconds until the code finishes its execution to see its output. Do you know if there's any workaround for this issue with an alternative command (not using `shell-command-to-string`)?
ieure · 2022-12-05 03:07
> I'm not sure why but I don't have the function (region-active-p) in Spacemacs... So I didn't manage to check if the text is selected or not, the solution above executes the whole file.
>
Are you sure? That's a very fundamental function and if you truly didn't have it, lots of elisp wouldn't work.
> But the main problem that I have with this implementation is that it waits for the whole code to execute to start displaying the results. For example, let's say I have the following bash code:
>
> bash echo "test" sleep 2 echo "test" sleep 2 echo "test" sleep 2 echo "test" sleep 2 echo "test" sleep 2 echo "test"
>
> In this case, it doesn't work like the terminal Vim command that displays results on the run. I have to wait 10 seconds until the code finishes its execution to see its output. Do you know if there's any workaround for this issue with an alternative command (not using shell-command-to-string)?
If you want live output, you need to:
- Create a process. Here's the [relevant part of the Emacs Lisp manual](https://www.gnu.org/software/emacs/manual/html_node/elisp/Processes.html).
- Create a buffer to put its output in.
- Write a [filter function](https://www.gnu.org/software/emacs/manual/html_node/elisp/Output-from-Processes.html) which accepts the stdout of the process and writes it into the buffer.
But, what you're describing is *very* similar to what `compilation-mode` already does — you're just providing the compilation command in a different way than prompting for it interactively in the minibuffer. So it's worth seeing if you can derive your mode from `compilation-mode`, and have the function that pulls the command from the current buffer as the thing that kicks it off.