97 lines
3.1 KiB
C#
97 lines
3.1 KiB
C#
using System.Diagnostics;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using NozCli.Repl;
|
|
using Spectre.Console;
|
|
|
|
namespace NozCli.Commands;
|
|
|
|
public class EditCommand : ICommand
|
|
{
|
|
public string Name => "edit";
|
|
public string Description => "Open a note in your default editor, then offer to push";
|
|
public string Usage => "edit <slug>";
|
|
|
|
public async Task ExecuteAsync(string[] args, ReplContext ctx)
|
|
{
|
|
var slug = args.FirstOrDefault();
|
|
if (slug is null) { AnsiConsole.MarkupLine("[red]Usage: edit <slug>[/]"); return; }
|
|
|
|
slug = ctx.ResolveSlug(slug);
|
|
|
|
// 1. Fetch note from server
|
|
NozCli.Core.Models.NoteContent note = default!;
|
|
await AnsiConsole.Status().StartAsync("Fetching…", async _ =>
|
|
note = await ctx.Api.GetNoteAsync(slug));
|
|
|
|
// 2. Write to temp file
|
|
var tmp = Path.Combine(Path.GetTempPath(), $"noz-{slug.Replace('/', '-')}.md");
|
|
await File.WriteAllTextAsync(tmp, note.Markdown);
|
|
|
|
// 3. Open editor
|
|
var editor = ResolveEditor();
|
|
AnsiConsole.MarkupLine($"[dim]Opening {editor} …[/]");
|
|
var proc = Process.Start(new ProcessStartInfo
|
|
{
|
|
FileName = editor,
|
|
Arguments = $"\"{tmp}\"",
|
|
UseShellExecute = !OperatingSystem.IsWindows(),
|
|
});
|
|
proc?.WaitForExit();
|
|
|
|
// 4. Compare hashes
|
|
var edited = await File.ReadAllTextAsync(tmp);
|
|
var newHash = HashContent(edited);
|
|
|
|
File.Delete(tmp);
|
|
|
|
if (newHash == note.Hash)
|
|
{
|
|
AnsiConsole.MarkupLine("[dim]No changes.[/]");
|
|
return;
|
|
}
|
|
|
|
// 5. Ask to push
|
|
AnsiConsole.MarkupLine("[green]Changes detected.[/]");
|
|
var push = AnsiConsole.Confirm("Push changes to server?");
|
|
if (!push) { AnsiConsole.MarkupLine("[dim]Discarded.[/]"); return; }
|
|
|
|
await AnsiConsole.Status().StartAsync("Pushing…", async _ =>
|
|
await ctx.Api.PutNoteAsync(slug, edited));
|
|
|
|
AnsiConsole.MarkupLine($"[green]✓[/] {slug} updated");
|
|
}
|
|
|
|
private static string ResolveEditor()
|
|
{
|
|
// Priority: $EDITOR → $VISUAL → VS Code → nano → vi
|
|
var env = Environment.GetEnvironmentVariable("EDITOR")
|
|
?? Environment.GetEnvironmentVariable("VISUAL");
|
|
if (env is not null) return env;
|
|
|
|
foreach (var candidate in new[] { "code", "nano", "vi", "notepad" })
|
|
if (IsOnPath(candidate)) return candidate;
|
|
|
|
return "nano";
|
|
}
|
|
|
|
private static bool IsOnPath(string name)
|
|
{
|
|
try
|
|
{
|
|
var result = Process.Start(new ProcessStartInfo
|
|
{
|
|
FileName = OperatingSystem.IsWindows() ? "where" : "which",
|
|
Arguments = name,
|
|
RedirectStandardOutput = true,
|
|
UseShellExecute = false,
|
|
});
|
|
result?.WaitForExit();
|
|
return result?.ExitCode == 0;
|
|
}
|
|
catch { return false; }
|
|
}
|
|
|
|
private static string HashContent(string content) =>
|
|
Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(content))).ToLower();
|
|
}
|