显示 Python 代码的良好缩进

显示 Python 代码的良好缩进

我用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}
}

您将获得:

L 示例

当然,pygments环境需要在TeXing之后运行pythontex example才能进行语法高亮。

解释宏将环境用于逐字输出的\makeL重新定义为。该宏计算行数并计算行前面的空格数,并将其保存为 宏,内容为。然后将当前行的 L 符号列表存储到宏中。每个 L 符号以 的形式表示,其中是 L 的宽度(列数),是 L 的高度(行数)。最后,使用 处理 L 符号(即 )。\FancyVerbFormatLinepygments\Lformat\L:num-spacesline-number\Llist\Ldraw{right}{up}rightup\Ldraw\pdfiteral

限制:此宏要求代码中间没有分页符。但熟练的宏程序员可以添加此功能作为练习。

依赖项:您不需要任何附加包。您可以通过pdflatexlualatex或 来处理宏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.styfancyvrb.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

答案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}

为了完整性:部分代码取自这里:将环境内容视为字符串,替换子字符串并进行解释

相关内容