Emacs 8 - A Case for Extensibility

Publish date: Mar 7, 2020
Tags: emacs, extensibility, editor, lisp

Emacs Killer Feature

I have heard a lot of Emacs related comments and reviews wondering about which is the one feature that makes Emacs so awesome. Many pointing to Org Mode, others choose Magit as the killer feature. A less common opinion is that the extensibility of Emacs is what differentiates Emacs from other text editors. In other words, people love Emacs because it is not only a text editor.

A Lisp Parser

Emacs is essentially a Lisp parser. Allowing the modification of most of its core functions while it runs, Emacs is designed for extensibility.

An Incentive for Hacking

As function definitions are readily available in Emacs, the user is invited open the hood, check out the internals, inspect the behaviors and even debug and change the running functions.

My Case for Extensibility

This post showcases a new functionality I added to Emacs by writing an extension to one of its built-in functionalities in GNU Emacs called thing-at-point. The actual implementation is heavily inspired by the work published in Emacs Redux. I will walk through the process of implementing the extension and I hope you enjoy the tour.

Thing At Point

There is a very useful Emacs built-in package called Thing At Point that allows you to programmatically read a certain type of text object under the cursor.

A thing defined in the package can be a symbol, sexp, defun, filename, url, email, word, sentence, whitespace, line, number or a page.

This package was written to allow extensibility and also contains information on how to define a symbol as a valid thing.

The Objective

As casual vim user, I noticed that C-a and C-x respectively incremented and decremented a number at point. Trying to do the same in Emacs, I realized that this functionality was not available out of the box. I’ve tried simply to use the thing defined as number, but the behavior was not reliable.

At this point I already knew that thing-at-point was extensible and there are some implementations of integer-at-point around the internet. Then I started studying the ideas and implementations to achieve the following goals:

Given that, if no arg is provided, the code will use the default value of 1.

Extending Thing At Point

As given by the package documentation, the thing at point is defined by finding the boundaries of that thing. To extend the package we should define functions to go from the current cursor position (known in Emacs as point) to the beginning-of-thing and to the end-of-thing. A function bounds-of-thing will be used to read that boundaries.

Implementating beginning-of-integer

This function moves the cursor from the current position to the beginning of the number by skipping all digit characters. In the end it moves left to include the plus or minus signs if any.

(defun thing-at-point--beginning-of-integer ()
  "Go to the beginning of the integer at point."
  (let ((inhibit-changing-match-data t))
    (skip-chars-backward "[:digit:]")
    (unless (looking-at "[+-]?[[:digit:]]")
      (error "No integer here"))
    (when (looking-back "[+-]")
      (backward-char 1))))

Implementating end-of-integer

This function moves the cursor from the current position to the end of the number by skipping all digit characters. In the beginning of its execution it checks if the current position contains the plus or minus signs. If it finds such character it moves one char forward.

(defun thing-at-point--end-of-integer ()
  "Go to the end of the integer at point."
  (let ((inhibit-changing-match-data t))
    (when (looking-at "[+-]")
      (forward-char 1))
    (skip-chars-forward "[:digit:]")
    (unless (looking-back "[[:digit:]]")
      (error "No integer here"))))

Implementing bounds-of-integer-at-point

This functions uses the previously defined functions to walk through the end and beginning of the integer and returns a list with the cursor positions of the start and the end of the integer number at point.

(defun thing-at-point--bounds-of-integer-at-point ()
  "Get boundaries of integer at point."
  (save-excursion
    (let (beg end)
      (thing-at-point--end-of-integer)
      (setq end (point))
      (thing-at-point--beginning-of-integer)
      (setq beg (point))
      (cons beg end))))

Implementing integer-at-point

This functions returns the value of the integer at point as a number.

