chore: housekeeping — linter & formatter setup

Add .prettierrc.json, .markdownlint.json, .yamllint.yaml, .gitattributes
Run CSharpier, Prettier and markdownlint across the entire codebase.
No logic changes — formatting, using order and line endings only.
This commit is contained in:
2026-05-10 13:01:00 +02:00
parent cd01fa63a1
commit 699d4ede1d
141 changed files with 8833 additions and 5733 deletions
+212 -102
View File
@@ -3,17 +3,17 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.ExceptionServices;
using HellionChat.Ipc;
using HellionChat.Resources;
using HellionChat.Ui;
using HellionChat.Util;
using Dalamud.Bindings.ImGui;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Windowing;
using Dalamud.IoC;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.ImGuiFileDialog;
using HellionChat.Ipc;
using HellionChat.Resources;
using HellionChat.Ui;
using HellionChat.Util;
namespace HellionChat;
@@ -22,27 +22,68 @@ public sealed class Plugin : IAsyncDalamudPlugin
{
public const string PluginName = "Hellion Chat";
[PluginService] public static IPluginLog Log { get; private set; } = null!;
[PluginService] public static IDalamudPluginInterface Interface { get; private set; } = null!;
[PluginService] public static IChatGui ChatGui { get; private set; } = null!;
[PluginService] public static IClientState ClientState { get; private set; } = null!;
[PluginService] public static ICommandManager CommandManager { get; private set; } = null!;
[PluginService] public static ICondition Condition { get; private set; } = null!;
[PluginService] public static IDataManager DataManager { get; private set; } = null!;
[PluginService] public static IFramework Framework { get; private set; } = null!;
[PluginService] public static IGameGui GameGui { get; private set; } = null!;
[PluginService] public static IKeyState KeyState { get; private set; } = null!;
[PluginService] public static IObjectTable ObjectTable { get; private set; } = null!;
[PluginService] public static IPartyList PartyList { get; private set; } = null!;
[PluginService] public static ITargetManager TargetManager { get; private set; } = null!;
[PluginService] public static ITextureProvider TextureProvider { get; private set; } = null!;
[PluginService] public static IGameInteropProvider GameInteropProvider { get; private set; } = null!;
[PluginService] public static IGameConfig GameConfig { get; private set; } = null!;
[PluginService] public static INotificationManager Notification { get; private set; } = null!;
[PluginService] public static IAddonLifecycle AddonLifecycle { get; private set; } = null!;
[PluginService] public static IPlayerState PlayerState { get; private set; } = null!;
[PluginService] public static ISeStringEvaluator Evaluator { get; private set; } = null!;
[PluginService] public static ISelfTestRegistry SelfTestRegistry { get; private set; } = null!;
[PluginService]
public static IPluginLog Log { get; private set; } = null!;
[PluginService]
public static IDalamudPluginInterface Interface { get; private set; } = null!;
[PluginService]
public static IChatGui ChatGui { get; private set; } = null!;
[PluginService]
public static IClientState ClientState { get; private set; } = null!;
[PluginService]
public static ICommandManager CommandManager { get; private set; } = null!;
[PluginService]
public static ICondition Condition { get; private set; } = null!;
[PluginService]
public static IDataManager DataManager { get; private set; } = null!;
[PluginService]
public static IFramework Framework { get; private set; } = null!;
[PluginService]
public static IGameGui GameGui { get; private set; } = null!;
[PluginService]
public static IKeyState KeyState { get; private set; } = null!;
[PluginService]
public static IObjectTable ObjectTable { get; private set; } = null!;
[PluginService]
public static IPartyList PartyList { get; private set; } = null!;
[PluginService]
public static ITargetManager TargetManager { get; private set; } = null!;
[PluginService]
public static ITextureProvider TextureProvider { get; private set; } = null!;
[PluginService]
public static IGameInteropProvider GameInteropProvider { get; private set; } = null!;
[PluginService]
public static IGameConfig GameConfig { get; private set; } = null!;
[PluginService]
public static INotificationManager Notification { get; private set; } = null!;
[PluginService]
public static IAddonLifecycle AddonLifecycle { get; private set; } = null!;
[PluginService]
public static IPlayerState PlayerState { get; private set; } = null!;
[PluginService]
public static ISeStringEvaluator Evaluator { get; private set; } = null!;
[PluginService]
public static ISelfTestRegistry SelfTestRegistry { get; private set; } = null!;
public static Configuration Config = null!;
public static FileDialogManager FileDialogManager { get; private set; } = null!;
@@ -136,8 +177,9 @@ public sealed class Plugin : IAsyncDalamudPlugin
if (Config.Version < 16)
{
throw new InvalidOperationException(
$"HellionChat v1.4.3 requires config schema v16, got v{Config.Version}. " +
"Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.3.");
$"HellionChat v1.4.3 requires config schema v16, got v{Config.Version}. "
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.3."
);
}
// Hellion Chat — Auto-Tell-Tabs Defense-in-Depth. SaveConfig
@@ -219,13 +261,15 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Auto-Tell-Tabs subscribes to MessageManager.MessageProcessed for
// live tells and to ClientState.Logout for cleanup; needs the live
// store handed in at construction.
AutoTellTabsService = new AutoTellTabsService(this, MessageManager, MessageManager.Store);
AutoTellTabsService = new AutoTellTabsService(
this,
MessageManager,
MessageManager.Store
);
AutoTellTabsService.Initialize();
// SelfTest steps poll Active per frame and need the registry wired.
SelfTestRegistry.RegisterTestSteps([
new SelfTests.ThemeSwitchSelfTestStep(this),
]);
SelfTestRegistry.RegisterTestSteps([new SelfTests.ThemeSwitchSelfTestStep(this)]);
ChatLogWindow = new ChatLogWindow(this);
SettingsWindow = new SettingsWindow(this);
@@ -268,14 +312,14 @@ public sealed class Plugin : IAsyncDalamudPlugin
Interface.UiBuilder.DisableCutsceneUiHide = true;
Interface.UiBuilder.DisableGposeUiHide = true;
#if !DEBUG
#if !DEBUG
// Fire-and-forget on a worker thread. The first auto-translate use of
// a session may have a sub-second hitch if the cache hasn't filled yet,
// but that's preferable to making every user wait ~300 ms during
// plugin load for a cache they may never touch. ChatTwo (upstream)
// does this sync; we trade load-time for first-use latency.
_ = Task.Run(AutoTranslate.PreloadCache, cancellationToken);
#endif
#endif
cancellationToken.ThrowIfCancellationRequested();
@@ -296,8 +340,13 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Mirror the v1.4.0 load-failure recovery: hand off to DisposeAsync
// so partially-built services are torn down. Swallow the cleanup
// exception so the original load failure stays the visible cause.
try { await DisposeAsync().ConfigureAwait(false); }
catch { /* keep original failure */ }
try
{
await DisposeAsync().ConfigureAwait(false);
}
catch
{ /* keep original failure */
}
throw;
}
}
@@ -324,14 +373,17 @@ public sealed class Plugin : IAsyncDalamudPlugin
// v1.4.0 F5.3 — flush a pending DeferredSave before service teardown,
// since FrameworkUpdate just got unsubscribed and won't fire it.
failure = CaptureFailure(failure, () =>
{
if (DeferredSaveFrames >= 0)
failure = CaptureFailure(
failure,
() =>
{
SaveConfig();
DeferredSaveFrames = -1;
if (DeferredSaveFrames >= 0)
{
SaveConfig();
DeferredSaveFrames = -1;
}
}
});
);
// Auto-Tell-Tabs unsubscribes from MessageProcessed before MessageManager
// goes away. Pure-memory cleanup, no framework-thread requirement.
@@ -342,7 +394,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
// framework-block so the worker threads are quiesced first.
if (MessageManager is not null)
{
failure = await CaptureFailureAsync(failure, () => MessageManager.DisposeAsync().AsTask())
failure = await CaptureFailureAsync(
failure,
() => MessageManager.DisposeAsync().AsTask()
)
.ConfigureAwait(false);
}
@@ -354,32 +409,37 @@ public sealed class Plugin : IAsyncDalamudPlugin
// behind it; mirrors Lightless DisposeFrameworkBoundServicesAsync.
try
{
await Framework.RunOnFrameworkThread(() =>
{
// Game-Functions first — other services may still query
// chat-interactable state during their Dispose.
failure = CaptureFailure(failure, () => GameFunctions.GameFunctions.SetChatInteractable(true));
await Framework
.RunOnFrameworkThread(() =>
{
// Game-Functions first — other services may still query
// chat-interactable state during their Dispose.
failure = CaptureFailure(
failure,
() => GameFunctions.GameFunctions.SetChatInteractable(true)
);
// IPC subscribers — dispose before windows so any final
// event firing from the IPC source can't reach a half-torn
// ChatLogWindow.
failure = CaptureFailure(failure, () => HonorificService?.Dispose());
failure = CaptureFailure(failure, () => TypingIpc?.Dispose());
failure = CaptureFailure(failure, () => ExtraChat?.Dispose());
failure = CaptureFailure(failure, () => Ipc?.Dispose());
// IPC subscribers — dispose before windows so any final
// event firing from the IPC source can't reach a half-torn
// ChatLogWindow.
failure = CaptureFailure(failure, () => HonorificService?.Dispose());
failure = CaptureFailure(failure, () => TypingIpc?.Dispose());
failure = CaptureFailure(failure, () => ExtraChat?.Dispose());
failure = CaptureFailure(failure, () => Ipc?.Dispose());
// Windows — RemoveAllWindows first, then per-window Dispose.
// Order matches the pre-v1.4.3 Dispose body byte-for-byte.
// CommandHelpWindow and FirstRunWizard don't implement
// IDisposable; their resources are reclaimed via WindowSystem.
failure = CaptureFailure(failure, () => WindowSystem?.RemoveAllWindows());
failure = CaptureFailure(failure, () => ChatLogWindow?.Dispose());
failure = CaptureFailure(failure, () => DbViewer?.Dispose());
failure = CaptureFailure(failure, () => InputPreview?.Dispose());
failure = CaptureFailure(failure, () => SettingsWindow?.Dispose());
failure = CaptureFailure(failure, () => DebuggerWindow?.Dispose());
failure = CaptureFailure(failure, () => SeStringDebugger?.Dispose());
}).ConfigureAwait(false);
// Windows — RemoveAllWindows first, then per-window Dispose.
// Order matches the pre-v1.4.3 Dispose body byte-for-byte.
// CommandHelpWindow and FirstRunWizard don't implement
// IDisposable; their resources are reclaimed via WindowSystem.
failure = CaptureFailure(failure, () => WindowSystem?.RemoveAllWindows());
failure = CaptureFailure(failure, () => ChatLogWindow?.Dispose());
failure = CaptureFailure(failure, () => DbViewer?.Dispose());
failure = CaptureFailure(failure, () => InputPreview?.Dispose());
failure = CaptureFailure(failure, () => SettingsWindow?.Dispose());
failure = CaptureFailure(failure, () => DebuggerWindow?.Dispose());
failure = CaptureFailure(failure, () => SeStringDebugger?.Dispose());
})
.ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -401,15 +461,30 @@ public sealed class Plugin : IAsyncDalamudPlugin
// skip every cleanup behind it and leave services half-torn.
private static Exception? CaptureFailure(Exception? failure, Action action)
{
try { action(); }
catch (Exception ex) { failure ??= ex; }
try
{
action();
}
catch (Exception ex)
{
failure ??= ex;
}
return failure;
}
private static async ValueTask<Exception?> CaptureFailureAsync(Exception? failure, Func<Task> action)
private static async ValueTask<Exception?> CaptureFailureAsync(
Exception? failure,
Func<Task> action
)
{
try { await action().ConfigureAwait(false); }
catch (Exception ex) { failure ??= ex; }
try
{
await action().ConfigureAwait(false);
}
catch (Exception ex)
{
failure ??= ex;
}
return failure;
}
@@ -434,12 +509,17 @@ public sealed class Plugin : IAsyncDalamudPlugin
if (!File.Exists(ourConfigFile) && File.Exists(legacyConfigFile))
{
File.Move(legacyConfigFile, ourConfigFile);
Log.Information($"HellionChat: migrated config file {legacyConfigFile} → {ourConfigFile}");
Log.Information(
$"HellionChat: migrated config file {legacyConfigFile} → {ourConfigFile}"
);
}
}
catch (IOException e)
{
Log.Warning(e, $"HellionChat: config file move blocked, leaving {legacyConfigFile} in place");
Log.Warning(
e,
$"HellionChat: config file move blocked, leaving {legacyConfigFile} in place"
);
lockedBlocker = true;
}
@@ -469,7 +549,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
}
catch (IOException e)
{
Log.Warning(e, $"HellionChat: file move blocked for {file}, will retry on next load");
Log.Warning(
e,
$"HellionChat: file move blocked for {file}, will retry on next load"
);
lockedBlocker = true;
}
}
@@ -486,7 +569,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
}
catch (IOException e)
{
Log.Warning(e, $"HellionChat: subdir move blocked for {dir}, will retry on next load");
Log.Warning(
e,
$"HellionChat: subdir move blocked for {dir}, will retry on next load"
);
lockedBlocker = true;
}
}
@@ -507,15 +593,18 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Surface the most common cause to the user as a notification
// so they don't think Hellion Chat lost their history when in
// fact upstream Chat 2 was still holding the database file.
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
{
Title = "Hellion Chat",
Content = "Could not migrate the Chat 2 database — the file appears to be in use. " +
"Disable Chat 2, fully close the game, then start it again. " +
"See the README troubleshooting section if the issue persists.",
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Warning,
InitialDuration = TimeSpan.FromSeconds(30),
});
Notification.AddNotification(
new Dalamud.Interface.ImGuiNotification.Notification
{
Title = "Hellion Chat",
Content =
"Could not migrate the Chat 2 database — the file appears to be in use. "
+ "Disable Chat 2, fully close the game, then start it again. "
+ "See the README troubleshooting section if the issue persists.",
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Warning,
InitialDuration = TimeSpan.FromSeconds(30),
}
);
}
}
@@ -575,11 +664,13 @@ public sealed class Plugin : IAsyncDalamudPlugin
// — the .Wait() here would return as soon as the inner Task.Run was
// dispatched, racing the next sweep cycle against the still-running
// filter pass. See AUDIT-2026-05-05 [QUAL-02].
Framework.Run(() =>
{
MessageManager.ClearAllTabs();
MessageManager.FilterAllTabs();
}).Wait();
Framework
.Run(() =>
{
MessageManager.ClearAllTabs();
MessageManager.FilterAllTabs();
})
.Wait();
}
else
{
@@ -595,7 +686,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
lock (RetentionSweepLock)
RetentionSweepRunning = false;
}
}) { IsBackground = true }.Start();
})
{
IsBackground = true,
}.Start();
}
private void Draw()
@@ -603,7 +697,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
// Theme-Engine ist ab v14 immer aktiv; Klassik ist jetzt ein eigenes
// Theme statt einem deaktivierten Hellion-Theme. Active wird einmal
// pro Frame aus der Registry gelesen.
using IDisposable _style = HellionStyle.PushGlobal(ThemeRegistry.Active, Config.WindowOpacity);
using IDisposable _style = HellionStyle.PushGlobal(
ThemeRegistry.Active,
Config.WindowOpacity
);
ChatLogWindow.BeginFrame();
@@ -617,7 +714,12 @@ public sealed class Plugin : IAsyncDalamudPlugin
// v1.0.2 — global skip while the New Game+ menu (QuestRedo addon) is
// open. Hides every plugin window in one shot (chat log, pop-outs,
// settings, db viewer, etc.), matching the LoadingScreens pattern.
if (Config.HideInNewGamePlusMenu && GameFunctions.GameFunctions.IsAddonInteractable(GameFunctions.GameFunctions.NewGamePlusAddonName))
if (
Config.HideInNewGamePlusMenu
&& GameFunctions.GameFunctions.IsAddonInteractable(
GameFunctions.GameFunctions.NewGamePlusAddonName
)
)
{
ChatLogWindow.FinalizeFrame();
TypingIpc.Update();
@@ -627,7 +729,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
ChatLogWindow.HideStateCheck();
Interface.UiBuilder.DisableUserUiHide = !Config.HideWhenUiHidden;
ChatLogWindow.DefaultText = ImGui.GetStyle().Colors[(int) ImGuiCol.Text];
ChatLogWindow.DefaultText = ImGui.GetStyle().Colors[(int)ImGuiCol.Text];
using ((Config.FontsEnabled ? FontManager.RegularFont : FontManager.Axis).Push())
WindowSystem.Draw();
@@ -655,9 +757,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
internal void LanguageChanged(string langCode)
{
var info = Config.LanguageOverride is LanguageOverride.None
? new CultureInfo(langCode)
: new CultureInfo(Config.LanguageOverride.Code());
var info =
Config.LanguageOverride is LanguageOverride.None
? new CultureInfo(langCode)
: new CultureInfo(Config.LanguageOverride.Code());
Language.Culture = info;
HellionStrings.Culture = info;
@@ -669,7 +772,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
"ChatLogPanel_0",
"ChatLogPanel_1",
"ChatLogPanel_2",
"ChatLogPanel_3"
"ChatLogPanel_3",
];
private void FrameworkUpdate(IFramework framework)
@@ -687,7 +790,9 @@ public sealed class Plugin : IAsyncDalamudPlugin
public static bool InBattle => Condition[ConditionFlag.InCombat];
public static bool GposeActive => Condition[ConditionFlag.WatchingCutscene];
public static bool CutsceneActive => Condition[ConditionFlag.OccupiedInCutSceneEvent] || Condition[ConditionFlag.WatchingCutscene78];
public static bool CutsceneActive =>
Condition[ConditionFlag.OccupiedInCutSceneEvent]
|| Condition[ConditionFlag.WatchingCutscene78];
// v1.1.0 — wenn der themes/-Ordner leer ist, schreiben wir die embedded
// example-theme.json als Vorlage rein. Bestehende User-Customs werden
@@ -698,7 +803,9 @@ public sealed class Plugin : IAsyncDalamudPlugin
return;
var examplePath = Path.Combine(dir, "example-theme.json");
var resourceStream = typeof(Plugin).Assembly.GetManifestResourceStream("HellionChat.Themes.Builtin.example-theme.json");
var resourceStream = typeof(Plugin).Assembly.GetManifestResourceStream(
"HellionChat.Themes.Builtin.example-theme.json"
);
if (resourceStream is null)
{
Log.Warning("Themes example template not found in assembly resources; skipping seed.");
@@ -713,7 +820,10 @@ public sealed class Plugin : IAsyncDalamudPlugin
}
catch (IOException ex)
{
Log.Warning(ex, "Failed to seed example-theme.json; user can create custom themes manually.");
Log.Warning(
ex,
"Failed to seed example-theme.json; user can create custom themes manually."
);
}
finally
{