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

feat(backend): Add convenience methods for setting up terminals #1180

Open
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

joshka
Copy link
Member

@joshka joshka commented Jun 16, 2024

Adds:

  • CrosstermBackend::stdout and CrosstermBackend::stderr for creating
    backends with std::io::stdout and std::io::stderr respectively.
  • CrosstermBackend::stdout_with_defaults and
    CrosstermBackend::stderr_with_defaults for creating
    backends with that use the alternate screen and raw mode.
  • CrosstermBackend::with_raw_mode for enabling raw mode.
  • CrosstermBackend::with_alternate_screen for switching to the
    alternate screen.
  • CrosstermBackend::with_mouse_capture for enabling mouse support.
  • CrosstermBackend::reset for resetting the terminal to its default
    state.
  • Drop implementation for restoring raw mode, mouse capture, and
    alternate screen on drop.

Most applications should generally use stdout_with_defaults to create
a backend with raw mode and the alternate screen, and now longer need
to worry about restoring the terminal to its default state when the
application exits. The reset method can be used to restore the terminal to
its default state if needed (e.g. in a panic handler).

use ratatui::{backend::CrosstermBackend};

let backend = CrosstermBackend::stdout_with_defaults()?;
// or
let backend = CrosstermBackend::stderr_with_defaults()?;
// or with custom settings
let backend = CrosstermBackend::stdout()
    .with_raw_mode()?
    .with_alternate_screen()?
    .with_mouse_capture()?
use std::io::stderr();

// in a panic / error handler:
CrosstermBackend::reset(stderr())

@joshka joshka changed the title feat(backend): add convenience methods feat(backend): Add convenience methods for setting up terminals Jun 16, 2024
Copy link

codecov bot commented Jun 16, 2024

Codecov Report

Attention: Patch coverage is 0% with 83 lines in your changes missing coverage. Please review.

Project coverage is 93.9%. Comparing base (3f2f2cd) to head (97bf4a3).
Report is 56 commits behind head on main.

Files Patch % Lines
src/backend/crossterm.rs 0.0% 83 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##            main   #1180     +/-   ##
=======================================
- Coverage   94.4%   93.9%   -0.5%     
=======================================
  Files         62      62             
  Lines      14941   15127    +186     
=======================================
+ Hits       14110   14217    +107     
- Misses       831     910     +79     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@joshka joshka force-pushed the jm/crossterm-init branch 2 times, most recently from aacf548 to 1d176d9 Compare June 16, 2024 01:28
@joshka
Copy link
Member Author

joshka commented Jun 16, 2024

Note - this is intentionally only implemented for crossterm right now, and it's not carried through to the examples as yet to allow for review of the concept without the bulk.

Note also the comments / previous effort on #280

Copy link
Member

@EdJoPaTo EdJoPaTo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like seeing the with_* methods and the internal state to allow for a simpler reset. This will simplify a lot of code.

With other stuff I have my problems, noted on them directly for easier, threaded discussions.

src/backend/crossterm.rs Outdated Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
src/backend/crossterm.rs Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
joshka added a commit that referenced this pull request Jun 18, 2024
In <#1180> and
<#1181>, we introduced some
simpler ways to create a backend and terminal, and to setup color_eyre
to handle errors. This PR updates the examples to use these new methods,
and removes a bunch of unnecessary boilerplate code.
@joshka joshka requested a review from EdJoPaTo June 18, 2024 06:07
@joshka joshka added Status: Controversial There is some discussion about whether this is the right direction. Clarify and seek consensus. Status: Review Needed PR needs a review / Issue needs buy-in from other maintainers / users labels Jun 19, 2024
@joshka joshka dismissed EdJoPaTo’s stale review June 20, 2024 00:49

Changes addressed

@joshka joshka requested a review from orhun June 20, 2024 00:50
Copy link
Member

