5

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>
ilprincipe
  • 153
  • 4

1 Answers1

5

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))))
xuchunyang
  • 14,302
  • 1
  • 18
  • 39
  • 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 '19 at 05: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 '19 at 05: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 '19 at 05: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 '19 at 05: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 '19 at 06:03
  • @ilprincipe If your program needs a final newline and extra newlines are harmless, you can use `(write-region "\n" nil tempfile t)` to ensure at least one newline is appended. – xuchunyang May 12 '19 at 06:25