Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into apiX-rollup
Browse files Browse the repository at this point in the history
  • Loading branch information
web-flow committed Jun 10, 2024
2 parents e06057f + 6adf0d7 commit 841dd35
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 51 deletions.
5 changes: 5 additions & 0 deletions Dalamud/Configuration/Internal/DalamudConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
/// </summary>
public int LogLinesLimit { get; set; } = 10000;

/// <summary>
/// Gets or sets a list of commands that have been run in the console window.
/// </summary>
public List<string> LogCommandHistory { get; set; } = new();

/// <summary>
/// Gets or sets a value indicating whether or not the dev bar should open at startup.
/// </summary>
Expand Down
26 changes: 19 additions & 7 deletions Dalamud/Console/ConsoleManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,9 @@ public bool ProcessCommand(string command)
for (var i = parsedArguments.Count; i < entry.ValidArguments.Count; i++)
{
var argument = entry.ValidArguments[i];
if (argument.DefaultValue == null)

// If the default value is DBNull, we need to error out as that means it was not specified
if (argument.DefaultValue == DBNull.Value)
{
Log.Error("Not enough arguments for command {CommandName}", entryName);
PrintUsage(entry);
Expand Down Expand Up @@ -382,11 +384,8 @@ public ConsoleEntry(string name, string description)
/// <param name="defaultValue">The default value to use if none is specified.</param>
/// <returns>An <see cref="ArgumentInfo"/> instance.</returns>
/// <exception cref="ArgumentException">Thrown if the given type cannot be handled by the console system.</exception>
protected static ArgumentInfo TypeToArgument(Type type, object? defaultValue = null)
protected static ArgumentInfo TypeToArgument(Type type, object? defaultValue)
{
// If the default value is DBNull, we want to treat it as null
defaultValue = defaultValue == DBNull.Value ? null : defaultValue;

if (type == typeof(string))
return new ArgumentInfo(ConsoleArgumentType.String, defaultValue);

Expand Down Expand Up @@ -490,7 +489,7 @@ private class ConsoleVariable<T> : ConsoleVariable, IConsoleVariable<T>
public ConsoleVariable(string name, string description)
: base(name, description)
{
this.ValidArguments = new List<ArgumentInfo> { TypeToArgument(typeof(T)) };
this.ValidArguments = new List<ArgumentInfo> { TypeToArgument(typeof(T), null) };
}

/// <inheritdoc/>
Expand All @@ -500,7 +499,20 @@ public ConsoleVariable(string name, string description)
public override bool Invoke(IEnumerable<object> arguments)
{
var first = arguments.FirstOrDefault();
if (first == null || first.GetType() != typeof(T))

if (first == null)
{
// Invert the value if it's a boolean
if (this.Value is bool boolValue)
{
this.Value = (T)(object)!boolValue;
}

Log.WriteLog(LogEventLevel.Information, "{VariableName} = {VariableValue}", null, this.Name, this.Value);
return true;
}

if (first.GetType() != typeof(T))
throw new ArgumentException($"Console variable must be set with an argument of type {typeof(T).Name}.");

this.Value = (T)first;
Expand Down
2 changes: 1 addition & 1 deletion Dalamud/Dalamud.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</PropertyGroup>

<PropertyGroup Label="Feature">
<DalamudVersion>9.1.0.10</DalamudVersion>
<DalamudVersion>9.1.0.12</DalamudVersion>
<Description>XIV Launcher addon framework</Description>
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
<Version>$(DalamudVersion)</Version>
Expand Down
117 changes: 74 additions & 43 deletions Dalamud/Interface/Internal/Windows/ConsoleWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,18 @@ internal class ConsoleWindow : Window, IDisposable
{
private const int LogLinesMinimum = 100;
private const int LogLinesMaximum = 1000000;
private const int HistorySize = 50;

// Only this field may be touched from any thread.
private readonly ConcurrentQueue<(string Line, LogEvent LogEvent)> newLogEntries;

// Fields below should be touched only from the main thread.
private readonly RollingList<LogEntry> logText;
private readonly RollingList<LogEntry> filteredLogEntries;

private readonly List<string> history = new();

private readonly List<PluginFilterEntry> pluginFilters = new();

private readonly DalamudConfiguration configuration;

private int newRolledLines;
private bool pendingRefilter;
Expand Down Expand Up @@ -78,13 +80,18 @@ internal class ConsoleWindow : Window, IDisposable
private int historyPos;
private int copyStart = -1;

private string? completionZipText = null;
private int completionTabIdx = 0;

private IActiveNotification? prevCopyNotification;

/// <summary>Initializes a new instance of the <see cref="ConsoleWindow"/> class.</summary>
/// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
public ConsoleWindow(DalamudConfiguration configuration)
: base("Dalamud Console", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
{
this.configuration = configuration;

this.autoScroll = configuration.LogAutoScroll;
this.autoOpen = configuration.LogOpenAtStartup;
SerilogEventSink.Instance.LogLine += this.OnLogLine;
Expand All @@ -111,7 +118,7 @@ public ConsoleWindow(DalamudConfiguration configuration)
this.logText = new(limit);
this.filteredLogEntries = new(limit);

configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved;
this.configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved;

unsafe
{
Expand All @@ -130,7 +137,7 @@ public override void OnOpen()
public void Dispose()
{
SerilogEventSink.Instance.LogLine -= this.OnLogLine;
Service<DalamudConfiguration>.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
this.configuration.DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
if (Service<Framework>.GetNullable() is { } framework)
framework.Update -= this.FrameworkOnUpdate;

Expand Down Expand Up @@ -314,9 +321,10 @@ public override void Draw()
ref this.commandText,
255,
ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion |
ImGuiInputTextFlags.CallbackHistory,
ImGuiInputTextFlags.CallbackHistory | ImGuiInputTextFlags.CallbackEdit,
this.CommandInputCallback))
{
this.newLogEntries.Enqueue((this.commandText, new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, new MessageTemplate(string.Empty, []), [])));
this.ProcessCommand();
getFocus = true;
}
Expand Down Expand Up @@ -460,8 +468,6 @@ private void CopyFilteredLogEntries(bool selectedOnly)

private void DrawOptionsToolbar()
{
var configuration = Service<DalamudConfiguration>.Get();

ImGui.PushItemWidth(150.0f * ImGuiHelpers.GlobalScale);
if (ImGui.BeginCombo("##log_level", $"{EntryPoint.LogLevelSwitch.MinimumLevel}+"))
{
Expand All @@ -470,8 +476,8 @@ private void DrawOptionsToolbar()
if (ImGui.Selectable(value.ToString(), value == EntryPoint.LogLevelSwitch.MinimumLevel))
{
EntryPoint.LogLevelSwitch.MinimumLevel = value;
configuration.LogLevel = value;
configuration.QueueSave();
this.configuration.LogLevel = value;
this.configuration.QueueSave();
this.QueueRefilter();
}
}
Expand All @@ -484,13 +490,13 @@ private void DrawOptionsToolbar()
var settingsPopup = ImGui.BeginPopup("##console_settings");
if (settingsPopup)
{
this.DrawSettingsPopup(configuration);
this.DrawSettingsPopup();
ImGui.EndPopup();
}
else if (this.settingsPopupWasOpen)
{
// Prevent side effects in case Apply wasn't clicked
this.logLinesLimit = configuration.LogLinesLimit;
this.logLinesLimit = this.configuration.LogLinesLimit;
}

this.settingsPopupWasOpen = settingsPopup;
Expand Down Expand Up @@ -638,18 +644,18 @@ private void DrawOptionsToolbar()
}
}

private void DrawSettingsPopup(DalamudConfiguration configuration)
private void DrawSettingsPopup()
{
if (ImGui.Checkbox("Open at startup", ref this.autoOpen))
{
configuration.LogOpenAtStartup = this.autoOpen;
configuration.QueueSave();
this.configuration.LogOpenAtStartup = this.autoOpen;
this.configuration.QueueSave();
}

if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll))
{
configuration.LogAutoScroll = this.autoScroll;
configuration.QueueSave();
this.configuration.LogAutoScroll = this.autoScroll;
this.configuration.QueueSave();
}

ImGui.TextUnformatted("Logs buffer");
Expand All @@ -658,8 +664,8 @@ private void DrawSettingsPopup(DalamudConfiguration configuration)
{
this.logLinesLimit = Math.Max(LogLinesMinimum, this.logLinesLimit);

configuration.LogLinesLimit = this.logLinesLimit;
configuration.QueueSave();
this.configuration.LogLinesLimit = this.logLinesLimit;
this.configuration.QueueSave();

ImGui.CloseCurrentPopup();
}
Expand Down Expand Up @@ -795,23 +801,18 @@ private void ProcessCommand()
{
try
{
this.historyPos = -1;
for (var i = this.history.Count - 1; i >= 0; i--)
{
if (this.history[i] == this.commandText)
{
this.history.RemoveAt(i);
break;
}
}

this.history.Add(this.commandText);

if (this.commandText is "clear" or "cls")
{
this.QueueClear();
if (string.IsNullOrEmpty(this.commandText))
return;
}

this.historyPos = -1;

if (this.commandText != this.configuration.LogCommandHistory.LastOrDefault())
this.configuration.LogCommandHistory.Add(this.commandText);

if (this.configuration.LogCommandHistory.Count > HistorySize)
this.configuration.LogCommandHistory.RemoveAt(0);

this.configuration.QueueSave();

this.lastCmdSuccess = Service<ConsoleManager>.Get().ProcessCommand(this.commandText);
this.commandText = string.Empty;
Expand All @@ -831,6 +832,11 @@ private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data)

switch (data->EventFlag)
{
case ImGuiInputTextFlags.CallbackEdit:
this.completionZipText = null;
this.completionTabIdx = 0;
break;

case ImGuiInputTextFlags.CallbackCompletion:
var textBytes = new byte[data->BufTextLen];
Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen);
Expand All @@ -841,22 +847,47 @@ private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data)
// We can't do any completion for parameters at the moment since it just calls into CommandHandler
if (words.Length > 1)
return 0;

var wordToComplete = words[0];
if (wordToComplete.IsNullOrWhitespace())
return 0;

if (this.completionZipText is not null)
wordToComplete = this.completionZipText;

// TODO: Improve this, add partial completion, arguments, description, etc.
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484
var candidates = Service<ConsoleManager>.Get().Entries
.Where(x => x.Key.StartsWith(words[0]))
.Where(x => x.Key.StartsWith(wordToComplete))
.Select(x => x.Key);

candidates = candidates.Union(
Service<CommandManager>.Get().Commands
.Where(x => x.Key.StartsWith(words[0])).Select(x => x.Key));
.Where(x => x.Key.StartsWith(wordToComplete)).Select(x => x.Key))
.ToArray();

var enumerable = candidates as string[] ?? candidates.ToArray();
if (enumerable.Length != 0)
if (candidates.Any())
{
ptr.DeleteChars(0, ptr.BufTextLen);
ptr.InsertChars(0, enumerable[0]);
string? toComplete = null;
if (this.completionZipText == null)
{
// Find the "common" prefix of all matches
toComplete = candidates.Aggregate(
(prefix, candidate) => string.Concat(prefix.Zip(candidate, (a, b) => a == b ? a : '\0')));

this.completionZipText = toComplete;
}
else
{
toComplete = candidates.ElementAt(this.completionTabIdx);
this.completionTabIdx = (this.completionTabIdx + 1) % candidates.Count();
}

if (toComplete != null)
{
ptr.DeleteChars(0, ptr.BufTextLen);
ptr.InsertChars(0, toComplete);
}
}

break;
Expand All @@ -867,15 +898,15 @@ private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data)
if (ptr.EventKey == ImGuiKey.UpArrow)
{
if (this.historyPos == -1)
this.historyPos = this.history.Count - 1;
this.historyPos = this.configuration.LogCommandHistory.Count - 1;
else if (this.historyPos > 0)
this.historyPos--;
}
else if (data->EventKey == ImGuiKey.DownArrow)
{
if (this.historyPos != -1)
{
if (++this.historyPos >= this.history.Count)
if (++this.historyPos >= this.configuration.LogCommandHistory.Count)
{
this.historyPos = -1;
}
Expand All @@ -884,7 +915,7 @@ private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data)

if (prevPos != this.historyPos)
{
var historyStr = this.historyPos >= 0 ? this.history[this.historyPos] : string.Empty;
var historyStr = this.historyPos >= 0 ? this.configuration.LogCommandHistory[this.historyPos] : string.Empty;

ptr.DeleteChars(0, ptr.BufTextLen);
ptr.InsertChars(0, historyStr);
Expand Down

0 comments on commit 841dd35

Please sign in to comment.