void CMP_TREE_NODE_LIB_ID::UpdateScore( EDA_COMBINED_MATCHER& aMatcher )
{
    if( Score <= 0 )
        return; // Leaf nodes without scores are out of the game.

    if( !SearchTextNormalized )
    {
        SearchText = SearchText.Lower();
        SearchTextNormalized = true;
    }

    // Keywords and description we only count if the match string is at
    // least two characters long. That avoids spurious, low quality
    // matches. Most abbreviations are at three characters long.
    int found_pos = EDA_PATTERN_NOT_FOUND;
    int matchers_fired = 0;

    if( aMatcher.GetPattern() == MatchName )
    {
        Score += 1000;  // exact match. High score :)
    }
    else if( aMatcher.Find( MatchName, matchers_fired, found_pos ) )
    {
        // Substring match. The earlier in the string the better.
        Score += matchPosScore( found_pos, 20 ) + 20;
    }
    else if( aMatcher.Find( Parent->MatchName, matchers_fired, found_pos ) )
    {
        Score += 19;   // parent name matches.         score += 19
    }
    else if( aMatcher.Find( SearchText, matchers_fired, found_pos ) )
    {
        // If we have a very short search term (like one or two letters),
        // we don't want to accumulate scores if they just happen to be in
        // keywords or description as almost any one or two-letter
        // combination shows up in there.
        if( aMatcher.GetPattern().length() >= 2 )
        {
            // For longer terms, we add scores 1..18 for positional match
            // (higher in the front, where the keywords are).
            Score += matchPosScore( found_pos, 17 ) + 1;
        }
    }
    else
    {
        // No match. That's it for this item.
        Score = 0;
    }

    // More matchers = better match
    Score += 2 * matchers_fired;
}
void COMPONENT_TREE_SEARCH_CONTAINER::UpdateSearchTerm( const wxString& aSearch )
{
    if( m_tree == NULL )
        return;
//#define SHOW_CALC_TIME      // uncomment this to show calculation time

#ifdef SHOW_CALC_TIME
    unsigned starttime =  GetRunningMicroSecs();
#endif

    // We score the list by going through it several time, essentially with a complexity
    // of O(n). For the default library of 2000+ items, this typically takes less than 5ms
    // on an i5. Good enough, no index needed.

    // Initial AND condition: Leaf nodes are considered to match initially.
    for( TREE_NODE* node : m_nodes )
    {
        node->PreviousScore = node->MatchScore;
        node->MatchScore = ( node->Type == TREE_NODE::TYPE_LIB ) ? 0 : kLowestDefaultScore;
    }

    // Create match scores for each node for all the terms, that come space-separated.
    // Scoring adds up values for each term according to importance of the match. If a term does
    // not match at all, the result is thrown out of the results (AND semantics).
    // From high to low
    //   - Exact match for a ccmponent name gives the highest score, trumping all.
    //   - A positional score depending of where a term is found as substring; prefix-match: high.
    //   - substring-match in library name.
    //   - substring match in keywords and descriptions with positional score. Keywords come
    //     first so contribute more to the score.
    //
    // This is of course subject to tweaking.
    wxStringTokenizer tokenizer( aSearch );

    while ( tokenizer.HasMoreTokens() )
    {
        const wxString term = tokenizer.GetNextToken().Lower();
        EDA_COMBINED_MATCHER matcher( term );

        for( TREE_NODE* node : m_nodes )
        {
            if( node->Type != TREE_NODE::TYPE_ALIAS )
                continue;      // Only aliases are actually scored here.

            if( node->MatchScore == 0)
                continue;   // Leaf node without score are out of the game.

            // Keywords and description we only count if the match string is at
            // least two characters long. That avoids spurious, low quality
            // matches. Most abbreviations are at three characters long.
            int found_pos;
            int matcher_fired = 0;

            if( term == node->MatchName )
                node->MatchScore += 1000;  // exact match. High score :)
            else if( (found_pos = matcher.Find( node->MatchName, &matcher_fired ) ) != EDA_PATTERN_NOT_FOUND )
            {
                // Substring match. The earlier in the string the better.  score += 20..40
                node->MatchScore += matchPosScore( found_pos, 20 ) + 20;
            }
            else if( matcher.Find( node->Parent->MatchName, &matcher_fired ) != EDA_PATTERN_NOT_FOUND )
                node->MatchScore += 19;   // parent name matches.         score += 19
            else if( ( found_pos = matcher.Find( node->SearchText, &matcher_fired ) ) != EDA_PATTERN_NOT_FOUND )
            {
                // If we have a very short search term (like one or two letters), we don't want
                // to accumulate scores if they just happen to be in keywords or description as
                // almost any one or two-letter combination shows up in there.
                // For longer terms, we add scores 1..18 for positional match (higher in the
                // front, where the keywords are).                        score += 0..18
                node->MatchScore += ( ( term.length() >= 2 )
                                       ? matchPosScore( found_pos, 17 ) + 1
                                       : 0 );
            }
            else
                node->MatchScore = 0;    // No match. That's it for this item.

            node->MatchScore += 2 * matcher_fired;
        }
    }

    // Library nodes have the maximum score seen in any of their children.
    // Alias nodes have the score of their parents.
    unsigned highest_score_seen = 0;
    bool any_change = false;

    for( TREE_NODE* node : m_nodes )
    {
        switch( node->Type )
        {
        case TREE_NODE::TYPE_ALIAS:
            {
                any_change |= (node->PreviousScore != node->MatchScore);
                // Update library score.
                node->Parent->MatchScore = std::max( node->Parent->MatchScore, node->MatchScore );
                highest_score_seen = std::max( highest_score_seen, node->MatchScore );
            }
            break;

        case TREE_NODE::TYPE_UNIT:
            node->MatchScore = node->Parent->MatchScore;
            break;

        default:
            break;
        }
    }

    // The tree update might be slow, so we want to bail out if there is no change.
    if( !any_change )
        return;

    // Now: sort all items according to match score, libraries first.
    std::sort( m_nodes.begin(), m_nodes.end(), scoreComparator );

#ifdef SHOW_CALC_TIME
    unsigned sorttime = GetRunningMicroSecs();
#endif

    // Fill the tree with all items that have a match. Re-arranging, adding and removing changed
    // items is pretty complex, so we just re-build the whole tree.
    m_tree->Freeze();
    m_tree->DeleteAllItems();
    const wxTreeItemId root_id = m_tree->AddRoot( wxEmptyString );
    const TREE_NODE* first_match = NULL;
    const TREE_NODE* preselected_node = NULL;

    for( TREE_NODE* node : m_nodes )
    {
        if( node->MatchScore == 0 )
            continue;

        // If we have nodes that go beyond the default score, suppress nodes that
        // have the default score. That can happen if they have an honary += 0 score due to
        // some one-letter match in the keyword or description. In this case, we prefer matches
        // that just have higher scores. Improves relevancy and performance as the tree has to
        // display less items.
        if( highest_score_seen > kLowestDefaultScore && node->MatchScore == kLowestDefaultScore )
            continue;

        wxString node_text;
#if 0
        // Node text with scoring information for debugging
        node_text.Printf( wxT("%s (s=%u)%s"), GetChars(node->DisplayName),
                          node->MatchScore, GetChars( node->DisplayInfo ));
#else
        node_text = node->DisplayName + node->DisplayInfo;
#endif
        node->TreeId = m_tree->AppendItem( node->Parent ? node->Parent->TreeId : root_id,
                                           node_text );

        // If we are a nicely scored alias, we want to have it visible. Also, if there
        // is only a single library in this container, we want to have it unfolded
        // (example: power library).
        if( node->Type == TREE_NODE::TYPE_ALIAS
             && ( node->MatchScore > kLowestDefaultScore || m_libraries_added == 1 ) )
        {
            m_tree->Expand( node->TreeId );

            if( first_match == NULL )
                first_match = node;   // First, highest scoring: the "I am feeling lucky" element.
        }

        // The first node that matches our pre-select criteria is choosen. 'First node'
        // means, it shows up in the history, as the history node is displayed very first
        // (by virtue of alphabetical ordering)
        if( preselected_node == NULL
             && node->Type == TREE_NODE::TYPE_ALIAS
             && node->MatchName == m_preselect_node_name )
            preselected_node = node;

        // Refinement in case we come accross a matching unit node.
        if( preselected_node != NULL && preselected_node->Type == TREE_NODE::TYPE_ALIAS
             && node->Parent == preselected_node
             && m_preselect_unit_number >= 1 && node->Unit == m_preselect_unit_number )
            preselected_node = node;
    }

    if( first_match )                      // Highest score search match pre-selected.
    {
        m_tree->SelectItem( first_match->TreeId );
        m_tree->EnsureVisible( first_match->TreeId );
    }
    else if( preselected_node )            // No search, so history item preselected.
    {
        m_tree->SelectItem( preselected_node->TreeId );
        m_tree->EnsureVisible( preselected_node->TreeId );
    }

    m_tree->Thaw();

#ifdef SHOW_CALC_TIME
    unsigned endtime = GetRunningMicroSecs();
    wxLogMessage( wxT("sort components %.1f ms,  rebuild tree %.1f ms"),
                  double(sorttime-starttime)/1000.0, double(endtime-sorttime)/1000.0 );
#endif
}