From 0d8d6606c3b61a377fcb2fe51720174ef1e65fae Mon Sep 17 00:00:00 2001 From: Graham Nelson Date: Sun, 30 Jul 2023 19:07:54 +0100 Subject: [PATCH] Added open-source-text links to Extensions home tab --- README.md | 2 +- build.txt | 4 +- docs/supervisor-module/2-ce.html | 2 +- docs/supervisor-module/2-cps.html | 4 +- docs/supervisor-module/2-rqr.html | 2 +- docs/supervisor-module/4-ebm.html | 6 +- docs/supervisor-module/4-em.html | 4 +- docs/supervisor-module/5-es.html | 73 +++++++++++-------- docs/supervisor-module/5-ps2.html | 4 +- docs/supervisor-module/6-hdn.html | 2 +- docs/supervisor-module/6-inc.html | 6 +- docs/supervisor-module/6-st.html | 4 +- docs/supervisor-module/7-dc.html | 2 +- docs/supervisor-module/7-dr.html | 2 +- docs/supervisor-module/7-eip.html | 39 ++++++---- docs/supervisor-module/7-tc.html | 2 +- docs/supervisor-module/7-ti.html | 4 +- docs/supervisor-module/7-tm.html | 2 +- .../Chapter 5/Extension Services.w | 10 +++ .../Chapter 7/Extensions Index Page.w | 33 ++++++--- inform7/Figures/memory-diagnostics.txt | 6 +- inform7/Figures/timings-diagnostics.txt | 16 ++-- .../Inter/Architecture16Kit/kit_metadata.json | 2 +- .../Inter/Architecture32Kit/kit_metadata.json | 2 +- .../Inter/BasicInformKit/kit_metadata.json | 2 +- .../Inter/CommandParserKit/kit_metadata.json | 2 +- .../EnglishLanguageKit/kit_metadata.json | 2 +- .../Inter/WorldModelKit/kit_metadata.json | 2 +- 28 files changed, 144 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 3c42b712a..c73266a37 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Inform 7 -[Version](notes/versioning.md): 10.2.0-beta+6W92 'Krypton' (28 July 2023) +[Version](notes/versioning.md): 10.2.0-beta+6W93 'Krypton' (30 July 2023) ## About Inform diff --git a/build.txt b/build.txt index 15b73a9ec..edc866430 100644 --- a/build.txt +++ b/build.txt @@ -1,3 +1,3 @@ Prerelease: beta -Build Date: 28 July 2023 -Build Number: 6W92 +Build Date: 30 July 2023 +Build Number: 6W93 diff --git a/docs/supervisor-module/2-ce.html b/docs/supervisor-module/2-ce.html index 7b57f97e2..7cbccbc37 100644 --- a/docs/supervisor-module/2-ce.html +++ b/docs/supervisor-module/2-ce.html @@ -127,7 +127,7 @@ fields are blank. return CE; } -copy_error *CopyErrors::new_T(int cat, int subcat, text_stream *NB) { +copy_error *CopyErrors::new_T(int cat, int subcat, text_stream *NB) { copy_error *CE = CopyErrors::new(cat, subcat); CE->details = Str::duplicate(NB); return CE; diff --git a/docs/supervisor-module/2-cps.html b/docs/supervisor-module/2-cps.html index 365f6bf28..a45e6731b 100644 --- a/docs/supervisor-module/2-cps.html +++ b/docs/supervisor-module/2-cps.html @@ -156,7 +156,7 @@ for later reporting. These are stored in a list.

