Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dropping AlternateScreen breaks alternate screen mode with Crossterm instance #88

Closed
defiori opened this issue Feb 8, 2019 · 4 comments

Comments

@defiori
Copy link

defiori commented Feb 8, 2019

extern crate crossterm;
use self::crossterm::{Crossterm, Screen, ClearType};
use std::io::Read;
use std::{thread, time};

fn main() {
    let raw_mode = true;
    let screen = Screen::new(raw_mode);
    let crossterm: Crossterm;
    {
        let alternate_screen = screen.enable_alternate_modes(raw_mode).unwrap();
        crossterm = Crossterm::from_screen(&alternate_screen.screen);
    }
    // <-- Dropping alternate_screen here

    let mut input = crossterm.input().read_async().bytes();
    let terminal = crossterm.terminal();
    let cursor = crossterm.cursor();
    cursor.hide().unwrap();

    loop {
        terminal.clear(ClearType::All).unwrap();
        cursor.goto(1, 1).unwrap();
        match input.next() {
            // These user inputs are not in raw mode
            Some(ipt) => {
                terminal.write(format!("{:?}    <- Character pressed", ipt)).unwrap();
                match ipt {
                    // Does not return to original screen
                    Ok(b'q') => break,
                    _ => {},
                };
            },
            _ => {}
        };
        thread::sleep(time::Duration::from_millis(200));
    }
}

When creating a Crossterm instance from an AlternateScreen, dropping the AlternateScreen breaks both raw and alternate screen mode, see example above. This gets tricky when users assume it is possible to only store the Crossterm instance to draw to the alternate screen.

@defiori
Copy link
Author

defiori commented Feb 8, 2019

It turns out it is possible to stay in raw mode by calling disable_drop() on AlternateScreen's Screen like so:

...
{
        let mut alternate_screen = screen.enable_alternate_modes(raw_mode).unwrap();
        alternate_screen.screen.disable_drop();
        crossterm = Crossterm::from_screen(&alternate_screen.screen);
    }
    // <-- Dropping alternate_screen here
...

However this feels like a bit of a hack. Alternate screen mode still breaks after the drop.

@TimonPost
Copy link
Member

  1. It is 'normal' behavior for an AlternateScreen to be switched back to mainscreen when it is dropped. AlternateScreen is meant for quickly doing something and not have it open for a long time. So, for now, you are bounded to the scope in which you are allowed to use the alternate screen.
  2. When Screen drops it will turn off the raw modes. There are usecases you don't want that from automatically happening so, therefore, you can call disable_drop().

In the future, this might be more flexible and I will build in the functionality to have multiple screen buffers who have their own settings. You can be using lazy_static this allows you to have your crossterm type living longer.

I get your argument that the user might be misled and I take a look to see if I can improve the docs here. Are there any problems you are encountering with otherwise I'll close this issue.

@defiori
Copy link
Author

defiori commented Feb 8, 2019

Thanks for the quick reply. I think AlternateScreen's behavior make sense and isn't the issue here. What threw me off is that if I create a Crossterm instance from an AlternateScreen's screen, the Crossterm instance 's behavior depends on whether the AlternateScreen has been dropped or not. I'm not very familiar with your code, but it looks like AlternateScreen and Crossterm share ownership of the TerminalOutput via Arc's, so they can both mutate it at will. In the worst case scenario you could create a Crossterm instance from an AlternateScreen's screen, send the AlternateScreen to whatever thread you like and use it to manipulate the screen, until you eventually drop it. The Crossterm could be doing the same in a completely different part of your code and work as expected, until the AlternateScreen drops and the behavior suddenly changes. It would be extremely difficult to figure out where this change comes from without going through the source, because it has nothing to do with what you do to the Crossterm locally.

Given that Crossterm seems to be a convenience wrapper, could it consume an AlternateScreen so that there's only one place to change behavior? Alternatively, could Crossterm retain a reference to AlternateScreen, making sure they're used in the same place?

Edit: Consuming the AlternateScreen seems preferable because the lack of self-referential structs means the AlternateScreen and Crossterm couldn't be held in the same struct.

@TimonPost
Copy link
Member

closing this, because it is answered if you have any more question feel free to reopen.

december1981 pushed a commit to december1981/crossterm that referenced this issue Oct 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants