Skip to content

Instantly share code, notes, and snippets.

@wappenull
Last active May 30, 2022 10:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wappenull/c03380929b9f579c9c2530390d313d30 to your computer and use it in GitHub Desktop.
Save wappenull/c03380929b9f579c9c2530390d313d30 to your computer and use it in GitHub Desktop.
(UnityEditor) Search for test assembly location and offer to turn its folder ON/OFF from Unity import
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
namespace Wappen.Editor
{
/// <summary>
/// Search for test assembly location and offer to turn its folder ON/OFF from unity import.
/// </summary>
/// <remarks>
/// Initial thread
/// https://forum.unity.com/threads/any-tip-for-hiding-away-test-assembly-for-faster-script-reload.1281116/
/// By Wappen.
/// </remarks>
public class EditorTestAssemblyOnOff : EditorWindow
{
readonly List<LocationState> m_Locations = new List<LocationState>( );
[MenuItem( "Tools/Wappen/EditorTestAssemblyOnOff" )]
static void ShowWindow( )
{
EditorWindow.GetWindow<EditorTestAssemblyOnOff>( true, "EditorTestAssemblyOnOff By Wappen" );
}
private void OnEnable( )
{
_SearchLocations( );
}
void OnGUI( )
{
EditorGUILayout.LabelField( "All location(s) found", EditorStyles.boldLabel );
EditorGUILayout.LabelField( "It will search for any asmdef location in 'Assets' folder with pattern '***.Test(s).asmdef'", EditorStyles.wordWrappedLabel );
EditorGUILayout.HelpBox( "Disabling test assemblies (when you dont need it) will save you some recompile time on your development cycle. Dont forget to revert the setting when you commiting to version control!", MessageType.Info );
if( EditorApplication.isCompiling )
{
EditorGUILayout.HelpBox( "Please wait until script reload to finish...", MessageType.Warning );
}
else
{
if( GUILayout.Button( "Refresh" ) )
_SearchLocations( );
if( m_Locations.Count == 0 )
EditorGUILayout.HelpBox( "Not found any test assembly...", MessageType.Warning );
for( int i = 0; i<m_Locations.Count; i++ )
_PrintEntry( i, m_Locations[i] );
GUILayout.BeginHorizontal( );
{
if( GUILayout.Button( "ALL OFF" ) )
_SetAllTo( false );
if( GUILayout.Button( "ALL ON" ) )
_SetAllTo( true );
}
GUILayout.EndHorizontal( );
EditorGUILayout.LabelField( "Turning the entry OFF will prepend '.' dot to its parent folder name, preventing Unity from importing it.", EditorStyles.wordWrappedLabel );
if( GUILayout.Button( "Apply" ) )
_ApplyState( );
}
}
private void _SetAllTo( bool v )
{
foreach( var loc in m_Locations )
loc.enabled = v;
}
private void _PrintEntry( int index, LocationState loc )
{
// Get the filename + top director name on display
string displaying = $"{loc.GetTopDirectory(withFullPath: false)}/{loc.GetFileName()}";
float backup = EditorGUIUtility.labelWidth;
EditorGUI.indentLevel++;
GUILayout.BeginHorizontal( UnityEngine.GUI.skin.box );
{
EditorGUILayout.LabelField( $"{index}: {displaying}" );
EditorGUIUtility.labelWidth = 50; // Reduce label height for inlining
loc.enabled = EditorGUILayout.Toggle( "ON", loc.enabled );
EditorGUIUtility.labelWidth = backup;
}
GUILayout.EndHorizontal( );
EditorGUI.indentLevel--;
}
private void _SearchLocations( )
{
// Find anything with ***.Test(s).asmdef
// Those file or location could already disabled by the tool (by prepending . to folder to exclude from importing)
// so dont expect to search via asset database
// Use normal IO file search operation
m_Locations.Clear( );
Regex testAsmPattern = new Regex( @".+?\.Test[s]?\.asmdef" );
string assetFolder = Application.dataPath;
foreach( string path in Directory.EnumerateFiles( assetFolder, "*.asmdef", SearchOption.AllDirectories ) )
{
string filename = Path.GetFileName( path );
if( testAsmPattern.IsMatch( filename ) )
{
m_Locations.Add( new LocationState( path ) );
}
}
}
private void _ApplyState( )
{
List<LocationState> changed = new List<LocationState>( );
string noticeText = "";
for( int i = 0; i<m_Locations.Count; i++ )
{
var loc = m_Locations[i];
if( loc.GetCurrentState( ) != loc.enabled )
{
string fn = loc.GetFileName( );
changed.Add( loc );
noticeText += $"{i}: {loc.GetTopDirectory( withFullPath: false )}/{fn} -> {loc.GetExpectedTopDirectoryNameForState( loc.enabled, withFullPath: false )}/{fn}\n";
}
}
if( changed.Count > 0 )
{
noticeText = "This will apply following changes:\n" + noticeText + "\nYou will get an auto script reload after this, continue?";
if( EditorUtility.DisplayDialog( nameof( EditorTestAssemblyOnOff ), noticeText, "Apply", "Cancel" ) )
{
_ApplyChangeList( changed );
AssetDatabase.Refresh( );
// Then wait for script reload, and it should recall OnEnabled on this again
}
}
else
{
EditorUtility.DisplayDialog( nameof( EditorTestAssemblyOnOff ), "Nothing need to be applied", "ok" );
}
}
private void _ApplyChangeList( List<LocationState> changed )
{
foreach( var loc in changed )
{
string current = loc.GetTopDirectory( withFullPath: true );
string target = loc.GetExpectedTopDirectoryNameForState( loc.enabled );
// If there is error, let user see the exception...
Directory.Move( current, target );
// Also rename meta file to avoid editor warning, and retaining same folder UID
string metaCurrent = current + ".meta";
string metaTarget = target + ".meta";
try
{
File.Move( metaCurrent, metaTarget );
}
catch( Exception )
{
// This is allowed to failed, that's fine, user will manage with their version control
Debug.LogWarning( $"Failed to move meta file {metaCurrent}, hope you good luck with that in your next commit :)" );
}
}
}
class LocationState
{
public readonly string fullPath;
public bool enabled;
public LocationState( string path )
{
this.fullPath = path;
enabled = GetCurrentState( );
}
public string GetFileName( )
{
return Path.GetFileName( fullPath );
}
public string GetTopDirectory( bool withFullPath )
{
string dir = Path.GetDirectoryName( fullPath );
if( !withFullPath )
return Path.GetFileName( dir );
return dir;
}
public bool GetCurrentState( )
{
return GetTopDirectory( withFullPath: false ).StartsWith( "." ) == false;
}
public string GetExpectedTopDirectoryNameForState( bool state, bool withFullPath = true )
{
string partBeforeTop = Path.GetDirectoryName( Path.GetDirectoryName( fullPath ) ); // Strip 2 times
string topDir = GetTopDirectory( withFullPath: false );
topDir = topDir.TrimStart( '.' ); // Make sure starting string has no dot
// Disabled state, with dot
if( !state )
topDir = "." + topDir;
if( withFullPath )
return Path.Combine( partBeforeTop, topDir );
else
return topDir;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment