Skip to content

Instantly share code, notes, and snippets.

@anatawa12
Last active October 25, 2024 08:16
Show Gist options
  • Save anatawa12/f64acfa804d285f5640dca79536d9937 to your computer and use it in GitHub Desktop.
Save anatawa12/f64acfa804d285f5640dca79536d9937 to your computer and use it in GitHub Desktop.
Typing file name in project window will select the file starts with the name.
/*
* Quick search with typing file name in project window.
* https://gist.github.com/anatawa12/f64acfa804d285f5640dca79536d9937
*
* Typing file name in project window will select the file starts with the name.
* This is very similar to finder's quick search.
*
* MIT License
*
* Copyright (c) 2023 anatawa12
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#if UNITY_EDITOR && (!ANATAWA12_GISTS_VPM_PACKAGE || GIST_f64acfa804d285f5640dca79536d9937)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace anatawa12.gists
{
[InitializeOnLoad]
internal static class ProjectWindowTypeToSelect
{
static ProjectWindowTypeToSelect() => EditorApplication.delayCall += Initialize;
static void Initialize()
{
foreach (var projectBrowserWrap in ProjectBrowserWrap.GetAllProjectBrowsers())
{
void Function()
{
var listArea = projectBrowserWrap.ListArea;
if (listArea != null)
{
var keyboardActionContext = new KeyboardActionContext(listArea);
listArea.OnKeyboardCallback += keyboardActionContext.KeyboardAction;
EditorApplication.update -= Function;
}
}
EditorApplication.update += Function;
}
}
class KeyboardActionContext
{
private readonly ObjectListAreaWrap _listAreaWrap;
public KeyboardActionContext(ObjectListAreaWrap listAreaWrap) => _listAreaWrap = listAreaWrap;
private string _searchText;
private DateTime _lastKeyTime;
private void UpdateSearchText(Func<string, string> update)
{
if (DateTime.Now - _lastKeyTime > TimeSpan.FromSeconds(1))
_searchText = "";
_lastKeyTime = DateTime.Now;
_searchText = update(_searchText);
// Debug.Log($"SearchText: {_searchText}");
int instanceId = 0;
if (_searchText != "")
{
var results = _listAreaWrap.LocalAssets.FilteredHierarchy.Results;
for (var i = 0; i < results.Length; i++)
{
if (results[i].Name.StartsWith(_searchText, StringComparison.OrdinalIgnoreCase))
{
Debug.Log($"selecting {results[i].Name}");
instanceId = results[i].InstanceID;
break;
}
}
if (instanceId != 0)
{
_listAreaWrap.SetSelection(new [] { instanceId }, false);
var selectedIdx = _listAreaWrap.GetSelectedAssetIdx();
_listAreaWrap.ScrollToPosition(
ObjectListAreaWrap.AdjustRectForFraming(
_listAreaWrap.LocalAssets.Grid.CalcRect(selectedIdx, 0.0f)));
}
}
}
public void KeyboardAction()
{
if (Event.current.type != EventType.KeyDown) return;
if (Event.current.character != 0)
{
Event.current.Use();
UpdateSearchText(s => s + char.ToLower(Event.current.character));
}
else
{
switch (Event.current.keyCode)
{
case KeyCode.UpArrow:
case KeyCode.DownArrow:
case KeyCode.LeftArrow:
case KeyCode.RightArrow:
case KeyCode.Return:
case KeyCode.KeypadEnter:
_searchText = "";
break;
}
}
}
}
#region Wrappers
class ProjectBrowserWrap
{
private static readonly Type Type = Type.GetType("UnityEditor.ProjectBrowser, UnityEditor")!;
private static readonly MethodInfo GetAllProjectBrowsersMethod =
Type.GetMethod("GetAllProjectBrowsers", BindingFlags.Public | BindingFlags.Static)!;
private static readonly FieldInfo ListAreaField =
Type.GetField("m_ListArea", BindingFlags.NonPublic | BindingFlags.Instance)!;
private EditorWindow _real;
public ProjectBrowserWrap(EditorWindow real)
{
if (real.GetType() != Type) throw new ArgumentException("EditorWindow is not a ProjectBrowser", nameof(real));
_real = real;
}
public static List<ProjectBrowserWrap> GetAllProjectBrowsers() =>
((IEnumerable<EditorWindow>)GetAllProjectBrowsersMethod.Invoke(null, null))
.Select(window => new ProjectBrowserWrap(window))
.ToList();
public ObjectListAreaWrap? ListArea => ListAreaField.GetValue(_real) is { } listArea
? new ObjectListAreaWrap(listArea)
: null;
}
class ObjectListAreaWrap
{
private static readonly Type Type = Type.GetType("UnityEditor.ObjectListArea, UnityEditor")!;
private static readonly PropertyInfo KeyboardCallbackProperty =
Type.GetProperty("keyboardCallback", BindingFlags.Public | BindingFlags.Instance)!;
private static readonly MethodInfo GetSelectedAssetIdxMethod =
Type.GetMethod("GetSelectedAssetIdx", BindingFlags.NonPublic | BindingFlags.Instance)!;
private static readonly FieldInfo LocalAssetsField =
Type.GetField("m_LocalAssets", BindingFlags.NonPublic | BindingFlags.Instance)!;
public static readonly FieldInfo SelectionOffsetField =
Type.GetField("m_SelectionOffset", BindingFlags.NonPublic | BindingFlags.Instance)!;
public static readonly MethodInfo SetSelectionMethod =
Type.GetMethod("SetSelection", BindingFlags.NonPublic | BindingFlags.Instance)!;
public static readonly MethodInfo ScrollToPositionMethod =
Type.GetMethod("ScrollToPosition", BindingFlags.NonPublic | BindingFlags.Instance)!;
public static readonly MethodInfo AdjustRectForFramingMethod =
Type.GetMethod("AdjustRectForFraming", BindingFlags.NonPublic | BindingFlags.Static)!;
private object _real;
public ObjectListAreaWrap(object real)
{
if (real.GetType() != Type) throw new ArgumentException("Object is not an ObjectListArea", nameof(real));
_real = real;
}
public Action OnKeyboardCallback
{
get => (Action)KeyboardCallbackProperty.GetValue(_real)!;
set => KeyboardCallbackProperty.SetValue(_real, value);
}
public int GetSelectedAssetIdx() => (int)GetSelectedAssetIdxMethod.Invoke(_real, null);
public LocalGroupWrap LocalAssets => new LocalGroupWrap(LocalAssetsField.GetValue(_real)!);
public int SelectionOffset
{
get => (int)SelectionOffsetField.GetValue(_real);
set => SelectionOffsetField.SetValue(_real, value);
}
public void SetSelection(int[] selectedInstanceIDs, bool doubleClicked)
{
SetSelectionMethod.Invoke(_real, new object[] {selectedInstanceIDs, doubleClicked});
}
public void ScrollToPosition(Rect rect)
{
ScrollToPositionMethod.Invoke(_real, new object[] {rect});
}
public static Rect AdjustRectForFraming(Rect rect) =>
(Rect)AdjustRectForFramingMethod.Invoke(null, new object[] {rect});
public class LocalGroupWrap
{
private static readonly Type Type = Type.GetType("UnityEditor.ObjectListArea+LocalGroup, UnityEditor")!;
private static readonly FieldInfo FilteredHierarchyField =
Type.GetField("m_FilteredHierarchy", BindingFlags.NonPublic | BindingFlags.Instance)!;
public static readonly FieldInfo GridField =
Type.GetField("m_Grid", BindingFlags.Public | BindingFlags.Instance)!;
private object _real;
public LocalGroupWrap(object real)
{
if (real.GetType() != Type) throw new ArgumentException("Object is not a LocalGroup", nameof(real));
_real = real;
}
public FilteredHierarchyWrap FilteredHierarchy => new FilteredHierarchyWrap(FilteredHierarchyField.GetValue(_real)!);
public VerticalGridWrap Grid => new VerticalGridWrap(GridField.GetValue(_real)!);
}
}
class FilteredHierarchyWrap
{
private static readonly Type Type = Type.GetType("UnityEditor.FilteredHierarchy, UnityEditor")!;
private static readonly FieldInfo resultsField =
Type.GetField("m_Results", BindingFlags.NonPublic | BindingFlags.Instance)!;
private object _real;
public FilteredHierarchyWrap(object real)
{
if (real.GetType() != Type) throw new ArgumentException("Object is not a FilteredHierarchy", nameof(real));
_real = real;
}
public FilterResultWrap[] Results =>
Array.ConvertAll((object[])resultsField.GetValue(_real)!, o => new FilterResultWrap(o));
public class FilterResultWrap
{
private static readonly Type Type = Type.GetType("UnityEditor.FilteredHierarchy+FilterResult, UnityEditor")!;
private static readonly FieldInfo InstanceIDField =
Type.GetField("instanceID", BindingFlags.Public | BindingFlags.Instance)!;
private static readonly FieldInfo NameField =
Type.GetField("name", BindingFlags.Public | BindingFlags.Instance)!;
private object _real;
public FilterResultWrap(object real)
{
if (real.GetType() != Type) throw new ArgumentException("Object is not a FilterResult", nameof(real));
_real = real;
}
public int InstanceID => (int)InstanceIDField.GetValue(_real);
public string Name => (string)NameField.GetValue(_real);
}
}
class VerticalGridWrap
{
private static readonly Type Type = Type.GetType("UnityEditor.VerticalGrid, UnityEditor")!;
private static readonly MethodInfo CalcRectMethod =
Type.GetMethod("CalcRect", BindingFlags.Public | BindingFlags.Instance)!;
private object _real;
public VerticalGridWrap(object real)
{
if (real.GetType() != Type) throw new ArgumentException("Object is not a VerticalGrid", nameof(real));
_real = real;
}
public Rect CalcRect(int index, float scroll)
{
return (Rect)CalcRectMethod.Invoke(_real, new object[] {index, scroll});
}
}
#endregion
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment