Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Break long function declarations into one parameter per line #1514

Closed
shimizukawa opened this issue Jan 3, 2015 · 26 comments
Closed

Break long function declarations into one parameter per line #1514

shimizukawa opened this issue Jan 3, 2015 · 26 comments
Labels
domains:c type:enhancement enhance or introduce a new feature

Comments

@shimizukawa
Copy link
Member

C function declarations that have many (or long) parameters resulting in a line break are hard to read. Would it be possible to optionally break them with one parameter per line (like doxygen).

I.e. instead of

size_t process_function(const void *source, void *destination, size_t length, void *data, int (*callback)(size_t, size_t, size_t, void *),
void *cbparam)

I would like something like

size_t process_function(const void *source,
                        void *destination,
                        size_t length,
                        void *data,
                        int (*callback)(size_t, size_t, size_t, void *),
                        void *cbparam)

@shimizukawa shimizukawa added this to the 1.2.x milestone Jan 3, 2015
@shimizukawa shimizukawa added type:enhancement enhance or introduce a new feature domains:c prio:low labels Jan 3, 2015
@shimizukawa shimizukawa removed this from the 1.2.x milestone Jul 10, 2015
@JavierJF
Copy link

I would like to participate in the implementation of this feature @shimizukawa. Do you have any plans or known starting point for it?

@tk0miya
Copy link
Member

tk0miya commented Dec 17, 2017

This is not an issue that he posted. This was migrated from bitbucket. The original author is Jørgen Ibsen.

Originally reported by: Jørgen Ibsen

BTW, this is related with writer modules. So I guess sphinx/writers/html.py (html5.py) is related with this.

@JavierJF
Copy link

@tk0miya yes, I knew it was a imported issue, but I saw that was labeled by him several times. Thanks for the info about the related module, I have looked into it and I think it's possible to sketch something. But probably the best will be trying to expose a setting for configuring it, like a number of parameters in which start doing the line-breaking.

@jakobandersen
Copy link
Contributor

It should be doable only by modifying individual domains. A domain is given a node to render a signature into. That node is a addnodes.desc_signature. Instead of directly adding child nodes to it, you can set is_multiline = True, and only add addnodes.desc_signature_line nodes as children. In those you can then add fragments of a signature. The C++ domain does this for templates.

@JavierJF
Copy link

@jakobandersen Nice info thanks, I'm going to start with that. Reading first how that was integrated in the C++ domain.

@hzhwcmhf
Copy link

I‘m facing the same issue in python. Is there any update?

@jakobandersen
Copy link
Contributor

The missing(?) part in implementing this is how to do indentation. E.g., for a function def f(a, b, c, d), which should be broken into, say, def f(a, b and c, d) we need some way to precisely indent the second line, taking whatever formatting of the signature happens into account. Is there such a thing in docutils? In Latex we could render a fake prefix into a \phantom. E.g, (with an ad hoc made up markup):

\kw{def} \textbf{f}(a, b\\
\phantom{\kw{def} \textbf{f}(}c, d)

This would make whitespace taking up the exact space needed. However, is something similar to this possible in HTML+CSS without too nasty haxes? and how with other output formats?

@oiao
Copy link

oiao commented Nov 3, 2020

Is there any progress on this issue? It would also be nice to use it with autodoc and autodoc_docstring_signature by manually defining line breaks

@kuzmoyev
Copy link

kuzmoyev commented Dec 2, 2020

Are there any updates on this? It would be very useful for Python docs especially with the type annotations and default values. As keeping it on one line just makes it useless for the reader.

image

@jakobandersen, having all the parameters on a new line (including the first one) should be simple, as it does not require calculating the indentation:

class gcsa.google_calendar.GoogleCalendar(
    calendar: str = 'primary', 
    *, 
    credentials_path: str = None, 
    token_path: str = None, 
    save_token: bool = True, 
    credentials: google.oauth2.credentials.Credentials = None, 
    read_only: bool = False
)

@tk0miya
Copy link
Member

tk0miya commented Dec 8, 2020

@kuzmoyev About python document, it was already improved at #5868. I believe you can change the style of functions via CSS. Could you try it?

@kuzmoyev
Copy link

kuzmoyev commented Dec 10, 2020

Thank you @tk0miya for the reference. Had to improve my CSS skills to achieve that :). So for anybody who is still searching, here is my custom.css (see the docs on how to add it):

/* Newlines (\a) and spaces (\20) before each parameter */
.sig-param::before {
    content: "\a\20\20\20\20\20\20\20\20\20\20\20\20\20\20\20\20";
    white-space: pre;
}

/* Newline after the last parameter (so the closing bracket is on a new line) */
dt em.sig-param:last-of-type::after {
    content: "\a";
    white-space: pre;
}

/* To have blue background of width of the block (instead of width of content) */
dl.class > dt:first-of-type {
    display: block !important;
}

