/// <summary>
        /// Gets a code completion matcher for the provided string. It will try to reuse an existing one.
        /// </summary>
        CompletionDataMatcher GetCompletionDataMatcher(string partialWord)
        {
            // It keeps the last used matcher in a field, and will reuse it if the requested word doesn't change.
            // 'partialWord' doesn't usually change during the lifetime of CompletionDataList, so in general
            // the same matcher will be used by all calculations required for a key press

            if (lastMatcher != null && partialWord == lastMatcher.MatchString)
            {
                return(lastMatcher);
            }

            return(lastMatcher = new CompletionDataMatcher {
                MatcherId = lastMatcherId++,
                StringMatcher = CompletionMatcher.CreateCompletionMatcher(partialWord),
                MatchString = partialWord
            });
        }
        internal static CompletionListFilterResult DefaultFilterItems(ICompletionDataList dataList, IReadOnlyList <int> currentFilteredItems, string oldCompletionString, string completionString, CompletionDataMatcher matcher = null)
        {
            List <int> filteredItems;
            var        newCategories = new List <CategorizedCompletionItems> ();

            matcher = matcher ?? new CompletionDataMatcher {
                StringMatcher = CompletionMatcher.CreateCompletionMatcher(completionString), MatcherId = -1, MatchString = completionString
            };

            if (oldCompletionString == null || !completionString.StartsWith(oldCompletionString, StringComparison.Ordinal))
            {
                // We are filtering the list from scratch (not reusing previous results)
                // This is the most slow operation since we may have to filter a very large amount of items.
                // To improve performance, when there are many items, we try to parallelize the filtering

                // Use multiple threads when we can split the work in chunks of at least 4000 items.
                int numFilterThreads = Math.Max(Math.Min(dataList.Count / 4000, Environment.ProcessorCount / 2), 1);

                // When completion string is empty the matching algorithm is not executed, so there is no need to parallelize
                if (string.IsNullOrEmpty(completionString))
                {
                    numFilterThreads = 1;
                }

                filteredItems = new List <int> ();

                int slice   = dataList.Count / numFilterThreads;
                var results = new(Task, List <int>) [numFilterThreads - 1];
        public virtual CompletionSelectionStatus FindMatchedEntry(ICompletionDataList completionDataList, MruCache cache, string partialWord, List <int> filteredItems)
        {
            // default - word with highest match rating in the list.
            int idx = -1;

            if (DefaultCompletionString != null && string.IsNullOrEmpty(partialWord))
            {
                partialWord = DefaultCompletionString;
            }
            CompletionDataMatcher matcher = null;

            if (!string.IsNullOrEmpty(partialWord))
            {
                matcher = GetCompletionDataMatcher(partialWord);
                string bestWord          = null;
                int    bestRank          = int.MinValue;
                int    bestIndex         = 0;
                int    bestIndexPriority = int.MinValue;
                for (int i = 0; i < filteredItems.Count; i++)
                {
                    int index = filteredItems [i];
                    var data  = completionDataList [index];
                    if (bestIndexPriority > data.PriorityGroup)
                    {
                        continue;
                    }
                    int rank;
                    if (!matcher.CalcMatchRank(data, out rank))
                    {
                        continue;
                    }
                    if (rank > bestRank || data.PriorityGroup > bestIndexPriority)
                    {
                        bestWord          = data.DisplayText;
                        bestRank          = rank;
                        bestIndex         = i;
                        bestIndexPriority = data.PriorityGroup;
                    }
                }

                if (bestWord != null)
                {
                    idx = bestIndex;
                    // exact match found.
                    if (string.Compare(bestWord, partialWord ?? "", true) == 0)
                    {
                        return(new CompletionSelectionStatus(idx));
                    }
                }
            }

            CompletionData currentData;
            int            bestMruIndex;

            if (idx >= 0)
            {
                currentData  = completionDataList [filteredItems [idx]];
                bestMruIndex = cache.GetIndex(currentData);
            }
            else
            {
                bestMruIndex = int.MaxValue;
                currentData  = null;
            }

            for (int i = 0; i < filteredItems.Count; i++)
            {
                var mruData     = completionDataList [filteredItems [i]];
                int curMruIndex = cache.GetIndex(mruData);
                if (curMruIndex == 1)
                {
                    continue;
                }
                if (curMruIndex < bestMruIndex)
                {
                    int r1 = 0, r2 = 0;
                    if (currentData == null || matcher != null && matcher.CalcMatchRank(mruData, out r1) && matcher.CalcMatchRank(currentData, out r2))
                    {
                        if (r1 >= r2 || partialWord.Length == 0 || partialWord.Length == 1 && mruData.DisplayText [0] == partialWord [0])
                        {
                            bestMruIndex = curMruIndex;
                            idx          = i;
                            currentData  = mruData;
                        }
                    }
                }
            }
            return(new CompletionSelectionStatus(idx));
        }