PowerShell / ConsoleGuiTools

Modules that mix PowerShell and GUIs/CUIs!
https://www.powershellgallery.com/packages/Microsoft.PowerShell.ConsoleGuiTools
MIT License
776 stars 59 forks source link

OCGV: Sometimes exits as soon as arrow key is pressed #167

Closed Tigger2014 closed 1 year ago

Tigger2014 commented 2 years ago

Prerequisites

Steps to reproduce

This one seems a bit random but ive managed to isolate it a block of code i think can trigger it sometimes. It might take a good few runs of the below code to get it to happen.

$list=@(1..100)
$count =0
do{
[PSCustomObject]$platform = $list  | Out-consoleGridView -verbose -OutputMode Single -Title $count
$platform.name.split()
Write-Host "fgfg"
$count ++
}
while ($count -lt 3)
Write-Host "gh"

I know the split will error on this by design as it seems to help trigger this more often.

What should happen is when running this loop with F8 in VS code sometime the first iteration will skip when you try to arrow down to select one in the list.

Version used is master compiled from source but same exact thing happens with 0.6.2

Expected behavior

OCGV not to skip

Actual behavior

occasionally OCGV exits without allowing you to select anything

Error details

Exception             : 
    Type        : System.Management.Automation.RuntimeException
    ErrorRecord : 
        Exception             : 
            Type    : System.Management.Automation.ParentContainsErrorRecordException
            Message : You cannot call a method on a null-valued expression.
            HResult : -2146233087
        CategoryInfo          : InvalidOperation: (:) [], ParentContainsErrorRecordException
        FullyQualifiedErrorId : InvokeMethodOnNull
        InvocationInfo        : 
            ScriptLineNumber : 3
            OffsetInLine     : 1
            HistoryId        : -1
            Line             : $platform.name.split()

            PositionMessage  : At line:3 char:1
                               + $platform.name.split()
                               + ~~~~~~~~~~~~~~~~~~~~~~
            CommandOrigin    : Internal
        ScriptStackTrace      : at <ScriptBlock>, <No file>: line 3
    TargetSite  : System.Object CallSite.Target(System.Runtime.CompilerServices.Closure, 
System.Runtime.CompilerServices.CallSite, System.Object)
    Message     : You cannot call a method on a null-valued expression.
    Data        : System.Collections.ListDictionaryInternal
    Source      : Anonymously Hosted DynamicMethods Assembly
    HResult     : -2146233087
    StackTrace  : 
   at CallSite.Target(Closure , CallSite , Object )
   at System.Management.Automation.Interpreter.DynamicInstruction`2.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
CategoryInfo          : InvalidOperation: (:) [], RuntimeException
FullyQualifiedErrorId : InvokeMethodOnNull
InvocationInfo        : 
    ScriptLineNumber : 3
    OffsetInLine     : 1
    HistoryId        : -1
    Line             : $platform.name.split()

    PositionMessage  : At line:3 char:1
                       + $platform.name.split()
                       + ~~~~~~~~~~~~~~~~~~~~~~
    CommandOrigin    : Internal
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 3

Environment data

Name                           Value
----                           -----
PSVersion                      7.2.6
PSEdition                      Core
GitCommitId                    7.2.6
OS                             Darwin 21.6.0 Darwin Kernel Version 21.6.0: Wed Aug 10 14:28:23 PDT 2022; root:xnu-8020.141.5~2…
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Version

0.6.3

Visuals

Screen Recording 2022-08-21 at 21 49 08

tig commented 2 years ago

I've tried dozens of times and I can't get this to repro.

Can you still repro it with what's in master?

Note that because of #93 the initial behaviour is different than when you first reported this and it might be masking the issue?

Tigger2014 commented 2 years ago

Ive just cloned master and built the module, I can still trigger this, Not sure if its relevant but im using a mac.

Ive triggered it by doing the following New pwsh console, Import module run script above, spam the esc key more times than required to exit the loop. Run the loop again, the first try should skip, If in-fact it seems if the esc key is hit too many times when displaying OCGV it will cause the OCGV to skip.

This is not whats triggered it in larger scripts this was a part of however this does seem to somewhat reliably produce the bug.

Hope this helps, If i can be of anymore assistance please do let me know

tig commented 2 years ago

I cannot repro this on Debian in WSL or Windows. I do not have access to a Mac.

  1. New pwsh
  2. Paste script above
  3. As soon as OCGV shows, hit ESC a bunch of times
  4. OCGV will show 3 times in succession
  5. Paste script above again
    1. OCGV will show 3 times in succession

Is this how you repo it?

Tigger2014 commented 2 years ago

Thats correct, after 3, if you try to scroll down to select an item in OCGV it should skip

tig commented 2 years ago

Still can't repo on Windows or Debian (Windows Terminal). Also tried within VS Code's Terminal.

image

@bdisp or @tznind - do either of you have a Mac you can try to repro this on?

Tigger2014 commented 2 years ago

Just managed to trigger this on WSL in Windows Terminal from master

----                           -----
PSVersion                      7.2.6
PSEdition                      Core
GitCommitId                    7.2.6
OS                             Linux 5.10.102.1-microsoft-standard-WSL2 #1 SMP Wed Mar 2 00:30:59 UTC 2022
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Binary     0.7.2                 Microsoft.PowerShell.ConsoleGuiToo… {Out-ConsoleGridView, ocgv}

WSL

steps to repro

Paste in the code, hit ESC 3 or 4 time rapidly during run 0, you should then find when the next OCGV pop ups, try using arrow keys to move down the list. It should skip and go to the next run without letting you select anything.

tig commented 2 years ago

Got it. Repo'd on Debian. But not Windows.

I have no ideas on what the cause could be. The stack trace you posted points to something involving PowerShell:

Exception             : 
    Type        : System.Management.Automation.RuntimeException
    ErrorRecord : 
        Exception             : 
            Type    : System.Management.Automation.ParentContainsErrorRecordException
            Message : You cannot call a method on a null-valued expression.
            HResult : -2146233087
        CategoryInfo          : InvalidOperation: (:) [], ParentContainsErrorRecordException
        FullyQualifiedErrorId : InvokeMethodOnNull
        InvocationInfo        : 
            ScriptLineNumber : 3
            OffsetInLine     : 1
            HistoryId        : -1
            Line             : $platform.name.split()

How did you collect that stack trace?

Tigger2014 commented 2 years ago

Glad you managed to reproduce it!

The stack was from Get-Error not entirely sure it relates to this problem vs something else.

BDisp commented 2 years ago

I tested on Ubuntu (WSL) and I can reproduce the error, but without open the warning window. By symptoms, it seems that the OCGV is not calling Application.Shutdown when the ESC key is pressed. On Windows there are no problem with this but with Linux and Mac, probably, will causing the OCGV not totally destroyed and re-created. The main cause for the ESC is not working properly in Linux is because of the delay used in ncurses to check if there some other escapes sequences associated. That why the OCGV is still opened after pressing three times. Another issue I'm facing is the OCGV changing his color when I re-run the script again.

tig commented 2 years ago

Another issue I'm facing is the OCGV changing his color when I re-run the script again.

@BDisp Do you think this is related? I don't see it. Should it be a separate issue?

tig commented 2 years ago

I tested on Ubuntu (WSL) and I can reproduce the error, but without open the warning window. By symptoms, it seems that the OCGV is not calling Application.Shutdown when the ESC key is pressed. On Windows there are no problem with this but with Linux and Mac, probably, will causing the OCGV not totally destroyed and re-created. The main cause for the ESC is not working properly in Linux is because of the delay used in ncurses to check if there some other escapes sequences associated. That why the OCGV is still opened after pressing three times.

Odd that you're not seeing Application.Shutdown being called...


...
            // Run the GUI.
            Application.Run();
            Application.Shutdown();

// Called when ESC is pressed:
        private void Close()
        {
            _cancelled = true;
            Application.RequestStop();
        }
BDisp commented 2 years ago

Another issue I'm facing is the OCGV changing his color when I re-run the script again.

@BDisp Do you think this is related? I don't see it. Should it be a separate issue?

I tested with the above code provided. Should that code explicit call Application.Shutdown? Since the OCGV already call, sorry my mistake.

tig commented 2 years ago

Another issue I'm facing is the OCGV changing his color when I re-run the script again.

@BDisp Do you think this is related? I don't see it. Should it be a separate issue?

I tested with the above code provided. Should that code explicit call Application.Shutdown? Since the OCGV already call, sorry my mistake.

That code is the code in OCGV; I pasted it to show why I think OCGV is actually calling Shutdown.

BDisp commented 2 years ago

That code is the code in OCGV; I pasted it to show why I think OCGV is actually calling Shutdown.

$list=@(1..100)
$count =0
do{
[PSCustomObject]$platform = $list  | Out-consoleGridView -verbose -OutputMode Single -Title $count
$platform.name.split()
Write-Host "fgfg"
$count ++
}
while ($count -lt 3)
Write-Host "gh"

Sorry, I meaning if this code should call Application.Shutdown, since it's calling OCGV three times?

tig commented 2 years ago

Sorry, I meaning if this code should call Application.Shutdown, since it's calling OCGV three times?

No, OCGV is a PowerShell Cmdlet that calls Application.Shutdown (per the code I pasted above) each time.

I just instrumented OCGV and using this version of the script, it's clear Shutdown is being called. So that's not the cause of this issue.


Write-Host "start of script"
$list=@(1..100)
$count =0
do{
$list  | Out-consoleGridView -Verbose -OutputMode Single -Title $count
#$platform.name.split()
Write-Host "ocgv exited"
$count ++
}
while ($count -lt 3)
Write-Host "end of script"
image
tig commented 2 years ago

@Tigger2014 - did this issue ever reproduce for you when the ESC key was not involved?

Do you have repro that involves ONLY the arrow key causing the cmdlet to exit?

Tigger2014 commented 2 years ago

@Tigger2014 - did this issue ever reproduce for you when the ESC key was not involved?

Do you have repro that involves ONLY the arrow key causing the cmdlet to exit?

I've seen it happen when i think the esc key wasn't involved but it's not code i can share unfortunately. Im also not 100% sure ESC wasn't touched.

tig commented 2 years ago

Ok, I'll keep poking around.

My current suspicion is that @BDisp was on the right track and this has something to do with the way the Terminal.Gui ncurses driver handles the ESC key.

So, if you figure out how to repro it WITHOUT involving the ESC key please yell!

tig commented 2 years ago

The other potential culprit is the "hack" in OCGV that resets the "Application Cursor" by emitting an ANSI ESC sequence when it closes:

https://github.com/PowerShell/GraphicalTools/blob/76dbfcef5c099b8e4a73a55fde6e3f0ff37fb15b/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs#L403

The thought (which may be way off base) is that this ESC sequence is getting echo'd back and causing OCGV to think the user pressed the ESC key.

BDisp commented 2 years ago

@tig see the End method on CursesDriver where I use some escape sequences to restore the terminal on exit. Another solution is OCGV using NetDriver on Linux and Mac.

BDisp commented 2 years ago

The other potential culprit is the "hack" in OCGV that resets the "Application Cursor" by emitting an ANSI ESC sequence when it closes:

https://github.com/PowerShell/GraphicalTools/blob/76dbfcef5c099b8e4a73a55fde6e3f0ff37fb15b/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs#L403

The thought (which may be way off base) is that this ESC sequence is getting echo'd back and causing OCGV to think the user pressed the ESC key.

Thinking better I bet that this is really the culprit, because the CursesDriver already do all clean up on exit to restore the terminal. Sending more escape sequences after exit will getting echoed back and reinitialize the CursesDriver again, like it never close. Try remove it.

Tigger2014 commented 2 years ago

The other potential culprit is the "hack" in OCGV that resets the "Application Cursor" by emitting an ANSI ESC sequence when it closes: https://github.com/PowerShell/GraphicalTools/blob/76dbfcef5c099b8e4a73a55fde6e3f0ff37fb15b/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs#L403

The thought (which may be way off base) is that this ESC sequence is getting echo'd back and causing OCGV to think the user pressed the ESC key.

Thinking better I bet that this is really the culprit, because the CursesDriver already do all clean up on exit to restore the terminal. Sending more escape sequences after exit will getting echoed back and reinitialize the CursesDriver again, like it never close. Try remove it.

Just commented out that line, Still happens unfortunately

Tigger2014 commented 2 years ago

Further to this, not sure it helps much but swapping ESC for a for example, i cant reproduce this. https://github.com/PowerShell/GraphicalTools/blob/master/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs#L214

Seems this could be related to your suggestion

. The main cause for the ESC is not working properly in Linux is because of the delay used in ncurses to check if there some other escapes sequences associated.

BDisp commented 2 years ago

Seems this could be related to your suggestion

. The main cause for the ESC is not working properly in Linux is because of the delay used in ncurses to check if there some other escapes sequences associated.

For sure that is. Unfortunately, that interval delay is necessary to unix process all the Ansi Esc Sequence. If no more char exist after the delay then assumes that only the Esc key was pressed and process it alone, otherwise continuous reading all chars to get a key combination, like Alt, Ctrl-Alt or a mouse event, whatever. Maybe lowing down the delay to check if it work without affect his functionality.

tznind commented 2 years ago

Found these docs (see below) on the feature I think you are talking about @BDisp. Looks like you can change the setting at runtime with an Environment Variable e.g.

export ESCDELAY=1

Set time taken to release an Escape keystroke to 1ms

I tested setting it to 2000 and saw Esc being delayed so it seems to work.

Although from the docs docs it sounds like the same setting interacts with mouse events:

Environment

The following environment symbols are useful for customizing the runtime behavior of the ncurses library. The most important ones have been already discussed in detail.

[...]

ESCDELAY Specifies the total time, in milliseconds, for which ncurses will await a character sequence, e.g., a function key. The default value, 1000 milliseconds, is enough for most uses. However, it is made a variable to accommodate unusual applications. The most common instance where you may wish to change this value is to work with slow hosts, e.g., running on a network. If the host cannot read characters rapidly enough, it will have the same effect as if the terminal did not send characters rapidly enough. The library will still see a timeout.

Note that xterm mouse events are built up from character sequences received from the xterm. If your application makes heavy use of multiple-clicking, you may wish to lengthen this default value because the timeout applies to the composed multi-click event as well as the individual clicks.

In addition to the environment variable, this implementation provides a global variable with the same name. Portable applications should not rely upon the presence of ESCDELAY in either form, but setting the environment variable rather than the global variable does not create problems when compiling an application.

https://linux.die.net/man/3/ncurses

BDisp commented 2 years ago

I submitted the pr https://github.com/gui-cs/Terminal.Gui/pull/1970 which I think fixes the ESC delay issue. I also think that the title of this issue isn't really an issue. What happen when the OCGV exits on a arrow key press is because there was a pending ESC key pressed to be processed and pressing the arrow key will awake the ESC, processing it first causing the app exit. So, the main problem here is the ESC delay issue on the CursesDriver which affect Linux and Mac OS.

Tigger2014 commented 2 years ago

@BDisp If you happen to have a build of the module with your changes happy to test and see if it does indeed fix this issue.

BDisp commented 2 years ago

@BDisp If you happen to have a build of the module with your changes happy to test and see if it does indeed fix this issue.

I only have this branch https://github.com/BDisp/GraphicalTools/tree/solution-with-gui-project, where I added the Terminal.Gui project with the "cursesdriver-esc-delay-fix" branch, instead of the nuget package. You must have the clone of the Terminal.Gui with the pr https://github.com/gui-cs/Terminal.Gui/pull/1970 and build.

Tigger2014 commented 2 years ago

Yep did that and can confirm this fixes it

tig commented 1 year ago

This issue is fixed in Terminal.Gui v1.8+. PR #184 will close it.