unofficial mirror of help-gnu-emacs@gnu.org
 help / color / mirror / Atom feed
* How to write the "interactive" form for a command acting on a region
@ 2015-01-13 22:05 Marcin Borkowski
  2015-01-13 23:24 ` Nicolas Richard
  0 siblings, 1 reply; 7+ messages in thread
From: Marcin Borkowski @ 2015-01-13 22:05 UTC (permalink / raw)
  To: Help Gnu Emacs mailing list

Hi all,

so I want to have a function which should do something on the region.
If no region is active, I want it to act on the whole buffer.  If called
from Lisp code, I want to be able to supply "begin" and/or "end"
parameters, which (if nil) should default to (point-min) and
(point-max).  Finally, I want my command to behave differently depending
on whether it was called interactively or programmatically.  I did some
RTFMing, and after a few iterations I came up with this:

(defun my-function (&optional begin end print-message)
  "Do something clever on region or buffer."
  (interactive
   (if (use-region-p)
       (list (region-beginning) (region-end) t)
     (list (point-min) (point-max) t)))
  (save-excursion
    (save-restriction
      (narrow-to-region (or begin (point-min)) (or end (point-max)))
      (let ((result))
	(ding) ; do something clever here
	(if print-message
	    (message "Result: %s." result)
	  result)))))

I'm wondering whether it can be made better?

Regards,

-- 
Marcin Borkowski               This email was proudly sent
http://mbork.pl                from my Emacs.



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: How to write the "interactive" form for a command acting on a region
       [not found] <mailman.17860.1421186716.1147.help-gnu-emacs@gnu.org>
@ 2015-01-13 22:38 ` Pascal J. Bourguignon
  2015-01-13 23:06   ` Marcin Borkowski
       [not found]   ` <mailman.17861.1421190402.1147.help-gnu-emacs@gnu.org>
  0 siblings, 2 replies; 7+ messages in thread
From: Pascal J. Bourguignon @ 2015-01-13 22:38 UTC (permalink / raw)
  To: help-gnu-emacs

Marcin Borkowski <mbork@wmi.amu.edu.pl> writes:

> Hi all,
>
> so I want to have a function which should do something on the region.
> If no region is active, I want it to act on the whole buffer.  If called
> from Lisp code, I want to be able to supply "begin" and/or "end"
> parameters, which (if nil) should default to (point-min) and
> (point-max).  Finally, I want my command to behave differently depending
> on whether it was called interactively or programmatically.  


If you want a different behavior, then you should have different
functions:

    (defun my-FUNCTION (…)
       …)

    (defun my-COMMAND (…)
       (interactive …)
       …
       (my-function …)
       …)

(defun my-command (start end)
   (interactive "r")
   (message "start=%s end=%s" start end))


A region is always defined, whether transient-mark-mode is on or off,
and whether the region is active or not.

Therefore interactive "r" will always give you start and end points.
You could have a command such as:

    (defun my-command (start end)
       (interactive "r")
       (if (use-region-p) ; region is active
          (my-function start end)
          (my-function (point-min) (point-max))))


