-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2dd28b8
commit 05f1ee6
Showing
4 changed files
with
85 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,65 @@ | ||
using Spectre.Console; | ||
using System.Diagnostics; | ||
using System.Threading.Channels; | ||
using Spectre.Console; | ||
using Spectre.Console.Advanced; | ||
using Spectre.Console.Rendering; | ||
using Vezel.Cathode.Processes; | ||
|
||
static class TerminalExtensions | ||
static class TerminalUtils | ||
{ | ||
public static string ToAnsi(this IRenderable @this) => | ||
AnsiConsole.Console.ToAnsi(@this); | ||
|
||
public static StaleChildProcess ShellExec(Context ctx, NPath command, IReadOnlyList<string> args) | ||
{ | ||
var captures = Channel.CreateUnbounded<LineCapture>(new UnboundedChannelOptions { SingleReader = true }); | ||
|
||
if (ShellExecUtility.ConfigureProcessExitToAlsoKillChildProcesses() && ctx.IsVerbose) | ||
ctx.VerboseLine("Configuring OS to kill child processes if the current process exits"); | ||
|
||
// TODO: consider checking if it's a console app | ||
// (see IsWindowsApplication at PowerShell\src\System.Management.Automation\engine\NativeCommandProcessor.cs:1199) | ||
|
||
var (commandPath, extraArgs) = ShellExecUtility.ResolveShellCommand(command); | ||
if (extraArgs.Any()) | ||
args = [..extraArgs, ..args]; | ||
|
||
var process = new ChildProcessBuilder() | ||
.WithFileName(commandPath) | ||
.WithArguments(args) | ||
.WithRedirections(false, true, true) | ||
.WithCreateWindow(false) | ||
.WithWindowStyle(ProcessWindowStyle.Hidden) | ||
.WithCancellationToken(ctx.CancelToken) | ||
.WithThrowOnError(false) | ||
.Run(); | ||
|
||
var open = 0; | ||
|
||
async Task CaptureLines(TextReader reader, bool isStdErr, CancellationToken stop) | ||
{ | ||
Interlocked.Increment(ref open); | ||
|
||
while (!stop.IsCancellationRequested) | ||
{ | ||
var line = await reader.ReadLineAsync(stop); | ||
if (line == null) | ||
break; | ||
|
||
await captures.Writer.WriteAsync(new LineCapture(isStdErr, DateTime.Now, line), stop); | ||
} | ||
|
||
if (Interlocked.Decrement(ref open) == 0) | ||
{ | ||
// use the non-throwing TryComplete here to avoid a potential race in this function among the two | ||
// readers and the cancel token causing a double-complete. | ||
captures.Writer.TryComplete(); | ||
} | ||
} | ||
|
||
ctx.LongTasks.Run($"StandardOut reader for pid {process.Id}", stop => CaptureLines(process.StandardOut.TextReader, false, stop)); | ||
ctx.LongTasks.Run($"StandardError reader for pid {process.Id}", stop => CaptureLines(process.StandardError.TextReader, true, stop)); | ||
|
||
return new(commandPath, args, captures.Reader, process.Id, process.Completion); | ||
} | ||
} |