Skip to content

Commit d1293f7

Browse files
committed
implement LaTeX support
1 parent 178b8a2 commit d1293f7

File tree

3 files changed

+153
-14
lines changed

3 files changed

+153
-14
lines changed

sphinx/texinputs/sphinxlatexobjects.sty

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@
128128
\parbox[t]{\py@argswidth}{\raggedright #1\sphinxcode{)}#2\strut}%
129129
% final strut is to help get correct vertical separation
130130
}
131+
\newcommand{\py@sigparamswithtplist}[3]{%
132+
% similar to \py@sigparams but with different delimiters and an additional
133+
% type parameters list given as #1, the argument list as #2 and the return
134+
% annotation as #3
135+
\parbox[t]{\py@argswidth}{\raggedright #1\sphinxcode{]}\sphinxcode{(}#2\sphinxcode{)}#3\strut}%
136+
}
137+
131138
\newcommand{\pysigline}[1]{%
132139
% as \py@argswidth is available, we use it but no "args" here
133140
% the \relax\relax is because \py@argswidth is a "skip" variable
@@ -146,6 +153,15 @@
146153
\item[{#1\sphinxcode{(}\py@sigparams{#2}{#3}\strut}]
147154
\pysigadjustitemsep
148155
}
156+
\newcommand{\pysiglinewithargsretwithtplist}[4]{
157+
% #1 = name, #2 = tplist, #3 = arglist, #4 = retann
158+
\let\spx@label\label\let\label\@gobble
159+
\settowidth{\py@argswidth}{#1\sphinxcode{[}}%
160+
\let\label\spx@label
161+
\py@argswidth=\dimexpr\linewidth+\labelwidth-\py@argswidth\relax\relax
162+
\item[{#1\sphinxcode{[}\py@sigparamswithtplist{#2}{#3}{#4}\strut}]
163+
\pysigadjustitemsep
164+
}
149165

150166
\def\sphinxoptionalextraspace{0.5mm}
151167
\newcommand{\pysigwithonelineperarg}[3]{%
@@ -167,6 +183,76 @@
167183
\nopagebreak\noindent\kern-\labelwidth\sphinxcode{)}{#3}
168184
\pysigadjustitemsep
169185
}
186+
\newcommand{\pysigwithonelineperargwithonelinepertparg}[4]{
187+
% #1 = name, #2 = tplist, #3 = arglist, #4 = retann
188+
% render each type parameters and argument lists on its own line
189+
\item[#1\sphinxcode{[}\strut]
190+
\leavevmode\par\nopagebreak
191+
\begingroup
192+
\let\sphinxparamcomma\sphinxparamcommaoneperline
193+
% \sphinxtypeparam is treated similarly to \sphinxparam but since
194+
% \sphinxoptional is not accepted in a type parameters list, we do
195+
% not need the hook or the global definition
196+
\let\spx@sphinxtypeparam\sphinxtypeparam
197+
\def\sphinxtypeparam{\def\sphinxtypeparam{\par\spx@sphinxtypeparam}\spx@sphinxtypeparam}%
198+
#2\par
199+
\endgroup
200+
\nopagebreak\noindent\kern-\labelwidth\sphinxcode{]}
201+
% render the rest of the signature like in \pysigwithonelineperarg
202+
\sphinxcode{(}\strut\leavevmode\par\nopagebreak
203+
\begingroup
204+
\let\sphinxparamcomma\sphinxparamcommaoneperline
205+
\def\sphinxoptionalhook{\ifvmode\else\kern\sphinxoptionalextraspace\relax\fi}%
206+
\global\let\spx@sphinxparam\sphinxparam
207+
\gdef\sphinxparam{\gdef\sphinxparam{\par\spx@sphinxparam}\spx@sphinxparam}%
208+
#3\par
209+
\endgroup
210+
\global\let\sphinxparam\spx@sphinxparam
211+
\nopagebreak\noindent\kern-\labelwidth\sphinxcode{)}{#4}
212+
\pysigadjustitemsep
213+
}
214+
\newcommand{\pysiglinewithargsretwithonelinepertparg}[4]{
215+
% #1 = name, #2 = tplist, #3 = arglist, #4 = retann
216+
% render each type parameters on its own line but the argument list is rendered inline
217+
\item[#1\sphinxcode{[}\strut]
218+
\leavevmode\par\nopagebreak
219+
\begingroup
220+
\let\sphinxparamcomma\sphinxparamcommaoneperline
221+
% \sphinxtypeparam is treated similarly to \sphinxparam but since
222+
% \sphinxoptional is not accepted in a type parameters list, we do
223+
% not need the hook or the global definition
224+
\let\spx@sphinxtypeparam\sphinxtypeparam
225+
\def\sphinxtypeparam{\def\sphinxtypeparam{\par\spx@sphinxtypeparam}\spx@sphinxtypeparam}%
226+
#2\par
227+
\endgroup
228+
\nopagebreak\noindent\kern-\labelwidth\sphinxcode{]}
229+
% render the arguments list on one line
230+
\settowidth{\py@argswidth}{\sphinxcode{]}\sphinxcode{()}}%
231+
\py@argswidth=\dimexpr\linewidth+\labelwidth-\py@argswidth\relax\relax
232+
\sphinxcode{(}\parbox[t]{\py@argswidth}{\raggedright #3\sphinxcode{)}#4\strut}%
233+
\pysigadjustitemsep
234+
}
235+
\newcommand{\pysigwithonelineperargwithtplist}[4]{
236+
% #1 = name, #2 = tplist, #3 = arglist, #4 = retann
237+
% render the type parameters list on one line, but each argument is rendered on its own line
238+
\let\spx@label\label\let\label\@gobble
239+
\settowidth{\py@argswidth}{#1\sphinxcode{[}}%
240+
\let\label\spx@label
241+
\py@argswidth=\dimexpr\linewidth+\labelwidth-\py@argswidth\relax\relax
242+
\item[{#1\sphinxcode{[}\parbox[t]{\py@argswidth}{\raggedright #2\sphinxcode{]}\sphinxcode{(}\strut}\strut}]
243+
% render the rest of the signature like in \pysigwithonelineperarg
244+
\begingroup
245+
\let\sphinxparamcomma\sphinxparamcommaoneperline
246+
\def\sphinxoptionalhook{\ifvmode\else\kern\sphinxoptionalextraspace\relax\fi}%
247+
\global\let\spx@sphinxparam\sphinxparam
248+
\gdef\sphinxparam{\gdef\sphinxparam{\par\spx@sphinxparam}\spx@sphinxparam}%
249+
#3\par
250+
\endgroup
251+
\global\let\sphinxparam\spx@sphinxparam
252+
\nopagebreak\noindent\kern-\labelwidth\sphinxcode{)}{#4}
253+
\pysigadjustitemsep
254+
}
255+
170256
\newcommand{\pysigadjustitemsep}{%
171257
% adjust \itemsep to control the separation with the next signature
172258
% sharing common description

sphinx/texinputs/sphinxlatexstyletext.sty

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
\protected\def\sphinxtermref#1{\emph{#1}}
5757
\protected\def\sphinxsamedocref#1{\emph{#1}}
5858
\protected\def\sphinxparam#1{\emph{#1}}
59+
\protected\def\sphinxtypeparam#1{\emph{#1}}
5960
% \optional is used for ``[, arg]``, i.e. desc_optional nodes.
6061
\long\protected\def\sphinxoptional#1{%
6162
{\sphinxoptionalhook\textnormal{\Large[}}{#1}\hspace{0.5mm}{\textnormal{\Large]}}}

sphinx/writers/latex.py

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -700,14 +700,51 @@ def depart_desc(self, node: Element) -> None:
700700
self.body.append(CR + r'\end{fulllineitems}' + BLANKLINE)
701701

702702
def _visit_signature_line(self, node: Element) -> None:
703+
def next_sibling(e: Element) -> Element | None:
704+
try:
705+
return e.parent[e.parent.index(e) + 1]
706+
except (AttributeError, IndexError):
707+
return None
708+
709+
def has_multi_line(e: Element) -> bool:
710+
return e.get('multi_line_parameter_list')
711+
712+
self.has_tplist = False
713+
703714
for child in node:
715+
if isinstance(child, addnodes.desc_tparameterlist):
716+
self.has_tplist = True
717+
# recall that return annotations must follow an argument list,
718+
# so signatures of the form "foo[tplist] -> retann" will not
719+
# be encountered (if they should, the `domains.python.py_sig_re`
720+
# pattern must be modified accordingly)
721+
arglist = next_sibling(child)
722+
assert isinstance(arglist, addnodes.desc_parameterlist)
723+
# tplist + arglist: \macro{name}{tplist}{arglist}{return}
724+
multi_tplist = has_multi_line(child)
725+
multi_arglist = has_multi_line(arglist)
726+
727+
if multi_tplist:
728+
if multi_arglist:
729+
self.body.append(CR + r'\pysigwithonelineperargwithonelinepertparg{')
730+
else:
731+
self.body.append(CR + r'\pysiglinewithargsretwithonelinepertparg{')
732+
else:
733+
if multi_arglist:
734+
self.body.append(CR + r'\pysigwithonelineperargwithtplist{')
735+
else:
736+
self.body.append(CR + r'\pysiglinewithargsretwithtplist{')
737+
break
738+
704739
if isinstance(child, addnodes.desc_parameterlist):
705-
if child.get('multi_line_parameter_list'):
740+
# arglist only: \macro{name}{arglist}{return}
741+
if has_multi_line(child):
706742
self.body.append(CR + r'\pysigwithonelineperarg{')
707743
else:
708744
self.body.append(CR + r'\pysiglinewithargsret{')
709745
break
710746
else:
747+
# no tplist, no arglist: \macro{name}
711748
self.body.append(CR + r'\pysigline{')
712749

713750
def _depart_signature_line(self, node: Element) -> None:
@@ -784,34 +821,47 @@ def visit_desc_returns(self, node: Element) -> None:
784821
def depart_desc_returns(self, node: Element) -> None:
785822
self.body.append(r'}')
786823

787-
def visit_desc_parameterlist(self, node: Element) -> None:
788-
# close name, open parameterlist
789-
self.body.append('}{')
824+
def _visit_sig_parameter_list(self, node: Element, parameter_group: type[Element]) -> None:
825+
"""Visit a signature parameters or type parameters list.
826+
827+
The *parameter_group* value is the type of a child node acting as a required parameter
828+
or as a set of contiguous optional parameters.
829+
830+
The caller is responsible for closing adding surrounding LaTeX macro argument start
831+
and stop tokens.
832+
"""
790833
self.is_first_param = True
791834
self.optional_param_level = 0
792835
self.params_left_at_level = 0
793836
self.param_group_index = 0
794837
# Counts as what we call a parameter group either a required parameter, or a
795838
# set of contiguous optional ones.
796-
self.list_is_required_param = [isinstance(c, addnodes.desc_parameter)
797-
for c in node.children]
839+
self.list_is_required_param = [isinstance(c, parameter_group) for c in node.children]
798840
# How many required parameters are left.
799841
self.required_params_left = sum(self.list_is_required_param)
800842
self.param_separator = r'\sphinxparamcomma '
801843
self.multi_line_parameter_list = node.get('multi_line_parameter_list', False)
802844

845+
def visit_desc_parameterlist(self, node: Element) -> None:
846+
if not self.has_tplist:
847+
# close name argument (#1), open parameters list argument (#2)
848+
self.body.append('}{')
849+
self._visit_sig_parameter_list(node, addnodes.desc_parameter)
850+
803851
def depart_desc_parameterlist(self, node: Element) -> None:
804852
# close parameterlist, open return annotation
805853
self.body.append('}{')
806854

807855
def visit_desc_tparameterlist(self, node: Element) -> None:
808-
# not supported yet
809-
raise nodes.SkipNode
856+
# close name argument (#1), open type parameters list argument (#2)
857+
self.body.append('}{')
858+
self._visit_sig_parameter_list(node, addnodes.desc_tparameter)
810859

811860
def depart_desc_tparameterlist(self, node: Element) -> None:
812-
pass
861+
# close type parameters list, open parameters list argument (#3)
862+
self.body.append('}{')
813863

814-
def visit_desc_parameter(self, node: Element) -> None:
864+
def _visit_sig_parameter(self, node: Element, parameter_macro: str) -> None:
815865
if self.is_first_param:
816866
self.is_first_param = False
817867
elif not self.multi_line_parameter_list and not self.required_params_left:
@@ -821,7 +871,10 @@ def visit_desc_parameter(self, node: Element) -> None:
821871
else:
822872
self.params_left_at_level -= 1
823873
if not node.hasattr('noemph'):
824-
self.body.append(r'\sphinxparam{')
874+
self.body.append(parameter_macro)
875+
876+
def visit_desc_parameter(self, node: Element) -> None:
877+
self._visit_sig_parameter(node, r'\sphinxparam{')
825878

826879
def depart_desc_parameter(self, node: Element) -> None:
827880
if not node.hasattr('noemph'):
@@ -844,11 +897,10 @@ def depart_desc_parameter(self, node: Element) -> None:
844897
self.param_group_index += 1
845898

846899
def visit_desc_tparameter(self, node: Element) -> None:
847-
# not supported yet
848-
raise nodes.SkipNode
900+
self._visit_sig_parameter(node, r'\sphinxtypeparam{')
849901

850902
def depart_desc_tparameter(self, node: Element) -> None:
851-
pass
903+
self.depart_desc_parameter(node)
852904

853905
def visit_desc_optional(self, node: Element) -> None:
854906
self.params_left_at_level = sum([isinstance(c, addnodes.desc_parameter)

0 commit comments

Comments
 (0)