Navigating HTML docs vs generated interfaces

Moderator comment: it was requested this topic, which was originally a thread on the SE-0406 review, be broken out into its own thread as it’s not really related to that review, but is a useful discussion of the benefits of navigating a module’s interface+doc comments, vs the HTML documentation generated from it with DocC (apologies if I’ve botched that summary description, if so LMK)
@Ben_Cohen

I concur. The documentation in HTML form isn't always easy to navigate; sometimes it's faster and more intuitive to just jump to the declarations in code. And those should be presented in descending order of preference / likelihood of use.

I had similar thoughts about the proposal document itself - it'd make more sense to start with the async API. It's simpler and what most people should be using, since it's more fool-proof, and afterwards the proposal could note that there's a lower-level version that's more work to use but might be necessary for performance in some cases.

4 Likes

for curiosity's sake, how do you view standard library declarations anyway? if i remember right, in VSCode, ctrl-click to definition just drops you off at the top of the swiftinterface file, it only shows declarations for SPM package dependencies.

1 Like

putting on my @swift-documentation-workgroup hat for a moment, do you mind elaborating a bit more about the difficulties you've had navigating the web documentation? that would of course really help us improve them.

Hard to say in brief because it's tightly coupled to other aspects of documentation, like whether it exists to begin with, how it's organised when people use explicit symbol orders, etc.

Don't get me wrong - the HTML documentation is sometimes superior, especially for guides as opposed to API references, since guides (or "howtos") tend to benefit from being able to inline images, tables, etc and from having more pleasing text aesthetics.

Though in practice most guides aren't in Apple's docs anyway, but rather written by 3rd parties on blogs etc (I can't count the number of times I've thought "why the heck isn't this blog post just placed exactly as-is into the official docs?!").

And the option-click documentation in Xcode is great (when it works). I use that a lot.

