To provide HTML links to an Inform source text, using the non-standard "source" protocol.

§1. Index and Problem pages generated by inform7, via the index and problems modules, frequently need to have clickable icons linking to positions in the source text; when these are clicked in the Inform UI applications, the Source panel opens at the appropriate line.

This is done with a non-standard HTML protocol called source:, one of two which the Inform UI apps must provide special handling for. For instance, line 21 of file Bits and Pieces/marbles.txt has URL:

    source:Bits and Pieces/marbles.txt#line14

When an Inform app is viewing an extension project, there can also be references to individual examples in that extension, and these are referred to as "cases" since they are test cases when the app is testing the extension. For example,

    source:Locksmith.i7x?case=B#line7

refers to line 7 of Example B of Locksmith.i7x.

§2. Locations are given relative to the current project bundle. However, if only a leafname is supplied, then this is read as a file within the Source subfolder of the project bundle. (Thus it is not possible to have a source link to a source file at the root of the project bundle: but this is no loss, since source is not allowed to be kept there.) For instance, line 14 of file Source/story.ni has URL

    source:story.ni#line14

The following routine writes the clickable source-reference icon, and is the only place in Inform where source: URLs are generated.

inchar32_t source_link_case = 0;
void SourceLinks::set_case(text_stream *p) {
    source_link_case = Characters::toupper(Str::get_first_char(p));
}

void SourceLinks::link(OUTPUT_STREAM, source_location sl, int nonbreaking_space) {
    if (sl.file_of_origin) {
        TEMPORARY_TEXT(fn)
        WRITE_TO(fn, "%f", TextFromFiles::get_filename(sl.file_of_origin));
        Truncate the path according to the current abbreviation2.1;
        Remove indication that the path is inside the Source directory2.2;
        if (nonbreaking_space) WRITE(" "); else WRITE(" ");
        if (source_link_case)
            HTML_OPEN_WITH("a", "href=\"source:%S?case=%c#line%d\"",
                fn, source_link_case, sl.line_number)
        else
            HTML_OPEN_WITH("a", "href=\"source:%S#line%d\"", fn, sl.line_number);
        HTML_TAG_WITH("img", "border=0 src=inform:/doc_images/Reveal.png");
        HTML_CLOSE("a");
        DISCARD_TEXT(fn)
    }
}

§2.1. This rather clumsily simplifies links to make them relative.

Truncate the path according to the current abbreviation2.1 =

    pathname *abbrev = HTML::get_link_abbreviation_path();
    if (abbrev) {
        TEMPORARY_TEXT(pp)
        WRITE_TO(pp, "%p", abbrev);
        int N = Str::len(pp);
        if (Str::prefix_eq(fn, pp, N)) Str::delete_n_characters(fn, N+1);
        DISCARD_TEXT(pp)
    }

§2.2. Remove indication that the path is inside the Source directory2.2 =

    if ((Str::begins_with_wide_string(fn, U"Source")) &&
        (Platform::is_folder_separator(Str::get_at(fn, 6))))
        Str::delete_n_characters(fn, 7);