Files
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

289 lines
7.6 KiB
C#

using Sandbox.Internal;
using System;
namespace Editor;
/// <summary>
/// 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 <see cref="IsInstalled"/> returns true.
///
/// Decorate your implementation with a <see cref="TitleAttribute"/>.
/// </summary>
public interface ICodeEditor
{
/// <summary>
/// Opens a file in the editor, optionally at a line and column.
/// </summary>
public void OpenFile( string path, int? line = null, int? column = null );
/// <summary>
/// Open the solution of all sandbox projects
/// </summary>
public void OpenSolution();
/// <summary>
/// Open given addon in the editor.
/// </summary>
public void OpenAddon( Project addon );
/// <summary>
/// Whether or not this editor is installed.
/// </summary>
public bool IsInstalled();
}
/// <summary>
/// For opening source code files in whatever code editor the user has selected.
/// </summary>
public static partial class CodeEditor
{
private static ICodeEditor _current;
/// <summary>
/// The current code editor we're using.
/// </summary>
[Title( "Code Editor" )]
public static ICodeEditor Current
{
get
{
if ( _current == null )
{
var editorName = EditorCookie.GetString( "CodeEditor", GetDefault() );
var editorType = EditorTypeLibrary.GetTypes<ICodeEditor>().FirstOrDefault( t => t.Name == editorName );
// Check if our selected editor is still valid
if ( editorType != null )
{
var editor = editorType.Create<ICodeEditor>();
if ( !editor.IsInstalled() )
{
Log.Warning( $"Code editor '{editorName}' not installed, using default" );
editorType = EditorTypeLibrary.GetTypes<ICodeEditor>().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<ICodeEditor>().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<ICodeEditor>();
}
}
return _current;
}
set
{
_current = value;
EditorCookie.SetString( "CodeEditor", value.GetType().Name );
}
}
/// <summary>
/// Tries to find an editor type with a matching name, and checks to see if it's installed on our system.
/// </summary>
/// <param name="editorName"></param>
/// <returns></returns>
private static TypeDescription GetEditorDescription( string editorName )
{
return EditorTypeLibrary.GetTypes<ICodeEditor>().Where( x => !x.IsInterface && x.Name == editorName && x.Create<ICodeEditor>().IsInstalled() ).FirstOrDefault();
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
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<ICodeEditor>()
.Where( x => !x.IsInterface && x.Create<ICodeEditor>().IsInstalled() )
.FirstOrDefault()
?.Name ?? null;
}
/// <summary>
/// Friendly name for our current code editor.
/// </summary>
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 );
}
/// <summary>
/// Opens a file in the current editor, optionally at a line and column.
/// </summary>
public static void OpenFile( string path, int? line = null, int? column = null )
{
OpenFile( path, line, column, null );
}
/// <summary>
/// Returns true if the file exists and can be opened by the current code editor.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Open the solution of all s&amp;box projects
/// </summary>
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;
}
/// <summary>
/// Finds a .sln this path belongs to, this is pretty much entirely for internal usage to open engine slns
/// </summary>
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";
}
}