我用pythontex
它来排版我的 Python 列表。我希望有一些 L 形线条,就像我在下图中添加的线条一样,以清楚地显示每行代码的缩进级别。
有任何想法吗?
\documentclass[varwidth,margin = 1mm]{standalone}
\usepackage{pythontex}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\begin{document}
\begin{pygments}[frame=single]{python}
def f(n):
if n == 1:
return 1
else:
return f(n-1)
print(f(4))
\end{pygments}
\end{document}
答案1
感谢您提出非常有趣的问题。
您可以使用以下宏:
\documentclass[varwidth,margin = 1mm]{standalone}
\usepackage{pythontex}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\def\makeL{\vbox\bgroup \global\linenum=0
\let\FancyVerbFormatLine=\Lformat \let\next=}
\def\sxdef#1{\expandafter\xdef\csname#1\endcsname}
\newcount\spacenum \newcount\linenum \newcount\linenumA \newcount\tmpnum
\def\Lformat#1{\global\spacenum=0
\ifx\end#1\end\else \LformatA#1\end\fi
\global\advance\linenum by1
\sxdef{L:\the\spacenum}{\the\linenum}%
\tmpnum=\spacenum \def\Llist{}\let\Ldraw=\relax
\loop
\advance\tmpnum by-1
\ifnum\tmpnum>-1
\expandafter\ifx\csname L:\the\tmpnum\endcsname \relax \else
\advance\spacenum by-\tmpnum
\linenumA=\linenum
\advance\linenumA by-\csname L:\the\tmpnum\endcsname
\edef\Llist{\Ldraw{\the\spacenum}{\the\linenumA}\Llist}%
\spacenum=\tmpnum
\fi
\repeat
\let\Ldraw=\LdrawX
\csname FV@ObeyTabs\endcsname{\rlap{\Llist}#1}}
\def\LformatA#1{%
\ifx#1\fspace \global\advance\spacenum by1 \let\next=\LformatA
\else \let\next=\formatlineB \fi
\ifx#1\end \let\next=\relax \fi
\next
}
\def\formatlineB#1\end{}
\expandafter\def\expandafter\fspace\expandafter{\csname FV@Space\endcsname}
\def\LdrawX#1#2{%
\dimen0=.5em \dimen0=#1\dimen0 \advance\dimen0 by-.4em
\dimen1=\normalbaselineskip \dimen1=#2\dimen1 \advance\dimen1 by-1ex
\message{(#1,#2)=(\the\dimen0,\the\dimen1,\the\normalbaselineskip)}%
\pdfliteral{q 1 g .5 G .9963 0 0 .9963 2 2 cm
0 0 \ptdim0 \ptdim1 re f \ptdim0 0 m 0 0 l 0 \ptdim1 l S Q}%
\dimen0=.5em \kern#1\dimen0
}
{\lccode`\?=`\p \lccode`\!=`\t \lowercase{\gdef\ignorept#1?!{#1}}}
\def\ptdim#1{\expandafter\ignorept\the\dimen#1 }
\begin{document}
\makeL{
\begin{pygments}[frame=single]{python}
class enum:
def __init__(self,level=1):
self.content=list()
self.level=level
def __repr__(self):
ret = "[["
for x in self.parse():
ret += repr(x)
return ret+"]]"
def parse(self):
itemcounter = 0
subbullets=enum(self.level+1)
parsed=list()
for item in self.content:
itemcounter += 1
try:
if item[0][0] in [PENITEM, ENITEM]:
except IndexError:
self.fail("foobar")
#ende
\end{pygments}
}
\end{document}
这意味着你必须写:
\makeL{
\begin{pygments}[frame=single]{python}
... your code
\end{pygments}
}
您将获得:
当然,pygments
环境需要在TeXing之后运行pythontex example
才能进行语法高亮。
解释宏将环境用于逐字输出的\makeL
重新定义为。该宏计算行数并计算行前面的空格数,并将其保存为 宏,内容为。然后将当前行的 L 符号列表存储到宏中。每个 L 符号以 的形式表示,其中是 L 的宽度(列数),是 L 的高度(行数)。最后,使用 处理 L 符号(即 )。\FancyVerbFormatLine
pygments
\Lformat
\L:num-spaces
line-number
\Llist
\Ldraw{right}{up}
right
up
\Ldraw
\pdfiteral
限制:此宏要求代码中间没有分页符。但熟练的宏程序员可以添加此功能作为练习。
依赖项:您不需要任何附加包。您可以通过pdflatex
或lualatex
或 来处理宏xelatex
。当xelatex
使用 时,您必须定义:
\def\pdfliteral#1{\special{pdf:literal #1}}
编辑下面我的宏的第二个版本(在这篇文章的末尾)简化了输入并允许在代码中间分页。
Verbatim
可以在(来自 fancyvrb)或(来自 pythontex)环境中正常准备输入pygments
。如果存在新的键值对,indent=L
则计算并打印 L 缩进,否则这些环境正常工作。示例:
\begin{pygments}[frame=single, indent=L]{python}
... your code
\end{pygments}
或者
\begim{Verbatim}[frame=single, indent=L]
... your code
\end{pygments}
为了允许分页,我们必须将较大的L
(超过多行)分解为一个或多个I
以小的结尾的 s L
:
XXXXXXXX
I XXXXXX
I I XXXX
I L XXXX
L XXXXXX
然后我们可以分别打印每行中的 Is 和 Ls,代码可以拆分成更多行。只有一个小问题:打印当前行时我们必须知道下一行的缩进,以便决定是否必须打印 L 或 I。幸运的是,将fancyverb.sty
当前行保留在框中,并在读取下一行后打印此框。因此,我们可以计算下一行的缩进并打印上一行与 Ls 或 Is 重叠的框。这项工作由\Lbox
宏完成(而不是宏\box
中的原始工作fancyvrb.sty
)。并\Lbox
使用从上一行计算得出的,其中是上面描述的宏\Llist
列表。宏通过以下方式简单地绘制 L 或 I:\Ldraw
\Ldraw
\def\Ldraw{\ifnum\tmpnum<\spacenum \expandafter\LdrawI \else \expandafter\LdrawL\fi}
其中\tmpnum
包括当前列号和\spacenum
下一行的空格数。
示例如下。我的宏必须包含在内后加载pythontex.sty
或fancyvrb.sty
因为一些内部宏被fancyvrb.sty
重新定义。
\documentclass{article}
\usepackage{pythontex}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\def\sxdef#1{\expandafter\xdef\csname#1\endcsname}
\def\sdef#1{\expandafter\def\csname#1\endcsname}
{\lccode`\?=`\p \lccode`\!=`\t \lowercase{\gdef\ignorept#1?!{#1}}}
\def\ptdim#1{\expandafter\ignorept\the\dimen#1 }
\newcount\spacenum \newcount\linenum \newcount\linenumA \newcount\tmpnum
\def\Lcountspaces#1{\spacenum=0 \ifx\end#1\end\else \LcountspacesA#1\end\fi}
\def\LcountspacesA#1{%
\ifx#1\fspace \advance\spacenum by1 \let\next=\LcountspacesA
\else \let\next=\LcountspacesB \fi
\ifx#1\end \let\next=\relax \fi
\next
}
\def\LcountspacesB#1\end{}
\expandafter\def\expandafter\fspace\expandafter{\csname FV@Space\endcsname}
\def\Lbegin{\global\linenum=0 \gdef\Llist{}}
\def\Lformat#1{%
\global\advance\linenum by1
\sxdef{L:\the\spacenum}{\the\linenum}%
\tmpnum=\spacenum \gdef\Llist{}\let\Ldraw=\relax
\loop
\advance\tmpnum by-1
\ifnum\tmpnum>-1
\expandafter\ifx\csname L:\the\tmpnum\endcsname \relax \else
\advance\spacenum by-\tmpnum
\linenumA=\linenum
\advance\linenumA by-\csname L:\the\tmpnum\endcsname
\xdef\Llist{\Ldraw{\the\spacenum}{\the\linenumA}\Llist}%
\spacenum=\tmpnum
\fi
\repeat
\csname FV@ObeyTabs\endcsname{#1}}
\def\Lbox#1{\hbox to\hsize{\rlap{\box#1}%
\kern\csname @totalleftmargin\endcsname \kern\csname FV@XLeftMargin\endcsname
\setbox0=\hbox{\csname FV@LeftListFrame\endcsname}\kern\wd0
\tmpnum=0 \Llist\hss}}
\def\Ldraw{\ifnum\tmpnum<\spacenum \expandafter\LdrawI \else \expandafter\LdrawL\fi}
\def\LdrawL#1#2{%
\dimen0=.5em \dimen0=#1\dimen0 \advance\dimen0 by-.4em
\dimen1=1.4ex
\dimen2=.5ex
\pdfliteral{q \Lshape\space .9963 0 0 .9963 \ptdim2 \ptdim2 cm
\ptdim0 0 m 0 0 l 0 \ptdim1 l S Q}%
\LdrawE{#1}}
\def\LdrawI#1#2{%
\dimen0=-.85ex
\dimen1=\normalbaselineskip
\dimen2=.5ex
\pdfliteral{q \Lshape\space .9963 0 0 .9963 \ptdim2 \ptdim0 cm
0 0 m 0 \ptdim1 l S Q}%
\LdrawE{#1}}
\def\LdrawE#1{\dimen0=.5em \kern#1\dimen0 \advance\tmpnum by#1 }
\csname define@key\endcsname{FV}{indent}{\csname FV@indent@#1\endcsname}
\sdef{FV@indent@L}{\let\FancyVerbFormatLine=\Lformat}
\def\Lshape{1 g .5 G 1 w 1 j 1 J} % .5 gray 1bp width rounded ends, rounded corner
%%%%%%% re-definition of internal macros from fancyvrb.sty
{\makeatletter
\gdef\FV@ListProcessLine@i#1{\Lcountspaces{#1}% <--- added by P.O.
\hbox{%
\ifvoid\@labels\else
\hbox to \z@{\kern\@totalleftmargin\box\@labels\hss}%
\fi
\FV@ListProcessLine{#1}}%
\let\FV@ProcessLine\FV@ListProcessLine@ii}
\gdef\FV@ListProcessLine@ii#1{\Lcountspaces{#1}% <--- added by P.O.
\setbox\@tempboxa=\FV@ListProcessLine{#1}%
\let\FV@ProcessLine\FV@ListProcessLine@iii}
\gdef\FV@ListProcessLine@iii#1{\Lcountspaces{#1}% <--- added by P.O.
{\advance\interlinepenalty\clubpenalty\penalty\interlinepenalty}%
\Lbox\@tempboxa % <--- Lbox by P.O.
\setbox\@tempboxa=\FV@ListProcessLine{#1}%
\let\FV@ProcessLine\FV@ListProcessLine@iv}
\gdef\FV@ListProcessLine@iv#1{\Lcountspaces{#1}% <--- added by P.O.
\penalty\interlinepenalty
\Lbox\@tempboxa % <--- Lbox by P.O.
\setbox\@tempboxa=\FV@ListProcessLine{#1}}%
\gdef\FV@ListProcessLastLine{\spacenum=0 % % <--- added by P.O.
\ifx\FV@ProcessLine\FV@ListProcessLine@iv
{\advance\interlinepenalty\widowpenalty\penalty\interlinepenalty}%
\Lbox\@tempboxa % <--- Lbox by P.O.
\else
\ifx\FV@ProcessLine\FV@ListProcessLine@iii
{\advance\interlinepenalty\widowpenalty
\advance\interlinepenalty\clubpenalty
\penalty\interlinepenalty}%
\Lbox\@tempboxa % <--- Lbox by P.O.
\else
\ifx\FV@ProcessLine\FV@ListProcessLine@i
\FV@Error{Empty verbatim environment}{}%
\FV@ProcessLine{}%
\fi
\fi
\fi}
\gdef\FVB@Verbatim{\Lbegin\FV@VerbatimBegin\FV@Scan} % <--- Lbegin added by P.O.
\gdef\@begin@pygments@hook{\Lbegin}
}
%%%%%%%%%%% end of re-definition
\begin{document}
Testing:
\begin{pygments}[frame=single, indent=L]{python}
def f(n):
if n == 1:
return 1
else:
return f(n-1)
print(f(4))
\end{pygments}
Next code:\vskip10cm
\begin{pygments}[frame=single,indent=L]{python}
class enum:
def __init__(self,level=1):
self.content=list()
self.level=level
def __repr__(self):
ret = "[["
for x in self.parse():
ret += repr(x)
return ret+"]]"
def parse(self):
itemcounter = 0
subbullets=enum(self.level+1)
parsed=list()
for item in self.content:
itemcounter += 1
try:
if item[0][0] in [PENITEM, ENITEM]:
except IndexError:
self.fail("foobar")
\end{pygments}
\end{document}
由于\Lshape
宏(灰色,圆角),结果的细节如下所示:
答案2
我知道我在这里做的是作弊。我也知道这远非最终解决方案,因为目前它仍然需要将代码输入到文件中两次。但这可以通过例如始终从外部文件加载代码来简化。将其视为以不同方式解决问题的概念/建议。
这段代码基本上是用“|_”替换每个前导双空格。当下一行打印“|_”时,上一行的“_”部分将被删除(覆盖)。最后,pygments
这可能不是最优雅或最复杂的方法,但它可以为你省去很多麻烦。当然这不是最终的实现结果如下所示
代码:
\documentclass{article}
\usepackage{luacode,luatexbase}
\usepackage{pythontex}
\usepackage{tikz}
%% Lua-side code
%this goes through the lines of the code and reads how many "doubles spaces are leading each line
\begin{luacode}
function readindent ( line )
if not string.find(line, "drawindent") then
indent = math.floor(string.len(string.match(line,"^%s*") or "") / 2)
line = "~\\\\"..string.rep("\\pycodehook",indent)..""
end
return ( line )
end
\end{luacode}
%% TeX-side code
\newcommand\ignore[1]{(#1)}
\newenvironment{drawindent}{%
\directlua{luatexbase.add_to_callback("process_input_buffer",readindent, "readindent")}}{%
\directlua{luatexbase.remove_from_callback("process_input_buffer","readindent")}}
\newcommand*\pycodehook{\rlap{\smash{~\tikz{ %this draws a hook in each and every code line and erases hooks from the lines above
\fill[fill=gray] (-0.24pt,0pt) rectangle (1ex,1\baselineskip);
\fill[fill=white] (0.24pt,.5pt) rectangle (1ex,1.5\baselineskip);
}}}\phantom{\ttfamily{~~}}}
\begin{document}\noindent
\begin{minipage}[t][0pt][t]{0pt}%
\begin{drawindent}
class enum:
def __init__(self,level=1):
self.content=list()
self.level=level
def __repr__(self):
ret = "[["
for x in self.parse():
ret += repr(x)
return ret+"]]"
def parse(self):
itemcounter = 0
subbullets=enum(self.level+1)
parsed=list()
for item in self.content:
itemcounter += 1
try:
if item[0][0] in [PENITEM, ENITEM]:
except IndexError:
self.fail("foobar")
#ende
}
\end{drawindent}
\end{minipage}%
\begin{minipage}[t]{\columnwidth}%
\begin{pygments}[frame=single]{python}
class enum:
def __init__(self,level=1):
self.content=list()
self.level=level
def __repr__(self):
ret = "[["
for x in self.parse():
ret += repr(x)
return ret+"]]"
def parse(self):
itemcounter = 0
subbullets=enum(self.level+1)
parsed=list()
for item in self.content:
itemcounter += 1
try:
if item[0][0] in [PENITEM, ENITEM]:
except IndexError:
self.fail("foobar")
#ende
\end{pygments}
\end{minipage}
\end{document}
为了完整性:部分代码取自这里:将环境内容视为字符串,替换子字符串并进行解释