using Sandbox.Internal; using System; namespace Editor; /// /// Interface for editors to open code files. /// Any class that implements this interface is automatically added to the list. /// An editor is only enabled if returns true. /// /// Decorate your implementation with a . /// public interface ICodeEditor { /// /// Opens a file in the editor, optionally at a line and column. /// public void OpenFile( string path, int? line = null, int? column = null ); /// /// Open the solution of all sandbox projects /// public void OpenSolution(); /// /// Open given addon in the editor. /// public void OpenAddon( Project addon ); /// /// Whether or not this editor is installed. /// public bool IsInstalled(); } /// /// For opening source code files in whatever code editor the user has selected. /// public static partial class CodeEditor { private static ICodeEditor _current; /// /// The current code editor we're using. /// [Title( "Code Editor" )] public static ICodeEditor Current { get { if ( _current == null ) { var editorName = EditorCookie.GetString( "CodeEditor", GetDefault() ); var editorType = EditorTypeLibrary.GetTypes().FirstOrDefault( t => t.Name == editorName ); // Check if our selected editor is still valid if ( editorType != null ) { var editor = editorType.Create(); if ( !editor.IsInstalled() ) { Log.Warning( $"Code editor '{editorName}' not installed, using default" ); editorType = EditorTypeLibrary.GetTypes().FirstOrDefault( t => t.Name == GetDefault() ); } } // Check if our selected editor even exists as a type if ( editorType == null ) { Log.Warning( $"Code editor '{editorName}' not found, using default" ); editorType = EditorTypeLibrary.GetTypes().FirstOrDefault( t => t.Name == GetDefault() ); } // If you seriously have no code editor installed, _current can just be null if ( editorType != null ) { EditorCookie.SetString( "CodeEditor", editorType.Name ); // Make sure the cookie gets reset if we're falling back _current = editorType.Create(); } } return _current; } set { _current = value; EditorCookie.SetString( "CodeEditor", value.GetType().Name ); } } /// /// Tries to find an editor type with a matching name, and checks to see if it's installed on our system. /// /// /// private static TypeDescription GetEditorDescription( string editorName ) { return EditorTypeLibrary.GetTypes().Where( x => !x.IsInterface && x.Name == editorName && x.Create().IsInstalled() ).FirstOrDefault(); } /// /// Decides on a default code editor to use, defaults to Visual Studio or Visual Studio Code if not installed. /// Since code editors are made in addon space, we don't have their types ready to use. /// /// private static string GetDefault() { // Default to Visual Studio var editorType = GetEditorDescription( "VisualStudio" ); if ( editorType != null ) return editorType.Name; // Default to Visual Studio Code editorType = GetEditorDescription( "VisualStudioCode" ); if ( editorType != null ) return editorType.Name; // Find any available code editor.. return EditorTypeLibrary.GetTypes() .Where( x => !x.IsInterface && x.Create().IsInstalled() ) .FirstOrDefault() ?.Name ?? null; } /// /// Friendly name for our current code editor. /// public static string Title { get { if ( CodeEditor.Current != null ) { var displayInfo = DisplayInfo.ForType( CodeEditor.Current.GetType(), true ); if ( !string.IsNullOrEmpty( displayInfo.Name ) ) return displayInfo.Name; } return "Code Editor"; } } private static void OpenFile( string path, int? line, int? column, string memberName ) { ArgumentNullException.ThrowIfNullOrEmpty( path ); if ( AssetSystem.FindByPath( path ) is { } asset ) { if ( !IAssetEditor.OpenInEditor( asset, out var editor ) ) { return; } if ( memberName is null || editor is null ) { return; } editor.SelectMember( memberName ); return; } if ( !System.IO.Path.IsPathRooted( path ) ) { foreach ( var project in EditorUtility.Projects.GetAll() ) { if ( !project.Active ) continue; if ( !project.HasCompiler ) continue; var codePath = project.GetCodePath(); var fullPath = System.IO.Path.Combine( codePath, path ); if ( System.IO.File.Exists( fullPath ) ) { path = fullPath; goto found; } } Log.Error( $"Couldn't resolve relative path: '{path}'" ); return; } found: if ( AssertCodeEditor() ) Current?.OpenFile( path, line, column ); } public static void OpenFile( ISourcePathProvider location ) { OpenFile( location.Path, (location as ISourceLineProvider)?.Line, (location as ISourceColumnProvider)?.Column, (location as IMemberNameProvider)?.MemberName ); } /// /// Opens a file in the current editor, optionally at a line and column. /// public static void OpenFile( string path, int? line = null, int? column = null ) { OpenFile( path, line, column, null ); } /// /// Returns true if the file exists and can be opened by the current code editor. /// /// /// public static bool CanOpenFile( string path ) { if ( string.IsNullOrWhiteSpace( path ) ) return false; if ( AssetSystem.FindByPath( path ) is { } asset ) { return true; } if ( !System.IO.Path.IsPathRooted( path ) ) { foreach ( var project in EditorUtility.Projects.GetAll() ) { if ( !project.Active ) continue; if ( !project.HasCompiler ) continue; var codePath = project.GetCodePath(); var fullPath = System.IO.Path.Combine( codePath, path ); if ( System.IO.File.Exists( fullPath ) ) { return true; } } return false; } return false; } /// /// Open the solution of all s&box projects /// public static void OpenSolution() { if ( AssertCodeEditor() ) Current?.OpenSolution(); } public static void OpenAddon( Project addon ) { if ( AssertCodeEditor() ) Current.OpenAddon( addon ); } private static bool AssertCodeEditor() { if ( Current == null ) { EditorUtility.DisplayDialog( "No Code Editor", "No code editor found, you'll want something like Visual Studio." ); return false; } return true; } /// /// Finds a .sln this path belongs to, this is pretty much entirely for internal usage to open engine slns /// public static string FindSolutionFromPath( string path ) { if ( path == null || path.Length < 5 ) throw new Exception( $"Couldn't find solution file from path \"{path}\"" ); var addonFile = System.IO.Path.Combine( path, ".sbproj" ); if ( System.IO.File.Exists( addonFile ) ) { return AddonSolutionPath(); } var solutions = System.IO.Directory.EnumerateFiles( path, "*.slnx" ).ToArray(); if ( solutions.Length > 0 ) { return string.Join( ";", solutions ); } return FindSolutionFromPath( System.IO.Directory.GetParent( path ).FullName ); } public static string AddonSolutionPath() { return $"{Project.Current.GetRootPath()}/{Project.Current.Config.Ident}.slnx"; } }