1

Xah Lee wrote (some time ago) the following function that toggles the letter-case of the selected region or the current word. When toggling to initial caps, some words should not be capitalized such as and, or, for, and perhaps some custom words like to.

Q:  How can toggle-letter-case be modified to exclude select words (e.g., from a list of custom words) when making initial caps.

Scenario #1 -- all uppercase:  FOO TO BAR FOR BAZ; AND, APPLE TO ORANGE

Scenario #2 -- all lowercase:  foo to bar for baz; and, apple to orange

Initial Caps -- Desired Result:  Foo to Bar for Baz; and, Apple to Orange

(defun toggle-letter-case ()
  "Toggle the letter case of current word or text selection.
  Toggles between: “all lower”, “Init Caps”, “ALL CAPS”."
(interactive)
  (let ((deactivate-mark nil)
        (case-fold-search nil)
        p1 p2)
   (if (region-active-p)
       (setq p1 (region-beginning) p2 (region-end))
     (let ((bds (bounds-of-thing-at-point 'word)))
       (setq p1 (car bds) p2 (cdr bds))))
   (when (not (eq last-command this-command))
     (save-excursion
       (goto-char p1)
       (cond
        ((looking-at "[[:lower:]][[:lower:]]") (put this-command 'state "all lower"))
        ((looking-at "[[:upper:]][[:upper:]]") (put this-command 'state "all caps"))
        ((looking-at "[[:upper:]][[:lower:]]") (put this-command 'state "init caps"))
        ((looking-at "[[:lower:]]") (put this-command 'state "all lower"))
        ((looking-at "[[:upper:]]") (put this-command 'state "all caps"))
        (t (put this-command 'state "all lower")))))
   (cond
    ((string= "all lower" (get this-command 'state))
     (upcase-initials-region p1 p2) (put this-command 'state "init caps"))
    ((string= "init caps" (get this-command 'state))
     (upcase-region p1 p2) (put this-command 'state "all caps"))
    ((string= "all caps" (get this-command 'state))
     (downcase-region p1 p2) (put this-command 'state "all lower")))))
  • The code is a bit weird. Why do you test region-active-p after already having tested it and thrown an error if nil? Why let* if there are no binding dependencies? And it's not clear what all that stuff about Form Interrogatory Number 17.1 is for. – Drew Aug 14 '16 at 20:46
  • As for the question itself: You already check what you are looking at. Why can't you also check whether it is one of your stop words and take the appropriate action? – Drew Aug 14 '16 at 20:47
  • @Drew -- Cleaning up the code is always a good thing. My only idea as to a potential solution to the main issue was to run a replace-regexp (with word boundaries) that mapc down the list one by one after running upcace-initials-region on the entire selected region. – lawlist Aug 14 '16 at 20:55
  • In the initial question (before the edit), I had picked a real-life example that came up today while working in the home-office, but the example has now been changed to be more programming-like. :) The first example is all uppercase, the second example is all lowercase, and the third example is the desired result. – lawlist Aug 14 '16 at 21:10
  • I'd write a function capitalize-region-except, which proceeds as you suggested, but using regexp-opt instead of mapc. Then you can clean-up the posted function, which has multiple problems, and incorporate your written function as a 4th case or replace the current capitalize one. – politza Aug 14 '16 at 21:42
1

There's this command maybe you've seen?

It doesn't toggle, but rather just Init case with a list of words of exceptions.

(defun xah-title-case-region-or-line (*begin *end)
  "Title case text between nearest brackets, or current line, or text selection.
Capitalize first letter of each word, except words like {to, of, the, a, in, or, and, …}. If a word already contains cap letters such as HTTP, URL, they are left as is.

When called in a elisp program, *begin *end are region boundaries.
URL `http://ergoemacs.org/emacs/elisp_title_case_text.html'
Version 2015-05-07"
  (interactive
   (if (use-region-p)
       (list (region-beginning) (region-end))
     (let (
           -p1
           -p2
           (-skipChars "^\"<>(){}[]“”‘’‹›«»「」『』【】〖〗《》〈〉〔〕"))
       (progn
         (skip-chars-backward -skipChars (line-beginning-position))
         (setq -p1 (point))
         (skip-chars-forward -skipChars (line-end-position))
         (setq -p2 (point)))
       (list -p1 -p2))))
  (let* (
         (-strPairs [
                     [" A " " a "]
                     [" And " " and "]
                     [" At " " at "]
                     [" As " " as "]
                     [" By " " by "]
                     [" Be " " be "]
                     [" Into " " into "]
                     [" In " " in "]
                     [" Is " " is "]
                     [" It " " it "]
                     [" For " " for "]
                     [" Of " " of "]
                     [" Or " " or "]
                     [" On " " on "]
                     [" Via " " via "]
                     [" The " " the "]
                     [" That " " that "]
                     [" To " " to "]
                     [" Vs " " vs "]
                     [" With " " with "]
                     [" From " " from "]
                     ["'S " "'s "]
                     ]))
    (save-excursion 
      (save-restriction
        (narrow-to-region *begin *end)
        (upcase-initials-region (point-min) (point-max))
        (let ((case-fold-search nil))
          (mapc
           (lambda (-x)
             (goto-char (point-min))
             (while
                 (search-forward (aref -x 0) nil t)
               (replace-match (aref -x 1) 'FIXEDCASE 'LITERAL)))
           -strPairs))))))
  • Thank you very much -- I'll be able to test it out later this evening -- greatly appreciated. I enjoy using your functions, and reading the tutorials on your website. :) – lawlist Aug 15 '16 at 20:19
  • @lawlist thank you for using and mentioning my site, i love it! – Xah Lee Aug 16 '16 at 1:29
0

Special thanks to @politza for (regexp-opt WORDS 'words)

(defun toggle-letter-case ()
"Initially written by Xah Lee, and slightly modified by @lawlist.
Toggle the letter case of current word or text selection.
Toggles between: “all lower”, “Init Caps”, “ALL CAPS”.
As to initial-capitals, do not capitalize hard-coded list of words."
(interactive)
  (let ((deactivate-mark nil)
        (case-fold-search nil)
        (regexp-exclusion (regexp-opt '("and" "for" "or" "to") 'words))
        p1 p2)
   (if (region-active-p)
       (setq p1 (region-beginning) p2 (region-end))
     (let ((bds (bounds-of-thing-at-point 'word)))
       (setq p1 (car bds) p2 (cdr bds))) )
   (when (not (eq last-command this-command))
     (save-excursion
       (goto-char p1)
       (cond
        ((looking-at "[[:lower:]][[:lower:]]") (put this-command 'state "all lower"))
        ((looking-at "[[:upper:]][[:upper:]]") (put this-command 'state "all caps"))
        ((looking-at "[[:upper:]][[:lower:]]") (put this-command 'state "init caps"))
        ((looking-at "[[:lower:]]") (put this-command 'state "all lower"))
        ((looking-at "[[:upper:]]") (put this-command 'state "all caps"))
        (t (put this-command 'state "all lower")))))
   (cond
    ((string= "all lower" (get this-command 'state))
      (upcase-initials-region p1 p2)
      (put this-command 'state "init caps")
      (let ((case-fold-search t))
        (save-excursion
          (goto-char p1)
            (while (re-search-forward regexp-exclusion p2 t)
              (replace-match (downcase (match-string 0)) t)))))
    ((string= "init caps" (get this-command 'state))
     (upcase-region p1 p2) (put this-command 'state "all caps"))
    ((string= "all caps" (get this-command 'state))
     (downcase-region p1 p2) (put this-command 'state "all lower")))))
  • It looks to me like the first two cases in the cond are not required, because they are subsets of cases 4 and 5. So the code could be simplified by removing them. And the last case 6 seem redundant too, unless there is a possibility of not looking at a letter at all. If it was me, I would try removing cases 1 and 2, swapping cases 4 and 5, turn the new 5 into a "t" case, and delete case 6. Just an idea. – Kevin Aug 16 '16 at 18:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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