2

When I select a rectangle and count the words (M =) in the selection it counts as if I had used the normal selection method.

Is there a way to count only in the rectangle?

Screenshot

2
  • One way to do it is to copy the rectangle to a new buffer and count the words; but I guess you are looking for a more direct way. I guess it is not a bug as the docstring says, when called from lisp count words between start and end. Sep 21, 2021 at 6:53
  • The last sentence reads a little strange but I was not allowed to edit it anymore. So, of course I meant... I guess it is not a bug, as the docstring says: 'If called from Lisp, return the number of words between positions START and END.' Sep 21, 2021 at 9:04

3 Answers 3

3

This function will probably suit you

  (defun rectangle-count-words (start end)
   "count the words in the delimited rectangular region from start to end"
   (interactive "r")
   (let ((rect (mapconcat #'substring-no-properties (extract-rectangle start end) " ")))
     (with-temp-buffer (insert rect)
             (princ(count-words (point-min) (point-max))))))

You can bind it to your favorite key.

2
  • 1
    IMO, it would be better to have the function return the number without doing a princ: let the caller decide what to do with the value.
    – NickD
    Sep 21, 2021 at 19:17
  • @NickD I know that, but when I tested the function, no echo was printed on the mini-buffer even in a emacs -q session. I had to evalue (call-interactively #'rectangle-count-words) for that. That is the first time it happend to me. Probably I have to post for some clues since I don't find out why.
    – gigiair
    Sep 21, 2021 at 20:09
1
(defun ph/count-words-in-rectangle (start end)
  "Count words in rectangle defined by START and END."
  (interactive "r")
  (copy-rectangle-as-kill start end)
  (with-temp-buffer  
    (yank-rectangle)
    (call-interactively #'count-words)))

Still outputs "Region has..." instead of "Rectangle has..." but otherwise does what you want, I think.

(This is effectively the same thing that @dalanicolai proposed in a comment above).

0

I just added command count-words-rectangle to my little file misc-cmds.el.

Unlike count-words and count-words-region, by default this does not count "words" that straddle the beginning or end of a rectangle row. If you use a prefix arg then such partial words at row boundaries are counted.

count-words-region counts as a "word" a piece of a word when the whole word starts before the beginning, or ends after the end, of the region. I think it's more useful, and more likely to be what someone expects, to count only words that are entirely within the region. But that ship has sailed...

(defun count-words-rectangle (start end &optional allow-partial-p msgp)
  "Count words in the rectangle from START to END.
This is similar to `count-words', but for a rectangular region.

Also:

* By default, a word that straddles the beginning or end of a
  rectangle row is not counted.  That is, this counts only words that
  are entirely within the rectangle.
* A prefix arg means count also such partial words at row boundaries.

If called interactively, START and END are the bounds of the start and
end of the active region.  Print a message reporting the number of
rows (lines), columns (characters per row), words, and characters.

If called from Lisp, return the number of words in the rectangle
between START and END, without printing any message."
  (interactive "r\nP\np")
  (let ((bounds  (extract-rectangle-bounds start end))
        (words   0)
        (chars   0))
    (dolist (beg+end  bounds)
      (setq words  (+ words (count-words (car beg+end) (cdr beg+end)))))
    (let (beg end)
      (dolist (beg+end  bounds)
        (setq beg  (car beg+end)
              end  (cdr beg+end))
        (unless allow-partial-p
          (when (and (char-after (1- beg))  (equal '(2) (syntax-after (1- beg)))
                     (char-after beg)       (equal '(2) (syntax-after beg)))
            (setq words  (1- words)))
          (when (and (char-after (1- end))  (equal '(2) (syntax-after (1- end)))
                     (char-after end)       (equal '(2) (syntax-after     end)))
            (setq words  (1- words))))))
    (when msgp
      (dolist
          (beg+end  bounds)
        (setq chars  (+ chars (- (cdr beg+end) (car beg+end)))))
      (let ((rows  (count-lines start end))
            (cols  (let ((rpc  (save-excursion
                                 (rectangle--pos-cols (region-beginning) (region-end)))))
                     (abs (- (car rpc) (cdr rpc))))))
        (message "Rectangle has %d row%s, %d colum%s, %d word%s, and %d char%s."
                 rows  (if (= rows 1)  "" "s")
                 cols  (if (= cols 1)  "" "s")
                 words (if (= words 1) "" "s")
                 chars (if (= chars 1) "" "s"))))
    words))

(I wish count-words and count-words-region optionally did the same - counted only words entirely within bounds. But their code is old and needs to be backward-compatible, and a prefix arg already has a different meaning there, so it's maybe not worth trying to fix that.)


Update

I added showing this in the mode-line, whenever a rectangle command is invoked.

This is in library modeline-posn.el. To choose it, just customize option modelinepos-rectangle-style, choosing Rows, columns, words, chars, instead of the default, Rows and columns. (For this, you also need library misc-cmds.el.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.