我们可以像这样listings
为该阶段定义一个钩子:Output
\lst@AddToHook{Output}{\output@hook}
在我们的钩子代码中,我们可以通过测试计数来测试我们是否处于一般处理模式,\lst@mode
如下所示:
\ifnum\lst@mode=\lst@Pmode
% code goes here
\fi
我想知道的是,是否存在注释和字符串的类似物。手册中列出了一个叫做 的东西,\lst@commentmode
但这不起作用:实验表明,\lst@mode
注释和字符串分别设置为 6 和 7,而\lst@commentmode
的值为 3。虽然简单地使用这些魔法数字是可行的,但这似乎是糟糕的代码实践。
答案1
为了清楚起见,我将其作为单独的答案发布,因为它集中于您的实际问题并使用与其他答案完全不同的方法。
问题
首先,除了在开发人员文档.listings
将当前模式作为数字存储在\lst@mode
整数寄存器中。 几个特殊模式具有硬编码的内部名称,就像\lst@Pmode
您的问题中提到的那样。
另一种命名模式是,\lst@commentmode
文档中称其为“注释的通用模式”。那么为什么这种模式不能检测注释呢?很简单,因为它从未在实际源代码中使用过。我只能猜测它是早期listings
版本的遗留。
对于实际的字符串或注释处理,listings
使用一组更通用的宏,这些宏涵盖所有类型的分隔符(请参阅开发人员指南的第 10.2 节)。由于这些分隔符由用户定义,因此所谓的动态模式与它们相关联。也就是说,对于每个选项(如morecomment = ...
in \lstset
(或语言定义)),都会分配一个新的模式编号并与此特定分隔符相关联。模式编号还取决于处理这些选项的顺序。
不幸的是,这些模式编号从未存储在全局表中,我们无法通过某些属性轻松访问它们。相反,它们似乎在宏调用之间传递,其中一些被用作部分函数,并且有很多参数混编,这使得很难集成一种机制将它们映射到可访问的名称。无论如何,我们都会尝试。
计划
第一个障碍是找到一个合适的地方来集成一个钩子,它允许我们将动态模式号映射到宏名。对于新的字符串类型定义,如
\lstset{ morestring = [s]{"}{"} }
我们在宏跟踪输出中发现一系列扩展
\lst@DefDelims ->\lst@UseDynamicMode \lst@StringDM@s {"}{"}\@empty \lst@BeginSt
ring \lst@EndString {{\lst@stringstyle }\lst@modetrue }
\lst@UseDynamicMode ->\@tempcnta \lst@dynamicmode \relax \advance \@tempcnta \@
ne \edef \lst@dynamicmode {\the \@tempcnta }\expandafter \lst@Swap \expandafter
{\expandafter {\lst@dynamicmode }}
...
\lst@StringDM@s #1#2#3\@empty #4#5#6->\lst@CArg #2\relax \lst@DefDelimB {}{}{}#
4{#1}{#6}\lst@CArg #3\relax \lst@DefDelimE {}{}{}#5{#1}
#1<-6
#2<-"
#3<-"
#4<-\lst@BeginString
#5<-\lst@EndString
#6<-{\lst@stringstyle }\lst@modetrue
\lst@DefDelims
启动字符串定义,\lst@StringDM@s
是接收新动态模式编号以及分隔符的开始和结束字符作为参数的宏。这似乎是安装钩子的好地方。listings
定义了几个这样的宏,所有的形式\lst@<class>DM@<type>
都是其中<class>
是分隔符类(当前为String
、Comment
或Delim
)和<type>
分隔符类型(例如,s
对于由第二个分隔符关闭的分隔符)。
通过使用这些宏,我们可以为某些分隔符类和类型安装钩子,但特定的分隔符对不是宏名的一部分。由于listings
模式编号也通过分隔符来区分,我们需要在每个钩子中植入另一个钩子!这个内部钩子首先是空的,然后针对每个具有相同分隔符类和类型的新模式定义进行扩展。在这个钩子中,我们需要将目标分隔符与传递给我们植入钩子的宏的分隔符进行比较。如果匹配,我们最终可以定义一个扩展到找到的模式编号的新宏。
实施
定义新模式别名的主要用户命令是
\DefineDynamicMode <name>{<class>}{<type>}{<opening delim>}{<closing delim>}
其中<name>
,是要定义的新宏,后面四个参数分别是分隔符的类、类型、开始和结束字符。对于morestring
上面的定义,调用可能看起来像
\DefineDynamicMode\dquoteMode{string}{s}{"}{"}
我们要做的第一件事是安装我们的外钩子(如果尚未通过 安装)\dynmode@install@hook
。它只是\lst@<class>DM@<type>
用其原始定义替换了内钩子的调用\dynmode@hook@<class>@<type>{#1}{#2}{#3}
,其中三个参数是模式编号和我们稍后需要比较的两个分隔符字符序列。
接下来是 中的内钩子的扩展\dynmode@extend@hook
。为了避免进一步扩展内钩子带来的麻烦,我们维护了一个标记列表\dynmode@hook@<class>@<type>@tl
,该列表将全局存储内钩子的替换文本。我们可以轻松地将代码添加到该列表中,该代码比较两个分隔符字符序列,如果它们匹配,则定义新的模式别名。之后,我们根据新的标记列表重新定义内钩子。调用上述模式别名定义后,内钩子将等于
\gdef\dynmode@hook@string@s#1#2#3{%
\@ifstrequal {#2}{"}{%
\@ifstrequal {#3}{"}{%
\newcommand \dquoteMode {#1}%
}{}%
}{}%
}
最后是带有示例的完整代码。请注意,该实现尚未经过大量测试,因此非常欢迎反馈。
定义了所有三个类的分隔符,每个类都以斜体形式排版。此外,还创建了四个模式别名。然后,在一个Output
钩子中,我们根据字符串测试每个输出标记text
,如果它们匹配,我们将根据我们当前所处的模式向该标记应用额外的颜色样式。
\documentclass{article}
\usepackage{listings}
\usepackage{xcolor}
%%% Mode alias implementation
\makeatletter
% #1 = macro name; #2 = delimiter class; #3 = delimiter type; #4/#5 = delimiters
\newcommand\DefineDynamicMode[5]{%
\begingroup
\@ifstrequal{#2}{string}{%
\@ifundefined{dynmode@hook@string}{%
\dynmode@install@hook{string}{String}{#3}%
}{}%
\dynmode@extend@hook{#1}{#2}{#3}{#4}{#5}%
}{\@ifstrequal{#2}{delim}{%
\@ifundefined{dynmode@hook@delim}{%
\dynmode@install@hook{delim}{Delim}{#3}%
}{}%
\dynmode@extend@hook{#1}{#2}{#3}{#4}{#5}%
}{\@ifstrequal{#2}{comment}{%
\@ifundefined{dynmode@hook@comment}{%
\dynmode@install@hook{comment}{Comment}{#3}%
}{}%
\dynmode@extend@hook{#1}{#2}{#3}{#4}{#5}%
}{%
\errmessage{Cannot define new mode alias for unknown delimiter class `#2'}%
}}}%
\endgroup
}
% #1 = delimiter class; #2 = delimiter class, first uppercased; #3 = delimiter type
\newcommand\dynmode@install@hook[3]{%
\expandafter\gdef\csname dynmode@hook@#1\endcsname{}%
\edef\@temp{%
\global\let\expandonce{\csname orig@lst@#2DM@#3\endcsname}=%
\expandonce{\csname lst@#2DM@#3\endcsname}%
\gdef\expandonce{\csname lst@#2DM@#3\endcsname}####1####2####3%
\noexpand\@empty####4####5####6{%
\expandonce{\csname dynmode@hook@#1@#3\endcsname}{####1}{####2}{####3}%
\expandonce{\csname orig@lst@#2DM@#3\endcsname}{####1}{####2}{####3}%
\noexpand\@empty{####4}{####5}{####6}%
}%
}\@temp
\expandafter\gdef\csname dynmode@hook@#1@#3@tl\endcsname{}%
}
% #1 = macro name; #2 = delimiter class; #3 = delimiter type; #4/#5 = delimiters
\newcommand\dynmode@extend@hook[5]{%
\expandafter\xdef\csname dynmode@hook@#2@#3@tl\endcsname{%
\expandafter\expandonce\expandafter{\csname dynmode@hook@#2@#3@tl\endcsname}%
\unexpanded{\@ifstrequal{##2}{#4}{\@ifstrequal{##3}{#5}{%
\newcommand#1{##1}%
\typeout{Defined new mode \string#1=##1 for #2 delimiters #4 #5.}%
}{}}{}}%
}%
\def\@temp##1{%
% #1 = mode number; #2/#3 = delimiters
\expandafter\gdef\csname dynmode@hook@#2@#3\endcsname####1####2####3{##1}%
}%
\expandafter\expandafter\expandafter\@temp\expandafter\expandafter\expandafter
{\csname dynmode@hook@#2@#3@tl\endcsname}%
}
\newcommand\@ifstrequal[2]{%
\ifnum\pdfstrcmp{\unexpanded{#1}}{\unexpanded{#2}}=0
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
\newcommand\expandonce[1]{\unexpanded\expandafter{#1}}
\makeatother
%%% Sample usage
\lstset{
basicstyle = \ttfamily,
showstringspaces = false,
stringstyle = \itshape,
morestring = [s]{"}{"},
morestring = [s]{'}{'},
moredelim = [s][\itshape]{(}{)},
morecomment=[s][\itshape]{[*}{*]}
}
\DefineDynamicMode\dquoteMode{string}{s}{"}{"}
\DefineDynamicMode\squoteMode{string}{s}{'}{'}
\DefineDynamicMode\parensMode{delim}{s}{(}{)}
\DefineDynamicMode\squareMode{comment}{s}{[*}{*]}
\makeatletter
\lst@AddToHook{Output}{\output@hook}
\def\output@hook{%
\ifnum\lst@mode=\dquoteMode
\output@hook@test@output{text}{\color{blue}}%
\fi
\ifnum\lst@mode=\squoteMode
\output@hook@test@output{text}{\color{blue!50!white}}%
\fi
\ifnum\lst@mode=\parensMode
\output@hook@test@output{text}{\color{green}}%
\fi
\ifnum\lst@mode=\squareMode
\output@hook@test@output{text}{\color{red}}%
\fi
}
\newcommand\output@hook@test@output[2]{%
\expandafter\@ifstrequal\expandafter{\the\lst@token}{#1}{%
\expandafter\def\expandafter\lst@thestyle\expandafter{\lst@thestyle#2}%
}{}%
}
\makeatother
\begin{document}
\begin{lstlisting}
some text "text in double quotes" more text
some text 'text in single quotes' more text
some text (text in parentheses) more text
some text [*text in square brackets*] more text
\end{lstlisting}
\end{document}
答案2
这不是对您关于模式的问题的直接回答,但我想提及一种可能仍然有助于实现预期结果的通用方法。
listings
我们可以设置和重置全局标志来界定我们想要特殊处理行为的某些代码部分,而不是比较当前以哪种模式处理代码。具体来说,我们使用stringstyle
或commentstyle
选项将由创建的新开关设置\newif
为 true,并在离开内部组时将其重置为 false listings
。对于后者,EndGroup
可以使用钩子。然后,其他样式或钩子可以检查该标志以产生不同的输出。
这种方法很容易实现,但也有一些限制。例如,当使用*
或**
版本的字符串、注释或其他分隔符时,它不能用于嵌套环境,因为第一个EndGroup
会在外部环境仍处于活动状态时重置标志。
下面是一个完整的示例,展示了这种方法的实际应用。在两个清单中,text
都创建了关键字,并且字符串文字中也启用了代码突出显示。第一个清单设置了一个固定的关键字样式,以绿色打印所有关键字,第二个清单使用上面描述的方法,\mykeywordstyle
如果在字符串中,则在本地从蓝色更改为红色。
\documentclass{article}
\usepackage{listings}
\usepackage{xcolor}
\makeatletter
\newif\if@instring
\lst@AddToHook{EndGroup}{\global\@instringfalse}
\newcommand\output@hook{%
\if@instring
\def\mykeywordstyle{\color{red}}%
\fi
}
\lst@AddToHook{Output}{\output@hook}
\makeatother
\lstset {
basicstyle = \ttfamily,
morestring = *[s]"",
morestring = *[s]'',
morekeywords = {text},
stringstyle = {\global\@instringtrue \itshape}
}
\begin{document}
\begin{lstlisting}[keywordstyle = \color{green}]
some text "text in string" more text
some text 'text in string' more text
\end{lstlisting}
\newcommand\mykeywordstyle{\color{blue}}
\begin{lstlisting}[keywordstyle = \mykeywordstyle]
some text "text in string" more text
some text 'text in string' more text
\end{lstlisting}
\end{document}