Skip to main content

Writing your own text objects for objed

When I published my post about moving text around in Emacs buffers, Mickey Petersen from Mastering Emacs told me that he had build something like that for Python code blocks in the past. As that was way back and his code relies on an old version of python-mode it’s not published anywhere. In this post I will show you how to implement this (and more) for objed.

One nice thing about objed is that, once you have some functionality, it works with any defined text object. If you want to move python blocks around like that, all you need to do is to define a python block object.

Thanks to macros, adding an object only needs a few lines of code. Defining it also means you get all the other navigation and editing features of objed for free:

  • Navigate to the next/previous instance of the object

  • Navigate to the first/last instance of the object

  • Toggle between the inner part and the whole object

  • Jump to an object on the screen using avy

  • Search for an instance by it’s first line

  • Apply an operation or any Emacs region command on it

  • …​

Before diving right into the code for the block object, let us start with a simpler object first. In general there should already be some base object for which you want to write a mode specific version. To see which objects are defined by default, see objed-object-map. The following defines a python-mode specific defun object:

(objed-define-object python defun
  :mode python-mode
  :no-skip t
  :get-obj
  (objed-bounds-from-region-cmd #'python-mark-defun)
  ;; the following two keywords are redundant,
  ;; but kept for illustration. They can be left unused,
  ;; to inherit from the default defun definition.
  :try-next
  (beginning-of-defun -1)
  :try-prev
  (beginning-of-defun 1))

As you can see the amount of code is quite manageable. First comes the package name, in this case python and the object name, which is defun obviously. The object should be used in python-mode buffers as defined by the :mode keyword. The object code will be loaded as soon as python is provided.

The :no-skip option is needed because in python a defun could also be a class definition. As a class contains methods you don’t want to skip the current object before the search for the next one starts. For many other objects it makes sense to skip the current object before searching for the next one. That’s all there is to it.

The remaining part gets the position data using :get-obj and defines how next or previous instances can be found with keywords :try-next, :try-prev. After evaluating the above definition the new object definition will be used in python-mode buffers automatically.

Now lets move on to the block object:

(objed-define-object python block
    :mode python-mode
    :commands
    (python-nav-backward-block python-nav-forward-block)
    :no-skip t
    :try-next
    (python-nav-forward-block)
    :try-prev
    (python-nav-backward-block)
    :beg
    (python-nav-beginning-of-block)
    (objed--skip-ws t (line-beginning-position))
    (point)
    :ibeg
    (forward-line 1)
    (objed--skip-ws)
    (point)
    :iend
    (python-nav-end-of-block)
    (point)
    :end
    (forward-line 1)
    (point))

There are a few differences here:

  • First we have a :commands keyword. This tells objed that we want to activate with this object after any of these commands are executed. That means if you press M-a in python-mode which calls python-nav-backward-block, objed will activate with the block object.

  • Instead of returning the object bounds in one go, we compute the positions separately using :beg, :ibeg, :iend, :end keywords. These are the beginning, inner beginning, inner end and end position of the object. The code for these keywords is allowed to move the point and runs in the same order the keywords are provided.

Now that objed knows about blocks let us look at an example, where I’m testing some objed features in a python buffer:

demo

See you in parendise!

Commments on Reddit