@EdJoPaTo EdJoPaTo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the simplicity that will come with this and a built-in panic handler. I still want to provoke some thoughts about the current state here. (Also, I want to test this branch to get a feel for its real world usage, which I haven't done yet.)

src/backend/crossterm.rs Outdated Show resolved Hide resolved
examples/minimal.rs Outdated Show resolved Hide resolved
examples/colors_rgb.rs Outdated Show resolved Hide resolved
src/backend/crossterm.rs Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
examples/colors_rgb.rs Show resolved Hide resolved
Adds:
- `CrosstermBackend::stdout` and `CrosstermBackend::stderr` for creating
  backends with `std::io::stdout` and `std::io::stderr` respectively.
- `CrosstermBackend::into_terminal` for converting a backend into a
  terminal.
- `CrosstermBackend::into_terminal_with_options` for converting a
  backend into a terminal with options.
- `CrosstermBackend::into_terminal_with_defaults` for converting a
  backend into a terminal with default settings (raw mode and alternate
  screen).
- `CrosstermBackend::with_raw_mode` for enabling raw mode.
- `CrosstermBackend::with_alternate_screen` for switching to the
  alternate screen.
- `CrosstermBackend::with_mouse_support` for enabling mouse support.
- `CrosstermBackend::reset` for resetting the terminal to its default
  state.
- `Drop` implementation for restoring raw mode, mouse capture, and
  alternate screen on drop.

Most applications can now just use the `into_terminal_with_defaults`
method to set up the terminal with raw mode and the alternate screen,
and now longer need to worry about restoring the terminal to its default
state when the application exits. The reset method can be used to
restore the terminal to its default state if needed (e.g. in a panic
handler).

```rust
use ratatui::backend::CrosstermBackend;

let terminal = CrosstermBackend::stdout()
     .into_terminal_with_defaults()?;
```
@EdJoPaTo
Copy link
Member

I am curious why crossterm itself doesn't have something like this. I think everyone using crossterm would benefit from something like this. Should this be proposed for crossterm?

@joshka
Copy link
Member Author

joshka commented Jun 28, 2024

I am curious why crossterm itself doesn't have something like this. I think everyone using crossterm would benefit from something like this. Should this be proposed for crossterm?

Define "this".

Regardless it doesn't change whether we'd want this PR to land in the meantime as the development cycle on that side of the fence is pretty slow.

@EdJoPaTo do you have any hard blockers for this PR getting merged? This PR underpins a massive simplification of all the example code in Ratatui as well as customer apps, tutorials, recipes, etc. I'm blocked on completing all of that by this not being merged.

Copy link
Member

@EdJoPaTo EdJoPaTo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still like the simplification that will come with this a lot. Some curiosity / minor nitpicks remain, some docs should improve, then I think this can be merged.


@joshka dismissed my review, I think this should be enabled automatically for this repo: dismiss reviews on new commits. Either it's quite fast to review them again or it might have changed something that should be reviewed again. (Maybe also a review of multiple and not just one person might be ideal especially for bigger things.)

examples/minimal.rs Show resolved Hide resolved
Comment on lines 39 to 40
/// to create a `CrosstermBackend` with [`std::io::stdout`] or [`std::io::stderr`] as the writer
/// with default settings to enable raw mode and switch to the alternate screen.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default went away from the method name. It provides the most used ways of using it. The docs should reflect it in my opinion.

Suggested change
/// to create a `CrosstermBackend` with [`std::io::stdout`] or [`std::io::stderr`] as the writer
/// with default settings to enable raw mode and switch to the alternate screen.
/// to create a `CrosstermBackend` with [`std::io::stdout`] or [`std::io::stderr`] as the writer
/// with the often used settings to enable raw mode and switch to the alternate screen.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marked as unresolved. Should be adapted once a better name is figured out (#1180 (comment)).

Comment on lines +42 to +43
/// If the default settings are not desired, the `CrosstermBackend` can be configured using the
/// `with_*` methods. These methods return an [`io::Result`] containing self so that they can be
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the mentioning of the default. Also, it sounds like it can be reverted while ::stdout().without_raw_mode() doesn't exist, so phrase it better.

(Will need reformatting for alignment for sure)

Suggested change
/// If the default settings are not desired, the `CrosstermBackend` can be configured using the
/// `with_*` methods. These methods return an [`io::Result`] containing self so that they can be
/// Additional settings be configured using the
/// `with_*` methods. These methods return an [`io::Result`] containing self so that they can be

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is clear enough and seems like too much of a nitpick to worry about changing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this again: It's also way easier to read and understand in my opinion.

src/backend/crossterm.rs Show resolved Hide resolved
examples/colors_rgb.rs Show resolved Hide resolved
Ok(self)
}

/// Restores the terminal to its default state.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be a bit more clear that this is for an argument and not &self (or similar).

Suggested change
/// Restores the terminal to its default state.
/// Restores the given terminal to its default state.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still think this would be helpful to differentiate more clearly between the &self methods and this static method consuming some other reader.

Comment on lines +314 to +316
/// If you have created a `CrosstermBackend` using the `with_*` methods, the settings are
/// restored when the `CrosstermBackend` is dropped, so you do not need to call this method
/// manually.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move this comment up? General description, why might I don't need to read further, what does it in detail.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick

