Published 2025-09-18
tag(s): #emacs
I wrote before
about my custom mode
line.
To briefly recap: it is text-only, based
on mood-line which is itself based on the
very popular doom-modeline.
From my time using doom-modeline, I came to appreciate the convenience of having the region
size displayed. Yes, I can use M-=
(default binding
for count-words-region
)...but turns out that it presents the same problem that my
mode line indicator had!
You can see the region size displayed above. The way this information was calculated until this morning:
(setq-default mode-line-position
(list "%l:%c"
'(:propertize " %p% %I" face shadow)
'(:eval
(when (use-region-p)
(propertize (format " (%sL:%sC)"
(count-lines (region-beginning)
(region-end))
(- (region-end) (region-beginning)))
'face 'shadow)))
" "))
Counting the lines in the region was provided by (count-lines (region-beginning)
(region-end))
, and then the number of characters was simply the difference between the
positions of the beginning and end of the region.
Except...
The problem is, when the region is a rectangle, the positions of the beginning and end of the region do not reflect accurately the character count, because obviously, a number of chars are excluded in each line.
I don't use rectangles that often, and I could live with the count being off the few times I
do. But...you know.
Once I realized it was wrong, I had to fix it. 👀
My first reaction was to look at how count-words-region
does this. To my
surprise, it doesn't account for rectangle selections. Maybe I should report this as
a bug?
Then I took a look at how to figure out that a rectangle selection is taking place, and from
there, if any of the code in rect.el
was counting the number of characters. I
didn't find anything, but figured that extract-rectangle
(used internally in a
number of operations) could help me, since it returns the rectangle as a list of strings.
(setq-default mode-line-position
(list "%l:%c"
'(:propertize " %p% %I" face shadow)
'(:eval
(when (use-region-p)
(propertize (apply #'format
" (%sL:%sC)"
(hoagie--mode-line-region-size))
'face 'shadow))
" ")))
(defun hoagie--mode-line-region-size ()
"Helper to calculate the region size to display in the mode line.
Returns a list with the number of lines, and the number of characters.
For the latter, it uses a more involved calculation for rectangle selections."
(let ((lines (count-lines (region-beginning) (region-end))))
(list lines
(if rectangle-mark-mode
;; first line or last line, both have the same amount of
;; characters.
(* (length (car (extract-rectangle (region-beginning)
(region-end))))
lines)
;; regular region
(- (region-end) (region-beginning))))))
A first approach used seq-reduce
to sum the length of all lines in the list, then
I realized I only need the length of the first line, the rest are the same. Because
rectangles. 🙃
After testing this version, I found extract-rectangle-bounds
, which returns the
positions in each line as a list of conses. I could have used that too, getting the difference
in the positions of the first line instead.
I find the code is getting too complicated for something that runs constantly (the mode line
is updated all the time). But I guess unless I run into performance problems, it should be
fine?
You can see in the screenshot the end result, with the output of M-=
in the echo
area.