using CliWrap; using CliWrap.Buffered; using InnovEnergy.Lib.Utils; namespace InnovEnergy.FossilTui; public static class Program { private static readonly Command Fossil = Cli.Wrap("fossil"); public static async Task Main() { Console.CancelKeyPress += (_, _) => "aborted".WriteLine(); while (true) { var needsUpdate = await NeedsUpdate(); var changes = await GetChanges(); var status = await GetStatus(); if (!changes.Any()) { Console.Clear(); status.WriteLine(Environment.NewLine); "No Changes".WriteLine(ConsoleColor.Green); return; } var selectedChange = changes.First(); while (true) { Console.Clear(); status.WriteLine(Environment.NewLine); if (needsUpdate) { "Update available for your branch".WriteLine(ConsoleColor.Red); "Press U to Update".WriteLine(Environment.NewLine); } ShowChanges(changes, selectedChange); var key = Console.ReadKey().Key; if (key == ConsoleKey.DownArrow) { selectedChange = changes.GetNext(selectedChange); } else if (key == ConsoleKey.UpArrow) { selectedChange = changes.GetPrevious(selectedChange); } else if (key == ConsoleKey.RightArrow) { selectedChange.SelectNextAction(!needsUpdate); } else if (key == ConsoleKey.LeftArrow) { selectedChange.SelectPreviousAction(!needsUpdate); } else if (key == ConsoleKey.D && selectedChange.State == FossilState.Edited) { var fossilGetDiff = Fossil.WithArguments("gdiff " + selectedChange.File); await fossilGetDiff.ExecuteBufferedAsync(); } else if (key == ConsoleKey.U) { await FossilUpdate(); break; } else if (key == ConsoleKey.Spacebar) { // } else if (key == ConsoleKey.Escape) { Console.Clear(); return; } else if (key == ConsoleKey.Enter) { await ApplyActions(changes, needsUpdate); break; } Console.Clear(); } } } private static async Task> GetChanges() { var result = await Fossil .WithArguments("changes --differ") .ExecuteBufferedAsync(); return result .StandardOutput .SplitLines() .Unless(String.IsNullOrWhiteSpace) //.Unless(f => f.Contains("FossilTui")) // TODO: remove .Select(l => l.Split(' ', StringSplitOptions.RemoveEmptyEntries)) .Select(t => new Change(FossilState.Parse(t[0]), t[1])) .ToList(); } private static async Task GetStatus() { var statusResult = await Fossil .WithArguments("status") .ExecuteBufferedAsync(); return statusResult .StandardOutput .SplitLines() .Unless(String.IsNullOrWhiteSpace) .Where(l => l[0] is >= 'a' and <= 'z') .JoinNonEmptyLines(); } private static async Task FossilUpdate() { var result = await Fossil .WithArguments("update") .WithValidation(CommandResultValidation.None) .ExecuteBufferedAsync(); if (result.ExitCode != 0) { Console.WriteLine(result.StandardOutput); Console.WriteLine(result.StandardError); } } private static async Task ApplyActions(IReadOnlyList changes, Boolean needsUpdate) { await DeleteFiles(changes); await AddFiles(changes); // must run before CommitFiles await RevertFiles(changes); await CommitFiles(changes, needsUpdate); } private static async Task CommitFiles(IReadOnlyList changes, Boolean needsUpdate) { var filesToCheckIn = changes.GetFilesWithAction(FossilAction.Commit); if (!filesToCheckIn.Any()) return; if (needsUpdate) { "Would fork".WriteLine(ConsoleColor.Red); "Not committing selected files".WriteLine(); "Please update first".WriteLine(); } else { var msg = RequestCommitMessage().Quote(); var files = filesToCheckIn.JoinWith(" "); var result = await Fossil .WithArguments($"ci -m {msg} {files}") .ExecuteBufferedAsync(); if (result.ExitCode != 0) { Console.WriteLine(result.StandardOutput); Console.WriteLine(result.StandardError); } } } private static String RequestCommitMessage() { while(true) { Console.WriteLine("Write your Commit message:"); var msg = Console.ReadLine(); if (msg.IsNullOrWhiteSpace()) continue; return msg!; } } private static async Task RevertFiles(IReadOnlyList changes) { var filesToRevert = changes.GetFilesWithAction(FossilAction.Revert); if (!filesToRevert.Any()) return; var args = filesToRevert.Aggregate("revert", (a, b) => a + " " + b); var result = await Fossil.WithArguments(args).ExecuteBufferedAsync(); if (result.ExitCode != 0) { Console.WriteLine(result.StandardOutput); Console.WriteLine(result.StandardError); } } private static async Task AddFiles(IReadOnlyList changes) { var args = changes .Where(c => c.SelectedAction == FossilAction.Add) .Select(c => c.File) .Aggregate("add", (a, b) => a + " " + b); var result = await Fossil.WithArguments(args).ExecuteBufferedAsync(); if (result.ExitCode != 0) { Console.WriteLine(result.StandardOutput); Console.WriteLine(result.StandardError); } } private static async Task DeleteFiles(IReadOnlyCollection changes) { foreach (var file in changes.GetFilesWithAction(FossilAction.Delete)) { var result = await Fossil.WithArguments($"rm {file}").WithValidation(CommandResultValidation.None).ExecuteBufferedAsync(); if (result.ExitCode != 0) { Console.WriteLine(result.StandardOutput); Console.WriteLine(result.StandardError); } try { File.Delete(file); } catch (Exception e) { Console.WriteLine($"Failed to delete file {file}"); Console.WriteLine(e); } } } private static IReadOnlyList GetFilesWithAction(this IEnumerable changes, FossilAction fossilAction) { return changes .Where(c => c.SelectedAction == fossilAction) .Select(c => c.File) .ToList(); } private static void ShowChanges(IReadOnlyList changes, Change selectedChange) { foreach (var change in changes) { const ConsoleColor bg = ConsoleColor.Black; const ConsoleColor fg = ConsoleColor.Gray; var state = change.State; var stateFgColor = state.Color; var selected = change.File == selectedChange.File; state.Name.PadRight(10).Write(stateFgColor, bg, selected); change.SelectedAction.ToString().PadRight(10).Write(fg, bg, selected); change.File.Write(fg, bg, selected); Console.WriteLine(); } } private static async Task NeedsUpdate() { var result = await Fossil .WithArguments("update -n") .ExecuteBufferedAsync(); return result .StandardOutput .SplitLines() .Any(l => l.StartsWith("updated-to")); } }