Editor/DefaultProviders.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Crux.Editor.Common;
using Editor;
using Sandbox;

public class AssetSearchProvider : ISearchProvider
{
	private string GetIcon( AssetType assetType )
	{
		if ( assetType == AssetType.Model )
		{
			return "view_in_ar";
		}

		if ( assetType == AssetType.Material )
		{
			return "palette";
		}

		if ( assetType == AssetType.Animation )
		{
			return "play_arrow";
		}

		return "insert_drive_file";
	}

	public IEnumerable<SearchResult> Search( string query )
	{
		foreach ( var asset in AssetSystem.All )
		{
			if ( asset.AssetType == AssetType.Texture || asset.AssetType == AssetType.ImageFile ) continue;

			if ( !FuzzyMatcher.FuzzyMatch( query, asset.Name, out int score ) )
				continue;

			if ( asset.Name.Contains( query, StringComparison.OrdinalIgnoreCase ) )
				score += 100;

			if ( asset.Name.StartsWith( query, StringComparison.OrdinalIgnoreCase ) )
				score += 50;

			yield return new SearchResult
			{
				Title = asset.Name,
				Subtitle = asset.RelativePath,
				Icon = GetIcon( asset.AssetType ),
				Score = score,
				OnSelect = () => asset.OpenInEditor(),
				Type = asset.AssetType.ToString()
			};
		}
	}
}

public class SceneObjectsProvider : ISearchProvider
{
	public IEnumerable<SearchResult> Search( string query )
	{
		var scene = SceneEditorSession.Active?.Scene;
		if ( scene is null ) yield break;

		foreach ( var go in scene.GetAllObjects( true ) )
		{
			if ( !FuzzyMatcher.FuzzyMatch( query, go.Name, out int score ) )
			{
				continue;
			}

			if ( go.Name.Contains( query, StringComparison.OrdinalIgnoreCase ) )
				score += 100;

			if ( go.Name.StartsWith( query, StringComparison.OrdinalIgnoreCase ) )
				score += 50;

			yield return new SearchResult
			{
				Title = go.Name,
				Subtitle = string.Join( ", ", go.Components.GetAll().Select( c => c.GetType().Name ) ),
				Icon = "gamepad",
				Score = score,
				OnSelect = () => SceneEditorSession.Active.Selection.Set( go ),
				Type = "SceneObject"
			};
		}
	}
}

public class ComponentSearchProvider : ISearchProvider
{
	public IEnumerable<SearchResult> Search( string query )
	{
		foreach ( var type in TypeLibrary.GetTypes<Component>() )
		{
			if ( !FuzzyMatcher.FuzzyMatch( query, type.Name, out int score ) )
				continue;


			if ( !string.IsNullOrEmpty( type.Namespace ) && type.Namespace.Contains( "Sandbox" ) ) continue;

			if ( type.Name.Contains( query, StringComparison.OrdinalIgnoreCase ) )
				score += 100;

			if ( type.Name.StartsWith( query, StringComparison.OrdinalIgnoreCase ) )
				score += 50;

			yield return new SearchResult
			{
				Title = type.Name,
				Subtitle = type.Namespace ?? "",
				Icon = "code",
				Score = score,
				OnSelect = () =>
				{
					CodeEditor.OpenFile( type.SourceFile, type.SourceLine );
				},
				Type = "Component"
			};
		}
	}
}

public class EditorActionProvider : ISearchProvider
{
	public IEnumerable<SearchResult> Search( string query )
	{
		var seen = new HashSet<string>();

		foreach ( var entry in EditorShortcuts.Entries )
		{
			if ( !seen.Add( entry.Identifier ) )
				continue;

			if ( !FuzzyMatcher.FuzzyMatch( query, entry.Name, out int score ) )
				continue;

			if ( entry.Name.Contains( query, StringComparison.OrdinalIgnoreCase ) )
				score += 100;

			if ( entry.Name.StartsWith( query, StringComparison.OrdinalIgnoreCase ) )
				score += 50;

			yield return new SearchResult
			{
				Title = entry.Name,
				Subtitle = entry.DisplayKeys,
				Type = entry.Group,
				Icon = "keyboard_command_key",
				Score = score,
				OnSelect = () =>
				{
					var id = entry.Identifier;
					IndexerPopup.PendingAction = () =>
					{
						foreach ( var e in EditorShortcuts.Entries )
						{
							if ( e.Identifier != id ) continue;
							if ( e.Invoke() ) break;
						}
					};
				}
			};
		}
	}
}

public class MathProvider : ISearchProvider
{
    public IEnumerable<SearchResult> Search( string query )
    {
        if ( !query.Any( char.IsDigit ) )
            return Enumerable.Empty<SearchResult>();

        var result = TryEvaluate( query );
        if ( result == null )
            return Enumerable.Empty<SearchResult>();

        var text = result.Value.ToString( "G" );

        return new[]
        {
            new SearchResult
            {
                Title = $"{query} = {text}",
                Subtitle = "Copy to clipboard",
                Type = "Math",
                Icon = "calculate",
                Score = 10000,
                OnSelect = () => EditorUtility.Clipboard.Copy( text )
            }
        };
    }
    
