📄 OutGridTree.Window/RpcService.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Management.Automation;
using System.Text.Json;
using System.Threading;

namespace OutGridTree.Window;

internal sealed class RpcService
{
    public event Action<string>? TitleReceived;
    public event Action<OutputMode>? OutputModeReceived;
    public event Action<IEnumerable<string>>? HeadersReceived;
    public event Action<Row?>? RecordReceived;

    private NamedPipeClientStream? pipe;
    private readonly CancellationTokenSource cts = new();
    private Thread? thread;

    public void Start(string pipeName)
    {
        pipe = new(".", pipeName, PipeDirection.InOut);
        thread = new(Run);
        thread.Start();
    }

    private void Run()
    {
        if (pipe is null)
        {
            return;
        }

        pipe.Connect();
        var reader = new StreamReader(pipe);
        while (reader.ReadLine(cts.Token) is string line)
        {
            if (line.Split(": ", 2) is not [var header, var body])
            {
                continue;
            }
            if (header is "TITLE")
            {
                TitleReceived?.Invoke(body);
            }
            else if (header is "OUTPUT_MODE")
            {
                OutputModeReceived?.Invoke(Enum.Parse<OutputMode>(body));
            }
            else if (header is "HEADERS")
            {
                var headers = JsonSerializer.Deserialize<string[]>(body);
                if (headers is null)
                {
                    continue;
                }
                HeadersReceived?.Invoke(headers);
            }
            else if (header is "RECORD")
            {
                var elements = JsonSerializer.Deserialize<string[]>(body);
                if (elements is null)
                {
                    continue;
                }
                RecordReceived?.Invoke(new(elements[..^1], PSSerializer.Deserialize(elements[^1]) as PSObject));
            }
        }
    }

    public void SendSelected(IEnumerable<int> selectedIndices)
    {
        if (pipe is null)
        {
            return;
        }

        cts.Cancel();
        var writer = new StreamWriter(pipe);
        writer.WriteLine($"SELECT: {JsonSerializer.Serialize(selectedIndices)}");
        writer.Flush();
    }

    public void Stop()
    {
        cts.Cancel();
        thread?.Join();
        pipe?.Dispose();
    }
}

file static class StreamReaderExtensions
{
    public static string? ReadLine(this StreamReader reader, CancellationToken cancellationToken)
    {
        var valueTask = reader.ReadLineAsync(cancellationToken);
        if (valueTask.IsCompletedSuccessfully)
        {
            return valueTask.Result;
        }
        var task = valueTask.AsTask();
        task.Wait(cancellationToken);
        return task.Result;
    }
}