Retire the lib-tree scoring algorithm.

It appears to cause more confusion than help.

Fixes https://gitlab.com/kicad/code/kicad/issues/13905
This commit is contained in:
Jeff Young 2023-04-14 23:34:14 +01:00
parent d64cb7f81b
commit d01c023d5a
12 changed files with 114 additions and 220 deletions

View File

@ -1,7 +1,7 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2022-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -110,10 +110,7 @@ void DIALOG_ASSIGN_NETCLASS::onPatternText( wxCommandEvent& aEvent )
for( const wxString& net : m_netCandidates )
{
int matches;
int offset;
if( matcher.Find( net, matches, offset ) && offset == 0 )
if( matcher.StartsWith( net ) )
{
m_matchingNets->Report( net );
matchingNetNames.push_back( net );

View File

@ -707,10 +707,7 @@ void PANEL_SETUP_NETCLASSES::OnUpdateUI( wxUpdateUIEvent& event )
for( const wxString& net : m_netNames )
{
int matches;
int offset;
if( matcher.Find( net, matches, offset ) && offset == 0 )
if( matcher.StartsWith( net ) )
m_matchingNets->Report( net );
}
}

View File

@ -399,25 +399,27 @@ EDA_COMBINED_MATCHER::EDA_COMBINED_MATCHER( const wxString& aPattern,
}
bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm, int& aMatchersTriggered, int& aPosition )
bool EDA_COMBINED_MATCHER::Find( const wxString& aTerm )
{
aPosition = EDA_PATTERN_NOT_FOUND;
aMatchersTriggered = 0;
for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
{
EDA_PATTERN_MATCH::FIND_RESULT local_find = matcher->Find( aTerm );
if( local_find )
{
aMatchersTriggered += 1;
if( local_find.start < aPosition || aPosition == EDA_PATTERN_NOT_FOUND )
aPosition = local_find.start;
}
if( matcher->Find( aTerm ).start >= 0 )
return true;
}
return aPosition != EDA_PATTERN_NOT_FOUND;
return false;
}
bool EDA_COMBINED_MATCHER::StartsWith( const wxString& aTerm )
{
for( const std::unique_ptr<EDA_PATTERN_MATCH>& matcher : m_matchers )
{
if( matcher->Find( aTerm ).start == 0 )
return true;
}
return false;
}

View File

