af3a143571e5aa064eab75c34f9444b35413b562
chmalee
  Tue Nov 30 15:28:15 2021 -0800
Add snippet support to trix searching. Required changing the
wordPos from the first highest matching wordIndex to the
wordIndex of the actual span. Have trixContextIndex create a
second level index for fast retrieval of line offsets in
original text file used by ixIxx. Create a simple UI for navigating
hgFind search results.

diff --git src/hg/cgilib/cartJson.c src/hg/cgilib/cartJson.c
index 2d504ff..8c0914e 100644
--- src/hg/cgilib/cartJson.c
+++ src/hg/cgilib/cartJson.c
@@ -55,122 +55,131 @@
 {
 regmatch_t matches[3];
 if (regexMatchSubstrNoCase(textIn, "<a href[^>]+>", matches, ArraySize(matches)))
     {
     char *textOut = cloneString(textIn);
     memmove(textOut+matches[0].rm_so, textOut+matches[0].rm_eo,
 	    strlen(textOut) + 1 - matches[0].rm_eo);
     if (regexMatchSubstrNoCase(textOut, "</a>", matches, ArraySize(matches)))
 	memmove(textOut+matches[0].rm_so, textOut+matches[0].rm_eo,
 		strlen(textOut) + 1 - matches[0].rm_eo);
     return textOut;
     }
 return textIn;
 }
 
-static void hgPositionsJson(struct jsonWrite *jw, char *db, struct hgPositions *hgp, struct cart *cart)
+void hgPositionsJson(struct jsonWrite *jw, char *db, struct hgPositions *hgp, struct cart *cart)
 /* Write out JSON description of multiple position matches. */
 {
 struct hgPosTable *table;
 jsonWriteListStart(jw, "positionMatches");
 struct trackDb *tdbList = NULL;
 for (table = hgp->tableList; table != NULL; table = table->next)
     {
     if (table->posList != NULL)
         {
-	char *tableName = table->name;
+        char *trackName = table->name, *tableName = table->name;
+        struct trackDb *tdb = NULL;
         // clear the tdb cache if this track is a hub track
+        if (! (sameString("trackDb", tableName) || sameString("helpDocs", tableName) || sameString("publicHubs", tableName)))
+            {
             if (isHubTrack(tableName))
                 tdbList = NULL;
-	struct trackDb *tdb = tdbForTrack(db, tableName, &tdbList);
+            tdb = tdbForTrack(db, tableName, &tdbList);
             if (!tdb && startsWith("all_", tableName))
                 tdb = tdbForTrack(db, tableName+strlen("all_"), &tdbList);
             if (!tdb)
                 errAbort("no track for table \"%s\" found via a findSpec", tableName);
-	char *trackName = tdb->track;
+            trackName = tdb->track;
+            }
         jsonWriteObjectStart(jw, NULL);
         jsonWriteString(jw, "name", table->name);
         jsonWriteString(jw, "trackName", trackName);
         jsonWriteString(jw, "description", table->description);
+        if (tdb != NULL)
             jsonWriteString(jw, "vis", hCarefulTrackOpenVis(db, trackName));
         jsonWriteListStart(jw, "matches");
         struct hgPos *pos;
         for (pos = table->posList; pos != NULL; pos = pos->next)
             {
             char *encMatches = cgiEncode(pos->browserName);
             jsonWriteObjectStart(jw, NULL); // begin one match
             if (pos->chrom != NULL)
                 jsonWriteStringf(jw, "position", "%s:%d-%d",
                                  pos->chrom, pos->chromStart+1, pos->chromEnd);
             else
                 // GenBank results set position to GB accession instead of chr:s-e position.
                 jsonWriteString(jw, "position", pos->name);
             // this is magic to tell the browser to make the
             // composite and this subTrack visible
-	    if (tdb->parent)
+            if (tdb && tdb->parent)
                 {
                 if (tdbIsSuperTrackChild(tdb))
                     jsonWriteStringf(jw, "extraSel", "%s=show&", tdb->parent->track);
                 else
                     {
                     // tdb is a subtrack of a composite or a view
                     jsonWriteStringf(jw, "extraSel", "%s_sel=1&%s_sel=1&",
                                      trackName, tdb->parent->track);
                     }
                 }
             jsonWriteString(jw, "hgFindMatches", encMatches);
             jsonWriteString(jw, "posName", htmlEncode(pos->name));
+            jsonWriteBoolean(jw, "canonical", pos->canonical);
             if (pos->description)
                 {
                 stripString(pos->description, "\n");
                 jsonWriteString(jw, "description", stripAnchor(pos->description));
                 }
             jsonWriteObjectEnd(jw); // end one match
             }
         jsonWriteListEnd(jw); // end matches
+        if (table->searchTime != 0)
+            jsonWriteNumber(jw, "searchTime", table->searchTime);
         jsonWriteObjectEnd(jw); // end one table
 	}
     }
     jsonWriteListEnd(jw); // end positionMatches
 }
 
 static struct hgPositions *genomePosCJ(struct jsonWrite *jw,
 				       char *db, char *spec, char **retChromName,
 				       int *retWinStart, int *retWinEnd, struct cart *cart)
 /* Search for positions in genome that match user query.
  * Return an hgp unless there is a problem.  hgp->singlePos will be set if a single
  * position matched.
  * Otherwise display list of positions, put # of positions in retWinStart,
  * and return NULL. */
 {
 char *hgAppName = "cartJson";
 struct hgPositions *hgp = NULL;
 char *chrom = NULL;
 int start = BIGNUM;
 int end = 0;
+boolean measureTiming = cartUsualBoolean(cart, "measureTiming", FALSE);
 
 char *terms[16];
 int termCount = chopByChar(cloneString(spec), ';', terms, ArraySize(terms));
 boolean multiTerm = (termCount > 1);
 
 int i = 0;
 for (i = 0;  i < termCount;  i++)
     {
     trimSpaces(terms[i]);
     if (isEmpty(terms[i]))
 	continue;
-    hgp = hgPositionsFind(db, terms[i], "", hgAppName, cart, multiTerm);
+    hgp = hgPositionsFind(db, terms[i], "", hgAppName, cart, multiTerm, measureTiming, NULL);
     if (hgp == NULL || hgp->posCount == 0)
 	{
 	jsonWriteStringf(jw, "error",
 			 "Sorry, couldn't locate %s in %s %s", htmlEncode(terms[i]),
                          trackHubSkipHubName(hOrganism(db)), hFreezeDate(db));
 	if (multiTerm)
 	    jsonWriteStringf(jw, "error",
 			     "%s not uniquely determined -- can't do multi-position search.",
 			     terms[i]);
 	*retWinStart = 0;
 	return NULL;
 	}
     if (hgp->singlePos != NULL)
 	{
 	if (chrom != NULL && !sameString(chrom, hgp->singlePos->chrom))