(defun thing-at-point-integer-at-point ()
  "Get integer at point."
  (let ((bounds (bounds-of-thing-at-point 'integer)))
    (string-to-number (buffer-substring (car bounds) (cdr bounds)))))

Registering the New Thing

Store integer symbol’s property name with the above defined functions. This configures the functions to be used by thing-at-point.

(put 'integer 'beginning-op 'thing-at-point--beginning-of-integer)
(put 'integer 'end-op 'thing-at-point--end-of-integer)
(put 'integer 'bounds-of-thing-at-point 'thing-at-point--bounds-of-integer-at-point)
(put 'integer 'thing-at-point 'thing-at-point-integer-at-point)

Adding the Operations

To allow the four basic math operations I created a generic function that receives the operation and the argument and replaces the integer at point with the result of this operation. I also created the specific operation functions to easily bind them to shortcuts.

(defun operate-integer-at-point (operation &optional arg)
  (interactive "p")
  (if (not (numberp (thing-at-point 'integer)))
      (error "thing at point is not a number"))
  (let ((start-pos (point))
        (operand (or arg 1))
        (value (thing-at-point 'integer)))
    (save-excursion
      (let ((bounds (bounds-of-thing-at-point 'integer)))
        (kill-region (car bounds) (cdr bounds))
        (insert (number-to-string (funcall operation value operand)))))
    (goto-char start-pos)))

(defun multiply-integer-at-point (&optional arg)
  (interactive "p")
  (operate-integer-at-point '* arg))

(defun divide-integer-at-point (&optional arg)
  (interactive "p")
  (operate-integer-at-point '/ arg))

(defun increment-integer-at-point (&optional arg)
  (interactive "p")
  (operate-integer-at-point '+ arg))

(defun decrement-integer-at-point (&optional arg)
  (interactive "p")
  (let ((increment (or arg 1)))
    (increment-integer-at-point (- 0 increment))))

Creating keybinding

I use modal editing with a very personal set of keybindings and I am not intended to preach to anyone to do something similar, but to showcase a possible way of using this, I created a set of global keybindings to add to this post.

(global-set-key (kbd "<kp-add>") 'increment-integer-at-point)
(global-set-key (kbd "<kp-subtract>") 'decrement-integer-at-point)
(global-set-key (kbd "<kp-multiply>") 'multiply-integer-at-point)
(global-set-key (kbd "<kp-divide>") 'divide-integer-at-point)

Full implementation

(require 'thingatpt)

(defun thing-at-point--beginning-of-integer ()
  "Go to the beginning of the integer at point."
  (let ((inhibit-changing-match-data t))
    (skip-chars-backward "[:digit:]")
    (unless (looking-at "[+-]?[[:digit:]]")
      (error "No integer here"))
    (when (looking-back "[+-]")
      (backward-char 1))))

(defun thing-at-point--end-of-integer ()
  "Go to the end of the integer at point."
  (let ((inhibit-changing-match-data t))
    (when (looking-at "[+-]")
      (forward-char 1))
    (skip-chars-forward "[:digit:]")
    (unless (looking-back "[[:digit:]]")
      (error "No integer here"))))

(defun thing-at-point--bounds-of-integer-at-point ()
  "Get boundaries of integer at point."
  (save-excursion
    (let (beg end)
      (thing-at-point--end-of-integer)
      (setq end (point))
      (thing-at-point--beginning-of-integer)
      (setq beg (point))
      (cons beg end))))

(defun thing-at-point-integer-at-point ()
  "Get integer at point."
  (let ((bounds (bounds-of-thing-at-point 'integer)))
    (string-to-number (buffer-substring (car bounds) (cdr bounds)))))

(put 'integer 'beginning-op 'thing-at-point--beginning-of-integer)
(put 'integer 'end-op 'thing-at-point--end-of-integer)
(put 'integer 'bounds-of-thing-at-point 'thing-at-point--bounds-of-integer-at-point)
(put 'integer 'thing-at-point 'thing-at-point-integer-at-point)

(defun operate-integer-at-point (operation &optional arg)
  (interactive "p")
  (if (not (numberp (thing-at-point 'integer)))
      (error "thing at point is not a number"))
  (let ((start-pos (point))
        (operand (or arg 1))
        (value (thing-at-point 'integer)))
    (save-excursion
      (let ((bounds (bounds-of-thing-at-point 'integer)))
        (kill-region (car bounds) (cdr bounds))
        (insert (number-to-string (funcall operation value operand)))))
    (goto-char start-pos)))

(defun multiply-integer-at-point (&optional arg)
  (interactive "p")
  (operate-integer-at-point '* arg))

(defun divide-integer-at-point (&optional arg)
  (interactive "p")
  (operate-integer-at-point '/ arg))

(defun increment-integer-at-point (&optional arg)
  (interactive "p")
  (operate-integer-at-point '+ arg))

(defun decrement-integer-at-point (&optional arg)
  (interactive "p")
  (let ((increment (or arg 1)))
    (increment-integer-at-point (- 0 increment))))

(global-set-key (kbd "<kp-add>") 'increment-integer-at-point)
(global-set-key (kbd "<kp-subtract>") 'decrement-integer-at-point)
(global-set-key (kbd "<kp-multiply>") 'multiply-integer-at-point)
(global-set-key (kbd "<kp-divide>") 'divide-integer-at-point)

Demo

Integer At Point

References