[Rust] Stuck on #CZ2 after a long time | stdout doesn't work properly

I’m stuck on Stage #CZ2.

After a long time I decided to continue and had many problems with the challenge. Looks like there is a new build pipeline. I copied the necessary code from another repo and gave executable permissions to them. I was trying to implement auto completion but now tester can’t even write anything to the shell in the raw mode.

Here are my logs:

[tester::#CZ2] Running tests for Stage #CZ2 (Handle invalid commands)
[tester::#CZ2] Running ./your_program.sh
[your-program] $ 
[tester::#CZ2] ^ Line does not match expected value.
[tester::#CZ2] Expected: "$ invalid_raspberry_command"
[tester::#CZ2] Received: "$ " (trailing space)
[tester::#CZ2] Test failed

And here’s a snippet of my code:

src/shell.rs

use std::io::{self, Error, ErrorKind, Stderr, Stdout, Write};

use core::{ShellCommandProvider, ShellInterpreter, ShellTokenizer};
use crossterm::{
    execute,
    style::Print,
    terminal::{disable_raw_mode, enable_raw_mode},
};

pub mod core;

pub struct Shell {
    pub(crate) buffer: String,
    pub(crate) stdout: Stdout,
    pub(crate) stderr: Stderr,

    // Auto Complete
    pub(crate) tab_query: String,
    pub(crate) tab_index: u8,
    // history: Vec<String>,
}

impl Shell {
    pub fn new() -> Self {
        Self {
            buffer: String::new(),
            stdout: io::stdout(),
            stderr: io::stderr(),
            tab_index: 0,
            tab_query: String::new(),
            // history: Vec::new(),
        }
    }

    pub async fn run<
        T,
        I: ShellInterpreter<T>,
        C: ShellCommandProvider<T>,
        K: ShellTokenizer<T>,
    >(
        &mut self,
    ) -> Result<(), Error> {
        self.init()?;
        self.stdout.flush()?;

        loop {
            let result = self.handle_event::<T, I, C, K>();

            if result.is_err() && result.unwrap_err().kind() == ErrorKind::Interrupted {
                break;
            }

            self.stdout.flush()?;
        }

        self.uninit()?;

        Ok(())
    }

    fn init(&mut self) -> Result<(), Error> {
        enable_raw_mode()?;

        execute!(self.stdout, Print("$ "),)?;
        Ok(())
    }

    fn uninit(&mut self) -> Result<(), Error> {
        disable_raw_mode()?;

        execute!(self.stdout)?;
        Ok(())
    }
}

src/modules/event_handler/key_handler/ch.rs

use crossterm::{
    cursor::{self, MoveToColumn},
    execute,
    style::Print,
    terminal::{Clear, ClearType},
};

use crate::shell::Shell;
use std::io::Error;

impl Shell {
    pub(crate) fn handle_ch(&mut self, ch: char) -> Result<(), Error> {
        let relative_cursor_x = cursor::position()?.0 as usize - super::PREFIX.len();

        if relative_cursor_x < self.buffer.len() {
            self.buffer.insert(relative_cursor_x, ch);

            execute!(
                self.stdout,
                Clear(ClearType::CurrentLine),
                MoveToColumn(0),
                Print(super::PREFIX),
                Print(&self.buffer),
                MoveToColumn((relative_cursor_x + 3) as u16)
            )?;
        } else {
            self.buffer.push(ch);
            execute!(self.stdout, Print(ch))?;
        }

        self.tab_index = 0;
        self.tab_query = String::new();
        Ok(())
    }
}

Here’s the repo link:

Hey @emrecancorapci, looks like there might be an issue with cursor::position(). Could you confirm whether it’s working as expected on your end?

I’m not an expert on Rust, so I might be missing something. Let me know what you find.

When I use keyboard it works fine with two different computers (macOS and arch linux).

I don’t know if it’s a proper way to use but when I run

 sh .codecrafters/run.sh invalid_command

it doesn’t work. Also tried it with your_program.sh the result is the same.

rust-shell (master) ✗ sh .codecrafters/run.sh invalid
$  

@emrecancorapci Thanks for following up!

I can confirm the issue is coming from cursor::position(). It sends an ANSI cursor-position query (ESC[6n) and waits for the terminal to respond, but our tester environment doesn’t support responding to that query. As a result, crossterm blocks there before it can read the tester’s input.

For this challenge, we’d recommend avoiding terminal cursor queries like this. A better approach is to track the cursor position in your own shell state instead of relying on cursor::position().

Let me know if that helps unblock things.

Thank you it worked!

But now there is a weirder bug. First it happened on #UN3 but then I used --previous flag and now it happens on #CZ2. What I see is test adds additional “j” at the end of the command. Maybe it’s a bug on my side but literally it is what the library returns. Should I blame crossterm?

...
[your-program]                                                                           'n',
[your-program]                                                                               )
[your-program] [src/modules/event_handler/key_handler/ch.rs:33:9] &ch = 'n'                   n
[your-program] [src/modules/event_handler/key_handler/ch.rs:35:9] &self.buffer = "invalid_blueberry_comman"
[your-program] [src/modules/event_handler/key_handler.rs:38:9] key_event.code = Char(
[your-program]                                                                           'd',
[your-program]                                                                               )
[your-program] [src/modules/event_handler/key_handler/ch.rs:33:9] &ch = 'd'                   d
[your-program] [src/modules/event_handler/key_handler/ch.rs:35:9] &self.buffer = "invalid_blueberry_command"
[your-program] [src/modules/event_handler/key_handler.rs:38:9] key_event.code = Char(
[your-program]                                                                           'j',
[your-program]                                                                               )
[your-program] [src/modules/event_handler/key_handler/ch.rs:33:9] &ch = 'j'                   j
[your-program] [src/modules/event_handler/key_handler/ch.rs:35:9] &self.buffer = "invalid_blueberry_commandj"
[your-program]                                                                                               
[tester::#CZ2] Test failed

This is actually a recurring issue. You can find more details here:

https://forum.codecrafters.io/t/why-is-the-testes-sending-an-extra-j-at-the-end-and-expects-it-removed/15545/3

Thank you again.

I guess new tests added since I’ve gone. There are a lot of bugs I need to fix :'))))