github.com/aretext/aretext@v1.3.0/menu/search.go (about)

     1  package menu
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/aretext/aretext/menu/fuzzy"
     7  )
     8  
     9  const (
    10  	maxSearchItemNameLen = 1024
    11  	maxSearchQueryLen    = 1024
    12  )
    13  
    14  // Search performs approximate text searches for menu items matching a query string.
    15  type Search struct {
    16  	emptyQueryShowAll bool
    17  	fuzzyIndex        *fuzzy.Index
    18  	aliasIndex        map[string]int
    19  	items             []Item
    20  	results           []Item
    21  }
    22  
    23  func NewSearch(items []Item, emptyQueryShowAll bool) *Search {
    24  	itemNames := make([]string, len(items))
    25  	aliasIndex := make(map[string]int, 0)
    26  	for itemId, item := range items {
    27  		// Truncate long names to avoid perf issues when fuzzy searching.
    28  		itemNames[itemId] = truncateString(item.Name, maxSearchItemNameLen)
    29  		for _, alias := range item.Aliases {
    30  			aliasIndex[alias] = itemId
    31  		}
    32  	}
    33  
    34  	var results []Item
    35  	if emptyQueryShowAll {
    36  		results = append(results, items...)
    37  	}
    38  
    39  	return &Search{
    40  		emptyQueryShowAll: emptyQueryShowAll,
    41  		fuzzyIndex:        fuzzy.NewIndex(itemNames),
    42  		aliasIndex:        aliasIndex,
    43  		items:             items,
    44  		results:           results,
    45  	}
    46  }
    47  
    48  // Execute searches for the given query.
    49  func (s *Search) Execute(q string) {
    50  	if len(q) == 0 {
    51  		if s.emptyQueryShowAll {
    52  			s.results = make([]Item, 0, len(s.items))
    53  			s.results = append(s.results, s.items...)
    54  		} else {
    55  			s.results = nil
    56  		}
    57  		return
    58  	}
    59  
    60  	// Truncate long queries to avoid perf issues when fuzzy searching.
    61  	truncatedQuery := truncateString(q, maxSearchQueryLen)
    62  	resultItemIds := s.fuzzyIndex.Search(truncatedQuery)
    63  	results := make([]Item, 0, len(resultItemIds)+1)
    64  	itemIdMatchingAlias := -1
    65  	if itemId, ok := s.aliasIndex[strings.ToLower(truncatedQuery)]; ok {
    66  		itemIdMatchingAlias = itemId
    67  		results = append(results, s.items[itemId])
    68  	}
    69  	for _, itemId := range resultItemIds {
    70  		if itemId != itemIdMatchingAlias {
    71  			results = append(results, s.items[itemId])
    72  		}
    73  	}
    74  	s.results = results
    75  }
    76  
    77  // Results returns the menu items matching the current query.
    78  // Items are sorted descending by relevance to the query,
    79  // with ties broken by lexicographic ordering.
    80  func (s *Search) Results() []Item {
    81  	return s.results
    82  }
    83  
    84  func truncateString(s string, maxLen int) string {
    85  	if len(s) > maxLen {
    86  		return s[0:maxLen]
    87  	} else {
    88  		return s
    89  	}
    90  }