/// <summary>
        /// Performs a free text search on all <paramref name="searchables"/>.  The <paramref name="searchText"/> will match on both the object
        /// and it's parental hierarchy e.g. "chi" "biochemistry" matches column "chi" in Catalogue "biochemistry" strongly.
        /// </summary>
        /// <param name="searchables">All available objects that can be searched (see <see cref="ICoreChildProvider.GetAllSearchables"/>)</param>
        /// <param name="searchText">Tokens to use separated by space e.g. "chi biochemistry CatalogueItem"</param>
        /// <param name="cancellationToken">Token for cancelling match scoring.  This method will return null if cancellation is detected</param>
        /// <param name="showOnlyTypes">Optional (can be null) list of types to return results from.  Not respected if <paramref name="searchText"/> includes type names</param>
        /// <returns></returns>
        public Dictionary <KeyValuePair <IMapsDirectlyToDatabaseTable, DescendancyList>, int> ScoreMatches(Dictionary <IMapsDirectlyToDatabaseTable, DescendancyList> searchables, string searchText, CancellationToken cancellationToken, List <Type> showOnlyTypes)
        {
            SetupRespectUserSettings();

            //do short code substitutions e.g. ti for TableInfo
            if (!string.IsNullOrWhiteSpace(searchText))
            {
                foreach (var kvp in ShortCodes)
                {
                    searchText = Regex.Replace(searchText, $@"\b{kvp.Key}\b", kvp.Value.Name);
                }
            }

            //if user hasn't typed any explicit Type filters
            if (showOnlyTypes != null && TypeNames != null)
            {
                //add the explicit types only if the search text does not contain any explicit type names
                if (string.IsNullOrWhiteSpace(searchText) || !TypeNames.Intersect(searchText.Split(' '), StringComparer.CurrentCultureIgnoreCase).Any())
                {
                    foreach (var showOnlyType in showOnlyTypes)
                    {
                        searchText = searchText + " " + showOnlyType.Name;
                    }
                }
            }

            //Search the tokens for also inclusions e.g. "Pipeline" becomes "Pipeline PipelineCompatibleWithUseCaseNode"
            if (!string.IsNullOrWhiteSpace(searchText))
            {
                foreach (var s in searchText.Split(' ').ToArray())
                {
                    if (AlsoIncludes.ContainsKey(s))
                    {
                        foreach (var v in AlsoIncludes[s])
                        {
                            searchText += " " + v.Name;
                        }
                    }
                }
            }

            //if we have nothing to search for return no results
            if (string.IsNullOrWhiteSpace(searchText) && ID == null)
            {
                return(new Dictionary <KeyValuePair <IMapsDirectlyToDatabaseTable, DescendancyList>, int>());
            }

            var tokens = (searchText ?? "").Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

            var regexes = new List <Regex>();

            //any token that 100% matches a type name is an explicitly typed token
            string[] explicitTypesRequested;

            if (TypeNames != null)
            {
                explicitTypesRequested = TypeNames.Intersect(tokens, StringComparer.CurrentCultureIgnoreCase).ToArray();

                //else it's a regex
                foreach (string token in tokens.Except(TypeNames, StringComparer.CurrentCultureIgnoreCase))
                {
                    regexes.Add(new Regex(Regex.Escape(token), RegexOptions.IgnoreCase));
                }
            }
            else
            {
                explicitTypesRequested = new string[0];
            }

            if (cancellationToken.IsCancellationRequested)
            {
                return(null);
            }

            return(searchables.ToDictionary(
                       s => s,
                       score => ScoreMatches(score, regexes, explicitTypesRequested, cancellationToken)
                       ));
        }