src/backend/crossterm.rs Show resolved Hide resolved
Comment on lines +343 to +360
if self.restore_raw_mode_on_drop {
let _ = disable_raw_mode();
}
if self.restore_mouse_capture_on_drop {
let _ = execute!(self.writer, DisableMouseCapture);
}
if self.restore_alternate_screen_on_drop {
let _ = execute!(self.writer, LeaveAlternateScreen);
}
if self.restore_bracketed_paste_on_drop {
let _ = execute!(self.writer, DisableBracketedPaste);
}
if self.restore_focus_change_on_drop {
let _ = execute!(self.writer, DisableFocusChange);
}
if self.restore_keyboard_enhancement_flags_on_drop {
let _ = execute!(self.writer, PopKeyboardEnhancementFlags);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same curiosity: Is the order relevant?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Searched the crossterm docs for a bit and didn't find an obvious hint to make sure of some order here. So probably fine to invoke them in any order.

src/backend/crossterm.rs Show resolved Hide resolved
@joshka
Copy link
Member Author

joshka commented Jun 28, 2024

@joshka dismissed my review, I think this should be enabled automatically for this repo: dismiss reviews on new commits. Either it's quite fast to review them again or it might have changed something that should be reviewed again. (Maybe also a review of multiple and not just one person might be ideal especially for bigger things.)

That approach causes a long turn around time for things which don't matter. I prefer to work based on trust that we'll do the right thing once given the go ahead to merge.

Co-authored-by: EdJoPaTo <rfc-conform-git-commit-email@funny-long-domain-label-everyone-hates-as-it-is-too-long.edjopato.de>
Copy link
Member Author

@joshka joshka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've noted that a most of these points are nitpicks and my tolerance for handling this amount of not relevant feedback on this PR is rapidly approaching zero. There's nothing that would block this from being merged or that would cause a user not to understand how these methods work. As such resolving them.

examples/colors_rgb.rs Show resolved Hide resolved
examples/minimal.rs Show resolved Hide resolved
src/backend/crossterm.rs Outdated Show resolved Hide resolved
Comment on lines +42 to +43
/// If the default settings are not desired, the `CrosstermBackend` can be configured using the
/// `with_*` methods. These methods return an [`io::Result`] containing self so that they can be
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is clear enough and seems like too much of a nitpick to worry about changing.

src/backend/crossterm.rs Show resolved Hide resolved
Ok(self)
}

/// Restores the terminal to its default state.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick

Comment on lines +314 to +316
/// If you have created a `CrosstermBackend` using the `with_*` methods, the settings are
/// restored when the `CrosstermBackend` is dropped, so you do not need to call this method
/// manually.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick

