using System.Collections.Generic;
using System.Diagnostics;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Reflection;
using Robust.Shared.Toolshed.Errors;
using Robust.Shared.Toolshed.Invocation;
using Robust.Shared.Toolshed.Syntax;
using Robust.Shared.Toolshed.TypeParsers;
using Robust.Shared.Utility;
namespace Robust.Shared.Toolshed;
///
/// The overarching controller for Toolshed, providing invocation, reflection, commands, parsing, and other tools used by the language.
/// External documentation has a more in-depth look.
///
///
///
public sealed partial class ToolshedManager
{
[Dependency] private readonly IDynamicTypeFactoryInternal _typeFactory = default!;
[Dependency] private readonly IEntityManager _entity = default!;
[Dependency] private readonly IReflectionManager _reflection = default!;
[Dependency] private readonly ILogManager _logManager = default!;
#if !CLIENT_SCRIPTING
[Dependency] private readonly INetManager _net = default!;
#endif
[Dependency] private readonly ISharedPlayerManager _player = default!;
[Dependency] private readonly IConsoleHost _conHost = default!;
private ISawmill _log = default!;
private Dictionary _contexts = new();
///
/// If you're not an engine developer, you probably shouldn't call this.
///
public void Initialize()
{
_log = _logManager.GetSawmill("toolshed");
InitializeParser();
_player.PlayerStatusChanged += OnStatusChanged;
}
private void OnStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (!_contexts.TryGetValue(e.Session.UserId, out var ctx))
return;
DebugTools.Assert(ctx.User == e.Session.UserId);
if (e.NewStatus == SessionStatus.Disconnected)
{
DebugTools.Assert(ctx.Session == e.Session);
ctx.Shell = null;
}
if (e.NewStatus == SessionStatus.InGame)
{
DebugTools.AssertNull(ctx.Session);
DebugTools.AssertNull(ctx.Shell);
ctx.Shell = new ConsoleShell(_conHost, e.Session, false);
}
}
///
/// Invokes a command as the given user.
///
/// User to run as.
/// Command to invoke.
/// An input value to use, if any.
/// The resulting value, if any.
/// Invocation success.
///
/// ToolshedManager toolshed = ...;
/// ICommonSession ctx = ...;
/// // Now run some user provided command and get a result!
/// toolshed.InvokeCommand(ctx, userCommand, "my input value", out var result);
///
///
/// This will use the same IInvocationContext as the one used by the user for debug console commands.
///
public bool InvokeCommand(ICommonSession session, string command, object? input, out object? result)
{
if (!_contexts.TryGetValue(session.UserId, out var ctx))
{
// Can't get a shell here.
result = null;
return false;
}
ctx.ClearErrors();
return InvokeCommand(ctx, command, input, out result);
}
///
/// Invokes a command as the given user.
///
/// User to run as.
/// Command to invoke.
/// An input value to use, if any.
/// The resulting value, if any.
/// Invocation success.
///
/// ToolshedManager toolshed = ...;
/// IConsoleShell ctx = ...;
/// // Now run some user provided command and get a result!
/// toolshed.InvokeCommand(ctx, userCommand, "my input value", out var result);
///
///
/// This will use the same IInvocationContext as the one used by the user for debug console commands.
///
public bool InvokeCommand(IConsoleShell session, string command, object? input, out object? result, out IInvocationContext ctx)
{
var idx = session.Player?.UserId ?? new NetUserId();
if (!_contexts.TryGetValue(idx, out var ourCtx))
{
ourCtx = new OldShellInvocationContext(session);
_contexts[idx] = ourCtx;
}
ourCtx.ClearErrors();
ctx = ourCtx;
return InvokeCommand(ctx, command, input, out result);
}
///
/// Invokes a command with the given context.
///
/// The context to run in.
/// Command to invoke.
/// An input value to use, if any.
/// The resulting value, if any.
/// Invocation success.
///
/// ToolshedManager toolshed = ...;
/// IInvocationContext ctx = ...;
/// // Now run some user provided command and get a result!
/// toolshed.InvokeCommand(ctx, userCommand, "my input value", out var result);
///
public bool InvokeCommand(IInvocationContext ctx, string command, object? input, out object? result)
{
ctx.ClearErrors();
result = null;
var parser = new ParserContext(command, this, ctx);
if (!CommandRun.TryParse(parser, input?.GetType(), null, out var expr))
{
ctx.ReportError(parser.Error ?? new FailedToParseError());
return false;
}
result = expr.Invoke(input, ctx);
return true;
}
public CompletionResult? GetCompletions(ConsoleShell shell, string command)
{
var idx = shell.Player?.UserId ?? new NetUserId();
if (!_contexts.TryGetValue(idx, out var ourCtx))
ourCtx = _contexts[idx] = new OldShellInvocationContext(shell);
return GetCompletions(ourCtx, command);
}
public CompletionResult? GetCompletions(IInvocationContext ctx, string command)
{
ctx.ClearErrors();
var parser = new ParserContext(command, this, ctx);
parser.GenerateCompletions = true;
CommandRun.TryParse(parser, null, null, out _);
return parser.Completions;
}
}
///
/// A command specification, containing both the command object and the selected subcommand if any.
///
/// Command object.
/// Subcommand, if any.
public readonly record struct CommandSpec(ToolshedCommand Cmd, string? SubCommand) : IAsType
{
///
public ToolshedCommand AsType()
{
return Cmd;
}
///
/// Returns a completion option for this command, for use in autocomplete.
///
public CompletionOption AsCompletion()
{
return new CompletionOption(FullName(), Cmd.Description(SubCommand));
}
///
/// Returns the full name of the command.
///
public string FullName() => SubCommand == null ? Cmd.Name : $"{Cmd.Name}:{SubCommand}";
///
/// Returns the localization string for the description of this command.
///
public string DescLocStr() => Cmd.DescriptionLocKey(SubCommand);
///
public override string ToString() => FullName();
}
public record struct FailedToParseError() : IConError
{
public FormattedMessage DescribeInner()
{
return FormattedMessage.FromUnformatted($"Failed to parse toolshed command");
}
public string? Expression { get; set; }
public Vector2i? IssueSpan { get; set; }
public StackTrace? Trace { get; set; }
}