But particularly for things like "what does this method do again?" or "what's the API for this type?", a few things that come to mind about the HTML docs that are at least somewhat relevant to the original point:

  • Symbol order is alphabetical by default, rather than the order in the original file. Which pretty much guarantees that things are completely disorganised unless the author goes through the hassle of manually creating (and then forever maintaining) a special DocC file (for every source file!).

    The older doc styles (HeaderDoc era, at least) even took into account #pragma marks to establish section headers etc. As far as I can tell DocC doesn't support any things like that.

  • DocC doesn't generate docs for some things (e.g. extensions for types declared in other modules), so it's literally unusable for some stuff.

  • HTML documentation involves a lot of clicking through nested pages, e.g. first the module then a type then its nested enum then a specific value. In some cases a partial description is inlined to the 'parent' page, but not always and not if the description is more than one paragraph.

    The original source is a single linear list (with the option to fold some of it away, within Xcode, if you really want). Much faster to read through and navigate.

    I don't specifically recall anymore, but I feel like the older HTML generator (HeaderDoc era, at least) produced much more practical layouts, in this respect. I seem to remember being able to get everything I needed about a class in a single page, e.g. being able expand a method definition inline rather than having to click through to a whole separate page. I might be remembering a 3rd party rendering of the docs, though…?

  • Compounding the above, Xcode almost never updates the cursor in the table of contents (left-hand pane in its documentation browser) as you click around in the "details" (right-hand pane). So navigating to nearby documentation entries, from whatever you're currently looking at in the main pane, is more difficult than it should be.

    There is the "breadcumb" view in Xcode's doc viewer, above the main pane, but interacting with deeply nested, very long menus is finicky.

  • Xcode's documentation browser doesn't support the most basic keyboard shortcuts, like command-arrows to go forward & back between pages. I'm vaguely aware that command-control-arrows is nominally the standard shortcut on macOS, but Safari doesn't use it, so, it's not.

  • Cross-references often don't work, for reasons unknown, in the HTML docs (and nothing is cross-reference unless the author explicitly makes it so, which most authors do too sparingly). Some are flat-out not supported (e.g. references across modules and/or packages?) while others just mysteriously don't work even though seemingly they should (don't have an example handy, but I hit this quite often).

    Command-double-clicking in Xcode is also sadly not reliable, but in my experience it's at least more reliable and it nominally works on any symbol anywhere.

That all said here, perhaps one of the forums admins can move this to a new thread, if you want to continue on this tangent. I don't want to distract from the actual proposal at hand. :slightly_smiling_face:

8 Likes

thanks for taking the time to write all this out! i’m going to try and go through these one at a time.

in general, i agree that the curation story is not ideal. there are a couple facets to this problem:

  • declaration order doesn’t generalize to modules that have more than one source file, which is to say, nearly all of them. there are some convenient situations where we can use declaration order, such as enum cases, which are always guaranteed to appear in exactly one source file. (and i have implemented this on swiftinit.org, example: https://swiftinit.org/docs/swift-nio/niocore/niothrowingasyncsequenceproducer/source/yieldresult .) but there is no intrinsic ordering of symbols we could use that makes sense to readers of documentation.

  • ideally, modules should not grow to the size where you need topics listings for anything other than the top-level namespace of the module. there is a sort of pattern i am seeing more and more of where people just define one set of top-level groupings and that serves as the partitioning model for the module with no additional levels of subdivisions. and i think that is the direction we should be nudging people towards, rather than building tooling that encourages bad practices.

i believe (@ethankusters might be able to confirm) that recent versions of DocC do now emit doc pages for extensions. DocC is still inherently a single-target documentation compiler, so it is not as easy to navigate as we would like, but the best solution for this problem specifically is going to be to upgrade the version of DocC you are using.

that sounds like the Jazzy rendering model. my main concern with this idea is that many types (e.g. Sequence) have a huge amount of API, and we don’t want to return all that data for a single query.

my opinion is that the ideal solution is going to involve some combination of an easily-accessible search feature and a pre-expanded sidebar that lists all the types in the current module. (which is, in fact, pretty much how Jazzy worked before everyone started using DocC.)

i can’t really talk about XCode, as i don’t use it, and VSCode has no corresponding in-editor browsing feature.

cross-module references don’t work in DocC because DocC is a single-target documentation compiler. multi-target documentation compilers, like the one that populates the swiftinit database have always supported this, (swiftinit can even validate and resolve cross-package symbol links), but unsuprisingly few package authors use this feature because DocC emits unresolved reference warnings for cross-module and cross-package symbol links.

i think it would be a great idea if DocC had an option to suppress such warnings, or at least reroute them to a JSON file or something.

1 Like

But declaration order within any given file is usually significant (or at least it should be, if the author is mindful). So DocC could e.g. put the primary file first (the one that actually declares the type) in all cases, and then follow up with the rest in some arbitrary order (e.g. alphabetical on file name). In each case keeping the symbols within each file in their declaration order.

Since often each extension file contains a distinct but coherent subset of the type - e.g. adding in all the properties & methods related to a particular aspect of its use - the result will likely be a decent approximation of how a human might manually categorise the symbols anyway. Maybe in a different ordering of categories, but that's a less important part of the problem.

Ah-ha, now that I test it in Xcode 15 (beta 5) it does indeed now generate all my extensions' docs that were missing in Xcode 14 and earlier. Fantastic!

Alas swiftpackageindex.com apparently is still using an older version of DocC as my documentation is still largely missing there, but that'll be fixed once Xcode 15 exits beta, I assume. @daveverwer & @finestructure can correct me if I'm wrong.

I remembered one additional thing: DocC's formatting of methods is pretty awful on the struct / class page, e.g.:

Mystifyingly it does a much better job on the detail page for a specific method; I don't know why it doesn't render it the same in both places.

@Ben_Cohen, may I please trouble you to move this tangential thread to its own topic? From this post up to # 38.

2 Likes

Alas swiftpackageindex.com apparently is still using an older version of DocC as my documentation is still largely missing there, but that'll be fixed once Xcode 15 exits beta, I assume. @daveverwer & @finestructurecan correct me if I'm wrong.

By default we're generating docs with Swift 5.8 but package authors can opt into using 5.9 by specifying it in your .spi.yml file:

version: 1
builder:
  configs:
    - documentation_targets: [Target1, Target2]
      swift_version: 5.9

Hope that helps!

2 Likes

for curiosity's sake, how do you view standard library declarations anyway? if i remember right, in VSCode, ctrl-click to definition just drops you off at the top of the swiftinterface file, it only shows declarations for SPM package dependencies.

LOL – this is like the "so, how do you walk" question! I've no idea which buttons I press … give me a minute to check :slight_smile:

…Right, in my Xcode setup, it's ⌘ + Click the symbol name. The example I tried with was DateFormatter, which takes me to what I assume is a generated file – it just shows the interface declaration without an implementation.

This is always my first avenue of investigation and I personally almost never go to the HTML documentation. I definitely do not want to claim this is a good work flow or approach! It's just the habit I have and potentially I should be nudged in a different direction.

I also make a lot of use of the pop up dock string help dialogue (I don't know the name of this feature). In my Xcode my setup, this is ⌥ + Click symbol name.

2 Likes

just an update, i tried implementing this for abridged signatures on swiftinit (using the DocC PR as inspiration) and ran into the issue where it is impossible to parse the argument boundaries in an abridged signature without argument labels:

for example, in an abridged signature like:

func transform<ElementOfResult>(
    (Self.Index, Self.Element) throws -> ElementOfResult?, 
    (Self.Index, Self.Element) throws -> ElementOfResult?
) rethrows -> [ElementOfResult]

it is impossible to compute the locations of the linebreaks without the AST of the declaration, which has long been lost by the time we get the symbol information from a symbol graph.

for comparison, it is possible to compute the linebreaks from the full signature without the AST because we can pick out the externalParam tokens and insert linebreaks before them.

func transform<ElementOfResult>(
    _ a:(Self.Index, Self.Element) throws -> ElementOfResult?, 
//  ^ linebreak
    _ b:(Self.Index, Self.Element) throws -> ElementOfResult?
//  ^ linebreak
) rethrows -> [ElementOfResult]
// line break before parenthesis can be computed by counting parentheses

it would be interesting if SymbolGraphGen could emit the declaration AST instead of flattening it into tokens. but that is blocked until we have a way to serialize syntax trees to JSON.

alternatively, we could throw away the SymbolGraphGen tokens entirely and reparse at documentation-compile time with the SwiftSyntax library. i am reluctant to do this because SwiftSyntax is enormous and takes ages to compile.