[Tex/LaTex] How to redefine \int to put a negative space after the integral sign, but keep the limits properly set

amsmathintegralkerningspacing

I'd like to modify the \int command such that it adds an automatic negative space, e.g. \! after the integral sign. Preferentially, I'd like to be able to not edit any of my code, so cleverly redefining \int seems like my best option. Unfortunately, naive implementations get the limits wrong:

\documentclass{article}
\usepackage[intlimits]{amsmath}
\usepackage{ifthen}

\newcommand{\diff}[2][]{
  \ifthenelse { \equal {#1} {} }
  {\ensuremath{\mathop{\mathrm{d} #2}}}
  {\ensuremath{\mathop{\mathrm{d}^#1 #2}}}
}

\begin{document}

This is what I would want:
\begin{equation}
 \int_0^\infty \mspace{-4mu}\diff[3]{\boldsymbol r} f(\boldsymbol r),
\end{equation}
but with this ``user code", containing no negative spacing explicitly:
\begin{verbatim}
 \int_0^\infty \diff[3]{\boldsymbol r} f(\boldsymbol r),
\end{verbatim}
which for comparison with the above gives:
\begin{equation}
\int_0^\infty \diff[3]{\boldsymbol r} f(\boldsymbol r),
\end{equation}

mostly because this gives me the option to remove it or change the spacing when I see fit.
\end{document}

Which results in:

Best Answer

enter image description here

With this code

\usepackage{etoolbox}

\newcommand*\diff[2][]{\mathop{\mathrm{d}\ifblank{#1}{}{^{#1}}{#2}}}
\let\originalint\int
\def\int_#1^#2{\originalint_{#1}^{#2}\mathopen{}}

you remove space after the \int but your code must be written always with \int_{..}^{..} with limits and in that specific order. You could write some trickery so that if the limits are blank they are not added to the code. Or you could do some trickery with \@ifnextchar so that one checks for both limits in any order and the result is more clean. If it's wanted, I can add that. Here's the code (thanks to @kyle_the_hacker):

\usepackage{etoolbox}
\makeatletter
\newcommand*\diff[2][]{\mathop{\mathrm{d}\ifblank{#1}{}{^{#1}}{#2}}}
\let\int@original\int
\def\int{\int@checkfirstsb}
\def\int@checkfirstsb{\@ifnextchar_{\int@checksecondsp}{\int@checkfirstsp}}
\def\int@checkfirstsp{\@ifnextchar^{\int@checksecondsb}{\int@{}{}}}
\def\int@checksecondsp_#1{\@ifnextchar^{\int@grabsp{#1}}{\int@{#1}{}}}
\def\int@checksecondsb^#1{\@ifnextchar_{\int@grabsb{#1}}{\int@{}{#1}}}
\def\int@grabsb#1_#2{\int@{#2}{#1}}
\def\int@grabsp#1^#2{\int@{#1}{#2}}
\def\int@#1#2{\int@original\ifblank{#1}{}{_{#1}}\ifblank{#2}{}{^{#2}}\mathopen{}}
\makeatother

In any case, here's an oportunity to add a bit of code I wrote some time ago to add new kind of arguments to xparse.

\documentclass{scrartcl}
\usepackage[intlimits]{amsmath}
\usepackage{etoolbox,xparse}

\ExplSyntaxOn
\cs_new_protected:Npn \__xparse_count_type_k:w #1
 {
  \__xparse_single_token_check:n { #1 }
  \quark_if_recursion_tail_stop_do:Nn #1 { \__xparse_bad_arg_spec:wn }
  \__xparse_count_mandatory:N
 }
\cs_new_protected:Npn \__xparse_count_type_K:w #1 #2
 {
  \__xparse_single_token_check:n { #1 }
  \quark_if_recursion_tail_stop_do:nn { #2 } { \__xparse_bad_arg_spec:wn }
  \__xparse_count_mandatory:N
 }
\cs_new_protected:Npn \__xparse_add_type_k:w #1
 { \exp_args:NNo \__xparse_add_type_K:w #1 { \c__xparse_no_value_tl } }
\cs_new_protected:Npn \__xparse_add_type_K:w #1 #2
 {
  \__xparse_flush_m_args:
  \__xparse_add_grabber_optional:N K
  \tl_put_right:Nn \l__xparse_signature_tl { #1 { #2 } }
  \__xparse_prepare_signature:N
 }
\cs_new_protected:Npn \__xparse_add_expandable_type_k:w #1
 {
  \exp_args:NNo \__xparse_add_expandable_type_K:w #1 { \c__xparse_no_value_tl }
 }
\cs_new_protected_nopar:Npn \__xparse_add_expandable_type_K:w #1 #2
 {
  \__msg_kernel_error:nnx { xparse } { invalid-expandable-argument-type } { K }
  \__xparse_add_expandable_type_m:w % May be create this?
 }
\cs_new_protected:Npn \__xparse_grab_K:w #1 #2 #3 \l__xparse_args_tl
 {
  \__xparse_grab_K_aux:NnnNn #1 { #2 } { #3 } \cs_set_protected_nopar:Npn
   { _ignore_spaces }
 }
\cs_new_protected:Npn \__xparse_grab_K_long:w #1 #2 #3 \l__xparse_args_tl
 {
  \__xparse_grab_K_aux:NnnNn #1 { #2 } { #3 } \cs_set_protected:Npn
   { _ignore_spaces }
 }
\cs_new_protected:Npn \__xparse_grab_K_trailing:w #1 #2 #3 \l__xparse_args_tl
 {
  \__xparse_grab_K_aux:NnnNn #1 { #2 } { #3 } \cs_set_protected_nopar:Npn
   { _ignore_spaces }
 }
\cs_new_protected:Npn \__xparse_grab_K_long_trailing:w #1 #2 #3 \l__xparse_args_tl
 {
  \__xparse_grab_K_aux:NnnNn #1 { #2 } { #3 } \cs_set_protected:Npn
   { _ignore_spaces }
 }
\cs_new_protected:Npn \__xparse_grab_K_aux:NnnNn #1 #2 #3 #4 #5
 {
  \exp_after:wN #4 \l__xparse_fn_tl ##1
   {
    \__xparse_add_arg:n { ##1 }
    #3 \l__xparse_args_tl
   }    
  \use:c { peek_meaning_remove #5 :NTF } #1
   { \l__xparse_fn_tl }
   {
    \__xparse_add_arg:n { #2 }
    #3 \l__xparse_args_tl
   }
 }

\prop_put:Nnn \c__xparse_shorthands_prop { a } { k \sb }
\prop_put:Nnn \c__xparse_shorthands_prop { b } { k \sp }
\prop_put:Nnn \c__xparse_shorthands_prop { A } { K \sb }
\prop_put:Nnn \c__xparse_shorthands_prop { B } { K \sp }

\NewDocumentCommand \diff { o m }
  {\mathop{\mathrm{d}\IfValueT{#1}{^{#1}}{#2}}}
\ExplSyntaxOff

\let\originalint\int
\RenewDocumentCommand \int { a b }
  {\originalint\IfValueT{#1}{_{#1}}\IfValueT{#2}{^{#2}}\mathopen{}}

\begin{document}

\begin{equation}
\int_0^\infty \diff[3]{\boldsymbol r} f(\boldsymbol r)
\end{equation}

\end{document}

And it may be even better to check if \diff follows and in case it doesn't just have the original behaviour, just change the definition to this (it's a pity that spaces aren't skipped, so we need to explicitly check for a space and then check for \diff after that)

\makeatletter
\RenewDocumentCommand \int { a b t\@sptoken t\diff }
  {\originalint\IfValueT{#1}{_{#1}}\IfValueT{#2}{^{#2}}%
   \IfBooleanT{#4}{\mathopen{}\diff}}
\makeatother

After a few thoughts

Completed rethink code that I would use for a complete flexibility. This adds all possible combinations of sub-superscripts for the \int plus the natural input for differentials \dd x, \dd{\bm x}, \dd^2 x, \dd^{a_1}{x_1}.

\usepackage{mathtools,bm}
\usepackage{etoolbox}
\makeatletter
\def\dd{\@ifnextchar^{\dd@grabsp}{\dd@{}}}
\def\dd@grabsp^#1#2{\dd@{#1}{#2}}
\def\dd@#1#2{\mathop{d\ifblank{#1}{}{^{#1}}{#2}}}
\let\int@original\int
\def\int{\int@checkfirstsb}
\def\int@checkfirstsb{\@ifnextchar_{\int@checksecondsp}{\int@checkfirstsp}}
\def\int@checkfirstsp{\@ifnextchar^{\int@checksecondsb}{\int@{}{}}}
\def\int@checksecondsp_#1{\@ifnextchar^{\int@grabsp{#1}}{\int@{#1}{}}}
\def\int@checksecondsb^#1{\@ifnextchar_{\int@grabsb{#1}}{\int@{}{#1}}}
\def\int@grabsb#1_#2{\int@{#2}{#1}}
\def\int@grabsp#1^#2{\int@{#1}{#2}}
\def\int@#1#2{\int@original\ifblank{#1}{}{_{#1}}\ifblank{#2}{}{^{#2}}\mathopen{}}
\makeatother

that with the xparse code from before it would become

\NewDocumentCommand\dd{bm}{\mathop{d\IfValueT{#1}{^{#1}}{#2}}}
\let\originalint\int
\RenewDocumentCommand\int{ab}{\originalint\IfValueT{#1}{_{#1}}\IfValueT{#2}{^{#2}}\mathopen{}}