Search code examples
emacselisp

Toggle case of next letter in elisp


I'd like to be able to toggle the case of the letter under the point. To that end, I wrote this:

(defun toggle-case-next-letter ()
  "Toggles the case of the next letter, then moves the point forward one character"
  (interactive)
  (let* ((p (point))
        (upcased (upcasep (char-after)))
        (f (if upcased 'downcase-region 'upcase-region)))
    (progn
      (f p (+ 1 p))
      (forward-char))))

However, when I run it (I've bound it to M-#), I get progn: Symbol's function definition is void: f. I assume this means f isn't bound, but I'm not sure.

Upcasep is defined as:

(defun upcasep (c) (eq c (upcase c)))

Is the problem in the let binding, or something else? (Also, if there's a better way to do this, that'd be nice as well).

Note that originally I had (upcased (upcasep (buffer-substring-no-properties p (+ 1 p)))), which I've corrected to (upcased (upcasep (char-after)), because using upcasep as defined above is always nil for strings (so I couldn't downcase again).


Solution

  • You've got a typical case of lisp-1 / lisp-2 confusion. Here's a fix (just a funcall):

    (defun toggle-case-next-letter ()
      "Toggles the case of the next letter, then moves the point forward one character"
      (interactive)
      (let* ((p (point))
            (upcased (char-upcasep (buffer-substring-no-properties p (+ 1 p))))
            (f (if upcased 'downcase-region 'upcase-region)))
        (progn
          (funcall f p (+ 1 p))
          (forward-char))))
    

    And here's what I have:

    (global-set-key (kbd "C->") 'upcase-word-toggle)
    (global-set-key (kbd "C-z") 'capitalize-word-toggle)
    
    (defun char-upcasep (letter)
      (eq letter (upcase letter)))
    
    (defun capitalize-word-toggle ()
      (interactive)
      (let ((start (car
                    (save-excursion
                      (backward-word)
                      (bounds-of-thing-at-point 'symbol)))))
        (if start
            (save-excursion
              (goto-char start)
              (funcall
               (if (char-upcasep (char-after))
                   'downcase-region
                 'upcase-region)
               start (1+ start)))
          (capitalize-word -1))))
    
    (defun upcase-word-toggle ()
      (interactive)
      (let ((bounds (bounds-of-thing-at-point 'symbol))
            beg end
            regionp)
        (if (eq this-command last-command)
            (setq regionp (get this-command 'regionp))
          (put this-command 'regionp nil))
        (cond
          ((or (region-active-p) regionp)
           (setq beg (region-beginning)
                 end (region-end))
           (put this-command 'regionp t))
          (bounds
           (setq beg (car bounds)
                 end (cdr bounds)))
          (t
           (setq beg (point)
                 end (1+ beg))))
        (save-excursion
          (goto-char (1- beg))
          (and (re-search-forward "[A-Za-z]" end t)
               (funcall (if (char-upcasep (char-before))
                            'downcase-region
                          'upcase-region)
                        beg end)))))