src/backend/crossterm.rs Show resolved Hide resolved
Comment on lines +343 to +360
if self.restore_raw_mode_on_drop {
let _ = disable_raw_mode();
}
if self.restore_mouse_capture_on_drop {
let _ = execute!(self.writer, DisableMouseCapture);
}
if self.restore_alternate_screen_on_drop {
let _ = execute!(self.writer, LeaveAlternateScreen);
}
if self.restore_bracketed_paste_on_drop {
let _ = execute!(self.writer, DisableBracketedPaste);
}
if self.restore_focus_change_on_drop {
let _ = execute!(self.writer, DisableFocusChange);
}
if self.restore_keyboard_enhancement_flags_on_drop {
let _ = execute!(self.writer, PopKeyboardEnhancementFlags);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick

src/backend/crossterm.rs Show resolved Hide resolved
joshka

This comment was marked as duplicate.

@joshka joshka requested a review from EdJoPaTo June 28, 2024 15:30
@joshka
Copy link
Member Author

joshka commented Jun 28, 2024

@EdJoPaTo see my email about nits etc.

@kdheepak
Copy link
Collaborator

Most of the PR looks good to me (but I haven't read all the comments on this thread), will do so over the weekend.

Comment on lines +160 to +164
pub fn stdout() -> io::Result<Self> {
Self::new(io::stdout())
.with_raw_mode()?
.with_alternate_screen()
}
Copy link
Collaborator

@kdheepak kdheepak Jun 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says:

  • CrosstermBackend::stdout and CrosstermBackend::stderr for creating backends with std::io::stdout and std::io::stderr respectively.
  • CrosstermBackend::stdout_with_defaults and CrosstermBackend::stderr_with_defaults for creating backends with that use the alternate screen and raw mode.

which doesn't match this implementation.

I prefer it to work the way the PR description mentions. It makes it much more clearer imo.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, CrosstermBackend::stdout_tui and CrosstermBackend::stderr_tui.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea behind doing some reasonable defaults (raw/alternate screen) on these is that this is necessary for 90% of all apps. Anything else can do CrosstermBackend::new(stdout()).with_xxx()...

Copy link
Collaborator

@kdheepak kdheepak Jun 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll be a little confusing imo to find in a Ratatui app's user code two things that are very similar but do different things

  • CrosstermBackend::new(stdout()) for inline
  • CrosstermBackend::stdout() for a TUI

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the reasonable defaults in place here, I'm just not convinced that the name conveys those defaults clearly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of CrosstermBackend::stdout_tui() with the alternative of CrossteermBackend::new(stdout()).with_* is actually quite good. It removes the CrosstermBackend::stdout() == CrosstermBackend::new(stdout()) similarity and is not just default which is more confusing as there are different defaults for different use cases.

Comment on lines +327 to +335
execute!(
writer,
LeaveAlternateScreen,
DisableMouseCapture,
DisableBracketedPaste,
DisableFocusChange,
PopKeyboardEnhancementFlags
)?;
writer.flush()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

execute! calls flush already. This likely wont change in crossterm as execute is queue + flush.

Suggested change
execute!(
writer,
LeaveAlternateScreen,
DisableMouseCapture,
DisableBracketedPaste,
DisableFocusChange,
PopKeyboardEnhancementFlags
)?;
writer.flush()
execute!(
writer,
LeaveAlternateScreen,
DisableMouseCapture,
DisableBracketedPaste,
DisableFocusChange,
PopKeyboardEnhancementFlags
)

Comment on lines +340 to +362
fn drop(&mut self) {
// note that these are not checked for errors because there is nothing that can be done if
// they fail. The terminal is likely in a bad state, and the application is exiting anyway.
if self.restore_raw_mode_on_drop {
let _ = disable_raw_mode();
}
if self.restore_mouse_capture_on_drop {
let _ = execute!(self.writer, DisableMouseCapture);
}
if self.restore_alternate_screen_on_drop {
let _ = execute!(self.writer, LeaveAlternateScreen);
}
if self.restore_bracketed_paste_on_drop {
let _ = execute!(self.writer, DisableBracketedPaste);
}
if self.restore_focus_change_on_drop {
let _ = execute!(self.writer, DisableFocusChange);
}
if self.restore_keyboard_enhancement_flags_on_drop {
let _ = execute!(self.writer, PopKeyboardEnhancementFlags);
}
let _ = self.writer.flush();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought: execute! is queue! + flush. This method ends with flush already. It could use queue! for all the calls and then flush once in the end.
Even when something fails the errors are ignored and the flush call is definitely executed.

It's probably not that relevant as it's not called that often, but I still think a worthy improvement as queue and execute both exist for exactly this reason.

Comment on lines +340 to +362
fn drop(&mut self) {
// note that these are not checked for errors because there is nothing that can be done if
// they fail. The terminal is likely in a bad state, and the application is exiting anyway.
if self.restore_raw_mode_on_drop {
let _ = disable_raw_mode();
}
if self.restore_mouse_capture_on_drop {
let _ = execute!(self.writer, DisableMouseCapture);
}
if self.restore_alternate_screen_on_drop {
let _ = execute!(self.writer, LeaveAlternateScreen);
}
if self.restore_bracketed_paste_on_drop {
let _ = execute!(self.writer, DisableBracketedPaste);
}
if self.restore_focus_change_on_drop {
let _ = execute!(self.writer, DisableFocusChange);
}
if self.restore_keyboard_enhancement_flags_on_drop {
let _ = execute!(self.writer, PopKeyboardEnhancementFlags);
}
let _ = self.writer.flush();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that CrosstermBackend::restore just does all these without checking for boolean flags. Is there a downside to do it that way in all the cases?

This would simplify this to just call CrosstermBackend::restore(self.writer) and removes a lot of boolean flags resulting in less complexity or possibility for mistakes.

@EdJoPaTo
Copy link
Member

EdJoPaTo commented Aug 4, 2024

As a crossterm version change is included with the upcoming release I would like to reduce the stuff needed from crossterm even further with the 0.28 release. Can we get this into the release? (Without rushing it, I would prefer to wait with the release in order to resolve all the thoughts to make this right)

@joshka
Copy link
Member Author

joshka commented Aug 4, 2024

As a crossterm version change is included with the upcoming release I would like to reduce the stuff needed from crossterm even further with the 0.28 release. Can we get this into the release? (Without rushing it, I would prefer to wait with the release in order to resolve all the thoughts to make this right)

I think this might be worth missing the 0.28.0 release. There's a lot of small nuanced things to go through, and then once this lands it would be nice to revamp all the examples to use this, that's a fair bit bigger than I'd like to commit to before releasing 0.28, and I'd really like those two things to go together.

@joshka joshka added Status: Revision Needed PR changes requested Status: On Hold Not actively being worked on for now due to time / other constraints and removed Status: Review Needed PR needs a review / Issue needs buy-in from other maintainers / users labels Aug 4, 2024
@joshka
Copy link
Member Author

joshka commented Aug 5, 2024

Something to consider as a total simplification would be to just have Terminal::init() and Terminal::restore() do the right thing (crossterm, stdout, raw mode, alternate screen). -> #1289

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Controversial There is some discussion about whether this is the right direction. Clarify and seek consensus. Status: On Hold Not actively being worked on for now due to time / other constraints Status: Revision Needed PR changes requested
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants