using Sdt.Config; using Spectre.Console; namespace Sdt.Tui; public sealed class WorkspaceScreen { private readonly WorkspaceConfig _workspace; private readonly string _workspaceRoot; private readonly string _currentProjectRoot; public WorkspaceScreen(WorkspaceConfig workspace, string workspaceRoot, string currentProjectRoot) { _workspace = workspace; _workspaceRoot = workspaceRoot; _currentProjectRoot = currentProjectRoot; } /// /// Shows the project switcher. Returns the absolute path to the selected project root, /// or null if the user cancelled. /// public string? SelectProject() { AnsiConsole.Clear(); AnsiConsole.Write(Theme.SectionRule("WORKSPACE — " + _workspace.Name)); AnsiConsole.WriteLine(); var projects = _workspace.Projects; if (projects.Count == 0) { AnsiConsole.MarkupLine(Theme.Warn("No projects defined in sdt-workspace.json.")); AnsiConsole.MarkupLine(Theme.Faint("Add entries to the \"projects\" array.")); AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back...")); Console.ReadKey(intercept: true); return null; } // Build choice list with current project marked var choices = new List(); foreach (var proj in projects) { var absPath = WorkspaceLoader.ResolveProjectRoot(_workspaceRoot, proj); var devtoolPath = Path.Combine(absPath, "devtool.json"); var isCurrent = string.Equals(absPath, _currentProjectRoot, StringComparison.OrdinalIgnoreCase); var exists = File.Exists(devtoolPath); var label = isCurrent ? $"[bold {Theme.GreenBold}]► {Markup.Escape(proj.Name)}[/] [{Theme.GreenDim}](current)[/]" : $"[{Theme.Green}] {Markup.Escape(proj.Name)}[/]"; var desc = !exists ? $" [{Theme.Red}]devtool.json not found[/]" : string.IsNullOrWhiteSpace(proj.Description) ? $" [{Theme.GreenDim}]{Markup.Escape(absPath)}[/]" : $" [{Theme.GreenDim}]{Markup.Escape(proj.Description)}[/]"; choices.Add(new WorkspaceMenuItem(label + "\n" + desc, absPath, exists && !isCurrent)); } choices.Add(new WorkspaceMenuItem($"[{Theme.GreenDim}]← Cancel[/]", null, true)); // Show project table for overview var table = new Table() .Border(TableBorder.Rounded) .BorderStyle(Theme.DimStyle) .AddColumn(new TableColumn($"[{Theme.Amber}]Project[/]")) .AddColumn(new TableColumn($"[{Theme.Amber}]Path[/]")) .AddColumn(new TableColumn($"[{Theme.Amber}]Status[/]").Width(12)); foreach (var proj in projects) { var absPath = WorkspaceLoader.ResolveProjectRoot(_workspaceRoot, proj); var isCurrent = string.Equals(absPath, _currentProjectRoot, StringComparison.OrdinalIgnoreCase); var hasConfig = File.Exists(Path.Combine(absPath, "devtool.json")); table.AddRow( isCurrent ? $"[bold {Theme.GreenBold}]► {Markup.Escape(proj.Name)}[/]" : Theme.G(proj.Name), Theme.Faint(proj.Path), hasConfig ? Theme.Ok("ready") : Theme.Fail("no config")); } AnsiConsole.Write(table); AnsiConsole.WriteLine(); var switchable = choices.Where(c => c.Selectable).ToList(); if (switchable.Count == 1) // only Cancel { AnsiConsole.MarkupLine(Theme.Warn("No other projects available to switch to.")); AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back...")); Console.ReadKey(intercept: true); return null; } var selected = AnsiConsole.Prompt( new SelectionPrompt() .Title($"[{Theme.Green}]Switch to project:[/]") .PageSize(15) .UseConverter(m => m.Display) .AddChoices(switchable)); return selected.AbsPath; // null = cancelled } private sealed record WorkspaceMenuItem(string Display, string? AbsPath, bool Selectable); }