-
Notifications
You must be signed in to change notification settings - Fork 278
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
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
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. |
aacf548
to
1d176d9
Compare
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 |
There was a problem hiding this 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.
There was a problem hiding this 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.)
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()?; ```
0b75ddc
to
48d6c5a
Compare
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. |
There was a problem hiding this 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.)
src/backend/crossterm.rs
Outdated
/// 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. |
There was a problem hiding this comment.
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.
/// 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. |
There was a problem hiding this comment.
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)).
/// 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 |
There was a problem hiding this comment.
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)
/// 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Ok(self) | ||
} | ||
|
||
/// Restores the terminal to its default state. |
There was a problem hiding this comment.
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).
/// Restores the terminal to its default state. | |
/// Restores the given terminal to its default state. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick
There was a problem hiding this comment.
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.
/// 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick
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); | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick
There was a problem hiding this comment.
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.
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. |
There was a problem hiding this 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.
/// 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 |
There was a problem hiding this comment.
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.
Ok(self) | ||
} | ||
|
||
/// Restores the terminal to its default state. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick
/// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick
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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick
@EdJoPaTo see my email about nits etc. |
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. |
pub fn stdout() -> io::Result<Self> { | ||
Self::new(io::stdout()) | ||
.with_raw_mode()? | ||
.with_alternate_screen() | ||
} |
There was a problem hiding this comment.
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
andCrosstermBackend::stderr
for creating backends withstd::io::stdout
andstd::io::stderr
respectively.CrosstermBackend::stdout_with_defaults
andCrosstermBackend::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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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()
...
There was a problem hiding this comment.
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 inlineCrosstermBackend::stdout()
for a TUI
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
execute!( | ||
writer, | ||
LeaveAlternateScreen, | ||
DisableMouseCapture, | ||
DisableBracketedPaste, | ||
DisableFocusChange, | ||
PopKeyboardEnhancementFlags | ||
)?; | ||
writer.flush() |
There was a problem hiding this comment.
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.
execute!( | |
writer, | |
LeaveAlternateScreen, | |
DisableMouseCapture, | |
DisableBracketedPaste, | |
DisableFocusChange, | |
PopKeyboardEnhancementFlags | |
)?; | |
writer.flush() | |
execute!( | |
writer, | |
LeaveAlternateScreen, | |
DisableMouseCapture, | |
DisableBracketedPaste, | |
DisableFocusChange, | |
PopKeyboardEnhancementFlags | |
) |
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(); | ||
} |
There was a problem hiding this comment.
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.
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(); | ||
} |
There was a problem hiding this comment.
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.
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. |
Something to consider as a total simplification would be to just have |
Adds:
CrosstermBackend::stdout
andCrosstermBackend::stderr
for creatingbackends with
std::io::stdout
andstd::io::stderr
respectively.CrosstermBackend::stdout_with_defaults
andCrosstermBackend::stderr_with_defaults
for creatingbackends with that use the alternate screen and raw mode.
CrosstermBackend::with_raw_mode
for enabling raw mode.CrosstermBackend::with_alternate_screen
for switching to thealternate screen.
CrosstermBackend::with_mouse_capture
for enabling mouse support.CrosstermBackend::reset
for resetting the terminal to its defaultstate.
Drop
implementation for restoring raw mode, mouse capture, andalternate screen on drop.
Most applications should generally use
stdout_with_defaults
to createa 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).