mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-29 03:49:19 +01:00
Dalamud Console Window UI-Rework (#1390)
Co-authored-by: Kaz Wolfe <root@kazwolfe.io>
This commit is contained in:
parent
7dbd15965a
commit
08fd4434ea
1 changed files with 388 additions and 196 deletions
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
|
|
@ -13,6 +14,7 @@ using Dalamud.Interface.Components;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
@ -27,26 +29,26 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
private readonly List<LogEntry> logText = new();
|
private readonly List<LogEntry> logText = new();
|
||||||
private readonly object renderLock = new();
|
private readonly object renderLock = new();
|
||||||
|
|
||||||
private readonly string[] logLevelStrings = new[] { "Verbose", "Debug", "Information", "Warning", "Error", "Fatal" };
|
private readonly List<string> history = new();
|
||||||
|
private readonly List<PluginFilterEntry> pluginFilters = new();
|
||||||
private List<LogEntry> filteredLogText = new();
|
|
||||||
private bool autoScroll;
|
|
||||||
private bool openAtStartup;
|
|
||||||
|
|
||||||
private bool? lastCmdSuccess;
|
private bool? lastCmdSuccess;
|
||||||
|
|
||||||
private string commandText = string.Empty;
|
private string commandText = string.Empty;
|
||||||
|
|
||||||
private string textFilter = string.Empty;
|
private string textFilter = string.Empty;
|
||||||
private int levelFilter;
|
private string selectedSource = "DalamudInternal";
|
||||||
private List<string> sourceFilters = new();
|
|
||||||
private bool filterShowUncaughtExceptions = false;
|
private bool filterShowUncaughtExceptions;
|
||||||
private bool isFiltered = false;
|
private bool showFilterToolbar;
|
||||||
|
private bool clearLog;
|
||||||
|
private bool copyLog;
|
||||||
|
private bool copyMode;
|
||||||
|
private bool killGameArmed;
|
||||||
|
private bool autoScroll;
|
||||||
|
private bool autoOpen;
|
||||||
|
|
||||||
private int historyPos;
|
private int historyPos;
|
||||||
private List<string> history = new();
|
private int copyStart = -1;
|
||||||
|
|
||||||
private bool killGameArmed = false;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ConsoleWindow"/> class.
|
/// Initializes a new instance of the <see cref="ConsoleWindow"/> class.
|
||||||
|
|
@ -57,16 +59,22 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
this.autoScroll = configuration.LogAutoScroll;
|
this.autoScroll = configuration.LogAutoScroll;
|
||||||
this.openAtStartup = configuration.LogOpenAtStartup;
|
this.autoOpen = configuration.LogOpenAtStartup;
|
||||||
SerilogEventSink.Instance.LogLine += this.OnLogLine;
|
SerilogEventSink.Instance.LogLine += this.OnLogLine;
|
||||||
|
|
||||||
this.Size = new Vector2(500, 400);
|
this.Size = new Vector2(500, 400);
|
||||||
this.SizeCondition = ImGuiCond.FirstUseEver;
|
this.SizeCondition = ImGuiCond.FirstUseEver;
|
||||||
|
|
||||||
|
this.SizeConstraints = new WindowSizeConstraints
|
||||||
|
{
|
||||||
|
MinimumSize = new Vector2(600.0f, 200.0f),
|
||||||
|
MaximumSize = new Vector2(9999.0f, 9999.0f),
|
||||||
|
};
|
||||||
|
|
||||||
this.RespectCloseHotkey = false;
|
this.RespectCloseHotkey = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LogEntry> LogEntries => this.isFiltered ? this.filteredLogText : this.logText;
|
private List<LogEntry> FilteredLogEntries { get; set; } = new();
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void OnOpen()
|
public override void OnOpen()
|
||||||
|
|
@ -91,10 +99,20 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
lock (this.renderLock)
|
lock (this.renderLock)
|
||||||
{
|
{
|
||||||
this.logText.Clear();
|
this.logText.Clear();
|
||||||
this.filteredLogText.Clear();
|
this.FilteredLogEntries.Clear();
|
||||||
|
this.clearLog = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the entire log contents to clipboard.
|
||||||
|
/// </summary>
|
||||||
|
public void CopyLog()
|
||||||
|
{
|
||||||
|
ImGui.LogToClipboard();
|
||||||
|
this.copyLog = false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a single log line to the display.
|
/// Add a single log line to the display.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -122,160 +140,15 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
// Options menu
|
this.DrawOptionsToolbar();
|
||||||
if (ImGui.BeginPopup("Options"))
|
|
||||||
{
|
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll))
|
this.DrawFilterToolbar();
|
||||||
{
|
|
||||||
configuration.LogAutoScroll = this.autoScroll;
|
|
||||||
configuration.QueueSave();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.Checkbox("Open at startup", ref this.openAtStartup))
|
|
||||||
{
|
|
||||||
configuration.LogOpenAtStartup = this.openAtStartup;
|
|
||||||
configuration.QueueSave();
|
|
||||||
}
|
|
||||||
|
|
||||||
var prevLevel = (int)EntryPoint.LogLevelSwitch.MinimumLevel;
|
|
||||||
if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Select(x => x.ToString()).ToArray(), 6))
|
|
||||||
{
|
|
||||||
EntryPoint.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel;
|
|
||||||
configuration.LogLevel = (LogEventLevel)prevLevel;
|
|
||||||
configuration.QueueSave();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter menu
|
|
||||||
if (ImGui.BeginPopup("Filters"))
|
|
||||||
{
|
|
||||||
if (ImGui.Checkbox("Enabled", ref this.isFiltered))
|
|
||||||
{
|
|
||||||
this.Refilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue))
|
|
||||||
{
|
|
||||||
this.Refilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm.");
|
|
||||||
|
|
||||||
if (ImGui.BeginCombo("Levels", this.levelFilter == 0 ? "All Levels..." : "Selected Levels..."))
|
|
||||||
{
|
|
||||||
for (var i = 0; i < this.logLevelStrings.Length; i++)
|
|
||||||
{
|
|
||||||
if (ImGui.Selectable(this.logLevelStrings[i], ((this.levelFilter >> i) & 1) == 1))
|
|
||||||
{
|
|
||||||
this.levelFilter ^= 1 << i;
|
|
||||||
this.Refilter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter by specific plugin(s)
|
|
||||||
var sourceNames = Service<PluginManager>.Get().InstalledPlugins
|
|
||||||
.Select(p => p.Manifest.InternalName)
|
|
||||||
.OrderBy(s => s)
|
|
||||||
.Prepend("DalamudInternal")
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var sourcePreviewVal = this.sourceFilters.Count switch
|
|
||||||
{
|
|
||||||
0 => "All sources...",
|
|
||||||
1 => "1 source...",
|
|
||||||
_ => $"{this.sourceFilters.Count} sources...",
|
|
||||||
};
|
|
||||||
var sourceSelectables = sourceNames.Union(this.sourceFilters).ToList();
|
|
||||||
if (ImGui.BeginCombo("Sources", sourcePreviewVal))
|
|
||||||
{
|
|
||||||
foreach (var selectable in sourceSelectables)
|
|
||||||
{
|
|
||||||
if (ImGui.Selectable(selectable, this.sourceFilters.Contains(selectable)))
|
|
||||||
{
|
|
||||||
if (!this.sourceFilters.Contains(selectable))
|
|
||||||
{
|
|
||||||
this.sourceFilters.Add(selectable);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.sourceFilters.Remove(selectable);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Refilter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.Checkbox("Always Show Uncaught Exceptions", ref this.filterShowUncaughtExceptions))
|
|
||||||
{
|
|
||||||
this.Refilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog))
|
|
||||||
ImGui.OpenPopup("Options");
|
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Options");
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Search))
|
|
||||||
ImGui.OpenPopup("Filters");
|
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Filters");
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
var clear = ImGuiComponents.IconButton(FontAwesomeIcon.Trash);
|
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Clear Log");
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
var copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy);
|
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Copy Log");
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (this.killGameArmed)
|
|
||||||
{
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Flushed))
|
|
||||||
Process.GetCurrentProcess().Kill();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull))
|
|
||||||
this.killGameArmed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Kill game");
|
|
||||||
|
|
||||||
ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar);
|
ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar);
|
||||||
|
|
||||||
if (clear)
|
if (this.clearLog) this.Clear();
|
||||||
{
|
|
||||||
this.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (copy)
|
if (this.copyLog) this.CopyLog();
|
||||||
{
|
|
||||||
ImGui.LogToClipboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
|
|
||||||
|
|
@ -291,27 +164,40 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
var childDrawList = ImGui.GetWindowDrawList();
|
var childDrawList = ImGui.GetWindowDrawList();
|
||||||
var childSize = ImGui.GetWindowSize();
|
var childSize = ImGui.GetWindowSize();
|
||||||
|
|
||||||
var cursorDiv = ImGuiHelpers.GlobalScale * 92;
|
var cursorDiv = ImGuiHelpers.GlobalScale * 93;
|
||||||
var cursorLogLevel = ImGuiHelpers.GlobalScale * 100;
|
var cursorLogLevel = ImGuiHelpers.GlobalScale * 100;
|
||||||
var cursorLogLine = ImGuiHelpers.GlobalScale * 135;
|
var cursorLogLine = ImGuiHelpers.GlobalScale * 135;
|
||||||
|
|
||||||
lock (this.renderLock)
|
lock (this.renderLock)
|
||||||
{
|
{
|
||||||
clipper.Begin(this.LogEntries.Count);
|
clipper.Begin(this.FilteredLogEntries.Count);
|
||||||
while (clipper.Step())
|
while (clipper.Step())
|
||||||
{
|
{
|
||||||
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
||||||
{
|
{
|
||||||
var line = this.LogEntries[i];
|
var line = this.FilteredLogEntries[i];
|
||||||
|
|
||||||
if (!line.IsMultiline && !copy)
|
if (!line.IsMultiline && !this.copyLog)
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level));
|
if (line.SelectedForCopy)
|
||||||
ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level));
|
{
|
||||||
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level));
|
ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedGrey);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.HeaderActive, ImGuiColors.ParsedGrey);
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, ImGuiColors.ParsedGrey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Selectable("###console_null", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns);
|
||||||
|
|
||||||
|
// This must be after ImGui.Selectable, it uses ImGui.IsItem... functions
|
||||||
|
this.HandleCopyMode(i, line);
|
||||||
|
|
||||||
ImGui.Selectable("###consolenull", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns);
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
ImGui.PopStyleColor(3);
|
ImGui.PopStyleColor(3);
|
||||||
|
|
@ -366,12 +252,12 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SetNextItemWidth(ImGui.GetWindowSize().X - 80);
|
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - (80.0f * ImGuiHelpers.GlobalScale) - (ImGui.GetStyle().ItemSpacing.X * ImGuiHelpers.GlobalScale));
|
||||||
|
|
||||||
var getFocus = false;
|
var getFocus = false;
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
if (ImGui.InputText("##commandbox", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback))
|
if (ImGui.InputText("##command_box", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback))
|
||||||
{
|
{
|
||||||
this.ProcessCommand();
|
this.ProcessCommand();
|
||||||
getFocus = true;
|
getFocus = true;
|
||||||
|
|
@ -387,16 +273,279 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
if (hadColor)
|
if (hadColor)
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
if (ImGui.Button("Send"))
|
if (ImGui.Button("Send", ImGuiHelpers.ScaledVector2(80.0f, 23.0f)))
|
||||||
{
|
{
|
||||||
this.ProcessCommand();
|
this.ProcessCommand();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleCopyMode(int i, LogEntry line)
|
||||||
|
{
|
||||||
|
var selectionChanged = false;
|
||||||
|
|
||||||
|
// If copyStart is -1, it means a drag has not been started yet, let's start one, and select the starting spot.
|
||||||
|
if (this.copyMode && this.copyStart == -1 && ImGui.IsItemClicked())
|
||||||
|
{
|
||||||
|
this.copyStart = i;
|
||||||
|
line.SelectedForCopy = !line.SelectedForCopy;
|
||||||
|
|
||||||
|
selectionChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the selected range when dragging over entries
|
||||||
|
if (this.copyMode && this.copyStart != -1 && ImGui.IsItemHovered() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
||||||
|
{
|
||||||
|
if (!line.SelectedForCopy)
|
||||||
|
{
|
||||||
|
foreach (var index in Enumerable.Range(0, this.FilteredLogEntries.Count))
|
||||||
|
{
|
||||||
|
if (this.copyStart < i)
|
||||||
|
{
|
||||||
|
this.FilteredLogEntries[index].SelectedForCopy = index >= this.copyStart && index <= i;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.FilteredLogEntries[index].SelectedForCopy = index >= i && index <= this.copyStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectionChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish the drag, we should have already marked all dragged entries as selected by now.
|
||||||
|
if (this.copyMode && this.copyStart != -1 && ImGui.IsItemHovered() && ImGui.IsMouseReleased(ImGuiMouseButton.Left))
|
||||||
|
{
|
||||||
|
this.copyStart = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectionChanged)
|
||||||
|
{
|
||||||
|
var allSelectedLines = this.FilteredLogEntries
|
||||||
|
.Where(entry => entry.SelectedForCopy)
|
||||||
|
.Select(entry => $"{line.TimeStamp:HH:mm:ss.fff} {this.GetTextForLogEventLevel(entry.Level)} | {entry.Line}");
|
||||||
|
|
||||||
|
ImGui.SetClipboardText(string.Join("\n", allSelectedLines));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawOptionsToolbar()
|
||||||
|
{
|
||||||
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
|
|
||||||
|
ImGui.PushItemWidth(150.0f * ImGuiHelpers.GlobalScale);
|
||||||
|
if (ImGui.BeginCombo("##log_level", $"{EntryPoint.LogLevelSwitch.MinimumLevel}+"))
|
||||||
|
{
|
||||||
|
foreach (var value in Enum.GetValues<LogEventLevel>())
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(value.ToString(), value == EntryPoint.LogLevelSwitch.MinimumLevel))
|
||||||
|
{
|
||||||
|
EntryPoint.LogLevelSwitch.MinimumLevel = value;
|
||||||
|
configuration.LogLevel = value;
|
||||||
|
configuration.QueueSave();
|
||||||
|
this.Refilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
this.autoScroll = configuration.LogAutoScroll;
|
||||||
|
if (this.DrawToggleButtonWithTooltip("auto_scroll", "Auto-scroll", FontAwesomeIcon.Sync, ref this.autoScroll))
|
||||||
|
{
|
||||||
|
configuration.LogAutoScroll = !configuration.LogAutoScroll;
|
||||||
|
configuration.QueueSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
this.autoOpen = configuration.LogOpenAtStartup;
|
||||||
|
if (this.DrawToggleButtonWithTooltip("auto_open", "Open at startup", FontAwesomeIcon.WindowRestore, ref this.autoOpen))
|
||||||
|
{
|
||||||
|
configuration.LogOpenAtStartup = !configuration.LogOpenAtStartup;
|
||||||
|
configuration.QueueSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (this.DrawToggleButtonWithTooltip("show_filters", "Show filter toolbar", FontAwesomeIcon.Search, ref this.showFilterToolbar))
|
||||||
|
{
|
||||||
|
this.showFilterToolbar = !this.showFilterToolbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (this.DrawToggleButtonWithTooltip("show_uncaught_exceptions", "Show uncaught exception while filtering", FontAwesomeIcon.Bug, ref this.filterShowUncaughtExceptions))
|
||||||
|
{
|
||||||
|
this.filterShowUncaughtExceptions = !this.filterShowUncaughtExceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGuiComponents.IconButton("clear_log", FontAwesomeIcon.Trash))
|
||||||
|
{
|
||||||
|
this.clearLog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Clear Log");
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (this.DrawToggleButtonWithTooltip("copy_mode", "Enable Copy Mode\nRight-click to copy entire log", FontAwesomeIcon.Copy, ref this.copyMode))
|
||||||
|
{
|
||||||
|
this.copyMode = !this.copyMode;
|
||||||
|
|
||||||
|
if (!this.copyMode)
|
||||||
|
{
|
||||||
|
foreach (var entry in this.FilteredLogEntries)
|
||||||
|
{
|
||||||
|
entry.SelectedForCopy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) this.copyLog = true;
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (this.killGameArmed)
|
||||||
|
{
|
||||||
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.ExclamationTriangle))
|
||||||
|
Process.GetCurrentProcess().Kill();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop))
|
||||||
|
this.killGameArmed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Kill game");
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosX(ImGui.GetContentRegionMax().X - (200.0f * ImGuiHelpers.GlobalScale));
|
||||||
|
ImGui.PushItemWidth(200.0f * ImGuiHelpers.GlobalScale);
|
||||||
|
if (ImGui.InputTextWithHint("##global_filter", "regex global filter", ref this.textFilter, 2048, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.AutoSelectAll))
|
||||||
|
{
|
||||||
|
this.Refilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemDeactivatedAfterEdit())
|
||||||
|
{
|
||||||
|
this.Refilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFilterToolbar()
|
||||||
|
{
|
||||||
|
if (!this.showFilterToolbar) return;
|
||||||
|
|
||||||
|
PluginFilterEntry? removalEntry = null;
|
||||||
|
if (ImGui.BeginTable("plugin_filter_entries", 4, ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersInnerV))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("##remove_button", ImGuiTableColumnFlags.WidthFixed, 25.0f * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.TableSetupColumn("##source_name", ImGuiTableColumnFlags.WidthFixed, 150.0f * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.TableSetupColumn("##log_level", ImGuiTableColumnFlags.WidthFixed, 150.0f * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.TableSetupColumn("##filter_text", ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGuiComponents.IconButton("add_entry", FontAwesomeIcon.Plus))
|
||||||
|
{
|
||||||
|
if (this.pluginFilters.All(entry => entry.Source != this.selectedSource))
|
||||||
|
{
|
||||||
|
this.pluginFilters.Add(new PluginFilterEntry
|
||||||
|
{
|
||||||
|
Source = this.selectedSource,
|
||||||
|
Filter = string.Empty,
|
||||||
|
Level = LogEventLevel.Debug,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Refilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X);
|
||||||
|
if (ImGui.BeginCombo("##Sources", this.selectedSource))
|
||||||
|
{
|
||||||
|
var sourceNames = Service<PluginManager>.Get().InstalledPlugins
|
||||||
|
.Select(p => p.Manifest.InternalName)
|
||||||
|
.OrderBy(s => s)
|
||||||
|
.Prepend("DalamudInternal")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var selectable in sourceNames)
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(selectable, this.selectedSource == selectable))
|
||||||
|
{
|
||||||
|
this.selectedSource = selectable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
foreach (var entry in this.pluginFilters)
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
if (ImGuiComponents.IconButton($"remove{entry.Source}", FontAwesomeIcon.Trash))
|
||||||
|
{
|
||||||
|
removalEntry = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(entry.Source);
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||||
|
if (ImGui.BeginCombo($"##levels{entry.Source}", $"{entry.Level}+"))
|
||||||
|
{
|
||||||
|
foreach (var value in Enum.GetValues<LogEventLevel>())
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(value.ToString(), value == entry.Level))
|
||||||
|
{
|
||||||
|
entry.Level = value;
|
||||||
|
this.Refilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||||
|
var entryFilter = entry.Filter;
|
||||||
|
if (ImGui.InputTextWithHint($"##filter{entry.Source}", $"{entry.Source} regex filter", ref entryFilter, 2048, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.AutoSelectAll))
|
||||||
|
{
|
||||||
|
entry.Filter = entryFilter;
|
||||||
|
this.Refilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemDeactivatedAfterEdit()) this.Refilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removalEntry is { } toRemove)
|
||||||
|
{
|
||||||
|
this.pluginFilters.Remove(toRemove);
|
||||||
|
this.Refilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ProcessCommand()
|
private void ProcessCommand()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (this.commandText is['/', ..])
|
||||||
|
{
|
||||||
|
this.commandText = this.commandText[1..];
|
||||||
|
}
|
||||||
|
|
||||||
this.historyPos = -1;
|
this.historyPos = -1;
|
||||||
for (var i = this.history.Count - 1; i >= 0; i--)
|
for (var i = this.history.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
|
|
@ -409,7 +558,7 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
|
|
||||||
this.history.Add(this.commandText);
|
this.history.Add(this.commandText);
|
||||||
|
|
||||||
if (this.commandText == "clear" || this.commandText == "cls")
|
if (this.commandText is "clear" or "cls")
|
||||||
{
|
{
|
||||||
this.Clear();
|
this.Clear();
|
||||||
return;
|
return;
|
||||||
|
|
@ -446,7 +595,8 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
|
|
||||||
// TODO: Improve this, add partial completion
|
// TODO: Improve this, add partial completion
|
||||||
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484
|
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484
|
||||||
var candidates = Service<CommandManager>.Get().Commands.Where(x => x.Key.Contains("/" + words[0]))
|
var candidates = Service<CommandManager>.Get().Commands
|
||||||
|
.Where(x => x.Key.Contains("/" + words[0]))
|
||||||
.ToList();
|
.ToList();
|
||||||
if (candidates.Count > 0)
|
if (candidates.Count > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -455,6 +605,7 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ImGuiInputTextFlags.CallbackHistory:
|
case ImGuiInputTextFlags.CallbackHistory:
|
||||||
var prevPos = this.historyPos;
|
var prevPos = this.historyPos;
|
||||||
|
|
||||||
|
|
@ -518,16 +669,14 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
|
|
||||||
this.logText.Add(entry);
|
this.logText.Add(entry);
|
||||||
|
|
||||||
if (!this.isFiltered)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.IsFilterApplicable(entry))
|
if (this.IsFilterApplicable(entry))
|
||||||
this.filteredLogText.Add(entry);
|
this.FilteredLogEntries.Add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsFilterApplicable(LogEntry entry)
|
private bool IsFilterApplicable(LogEntry entry)
|
||||||
{
|
{
|
||||||
if (this.levelFilter > 0 && ((this.levelFilter >> (int)entry.Level) & 1) == 0)
|
// If this entry is below a newly set minimum level, fail it
|
||||||
|
if (EntryPoint.LogLevelSwitch.MinimumLevel > entry.Level)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Show exceptions that weren't properly tagged with a Source (generally meaning they were uncaught)
|
// Show exceptions that weren't properly tagged with a Source (generally meaning they were uncaught)
|
||||||
|
|
@ -535,20 +684,34 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
if (this.filterShowUncaughtExceptions && entry.HasException && entry.Source == null)
|
if (this.filterShowUncaughtExceptions && entry.HasException && entry.Source == null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (this.sourceFilters.Count > 0 && !this.sourceFilters.Contains(entry.Source))
|
// If we have a global filter, check that first
|
||||||
return false;
|
if (!this.textFilter.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
// Someone will definitely try to just text filter a source without using the actual filters, should allow that.
|
||||||
|
var matchesSource = entry.Source is not null && Regex.IsMatch(entry.Source, this.textFilter, RegexOptions.IgnoreCase);
|
||||||
|
var matchesContent = Regex.IsMatch(entry.Line, this.textFilter, RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(this.textFilter) && !entry.Line.Contains(this.textFilter))
|
return matchesSource || matchesContent;
|
||||||
return false;
|
}
|
||||||
|
|
||||||
return true;
|
// If this entry has a filter, check the filter
|
||||||
|
if (this.pluginFilters.FirstOrDefault(filter => string.Equals(filter.Source, entry.Source, StringComparison.InvariantCultureIgnoreCase)) is { } filterEntry)
|
||||||
|
{
|
||||||
|
var allowedLevel = filterEntry.Level <= entry.Level;
|
||||||
|
var matchesContent = filterEntry.Filter.IsNullOrEmpty() || Regex.IsMatch(entry.Line, filterEntry.Filter, RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
return allowedLevel && matchesContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// else we couldn't find a filter for this entry, if we have any filters, we need to block this entry.
|
||||||
|
return !this.pluginFilters.Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Refilter()
|
private void Refilter()
|
||||||
{
|
{
|
||||||
lock (this.renderLock)
|
lock (this.renderLock)
|
||||||
{
|
{
|
||||||
this.filteredLogText = this.logText.Where(this.IsFilterApplicable).ToList();
|
this.FilteredLogEntries = this.logText.Where(this.IsFilterApplicable).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -579,15 +742,33 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
this.HandleLogLine(logEvent.Line, logEvent.LogEvent);
|
this.HandleLogLine(logEvent.Line, logEvent.LogEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool DrawToggleButtonWithTooltip(string buttonId, string tooltip, FontAwesomeIcon icon, ref bool enabledState)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
|
||||||
|
var buttonEnabled = enabledState;
|
||||||
|
if (buttonEnabled) ImGui.PushStyleColor(ImGuiCol.Button, ImGuiColors.HealerGreen with { W = 0.25f });
|
||||||
|
if (ImGuiComponents.IconButton(buttonId, icon))
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered()) ImGui.SetTooltip(tooltip);
|
||||||
|
|
||||||
|
if (buttonEnabled) ImGui.PopStyleColor();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private class LogEntry
|
private class LogEntry
|
||||||
{
|
{
|
||||||
public string Line { get; set; }
|
public string Line { get; init; } = string.Empty;
|
||||||
|
|
||||||
public LogEventLevel Level { get; set; }
|
public LogEventLevel Level { get; init; }
|
||||||
|
|
||||||
public DateTimeOffset TimeStamp { get; set; }
|
public DateTimeOffset TimeStamp { get; init; }
|
||||||
|
|
||||||
public bool IsMultiline { get; set; }
|
public bool IsMultiline { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the system responsible for generating this log entry. Generally will be a plugin's
|
/// Gets or sets the system responsible for generating this log entry. Generally will be a plugin's
|
||||||
|
|
@ -595,6 +776,17 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Source { get; set; }
|
public string? Source { get; set; }
|
||||||
|
|
||||||
public bool HasException { get; set; }
|
public bool SelectedForCopy { get; set; }
|
||||||
|
|
||||||
|
public bool HasException { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PluginFilterEntry
|
||||||
|
{
|
||||||
|
public string Source { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
public string Filter { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public LogEventLevel Level { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue