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:
- It ensures the command can only be run when a region is active (i.e., the mark is set).
- 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:
- Extract the text from the region using
buffer-substring. - Transform the text: split it into a list of words, capitalize each word, and join them back into a single string.
- 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):mapcarapplies thecapitalizefunction to every item in thewordslist, returning a new list.("Hello" "World")(mapconcat #'identity capitalized-words " "):mapconcatjoins the elements of the list back into a single string, putting a space between each element."Hello World"
Try It Out:
- Evaluate the
title-case-regionfunction usingC-x C-e. - Type some text in your buffer, for example:
a heading for my new story. - Select that text to activate the region.
- 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!

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:
- Define a new function
wrap-with-species-tagthat takesbeginandendarguments. - Make it interactive for a region.
- Use
save-excursionto wrap your modification logic. - 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, theendposition of the region will shift, leading to incorrect placement. - Move to the beginning of the region (
begin) and insert the opening tag<species>.
- Move to the end of the region (
- 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'sbeginandendboundaries to your function. - The primary pattern for transforming text is:
buffer-substringto extract the text.- Use string and list functions to transform it.
delete-regionandinsertto replace the original content.
- For wrapping text, the pattern is to use
save-excursionwithgoto-charandinsertto 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.