    // Note: Unfortunately System.Data.DataTable seems to be blocked in s&box (only when I tried to pull from the library manager)
    // So this requires some manual labor :(

    private double? TryEvaluate( string expr )
    {
        try
        {
            expr = expr.Replace( " ", "" );
            var pos = 0;
            var result = ParseExpression( expr, ref pos );
            return pos == expr.Length ? result : null;
        }
        catch
        {
            return null;
        }
    }

    private double ParseExpression( string expr, ref int pos )
    {
        var left = ParseTerm( expr, ref pos );
        while ( pos < expr.Length && (expr[pos] == '+' || expr[pos] == '-') )
        {
            var op = expr[pos++];
            var right = ParseTerm( expr, ref pos );
            left = op == '+' ? left + right : left - right;
        }
        return left;
    }

    private double ParseTerm( string expr, ref int pos )
    {
        var left = ParseFactor( expr, ref pos );
        while ( pos < expr.Length && (expr[pos] == '*' || expr[pos] == '/') )
        {
            var op = expr[pos++];
            var right = ParseFactor( expr, ref pos );
            left = op == '*' ? left * right : left / right;
        }
        return left;
    }

    private double ParseFactor( string expr, ref int pos )
    {
        if ( pos < expr.Length && expr[pos] == '(' )
        {
            pos++;
            var result = ParseExpression( expr, ref pos );
            if ( pos < expr.Length && expr[pos] == ')' ) pos++;
            return result;
        }

        if ( pos < expr.Length && expr[pos] == '-' )
        {
            pos++;
            return -ParseFactor( expr, ref pos );
        }

        var start = pos;
        while ( pos < expr.Length && (char.IsDigit( expr[pos] ) || expr[pos] == '.') )
            pos++;

        if ( pos == start )
            throw new Exception( "Expected number" );

        return double.Parse( expr.Substring( start, pos - start ), System.Globalization.CultureInfo.InvariantCulture );
    }
}

public class ColorProvider : ISearchProvider
{
    public IEnumerable<SearchResult> Search( string query )
    {
        var color = TryParseColor( query.Trim() );
        if ( color == null )
            return Enumerable.Empty<SearchResult>();

        var c = color.Value;
        var hex = $"#{(int)(c.r * 255):X2}{(int)(c.g * 255):X2}{(int)(c.b * 255):X2}";
        var rgb = $"rgb({(int)(c.r * 255)}, {(int)(c.g * 255)}, {(int)(c.b * 255)})";
        var vec = $"new Color({c.r:F2}f, {c.g:F2}f, {c.b:F2}f)";

        var results = new List<SearchResult>();

        results.Add( new SearchResult
        {
            Title = hex,
            Subtitle = "Copy hex",
            Type = "Color",
            Icon = "palette",
            Score = 10000,
            OnSelect = () => EditorUtility.Clipboard.Copy( hex )
        } );

        results.Add( new SearchResult
        {
            Title = rgb,
            Subtitle = "Copy rgb",
            Type = "Color",
            Icon = "palette",
            Score = 9999,
            OnSelect = () => EditorUtility.Clipboard.Copy( rgb )
        } );

        results.Add( new SearchResult
        {
            Title = vec,
            Subtitle = "Copy C#",
            Type = "Color",
            Icon = "palette",
            Score = 9998,
            OnSelect = () => EditorUtility.Clipboard.Copy( vec )
        } );

        return results;
    }

    private Color? TryParseColor( string input )
    {
	    if ( string.IsNullOrEmpty( input ) ) return null;

	    if ( input.Length == 4 && input[0] == '#' )
	    {
		    var r = Convert.ToInt32( new string( input[1], 2 ), 16 );
		    var g = Convert.ToInt32( new string( input[2], 2 ), 16 );
		    var b = Convert.ToInt32( new string( input[3], 2 ), 16 );
		    return new Color( r / 255f, g / 255f, b / 255f );
	    }

	    if ( (input.Length == 7 || input.Length == 9) && input[0] == '#' )
		    return Color.Parse( input );

	    if ( input.StartsWith( "rgb(", StringComparison.OrdinalIgnoreCase ) && input.EndsWith( ")" ) )
	    {
		    var inner = input.Substring( 4, input.Length - 5 );
		    var parts = inner.Split( ',' );
		    if ( parts.Length == 3
		         && int.TryParse( parts[0].Trim(), out int r )
		         && int.TryParse( parts[1].Trim(), out int g )
		         && int.TryParse( parts[2].Trim(), out int b ) )
		    {
			    return new Color( r / 255f, g / 255f, b / 255f );
		    }
	    }

	    return Color.Parse( input );
    }
}