Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 64

Extract and Transform Text from an Active Region

Recommended resources

Introduction

Welcome to the final project-focused lessons of the course! In our last session, you mastered Edebug, the interactive source-level debugger. This gave you a powerful tool to proactively inspect your code, step through its execution, and understand its behavior in detail. This skill is your safety net as we move on to building more complex functionality.

In this lesson, you will put everything together to achieve a core goal for any Emacs customizer: writing an interactive command that operates on a selected region of text. You will learn how to extract text from the region, apply a transformation to it, and then replace the original selection with the result. This is a fundamental pattern for building tools that integrate seamlessly into a writing and editing workflow, directly aligning with your goal of extending Emacs to suit your own needs.

By the end of this lesson, you'll have built a command that is not just a theoretical exercise, but a practical tool you can use and adapt.


1. The Anatomy of a Region-Based Command

In Emacs, the "region" is the text between the "point" (the cursor's current position) and the "mark" (a location you can set, often the other end of a selection). Many built-in commands, like upcase-region or comment-dwim, operate on this area. To create our own such command, we need to tell our function that it should expect the region's boundaries as input.

This is done with a special argument to the interactive form: (interactive "r").

When Emacs sees (interactive "r"), it does two things:

  1. It ensures the command can only be run when a region is active (i.e., the mark is set).
  2. It automatically calls the function with two arguments: the numeric positions of the beginning and end of the region.

Let's see this in action with a simple function that just tells us about the region.

3 Interactive Functions that work on Regions

The article '3 Interactive Functions that work on Regions' by Tony Ballantyne provides a concise and practical starting point. We'll begin with the first example to understand how (interactive "r") works.

Read the first section, '1 Start Point, End Point and Contents of a Region'. Pay close attention to the region-contents function. Notice how rStart and rEnd are defined in the function's argument list to receive the values from (interactive "r").

As the article demonstrates, the function signature (defun region-contents (rStart rEnd) ...) directly captures the start and end points provided by (interactive "r").

Once we have the start and end points, we can extract the text itself using the buffer-substring function. It takes two arguments, a start position and an end position, and returns the string of text between them.

Here's the pattern:

(defun my-region-command (begin end)
  "A template for a command that operates on the region."
  (interactive "r")
  ;; 1. Extract the text from the region into a variable.
  (let ((region-text (buffer-substring begin end)))
    ;; 2. Transform the text.
    (let ((transformed-text ( ... transformation logic on region-text ... )))
      ;; 3. Do something with the result (e.g., replace the original text).
      ...
      )))

2. Extracting, Transforming, and Replacing Text

Now let's build a complete, practical command. A common task for writers is changing the case of a heading or title. We will write a command title-case-region that converts the selected text to "Title Case".

Our process will be:

  1. Extract the text from the region using buffer-substring.
  2. Transform the text: split it into a list of words, capitalize each word, and join them back into a single string.
  3. Replace the original text by first deleting the region and then inserting the new, transformed string.

This process is demonstrated very clearly in the same article from Tony Ballantyne, but with a different transformation (reversing words).

3 Interactive Functions that work on Regions

Let's continue with the same article to see the full extract-transform-replace pattern.

Read section '2 Iterate through the words in a region'. Focus on the second function provided, reverse-region. This function is a perfect illustration of our goal. It uses buffer-substring to get the text, delete-region to remove it, and insert to put the new text in its place.

Following that pattern, here is the complete title-case-region function. Copy this into your *scratch* buffer.

(defun title-case-region (begin end)
  "Convert the active region to Title Case."
  (interactive "r")
  ;; 1. Extract the text.
  (let* ((original-text (buffer-substring begin end))
         ;; 2. Transform the text.
         (words (split-string original-text))
         (capitalized-words (mapcar #'capitalize words))
         (new-text (mapconcat #'identity capitalized-words " ")))
    ;; 3. Replace the original region.
    (delete-region begin end)
    (insert new-text)))

Let's break down the transformation steps inside the let*:

  • (split-string original-text): This takes our string and splits it into a list of words, using whitespace as the delimiter. ("hello" "world")
  • (mapcar #'capitalize words): mapcar applies the capitalize function to every item in the words list, returning a new list. ("Hello" "World")
  • (mapconcat #'identity capitalized-words " "): mapconcat joins the elements of the list back into a single string, putting a space between each element. "Hello World"

Try It Out:

  1. Evaluate the title-case-region function using C-x C-e.
  2. Type some text in your buffer, for example: a heading for my new story.
  3. Select that text to activate the region.
  4. Run M-x title-case-region.

Your text should be replaced with A Heading For My New Story. You've just created a useful, custom writing tool!

Text Region Selection and Transformation in Emacs
This animation illustrates the core concept of our lesson: selecting a region of text and then applying a function to transform its content in place.

3. An Alternative: Modifying Text Around the Region

Sometimes, you don't want to replace the text but rather wrap it with something. This is common for adding markup, like in HTML, Markdown, or your own custom format for creative writing notes.

For this, we can use a slightly different pattern that doesn't delete the region. Instead, we use goto-char to move to the beginning and end of the region and insert our desired text.

A key function to use here is save-excursion. This special form records the current position of the cursor (and other buffer states), executes the code inside it, and then restores the cursor to its original position. This makes your command feel professional and non-disruptive.

3 Interactive Functions that work on Regions

The same article from Tony Ballantyne demonstrates this wrapping pattern perfectly.

Read section '4 Enclose a Region'. The myMark-elisp-region function is a prime example of wrapping text. Note the use of save-excursion to keep the user's cursor in a predictable place, and how goto-char is used with rStart and rEnd to position the insertions.


4. Practical Exercise: Build a Custom Markup Command

Now it's your turn to apply this second pattern. As a writer of speculative fiction, you might want to tag specific terms in your manuscript for your own world-building bible. For example, you might want to wrap the name of an alien species, K'tharr, to become <species>K'tharr</species>.

Your task is to write an interactive command called wrap-with-species-tag that wraps the selected region with <species> and </species>.

Instructions:

  1. Define a new function wrap-with-species-tag that takes begin and end arguments.
  2. Make it interactive for a region.
  3. Use save-excursion to wrap your modification logic.
  4. Inside save-excursion:
    • Move to the end of the region (end) and insert the closing tag </species>. Important: You must insert the closing tag first! If you insert the opening tag first, the end position of the region will shift, leading to incorrect placement.
    • Move to the beginning of the region (begin) and insert the opening tag <species>.
  5. Evaluate your function, select a word in your buffer, and test it with M-x wrap-with-species-tag.
Click here to see a possible solution.
(defun wrap-with-species-tag (begin end)
  "Wrap the active region with <species>...</species> tags."
  (interactive "r")
  (save-excursion
    ;; Go to the end of the region and insert the closing tag first.
    (goto-char end)
    (insert "</species>")
    ;; Go to the beginning of the region and insert the opening tag.
    (goto-char begin)
    (insert "<species>")))

Conclusion

You have now successfully built an interactive command that manipulates text within a region—a cornerstone of extending Emacs. This combines many of the concepts we've covered, from defining functions and using interactive to manipulating strings and modifying the buffer.

Key Takeaways:

  • The (interactive "r") form is the key to creating commands that operate on the active region. It passes the region's begin and end boundaries to your function.
  • The primary pattern for transforming text is:
    1. buffer-substring to extract the text.
    2. Use string and list functions to transform it.
    3. delete-region and insert to replace the original content.
  • For wrapping text, the pattern is to use save-excursion with goto-char and insert to add content at the region's boundaries without disrupting the cursor position.

Preview of the Next Lesson

You are now equipped with the skills to debug and build practical, interactive commands. In the next two lessons, we will complete our journey by focusing on how to organize and manage your personal Elisp code. You will learn how to structure your functions in a dedicated file and how to make Emacs load it correctly using provide and require, turning your collection of functions into a true, personal extension.