/emacs-sedition

Missing those sweet sed-style ex commands from your favourite ex-editor? Weep no more.

Primary LanguageEmacs LispGNU General Public License v3.0GPL-3.0

Vi-like Sed Commands with sedition.el

sedition.el provides Vi-like Sed Commands (without Evil)

The thing I miss most from Vim isn’t so much Vim itself as Sed. I like being able to issue sed-like regex substitution commands from the ex command line.

Package Header

;;; sedition.el --- Vi-like Sed Commands without Evil

And some package metadata:

;; Author Olivia Lucca Fraser <lucca.fraser@gmail.com>
;; Version: 0.1
;; Keywords: convenience, matching, tools, unix
;; URL: https://github.com/oblivia-simplex/emacs-sedition

We’ll keep the commentary here brief, and just refer the reader to this README.org.

;;; Commentary:

;; This package lets you issue sed commands to act on the currently active
;; region or buffer, more or less like you can in Vi with ex commands like
;; :%s/foo/bar/g
;; :'<,'>s/foo/bar/g
;; and so on.  The work behind the scenes is all being done by sed, so you'll
;; need to have the sed tool installed.  (On Unixy systems this won't be an
;; issue, generally speaking.)
;;
;; See the README.org at https://github.com/oblivia-simplex/emacs-sedition/
;; for more details.

And that’s just about it for the package header. We just need to indicate where the code begins, which will be right… here:

;;; Code:

Sed command History

Let’s create a variable to store the command history for our sed commands.

(defvar sedition--command-history nil
  "History list for `sed/pipe-region'.")

Issuing Sed-like Commands on Buffers and Regions

We could use shell-command-region for this, but that would be a mistake. It’s not malicious shell injection we’re worried about here – if the user is running emacs, they can presumably be trusted with arbitrary shell commands – but inadvertant shell commands. A command to substitute "->" with "==>", for example, could inadvertantly invoke output redirection in the shell, thanks to the “>” character.

So we’ll take a couple precautions here: we’ll use call-process-region instead, passing the sed command argument explicitly, and we’ll check the exit code of the command before we do anything to the buffer or region selected.

    ;;;###autoload
    (defun sedition ()
      "Pipe the contents of the region to the shell command sed.
When called interactively, `REGION-BEGINNING' and `REGION-END'
refer to the beginning and end points of the active region.
If no region is active, they are set to the beginning and end of
the current buffer."
      (interactive)
      (let* ((in-region (use-region-p))
	     (start (if in-region (region-beginning) (point-min)))
	     (end (if in-region (region-end) (point-max)))
	     (prompt (if in-region
    			 (format "sed (region %d-%d): " start end)
    		       "sed buffer: "))
    	     (command (read-string prompt nil 'sedition--command-history))
    	 ;; generate-new-buffer will create a unique name with this prefix
    	 ;; the initial space hides it from list-buffers and buffer-menu
    	 ;; commands
    	 (tmpbuffer (generate-new-buffer " *sed*"))
    	 (exitcode (call-process-region start
    					end
    					"sed"
    					nil ;; don't delete anything yet
    					tmpbuffer
    					t
    					command)))
        ;; We only want to perform any action on the region/buffer
        ;; if the command succeeded, so check the exit code here.
        (cond ((zerop exitcode)
    	   (delete-region start end)
    	   (goto-char start)
    	   (insert-buffer-substring tmpbuffer))
    	  ((= exitcode 127)
    	   (message "sed: command not found"))
    	  ((= exitcode 1)
    	   (message "sed: error in command '%s'" command))
    	  ((stringp exitcode)
    	   (message "sed: %s" exitcode))
    	  (t (message "sed: exited with code %d" exitcode)))
        (kill-buffer tmpbuffer)))

Wrapping Everything Up

Okay, our sed tool is ready! Go ahead and bind a key to it. I have it set to C-c s, myself, and to / in Meow mode.

(global-set-key (kbd "C-c s") 'sedition-dwim)

This is the whole module, by the way. Just org-babel-tangle this file, and you’ll have an up-to-date sedition.el.

(provide 'sedition)
;;; sedition.el ends here