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);
}