36

I have a command like

\newcommand{\todo}[1]{#1}

which I sometimes replace by:

\newcommand{\todo}[1]{}

In terms of spacing/whitespace, the behavior of the "empty" command is as follows:

Test.

\todo{foo} % does not generate (vertical) whitespace => good

Test\todo{foo}. Test. % does not generate (horizontal) whitespace => good

Test\todo{foo}. \todo{foo} Test. % _does_ generate (horizontal) whitespace => bad

Is there any way to avoid this, i.e., to (temporarily) replace a command in a way so that it is fully equivalent to removing all occurrences of the command?

(I know that there are packages like todonotes or verbatim, which provide similar functionality. But I always wanted to know how to solve this manually, and never had an idea how to approach this / what to search for. Using \xspace or \empty in the command did not work at least.)

bluenote10
  • 1,479

3 Answers3

35

The classical way is using \@bsphack and \@esphack. They are used by \label or \index, both supposed to be invisible/empty regarding spacing. These commands remember, if there is a space before the command. If yes, the second space afterwards is suppressed by \ignorespaces. The commands are defined in the LaTeX kernel:

\def\@bsphack{%
  \relax
  \ifhmode
    \@savsk\lastskip
    \@savsf\spacefactor
  \fi}
\def\@esphack{%
  \relax
  \ifhmode
    \spacefactor\@savsf
    \ifdim\@savsk>\z@
      \ignorespaces
    \fi
  \fi}

Applied to \todo:

\documentclass{article}

\makeatletter
\newcommand{\todo}[1]{%
  \@bsphack
  \@esphack
}
\makeatother

\begin{document}
Test.

\todo{foo}

Test\todo{foo}. Test.

Test\todo{foo}. \todo{foo} Test.
\end{document}

Result

Limitation: The method can fail, if the macros using \@bsphack and \@esphack are used one after each other. The second \@bsphack does not know the state of the previous one. Therefore \ignorespaces can be suppressed leaving the following space.

Heiko Oberdiek
  • 271,626
  • Works like a charm, thanks! Apparently, the occurrence of a command will internally never be equivalent to the case of a "non-occurrence". So I guess the best solution here is to emulate the non-occurrence. – bluenote10 Sep 18 '14 at 12:39
16

It doesn't really generate space it's just that you have added two spaces, you would see same from a {} b with one space before and one after. But you can end your command with \ignorespaces to ignore the following space.

\newcommand{\todo}[1]{\ignorespaces}
David Carlisle
  • 757,742
  • This is looking good, but: In this case Test\todo{foo}.\todo{foo} Test. behaves "unexpected", since it is not the same as Test\todo{foo}. Test. -- any idea to solve this as well? – bluenote10 Sep 18 '14 at 12:15
  • 1
    @bluenote10 \ignorespaces is simpler, the more complicated thing is to see if there was a space before and just ignorespaces in that case) that's what the latex \@bspack macro does (it is used in \label{} and \index{] and similar commands that are supposed to be "invisible"). I thought I'd just do the simple one, but if you want the other, give the tick to Heiko:-) – David Carlisle Sep 18 '14 at 12:32
  • Thanks for the clarification! Considering the educational aspect of my question, your answer is highly appreciated :). – bluenote10 Sep 18 '14 at 12:35
6

You put nothing into the horizontal/vertical list when \toto is empty. Thus the \spacefactor manipulating is redundant (see the accepted answer). The following code is sufficient for this task:

\def\hideme{\ifdim\lastskip>0pt \ignorespaces\fi}
\def\todo#1{#1}
% or:
\def\todo#1{\hideme}
wipet
  • 74,238