4

I'm completely new to elisp, and I want a function which cycles through 3 options:

  • Highlight the word.
  • Highlight the sentence.
  • Then highlight the paragraph.

What is the proper way to get the function to (setq setnewpoint t) and (setq cyclemarker 1) if I C-g mid-cycle?

(setq cyclemarker 1)
(setq setnewpoint t)

(defun highlight-cycle ()
  (interactive)
  (if setnewpoint
      (progn
        (forward-word)
        (backward-word)
        (point-to-register 1)
        (setq setnewpoint nil)))
  (cond ((= cyclemarker 1)
           (progn
             (register-to-point 1)
             (setq setnewpoint t)
             (setq cyclemarker 2)
             (keyboard-quit)))
        ((= cyclemarker 2)
           (progn
             (register-to-point 1)                   
             (mark-word)
             (setq cyclemarker 3)))
        ((= cyclemarker 3)
           (progn
             (register-to-point 1)
             (mark-end-of-sentence 1)
             (forward-sentence 1)
             (backward-sentence 1)
             (setq cyclemarker 4)
             ))
        ((= cyclemarker 4)
           (progn
             (register-to-point 1)
             (mark-paragraph)
             (cond ((/= (line-beginning-position) (point-min))
             (next-line)
             (beginning-of-line)))
             (setq cyclemarker 1)))
        ))
12
  • 2
    Check out unwind-protect.
    – Dan
    Nov 11, 2014 at 16:40
  • 1
    Seth, when you say "mid-cycle", do you mean between two separate executions of the function? If so, the function is not being interrupted at all, and you need to fix your title.
    – Malabarba
    Nov 11, 2014 at 16:55
  • 2
    @SethRothschild I think the way you're doing it is the best one. You just need to advise keyboard-quit. I'm sure somebody will explain it if you fix the title to match the text. :)
    – Malabarba
    Nov 11, 2014 at 17:01
  • 1
    @Malabarba Oh! I never would have figured that out myself. Should I write my own answer? Nov 11, 2014 at 17:06
  • 2
    FWIW - I would use condition-case with quit, and have the behavior be that when you use C-g (quit) during the command it resets the things you want to reset and then reinvokes the command. I would not advise keyboard-quit or in any other way try to have a C-g outside of the command affect the state for the command.
    – Drew
    Nov 11, 2014 at 17:13

4 Answers 4

2

The variable last-command contains the last command executed. If the last command was keyboard-quit, it will contain the symbol keyboard-quit. You can test this command in your function:

(defvar highlight-something-counter 0)

(defun highlight-something ()
  (interactive)
  (if (eql last-command 'keyboard-quit)
    (setq highlight-something-counter 0))
  (message "Highlight counter is now %d, last command was %s"
           highlight-something-counter last-command)
  (incf highlight-something-counter))

Note however that this will only work when the user pressed C-g immediately before invoking your command, so you will probably want to reset the counter on any command other than yours:

(defvar highlight-something-counter 0)

(defun highlight-something ()
  (interactive)
  (unless (eql last-command 'highlight-something)
    (setq highlight-something-counter 0))
  (message "Highlight counter is now %d, last command was %s"
           highlight-something-counter last-command)
  (incf highlight-something-counter))

If you really want only C-g to reset your counter and allow intervening commands, then you will need to use a different solution — advising keyboard-quit, as suggested by Malabarba, might be workable.

4

This is tangential to the elisp related question but you might find the behaviour of expand-region what you are looking for. You may also find it's implementation interesting from an idiomatic approach to the problem.

Basically the main expander wraps the per-word, per-sentence and per-paragraph hooks in a save-excursion loop and then uses the "best" results going forward.

1

You shouldn't sweat about keyboard interrupts for such an undemanding task as you described. But the way to do it is though unwind-protect:

(require 'cl)

(defun lispy--mark (bnd)
  "Mark BND.  BND is a cons of beginning and end positions."
  (destructuring-bind (beg . end) bnd
    (goto-char beg)
    (push-mark-command t t)
    (goto-char end)))

(defun highlight-cycle ()
  (interactive)
  (unwind-protect
       (progn
         (sit-for 2)
         (let ((bnd (if (region-active-p)
                        (cons (region-beginning)
                              (region-end))
                      (bounds-of-thing-at-point 'word))))
           (if (region-active-p)
               (cond ((equal bnd (bounds-of-thing-at-point 'word))
                      (lispy--mark (bounds-of-thing-at-point 'sentence)))
                     ((equal bnd (bounds-of-thing-at-point 'page))
                      (deactivate-mark t)
                      (lispy--mark (get 'highlight-cycle 'word)))
                     (t
                      (lispy--mark (bounds-of-thing-at-point 'page))))
             (put 'highlight-cycle 'word bnd)
             (lispy--mark bnd))))
    (error "Have some patience, please")))

(global-set-key (kbd "M-m") 'highlight-cycle)

highlight-cycle will cycle between word, sentence and page, but it will wait for 2 seconds before doing anything. If the user interrupts with C-g, it will complain. You can put something other than complaint there, of course.

2
  • Really? You want the command to always end by raising an error? Doesn't sound too good, to me.
    – Drew
    Nov 11, 2014 at 17:15
  • 1
    It's just for example. I could change it to user-error instead. message is too weak - it's overwritten.
    – abo-abo
    Nov 11, 2014 at 17:20
1

If I don't invoke keyboard-quit mid code above, advising keyboard-quit directly answers the question.

(defadvice keyboard-quit (before highlight-cycle-counter)
"reset counter for highlight-cycle"
     (progn
         (setq setnewpoint t)
         (setq cyclemarker 1)
      )
)

(ad-activate 'keyboard-quit)

This might not be the right answer to the problem, but it is sufficient for now.

1
  • 1
    There are only 3 highlight states to cycle among, plus an unhighlight state, AFAICT. Why bother fiddling with C-g - that only makes things more, not less, complex for users. Why not just let them cycle to the reset state? And if you really need a way for users to explicitly reset other than by cycling, let C-u do that. Advising keyboard-quit is not a great idea, IMHO.
    – Drew
    Nov 11, 2014 at 17:44

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.