Skip to content

Make the output of script block handler formatted and rendered to the console? #1302

Open
@gogsbread

Description

@gogsbread

When using programs(less, fzf etc.), that emit to alternate buffers, inside ScriptBlock, there is no display of the program buffer.
For Eg:

Set-PSReadLineKeyHandler -Chord Ctrl+o -ScriptBlock { less foo.txt }
Set-PSReadLineKeyHandler -Chord Ctrl+o -ScriptBlock { find . | fzf }

Show no output

Couple of questions
a) Why is that?
b) What should I do if I need to display them? (Start separate process and redirect stdout from that process?)

Activity

gogsbread

gogsbread commented on Jan 20, 2020

@gogsbread
Author

Found my own answers. Documenting for reference
a) PsReadLine seems to handle pipe outputs differently using [Microsoft.PowerShell.PSConsoleReadLine] primitives. So a normal command like Set-PSReadLineKeyHandler -Chord Ctrl+o -ScriptBlock { Write-Output "Foo" } won't work.
b) Starting a new process and waiting for that process to finish shows the alternate buffer from that process. From the example above
Set-PSReadLineKeyHandler -Chord Ctrl+o -ScriptBlock { Start-Process -FilePath /usr/bin/less -ArgumentList ./foo.txt -Wait } will work

lzybkr

lzybkr commented on Jan 21, 2020

@lzybkr
Contributor

That's a reasonable workaround, but it shouldn't be necessary. Unrelated, but note that less does not use the alternate screen buffer.

The issue is how PowerShell implements ScriptBlock.Invoke which redirects stdout to capture the output. less honors this redirection and does not run interactively.

I think this is fixable in PSReadLine. This line would need to be changed, but after some quick experiments, I'm not sure how exactly. Maybe @daxian-dbw has an idea.

daxian-dbw

daxian-dbw commented on Jan 21, 2020

@daxian-dbw
Member

The only way I can think of is changing scriptBlock.Invoke(k, arg); to the following:

PowerShell.Create(RunspaceMode.CurrentRunspace)
    .AddScript(scriptBlock.ToString())
        .AddArgument(k)
        .AddArgument(arg)
    .AddCommand("Out-Default")
    .Invoke();

By explicitly set the downstream cmdlet to be Out-Default, we force the stdout to not be redirected when executing scriptBlock.ToString().

However, it will be a breaking change if we go with this because the scriptBlock may be a closure created by GetNewClosure(), and scriptBlock.ToString() will lose the information about the closure.

lzybkr

lzybkr commented on Jan 21, 2020

@lzybkr
Contributor

Can you use Invoke-Command instead?

daxian-dbw

daxian-dbw commented on Jan 21, 2020

@daxian-dbw
Member

Yup, that would work for the closure:

PowerShell.Create(RunspaceMode.CurrentRunspace)
    .AddCommand("Invoke-Command")
        .AddParameter("ScriptBlock", scriptBlock)
        .AddParameter("ArgumentList", new[] { k, arg })
    .AddCommand("Out-Default")
    .Invoke();

It will still be a behavior change though, as all the output of the script block will be written out to the console. So for any existing script block handlers that accidentally leak to the output pipe, the user will see those output written out. If we change to this, then the guideline should be don't write anything to the output pipe in the handler. Otherwise, it will go through the formatting code and slow down the handler.

Any other side effects you can think of, @lzybkr?

SeeminglyScience

SeeminglyScience commented on Jan 21, 2020

@SeeminglyScience
Contributor

It will still be a behavior change though, as all the output of the script block will be written out to the console. So for any existing script block handlers that accidentally leak to the output pipe, the user will see those output written out.

Or purposefully. I've been treating them like void methods, so typing anything is probably going to result in a large wall of StringBuilder spam for me. Not sure how typical that is though.

daxian-dbw

daxian-dbw commented on Jan 21, 2020

@daxian-dbw
Member

Alright, reopen this issue as it looks like something we are interested in improving.

lzybkr

lzybkr commented on Jan 21, 2020

@lzybkr
Contributor

The behavior change is probably OK. Arguments in favor:

  • It's a major version change.
  • It only affects the interactive experience.
  • It only affects custom key bindings, and likely few at that.
  • The workaround isn't that obvious.

I was worried it might affect modules like PSFzf but I don't think it will as that module uses the .Net api to launch fzf.

changed the title [-]Question: Programs that use alternate buffers don't output inside scriptblock `Set-PSReadLineKeyHandler`[/-] [+]Make the output of script block handler formatted and rendered to the console?[/+] on Jan 21, 2020
daxian-dbw

daxian-dbw commented on Jan 21, 2020

@daxian-dbw
Member

Note that PSReadLine doesn't know there is text rendered on the console, so when typing anything after the handler execution, the new characters will be rendered at the original cursor position and overwrite the output from the handler. This will be annoying.
It's already the case today when commands like Write-Host is used in the handler, but overall it's not too bad given that it behaves like a void method today. Making this change will have it look like we encourage rendering the handler output to the console.

