这个问题导致了一个新的方案的出现:
lstautogobble
(lstaddons
捆)
对于我的编程讲座幻灯片,我大量使用带有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}