Adds help method to C# scripting. (#2368)

This commit is contained in:
Vera Aguilera Puerto
2021-12-27 18:47:16 +01:00
committed by GitHub
parent a54283e637
commit eecb104cc5
6 changed files with 290 additions and 5 deletions

View File

@@ -210,6 +210,23 @@ namespace Robust.Client.Console
vvm.OpenVV(a);
}
protected override void WriteSyntax(object toString)
{
var code = toString.ToString();
if (code == null)
return;
var options = ScriptInstanceShared.GetScriptOptions(_owner._reflectionManager).AddReferences(typeof(Image).Assembly);
var script = CSharpScript.Create(code, options, typeof(ScriptGlobals));
script.Compile();
var syntax = new FormattedMessage();
ScriptInstanceShared.AddWithSyntaxHighlighting(script, syntax, code, _owner._highlightWorkspace);
_owner.OutputPanel.AddMessage(syntax);
}
public override void write(object toString)
{
_owner.OutputPanel.AddText(toString?.ToString() ?? "");

View File

@@ -155,6 +155,11 @@ namespace Robust.Client.Console
IoCManager.InjectDependencies(this);
}
protected override void WriteSyntax(object toString)
{
// No-op: nothing to write to.
}
public override void write(object toString)
{
// No-op: nothing to write to.

View File

@@ -224,9 +224,9 @@ namespace Robust.Server.Scripting
instance.RunningScript = false;
}
if (instance.OutputBuffer.Length != 0)
if (!instance.OutputBuffer.IsEmpty)
{
msg.AddText(instance.OutputBuffer.ToString());
msg.AddMessage(instance.OutputBuffer);
instance.OutputBuffer.Clear();
}
@@ -332,7 +332,7 @@ namespace Robust.Server.Scripting
{
public Workspace HighlightWorkspace { get; } = new AdhocWorkspace();
public StringBuilder InputBuffer { get; } = new();
public StringBuilder OutputBuffer { get; } = new();
public FormattedMessage OutputBuffer { get; } = new();
public bool RunningScript { get; set; }
public ScriptGlobals Globals { get; }
@@ -348,6 +348,8 @@ namespace Robust.Server.Scripting
private sealed class ScriptGlobalsImpl : ScriptGlobals
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
private readonly ScriptInstance _scriptInstance;
public ScriptGlobalsImpl(ScriptInstance scriptInstance)
@@ -356,11 +358,26 @@ namespace Robust.Server.Scripting
IoCManager.InjectDependencies(this);
}
protected override void WriteSyntax(object toString)
{
if (_scriptInstance.RunningScript && toString?.ToString() is {} code)
{
var options = ScriptInstanceShared.GetScriptOptions(_reflectionManager);
var script = CSharpScript.Create(code, options, typeof(ScriptGlobals));
script.Compile();
var syntax = new FormattedMessage();
ScriptInstanceShared.AddWithSyntaxHighlighting(script, syntax, code, _scriptInstance.HighlightWorkspace);
_scriptInstance.OutputBuffer.AddMessage(syntax);
}
}
public override void write(object toString)
{
if (_scriptInstance.RunningScript)
if (_scriptInstance.RunningScript && toString.ToString() is {} value)
{
_scriptInstance.OutputBuffer.AppendLine(toString?.ToString());
_scriptInstance.OutputBuffer.AddText(value);
}
}

View File

@@ -1,11 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Text;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Robust.Shared.Scripting
{
@@ -15,6 +19,9 @@ namespace Robust.Shared.Scripting
[SuppressMessage("ReSharper", "CA1822")]
public abstract class ScriptGlobalsShared
{
private const BindingFlags DefaultHelpFlags =
BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
[field: Dependency] public IEntityManager ent { get; } = default!;
[field: Dependency] public IPrototypeManager prot { get; } = default!;
[field: Dependency] public IMapManager map { get; } = default!;
@@ -103,6 +110,50 @@ namespace Robust.Shared.Scripting
return m!.Invoke(target, args);
}
public void help()
{
help(GetType(), DefaultHelpFlags &~ BindingFlags.NonPublic, false, false);
}
public void help(Type type, BindingFlags flags = DefaultHelpFlags, bool specialNameMethods = true, bool modifiers = true)
{
var builder = new StringBuilder();
foreach (var member in type.GetMembers(flags))
{
switch (member.MemberType)
{
case MemberTypes.Method:
var method = (MethodInfo) member;
if (!specialNameMethods && method.IsSpecialName)
continue; // Let's not print constructors, property methods, etc.
builder.Append(method.PrintMethodSignature(modifiers));
builder.AppendLine(";");
break;
case MemberTypes.Property:
builder.AppendLine(((PropertyInfo)member).PrintPropertySignature(modifiers, true));
break;
case MemberTypes.Field:
builder.Append(((FieldInfo) member).PrintFieldSignature(modifiers));
builder.AppendLine(";");
break;
default:
continue;
}
builder.AppendLine();
}
// This is slow, so do it all at once.
WriteSyntax(builder.ToString());
}
protected abstract void WriteSyntax(object toString);
public abstract void write(object toString);
public abstract void show(object obj);
}

View File