-void Copies::attach_error(inbuild_copy *C, copy_error *CE) {
+void Copies::attach_error(inbuild_copy *C, copy_error *CE) {
     if (C == NULL) internal_error("no copy to attach to");
     CopyErrors::supply_attached_copy(CE, C);
     ADD_TO_LINKED_LIST(CE, copy_error, C->errors_reading_source_text);
@@ -210,7 +210,7 @@ for later reporting. These are stored in a list.
     return C->source_text_read;
 }
 
-wording Copies::get_source_text(inbuild_copy *C, text_stream *reason) {
+wording Copies::get_source_text(inbuild_copy *C, text_stream *reason) {
     if (C->source_text_read == FALSE) {
         C->source_text_read = TRUE;
         if (LinkedLists::len(C->errors_reading_source_text) > 0) {
diff --git a/docs/supervisor-module/2-rqr.html b/docs/supervisor-module/2-rqr.html
index 5f2146d47..94f6a0fb1 100644
--- a/docs/supervisor-module/2-rqr.html
+++ b/docs/supervisor-module/2-rqr.html
@@ -243,7 +243,7 @@ a requirement, then so will all other copies of it.
 

-int Requirements::meets(inbuild_edition *edition, inbuild_requirement *req) {
+int Requirements::meets(inbuild_edition *edition, inbuild_requirement *req) {
     if (req == NULL) return TRUE;
     if (req->work) {
         if (req->work->genre)
diff --git a/docs/supervisor-module/4-ebm.html b/docs/supervisor-module/4-ebm.html
index c82f9a835..c160b176c 100644
--- a/docs/supervisor-module/4-ebm.html
+++ b/docs/supervisor-module/4-ebm.html
@@ -137,7 +137,7 @@ which stores data about extensions used by the Inform compiler.
                 if (Str::get(pos) == '.')
                     Str::put(pos, '_');
             WRITE_TO(new_name, "%S-v%S.i7xd", C->edition->work->title, new_version);
-            if (Extensions::rename_directory(P, new_name)) {
+            if (Extensions::rename_directory(P, new_name)) {
                 Str::clear(key);
                 WRITE_TO(key, "%p", P);
                 apparent_V = C->edition->version;
@@ -559,14 +559,14 @@ directory, we need to rsync
 void ExtensionBundleManager::document(inbuild_genre *gen, inbuild_copy *C, pathname *dest) {
-    Extensions::document(Extensions::from_copy(C), dest);
+    Extensions::document(Extensions::from_copy(C), dest);
 }
 

§11. Modernisation.

 int ExtensionBundleManager::modernise(inbuild_genre *gen, inbuild_copy *C, text_stream *OUT) {
-    return Extensions::modernise(Extensions::from_copy(C), OUT);
+    return Extensions::modernise(Extensions::from_copy(C), OUT);
 }
 
-

§11. In directory extensions, documentation can be stored separately: +

§11. And here's the top line:

-compiled_documentation *Extensions::get_documentation(inform_extension *E) {
+source_location Extensions::top_line_location(inform_extension *E) {
+    source_location sl;
+    sl.file_of_origin = E->read_into_file;
+    sl.line_number = 1;
+    return sl;
+}
+
+

§12. In directory extensions, documentation can be stored separately: +

+ +
+compiled_documentation *Extensions::get_documentation(inform_extension *E) {
     if (E == NULL) return NULL;
     Copies::get_source_text(E->as_copy, I"getting documentation");  in the unlikely event this has not happened yet
     if (E->documentation_sought == FALSE) {
         if (E->as_copy->location_if_path) {
             pathname *D = Pathnames::down(E->as_copy->location_if_path, I"Documentation");
-            if (Directories::exists(D)) Fetch wording from stand-alone directory11.1;
+            if (Directories::exists(D)) Fetch wording from stand-alone directory12.1;
         }
         E->documentation_sought = TRUE;
     }
     return E->documentation;
 }
 
-

§11.1. Fetch wording from stand-alone directory11.1 = +

§12.1. Fetch wording from stand-alone directory12.1 =

@@ -1039,24 +1050,24 @@ then its sentences will go to the extension's own tree.
         E->documentation = DocumentationCompiler::compile_from_path(D, E);
     }
 
- -

§12. And this serves the -document feature of inbuild: +

+

§13. And this serves the -document feature of inbuild:

-void Extensions::document(inform_extension *E, pathname *dest) {
+void Extensions::document(inform_extension *E, pathname *dest) {
     SVEXPLAIN(1, "(documenting %X to %p)\n", E->as_copy->edition->work, dest);
-    compiled_documentation *cd = Extensions::get_documentation(E);
+    compiled_documentation *cd = Extensions::get_documentation(E);
     DocumentationRenderer::as_HTML(dest, cd, NULL);
 }
 
-

§13. When the extension source text was read from its source_file, we +

§14. When the extension source text was read from its source_file, we attached a reference to say which inform_extension it was, and here we make use of that:

-inform_extension *Extensions::corresponding_to(source_file *sf) {
+inform_extension *Extensions::corresponding_to(source_file *sf) {
     if (sf == NULL) return NULL;
     inbuild_copy *C = RETRIEVE_POINTER_inbuild_copy(sf->your_ref);
     if (C == NULL) return NULL;
@@ -1065,7 +1076,7 @@ make use of that:
     return Extensions::from_copy(C);
 }
 
-

§14. Miscellaneous.

+

§15. Miscellaneous.

 void Extensions::write(OUTPUT_STREAM, inform_extension *E) {
@@ -1081,7 +1092,7 @@ make use of that:
     WRITE("%S", E->as_copy->edition->work->raw_author_name);
 }
 
-

§15. Three pieces of information will be set later on, by other parts of Inform +

§16. Three pieces of information will be set later on, by other parts of Inform calling the routines below.

@@ -1099,12 +1110,12 @@ that happens, the following function will be called to set the rubric. LOGIF(EXTENSIONS_CENSUS, "Extension rubric: %S\n", E->rubric_as_lexed); } -text_stream *Extensions::get_rubric(inform_extension *E) { +text_stream *Extensions::get_rubric(inform_extension *E) { if (E == NULL) return NULL; return E->rubric_as_lexed; } -

§16. The optional extra credit line is used to acknowledge I6 sources, +

§17. The optional extra credit line is used to acknowledge I6 sources, collaborators, translators and so on.

@@ -1115,7 +1126,7 @@ collaborators, translators and so on. LOGIF(EXTENSIONS_CENSUS, "Extension extra credit: %S\n", E->extra_credit_as_lexed); } -

§17. The use option "authorial modesty" is unusual in applying to the extension +

§18. The use option "authorial modesty" is unusual in applying to the extension it is found in, not the whole source text. When we read it, we call one of the following routines, depending on whether it was in an extension or in the main source text: @@ -1131,21 +1142,21 @@ the main source text: general_authorial_modesty = TRUE; } -

§18. The inclusion sentence for an extension is where it was Included in a +

§19. The inclusion sentence for an extension is where it was Included in a project's syntax tree (if it was). It isn't used in compilation, only for problem messages and the index.

-void Extensions::set_inclusion_sentence(inform_extension *E, parse_node *N) {
+void Extensions::set_inclusion_sentence(inform_extension *E, parse_node *N) {
     E->inclusion_sentence = N;
 }
-parse_node *Extensions::get_inclusion_sentence(inform_extension *E) {
+parse_node *Extensions::get_inclusion_sentence(inform_extension *E) {
     if (E == NULL) return NULL;
     return E->inclusion_sentence;
 }
 
-

§19. An extension is "standard" if it's either the Standard Rules or Basic Inform. +

§20. An extension is "standard" if it's either the Standard Rules or Basic Inform.

@@ -1154,33 +1165,33 @@ problem messages and the index.
     return E->standard;
 }
 
-

§20. Version requirements. When it's known that an extension must satisfy a given version requirement — +

§21. Version requirements. When it's known that an extension must satisfy a given version requirement — say, being version 7.2.1 or better — the following is called. Note that if incompatible requirements are placed on it, the range in E->must_satisfy becomes empty and stays that way.

-void Extensions::must_satisfy(inform_extension *E, inbuild_requirement *req) {
+void Extensions::must_satisfy(inform_extension *E, inbuild_requirement *req) {
     if (E->must_satisfy == NULL) E->must_satisfy = req;
     else VersionNumberRanges::intersect_range(E->must_satisfy->version_range, req->version_range);
 }
 
-

§21. And it is certainly possible, if an extension is loaded for multiple +

§22. And it is certainly possible, if an extension is loaded for multiple reasons with different versioning needs, that the extension no longer meets its requirements (even though it did when first loaded). This tests for that:

-int Extensions::satisfies(inform_extension *E) {
+int Extensions::satisfies(inform_extension *E) {
     if (E == NULL) return FALSE;
     return Requirements::meets(E->as_copy->edition, E->must_satisfy);
 }
 
-

§22. File hierarchy tidying.

+

§23. File hierarchy tidying.

-int Extensions::rename_directory(pathname *P, text_stream *new_name) {
+int Extensions::rename_directory(pathname *P, text_stream *new_name) {
     TEMPORARY_TEXT(task)
     WRITE_TO(task, "(Changing directory name '%p' to '%S')\n", P, new_name);
     int rv = Directories::rename(P, new_name);
@@ -1188,7 +1199,7 @@ its requirements (even though it did when first loaded). This tests for that:
     return rv;
 }
 
-int Extensions::rename_file(filename *F, text_stream *new_name) {
+int Extensions::rename_file(filename *F, text_stream *new_name) {
     TEMPORARY_TEXT(task)
     WRITE_TO(task, "(Changing file name '%f' to '%S')\n", F, new_name);
     int rv = Filenames::rename(F, new_name);
@@ -1196,10 +1207,10 @@ its requirements (even though it did when first loaded). This tests for that:
     return rv;
 }
 
-

§23. Modernisation.

+

§24. Modernisation.

-int Extensions::modernise(inform_extension *E, text_stream *OUT) {
+int Extensions::modernise(inform_extension *E, text_stream *OUT) {
     if (E->as_copy->edition->work->genre == extension_bundle_genre) {
         WRITE("already in directory format\n");
         return TRUE;
diff --git a/docs/supervisor-module/5-ps2.html b/docs/supervisor-module/5-ps2.html
index a13ec439b..108a0b4d9 100644
--- a/docs/supervisor-module/5-ps2.html
+++ b/docs/supervisor-module/5-ps2.html
@@ -1165,10 +1165,10 @@ each extension against the intersection of all requirements put on it:
 void Projects::check_extension_versions_d(inform_project *proj, build_vertex *V) {
     if ((V->as_copy) && (V->as_copy->edition->work->genre == extension_genre)) {
         inform_extension *E = Extensions::from_copy(V->as_copy);
-        if (Extensions::satisfies(E) == FALSE) {
+        if (Extensions::satisfies(E) == FALSE) {
             copy_error *CE = CopyErrors::new_T(SYNTAX_CE, ExtVersionTooLow_SYNERROR,
                 I"two incompatible versions");
-            CopyErrors::supply_node(CE, Extensions::get_inclusion_sentence(E));
+            CopyErrors::supply_node(CE, Extensions::get_inclusion_sentence(E));
             Copies::attach_error(proj->as_copy, CE);
         }
     }
diff --git a/docs/supervisor-module/6-hdn.html b/docs/supervisor-module/6-hdn.html
index 49359043d..29cf57491 100644
--- a/docs/supervisor-module/6-hdn.html
+++ b/docs/supervisor-module/6-hdn.html
@@ -789,7 +789,7 @@ file and a line number of at least 1).
 
 inform_extension *Headings::get_extension_containing(heading *h) {
     if ((h == NULL) || (h->start_location.file_of_origin == NULL)) return NULL;
-    return Extensions::corresponding_to(h->start_location.file_of_origin);
+    return Extensions::corresponding_to(h->start_location.file_of_origin);
 }
 

§19. Although Implied (0) headings do have text, contrary to the implication of diff --git a/docs/supervisor-module/6-inc.html b/docs/supervisor-module/6-inc.html index 1db23df2a..96c7c1dce 100644 --- a/docs/supervisor-module/6-inc.html +++ b/docs/supervisor-module/6-inc.html @@ -326,7 +326,7 @@ Sausages by Mr Punch, and loaded it, but then read the sentence LOOP_OVER_LINKED_LIST(E, inform_extension, for_project->extensions_included) if ((Requirements::meets(E->as_copy->edition, req)) && (Copies::source_text_has_been_read(E->as_copy))) { - Extensions::must_satisfy(E, req); + Extensions::must_satisfy(E, req); return E; } Read the extension file into the lexer, and break it into body and documentation6.1; @@ -344,7 +344,7 @@ Sausages by Mr Punch, and loaded it, but then read the sentence Nests::search_for_best(req, Projects::nest_list(for_project)); if (search_result) { E = Extensions::from_copy(search_result->copy); - Extensions::set_inclusion_sentence(E, at); + Extensions::set_inclusion_sentence(E, at); Extensions::set_associated_project(E, for_project); if (Nests::get_tag(search_result->nest) == INTERNAL_NEST_TAG) E->loaded_from_built_in_area = TRUE; @@ -379,7 +379,7 @@ report this problem at the inclusion line.

     copy_error *CE = CopyErrors::new_T(SYNTAX_CE, ExtInadequateVM_SYNERROR, C->parsed_from);
-    CopyErrors::supply_node(CE, Extensions::get_inclusion_sentence(E));
+    CopyErrors::supply_node(CE, Extensions::get_inclusion_sentence(E));
     Copies::attach_error(inclusions_errors_to, CE);
 
diff --git a/docs/supervisor-module/6-st.html b/docs/supervisor-module/6-st.html index 09c541471..655d96935 100644 --- a/docs/supervisor-module/6-st.html +++ b/docs/supervisor-module/6-st.html @@ -171,7 +171,7 @@ source files. paraphrase = I"source text"; inform_extension *E = NULL; if (referred) { - E = Extensions::corresponding_to(referred); + E = Extensions::corresponding_to(referred); } else { TEMPORARY_TEXT(matched_filename) inform_extension *F; @@ -202,7 +202,7 @@ source files.
 void SourceText::gloss_extension(text_stream *OUT, source_file *referred) {
-    inform_extension *E = Extensions::corresponding_to(referred);
+    inform_extension *E = Extensions::corresponding_to(referred);
     if (E) WRITE(" in the extension %X", E->as_copy->edition->work);
 }
 
diff --git a/docs/supervisor-module/7-dc.html b/docs/supervisor-module/7-dc.html index 7a8fb15f8..0ad4802ff 100644 --- a/docs/supervisor-module/7-dc.html +++ b/docs/supervisor-module/7-dc.html @@ -107,7 +107,7 @@ it's really not much more than the tree produced by -compiled_documentation *DocumentationCompiler::compile_from_path(pathname *P, +compiled_documentation *DocumentationCompiler::compile_from_path(pathname *P, inform_extension *associated_extension) { filename *F = Filenames::in(P, I"Documentation.txt"); if (TextFiles::exists(F) == FALSE) return NULL; diff --git a/docs/supervisor-module/7-dr.html b/docs/supervisor-module/7-dr.html index 2899ba79b..8e4277711 100644 --- a/docs/supervisor-module/7-dr.html +++ b/docs/supervisor-module/7-dr.html @@ -137,7 +137,7 @@ except the examples, and then up to 26 pages holding the content of examples A t

-void DocumentationRenderer::as_HTML(pathname *P, compiled_documentation *cd, text_stream *extras) {
+void DocumentationRenderer::as_HTML(pathname *P, compiled_documentation *cd, text_stream *extras) {
     if (cd) {
         text_stream *OUT = DocumentationRenderer::open_subpage(P, I"index.html");
         if (OUT) {
diff --git a/docs/supervisor-module/7-eip.html b/docs/supervisor-module/7-eip.html
index ea0dc722f..27bbc9baa 100644
--- a/docs/supervisor-module/7-eip.html
+++ b/docs/supervisor-module/7-eip.html
@@ -473,7 +473,18 @@ the usual ones seen in Mac OS X applications such as iTunes.
 

+    inform_extension *E = Extensions::from_copy(res->copy);
+
     HTML::begin_span(OUT, I"extensionindexentry");
+    if (LinkedLists::len(res->copy->errors_reading_source_text) == 0) {
+        source_location sl = Extensions::top_line_location(E);
+        if (sl.file_of_origin) {
+            ExtensionIndex::add_to_key(key_list, REVEAL_SYMBOL,
+                I"Open source (left of title: the whole extension; right: where it is Included");
+            SourceLinks::link(OUT, sl, FALSE);
+            WRITE(" ");
+        }
+    }
     if (d != SORT_CE_BY_AUTHOR) {
         WRITE("%S", res->copy->edition->work->raw_title);
         if (Nests::get_tag(res->nest) != INTERNAL_NEST_TAG)
@@ -499,14 +510,14 @@ the usual ones seen in Mac OS X applications such as iTunes.
         HTML_CLOSE("a");
     }
 
-    inform_extension *E = Extensions::from_copy(res->copy);
-    parse_node *at = Extensions::get_inclusion_sentence(E);
+    parse_node *at = Extensions::get_inclusion_sentence(E);
     if (at) {
         wording W = Node::get_text(at);
         source_location sl = Lexer::word_location(Wordings::first_wn(W));
         if (sl.file_of_origin) {
             SourceLinks::link(OUT, sl, TRUE);
-            ExtensionIndex::add_to_key(key_list, REVEAL_SYMBOL, I"Included here (click to see)");
+            ExtensionIndex::add_to_key(key_list, REVEAL_SYMBOL,
+                I"Open source (left of title: the whole extension; right: where it is Included");
         }
     }
 
@@ -514,14 +525,16 @@ the usual ones seen in Mac OS X applications such as iTunes.
         WRITE(" ");
         HTML_TAG_WITH("img", "%s", PROBLEM_SYMBOL);
         ExtensionIndex::add_to_key(key_list, PROBLEM_SYMBOL, I"Has errors (see below)");
-    } else if (usage_state == FALSE) {
-        WRITE(" ");
-        TEMPORARY_TEXT(inclusion_text)
-        WRITE_TO(inclusion_text, "Include %X.\n\n\n", res->copy->edition->work);
-        ExtensionWebsite::paste_button(OUT, inclusion_text);
-        DISCARD_TEXT(inclusion_text)
-        ExtensionIndex::add_to_key(key_list, PASTE_SYMBOL,
-            I"Source text to Include this (click to paste in)");
+    } else {
+        if (usage_state == FALSE) {
+            WRITE(" ");
+            TEMPORARY_TEXT(inclusion_text)
+            WRITE_TO(inclusion_text, "Include %X.\n\n\n", res->copy->edition->work);
+            ExtensionWebsite::paste_button(OUT, inclusion_text);
+            DISCARD_TEXT(inclusion_text)
+            ExtensionIndex::add_to_key(key_list, PASTE_SYMBOL,
+                I"Source text to Include this (click to paste in)");
+        }
     }
 
     compatibility_specification *C = res->copy->edition->compatibility;
@@ -593,7 +606,7 @@ the first and last word and just look at what is in between:
         else
             WRITE("Installed in this project");
     } else {
-        text_stream *R = Extensions::get_rubric(Extensions::from_copy(res->copy));
+        text_stream *R = Extensions::get_rubric(Extensions::from_copy(res->copy));
         if (Str::len(R) > 0) WRITE("%S", R); else WRITE("--");
     }
     HTML::end_span(OUT);
@@ -604,7 +617,7 @@ the first and last word and just look at what is in between:
 
 
     HTML::begin_span(OUT, I"smaller");
-    text_stream *R = Extensions::get_rubric(Extensions::from_copy(res->copy));
+    text_stream *R = Extensions::get_rubric(Extensions::from_copy(res->copy));
     if (Str::len(R) > 0) WRITE("%S", R); else WRITE("--");
     HTML::end_span(OUT);
 
diff --git a/docs/supervisor-module/7-tc.html b/docs/supervisor-module/7-tc.html index ae7499203..bb04cbb00 100644 --- a/docs/supervisor-module/7-tc.html +++ b/docs/supervisor-module/7-tc.html @@ -65,7 +65,7 @@ a text file or directory.

-void ExtensionConverter::go(inform_extension *E, text_stream *OUT) {
+void ExtensionConverter::go(inform_extension *E, text_stream *OUT) {
     Extensions::read_source_text_for(E);
 
     TEMPORARY_TEXT(dirname)
diff --git a/docs/supervisor-module/7-ti.html b/docs/supervisor-module/7-ti.html
index 827b1314b..6e0bb5d67 100644
--- a/docs/supervisor-module/7-ti.html
+++ b/docs/supervisor-module/7-ti.html
@@ -197,7 +197,7 @@ produces a second report.
     WRITE("Specifically:");
     HTML_CLOSE("p");
     Copies::list_attached_errors_to_HTML(OUT, C);
-    text_stream *rubric = Extensions::get_rubric(Extensions::from_copy(C));
+    text_stream *rubric = Extensions::get_rubric(Extensions::from_copy(C));
     if (Str::len(rubric) > 0) {
         WRITE("The extension says this about itself:");
         HTML_CLOSE("p");
@@ -213,7 +213,7 @@ produces a second report.
 
     HTML_OPEN("p");
     WRITE("This looks like a valid extension");
-    text_stream *rubric = Extensions::get_rubric(Extensions::from_copy(C));
+    text_stream *rubric = Extensions::get_rubric(Extensions::from_copy(C));
     if (Str::len(rubric) > 0) {
         WRITE(", and says this about itself:");
         HTML_CLOSE("p");
diff --git a/docs/supervisor-module/7-tm.html b/docs/supervisor-module/7-tm.html
index 722e97667..cec00d055 100644
--- a/docs/supervisor-module/7-tm.html
+++ b/docs/supervisor-module/7-tm.html
@@ -219,7 +219,7 @@ examples provided in the extension.
         if (F == NULL) return;
         pathname *P = Filenames::up(F);
         if (Pathnames::create_in_file_system(P) == 0) return;
-        compiled_documentation *doc = Extensions::get_documentation(E);
+        compiled_documentation *doc = Extensions::get_documentation(E);
         TEMPORARY_TEXT(OUT)
         #ifdef CORE_MODULE
         TEMPORARY_TEXT(details)
diff --git a/inbuild/supervisor-module/Chapter 5/Extension Services.w b/inbuild/supervisor-module/Chapter 5/Extension Services.w
index 0ec67dbf4..42a8ed45e 100644
--- a/inbuild/supervisor-module/Chapter 5/Extension Services.w	
+++ b/inbuild/supervisor-module/Chapter 5/Extension Services.w	
@@ -819,6 +819,16 @@ then its sentences will go to the extension's own tree.
 		E->body_text, E->as_copy, project);
 	E->body_text_unbroken = FALSE;
 
+@ And here's the top line:
+
+=
+source_location Extensions::top_line_location(inform_extension *E) {
+	source_location sl;
+	sl.file_of_origin = E->read_into_file;
+	sl.line_number = 1;
+	return sl;
+}
+
 @ In directory extensions, documentation can be stored separately:
 
 =
diff --git a/inbuild/supervisor-module/Chapter 7/Extensions Index Page.w b/inbuild/supervisor-module/Chapter 7/Extensions Index Page.w
index 704d89579..de76f5512 100644
--- a/inbuild/supervisor-module/Chapter 7/Extensions Index Page.w	
+++ b/inbuild/supervisor-module/Chapter 7/Extensions Index Page.w	
@@ -338,7 +338,18 @@ the usual ones seen in Mac OS X applications such as iTunes.
 	@;
 
 @ =
+	inform_extension *E = Extensions::from_copy(res->copy);
+
 	HTML::begin_span(OUT, I"extensionindexentry");
+	if (LinkedLists::len(res->copy->errors_reading_source_text) == 0) {
+		source_location sl = Extensions::top_line_location(E);
+		if (sl.file_of_origin) {
+			ExtensionIndex::add_to_key(key_list, REVEAL_SYMBOL,
+				I"Open source (left of title: the whole extension; right: where it is Included");
+			SourceLinks::link(OUT, sl, FALSE);
+			WRITE(" ");
+		}
+	}
 	if (d != SORT_CE_BY_AUTHOR) {
 		WRITE("%S", res->copy->edition->work->raw_title);
 		if (Nests::get_tag(res->nest) != INTERNAL_NEST_TAG)
@@ -364,14 +375,14 @@ the usual ones seen in Mac OS X applications such as iTunes.
 		HTML_CLOSE("a");
 	}
 
-	inform_extension *E = Extensions::from_copy(res->copy);
 	parse_node *at = Extensions::get_inclusion_sentence(E);
 	if (at) {
 		wording W = Node::get_text(at);
 		source_location sl = Lexer::word_location(Wordings::first_wn(W));
 		if (sl.file_of_origin) {
 			SourceLinks::link(OUT, sl, TRUE);
-			ExtensionIndex::add_to_key(key_list, REVEAL_SYMBOL, I"Included here (click to see)");
+			ExtensionIndex::add_to_key(key_list, REVEAL_SYMBOL,
+				I"Open source (left of title: the whole extension; right: where it is Included");
 		}
 	}
 
@@ -379,14 +390,16 @@ the usual ones seen in Mac OS X applications such as iTunes.
 		WRITE(" ");
 		HTML_TAG_WITH("img", "%s", PROBLEM_SYMBOL);
 		ExtensionIndex::add_to_key(key_list, PROBLEM_SYMBOL, I"Has errors (see below)");
-	} else if (usage_state == FALSE) {
-		WRITE(" ");
-		TEMPORARY_TEXT(inclusion_text)
-		WRITE_TO(inclusion_text, "Include %X.\n\n\n", res->copy->edition->work);
-		ExtensionWebsite::paste_button(OUT, inclusion_text);
-		DISCARD_TEXT(inclusion_text)
-		ExtensionIndex::add_to_key(key_list, PASTE_SYMBOL,
-			I"Source text to Include this (click to paste in)");
+	} else {
+		if (usage_state == FALSE) {
+			WRITE(" ");
+			TEMPORARY_TEXT(inclusion_text)
+			WRITE_TO(inclusion_text, "Include %X.\n\n\n", res->copy->edition->work);
+			ExtensionWebsite::paste_button(OUT, inclusion_text);
+			DISCARD_TEXT(inclusion_text)
+			ExtensionIndex::add_to_key(key_list, PASTE_SYMBOL,
+				I"Source text to Include this (click to paste in)");
+		}
 	}
 
 	compatibility_specification *C = res->copy->edition->compatibility;
diff --git a/inform7/Figures/memory-diagnostics.txt b/inform7/Figures/memory-diagnostics.txt
index 4a704a254..24d047dbc 100644
--- a/inform7/Figures/memory-diagnostics.txt
+++ b/inform7/Figures/memory-diagnostics.txt
@@ -1,4 +1,4 @@
-Total memory consumption was 139117K = 136 MB
+Total memory consumption was 139118K = 136 MB
 
  ---- was used for 2123163 objects, in 374917 frames in 0 x 800K = 0K = 0 MB:
 
@@ -261,9 +261,9 @@ Total memory consumption was 139117K = 136 MB
      ----  tree_type                                1 object, 40 bytes
      ----  I6_generation_data                       1 object, 40 bytes
 
-99.9% was used for memory not allocated for objects:
+100.0% was used for memory not allocated for objects:
 
-    62.1%  text stream storage                      88591296 bytes in 508365 claims
+    62.1%  text stream storage                      88592392 bytes in 508366 claims
      3.8%  dictionary storage                       5480960 bytes in 7754 claims
      ----  sorting                                  2624 bytes in 531 claims
      5.0%  source text                              7200000 bytes in 3 claims
diff --git a/inform7/Figures/timings-diagnostics.txt b/inform7/Figures/timings-diagnostics.txt
index accd5f382..c45645508 100644
--- a/inform7/Figures/timings-diagnostics.txt
+++ b/inform7/Figures/timings-diagnostics.txt
@@ -1,8 +1,8 @@
 100.0% in inform7 run
-     67.5% in compilation to Inter
-         45.9% in //Sequence::undertake_queued_tasks//
+     67.4% in compilation to Inter
+         45.5% in //Sequence::undertake_queued_tasks//
           4.2% in //MajorNodes::pre_pass//
-          3.8% in //MajorNodes::pass_1//
+          3.4% in //MajorNodes::pass_1//
           1.9% in //ImperativeDefinitions::assess_all//
           1.5% in //RTKindConstructors::compile//
           1.5% in //RTPhrasebook::compile_entries//
@@ -14,11 +14,11 @@
           0.3% in //Sequence::undertake_queued_tasks//
           0.3% in //Sequence::undertake_queued_tasks//
           0.3% in //World::stage_V//
-          4.6% not specifically accounted for
-     28.1% in running Inter pipeline
-          9.6% in step 14/15: generate inform6 -> auto.inf
-          6.9% in step 5/15: load-binary-kits
-          5.7% in step 6/15: make-synoptic-module
+          5.4% not specifically accounted for
+     28.3% in running Inter pipeline
+          9.5% in step 14/15: generate inform6 -> auto.inf
+          6.8% in step 5/15: load-binary-kits
+          6.1% in step 6/15: make-synoptic-module
           1.9% in step 9/15: make-identifiers-unique
           0.3% in step 12/15: eliminate-redundant-operations
           0.3% in step 4/15: compile-splats
diff --git a/inform7/Internal/Inter/Architecture16Kit/kit_metadata.json b/inform7/Internal/Inter/Architecture16Kit/kit_metadata.json
index f55190924..c75fb4a7a 100644
--- a/inform7/Internal/Inter/Architecture16Kit/kit_metadata.json
+++ b/inform7/Internal/Inter/Architecture16Kit/kit_metadata.json
@@ -2,7 +2,7 @@
     "is": {
         "type": "kit",
         "title": "Architecture16Kit",
-        "version": "10.2.0-beta+6W92"
+        "version": "10.2.0-beta+6W93"
     },
     "compatibility": "16-bit",
     "kit-details": {
diff --git a/inform7/Internal/Inter/Architecture32Kit/kit_metadata.json b/inform7/Internal/Inter/Architecture32Kit/kit_metadata.json
index 42b991fe7..f505095ca 100644
--- a/inform7/Internal/Inter/Architecture32Kit/kit_metadata.json
+++ b/inform7/Internal/Inter/Architecture32Kit/kit_metadata.json
@@ -2,7 +2,7 @@
     "is": {
         "type": "kit",
         "title": "Architecture32Kit",
-        "version": "10.2.0-beta+6W92"
+        "version": "10.2.0-beta+6W93"
     },
     "compatibility": "32-bit",
     "kit-details": {
diff --git a/inform7/Internal/Inter/BasicInformKit/kit_metadata.json b/inform7/Internal/Inter/BasicInformKit/kit_metadata.json
index 7ec238ea3..c59a63d1e 100644
--- a/inform7/Internal/Inter/BasicInformKit/kit_metadata.json
+++ b/inform7/Internal/Inter/BasicInformKit/kit_metadata.json
@@ -2,7 +2,7 @@
     "is": {
         "type": "kit",
         "title": "BasicInformKit",
-        "version": "10.2.0-beta+6W92"
+        "version": "10.2.0-beta+6W93"
     },
     "needs": [ {
         "need": {
diff --git a/inform7/Internal/Inter/CommandParserKit/kit_metadata.json b/inform7/Internal/Inter/CommandParserKit/kit_metadata.json
index b2d0abe29..1ecc0b9af 100644
--- a/inform7/Internal/Inter/CommandParserKit/kit_metadata.json
+++ b/inform7/Internal/Inter/CommandParserKit/kit_metadata.json
@@ -2,7 +2,7 @@
     "is": {
         "type": "kit",
         "title": "CommandParserKit",
-        "version": "10.2.0-beta+6W92"
+        "version": "10.2.0-beta+6W93"
     },
     "needs": [ {
         "need": {
diff --git a/inform7/Internal/Inter/EnglishLanguageKit/kit_metadata.json b/inform7/Internal/Inter/EnglishLanguageKit/kit_metadata.json
index 2823f7428..f24887f60 100644
--- a/inform7/Internal/Inter/EnglishLanguageKit/kit_metadata.json
+++ b/inform7/Internal/Inter/EnglishLanguageKit/kit_metadata.json
@@ -2,7 +2,7 @@
     "is": {
         "type": "kit",
         "title": "EnglishLanguageKit",
-        "version": "10.2.0-beta+6W92"
+        "version": "10.2.0-beta+6W93"
     },
     "needs": [ {
         "need": {
diff --git a/inform7/Internal/Inter/WorldModelKit/kit_metadata.json b/inform7/Internal/Inter/WorldModelKit/kit_metadata.json
index 682cfa747..3e0dfb24b 100644
--- a/inform7/Internal/Inter/WorldModelKit/kit_metadata.json
+++ b/inform7/Internal/Inter/WorldModelKit/kit_metadata.json
@@ -2,7 +2,7 @@
     "is": {
         "type": "kit",
         "title": "WorldModelKit",
-        "version": "10.2.0-beta+6W92"
+        "version": "10.2.0-beta+6W93"
     },
     "needs": [ {
         "need": {