如何自动跳过列表中的前导空格

如何自动跳过列表中的前导空格

这个问题导致了一个新的方案的出现:
lstautogobblelstaddons捆)

对于我的编程讲座幻灯片,我大量使用带有listings包的\lstnewenvironment源代码排版。这是一个很棒的包(不,我确实不是想要切换到minted),唯一令我烦恼的是,我必须传递gobble=<indent>选项才能获得格式良好的源以及输出:

\documentclass{beamer}
\usepackage{listings}
\lstloadlanguages{C}

\lstnewenvironment{C}[1][] {\lstset{language=C, basicstyle=\scriptsize\ttfamily, #1}}{} 

\begin{document}

\begin{frame}[fragile]{A simple truth\ldots}
  \begin{C}[gobble=4] % <-- ** This is annoying! **
    #include <stdio.h>
    int main(){
      printf("tex.stackexchange.com: the coolest community ever!\n");
    }
  \end{C}
\end{frame}

\end{document}

gobble如果移动代码示例并更改嵌套级别,手动处理尤其痛苦。在这种情况下,我经常忘记更新参数。理想情况下,C上述示例中的环境将gobble根据源代码中它之前的缩进(即空格数)自动确定。类似于以下内容(伪代码):

\lstnewenvironment{C}[1][] {\PreceedingWS{\WScount}%
  \lstset{language=C, basicstyle=\scriptsize\ttfamily, 
  gobble=\the\numexpr\WScount+2\relax, #1}}{} 

这个想法是\PreceedingWS神奇地确定当前行之前的空格数\begin{C}并将其存储在某个宏中(\WScount)。

这可能吗?


结果摘要(2011-06-27)

我得到了三个答案,其中两个确实对我有帮助:

  • Aditya 的解决方案基于 ConTeXt,这对我没有帮助,但对实际使用 ConTeXt 的人来说可能是一个很好的参考。
  • 布鲁诺的解决方案有点 hacky,但优点是它可以应用于(可能)任何逐字格式的环境。此外,我从他有据可查的回答中学到了一些关于 TeX 扫描的知识。它来得正是时候,让我节省了大量的工作。
  • 马丁的解决方案出现得相对较晚。它更简洁,因为它直接集成到listings包中,并作为自己的包提供。我很惊讶他为此需要的代码如此之少,对我来说,这表明他的解决方案集成得很好,很有可能在listings包的进一步发展中存活下来。我当然希望他的lstautogobble包能进入 CTAN 或最终直接集成到listings

我已经用我的讲座幻灯片测试了后两种方法,其中包含大约 100 个小列表,它们处于非常不同的设置和环境中(表格内、小页面、与投影仪覆盖相结合等),所以我可以说这两种方法确实有效。

总结:Bruno 的答案被接受,因为它在我真正需要的时候帮助了我最多。Martin 的答案获得了奖励,因为它是更干净、更稳定的解决方案,我将在未来的任何项目中使用它。

答案1

不同的方法,因此答案不同。捕获环境主体(不是以一种非常强大的方式,但我认为您不会嵌套这些环境),并计算前导空格的数量。使用 and 有点棘手\endlinechar\newlinechar唉,如果某些行的缩进量小于第一行,它们将被修剪。

\documentclass{article}

\usepackage{listings}
\lstloadlanguages{C}
\lstnewenvironment{CCCC}[1][]
  {\lstset{language=C, basicstyle=\scriptsize\ttfamily, #1}}{}
\makeatletter
% ===== Helper.
% Use some tokens after fully expanding them: for this, redefine
% \use@x within a group, and use it outside using \expandafter.
\newcommand{\use@x}[1]
  {\begingroup\edef\use@x{#1}\expandafter\endgroup\use@x}

% ===== Environment.
% Give "special" characters the catcode "other", and tell TeX
% to insert the "newline" character at the end of every line
% (this "newline" character causes TeX to produce a new line
% when writing to a file (done in \scantokens later)).
\newenvironment{C}
  {%
    \let\do\@makeother\dospecials
    \endlinechar\newlinechar
    \LS@hack@i
  }{}
% Look for an optional argument. 
\newcommand{\LS@hack@i}[1][]{\LS@hack@ii{#1}}
% Expand...
\use@x{%
  % Roughly \def\LS@hack@ii#1#2\end{C}^^J{\LS@hack@iii{#2}{#1}}
  % except that some pieces of \end{C} are converted to strings,
  % and one space is removed from #2.
  \unexpanded{\def\LS@hack@ii#1#2}%
    \expandafter\@gobble\string\\end\string{C\string}^^J%
    \unexpanded{{\expandafter\LS@hack@iii\expandafter{\romannumeral-`0#2}{#1}}}%
  %
  % Essentially,
  % \def\LS@hack@iii{\end{C}
  %   \begin{CCCC}[gobble=numberofspaces]##1\end{CCCC}}
  % Restore catcodes of special characters and reread,
  % with \scantokens, the body of the environment, feeding
  % it to the listing CCCC environment. Note that the user-
  % provided optional argument to begin{C} is transmitted
  % to begin{CCCC} (not tested). All the \noexpand are there
  % to prevent the full expansion of \use@x, necessary
  % for \string\begin and \string\end.
  \noexpand\newcommand{\noexpand\LS@hack@iii}[2]
    {\noexpand\end{C}%
      \noexpand\LS@hack@countspaces{##1}%
      \noexpand\scantokens{%
        \string\begin{CCCC}[gobble=\noexpand\value{LS@hack@spacecount},##2]^^J%
          ##1%
        \string\end{CCCC}}}}

\newcounter{LS@hack@spacecount}
\newcommand{\LS@hack@countspaces@}[1]{%
  \if#1 \else
    \expandafter\remove@to@nnil
  \fi
  +1\LS@hack@countspaces@}
\newcommand{\LS@hack@countspaces}[1]{%
  \setcounter{LS@hack@spacecount}
    {\the\numexpr0\LS@hack@countspaces@#1\@nnil\relax}}


\begin{document}
  A piece of code:
  \begin{C}
    #include <stdio.h>
    int main(){
      printf("tex.stackexchange.com: the coolest community ever!\n");
    }
  \end{C}
  and the same with no gobble:
  \begin{CCCC}
    #include <stdio.h>
    int main(){
      printf("tex.stackexchange.com: the coolest community ever!\n");
    }
  \end{CCCC}
\end{document}

答案2

我认为这种功能应该使用listings其内部结构来实现,例如通过使用相同的 catcode 等,以最大限度地提高与环境正常操作的兼容性。

listings包使用内部宏\lstenv@Process来处理lstlisting和 定义的其他列表环境\lstnewenvironment。此宏在处理环境参数后使用,可以本地重新定义为计算第一个换行符(^^M, 之后的换行符\begin{lstlisting}[options])后的空格(然后激活!),然后用正确的数字设置gobble选项并调用原始\lstenv@Process。该宏实际上递归调用自身,因此还必须恢复其原始定义。

现在,我的解决方案以包的形式呈现。它将上述功能添加为布尔选项autogobble,可以像任何其他选项一样使用,所有由 定义的所有列表环境listings(包括自定义环境)。下面还有一个测试文件来展示其正确功能。

MWE(基于原始 MWE):

\documentclass{beamer}
\usepackage{listings}
\usepackage{lstautogobble}
\lstloadlanguages{C}

\lstnewenvironment{C}[1][] {\lstset{language=C, basicstyle=\scriptsize\ttfamily, autogobble, #1}}{} 

\begin{document}

\begin{frame}[fragile]{A simple truth\ldots}
  \noindent Normal text as reference
  \begin{C}
    #include <stdio.h>
    int main(){
      printf("tex.stackexchange.com: the coolest community ever!\n");
    }
  \end{C}
\end{frame}

\end{document}

包裹:

(v1.1 2012/02/04:现在支持制表符)

% LaTeX Package `lstautogobble`
% Counts the leading spaces of the first line and sets `gobble` to this number
% Copyright (c) 2011 by Martin Scharrer <[email protected]>
% for http://tex.stackexchange.com/questions/19953/how-to-automatically-skip-leading-white-spaces-in-listings
% This is free software under the LPPL v1.3c or later and was also posted under the CC BY-SA 3.0 license.
\ProvidesPackage{lstautogobble}[2012/02/04 v1.1 Implements 'autogobble' option for 'listings']

% This is an add-on to the `listings` package
\RequirePackage{listings}

% Counter for leading spaces
\newcount\lstag@spacecount

% Some macros for comparison:
\def\lstag@activespace{\lst@ProcessSpace}%  Definition of an active space
\def\lstag@tabulator{\lst@ProcessTabulator}%  Definition of an tabulator

\begingroup
\catcode`\^^M=\active%
\gdef\lstag@activenl{^^M}%  Active CR (ASCII 13) character which is used as line break
\endgroup


% Define `autogobble` option as boolean (by default off)
\lst@Key{autogobble}{false}[t]{\lstKV@SetIf{#1}\lst@ifautogobble}

% `ungobble` option
\lst@Key{ungobble}{0}{\def\lst@ungobble{#1}}

% Insert required code at environment init
\lst@AddToHook{Init}{\lst@autogobble}

% Autogobble init macro.
% If the option is active and `gobble` is not set, init vars and overwrite the process macro with own definition.
\def\lst@autogobble{%
    \lst@ifautogobble
        \ifnum\lst@gobble>0\else
            \def\lst@gobble{\lstag@gobble}%
            \def\lstag@gobble{0}%
            \lstag@spacecount\z@
            \def\lstag@spaceaccu{}%
            \let\lstag@restofline\empty
            \let\lstag@origlstenv@Process\lstenv@Process
            \let\lstenv@Process\lstag@countleadingspaces
        \fi
    \fi
}

% Checks if the next following character (read as argument) is a line break (as it is supposed to be)
% Otherwise there is some text direct after the `\begin{<env>}[<options>]` which is dropped by `listings`.
\def\lstag@countleadingspaces#1{%
    \expandafter\ifx\lstag@activenl#1\relax
        \expandafter\lstag@countleadingspaces@
    \else
        \def\lstag@restofline{Dummy replacement of text after begin of listing to trigger original warning message}%
        \expandafter\lstag@countleadingspaces
    \fi
}

% After the new line is found this macro counts the spaces and tabulators
\def\lstag@countleadingspaces@#1{%
    \ifx\lstag@activespace#1\relax
        \advance\lstag@spacecount by \@ne
        % Accumulate spaces (i.e. their definitions) for later re-insertion:
        \expandafter\def\expandafter\lstag@spaceaccu\expandafter{\lstag@spaceaccu\lst@ProcessSpace}%
        \let\next\lstag@countleadingspaces@
    \else% Character wasn't a space
    \ifx\lstag@tabulator#1\relax
        \advance\lstag@spacecount by \lst@tabsize\relax
        % Accumulate spaces (i.e. their definitions) for later re-insertion:
        \@tempcnta=\lst@tabsize\relax
        \loop
        \ifnum\@tempcnta>\z@
            \expandafter\def\expandafter\lstag@spaceaccu\expandafter{\lstag@spaceaccu\lst@ProcessSpace}%
            \advance\@tempcnta\m@ne
        \repeat
        \let\next\lstag@countleadingspaces@
    \else% Character wasn't a tabulator either
        % Set gobble option (indirect):
        \xdef\lstag@gobble{\the\numexpr\lstag@spacecount-\lst@ungobble\relax}%
        % Restore original definition of process macro:
        \global\let\lstenv@Process\lstag@origlstenv@Process
        % Re-insert all collected material or appropriate replacement material:
        \edef\next{\noexpand\lstenv@Process\lstag@restofline\expandafter\noexpand\lstag@activenl\expandafter\unexpanded\expandafter{\lstag@spaceaccu}\noexpand#1}%
    \fi\fi
    \next
}

\endinput

测试文件:

\documentclass{article}

\usepackage{listings}
\usepackage{lstautogobble}


\lstnewenvironment{test}[1][]{%
    \lstset{basicstyle=\ttfamily,autogobble=true,#1}%
}{}

\parindent=0pt% for test only
\begin{document}

Normal text as reference

\hrule

    Only environment
    \begin{test}
        test
        it
    \end{test}

    With options
    \begin{test}[basicstyle=\ttfamily\scriptsize]
        test
        it
    \end{test}

    Manual gobble option (override)
    \begin{test}[gobble=7]
        test
        it
    \end{test}

    Autogobble off:
    \begin{test}[autogobble=false]
        test
        it
    \end{test}

    With some material on the same line as \texttt{\string\begin} (dropped by listings. The warning message got preserved)
    \begin{test} some text at the first line
        test
        it
    \end{test}

    dito with optional argument
    \begin{test}[] some text at the first line
        test
        it
    \end{test}

    Different indention levels:
\begin{test}
    test
    it
\end{test}

        \begin{test}
            test
            it
        \end{test}

        \begin{test}
    test
    it
        \end{test}

            \begin{test}
                            test
                            it
            \end{test}

    \begin{test}
                    test
                    it
    \end{test}


\hrule

  Some real C Code
  \begin{lstlisting}[autogobble]
    #include <stdio.h>
    int main(){
      printf("tex.stackexchange.com: the coolest community ever!\n");
    }
  \end{lstlisting}

\end{document}

检测结果:

结果

答案3

在 LuaTeX 中这相对简单。ConTeXt 维基有这个确切的问题作为 luatex 编程的示例。解决方案由两部分组成:

  • 捕获环境内容:在 ConTeXt 中,这是使用缓冲区完成的。我认为许多 LaTeX verbatim 包中的一个提供了类似的功能。

  • 删除内容中的前导空格:这在 Lua 中很容易。解决方案的复杂性取决于您希望实现的健壮程度(如果源代码格式不正确,即后面几行的空格比第一行少,会发生什么情况)。以下是去齿状来自 ConTeXt wiki 的函数


\startluacode
  -- Initialize a userdata name space to keep our own functions in.
  -- That way, we won't interfere with anything ConTeXt keeps in 
  -- the global name space.
  userdata = userdata or {}

  function userdata.dedentedtyping(content)
    local lines    = string.splitlines(content)
    local indent   = string.match(lines[1], '^ +') or ''
    local pattern  = '^' .. indent
    for i=1,#lines do
      lines[i] = string.gsub(lines[i],pattern,"")
    end

    content = table.concat(lines,'\n')

    tex.sprint("\\starttyping\n" .. content .. "\\stoptyping\n")

  end
\stopluacode

\starttyping对于列表,您可以将...\stoptyping传递回 TeX,而不是\begin{listing}...\end{listing}

答案4

我作弊了:使用 beamer 生成的文件 ( \jobname.vrb) 来表示脆弱帧,并计算一些额外命令 ( \setgobble) 和后面的之间的空格\begin。这限制为\setgobble每帧一个,并且最多吞噬 8 个(或 10 个?)的值。

我认为最好以某种方式计算列表环境中的空间,并将其输入放回到其预期的状态。

\documentclass{beamer}
\usepackage{listings}
\lstloadlanguages{C}

\lstnewenvironment{C}[1][] {\lstset{language=C, basicstyle=\scriptsize\ttfamily, #1}}{} 


\makeatletter
\newcount\gobblecount
\newcommand{\setgobble}
  {%
    \begingroup
    \everyeof{\@nnil}%
    \obeyspaces
    \expandafter\setgobble@aux\@@input\jobname.vrb%
    \endgroup
  }
\newcommand\@ninthofnine[9]{#9}
\def\setgobble@aux#1\setgobble#2\begin{%
    \global\gobblecount
      \expandafter\@car\@ninthofnine #2876543210\@nil\relax
    \global\advance\gobblecount\tw@
    \remove@to@nnil
  }
\makeatother

\begin{document}

\begin{frame}[fragile]{A simple truth\ldots}
  \setgobble
  \begin{C}[gobble=\the\gobblecount] % <-- ** This is annoying! **
    #include <stdio.h>
    int main(){
      printf("tex.stackexchange.com: the coolest community ever!\n");
    }
  \end{C}
\end{frame}

\end{document}

相关内容