@@ -19,6 +19,8 @@ namespace Robust.Shared.Utility
public TagList Tags => new(_tags);
private readonly List<Tag> _tags;
public bool IsEmpty => _tags.Count == 0;
public FormattedMessage()
{
_tags = new List<Tag>();

View File

@@ -1,5 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Text;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -68,5 +73,193 @@ namespace Robust.Shared.Utility
return stringRep!;
}
public static string PrintTypeSignature(this Type type)
{
if (TypeShortHand.TryGetValue(type, out var value))
return value;
var nullable = false;
if (type.IsNullable(out var underlying))
{
type = underlying;
nullable = true;
}
if (type.IsGenericType)
{
return $"{type.Name.Split('`')[0]}<{string.Join(", ", type.GetGenericArguments().Select(PrintTypeSignature))}>{(nullable ? "?" : string.Empty)}";
}
return type.Name;
}
public static string PrintParameterSignature(this ParameterInfo parameter)
{
var builder = new StringBuilder();
if (parameter.IsIn)
{
builder.Append($"in ");
}
if (parameter.IsOut)
{
builder.Append($"out ");
}
else if (parameter.ParameterType.IsByRef)
{
builder.Append($"ref ");
}
builder.Append(parameter.ParameterType.PrintTypeSignature());
if (parameter.Name is { } name)
{
builder.Append($" {name}");
}
if (parameter.HasDefaultValue)
{
builder.Append($" = {PrintUserFacing(parameter.DefaultValue)}");
}
return builder.ToString();
}
public static string PrintMethodSignature(this MethodInfo method, bool modifiers = false, bool arguments = true, bool returnType = true, bool name = true)
{
var builder = new StringBuilder();
if (modifiers)
{
// Access modifiers.
if (method.IsPublic)
builder.Append("public ");
if (method.IsPrivate)
builder.Append("private ");
if (method.IsFamilyAndAssembly)
builder.Append("private protected ");
if (method.IsFamily)
builder.Append("protected ");
if (method.IsFamilyOrAssembly)
builder.Append("protected internal ");
if (method.IsAssembly)
builder.Append("internal ");
if(method.IsStatic)
builder.Append("static ");
if (method.IsAbstract && method.DeclaringType is {IsAbstract:true, IsInterface:false})
builder.Append("abstract ");
else if(method.DeclaringType is {IsInterface:false})
{
if (method.IsFinal)
builder.Append("sealed override ");
else if (method.IsVirtual)
builder.Append(method.Equals(method.GetBaseDefinition()) ? "virtual " : "override ");
}
}
if (returnType && !method.IsConstructor)
builder.Append($"{method.ReturnType.PrintTypeSignature()} ");
if(name)
builder.Append(method.Name);
if (!arguments)
return builder.ToString();
if (method.IsGenericMethod)
builder.Append($"<{string.Join(", ", method.GetGenericArguments().Select(PrintTypeSignature))}>");
builder.Append($"({string.Join($", ", method.GetParameters().Select(PrintParameterSignature))})");
return builder.ToString();
}
public static string PrintPropertySignature(this PropertyInfo property, bool modifiers = false, bool accessors = false)
{
var builder = new StringBuilder();
builder.Append($"{property.PropertyType.PrintTypeSignature()} {property.Name}");
if (accessors)
{
builder.Append(" { ");
if (property.CanRead)
builder.Append($"{property.GetMethod!.PrintMethodSignature(modifiers, false, false, false)}get; ");
if (property.CanWrite)
builder.Append($"{property.SetMethod!.PrintMethodSignature(modifiers, false, false, false)}set; ");
builder.Append('}');
}
return builder.ToString();
}
public static string PrintFieldSignature(this FieldInfo field, bool modifiers = false)
{
var builder = new StringBuilder();
if (modifiers)
{
// Access modifiers. Hmm... Déjà vu.
if (field.IsPublic)
builder.Append("public ");
if (field.IsPrivate)
builder.Append("private ");
if (field.IsFamilyAndAssembly)
builder.Append("private protected ");
if (field.IsFamily)
builder.Append("protected ");
if (field.IsFamilyOrAssembly)
builder.Append("protected internal ");
if (field.IsAssembly)
builder.Append("internal ");
if(field.IsStatic)
builder.Append("static ");
}
builder.Append($"{field.FieldType.PrintTypeSignature()} {field.Name}");
return builder.ToString();
}
private static readonly IReadOnlyDictionary<Type, string> TypeShortHand = new Dictionary<Type, string>()
{
// ReSharper disable BuiltInTypeReferenceStyle
{typeof(void), "void"},
{typeof(Object), "object"},
{typeof(Boolean), "bool"},
{typeof(Byte), "byte"},
{typeof(Char), "char"},
{typeof(Decimal), "decimal"},
{typeof(Double), "double"},
{typeof(Single), "float"},
{typeof(Int32), "int"},
{typeof(Int64), "long"},
{typeof(SByte), "sbyte"},
{typeof(Int16), "short"},
{typeof(String), "string"},
{typeof(UInt32), "uint"},
{typeof(UInt64), "ulong"},
{typeof(UInt16), "ushort"},
// ReSharper restore BuiltInTypeReferenceStyle
};
}
}