@ -86,12 +86,11 @@ void FOOTPRINT_FILTER_IT::increment()
candidate.GetLibNickname(),
candidate.GetFootprintName(),
candidate.GetSearchText() );
int matches, position;
bool exclude = false;
bool exclude = false;
for( std::unique_ptr<EDA_COMBINED_MATCHER>& matcher : m_filter->m_pattern_filters )
{
if( !matcher->Find( searchStr.Lower(), matches, position ) )
if( !matcher->Find( searchStr.Lower() ) )
{
exclude = true;
break;

View File

@ -3,7 +3,7 @@
*lib_tree_model
* Copyright (C) 2017 Chris Pavlina <pavlina.chris@gmail.com>
* Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
* Copyright (C) 2014-2019 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2014-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@ -24,32 +24,16 @@
#include <algorithm>
#include <eda_pattern_match.h>
#include <lib_tree_item.h>
#include <utility>
#include <pgm_base.h>
#include <string_utils.h>
// Each node gets this lowest score initially, without any matches applied.
// Matches will then increase this score depending on match quality. This way,
// an empty search string will result in all components being displayed as they
// have the minimum score. However, in that case, we avoid expanding all the
// Each node gets this lowest score initially, without any matches applied. Matches will then
// increase this score. This way, an empty search string will result in all components being
// displayed as they have the minimum score. However, in that case, we avoid expanding all the
// nodes asd the result is very unspecific.
static const unsigned kLowestDefaultScore = 1;
// Creates a score depending on the position of a string match. If the position
// is 0 (= prefix match), this returns the maximum score. This degrades until
// pos == max, which returns a score of 0; Evertyhing else beyond that is just
// 0. Only values >= 0 allowed for position and max.
//
// @param aPosition is the position a string has been found in a substring.
// @param aMaximum is the maximum score this function returns.
// @return position dependent score.
static int matchPosScore(int aPosition, int aMaximum)
{
return ( aPosition < aMaximum ) ? aMaximum - aPosition : 0;
}
void LIB_TREE_NODE::ResetScore()
{
for( std::unique_ptr<LIB_TREE_NODE>& child: m_Children )
@ -259,46 +243,14 @@ void LIB_TREE_NODE_LIB_ID::UpdateScore( EDA_COMBINED_MATCHER& aMatcher, const wx
return;
}
// 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() == m_MatchName )
{
m_Score += 1000; // exact match. High score :)
}
else if( aMatcher.Find( m_MatchName, matchers_fired, found_pos ) )
{
// Substring match. The earlier in the string the better.
m_Score += matchPosScore( found_pos, 20 ) + 20;
}
else if( aMatcher.Find( m_Parent->m_MatchName, matchers_fired, found_pos ) )
{
m_Score += 19; // parent name matches. score += 19
}
else if( aMatcher.Find( m_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).
m_Score += matchPosScore( found_pos, 17 ) + 1;
}
}
// Keyword and description matches only count if they're at least two chars long. This
// avoids spurious, low quality matches. (Most abbreviations are at least 3 chars.)
if( aMatcher.Find( m_MatchName ) )
m_Score++;
else if( aMatcher.GetPattern().length() >= 2 && aMatcher.Find( m_SearchText ) )
m_Score++;
else
{
// No match. That's it for this item.
m_Score = 0;
}
// More matchers = better match
m_Score += 2 * matchers_fired;
}
@ -340,27 +292,10 @@ void LIB_TREE_NODE_LIB::UpdateScore( EDA_COMBINED_MATCHER& aMatcher, const wxStr
{
// No children; we are a leaf.
if( !aLib.IsEmpty() )
{
m_Score = m_MatchName == aLib ? 1000 : 0;
return;
}
int found_pos = EDA_PATTERN_NOT_FOUND;
int matchers_fired = 0;
if( aMatcher.GetPattern() == m_MatchName )
{
m_Score += 1000; // exact match. High score :)
}
else if( aMatcher.Find( m_MatchName, matchers_fired, found_pos ) )
{
// Substring match. The earlier in the string the better.
m_Score += matchPosScore( found_pos, 20 ) + 20;
}
// More matchers = better match
m_Score += 2 * matchers_fired;
if( !aLib.IsEmpty() && m_MatchName == aLib )
m_Score++;
else if( aMatcher.Find( m_MatchName ) )
m_Score++;
}
}

View File

@ -3,7 +3,7 @@
*
* Copyright (C) 2017 Chris Pavlina <pavlina.chris@gmail.com>
* Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
* Copyright (C) 2014-2022 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2014-2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@ -213,17 +213,11 @@ void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( const wxString& aSearch, bool a
Thaw();
}
LIB_TREE_NODE* bestMatch = ShowResults();
const LIB_TREE_NODE* firstMatch = ShowResults();
if( !bestMatch )
bestMatch = ShowPreselect();
if( !bestMatch )
bestMatch = ShowSingleLibrary();
if( bestMatch )
if( firstMatch )
{
wxDataViewItem item = wxDataViewItem( bestMatch );
wxDataViewItem item = ToItem( firstMatch );
m_widget->Select( item );
// Make sure the *parent* item is visible. The selected item is the
@ -619,93 +613,83 @@ bool LIB_TREE_MODEL_ADAPTER::GetAttr( const wxDataViewItem& aItem,
}
void LIB_TREE_MODEL_ADAPTER::Find( LIB_TREE_NODE& aNode,
std::function<bool( const LIB_TREE_NODE* )> aFunc,
LIB_TREE_NODE** aHighScore )
void recursiveDescent( LIB_TREE_NODE& aNode, const std::function<bool( const LIB_TREE_NODE* )>& f )
{
for( std::unique_ptr<LIB_TREE_NODE>& node: aNode.m_Children )
{
if( aFunc( &*node ) )
{
if( !(*aHighScore) || node->m_Score > (*aHighScore)->m_Score )
(*aHighScore) = &*node;
}
if( !f( node.get() ) )
break;
Find( *node, aFunc, aHighScore );
recursiveDescent( *node, f );
}
}
LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ShowResults()
const LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ShowResults()
{
LIB_TREE_NODE* highScore = nullptr;
const LIB_TREE_NODE* firstMatch = nullptr;
Find( m_tree,
[]( LIB_TREE_NODE const* n )
{
// return leaf nodes with some level of matching
return n->m_Type == LIB_TREE_NODE::TYPE::LIBID && n->m_Score > 1;
},
&highScore );
if( highScore)
{
wxDataViewItem item = wxDataViewItem( highScore );
m_widget->ExpandAncestors( item );
}
return highScore;
}
LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ShowPreselect()
{
LIB_TREE_NODE* highScore = nullptr;
if( !m_preselect_lib_id.IsValid() )
return highScore;
Find( m_tree,
[&]( LIB_TREE_NODE const* n )
// Expand parents of leaf nodes with some level of matching
recursiveDescent( m_tree,
[&]( const LIB_TREE_NODE* n )
{
if( n->m_Type == LIB_TREE_NODE::LIBID && ( n->m_Children.empty() ||
!m_preselect_unit ) )
return m_preselect_lib_id == n->m_LibId;
else if( n->m_Type == LIB_TREE_NODE::UNIT && m_preselect_unit )
return m_preselect_lib_id == n->m_Parent->m_LibId &&
m_preselect_unit == n->m_Unit;
else
return false;
},
&highScore );
if( n->m_Type == LIB_TREE_NODE::TYPE::LIBID && n->m_Score > 1 )
{
if( !firstMatch )
firstMatch = n;
if( highScore)
m_widget->ExpandAncestors( ToItem( n ) );
}
return true; // keep going to expand ancestors of all found items
} );
// If no matches, find and show the preselect node
if( !firstMatch && m_preselect_lib_id.IsValid() )
{
wxDataViewItem item = wxDataViewItem( highScore );
m_widget->ExpandAncestors( item );
recursiveDescent( m_tree,
[&]( const LIB_TREE_NODE* n )
{
if( n->m_Type == LIB_TREE_NODE::LIBID
&& ( n->m_Children.empty() || !m_preselect_unit )
&& m_preselect_lib_id == n->m_LibId )
{
firstMatch = n;
m_widget->ExpandAncestors( ToItem( n ) );
return false;
}
else if( n->m_Type == LIB_TREE_NODE::UNIT
&& ( m_preselect_unit && m_preselect_unit == n->m_Unit )
&& m_preselect_lib_id == n->m_Parent->m_LibId )
{
firstMatch = n;
m_widget->ExpandAncestors( ToItem( n ) );
return false;
}
return true;
} );
}
return highScore;
// If still no matches expand a single library if there is only one
if( !firstMatch )
{
recursiveDescent( m_tree,
[&]( const LIB_TREE_NODE* n )
{
if( n->m_Type == LIB_TREE_NODE::TYPE::LIBID
&& n->m_Parent->m_Parent->m_Children.size() == 1 )
{
firstMatch = n;
m_widget->ExpandAncestors( ToItem( n ) );
return false;
}
return true;
} );
}
return firstMatch;
}
LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ShowSingleLibrary()
{
LIB_TREE_NODE* highScore = nullptr;
Find( m_tree,
[]( LIB_TREE_NODE const* n )
{
return n->m_Type == LIB_TREE_NODE::TYPE::LIBID &&
n->m_Parent->m_Parent->m_Children.size() == 1;
},
&highScore );
if( highScore)
{
wxDataViewItem item = wxDataViewItem( highScore );
m_widget->ExpandAncestors( item );
}
return highScore;
}

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2020 CERN
* Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
* @author Jon Evans <jon@craftyjon.com>
*
* This program is free software: you can redistribute it and/or modify it
@ -398,10 +398,7 @@ std::shared_ptr<NETCLASS> NET_SETTINGS::GetEffectiveNetClass( const wxString& aN
for( const auto& [ matcher, netclassName ] : m_NetClassPatternAssignments )
{
int matches;
int offset;
if( matcher->Find( aNetName, matches, offset ) && offset == 0 )
if( matcher->StartsWith( aNetName ) )
{
m_NetClassPatternAssignmentCache[ aNetName ] = netclassName;
return getNetclass( netclassName );

View File

@ -796,10 +796,7 @@ void SIMULATOR_FRAME::rebuildSignalsGrid( wxString aFilter )
for( const wxString& signal : m_signals )
{
int matches;
int offset;
if( matcher.Find( signal, matches, offset ) && offset == 0 )
if( matcher.StartsWith( signal ) )
{
int traceType = SPT_UNKNOWN;
wxString vectorName = vectorNameFromSignalName( signal, &traceType );

View File

@ -719,11 +719,10 @@ bool SYMBOL_VIEWER_FRAME::ReCreateLibList()
{
const wxString term = tokenizer.GetNextToken().Lower();
EDA_COMBINED_MATCHER matcher( term, CTX_LIBITEM );
int matches, position;
for( const wxString& lib : libs )
{
if( matcher.Find( lib.Lower(), matches, position ) )
if( matcher.Find( lib.Lower() ) )
process( lib );
}
}
@ -796,12 +795,11 @@ bool SYMBOL_VIEWER_FRAME::ReCreateSymbolList()
{
const wxString term = tokenizer.GetNextToken().Lower();
EDA_COMBINED_MATCHER matcher( term, CTX_LIBITEM );
int matches, position;
for( LIB_SYMBOL* symbol : symbols )
{
wxString search = symbol->GetName() + wxS( " " ) + symbol->GetSearchText();
bool matched = matcher.Find( search.Lower(), matches, position );
bool matched = matcher.Find( search.Lower() );
if( !matched && term.IsNumber() )
matched = ( wxAtoi( term ) == (int)symbol->GetPinCount() );

View File

@ -197,7 +197,9 @@ public:
*
* @return true if any matchers found the term
*/
bool Find( const wxString& aTerm, int& aMatchersTriggered, int& aPosition );
bool Find( const wxString& aTerm );
bool StartsWith( const wxString& aTerm );
const wxString& GetPattern() const;

View File

@ -391,26 +391,14 @@ protected:
private:
/**
* Find any results worth highlighting and expand them, according to given criteria
* The highest-scoring node is written to aHighScore
* Find any results worth highlighting and expand them.
*/
void Find( LIB_TREE_NODE& aNode, std::function<bool( const LIB_TREE_NODE* )> aFunc,
LIB_TREE_NODE** aHighScore );
void Find( LIB_TREE_NODE& aNode, std::function<bool( const LIB_TREE_NODE* )> aFunc );
/**
* Find and expand successful search results. Return the best match (if any).
*/
LIB_TREE_NODE* ShowResults();
/**
* Find and expand preselected node. Return the best match (if any).
*/
LIB_TREE_NODE* ShowPreselect();
/**
* Find and expand a library if there is only one. Return the best match (if any).
*/
LIB_TREE_NODE* ShowSingleLibrary();
const LIB_TREE_NODE* ShowResults();
wxDataViewColumn* doAddColumn( const wxString& aHeader, bool aTranslate = true );

View File

@ -493,11 +493,10 @@ void FOOTPRINT_VIEWER_FRAME::ReCreateLibraryList()
{
const wxString term = tokenizer.GetNextToken().Lower();
EDA_COMBINED_MATCHER matcher( term, CTX_LIBITEM );
int matches, position;
for( const wxString& nickname : nicknames )
{
if( matcher.Find( nickname.Lower(), matches, position ) )
if( matcher.Find( nickname.Lower() ) )
process( nickname );
}
}
@ -570,12 +569,11 @@ void FOOTPRINT_VIEWER_FRAME::ReCreateFootprintList()
{
const wxString term = tokenizer.GetNextToken().Lower();
EDA_COMBINED_MATCHER matcher( term, CTX_LIBITEM );
int matches, position;
for( const std::unique_ptr<FOOTPRINT_INFO>& footprint : fp_info_list->GetList() )
{
wxString search = footprint->GetFootprintName() + wxS( " " ) + footprint->GetSearchText();
bool matched = matcher.Find( search.Lower(), matches, position );
bool matched = matcher.Find( search.Lower() );
if( !matched && term.IsNumber() )
matched = ( wxAtoi( term ) == (int)footprint->GetPadCount() );