412456cf57b44d0747bfe632c7ea41f85554501d
angie
  Fri Apr 14 09:56:57 2017 -0700
Add highlighting of HGVS mapped bases to distinguish them from padding bases.  Also apply to other padded searches (e.g. SNPs).  refs #19215

diff --git src/hg/lib/hgFind.c src/hg/lib/hgFind.c
index 2567d04..a9234ab 100644
--- src/hg/lib/hgFind.c
+++ src/hg/lib/hgFind.c
@@ -2724,30 +2724,52 @@
     {
     if (!isFuzzy || keyIsPrefixIgnoreCase(term, row[1]))
         {
 	xrefPtr = slPairNew(cloneString(row[1]), cloneString(row[0]));
 	slAddHead(&xrefList, xrefPtr);
 	}
     }
 sqlFreeResult(&sr);
 hFreeConn(&conn);
 slReverse(&xrefList);
 if (xrefList == NULL && hgFindSpecSetting(hfs, "searchBoth") != NULL)
     xrefList = slPairNew(cloneString(""), cloneString(term));
 return(xrefList);
 }
 
+static void addHighlight(struct cart *cart, char *db, char *chrom, unsigned start, unsigned end)
+/* Add the given region to the cart variable highlight. */
+{
+char *color = "fcfcac";
+struct dyString *dy = dyStringCreate("%s.%s:%u-%u#%s", db, chrom, start+1, end, color);
+char *existing = cartOptionalString(cart, "highlight");
+if (isEmpty(existing))
+    cartSetString(cart, "highlight", dyStringContents(dy));
+else
+    {
+    // Don't add region if it is already in the existing highlight setting.
+    char *alreadyIn = strstr(existing, dyStringContents(dy));
+    if (!alreadyIn ||
+        !(alreadyIn[dyStringLen(dy)] == '|' || alreadyIn[dyStringLen(dy)] == '\0'))
+        {
+        dyStringPrintf(dy, "|%s", existing);
+        cartSetString(cart, "highlight", dyStringContents(dy));
+        }
+    }
+dyStringFree(&dy);
+}
+
 static boolean doQuery(char *db, struct hgFindSpec *hfs, char *xrefTerm, char *term,
 		       struct hgPositions *hgp,
 		       boolean relativeFlag, int relStart, int relEnd,
 		       boolean multiTerm)
 /* Perform a query as specified in hfs, assuming table existence has been 
  * checked and xref'ing has been taken care of. */
 {
 struct slName *tableList = hSplitTableNames(db, hfs->searchTable);
 struct slName *tPtr = NULL;
 struct hgPosTable *table = NULL;
 struct hgPos *pos = NULL;
 struct sqlConnection *conn = hAllocConn(db);
 struct sqlResult *sr = NULL;
 char **row = NULL;
 char *termPrefix = hgFindSpecSetting(hfs, "termPrefix");
@@ -2801,30 +2823,32 @@
 	pos->name = cloneString(buf);
 	pos->browserName = cloneString(row[3]);
 	if (isNotEmpty(xrefTerm))
 	    {
 	    safef(buf, sizeof(buf), "(%s%s)",
 		  termPrefix ? termPrefix : "", row[3]);
 	    pos->description = cloneString(buf);
 	    }
 	if (relativeFlag && (pos->chromStart + relEnd) <= pos->chromEnd)
 	    {
 	    pos->chromEnd   = pos->chromStart + relEnd;
 	    pos->chromStart = pos->chromStart + relStart;
 	    }
 	else if (padding > 0 && !multiTerm)
 	    {
+            // highlight the item bases to distinguish from padding
+            addHighlight(cart, db, pos->chrom, pos->chromStart, pos->chromEnd);
 	    int chromSize = hChromSize(db, pos->chrom);
 	    pos->chromStart -= padding;
 	    pos->chromEnd   += padding;
 	    if (pos->chromStart < 0)
 		pos->chromStart = 0;
 	    if (pos->chromEnd > chromSize)
 		pos->chromEnd = chromSize;
 	    }
 	slAddHead(&table->posList, pos);
 	}
 
     }
 if (table != NULL)
     slReverse(&table->posList);
 sqlFreeResult(&sr);
