Open tig opened 3 years ago
@mklement0,
tput reset # To make PSReadLine work properly again, notably up- and down-arrow.
What's up with this? What does tput reset
do? Shouldn't terminal.gui do this?
I hope that could fix the #931 issue.
@tig:
tput reset
resets the terminal to its default state.
Yes, such a call shouldn't be necessary - Terminal.Gui should restore the previous state - though I'm not 100% certain if PowerShell, specifically its PSReadLine module, is completely blameless here, as I've seen other strange behaviors after running programs that don't seem to surface in other shell.
Actually, resetting the terminal with tput init
is better, because it doesn't clear the screen.(It is POSIX-mandated, though what it actually does is left to the implementation).
It is effective in preventing subsequent command-line editing problems on both macOS and Ubuntu, so it might indeed help in WSL too.
The problem only occurs in-process in PowerShell and is not related to the PSReadLine
module, as it turns out (running Remove-Module PSReadLine
first still exhibits the same up/down-arrow problems after).
Possibly related: there's a longstanding bug report that describes the opposite problem (up/down-arrow keys not working in a utility that displays an alternate screen): https://github.com/PowerShell/PowerShell/issues/7375
If you call the code via PowerShell's CLI - pwsh -noprofile -file script.ps1
- it works fine even without tput init
, including when calling from, say, bash
.
@mklement0 Do you know where I can find the source to tput
?
I have a feeling this is relevant:
I thought I had removed this and moved it into gui.cs already.
Console.Write("\u001b[?1h");
I can confirm that replacing tput init
with "$([char] 0x1b)[?1h"
(simpler PS Core alternative: "`e[?1h"
) in the sample code enough is sufficient.
Examining what escape sequences tput init
emits in practice is elusive, because trying to capture them seems to change what is being emitted:
PS> (tput init) -replace '\e', '\u001b'
\u001b[!p\u001b[?3;4l\u001b[4l\u001b>
Even executing (tput init)
(wrapping in parentheses) makes the command no longer effective; not only that, it actively triggers the symptom, even if the arrow keys worked fine at the time.
I don't think the implementation of tput
itself will tell us much, because it looks up the specific escape sequences to use in the terminfo "database" (a 2-level file hierarchy at /usr/share/terminfo/*/*
) based on the predefined TERM
environment variable, which indicate the terminal type being emulated. On macOS, that variable contains xterm-256
, which results in file /usr/share/terminfo/78/xterm-256color
getting consulted.
Based on man tput
and man terminfo
, tput init
does the following:
init If the terminfo database is present and an entry for the user's
terminal exists (see -Ttype, above), the following will occur:
(1) if present, the terminal's initialization strings will be
output as detailed in the terminfo(5) section on Tabs and
Initialization,
(2) any delays (e.g., newline) specified in the entry will be
set in the tty driver,
(3) tabs expansion will be turned on or off according to the
specification in the entry, and
(4) if tabs are not expanded, standard tabs will be set
(every 8 spaces).
If an entry does not contain the information needed for any of
the four above activities, that activity will silently be
skipped.
where terminfo(5) (man terminfo
) states (identifiers such as is1
, ... are terminal capabilities, listed in the same page):
output is1 is2
set the margins using
mgc, smgl and smgr
set tabs using
tbc and hts
print the file
if
and finally
output is3.
The only sequence escape which worked to me, after testing many of them, was as shown in my PR #953.
On enter \x1b[?1049l
On exit \x1b[?1049h
The portable equivalent of VT100 escape sequence \x1b[?1h
(which does work for macOS and Ubuntu terminals, however) appears to be tput rmkx
; that is, the keypad_local
aka rmkx
TermInfo capability ("leave 'keyboard_transmit' mode") - the inverse is keypad_xmit
aka smkx
("enter 'keyboard_transmit' mode").
@BDisp, I get similar escape sequences via tput
, though the caveat is that I don't fully trust the captured versions:
PS> (tput rmkx) -replace '\e', '\x1b'; (tput smkx) -replace '\e', '\x1b'
\x1b[?1l\x1b>
\x1b[?1h\x1b=
@mklement0 that great, thanks. Work better than the other because the prompt is maintained sequential instead pushing to the bottom of the terminal. With the Mac
I don't have this hang on the terminal. It's only happens with the Linux
.
What the difference in the end of the sequences?
>
=
@BDisp, for maximum robustness we shouldn't hard-code these sequences, as they may vary by terminal emulation. - though perhaps, pragmatically speaking, that is fine nonetheless, given that terminal emulators today mostly seem to be Xterm-compatible (I don't know, however).
If we do have to make this terminal-agnostic: While we don't want to have to create an expensive tput
child process, the question is whether ncurses allows us to query and submit the rmkx
escape sequence in-process.
As for what >
and =
mean:
At the moment I'm not aware of a reverse Terminfo lookup, but the table of VT100 escape sequences at http://ascii-table.com/ansi-escape-sequences-vt-100.php - whose identifiers do NOT respond to the TermInfo capability names - suggest the following:
Esc= ... Set alternate keypad mode | DECKPAM Esc> ... Set numeric keypad mode | DECKPNM
That is, they seem to act on the keys of the numeric keypad.
@mklement0 I ended up to use like https://github.com/migueldeicaza/gui.cs/issues/418#issuecomment-707648746
\x1b[?1h
\x1b[?1l
as documented at http://ascii-table.com/ansi-escape-sequences-vt-100.php
@mklement0 I'm experiencing another strange behavior with both escapes sequences on screen resizing as it's not refreshing well letting the screen all messed up. Do you know why this happens?
One clue is that the GUI
does not receive notification of changes in relation to the screen size, always keeping the number of columns and rows unchanged.
@BDisp, I think that problem is not related to the escape sequences we've discussed per se:
Even without these sequences, I see the following behavior:
macOS:
compiled executable:
Terminal.app: OK
iTerm2.app: mostly OK while the app is running (there are aritifacts while resizing, but on releasing the mouse it redraws fine), but leaves remnants of the alternate screen after exiting
PowerShell script: broken in both Terminal.app and iTerm.app, though with different symptoms:
Linux (Ubuntu 18.04):
compiled executable: OK
PowerShell script: broken (doesn't seem to be aware of the resizing)
So it seems that PowerShell is at least part of the problem - are you saying that with compiled executables you only started seeing the problem after emitting the escape sequence on shutdown and therefore only in subsequent runs?
Also, I've noticed that both terminal apps on macOS apparently don't draw the application on the alternate screen: you can actually scroll up while the application is running (using the terminal app's scroll bars) and see the previous terminal output. Is this a known limitation?
Furthermore, the radio buttons and checkboxes in the example application aren't drawing, in both terminal apps.
iTerm2.app: mostly OK while the app is running (there are aritifacts while resizing, but on releasing the mouse it redraws fine), but leaves remnants of the alternate screen after exiting
I've noticed too. It's annoying.
So it seems that PowerShell is at least part of the problem - are you saying that with compiled executables you only started seeing the problem after emitting the escape sequence on shutdown and therefore only in subsequent runs?
I run with dotnet ./app.dll and I seeing the problem while the app is open and resizing the screen. I can exit and reopen again but the messed screen on resizing is worse than the terminal hang.
Also, I've noticed that both terminal apps on macOS apparently don't draw the application on the alternate screen: you can actually scroll up while the application is running (using the terminal app's scroll bars) and see the previous terminal output. Is this a known limitation?
I noticed that too and is very annoying too on both Terminal and iTerm2.
Furthermore, the radio buttons and checkboxes in the example application aren't drawing, in both terminal apps.
Yes that true unfortunately. Mac does not handle very well with unicode. I tested with many configurations without success. I debugged into the code and the keys are sending correct but both the Terminal and iTerm2 don't process them. I think Mac is causing that.
I'm still a bit unclear: did emitting the escape sequence on shutdown make the resizing problem worse in your dotnet application on rerunning it in the same session?
Mac does not handle very well with unicode.
Ah, yes, it hadn't occurred to me that this was also the lack of Unicode support - I'll provide more feedback in #949.
The escape sequence works perfectly on shutdown but while running the app on the same session the screen does not redraws well. In WSL it's a messy screen and in linux the app does not resize as the screen size changes.
Perhaps if we take a step back and try to let ncurses handle restoring the previous terminal state - which is preferable anyway - the problem would go away:
On macOS, man curs_kernel
covers ncurses functions related to saving and restoring the terminal state.
However, it suggests that pairing initscr()
and endwin()
performs that automatically, and I don't understand the relationship between def_prog_mode
/ def_shell_mode
and reset_prog_mode
/ reset_shell_mode
on the one hand, and savetty
/ resettty
on the other:
The following routines give low-level access to various curses capabilities.
Theses routines typically are used inside library routines.
The def_prog_mode and def_shell_mode routines save the current terminal
modes as the "program" (in curses) or "shell" (not in curses) state for
use by the reset_prog_mode and reset_shell_mode routines. This is done
automatically by initscr. There is one such save area for each screen
context allocated by newterm().
The reset_prog_mode and reset_shell_mode routines restore the terminal
to "program" (in curses) or "shell" (out of curses) state. These are
done automatically by endwin and, after an endwin, by doupdate, so they
normally are not called.
The resetty and savetty routines save and restore the state of the ter-
minal modes. savetty saves the current state in a buffer and resetty
restores the state to what it was at the last call to savetty.
I think that will be the way in the future to manage properly ncurses (save and restore previous state). The strange is the GUI works with no problems before. I suspect about Net5.0 not working perfectly yet and also I'm using the preview 7 because I'm not using the preview version of Visual Studio.
To shed some more light on the original problem:
What breaks PowerShell is that the so-called application-cursor mode is not restored (which tput rmkx
would do) when an in-process (PowerShell-code-based) Terminal.Gui application exits.
Note that PowerShell is unusual in this respect, because the major POSIX-compatible shells do not turn this mode on for themselves, whereas PowerShell requires it. bash
and zsh
work properly whether the mode is set or not, whereas ksh
is the inverse: it requires that the mode be OFF (see below).
By contrast, if a Terminal.Gui application is run as an external application (child process), PowerShell compensates for Terminal.Gui not restoring the state by automatically turning application-cursor mode back on for itself.
By contrast, running a Terminal.Gui-based .NET application, invariably in a child process:
dotnet <DLL>
or as a self-contained executable (dotnet publish -p:PublishSingleFile=true ...
), application-cursor mode apparently is properly restored to the pre-application-startup state on exit, presumably thanks to ncurses, because an application that just uses Console
calls does not restore the the mode.dotnet run <project>
.In other words: The .NET Core runtime itself has the inverse problem in that it unconditionally leaves application-cursor mode ON on exiting (except if compensated for via ncurses):
PowerShell exhibits the same problematic behavior:
These misbehaviors rarely surface as a problem, at least in single-platform use, because bash
and zsh
handle cursor keys correctly whether or not application-cursor mode is turned on or off.
By contrast, ksh
only works when application-cursor mode is OFF, so it surfaces there.
Unfortunately, I misspoke - please see the updated previous comment; in short: irrespective of whether the Terminal.Gui application is run in-process as PowerShell code or via an external executable, the application-cursor mode is unconditionally turned off. However, that is only problematic for PowerShell in the in-process case, because when it runs external applications it always turns that mode back on for itself.
Started thinking about this again.
'tis a bummer this is not fixed: https://github.com/PowerShell/PowerShell/issues/6724
Thanks for a really nice example! There are others out there, but I like how cleanly this one is implemented.
I wrote a slight modification of your example which installs the ConsoleGuiTools module to help make life easier.
using namespace Terminal.Gui
if (!$(Get-Module Microsoft.PowerShell.ConsoleGuiTools)) {
Install-Module Microsoft.PowerShell.ConsoleGuiTools
}
Import-Module Microsoft.PowerShell.ConsoleGuiTools
$module = (Get-Module Microsoft.PowerShell.ConsoleGuiTools -List).ModuleBase
Add-Type -Path (Join-path $module Terminal.Gui.dll)
[Application]::Init()
$topLevel = [TopLevel]@{}
$topLevel.Add([StatusBar]@{
Visible = $true
Items = @(
[StatusItem]::new([int][Key]("CtrlMask") -bor [int][Key]("Q"), '~CTRL-Q~ Quit', {
[Application]::RequestStop()
})
)
})
$dialog = [Dialog]@{
X = [Pos]::Center()
Y = [Pos]::Center()
Title = 'Dialog'
}
$text = [TextField]@{
X = [Pos]::Center()
Y = [Pos]::Center()
Width = 20
Text = 'Howdy!'
}
$dialog.Add($text)
$quit = [Button]@{
X = [Pos]::Center()
Y = [Pos]::Center() + 1
Text = 'Quit'
}
$quit.add_Clicked({ [Application]::RequestStop() })
$dialog.Add($quit)
$topLevel.Add($dialog)
[Application]::Run($topLevel)
[Application]::Shutdown()
Write-Output "Got ""$($text.Text.ToString())"" from user input"
edit 1: I struggled with status bar items and the Key
class being not CLS compliant. Updated this example to show my workaround.
Thanks, @ca0abinary - that's the definitely the simplest option in v7.2+.
I took the liberty of integrating your Microsoft.PowerShell.ConsoleGuiTools
workaround into this StackOverflow answer (which is where the code in the initial post came from), and I've streamlined it a bit (no need to actually import the module) - though your sample code here has more features.
I've also updated the answer, which was written pre-v1, to reflect the status quo.