image

msftrncs

msftrncs commented on Jan 24, 2020

@msftrncs
Collaborator

Wouldn't the correct behavior for the OP's purpose be to:

  1. Save the current command.
  2. Cancel the command to clear the prompt. (RevertLine())
  3. Enter the desired command on the commandline via SelfInsert() or similar.
  4. Execute the command (AcceptLine()).
  5. Restore the original command.

This way keybinding handlers can remain void. The desired command could even be a [psconsolereadline]::method() if needed, but should, if complexity requires, be implemented as a PowerShell function. This would resolve the issue of output in the middle of editing.

I'll admit, I do not know if all the required services to implement this are available.

lzybkr

lzybkr commented on Jan 24, 2020

@lzybkr
Contributor

Running via AcceptLine might be an option, but there are likely unintended consequences like unwanted commands in your history.

Also - the command you launch would have to be the last (only?) command in your scriptblock - you couldn't do anything afterwards which could be a deal breaker for some useful extensions.

SeeminglyScience

SeeminglyScience commented on Jan 24, 2020

@SeeminglyScience
Contributor

If it's determined to be the responsibility of the handler writer, they could just pipe to Out-Default themselves. The biggest issue with that currently is that PSRL doesn't notice the cursor position change. So the next key press moves the cursor position back to where PSRL thinks it should be.

FWIW, the handlers being invoked with a null pipe is consistent with similar API's like event subscribers and breakpoint actions.

Running via AcceptLine might be an option, but there are likely unintended consequences like unwanted commands in your history.

For AcceptLine to work, wouldn't the key handler have to return before any action would actually take place? Meaning you'd have to discard the current input completely? Or is that what you're referring to with the next sentence?

lzybkr

lzybkr commented on Jan 24, 2020

@lzybkr
Contributor

Yes, I guess I was hinting that you'd have to return, but a function like AcceptAndGetNext could be used to restore the current input.

isti115

isti115 commented on Dec 9, 2020

@isti115

Hmm, PSFzf doesn't want to play nice for me under linux under PowerShell Core 7 and I think that it would be awesome if interactive scripts could be invoked using hotkeys, so I would appreciate if this issue could be solved in a way that doesn't require external tools.

Edit: Hmm, it seems like I've managed to solve it for now, but this doesn't seem to be the cleanest workaround:

Set-PSReadLineKeyHandler -Chord Ctrl+f -ScriptBlock {
  # Start-Process -FilePath fzf -Wait

  $history = Get-Content -Raw (Get-PSReadLineOption).HistorySavePath

  $p = [System.Diagnostics.Process]@{StartInfo = @{
    FileName = "fzf";
    Arguments = "--tac --no-sort --bind tab:toggle-sort --height `"25%`"";
    RedirectStandardInput = $true;
    RedirectStandardOutput = $true;
  }}

  $p.Start()
  $p.StandardInput.Write($history)
  $stdout = $p.StandardOutput.ReadToEnd()
  $p.WaitForExit()

  [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
  [Microsoft.PowerShell.PSConsoleReadLine]::Insert($stdout.Trim())
}

rafaeloledo

rafaeloledo commented on Dec 12, 2023

@rafaeloledo

I adapted the @isti115 workaround, the behavior is still the same here. PSReadLineKeyHandler could have different default values in his context in further versions to prevent user manipulanting processes under the nail like this example.

function MyFzf {
    $p = [System.Diagnostics.Process]@{StartInfo = @{
      FileName = "fzf";
      Arguments = @(
        "--layout=reverse",
        "--height=85%",
        "--border",
        "--no-sort",
        "--prompt=`"Search Directory> `""
        "--bind=`"ctrl-d:preview-page-down`"",
        "--bind=`"ctrl-u:preview-page-up`"",
        "--preview=`"bat --plain --color=always {}`""
        "--preview-window=`"110`""
        "--color=`"bg+:#293739,bg:#1B1D1E,border:#808080,spinner:#E6DB74,hl:#7E8E91,fg:#F8F8F2,header:#7E8E91,info:#A6E22E,pointer:#A6E22E,marker:#F92672,fg+:#F8F8F2,prompt:#F92672,hl+:#F92671`""
      );
      RedirectStandardOutput = $true;
      WorkingDirectory = $PWD;
    }}

    $p.Start()
    $result = $p.StandardOutput.ReadLine()
    $p.WaitForExit()

    [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
    [Microsoft.PowerShell.PSConsoleReadLine]::Insert($result)
    [Microsoft.PowerShell.PSConsoleReadLine]::ClearScreen()
 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-QuestionFor non-bug questions or discussion.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @daxian-dbw@gogsbread@lzybkr@isti115@SeeminglyScience

        Issue actions

          Make the output of script block handler formatted and rendered to the console? · Issue #1302 · PowerShell/PSReadLine