From: Xah
Subject: elisp exercise: toggle-letter-case
Date: 
Message-ID: <420ba543-19ba-4987-9f3a-a57878777c9d@n33g2000pri.googlegroups.com>
here's a little interesting exercise.

I'm writing a toggle-letter-case function below. However, it has some
problems. (see the doc string). After thinking on this for a while,
the problem seems a bit complex. It'll take perhaps few hours to fix
these, in particular, if it is to cover chars like éÉ èÈ üÜ. Also, it
seems a good solution will require this function to have a state, but
i'm reluctant to introduce a global variable for it.

I'm wondering, if anyone have a better solution?

(defun toggle-letter-case ()
  "Toggle the letter case of current word or text selection.
Toggles from 3 cases: upper case, lower case, title case,
in that order.
Title case means upcase first letter of each word.

Todo:
• this command only consider English alphabets. For example, it may
not work properly if you have éÉ èÈ üÜ.
• It may not work when the first or second letter is a number, e.g.
“1time”.
• It may not work when you only have a single letter. e.g. “A
teapot”."
(interactive)

(save-excursion
(let (pt pos1 pos2 cap1p cap2p (deactivate-mark nil) (case-fold-search
nil)
         )
  (setq pt (point))
  (if (and transient-mark-mode mark-active)
      (setq pos1 (region-beginning)
            pos2 (region-end))
    (setq pos1 (car (bounds-of-thing-at-point 'word))
          pos2 (cdr (bounds-of-thing-at-point 'word))))

;; check 1th and 2th letters cases
  (goto-char pos1)
  (setq cap1p (looking-at "[A-Z]"))
  (goto-char (1+ pos1))
  (setq cap2p (looking-at "[A-Z]"))

  (cond
   ((and (not cap1p) (not cap2p)) (upcase-initials-region pos1 pos2))
   ((and cap1p (not cap2p)) (upcase-region pos1 pos2) )
   ((and cap1p cap2p) (downcase-region pos1 pos2) )
   (t (downcase-region pos1 pos2) )
   )
  )
)
)

PS the above assumes you have transient-mode on.

  Xah
∑ http://xahlee.org/

☄
From: ······@gmail.com
Subject: Re: elisp exercise: toggle-letter-case
Date: 
Message-ID: <8d984096-eb5f-47ea-8239-c6e3db0c97ca@b38g2000prf.googlegroups.com>
 Dyre Tjeldvoll (··········@sun.com) wrote:
> Xah<······@gmail.com> writes:
> > here's a little interesting exercise.
>
> > I'm writing a toggle-letter-case function below. However, it has some
> > problems. (see the doc string). After thinking on this for a while,
> > the problem seems a bit complex. It'll take perhaps few hours to fix
> > these, in particular, if it is to cover chars like éÉ èÈ üÜ. Also, it
> > seems a good solution will require this function to have a state, but
> > i'm reluctant to introduce a global variable for it.
>
> > I'm wondering, if anyone have a better solution?
>
> Maybe it is obvious, but why can't you just use
>
> upcase-word downcase-word capitalize-word
> upcase-region downcase-region and capitalize-region
>
> that already come with emacs? Or crate a wrapper function
> to get the behavior you want?

See:

Usability Problems With Emacs's Letter-Case Commands
http://xahlee.org/emacs/modernization_upcase-word.html

plain text version follows
-----------------------------
Usability Problems With Emacs's Letter-Case Commands

Xah Lee, 2008-10

Emacs has several user level commands for changing letter case. They
are: upcase-word (M-u), downcase-word (M-l), capitalize-word (M-c).

There are also “region” versions for each: upcase-region (C-x C-u),
downcase-region (C-x C-l), capitalize-region, and also upcase-initials-
region. (Note: for elisp programing, there are also these functions:
upcase, capitalize, downcase, upcase-initials.)

One problem with these commands is that you need to move your cursor
to the beginning of the word first. For example, if your cursor is on
the “a” In “THat”, and you call downcase-word, but it doesn't do
anything because it only start at the cursor point to end of word. It
would be nice if it'll just automatically perform the operation on the
whole word.

Another problem is that it only performs one specific letter-case
transform as opposed to transforming by considering the final result.
For example, if you have “oncE upon a time ...”, and you select the
whole sentence and call upcase-initials-region, it becomes “OncE Upon
A Time ...”. Note the capital E is not automatically lowered.

Also, these commands have a “-word” and “-region” versions. ... great
for precision in elisp programing but not smart as user commands. It
would be nice if emacs automatically choose the right command
depending whether there is text selection. (emacs is this way because
technically, a “region” always exists. The modern concept of text
selection (with highlighting) didn't come to emacs until it had
transient-mark-mode, along with the complexity of “mark-active”.
Things would be much simpler if emacs adopted transient-mark-mode by
default.)

The following code combines them into one user function and should fix
these problems.

(defun toggle-letter-case ()
  "Toggle the letter case of current word or text selection.
Toggles from 3 cases: UPPER CASE, lower case, Title Case,
in that cyclic order."
  (interactive)

  (let (pos1 pos2 (deactivate-mark nil) (case-fold-search nil))
    (if (and transient-mark-mode mark-active)
        (setq pos1 (region-beginning)
              pos2 (region-end))
      (setq pos1 (car (bounds-of-thing-at-point 'word))
            pos2 (cdr (bounds-of-thing-at-point 'word))))

    (when (not (eq last-command this-command))
      (save-excursion
        (goto-char pos1)
        (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") )
         (t (put this-command 'state "all lower") )
         )
        )
      )

    (cond
     ((string= "all lower" (get this-command 'state))
      (upcase-initials-region pos1 pos2) (put this-command 'state
"init caps"))
     ((string= "init caps" (get this-command 'state))
      (upcase-region pos1 pos2) (put this-command 'state "all caps"))
     ((string= "all caps" (get this-command 'state))
      (downcase-region pos1 pos2) (put this-command 'state "all
lower"))
     )
    )
  )

This toggle-letter-case command is given a single keyboard shortcut in
A Ergonomic Keyboard Shortcut Layout For Emacs.

  Xah
∑ http://xahlee.org/

☄