+ String seqString = null;
+ if (ignoreHidden)
+ {
+ seqString = getVisibleSequence(seq);
+ this.searchedSequenceStartPosition = 1;
+ }
+ else
+ {
+ int startCol = 0;
+ int endCol = seq.getLength() - 1;
+ this.searchedSequenceStartPosition = seq.getStart();
+ if (selection != null)
+ {
+ startCol = selection.getStartRes();
+ endCol = Math.min(endCol, selection.getEndRes());
+ this.searchedSequenceStartPosition = seq.findPosition(startCol);
+ }
+ seqString = seq.getSequenceAsString(startCol, endCol + 1);
+ }
+
+ /*
+ * remove gaps; note that even if this leaves an empty string, we 'search'
+ * the sequence anyway (for possible match on name or description)
+ */
+ String ungapped = AlignSeq.extractGaps(Comparison.GapChars, seqString);
+ this.seqToSearch = ungapped;
+
+ return true;
+ }
+
+ /**
+ * Returns a string consisting of only the visible residues of {@code seq}
+ * from alignment column {@ fromColumn}, restricted to the current selection
+ * region if there is one.
+ * <p>
+ * As a side-effect, also computes the mapping from the true sequence
+ * positions to the positions (1, 2, ...) of the returned sequence. This is to
+ * allow search matches in the visible sequence to be converted to sequence
+ * positions.
+ *
+ * @param seq
+ * @return
+ */
+ private String getVisibleSequence(SequenceI seq)
+ {
+ /*
+ * get start / end columns of sequence and convert to base 0
+ * (so as to match the visible column ranges)
+ */
+ int seqStartCol = seq.findIndex(seq.getStart()) - 1;
+ int seqEndCol = seq.findIndex(seq.getStart() + seq.getLength() - 1) - 1;
+ Iterator<int[]> visibleColumns = viewport.getViewAsVisibleContigs(true);
+ StringBuilder visibleSeq = new StringBuilder(seqEndCol - seqStartCol);
+ List<int[]> fromRanges = new ArrayList<>();
+
+ while (visibleColumns.hasNext())
+ {
+ int[] range = visibleColumns.next();
+ if (range[0] > seqEndCol)
+ {
+ // beyond the end of the sequence
+ break;
+ }
+ if (range[1] < seqStartCol)
+ {
+ // before the start of the sequence
+ continue;
+ }
+ String subseq = seq.getSequenceAsString(range[0], range[1] + 1);
+ String ungapped = AlignSeq.extractGaps(Comparison.GapChars, subseq);
+ visibleSeq.append(ungapped);
+ if (!ungapped.isEmpty())
+ {
+ /*
+ * visible region includes at least one non-gap character,
+ * so add the range to the mapping being constructed
+ */
+ int seqResFrom = seq.findPosition(range[0]);
+ int seqResTo = seqResFrom + ungapped.length() - 1;
+ fromRanges.add(new int[] { seqResFrom, seqResTo });
+ }
+ }
+
+ /*
+ * construct the mapping
+ * from: visible sequence positions 1..length
+ * to: true residue positions of the alignment sequence
+ */
+ List<int[]> toRange = Arrays
+ .asList(new int[]
+ { 1, visibleSeq.length() });
+ searchedSequenceMap = new MapList(fromRanges, toRange, 1, 1);
+
+ return visibleSeq.toString();
+ }
+
+ /**
+ * Advances the search to the next sequence in the alignment. Sequences not in
+ * the current selection group (if there is one) are skipped. The
+ * (sub-)sequence to be searched is extracted, gaps removed, and saved, or set
+ * to null if there are no more sequences to search.
+ * <p>
+ * Returns true if a sequence could be found, false if end of alignment was
+ * reached
+ *
+ * @param ignoreHidden
+ */
+ private boolean nextSequence(boolean ignoreHidden)
+ {
+ sequenceIndex++;
+ residueIndex = -1;
+
+ return getSequence(ignoreHidden);
+ }
+
+ /**
+ * Finds the next match in the given sequence, starting at offset
+ * {@code residueIndex}. Answers true if a match is found, else false.
+ * <p>
+ * If a match is found, {@code residueIndex} is advanced to the position after
+ * the start of the matched region, ready for the next search.
+ * <p>
+ * If no match is found, {@code sequenceIndex} is advanced ready to search the
+ * next sequence.
+ *
+ * @param seqToSearch
+ * @param searchString
+ * @param searchPattern
+ * @param matchDescription
+ * @param ignoreHidden
+ * @return
+ */
+ protected boolean findNextMatch(String searchString, Regex searchPattern,
+ boolean matchDescription, boolean matchFeatureDesc,
+ boolean ignoreHidden)
+ {
+ if (residueIndex < 0)
+ {
+ /*
+ * at start of sequence; try find by residue number, in sequence id,
+ * or (optionally) in sequence description
+ */
+ if (doNonMotifSearches(searchString, searchPattern, matchDescription))
+ {
+ return true;
+ }
+ }
+
+ /*
+ * search for next match in sequence string
+ */
+ int end = seqToSearch.length();
+ while (residueIndex < end)
+ {
+ boolean matched = searchPattern.searchFrom(seqToSearch, residueIndex);
+ if (matched)
+ {
+ if (recordMatch(searchPattern, ignoreHidden))