Skip to content

Commit

Permalink
Clear the screen
Browse files Browse the repository at this point in the history
  • Loading branch information
pflenker committed Apr 2, 2024
1 parent 3134980 commit ba50855
Showing 1 changed file with 44 additions and 20 deletions.
64 changes: 44 additions & 20 deletions src/editor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crossterm::event::{read, Event::Key, KeyCode::Char, KeyEvent, KeyModifiers};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use crossterm::event::{read, Event, Event::Key, KeyCode::Char, KeyEvent, KeyModifiers};
use crossterm::execute;
use crossterm::terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType};
use std::io::stdout;

pub struct Editor {
should_quit: bool,
Expand All @@ -10,31 +12,53 @@ impl Editor {
Editor { should_quit: false }
}
pub fn run(&mut self) {
if let Err(err) = self.repl() {
panic!("{err:#?}");
}
print!("Goodbye.\r\n");
Self::initialize().unwrap();
let result = self.repl();
Self::terminate().unwrap();
result.unwrap();

This comment has been minimized.

Copy link
@pflenker

pflenker Apr 2, 2024

Author Owner

We've changed our error handling here a bit and did something strange with the result of self.repl.

So we have three places where we can run into an error: During the preparation of the terminal, during execution of our loop and during shutting down our terminal.
In all three cases, there is nothing we can do to handle this. We're simply unwraping here, which will cause a panic anyways - no need to do our own panicing.

But then what's up with the error that can come from the repl loop?
You see, we have no idea what to do with it, but we do know that our terminal still needs to be cleaned up - raw mode needs to be ended. So we safe the result of repl, then we attempt to wind down the terminal. And if we did this successfully, we're unwrapping whatever repl gave us: Either an Ok with nothing in it, or an Err that would then cause the panic on a clear terminal.

}
fn repl(&mut self) -> Result<(), std::io::Error> {

fn initialize() -> Result<(), std::io::Error> {
enable_raw_mode()?;
Self::clear_screen()
}
fn terminate() -> Result<(), std::io::Error> {
disable_raw_mode()
}

This comment has been minimized.

Copy link
@pflenker

pflenker Apr 2, 2024

Author Owner

These two methods now handle our setup and our termination of the terminal.

fn clear_screen() -> Result<(), std::io::Error> {
let mut stdout = stdout();
execute!(stdout, Clear(ClearType::All))
}

This comment has been minimized.

Copy link
@pflenker

pflenker Apr 2, 2024

Author Owner

This is how we use crossterm to clear the screen. execute! means that we want to write out immediately and not wait for the buffer to be filled until it's written out.

This comment has been minimized.

Copy link
@dylwil3

dylwil3 Apr 14, 2024

Not sure if this is on purpose, but the implementation in this commit causes the cursor to stay wherever it was. So when the program begins/exits, text gets written wherever the cursor happens to be.

So maybe this works more as expected?

  fn clear_screen() -> Result<(), std::io::Error> {
      let mut stdout = stdout();
      execute!(stdout, MoveTo(0,0))?;
      execute!(stdout, Clear(ClearType::All))
  }

This comment has been minimized.

Copy link
@pflenker

pflenker Apr 14, 2024

Author Owner

Hey @dylwil3 , I'm happy to see that you're already looking ahead at what is to come! This is indeed purposefully ignored at that stage, cursor handling will come one step later.


fn repl(&mut self) -> Result<(), std::io::Error> {
loop {
if let Key(KeyEvent {
code, modifiers, kind, state
}) = read()?
{
println!("Code: {code:?} Modifiers: {modifiers:?} Kind: {kind:?} State: {state:?} \r");
match code {
Char('q') if modifiers == KeyModifiers::CONTROL => {
self.should_quit = true;
}
_ => (),
}
}
let event = read()?;
self.evaluate_event(&event);

This comment has been minimized.

Copy link
@pflenker

pflenker Apr 2, 2024

Author Owner

The logic to evaluate the event now moves into a separate function. We're not passing the event directly, though, but only a reference to it, as indicated by &.

Think of it like this: Instead of paying you with a huge amount of gold, I'm paying you with a treasure map that points to that gold. The map is easier for you to handle and carry around, and the promise is: if you follow it, you'll find the gold.

self.refresh_screen()?;
if self.should_quit {
break;
}
}
disable_raw_mode()?;
Ok(())
}
fn evaluate_event(&mut self, event: &Event) {
if let Key(KeyEvent {
code, modifiers, ..

This comment has been minimized.

Copy link
@pflenker

pflenker Apr 2, 2024

Author Owner

Since we're no longer printing out the event, we're only destructuring the parts that we're interested in now. The .. tells rust: "I know there is more, but I don't need it".

This comment has been minimized.

Copy link
@dylwil3

dylwil3 Apr 14, 2024

Why not do

if let KeyEvent {
        code: Char('q'),
        modifiers: KeyModifiers::CONTROL,
        ..
    } = event

to avoid the match afterwards?

This comment has been minimized.

Copy link
@pflenker

pflenker Apr 14, 2024

Author Owner

You're right! I come from a more complex mental model where we would already do more complex handling of the keypresses (this happens somewhere in Chapter 4 and 5) and forgot to simplify the logic at this point again after removing the println!.
Thanks for the suggestion!

This comment has been minimized.

Copy link
@dylwil3

dylwil3 Apr 14, 2024

Sure! Thanks for making this great tutorial!

}) = event
{
match code {
Char('q') if *modifiers == KeyModifiers::CONTROL => {

This comment has been minimized.

Copy link
@pflenker

pflenker Apr 2, 2024

Author Owner

Subtle change here!
We're no longer operating on an Event, but on a reference to an event, and subsequently everything we've destructured out of the event is also a reference and not the real thing. But we want to do a real comparison here, so we need to dereference in this case modifiers - with the *. Or we could compare it to a reference to KeyModifiers::Control, we would do it by prefixing it with &.

To stay with the metaphor from above:
If you want to check that your treasure is actually gold, you either need to go to your treasure and compare it to actual gold (that's the * approach), or you need to compare your map that shows where the gold lies with another map that shows where gold is and see if they point to the same thing (that'd be the & approach). Comparing the map with gold doesn't do any good.

self.should_quit = true;
}
_ => (),
}
}
}
fn refresh_screen(&self) -> Result<(), std::io::Error> {
if self.should_quit {
Self::clear_screen()?;
print!("Goodbye.\r\n");

This comment has been minimized.

Copy link
@pflenker

pflenker Apr 2, 2024

Author Owner

Our Refresh Screen does only ever do one thing: Print the exit message. Otherwise there is nothing to refresh.

For now.

}
Ok(())
}
}

0 comments on commit ba50855

Please sign in to comment.