If you are using readthedocs (which uses sphinx<2):

/*Newlines (\a) and spaces (\20) before each parameter*/
dl.class em:not([class])::before {
    content: "\a\20\20\20\20\20\20\20\20\20\20\20\20\20\20\20\20";
    white-space: pre;
}

/*Newline after the last parameter (so the closing bracket is on a new line)*/
dl.class em:not([class]):last-of-type::after {
    content: "\a";
    white-space: pre;
}

/*To have blue background of width of the block (instead of width of content)*/
dl.class > dt:first-of-type {
    display: block !important;
}

Here it is in my project

Class:

image

Methods:

image

I'm not very good at CSS, so suggestions are very welcomed! :)

@tk0miya
Copy link
Member

tk0miya commented Dec 11, 2020

Cool :-)

@jakobandersen
Copy link
Contributor

I think I found a scheme to solve this in HTML and somewhat in Latex. I'm not sure if there are other formats this is directly relevant for.
A prototype for seeing how it works can be seen at https://jakobandersen.github.io/sphinx_formatting_test/test.html, try zooming in and out to force different and extreme line breaking.

Some observations, mostly applying to the dynamic nature of HTML, but I think the boxing in Latex can be derived from it:

  • There is already a mechanism for forcibly breaking lines, via the multiline signatures. In the example, these boxes are coloured black.
  • Some parts of a line should never be wrapped, e.g., in a Python function def f(, or in a derived C++ class class A :. These boxes are red in the example.
  • The line wrapping should sometimes create a hanging indent, e.g., in a function parameter list, template parameter list, or the case in No wrapping for cpp:enumerator #7241 . This can be done by having a box (blue in the example) that does not allow wrapping, which then contains multiple box, of which the last box is one that allows wrapping (green in the example).
    The Latex writer already does this trick with the current desc_parameter_list.
  • In some cases there can be multiple boxes in a line that each should allow dynamic wrapping, and the whole line should allow wrapping as well if it doesn't fit (e.g., the example``Function, with noexcept allowed to wrap to own line as well.").
    I'm not sure if one can get such a line wrapping calculated in Latex (ping @jfbu :-)).
  • In some signature lines there should be an explicit indent, so a box is needed for that as well.

The CSS in the example is based on using Flexbox, which can solve the vertical alignment by baseline issue as well as seemingly handle the wrapping in a nice way.

These new box types will then become new classes in addnodes (for those that doesn't already exist) and each domain must then generate the right nodes.

Any thoughts on this design idea?

I started trying to implement some of this, but it diverged into extending the work started in #6417 to get closer to a solution to #4289.

@jfbu
Copy link
Contributor

jfbu commented Mar 13, 2021

  • The line wrapping should sometimes create a hanging indent, e.g., in a function parameter list, template parameter list, or the case in No wrapping for cpp:enumerator #7241 . This can be done by having a box (blue in the example) that does not allow wrapping, which then contains multiple box, of which the last box is one that allows wrapping (green in the example).
    The Latex writer already does this trick with the current desc_parameter_list.

  • In some cases there can be multiple boxes in a line that each should allow dynamic wrapping, and the whole line should allow wrapping as well if it doesn't fit (e.g., the example``Function, with noexcept allowed to wrap to own line as well.").
    I'm not sure if one can get such a line wrapping calculated in Latex (ping @jfbu :-)).

In LaTeX there is
\pysigline and \pysiglinewithargsret and the latter uses already a \parbox. We can enhance the \parbox to a minipage. Already with \parbox we can emit \newline in it: for example desc_sig_name node gets class n which converts in LaTeX into \DUrole{n}{the name} but using this to emit \newline is uneasy because the styling is used for other things than parameter names, it is also used in python domain for type annotation (e.g. I see this latex code \emph{\DUrole{n}{default}\DUrole{p}{:} \DUrole{n}{Any}}. Also, the \newline shoud not be done for first parameter name. Also I am mentioning here Python domain where I believe, especially for type annotated signature that each parameter should be on its own line.

These things will not allow pagebreaks: even if we did not use \parbox or minipage, and barring complete re-thinking of fulllineitems, currently all of this is inside an \item[...] and ends up at core LaTeX in a box ; a horizontal box which was not really thought out to hold multi-line content, but via embedded \parbox or minipage we can do it.

With #8997 also \pysigline argument uses \parbox. To specify a hanging indent a variant with two arguments would only have to imitate \pysiglinewithargsret.

So line wrapping inside signatures can definitly be done in LaTeX with suitable mark-up. Already now inserting a newline is enough with no need to change the sphinx.sty or related LaTeX style files.

Capture d’écran 2021-03-13 à 22 19 06

@Hmiku8338
Copy link

Any updates on this?

@tsathoqqua
Copy link

Same, would be useful for multiline C functions.

@bva99
Copy link

bva99 commented May 13, 2022

As a workaround, how can you just set to "ignore pysiglinewithargsret"?

For example, changing

class some.crazy.long.Name(lots, of,    # | <- line end
                           parameters,  # | <- line end
                           right, here) # | <- line end

to

class some.crazy.long.Name(lots, of,    # | <- line end
parameters, right, here)                # | <- line end

or maybe even

class some.crazy.long.Name(lots, of,    # | <- line end
          parameters, right, here)      # | <- line end

?

Either that or remove some.crazy.long would be great, e.g:

class Name(lots, of, parameters, right, # | <- line end
           here)

(Using Sphinx=4.4 and autodoc, specifically automodule, for Python documentation here)


Edit:
The first two options can be done by adding some things to the preamble in latex_elements. Something like this, but not exactly.

The last option is the easiest. Set add_module_name = False.

@cjw296
Copy link

cjw296 commented Jul 6, 2022

@tk0miya - I see #5868 is locked as resolved, but this issue is still open.
What's the state of play with this?
I tried setting autodoc_typehints = "signature" in conf.py but it appears to have little impact either on the default theme or @pradyunsg's https://github.com/pradyunsg/furo theme which I'd like to use.

I have this function definition which is an example of one that has become particularly hard to read after adding type annotations:
https://testfixtures.readthedocs.io/en/latest/api.html#testfixtures.compare

@pradyunsg
Copy link
Contributor

FWIW, I use the following as a workaround to keep function signatures legible. As a bonus, it forces me to clearly describe each argument to a function.

# Automatically extract typehints when specified and place them in
# descriptions of the relevant function/method.
autodoc_typehints = "description"

# Don't show class signature with the class' name.
autodoc_class_signature = "separated"

@cjw296
Copy link

cjw296 commented Jul 7, 2022

Unfortunately this doesn't help my case much, especially as this particular function has a pragmatic API that isn't well documented by covering every single parameter in that style.

I guess we need to wait to hear from @tk0miya about where Sphinx is on this...

@TLouf
Copy link
Contributor

TLouf commented Nov 25, 2022

Building on @kuzmoyev's solution, in order to make the newlines optional, I have the following custom.css:

/*Newlines (\a) and spaces (\20) before each parameter*/
.long-sig .sig-param:not(.short-sig .sig-param)::before {
    content: "\a\20\20\20\20";
    white-space: pre;
}

/* Newline after the last parameter (so the closing bracket is on a new line) */
.long-sig .sig-param:not(.short-sig .sig-param):last-of-type::after {
    content: "\a";
    white-space: pre;
}

And I can thus optionally enable / disable this behaviour, using the container directive in rst. Let's say I have the following module.rst:

Module
======

.. container:: long-sig

   .. autoclass:: MyClass
     
      .. automethod:: method_with_many_args

      .. container:: short-sig

         .. automethod:: method_with_few_args

Then MyClass and method_with_many_args will have all their arguments on separate lines, while method_with_few_args won't.

This cannot get @shimizukawa's expected result though, as one would need to adapt the added spacing to the length of the function name to get it. And well that's just hacky, a builtin solution from sphinx to automatically do this when you exceed N characters or arguments would be ideal.

@emezh
Copy link

emezh commented Dec 15, 2022

@kuzmoyev, thanks for a workaround (#1514 (comment)).
The custom.css (specifically the first snippet) worked for me with Py, but not with C APIs.
Any quick advice how it can be applied to C functions?

@leycec
Copy link

leycec commented Feb 28, 2023

Interestingly, the Sphinx Immaterial theme already does this. That theme extends apidoc with two new theme-specific globals controlling line wrapping in Python signatures: wrap_signatures_with_css and wrap_signatures_column_limit. The underlying code implementing this functionality lives here.

It's only 76 lines of code to implement this commonly requested functionality. This only applies to autodoc, but that's better than nothing – which is what Sphinx currently has. If someone (...who is not me) would consider submitting a PR inspired by that code, that would be wondrous for all! 🪄

Until then, long live the infamous CSS hack by @kuzmoyev. \o/

@pradyunsg
Copy link
Contributor

@leycec I take it that you have not seen #11011? :)

@leycec
Copy link

leycec commented Mar 1, 2023

Gah! That's fantastic. I am, as always, profoundly ignorant of everything not @beartype (i.e., basically everything). Thanks a heap for both that sanity check and the immense volunteerism you do here and elsewhere, @pradyunsg.

May @TLouf go down in Sphinx history as the blessed saviour of readable APIs.

@picnixz
Copy link
Member

picnixz commented Aug 10, 2023

Closing as it is now supported since 7.1. Thank you again TLouf for that huge work!

@picnixz picnixz closed this as completed Aug 10, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 10, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
domains:c type:enhancement enhance or introduce a new feature
Projects
None yet
Development

No branches or pull requests