# SetupEngine<T>(QueryEngine<T>)

> Extended a query engine to work with dynamic search values.

## Definition

* **Type:** Method
* **Namespace:** [UnityEditor.Search](/engine/6000.0/script-reference/unityeditor/search.md)
* **Assembly:** UnityEditor

```csharp
public static void SetupEngine<T>(QueryEngine<T> queryEngine)
```

### Parameters

**** (\[QueryEngine\<T>]\(/engine/6000.0/script-reference/unityeditor/search/queryengine)): Query engine to add extended search value filters and parsers.

### Remarks

Here's a few example of custom providers that use the extended search value when setting up a [QueryEngine](/engine/6000.0/script-reference/unityeditor/search/queryengine.md).

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Search;
using UnityEngine;

static class EasySearchProviderExample
{
    enum ExampleProvider { win, material, shader, folder, component, project, res }

    /// <summary>
    /// Search opened editor windows
    /// </summary>
    [SearchItemProvider]
    public static SearchProvider ExampleWindows()
    {
        return EasySearchProvider.Create(ExampleProvider.win.ToString(), _ => Resources.FindObjectsOfTypeAll<EditorWindow>())
            .AddAction("select", win => win.Focus())
            .AddFilter("floating", "Floating", win => !win.docked)
            .AddOption(ShowDetailsOptions.Inspector)
            .AddOption(EasyOptions.DisplayFilterValueInDescription)
            .AddByReflectionActions();
    }

    /// <summary>
    /// Search any loaded material and select them in the inspector.
    /// </summary>
    [SearchItemProvider]
    public static SearchProvider ExampleMaterials()
    {
        return EasySearchProvider.Create(_ => Resources.FindObjectsOfTypeAll<Material>())
            .AddAction("select", o => Selection.activeObject = o)
            .AddOption(ShowDetailsOptions.Actions | ShowDetailsOptions.Inspector)
            .RemoveOption(ShowDetailsOptions.Description);
    }

    /// <summary>
    /// Search any loaded shader.
    /// Select them in the inspector.
    /// View the shader source in the preview inspector.
    /// </summary>
    [SearchItemProvider]
    public static SearchProvider ExampleShaders()
    {
        static string ReadSource(Shader shader)
        {
            var shaderPath = AssetDatabase.GetAssetPath(shader);
            if (string.IsNullOrEmpty(shaderPath))
                return ShaderUtil.GetShaderData(shader).ToString();
            if (System.IO.File.Exists(shaderPath))
                return $"<size=8>{System.IO.File.ReadAllText(shaderPath)}</size>";
            return shaderPath;
        }

        static string FetchShaderSource(Shader shader, SearchItemOptions options)
        {
            if ((options & SearchItemOptions.FullDescription) != 0)
                return ReadSource(shader) ?? shader.ToString();
            return AssetDatabase.GetAssetPath(shader);
        }

        return EasySearchProvider.Create(_ => Resources.FindObjectsOfTypeAll<Shader>())
            .AddAction("select", o => Selection.activeObject = o)
            .AddOption(ShowDetailsOptions.Actions | ShowDetailsOptions.Inspector | ShowDetailsOptions.Description)
            .AddFilter("source", "Source Code", s => ReadSource(s))
            .SetDescriptionHandler(FetchShaderSource);
    }

    /// <summary>
    /// Search any active game object component.
    /// </summary>
    [SearchItemProvider]
    public static SearchProvider ExampleComponents()
    {
        return EasySearchProvider.Create(_ => Resources.FindObjectsOfTypeAll<Component>())
            .SetDescriptionHandler((obj, options) => obj.GetType().FullName)
            .AddAction("ping", o => EditorGUIUtility.PingObject(o.gameObject))
            .AddAction("select", o => Selection.activeObject = o)
            .AddOption(ShowDetailsOptions.Actions | ShowDetailsOptions.Inspector)
            .RemoveOption(ShowDetailsOptions.Description);
    }

