mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
* Console command completions v1.
I think it works™️
* Unify cvar commands
* Handle no-completions-at-all better.
* Don't crash if you tab complete while no completions available.
* Always show hints if available
* Properly null completion hint over the wire
* Unify help command, localize it.
* Clean up + localize cvar command.
* Remove debug logging
* List command unified & localized.
* Remove server completions debug logging
* Remote execute command.
Had to make everything async for this.
* Don't lower case enums or bools
Why
* GC commands converted and localized.
* Fix remote command completions.
Whoops
* Kick command completions
* lsasm unified & localized.
* Revert "Don't lower case enums or bools"
This reverts commit 2f825347c3.
* ToString gc_mode command enums instead of trying to fix Fluent.
Ah well.
* Unify szr_stats
* Unify log commands, completions
* Fix compile
* Improve completion with complex cases (quotes, escapes)
* Code cleanup, comments.
* Fix tab completion with empty arg ruining everything.
* Fix RegisteredCommand completions
* Add more complex completion options system.
* Refactor content directory entries into a proper resource manager API.
* Implement GetEntries for DirLoader
* Make type hint darker.
* Exec command autocomplete, pulled play global sound code out to engine.
287 lines
8.4 KiB
C#
287 lines
8.4 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.IO;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.Console;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.XAML;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.ContentPack;
|
|
using Robust.Shared.Input;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Client.UserInterface.CustomControls
|
|
{
|
|
public interface IDebugConsoleView
|
|
{
|
|
/// <summary>
|
|
/// Write a line with a specific color to the console window.
|
|
/// </summary>
|
|
void AddLine(string text, Color color);
|
|
|
|
void AddLine(string text);
|
|
|
|
void AddFormattedLine(FormattedMessage message);
|
|
|
|
void Clear();
|
|
}
|
|
|
|
// Quick note on how thread safety works in here:
|
|
// Messages from other threads are not actually immediately drawn. They're stored in a queue.
|
|
// Every frame OR the next time a message on the main thread comes in, this queue is drained.
|
|
// This keeps thread safety while still making it so messages are ordered how they come in.
|
|
// And also if Update() stops firing due to an exception loop the console will still work.
|
|
// (At least from the main thread, which is what's throwing the exceptions..)
|
|
[GenerateTypedNameReferences]
|
|
public sealed partial class DebugConsole : Control, IDebugConsoleView
|
|
{
|
|
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
|
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
|
|
|
private static readonly ResourcePath HistoryPath = new("/debug_console_history.json");
|
|
|
|
private readonly ConcurrentQueue<FormattedMessage> _messageQueue = new();
|
|
|
|
public DebugConsole()
|
|
{
|
|
RobustXamlLoader.Load(this);
|
|
|
|
IoCManager.InjectDependencies(this);
|
|
|
|
InitCompletions();
|
|
|
|
CommandBar.OnTextChanged += OnCommandChanged;
|
|
CommandBar.OnKeyBindDown += CommandBarOnOnKeyBindDown;
|
|
CommandBar.OnTextEntered += CommandEntered;
|
|
CommandBar.OnHistoryChanged += OnHistoryChanged;
|
|
|
|
_loadHistoryFromDisk();
|
|
|
|
_compPopup = new DebugConsoleCompletion();
|
|
}
|
|
|
|
protected override void EnteredTree()
|
|
{
|
|
base.EnteredTree();
|
|
|
|
_consoleHost.AddString += OnAddString;
|
|
_consoleHost.AddFormatted += OnAddFormatted;
|
|
_consoleHost.ClearText += OnClearText;
|
|
|
|
UserInterfaceManager.ModalRoot.AddChild(_compPopup);
|
|
}
|
|
|
|
protected override void ExitedTree()
|
|
{
|
|
base.ExitedTree();
|
|
|
|
_consoleHost.AddString -= OnAddString;
|
|
_consoleHost.AddFormatted -= OnAddFormatted;
|
|
_consoleHost.ClearText -= OnClearText;
|
|
|
|
UserInterfaceManager.ModalRoot.RemoveChild(_compPopup);
|
|
}
|
|
|
|
private void OnClearText(object? _, EventArgs args)
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
private void OnAddFormatted(object? _, AddFormattedMessageArgs args)
|
|
{
|
|
AddFormattedLine(args.Message);
|
|
}
|
|
|
|
private void OnAddString(object? _, AddStringArgs args)
|
|
{
|
|
AddLine(args.Text, DetermineColor(args.Local, args.Error));
|
|
}
|
|
|
|
private Color DetermineColor(bool local, bool error)
|
|
{
|
|
return Color.White;
|
|
}
|
|
|
|
protected override void FrameUpdate(FrameEventArgs args)
|
|
{
|
|
base.FrameUpdate(args);
|
|
|
|
_flushQueue();
|
|
}
|
|
|
|
private void CommandEntered(LineEdit.LineEditEventArgs args)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(args.Text))
|
|
{
|
|
_consoleHost.ExecuteCommand(args.Text);
|
|
CommandBar.Clear();
|
|
|
|
CompletionCommandEntered();
|
|
}
|
|
|
|
// commandChanged = true;
|
|
}
|
|
|
|
private void OnHistoryChanged()
|
|
{
|
|
_flushHistoryToDisk();
|
|
}
|
|
|
|
public void AddLine(string text, Color color)
|
|
{
|
|
var formatted = new FormattedMessage(3);
|
|
formatted.PushColor(color);
|
|
formatted.AddText(text);
|
|
formatted.Pop();
|
|
AddFormattedLine(formatted);
|
|
}
|
|
|
|
public void AddLine(string text)
|
|
{
|
|
AddLine(text, Color.White);
|
|
}
|
|
|
|
public void AddFormattedLine(FormattedMessage message)
|
|
{
|
|
_messageQueue.Enqueue(message);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
Output.Clear();
|
|
}
|
|
|
|
private void _addFormattedLineInternal(FormattedMessage message)
|
|
{
|
|
Output.AddMessage(message);
|
|
}
|
|
|
|
private void _flushQueue()
|
|
{
|
|
while (_messageQueue.TryDequeue(out var message))
|
|
{
|
|
_addFormattedLineInternal(message);
|
|
}
|
|
}
|
|
|
|
private void CommandBarOnOnKeyBindDown(GUIBoundKeyEventArgs args)
|
|
{
|
|
if (args.Function == EngineKeyFunctions.TextScrollToBottom)
|
|
{
|
|
Output.ScrollToBottom();
|
|
args.Handle();
|
|
return;
|
|
}
|
|
|
|
CompletionKeyDown(args);
|
|
}
|
|
|
|
private void OnCommandChanged(LineEdit.LineEditEventArgs args)
|
|
{
|
|
// commandChanged = true;
|
|
}
|
|
|
|
private async void _loadHistoryFromDisk()
|
|
{
|
|
var sawmill = Logger.GetSawmill("dbgconsole");
|
|
var data = await Task.Run(async () =>
|
|
{
|
|
Stream? stream = null;
|
|
for (var i = 0; i < 3; i++)
|
|
{
|
|
try
|
|
{
|
|
stream = _resourceManager.UserData.OpenRead(HistoryPath);
|
|
break;
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
// Nada, nothing to load in that case.
|
|
return null;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
// File locked probably??
|
|
await Task.Delay(10);
|
|
}
|
|
}
|
|
|
|
if (stream == null)
|
|
{
|
|
sawmill.Warning("Failed to load debug console history!");
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
return await JsonSerializer.DeserializeAsync<string[]>(stream);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
sawmill.Warning($"Failed to load debug console history due to exception!\n{e}");
|
|
return null;
|
|
}
|
|
finally
|
|
{
|
|
// ReSharper disable once MethodHasAsyncOverload
|
|
stream.Dispose();
|
|
}
|
|
});
|
|
|
|
if (data == null)
|
|
return;
|
|
|
|
CommandBar.ClearHistory();
|
|
CommandBar.History.AddRange(data);
|
|
CommandBar.HistoryIndex = CommandBar.History.Count;
|
|
}
|
|
|
|
private async void _flushHistoryToDisk()
|
|
{
|
|
CommandBar.HistoryIndex = CommandBar.History.Count;
|
|
|
|
var sawmill = Logger.GetSawmill("dbgconsole");
|
|
var newHistory = JsonSerializer.Serialize(CommandBar.History);
|
|
|
|
await Task.Run(async () =>
|
|
{
|
|
StreamWriter? writer = null;
|
|
|
|
for (var i = 0; i < 3; i++)
|
|
{
|
|
try
|
|
{
|
|
writer = _resourceManager.UserData.OpenWriteText(HistoryPath);
|
|
break;
|
|
}
|
|
catch (IOException)
|
|
{
|
|
// Probably locking.
|
|
await Task.Delay(10);
|
|
}
|
|
}
|
|
|
|
if (writer == null)
|
|
{
|
|
sawmill.Warning("Failed to save debug console history!");
|
|
return;
|
|
}
|
|
|
|
// ReSharper disable once UseAwaitUsing
|
|
using (writer)
|
|
{
|
|
// ReSharper disable once MethodHasAsyncOverload
|
|
writer.Write(newHistory);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|