739 lines
24 KiB
Plaintext
739 lines
24 KiB
Plaintext
@using LazyBear.MCP.Services.Logging
|
|
@using LazyBear.MCP.Services.ToolRegistry
|
|
@inject ToolRegistryService Registry
|
|
@inject InMemoryLogSink LogSink
|
|
@inject GlobalKeyboardService KeyboardService
|
|
@inject LocalizationService Localization
|
|
@inject IHostApplicationLifetime AppLifetime
|
|
|
|
@implements IDisposable
|
|
|
|
<Rows Expand="true">
|
|
<Columns Expand="true">
|
|
<Markup Content="@GetHeaderProjectSegment()"
|
|
Foreground="@UiPalette.Text"
|
|
Background="@UiPalette.HeaderBrandBackground"
|
|
Decoration="@Spectre.Console.Decoration.Bold" />
|
|
@foreach (var segment in BuildHeaderSegments())
|
|
{
|
|
<Markup Content="@segment.Content"
|
|
Foreground="@segment.Foreground"
|
|
Background="@segment.Background"
|
|
Decoration="@segment.Decoration" />
|
|
}
|
|
</Columns>
|
|
|
|
<Panel BorderColor="@UiPalette.Frame"
|
|
Expand="true"
|
|
Height="@GetPanelHeight()"
|
|
Padding="@(new Spectre.Console.Padding(1, 0, 1, 0))">
|
|
<Rows Expand="true">
|
|
@if (_activeTab == Tab.Overview)
|
|
{
|
|
<OverviewTab Rows="@GetOverviewRows()"
|
|
SelectedIndex="@_overviewSelection"
|
|
SelectedIndexChanged="@OnOverviewSelectionChanged"
|
|
ViewportRows="@GetOverviewViewportRows()"
|
|
Loc="@Localization.Current" />
|
|
}
|
|
else if (_activeTab == Tab.Logs)
|
|
{
|
|
<LogsTab Entries="@GetFilteredLogEntries()"
|
|
SelectedIndex="@_logSelection"
|
|
SelectedIndexChanged="@OnLogSelectionChanged"
|
|
SelectedFilter="@_logFilters[_logFilterIndex]"
|
|
ViewportRows="@GetLogsViewportRows()"
|
|
IsStickyToBottom="@_logsStickToBottom"
|
|
Loc="@Localization.Current" />
|
|
}
|
|
else
|
|
{
|
|
<SettingsTab Entries="@GetSettingsEntries()"
|
|
SelectedIndex="@_settingsSelection"
|
|
SelectedIndexChanged="@OnSettingsSelectionChanged"
|
|
ViewportRows="@GetSettingsViewportRows()"
|
|
Loc="@Localization.Current" />
|
|
}
|
|
</Rows>
|
|
</Panel>
|
|
<Markup Content="@BuildStatusBar(UiMetrics.ConsoleWidth)"
|
|
Foreground="@UiPalette.Text"
|
|
Background="@UiPalette.SurfaceMuted" />
|
|
|
|
<ModalWindow IsOpened="@_isHelpOpen">
|
|
<Panel Title="@Localization.Current.HelpModalTitle"
|
|
TitleColor="@UiPalette.Accent"
|
|
BorderColor="@UiPalette.AccentSoft"
|
|
Width="@GetHelpModalWidth()"
|
|
Padding="@(new Spectre.Console.Padding(1, 0, 1, 0))">
|
|
<Rows>
|
|
<Markup Content="@Localization.Current.HintBar"
|
|
Foreground="@UiPalette.Text" />
|
|
<Markup Content=" " />
|
|
<Markup Content="@GetHelpLine(Tab.Overview, Localization.Current.OverviewHint)"
|
|
Foreground="@UiPalette.TextMuted" />
|
|
<Markup Content="@GetHelpLine(Tab.Logs, Localization.Current.LogsHint)"
|
|
Foreground="@UiPalette.TextMuted" />
|
|
<Markup Content="@GetHelpLine(Tab.Settings, Localization.Current.SettingsHint)"
|
|
Foreground="@UiPalette.TextMuted" />
|
|
<Markup Content=" " />
|
|
<Markup Content="@Localization.Current.HelpCloseHint"
|
|
Foreground="@UiPalette.AccentSoft"
|
|
Decoration="@Spectre.Console.Decoration.Bold" />
|
|
</Rows>
|
|
</Panel>
|
|
</ModalWindow>
|
|
</Rows>
|
|
|
|
@code {
|
|
private const char Nbsp = '\u00A0';
|
|
|
|
private enum Tab
|
|
{
|
|
Overview,
|
|
Logs,
|
|
Settings
|
|
}
|
|
|
|
private readonly record struct HeaderSegment(
|
|
string Content,
|
|
Spectre.Console.Color Foreground,
|
|
Spectre.Console.Color Background,
|
|
Spectre.Console.Decoration Decoration);
|
|
|
|
private static readonly Tab[] _tabs = [Tab.Overview, Tab.Logs, Tab.Settings];
|
|
private static readonly string[] _logFilters = ["All", "Info", "Warn", "Error"];
|
|
private readonly HashSet<string> _expandedModules = new(StringComparer.Ordinal);
|
|
|
|
private Tab _activeTab = Tab.Overview;
|
|
private int _overviewSelection;
|
|
private int _logFilterIndex;
|
|
private int _logSelection;
|
|
private int _settingsSelection;
|
|
private bool _logsStickToBottom = true;
|
|
private bool _isHelpOpen;
|
|
|
|
private static readonly string _mcpEndpoint =
|
|
Environment.GetEnvironmentVariable("ASPNETCORE_URLS") ?? "http://localhost:5000";
|
|
|
|
private static int GetPanelHeight() => Math.Max(UiMetrics.ConsoleHeight - 4, 10);
|
|
private static int GetOverviewViewportRows() => Math.Max(GetPanelHeight() - 6, 3);
|
|
private static int GetLogsViewportRows() => Math.Max(GetPanelHeight() - 10, 5);
|
|
private static int GetSettingsViewportRows() => Math.Max(GetPanelHeight() - 7, 5);
|
|
private static int GetHelpModalWidth() => Math.Max(Math.Min(UiMetrics.ConsoleWidth - 6, 84), 36);
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
Registry.StateChanged += OnRegistryChanged;
|
|
LogSink.OnLog += OnNewLog;
|
|
KeyboardService.OnKeyPressed += OnConsoleKeyPressed;
|
|
Localization.OnChanged += OnLocaleChanged;
|
|
}
|
|
|
|
// Конвертация ConsoleKeyInfo → KeyboardEventArgs для переиспользования существующей логики
|
|
private static KeyboardEventArgs ConvertKey(ConsoleKeyInfo key)
|
|
{
|
|
var name = key.Key switch
|
|
{
|
|
ConsoleKey.UpArrow => "ArrowUp",
|
|
ConsoleKey.DownArrow => "ArrowDown",
|
|
ConsoleKey.LeftArrow => "ArrowLeft",
|
|
ConsoleKey.RightArrow => "ArrowRight",
|
|
ConsoleKey.Enter => "Enter",
|
|
ConsoleKey.Spacebar => " ",
|
|
ConsoleKey.Home => "Home",
|
|
ConsoleKey.End => "End",
|
|
ConsoleKey.PageUp => "PageUp",
|
|
ConsoleKey.PageDown => "PageDown",
|
|
ConsoleKey.Tab => "Tab",
|
|
ConsoleKey.Escape => "Escape",
|
|
_ => key.KeyChar == '\0' ? string.Empty : key.KeyChar.ToString()
|
|
};
|
|
|
|
return new KeyboardEventArgs
|
|
{
|
|
Key = name,
|
|
ShiftKey = (key.Modifiers & ConsoleModifiers.Shift) != 0
|
|
};
|
|
}
|
|
|
|
private void OnConsoleKeyPressed(ConsoleKeyInfo key)
|
|
{
|
|
var args = ConvertKey(key);
|
|
if (string.IsNullOrEmpty(args.Key))
|
|
{
|
|
return;
|
|
}
|
|
|
|
InvokeAsync(() =>
|
|
{
|
|
HandleKeyDown(args);
|
|
StateHasChanged();
|
|
});
|
|
}
|
|
|
|
private void OnLocaleChanged() =>
|
|
InvokeAsync(StateHasChanged);
|
|
|
|
private void HandleKeyDown(KeyboardEventArgs args)
|
|
{
|
|
if (args.Key is "h" or "H")
|
|
{
|
|
_isHelpOpen = !_isHelpOpen;
|
|
return;
|
|
}
|
|
|
|
if (_isHelpOpen)
|
|
{
|
|
if (args.Key == "Escape")
|
|
{
|
|
_isHelpOpen = false;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (string.Equals(args.Key, "Tab", StringComparison.Ordinal))
|
|
{
|
|
ChangeTab(args.ShiftKey ? -1 : 1);
|
|
return;
|
|
}
|
|
|
|
if (args.Key is "l" or "L")
|
|
{
|
|
Localization.SwitchNext();
|
|
return; // StateHasChanged вызовет OnLocaleChanged
|
|
}
|
|
|
|
if (args.Key is "q" or "Q")
|
|
{
|
|
AppLifetime.StopApplication();
|
|
return;
|
|
}
|
|
|
|
switch (_activeTab)
|
|
{
|
|
case Tab.Overview:
|
|
HandleOverviewKey(args);
|
|
break;
|
|
case Tab.Logs:
|
|
HandleLogsKey(args);
|
|
break;
|
|
case Tab.Settings:
|
|
HandleSettingsKey(args);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private Task OnOverviewSelectionChanged(int value)
|
|
{
|
|
var rows = GetOverviewRows();
|
|
_overviewSelection = rows.Count == 0 ? 0 : Math.Clamp(value, 0, rows.Count - 1);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private Task OnLogSelectionChanged(int value)
|
|
{
|
|
var entries = GetFilteredLogEntries();
|
|
_logSelection = entries.Count == 0 ? 0 : Math.Clamp(value, 0, entries.Count - 1);
|
|
_logsStickToBottom = entries.Count == 0 || _logSelection >= entries.Count - 1;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private Task OnSettingsSelectionChanged(int value)
|
|
{
|
|
var entries = GetSettingsEntries();
|
|
_settingsSelection = entries.Count == 0 ? 0 : Math.Clamp(value, 0, entries.Count - 1);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private void ChangeTab(int step)
|
|
{
|
|
var currentIndex = Array.IndexOf(_tabs, _activeTab);
|
|
if (currentIndex < 0)
|
|
{
|
|
currentIndex = 0;
|
|
}
|
|
|
|
var nextIndex = (currentIndex + step + _tabs.Length) % _tabs.Length;
|
|
_activeTab = _tabs[nextIndex];
|
|
ClampSelections();
|
|
}
|
|
|
|
private void HandleOverviewKey(KeyboardEventArgs args)
|
|
{
|
|
var rows = GetOverviewRows();
|
|
if (rows.Count == 0)
|
|
{
|
|
_overviewSelection = 0;
|
|
return;
|
|
}
|
|
|
|
_overviewSelection = Math.Clamp(_overviewSelection, 0, rows.Count - 1);
|
|
|
|
switch (args.Key)
|
|
{
|
|
case "ArrowUp":
|
|
_overviewSelection = Math.Max(0, _overviewSelection - 1);
|
|
break;
|
|
case "ArrowDown":
|
|
_overviewSelection = Math.Min(rows.Count - 1, _overviewSelection + 1);
|
|
break;
|
|
case "Home":
|
|
_overviewSelection = 0;
|
|
break;
|
|
case "End":
|
|
_overviewSelection = rows.Count - 1;
|
|
break;
|
|
case "Enter":
|
|
_activeTab = Tab.Settings;
|
|
SelectSettingsModule(rows[_overviewSelection].ModuleName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void HandleLogsKey(KeyboardEventArgs args)
|
|
{
|
|
switch (args.Key)
|
|
{
|
|
case "ArrowLeft":
|
|
_logFilterIndex = (_logFilterIndex - 1 + _logFilters.Length) % _logFilters.Length;
|
|
ResetLogsSelectionToBottom();
|
|
return;
|
|
case "ArrowRight":
|
|
_logFilterIndex = (_logFilterIndex + 1) % _logFilters.Length;
|
|
ResetLogsSelectionToBottom();
|
|
return;
|
|
}
|
|
|
|
var entries = GetFilteredLogEntries();
|
|
if (entries.Count == 0)
|
|
{
|
|
_logSelection = 0;
|
|
_logsStickToBottom = true;
|
|
return;
|
|
}
|
|
|
|
_logSelection = Math.Clamp(_logSelection, 0, entries.Count - 1);
|
|
var page = Math.Max(GetLogsViewportRows() - 1, 1);
|
|
|
|
switch (args.Key)
|
|
{
|
|
case "ArrowUp":
|
|
_logSelection = Math.Max(0, _logSelection - 1);
|
|
break;
|
|
case "ArrowDown":
|
|
_logSelection = Math.Min(entries.Count - 1, _logSelection + 1);
|
|
break;
|
|
case "PageUp":
|
|
_logSelection = Math.Max(0, _logSelection - page);
|
|
break;
|
|
case "PageDown":
|
|
case " ":
|
|
case "Spacebar":
|
|
_logSelection = Math.Min(entries.Count - 1, _logSelection + page);
|
|
break;
|
|
case "Home":
|
|
_logSelection = 0;
|
|
break;
|
|
case "End":
|
|
_logSelection = entries.Count - 1;
|
|
break;
|
|
}
|
|
|
|
_logsStickToBottom = _logSelection >= entries.Count - 1;
|
|
}
|
|
|
|
private void HandleSettingsKey(KeyboardEventArgs args)
|
|
{
|
|
var entries = GetSettingsEntries();
|
|
if (entries.Count == 0)
|
|
{
|
|
_settingsSelection = 0;
|
|
return;
|
|
}
|
|
|
|
_settingsSelection = Math.Clamp(_settingsSelection, 0, entries.Count - 1);
|
|
var selected = entries[_settingsSelection];
|
|
var page = Math.Max(GetSettingsViewportRows() - 1, 1);
|
|
|
|
switch (args.Key)
|
|
{
|
|
case "ArrowUp":
|
|
_settingsSelection = Math.Max(0, _settingsSelection - 1);
|
|
return;
|
|
case "ArrowDown":
|
|
_settingsSelection = Math.Min(entries.Count - 1, _settingsSelection + 1);
|
|
return;
|
|
case "PageUp":
|
|
_settingsSelection = Math.Max(0, _settingsSelection - page);
|
|
return;
|
|
case "PageDown":
|
|
_settingsSelection = Math.Min(entries.Count - 1, _settingsSelection + page);
|
|
return;
|
|
case "Home":
|
|
_settingsSelection = 0;
|
|
return;
|
|
case "End":
|
|
_settingsSelection = entries.Count - 1;
|
|
return;
|
|
case "ArrowRight":
|
|
ExpandModule(selected);
|
|
return;
|
|
case "ArrowLeft":
|
|
CollapseModuleOrFocusParent(selected);
|
|
return;
|
|
case "Enter":
|
|
ToggleExpansion(selected);
|
|
return;
|
|
case " ":
|
|
case "Spacebar":
|
|
ToggleSetting(selected);
|
|
return;
|
|
}
|
|
}
|
|
|
|
private void ExpandModule(SettingsEntry entry)
|
|
{
|
|
if (entry.Kind != SettingsEntryKind.Module || entry.IsExpanded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_expandedModules.Add(entry.ModuleName);
|
|
ClampSelections();
|
|
}
|
|
|
|
private void CollapseModuleOrFocusParent(SettingsEntry entry)
|
|
{
|
|
if (entry.Kind == SettingsEntryKind.Module)
|
|
{
|
|
if (_expandedModules.Remove(entry.ModuleName))
|
|
{
|
|
ClampSelections();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
_expandedModules.Remove(entry.ModuleName);
|
|
SelectSettingsModule(entry.ModuleName);
|
|
}
|
|
|
|
private void ToggleExpansion(SettingsEntry entry)
|
|
{
|
|
if (entry.Kind != SettingsEntryKind.Module)
|
|
{
|
|
ToggleSetting(entry);
|
|
return;
|
|
}
|
|
|
|
if (_expandedModules.Contains(entry.ModuleName))
|
|
{
|
|
_expandedModules.Remove(entry.ModuleName);
|
|
}
|
|
else
|
|
{
|
|
_expandedModules.Add(entry.ModuleName);
|
|
}
|
|
|
|
SelectSettingsModule(entry.ModuleName);
|
|
}
|
|
|
|
private void ToggleSetting(SettingsEntry entry)
|
|
{
|
|
if (entry.Kind == SettingsEntryKind.Module)
|
|
{
|
|
Registry.ToggleModule(entry.ModuleName);
|
|
return;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(entry.ToolName))
|
|
{
|
|
Registry.ToggleTool(entry.ModuleName, entry.ToolName);
|
|
}
|
|
}
|
|
|
|
private void SelectSettingsModule(string moduleName)
|
|
{
|
|
var entries = GetSettingsEntries();
|
|
var index = entries.FindIndex(entry =>
|
|
entry.Kind == SettingsEntryKind.Module &&
|
|
string.Equals(entry.ModuleName, moduleName, StringComparison.Ordinal));
|
|
|
|
_settingsSelection = index >= 0 ? index : 0;
|
|
}
|
|
|
|
private void ResetLogsSelectionToBottom()
|
|
{
|
|
var entries = GetFilteredLogEntries();
|
|
_logSelection = Math.Max(entries.Count - 1, 0);
|
|
_logsStickToBottom = true;
|
|
}
|
|
|
|
private List<OverviewRow> GetOverviewRows() =>
|
|
Registry.GetModules()
|
|
.Select(module =>
|
|
{
|
|
var (configuredTools, totalTools) = Registry.GetConfiguredToolCounts(module.ModuleName);
|
|
return new OverviewRow(
|
|
module.ModuleName,
|
|
module.Description,
|
|
Registry.IsModuleEnabled(module.ModuleName),
|
|
configuredTools,
|
|
totalTools);
|
|
})
|
|
.ToList();
|
|
|
|
private List<SettingsEntry> GetSettingsEntries()
|
|
{
|
|
var entries = new List<SettingsEntry>();
|
|
|
|
foreach (var module in Registry.GetModules())
|
|
{
|
|
var isModuleEnabled = Registry.IsModuleEnabled(module.ModuleName);
|
|
var isExpanded = _expandedModules.Contains(module.ModuleName);
|
|
|
|
entries.Add(new SettingsEntry(
|
|
SettingsEntryKind.Module,
|
|
module.ModuleName,
|
|
null,
|
|
module.ModuleName,
|
|
module.Description,
|
|
isModuleEnabled,
|
|
isModuleEnabled,
|
|
isExpanded,
|
|
0));
|
|
|
|
if (!isExpanded)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var toolName in module.ToolNames)
|
|
{
|
|
var isConfigured = Registry.IsToolConfiguredEnabled(module.ModuleName, toolName);
|
|
entries.Add(new SettingsEntry(
|
|
SettingsEntryKind.Tool,
|
|
module.ModuleName,
|
|
toolName,
|
|
toolName,
|
|
isModuleEnabled
|
|
? $"{module.ModuleName} / {toolName}"
|
|
: $"{module.ModuleName} / {toolName} {Localization.Current.ModuleOffPreserved}",
|
|
isConfigured,
|
|
isModuleEnabled,
|
|
false,
|
|
1));
|
|
}
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
|
|
private List<LogEntry> GetFilteredLogEntries()
|
|
{
|
|
var entries = LogSink.GetEntries();
|
|
return _logFilters[_logFilterIndex] switch
|
|
{
|
|
"Info" => entries.Where(IsInfoLevel).ToList(),
|
|
"Warn" => entries.Where(entry => entry.Level == LogLevel.Warning).ToList(),
|
|
"Error" => entries.Where(entry => entry.Level is LogLevel.Error or LogLevel.Critical).ToList(),
|
|
_ => entries.ToList()
|
|
};
|
|
}
|
|
|
|
private void ClampSelections()
|
|
{
|
|
var overviewRows = GetOverviewRows();
|
|
_overviewSelection = overviewRows.Count == 0
|
|
? 0
|
|
: Math.Clamp(_overviewSelection, 0, overviewRows.Count - 1);
|
|
|
|
var logEntries = GetFilteredLogEntries();
|
|
_logSelection = logEntries.Count == 0
|
|
? 0
|
|
: Math.Clamp(_logSelection, 0, logEntries.Count - 1);
|
|
|
|
var settingsEntries = GetSettingsEntries();
|
|
_settingsSelection = settingsEntries.Count == 0
|
|
? 0
|
|
: Math.Clamp(_settingsSelection, 0, settingsEntries.Count - 1);
|
|
}
|
|
|
|
private void OnRegistryChanged()
|
|
{
|
|
InvokeAsync(() =>
|
|
{
|
|
ClampSelections();
|
|
StateHasChanged();
|
|
});
|
|
}
|
|
|
|
private void OnNewLog(LogEntry entry)
|
|
{
|
|
InvokeAsync(() =>
|
|
{
|
|
if (_logsStickToBottom && MatchesCurrentLogFilter(entry))
|
|
{
|
|
var filteredEntries = GetFilteredLogEntries();
|
|
_logSelection = Math.Max(filteredEntries.Count - 1, 0);
|
|
}
|
|
else
|
|
{
|
|
ClampSelections();
|
|
}
|
|
|
|
StateHasChanged();
|
|
});
|
|
}
|
|
|
|
private bool MatchesCurrentLogFilter(LogEntry entry) =>
|
|
_logFilters[_logFilterIndex] switch
|
|
{
|
|
"Info" => IsInfoLevel(entry),
|
|
"Warn" => entry.Level == LogLevel.Warning,
|
|
"Error" => entry.Level is LogLevel.Error or LogLevel.Critical,
|
|
_ => true
|
|
};
|
|
|
|
private static bool IsInfoLevel(LogEntry entry) =>
|
|
entry.Level is LogLevel.Information or LogLevel.Debug or LogLevel.Trace;
|
|
|
|
private string GetTabLabel(Tab tab) => tab switch
|
|
{
|
|
Tab.Overview => Localization.Current.TabOverview,
|
|
Tab.Logs => Localization.Current.TabLogs,
|
|
_ => Localization.Current.TabSettings
|
|
};
|
|
|
|
private HeaderSegment[] BuildHeaderSegments()
|
|
{
|
|
var width = Math.Max(UiMetrics.ConsoleWidth, 1);
|
|
var projectSegmentWidth = GetHeaderProjectSegment().Length;
|
|
var availableTabsWidth = Math.Max(width - projectSegmentWidth, 0);
|
|
if (availableTabsWidth <= 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var labels = _tabs.Select(GetTabLabel).ToArray();
|
|
var labelWidths = labels.Select(label => label.Length).ToArray();
|
|
var availableLabelWidth = availableTabsWidth - (_tabs.Length * 2);
|
|
if (availableLabelWidth < _tabs.Length)
|
|
{
|
|
return
|
|
[
|
|
new HeaderSegment(
|
|
FillBar(availableTabsWidth),
|
|
UiPalette.Text,
|
|
UiPalette.HeaderTabsBackground,
|
|
Spectre.Console.Decoration.None)
|
|
];
|
|
}
|
|
|
|
while (labelWidths.Sum() > availableLabelWidth)
|
|
{
|
|
var largestIndex = Array.IndexOf(labelWidths, labelWidths.Max());
|
|
if (largestIndex < 0 || labelWidths[largestIndex] <= 1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
labelWidths[largestIndex]--;
|
|
}
|
|
|
|
var segments = new List<HeaderSegment>(_tabs.Length + 1);
|
|
var usedWidth = 0;
|
|
|
|
for (var i = 0; i < _tabs.Length; i++)
|
|
{
|
|
var tab = _tabs[i];
|
|
var label = FitInline(labels[i], labelWidths[i]);
|
|
var content = WrapBarSegment(label);
|
|
|
|
segments.Add(new HeaderSegment(
|
|
content,
|
|
tab == _activeTab ? UiPalette.SelectionForeground : UiPalette.Text,
|
|
tab == _activeTab ? UiPalette.SelectionBackground : UiPalette.HeaderTabsBackground,
|
|
tab == _activeTab ? Spectre.Console.Decoration.Bold : Spectre.Console.Decoration.None));
|
|
|
|
usedWidth += content.Length;
|
|
}
|
|
|
|
var fillerWidth = Math.Max(availableTabsWidth - usedWidth, 0);
|
|
if (fillerWidth > 0)
|
|
{
|
|
segments.Add(new HeaderSegment(
|
|
FillBar(fillerWidth),
|
|
UiPalette.Text,
|
|
UiPalette.HeaderTabsBackground,
|
|
Spectre.Console.Decoration.None));
|
|
}
|
|
|
|
return [.. segments];
|
|
}
|
|
|
|
private string GetHeaderProjectSegment() => WrapBarSegment(Localization.Current.ProjectTitle);
|
|
|
|
private string GetHelpLine(Tab tab, string hint) => $"{GetTabLabel(tab)}: {hint}";
|
|
|
|
private string GetStatusBarLeftText() => $"{Localization.Label} | {Localization.Current.HelpStatusHint}";
|
|
|
|
private string GetStatusBarRightText() => $"{Localization.Current.DashboardEndpointLabel}: {_mcpEndpoint}";
|
|
|
|
private string BuildStatusBar(int width)
|
|
{
|
|
var left = GetStatusBarLeftText();
|
|
var right = GetStatusBarRightText();
|
|
width = Math.Max(width, 1);
|
|
|
|
if (right.Length >= width)
|
|
{
|
|
return right[^width..];
|
|
}
|
|
|
|
var leftWidth = Math.Max(width - right.Length - 1, 0);
|
|
var fittedLeft = leftWidth == 0 ? string.Empty : PadRightVisible(FitInline(left, leftWidth), leftWidth);
|
|
|
|
return leftWidth == 0
|
|
? PadLeftVisible(right, width)
|
|
: $"{fittedLeft} {right}";
|
|
}
|
|
|
|
private static string FillBar(int width) =>
|
|
width <= 0 ? string.Empty : new string(Nbsp, width);
|
|
|
|
private static string WrapBarSegment(string text) => $"{Nbsp}{text}{Nbsp}";
|
|
|
|
private static string PadRightVisible(string text, int width)
|
|
{
|
|
if (width <= 0) return string.Empty;
|
|
if (text.Length >= width) return text[..width];
|
|
return text + FillBar(width - text.Length);
|
|
}
|
|
|
|
private static string PadLeftVisible(string text, int width)
|
|
{
|
|
if (width <= 0) return string.Empty;
|
|
if (text.Length >= width) return text[^width..];
|
|
return FillBar(width - text.Length) + text;
|
|
}
|
|
|
|
private static string FitInline(string text, int width)
|
|
{
|
|
if (width <= 0) return string.Empty;
|
|
if (text.Length <= width) return text;
|
|
if (width <= 3) return text[..width];
|
|
return text[..(width - 3)] + "...";
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Registry.StateChanged -= OnRegistryChanged;
|
|
LogSink.OnLog -= OnNewLog;
|
|
KeyboardService.OnKeyPressed -= OnConsoleKeyPressed;
|
|
Localization.OnChanged -= OnLocaleChanged;
|
|
}
|
|
}
|