Otherwise, if the behavior of your command and your function was the
same, you could write a single command, using (require 'cl) to deal with
the default values.  

But since you want to force the arguments when it's called interactively
without an active region, you will have to duplicate some code.
Separating the function and command is probablyh preferable in your
situation.

    (require 'cl)
    (defun* my-command (&optional (start (point-min)) (end (point-max)))
       (interactive "r")
       (when (and (called-interactively-p)
                  (not (use-region-p)))
          (setf start (point-min)
                end   (point-max)))
       …)


-- 
__Pascal Bourguignon__                 http://www.informatimago.com/
“The factory of the future will have only two employees, a man and a
dog. The man will be there to feed the dog. The dog will be there to
keep the man from touching the equipment.” -- Carl Bass CEO Autodesk


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: How to write the "interactive" form for a command acting on a region
  2015-01-13 22:38 ` How to write the "interactive" form for a command acting on a region Pascal J. Bourguignon
@ 2015-01-13 23:06   ` Marcin Borkowski
       [not found]   ` <mailman.17861.1421190402.1147.help-gnu-emacs@gnu.org>
  1 sibling, 0 replies; 7+ messages in thread
From: Marcin Borkowski @ 2015-01-13 23:06 UTC (permalink / raw)
  To: help-gnu-emacs


On 2015-01-13, at 23:38, Pascal J. Bourguignon <pjb@informatimago.com> wrote:

> Marcin Borkowski <mbork@wmi.amu.edu.pl> writes:
>
>> Hi all,
>>
>> so I want to have a function which should do something on the region.
>> If no region is active, I want it to act on the whole buffer.  If called
>> from Lisp code, I want to be able to supply "begin" and/or "end"
>> parameters, which (if nil) should default to (point-min) and
>> (point-max).  Finally, I want my command to behave differently depending
>> on whether it was called interactively or programmatically.  
>
> If you want a different behavior, then you should have different
> functions:

Why?  In many Emacs functions/commands it works like what I want to
have.  What's wrong with this approach?  And in fact, I /don't/ want
different behavior: I want both the function and the command to
(essentially) do the same, with the (minor) difference that the function
will return a value and the command will print a message.

>     (defun my-FUNCTION (…)
>        …)
>
>     (defun my-COMMAND (…)
>        (interactive …)
>        …
>        (my-function …)
>        …)
>
> (defun my-command (start end)
>    (interactive "r")
>    (message "start=%s end=%s" start end))
>
> A region is always defined, whether transient-mark-mode is on or off,
> and whether the region is active or not.

Yes, of course.  (Incidentally, I didn't notice your snippet above at
first, and just to make sure, I wrote an identical one, differing only
in the names of the parameters and the text of the message;-).)

> Therefore interactive "r" will always give you start and end points.
> You could have a command such as:
>
>     (defun my-command (start end)
>        (interactive "r")
>        (if (use-region-p) ; region is active
>           (my-function start end)
>           (my-function (point-min) (point-max))))

This does not seem very lispy to me, though most probably have much less
experience than you...

> Otherwise, if the behavior of your command and your function was the
> same, you could write a single command, using (require 'cl) to deal with
> the default values.  

I'll have to check cl (I use it anyway for (incf)), but again: what's
wrong with (or start (point-min))?

> But since you want to force the arguments when it's called interactively
> without an active region, you will have to duplicate some code.

This I don't understand.  (Though I /do/ have some duplication, see
below.)

> Separating the function and command is probablyh preferable in your
> situation.
>
>     (require 'cl)
>     (defun* my-command (&optional (start (point-min)) (end (point-max)))
>        (interactive "r")
>        (when (and (called-interactively-p)
>                   (not (use-region-p)))
>           (setf start (point-min)
>                 end   (point-max)))
>        …)

No offence, but this seems plain ugly for me, especially the setf part.

IMHO, using the (interactive) form to define default arguments is more
elegant, though of course I also have some duplicate code (point-min and
point-max appear twice - though for different reasons, so to speak -
which I don't like).  I can't see why your proposal is better - I would
prefer to use defun and not defun*, and the Emacs manual says it's
better to use the interactive form and not called-interactively-p (and
I can see the reason).

Regards,

-- 
Marcin Borkowski
http://octd.wmi.amu.edu.pl/en/Marcin_Borkowski
Faculty of Mathematics and Computer Science
Adam Mickiewicz University



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: How to write the "interactive" form for a command acting on a region
  2015-01-13 22:05 Marcin Borkowski
@ 2015-01-13 23:24 ` Nicolas Richard
  2015-01-13 23:35   ` Marcin Borkowski
  0 siblings, 1 reply; 7+ messages in thread
From: Nicolas Richard @ 2015-01-13 23:24 UTC (permalink / raw)
  To: Marcin Borkowski; +Cc: Help Gnu Emacs mailing list

Hello,


Marcin Borkowski <mbork@wmi.amu.edu.pl> writes:
>    (if (use-region-p)
>        (list (region-beginning) (region-end) t)
>      (list (point-min) (point-max) t)))

You could do
(if (use-region-p)
    (list (region-beginning) (region-end) t)
  (list nil nil t))

(naive suggestion -- I have no experience !)

-- 
Nicolas Richard



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: How to write the "interactive" form for a command acting on a region
  2015-01-13 23:24 ` Nicolas Richard
@ 2015-01-13 23:35   ` Marcin Borkowski
  0 siblings, 0 replies; 7+ messages in thread
From: Marcin Borkowski @ 2015-01-13 23:35 UTC (permalink / raw)
  To: Help Gnu Emacs mailing list


On 2015-01-14, at 00:24, Nicolas Richard <theonewiththeevillook@yahoo.fr> wrote:

> Hello,
>
>
> Marcin Borkowski <mbork@wmi.amu.edu.pl> writes:
>>    (if (use-region-p)
>>        (list (region-beginning) (region-end) t) (list (point-min)
>>      (point-max) t)))
>
> You could do (if (use-region-p)
>     (list (region-beginning) (region-end) t) (list nil nil t))
>
> (naive suggestion -- I have no experience !)

Ah, this looks nice!  Seems a bit artificial, but defers the binding of
the default values to the one and only one place, and hence avoids
duplication.  Thanks, I like it!

Best,

-- 
Marcin Borkowski               This email was proudly sent
http://mbork.pl                from my Emacs.



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: How to write the "interactive" form for a command acting on a region
       [not found]   ` <mailman.17861.1421190402.1147.help-gnu-emacs@gnu.org>
@ 2015-01-14  2:04     ` Pascal J. Bourguignon
  2015-01-14  3:49       ` Drew Adams
  0 siblings, 1 reply; 7+ messages in thread
From: Pascal J. Bourguignon @ 2015-01-14  2:04 UTC (permalink / raw)
  To: help-gnu-emacs

Marcin Borkowski <mbork@wmi.amu.edu.pl> writes:

> On 2015-01-13, at 23:38, Pascal J. Bourguignon <pjb@informatimago.com> wrote:
>
>> If you want a different behavior, then you should have different
>> functions:
>
> Why?  In many Emacs functions/commands it works like what I want to
> have.  What's wrong with this approach?  And in fact, I /don't/ want
> different behavior: I want both the function and the command to
> (essentially) do the same, with the (minor) difference that the function
> will return a value and the command will print a message.

Indeed, the question is how you define _different_ behavior, and whether
you really have a different behavior or not.  

Usually you need just to be able to run the function interactively
easily. (You can always use M-: to call a function interactively, eg.:

        M-: (my-function (point-min) (point-max)) RET

if you don't have a command to do that.


 
>> Therefore interactive "r" will always give you start and end points.
>> You could have a command such as:
>>
>>     (defun my-command (start end)
>>        (interactive "r")
>>        (if (use-region-p) ; region is active
>>           (my-function start end)
>>           (my-function (point-min) (point-max))))
>
> This does not seem very lispy to me, though most probably have much less
> experience than you...

Notice how short this command is (it's always good to write short
functions and commands).

But more importantly, I feel that emacs lisp code would benefit in using
a more abstracted and layered approach.

The requirements of a functional API are not the same as of a user
interface command set.  

This is why you could want to define a set of data structures and
functions,  and later add a command layer calling those functions, with
variants such as in this my-command example.  We could assume that
my-function could be used also by other functions or other commands.

In any case, if the code of my-function was put inside my-command,
instead of being factored out, it would become much harder to use
my-command from other functions or commands (perhaps those other
commands or functions really want to work on the whole buffer even when
there's an active region).

When you see a command in emacs that has hundreds of lines of code (and
where the best "abstraction" they can do is:

    (defun bad-command (…)
       (interactive …)
       … hundreds of lines …
       (bad-command-1 …))

    (defun bad-command-1 (…)
       … hundreds of lines …
       (bad-command-2 …))

    (defun bad-command-2 (…)
       … hundreds of lines …)

and there are more than one such bad examples :-(, well, this is not
good at all.


>> Otherwise, if the behavior of your command and your function was the
>> same, you could write a single command, using (require 'cl) to deal with
>> the default values.  
>
> I'll have to check cl (I use it anyway for (incf)), but again: what's
> wrong with (or start (point-min))?

Nothing wrong.  Just that (interactive "r") will never pass nil for
start or end.



Notice that you can also just use (interactive) and (region-beginning) and
(region-end) inside the command.  But then one might worry about
commands that don't declare with interactive the parameters they use and
require from the user. If you can declare them with interactive, it's better.



>> But since you want to force the arguments when it's called interactively
>> without an active region, you will have to duplicate some code.
>
> This I don't understand.  (Though I /do/ have some duplication, see
> below.)

In this case, the duplication is minime, since it's only the
(setf start (point-min) end (point-max)) in the code, and hidden in the
lambda list.


>> Separating the function and command is probablyh preferable in your
>> situation.
>>
>>     (require 'cl)
>>     (defun* my-command (&optional (start (point-min)) (end (point-max)))
>>        (interactive "r")
>>        (when (and (called-interactively-p)
>>                   (not (use-region-p)))
>>           (setf start (point-min)
>>                 end   (point-max)))
>>        …)
>
> No offence, but this seems plain ugly for me, especially the setf part.

Absolutely.  This works better when you don't need this
called-interactive-p case.


> IMHO, using the (interactive) form to define default arguments is more
> elegant, though of course I also have some duplicate code (point-min and
> point-max appear twice - though for different reasons, so to speak -
> which I don't like).  I can't see why your proposal is better - I would
> prefer to use defun and not defun*, and the Emacs manual says it's
> better to use the interactive form and not called-interactively-p (and
> I can see the reason).

I hope we all do.

-- 
__Pascal Bourguignon__                 http://www.informatimago.com/
“The factory of the future will have only two employees, a man and a
dog. The man will be there to feed the dog. The dog will be there to
keep the man from touching the equipment.” -- Carl Bass CEO Autodesk


^ permalink raw reply	[flat|nested] 7+ messages in thread

* RE: How to write the "interactive" form for a command acting on a region
  2015-01-14  2:04     ` Pascal J. Bourguignon
@ 2015-01-14  3:49       ` Drew Adams
  0 siblings, 0 replies; 7+ messages in thread
From: Drew Adams @ 2015-01-14  3:49 UTC (permalink / raw)
  To: Pascal J. Bourguignon, help-gnu-emacs

> >>     (defun my-command (start end)
> >>        (interactive "r")
> >>        (if (use-region-p)
> >>            (my-function start end)
> >>          (my-function (point-min) (point-max))))
> 
> In any case, if the code of my-function was put inside my-command,
> instead of being factored out, it would become much harder to use
> my-command from other functions or commands (perhaps those other
> commands or functions really want to work on the whole buffer even
> when there's an active region).

It is more likely (typical) that the automatic choice (predefined
decision) to use (a) the region when it is active and nonempty,
versus (b) the current buffer limits otherwise (i.e., respecting
narrowing restrictions), is a behavior difference _only_ for
interactive use.

And in that (typical) case, the standard, simple approach is
to limit that behavior decision to the `interactive' spec.
Others have already shown the simple code used to do that.

In that way, the body of the command _is_ what you proposed
to "factor" out as a separate `my-function' for non-interactive
use.  Instead of having a command and a function, the typical
approach is to just put all of the "command" stuff in the
`interactive' spec and then use the one function, interactively
or not.

Your proposed dilemma ("it would become much harder...") then
evaporates.  Any non-interactive use of the command simply
specifies the limits to use, whether they correspond to the
region (active or not), the buffer limits, or anything else.

In sum, the typical approach in this common scenario is to
define a function that is useful both as a command and more
generally (from Lisp).

You could even say that this is a main raison d'etre for
the `interactive' spec: put all of the logic that pertains
only to interactive use in that one place, when possible.
That's the factoring that is usually done.

> The requirements of a functional API are not the same as
> of a user interface command set.

Yes, not necessarily the same, and not in general - true.

But sometimes (often) the interactive use of a function is
a simple specialization of its more general use.  When that
is the case it can make sense to factor out that difference
into an `interactive' spec.

---

In fact, there are probably more Emacs functions that it
wouldn't hurt to define as commands, IMHO.  I've been
surprised more than once to find that a function I intended
only for Lisp is useful bound to a key in some contexts.



^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2015-01-14  3:49 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <mailman.17860.1421186716.1147.help-gnu-emacs@gnu.org>
2015-01-13 22:38 ` How to write the "interactive" form for a command acting on a region Pascal J. Bourguignon
2015-01-13 23:06   ` Marcin Borkowski
     [not found]   ` <mailman.17861.1421190402.1147.help-gnu-emacs@gnu.org>
2015-01-14  2:04     ` Pascal J. Bourguignon
2015-01-14  3:49       ` Drew Adams
2015-01-13 22:05 Marcin Borkowski
2015-01-13 23:24 ` Nicolas Richard
2015-01-13 23:35   ` Marcin Borkowski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).