    /// <summary>
    /// Search all project folders.
    /// </summary>
    /// <returns></returns>
    [SearchItemProvider]
    public static SearchProvider ExampleFolders()
    {
        var folderIcon = EditorGUIUtility.FindTexture("Folder Icon");
        return EasySearchProvider.Create(ExampleProvider.folder.ToString(), "Folders",
            _ => System.IO.Directory.EnumerateDirectories("Assets", "*", System.IO.SearchOption.AllDirectories).Select(d => d.Replace("
", "/")))
            .SetThumbnailHandler(dir => folderIcon)
            .AddAction("open", dir => EditorUtility.RevealInFinder(dir))
            .AddAction("select", dir => Selection.activeObject =AssetDatabase.LoadMainAssetAtPath(dir))
            .AddOption(EasyOptions.DescriptionSameAsLabel | EasyOptions.SortByName);
    }

    /// <summary>
    /// Search editor bundle resources.
    /// Note: that bundle resources are cached once.
    /// </summary>
    /// <returns></returns>
    [SearchItemProvider]
    public static SearchProvider ExampleEditorBundles()
    {
        Func<UnityEngine.Object, bool> FR = r => string.Equals(AssetDatabase.GetAssetPath(r), "Library/unity editor resources", StringComparison.Ordinal);
        return EasySearchProvider.Create(ExampleProvider.res.ToString(), "Resources", Resources.FindObjectsOfTypeAll<UnityEngine.Object>().Where(FR))
            .SetDescriptionHandler(r => $"{r.GetType().FullName} ({r.GetInstanceID()})")
            .AddAction("select", o => Selection.activeObject = o)
            .AddAction("copy", "Copy Name", r => EditorGUIUtility.systemCopyBuffer = r.name)
            .AddOption(ShowDetailsOptions.Actions | ShowDetailsOptions.Inspector)
            .AddOption(EasyOptions.YieldAllItemsIfSearchQueryEmpty);
    }

    [MenuItem("Window/Search/Easy")]
    public static void ShowProvider()
    {
        SearchService.ShowContextual(Enum.GetValues(typeof(ExampleProvider)).Cast<ExampleProvider>().Select(e => e.ToString()).ToArray());
    }
}
```

Easy search providers.

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Search;
using UnityEditor.Search.Providers;
using UnityEngine;

[Flags]
enum EasyOptions
{
    None = 0,

    YieldAllItemsIfSearchQueryEmpty = 1 << 5,
    DescriptionSameAsLabel = 1 << 6,
    SortByName = 1 << 7,
    DisplayFilterValueInDescription = 1 << 8,
}

static class EasyOptionsExtensions
{
    public static bool HasAny(this EasyOptions flags, in EasyOptions f) => (flags & f) != 0;
    public static bool HasAll(this EasyOptions flags, in EasyOptions all) => (flags & all) == all;
}

static class EasySearchProvider
{
    public static EasySearchProvider<T> Create<T>(string id, Func<SearchContext, IEnumerable<T>> fetchObjects)
    {
        return Create(id, null, fetchObjects);
    }

    public static EasySearchProvider<T> Create<T>(Func<SearchContext, IEnumerable<T>> fetchObjects)
    {
        return Create(typeof(T).Name.ToLowerInvariant(), null, fetchObjects);
    }

    public static EasySearchProvider<T> Create<T>(string id, string label, Func<SearchContext, IEnumerable<T>> fetchObjects)
    {
        return new EasySearchProvider<T>(id, label, fetchObjects);
    }

    public static EasySearchProvider<T> Create<T>(string id, string label, IEnumerable<T> objects)
    {
        return new EasySearchProvider<T>(id, label, objects);
    }
}

readonly struct EasyFilter
{
    public readonly string name;
    public readonly string label;
    public readonly Func<object, object> func;

    public EasyFilter(string name, string label, Func<object, object> func)
    {
        this.name = name;
        this.label = label;
        this.func = func;
    }
}

class EasySearchProvider<T> : SearchProvider
{
    private bool m_FiltersAdded;
    private EasyOptions m_Options;
    private QueryEngine<T> m_QueryEngine;
    private List<EasyFilter> m_Filters;

    private T[] m_CachedObjects;
    private Func<SearchContext, IEnumerable<T>> m_FetchObjects;

    public EasySearchProvider(string id, string displayName, IEnumerable<T> objects)
        : this(id, displayName, null as Func<SearchContext, IEnumerable<T>>)
    {
        m_CachedObjects = objects.ToArray();
        m_FetchObjects = _ => m_CachedObjects;
    }

    public EasySearchProvider(string id, string displayName, Func<SearchContext, IEnumerable<T>> fetchObjects)
        : base(id, displayName ?? ObjectNames.NicifyVariableName(typeof(T).Name))
    {
        active = false;
        m_FetchObjects = fetchObjects;
        m_Filters = new List<EasyFilter>();

        showDetails = false;
        showDetailsOptions = ShowDetailsOptions.None;
        fetchItems = (context, items, provider) => FetchItems(context);
        fetchPropositions = FetchPropositions;
        fetchDescription = FetchDescription;
        fetchThumbnail = FetchThumbnail;
        startDrag = StartDrag;
        toObject = ToObject;

        m_QueryEngine = new QueryEngine<T>();
        m_QueryEngine.SetSearchDataCallback(SearchWords, s => s.ToLowerInvariant(), StringComparison.Ordinal);
        SearchValue.SetupEngine(m_QueryEngine);

        var dataType = typeof(T);
        if (!dataType.IsPrimitive && dataType != typeof(string))
        {
            AddByReflectionFilters();
            AddFilter("t", "Object Type", obj => obj.GetType().Name);
        }
    }

    public EasySearchProvider<T> AddOption(in EasyOptions options)
    {
        m_Options |= options;
        return this;
    }

    public EasySearchProvider<T> AddOption(ShowDetailsOptions showDetailsOptions)
    {
        this.showDetailsOptions |= showDetailsOptions;
        this.showDetails = this.showDetailsOptions != ShowDetailsOptions.None;
        return this;
    }

    public EasySearchProvider<T> AddAction(string name, Action<T> handler)
    {
        actions.Insert(0, new SearchAction(id, name,
            new GUIContent(ObjectNames.NicifyVariableName(name)),
            (items) => ActionToDataHandler(items, handler)));
        return this;
    }

    public EasySearchProvider<T> AddAction(string name, string label, Action<T> handler)
    {
        actions.Insert(Math.Min(1, actions.Count), new SearchAction(id, name, new GUIContent(label),
            (items) => ActionToDataHandler(items, handler)));
        return this;
    }

    public EasySearchProvider<T> RemoveOption(ShowDetailsOptions showDetailsOptions)
    {
        this.showDetailsOptions &= ~showDetailsOptions;
        this.showDetails = this.showDetailsOptions != ShowDetailsOptions.None;
        return this;
    }

    public EasySearchProvider<T> SetDescriptionHandler(Func<T, SearchItemOptions, string> handler)
    {
        fetchDescription = (item, context) => FetchObjectDescription(item, context, handler);
        return AddOption(ShowDetailsOptions.Description);
    }

    public EasySearchProvider<T> SetDescriptionHandler(Func<T, string> handler)
    {
        return SetDescriptionHandler((T o, SearchItemOptions options) => handler(o));
    }

    public EasySearchProvider<T> SetThumbnailHandler(Func<T, Texture2D> handler)
    {
        this.fetchThumbnail = (item, context) => FetchObjectThumbnail(item, context, handler);
        return this;
    }

    public EasySearchProvider<T> AddFilter<TResult>(string filter, in string label, Func<T, TResult> func)
    {
        if (m_Filters.Any(f => string.Equals(f.name, filter, StringComparison.Ordinal)))
            return this;

        m_Filters.Add(new EasyFilter(filter, label, o => func((T)o)));
        m_QueryEngine.AddFilter(filter, func);
        return this;
    }

    public EasySearchProvider<T> AddByReflectionActions()
    {
        foreach (var m in typeof(T).GetMethods().OrderBy(m => m.Name))
        {
            if (m.GetParameters().Length > 0 || m.IsStatic)
                continue;

            actions.Add(new SearchAction(id, m.Name) { handler = item => HandleMethod(item, m), closeWindowAfterExecution = false });
        }
        return this;
    }

    void StartDrag(SearchItem item, SearchContext context)
    {
        var data = (T)item.data;
        DragAndDrop.PrepareStartDrag();
        if (data is UnityEngine.Object uo)
            DragAndDrop.objectReferences = new [] { uo };
        else
            DragAndDrop.SetGenericData(item.data.GetType().Name, item.data);
        DragAndDrop.StartDrag(GetName(data));
    }

    string FetchObjectDescription(SearchItem item, SearchContext context, Func<T, SearchItemOptions, string> handler)
    {
        if (item.data is T obj)
            return handler(obj, item.options);
        return null;
    }

    Texture2D FetchObjectThumbnail(SearchItem item, SearchContext context, Func<T, Texture2D> handler)
    {
        if (item.data is T obj)
            return handler(obj);
        return null;
    }

    void ActionToDataHandler(SearchItem[] items, Action<T> handler)
    {
        foreach (var item in items)
        {
            if (item.data is T obj)
                handler(obj);
        }
    }

    void AddByReflectionFilters()
    {
        foreach (var prop in typeof(T).GetProperties())
        {
            var type = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
            var filterName = ObjectNames.NicifyVariableName(prop.Name).Replace(" ", "").ToLowerInvariant();
            AddFilter(filterName, $"C# {type.Name}", obj => FetchPropertyValue(obj, prop));
        }

        foreach (var prop in typeof(T).GetFields())
        {
            var type = Nullable.GetUnderlyingType(prop.FieldType) ?? prop.FieldType;
            var filterName = ObjectNames.NicifyVariableName(prop.Name).Replace(" ", "").ToLowerInvariant();
            AddFilter(filterName, $"C# {type.Name}", obj => FetchPropertyValue(obj, prop));
        }
    }

    IEnumerable<SearchProposition> FetchPropositions(SearchContext context, SearchPropositionOptions options)
    {
        foreach (var f in m_Filters)
            yield return new SearchProposition(f.name, f.name, f.label);
    }

    IEnumerable<string> SearchWords(T obj)
    {
        yield return GetName(obj).ToLowerInvariant();
        yield return obj.ToString().ToLowerInvariant();
    }

    string GetName(T obj)
    {
        if (TryGetProperty(obj, "name", out var objName) && objName is string ons && !string.IsNullOrEmpty(ons))
            return ons.ToString();
        return obj.ToString();
    }

    bool TryGetProperty(in T obj, string name, out object value)
    {
        var p = obj.GetType().GetProperty(name);
        if (p != null)
        {
            value = p.GetValue(obj);
            return true;
        }

        value = null;
        return false;
    }

    IEnumerable<SearchItem> FetchItems(SearchContext context)
    {
        if (string.IsNullOrEmpty(context.searchQuery))
        {
            if (HasOption(EasyOptions.YieldAllItemsIfSearchQueryEmpty))
                return SortObjects(m_FetchObjects(context)).Select((o, index) => CreateItem(context, GetName(o), index, o));
            return Enumerable.Empty<SearchItem>();
        }

        if (!m_FiltersAdded)
            AddFilters(m_FetchObjects(context).FirstOrDefault());

        var query = m_QueryEngine.Parse(context.searchQuery);
        if (!query.valid)
        {
            context.AddSearchQueryErrors(query.errors.Select(e => new SearchQueryError(e, context, this)));
            return Enumerable.Empty<SearchItem>();
        }

        return SearchItems(context, query);
    }

    string FetchDescription(SearchItem item, SearchContext context)
    {
        if ((item.options & SearchItemOptions.Compacted) != 0 || m_Options.HasAny(EasyOptions.DescriptionSameAsLabel))
            return item.GetLabel(context);

        if (m_Options.HasAny(EasyOptions.DisplayFilterValueInDescription))
        {
            var description = "";
            foreach (var f in m_Filters)
            {
                if (context.searchQuery.LastIndexOf(f.name) == -1)
                    continue;

                if (description.Length != 0)
                    description += ", ";
                description += $"{f.name}={f.func(item.data)}";
            }

            return description;
        }

        return item.data.ToString();
    }

    private Texture2D FetchThumbnail(SearchItem item, SearchContext context)
    {
        if (item.data is UnityEngine.Object uo)
            return AssetPreview.GetMiniThumbnail(uo);
        return EditorGUIUtility.FindTexture(item.data.GetType().Name);
    }

    private UnityEngine.Object ToObject(SearchItem item, Type type)
    {
        return item.data as UnityEngine.Object;
    }

    IEnumerable<T> SortObjects(IEnumerable<T> items)
    {
        if (m_Options.HasAny(EasyOptions.SortByName))
            items = items.OrderBy(e => GetName(e));
        return items;
    }

    IEnumerable<SearchItem> SearchItems(SearchContext context, Query<T> query)
    {
        int index = 0;
        foreach (var o in SortObjects(m_FetchObjects(context)))
        {
            if (o == null || o.Equals(default(T)))
            {
                yield return null;
                continue;
            }

            if (!query.Test(o))
            {
                yield return null;
                continue;
            }

            var score = index++;
            var name = GetName(o);
            if (!m_Options.HasAny(EasyOptions.SortByName))
                score = ComputeScore(name);
            yield return CreateItem(context, name, score, o);
        }
    }

    void AddFilters(T o)
    {
        if (o == null)
            return;

        if (o is UnityEngine.Object uo)
        {
            using (var so = new SerializedObject(uo))
            {
                var p = so.GetIterator();
                var next = p.NextVisible(true);
                while (next)
                {
                    var propertyPath = p.propertyPath;
                    var filterName = ObjectNames.NicifyVariableName(propertyPath).Replace(" ", "").ToLowerInvariant();

                    AddFilter(filterName, $"{p.displayName} ({p.propertyType})", obj => FetchPropertyValue(obj, propertyPath));
                    next = p.NextVisible(p.hasChildren);
                }
            }
        }

        m_FiltersAdded = true;
    }

    private void HandleMethod(SearchItem item, MethodInfo mi)
    {
        var result = mi.Invoke(item.data, null);
        var unityObject = item.data as UnityEngine.Object;
        if (result != null)
            Debug.Log(result, unityObject);
        else
            Debug.Log($"Executed {mi.DeclaringType.FullName}.{mi.Name}", unityObject);
    }

    SearchValue FetchPropertyValue(in T obj, in FieldInfo prop) => new SearchValue(prop.GetValue(obj));
    SearchValue FetchPropertyValue(in T obj, in PropertyInfo prop) => new SearchValue(prop.GetValue(obj));

    SearchValue FetchPropertyValue(in T obj, in string propertyPath)
    {
        if (obj is UnityEngine.Object uo)
        {
            using (var so = new SerializedObject(uo))
            {
                var p = so.FindProperty(propertyPath);
                if (p == null)
                    return SearchValue.invalid;
                return SearchValue.ConvertPropertyValue(p);
            }
        }

        return SearchValue.invalid;
    }

    SearchItem CreateItem(in SearchContext context, in string name, in int score, in T o)
    {
        if (o == null)
            return null;
        return CreateItem(context, Guid.NewGuid().ToString("N"), score, name, null, null, o);
    }

    int ComputeScore(in string name)
    {
        if (name.Length > 2)
        {
            var sp = Math.Max(0, name.LastIndexOf('/'));
            if (sp + 2 < name.Length)
                return name[sp] * 5 + name[sp + 1] * 2 + name[sp + 2];
        }
        return 99;
    }

    bool HasOption(in EasyOptions option)
    {
        return m_Options.HasAny(option);
    }
}
```
