4

I am transitioning to Emacs with evil-mode after years of Vim. For work reasons I have to use a GUI statistics program (Stata) frequently. This program does not integrate well with external editors. ess-mode doesn't support the GUI version of Stata but only a very limited terminal version. I prefer a simple solution that I can maintain myself.

To make it work with Vim, I wrote a shell script using xdotool that takes as argument a filename, opens the GUI window, pastes "do filename" in the command window, sends return and then goes back to the original editor window. It's not a sophisticated solution but works well in practice.

In my .vimrc I had some code that would write the currently selected region or the current line to a temporary file in /tmp/randomfoldername/consecutivenumber.do, then call the shell script with that file as the argument. Upon leaving vim, the folder would be deleted.

I am looking to replicate this functionality in Emacs, but don't know how or where to start. I essentially want to mimick what the ess-mode functions ess-eval-region-or-line-and-step and ess-eval-buffer are doing, simply by calling the shell script with the name of a temporary file or the name of the current buffer.

If it helps, this is the vimscript code:


" Run current buffer
fun! RunIt()
  w
  !sh "~/dotfiles/rundo.sh" "%:p"
endfun

" Run selection
fun! RunDoLines()
  let selectedLines = getbufline('%', line("'<"), line("'>"))
 if col("'>") < strlen(getline(line("'>")))
  let selectedLines[-1] = strpart(selectedLines[-1], 0, col("'>"))
  endif
 if col("'<") != 1
  let selectedLines[0] = strpart(selectedLines[0], col("'<")-1)
  endif
 let temp = tempname() . ".do"
 call writefile(selectedLines, temp)
    exec "!sh ~/dotfiles/rundo.sh " . temp
    au VimLeave * silent exe '!del /Q "'.$TEMP.'\*.tmp.do"'
endfun

" Mappings
au FileType stata noremap <F8> :<C-U>call RunIt()<CR><CR>
au FileType stata noremap <F9> :<C-U>call RunDoLines()<CR><CR>
au FileType stata noremap <C-Return> <S-v>:<C-U>call RunDoLines()<CR><CR>
4

If your ~/dotfiles/rundo.sh accepts stdin as input, like many other commands such as grep/wc/bash/python, to run a command with the region as stdin, simply run M-| ~/dotfiles/rundo.sh (M-| runs shell-command-on-region).

If the command doesn't support stdin, the following should do what you described

(defun rundo (beg end)
  "Wrapper of ~/dotfiles/rundo.sh."
  (interactive
   ;; 1. If the region is highlighted
   (if (use-region-p)
       ;; the region
       (list (region-beginning) (region-end))
     ;; the line
     (list (line-beginning-position) (line-end-position))))
  ;; 2. create a temp file
  (let ((tempfile (make-temp-file nil nil ".do")))
    ;; 3. save text to the file
    (write-region beg end tempfile)
    ;; 4. run the command asynchronously 
    ;; (remove '&' to run it synchronously, i.e., blocking Emacs)
    (shell-command (format "~/dotfiles/rundo.sh %s &" tempfile))))
  • Thanks, the function does what I wanted. For some reason running it synchronously blocks emacs and doesn't do anything. With the & it works fine. Is there a way to get rid of the empty *Async shell command* buffer that is opened every time shell-command is executed? – ilprincipe May 12 at 5:08
  • Actually, it only sends the selected reason. When nothing is selected, the tmp file is empty instead of containing the current line. – ilprincipe May 12 at 5:21
  • @ilprincipe The empty buffer tells you the command starts without error and gives no output, its mode-line also displays the status of the command (running or finished). If you don't need these, you can use start-process-shell-command, e.g., (start-process-shell-command "rundo" nil (format "~/dotfiles/rundo.sh %s" tempfile)). You don't need & in start-process-shell-command because start-process-shell-command already runs command asynchronously. – xuchunyang May 12 at 5:27
  • @ilprincipe I don't think so, read the code to see how it chooses the region. When nothing is selected, (use-region-p) returns nil. – xuchunyang May 12 at 5:30
  • Thanks, that gets rid of the buffer. You're right, the tmp file is correct and contains the line. It just doesn't get executed for some reason. I might need to add a newline character. At which point could I best add something like deactivate-mark to deselect the region and (forward-line 1) to move the cursor? – ilprincipe May 12 at 6:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.