fix: исправить навигацию клавиатуры в TUI через GlobalKeyboardService
- Добавить GlobalKeyboardService — выделенный поток с блокирующим Console.ReadKey, единственный источник клавишных событий для TUI - Убрать FocusManager из App.razor: перехватывал Tab до компонентов - Удалить @onkeydown с <Select>: RazorConsole не пробрасывает Tab/стрелки через этот механизм - Использовать FocusedValue вместо Value в Select для корректной подсветки - Обновить CLAUDE.md и AGENTS.md: архитектура TUI, RazorConsole gotchas - Добавить docs/tui_log.md: разбор проблемы и справочник по RazorConsole Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,42 +1,89 @@
|
||||
@using LazyBear.MCP.Services.ToolRegistry
|
||||
@inject ToolRegistryService Registry
|
||||
|
||||
<Rows>
|
||||
<Markup Content=" " />
|
||||
<Markup Content="[bold]Tool Registry — runtime enable/disable[/]" />
|
||||
<Markup Content="[grey]Changes take effect immediately without restart[/]" />
|
||||
<Markup Content="Tool Registry" Foreground="@UiPalette.Text" Decoration="@Spectre.Console.Decoration.Bold" />
|
||||
<Markup Content="Up/Down select. Left/Right collapse or expand. Space toggles state." Foreground="@UiPalette.TextMuted" />
|
||||
<Markup Content=" " />
|
||||
|
||||
@{
|
||||
int focusIdx = 20;
|
||||
}
|
||||
|
||||
@foreach (var module in Registry.GetModules())
|
||||
@if (Entries.Count == 0)
|
||||
{
|
||||
var moduleEnabled = Registry.IsModuleEnabled(module.ModuleName);
|
||||
var moduleColor = moduleEnabled ? Spectre.Console.Color.Green : Spectre.Console.Color.Red;
|
||||
var moduleName = module.ModuleName;
|
||||
var capturedFocus = focusIdx++;
|
||||
|
||||
<Panel Title="@module.ModuleName" BorderColor="@moduleColor" Expand="true">
|
||||
<Rows>
|
||||
<Columns>
|
||||
<TextButton Content="@(moduleEnabled ? "[green]■ MODULE ENABLED[/]" : "[red]□ MODULE DISABLED[/]")"
|
||||
OnClick="@(() => Registry.ToggleModule(moduleName))"
|
||||
BackgroundColor="@(moduleEnabled ? Spectre.Console.Color.DarkGreen : Spectre.Console.Color.DarkRed)"
|
||||
FocusedColor="@Spectre.Console.Color.Yellow"
|
||||
FocusOrder="@capturedFocus" />
|
||||
<Markup Content="@($" {module.Description}")" Foreground="@Spectre.Console.Color.Grey" />
|
||||
</Columns>
|
||||
<Markup Content=" " />
|
||||
<ToolButtonList Module="@module" StartFocusIdx="@focusIdx" />
|
||||
</Rows>
|
||||
</Panel>
|
||||
|
||||
focusIdx += module.ToolNames.Count;
|
||||
<Markup Content=" " />
|
||||
<Border BorderColor="@UiPalette.Frame" BoxBorder="@Spectre.Console.BoxBorder.Rounded" Padding="@(new Spectre.Console.Padding(0, 0, 0, 0))">
|
||||
<Markup Content="No modules available." Foreground="@UiPalette.TextDim" />
|
||||
</Border>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Select TItem="int"
|
||||
Options="@GetOptions()"
|
||||
Value="@GetNormalizedIndex()"
|
||||
FocusedValue="@GetNormalizedIndex()"
|
||||
Formatter="@FormatEntry"
|
||||
Expand="true"
|
||||
BorderStyle="@Spectre.Console.BoxBorder.Rounded"
|
||||
SelectedIndicator="@('>')" />
|
||||
}
|
||||
|
||||
<Markup Content=" " />
|
||||
<Markup Content="@GetSelectedDescription()" Foreground="@UiPalette.TextMuted" />
|
||||
</Rows>
|
||||
|
||||
@code {
|
||||
[Parameter, EditorRequired] public IReadOnlyList<SettingsEntry> Entries { get; set; } = Array.Empty<SettingsEntry>();
|
||||
[Parameter] public int SelectedIndex { get; set; }
|
||||
[Parameter] public EventCallback<int> SelectedIndexChanged { get; set; }
|
||||
[Parameter] public int ViewportRows { get; set; } = 5;
|
||||
|
||||
private int[] GetOptions() => Enumerable.Range(0, Entries.Count).ToArray();
|
||||
|
||||
private int GetNormalizedIndex() => Entries.Count == 0 ? 0 : Math.Clamp(SelectedIndex, 0, Entries.Count - 1);
|
||||
|
||||
private string GetSelectedDescription()
|
||||
{
|
||||
if (Entries.Count == 0)
|
||||
{
|
||||
return "Runtime enable/disable settings are unavailable.";
|
||||
}
|
||||
|
||||
var selected = Entries[Math.Clamp(SelectedIndex, 0, Entries.Count - 1)];
|
||||
return selected.Description;
|
||||
}
|
||||
|
||||
private string FormatEntry(int index)
|
||||
{
|
||||
var entry = Entries[index];
|
||||
var indent = new string(' ', entry.Depth * 4);
|
||||
var checkbox = entry.IsChecked ? "[x]" : "[ ]";
|
||||
var disabledSuffix = entry.Kind == SettingsEntryKind.Tool && !entry.IsModuleEnabled ? " (module off)" : string.Empty;
|
||||
|
||||
string text;
|
||||
if (entry.Kind == SettingsEntryKind.Module)
|
||||
{
|
||||
var expander = entry.IsExpanded ? "[-]" : "[+]";
|
||||
text = $"{expander} {checkbox} {entry.Label}";
|
||||
}
|
||||
else
|
||||
{
|
||||
text = $"{indent}{checkbox} {entry.Label}{disabledSuffix}";
|
||||
}
|
||||
|
||||
return Fit(text, Math.Max(Console.WindowWidth - 12, 32));
|
||||
}
|
||||
|
||||
private static string Fit(string text, int width)
|
||||
{
|
||||
if (width <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (text.Length <= width)
|
||||
{
|
||||
return text.PadRight(width);
|
||||
}
|
||||
|
||||
if (width <= 3)
|
||||
{
|
||||
return text[..width];
|
||||
}
|
||||
|
||||
return text[..(width - 3)] + "...";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user