@@ -3028,57 +3052,59 @@
 	hfs = hfsFind(longList, search);
     if (hfs != NULL)
 	foundIt = hgFindUsingSpec(db, hfs, term, limitResults, hgp, FALSE, 0,0, FALSE);
     else
 	warn("Unrecognized singleSearch=%s in URL", search);
     }
 if (foundIt)
     {
     fixSinglePos(hgp);
     if (cart != NULL)
         cartSetString(cart, "hgFind.matches", hgp->tableList->posList->browserName);
     }
 return foundIt;
 }
 
-static boolean matchesHgvs(char *db, char *term, struct hgPositions *hgp)
+static boolean matchesHgvs(struct cart *cart, char *db, char *term, struct hgPositions *hgp)
 /* Return TRUE if the search term looks like a variant encoded using the HGVS nomenclature */
 /* See http://varnomen.hgvs.org/ */
 {
 boolean foundIt = FALSE;
 struct hgvsVariant *hgvs = hgvsParseTerm(term);
 if (hgvs == NULL)
     hgvs = hgvsParsePseudoHgvs(db, term);
 if (hgvs)
     {
     struct dyString *dyWarn = dyStringNew(0);
     char *pslTable = NULL;
     struct bed *mapping = hgvsValidateAndMap(hgvs, db, term, dyWarn, &pslTable);
     if (dyStringLen(dyWarn) > 0)
         warn("%s", dyStringContents(dyWarn));
     if (mapping)
         {
         int padding = 5;
         char *trackTable;
         if (isEmpty(pslTable))
             trackTable = "chromInfo";
         else if (startsWith("lrg", pslTable))
             trackTable = "lrgTranscriptAli";
         else
             trackTable = "refGene";
         singlePos(hgp, "HGVS", NULL, trackTable, term, "",
                   mapping->chrom, mapping->chromStart-padding, mapping->chromEnd+padding);
+        // highlight the mapped bases to distinguish from padding
+        addHighlight(cart, db, mapping->chrom, mapping->chromStart, mapping->chromEnd);
         foundIt = TRUE;
         }
     dyStringFree(&dyWarn);
     }
 return foundIt;
 }
 
 struct hgPositions *hgPositionsFind(char *db, char *term, char *extraCgi,
 	char *hgAppNameIn, struct cart *cart, boolean multiTerm)
 /* Return table of positions that match term or NULL if none such. */
 {
 struct hgPositions *hgp = NULL, *hgpItem = NULL;
 regmatch_t substrs[4];
 boolean canonicalSpec = FALSE;
 boolean gbrowserSpec = FALSE;
@@ -3163,31 +3189,31 @@
 
     hgParseChromRange(db, term, &chrom, &start, &end);
     if (relativeFlag)
 	{
 	int chromSize = end;
 	end = start + relEnd;
 	start = start + relStart;
 	if (end > chromSize)
 	    end = chromSize;
 	if (start < 0)
 	    start = 0;
 	}
     singlePos(hgp, "Chromosome Range", NULL, "chromInfo", originalTerm,
 	      "", chrom, start, end);
     }
-else if (!matchesHgvs(db, term, hgp))
+else if (!matchesHgvs(cart, db, term, hgp))
     {
     struct hgFindSpec *shortList = NULL, *longList = NULL;
     struct hgFindSpec *hfs;
     boolean done = FALSE;
 
     // Disable singleBaseSpec for any term that is not hgOfficialChromName
     // because that mangles legitimate IDs that are [A-Z]:[0-9]+.
     if (singleBaseSpec)
 	{
 	singleBaseSpec = relativeFlag = FALSE;
 	term = cloneString(originalTerm);  // restore original term
 	relStart = relEnd = 0;
 	}
 
     if (!trackHubDatabase(db))