適合胖手指打字的 REPL

Barbara Streisand
發布: 2024-10-22 06:12:30
原創
413 人瀏覽過

A REPL for Fat-Finger Friendly Typing

我的 Python 解釋器 Memphis 有一個 REPL(讀取-評估-列印循環)!

這是舊聞了。只要你在與聰明的老貓頭鷹互動時零失誤,就可以隨心所欲地解讀。假設您不想對同一個語句求值兩次,或者如果您這樣做,也不介意重新輸入它。而且零錯誤。

我對這個 REPL 非常滿意。甚至激動不已。我已經寫信回家談論這個 REPL。但我老闆的老闆的老闆要求我們為了人民的底線而改進 REPL。他們把我叫進辦公室,點頭讓我坐在紅木辦公桌對面的椅子上。 「有些用戶希望退格鍵起作用。」讓用戶見鬼去吧! 「向上箭頭應該會顯示他們的最後一個命令。」箭頭鍵支援是如此九十年代! 「你能在第五季結束前完成這項工作嗎?」我放棄了!

所以我回到辦公桌前改進了 REPL。

我對其進行了很大改進,所有按鍵都可以使用。退格鍵、向上箭頭、向下箭頭、向左箭頭,以及最後但並非最不重要的退格箭頭。會計師可以使用新的 REPL 進行實地考察。勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾勾。那是會計師在敲數字,而不是慢慢擴散的炸彈。

我將 REPL 送到實驗室,並告訴我的主要機械師對該訂單進行加急工作。這是我說的 REPL,從他們的眼神我可以看出他們明白了。 750 毫秒後,建置完成,我們獲得了箭頭鍵支援。我把產品帶回給大佬們,請求讓我恢復工作,並詢問他們的想法。他們運行了一些命令,列印了一些列印內容,並添加了一些添加內容。他們犯了一個錯誤,按下了退格鍵。我翻了個白眼,因為說真的,誰犯了錯誤,但他們似乎很滿意。他們意識到他們不想運行已經輸入的長命令,這就是我的生活陷入困境的地方。他們。打。 Ctrl。 C. 說真的,這是誰幹的? !您知道這會結束當前進程,對嗎?對嗎? ? ?

「到明年年底,我們需要 Ctrl-C 支援。」這些人和他們的要求。我會添加 Ctrl-C 支援。但兩年之內絕對不會。

所以我回到辦公桌並添加了 Ctrl-C 支援。

是什麼讓這個 REPL 值得那些有胖手指傾向的人使用呢?

我會成為一個工具嗎?

我把我的整個職業和財務未來都押在「從頭開始」建立東西上,所以我在這個專案的第一天就面臨著困境。我選擇使用 crossterm 進行關鍵檢測主要是因為跨平台支援。老實說,crossterm 非常非常好。 API 很直觀,我對 KeyModifiers 特別滿意(我們需要它來處理 Ctrl-C,我認為這是不必要的,請參見上文)。

原始模式很痛苦

我們需要它,這樣終端就不會為我們處理特殊的金鑰。但該死的,我沒有意識到它會把我們的螢幕變成一台故障的打字機。無論如何,我必須規範所有字串以在任何換行符之前添加回車符。效果很好,我對此感到非常興奮。

/// When the terminal is in raw mode, we must emit a carriage return in addition to a newline,
/// because that does not happen automatically.
fn normalize<T: Display>(err: T) -> String {
    let formatted = format!("{}", err);
    if terminal::is_raw_mode_enabled().expect("Failed to query terminal raw mode") {
        formatted.replace("\n", "\n\r")
    } else {
        formatted.to_string()
    }
}

/// Print command which will normalize newlines + carriage returns before printing.
fn print_raw<T: Display>(val: T) {
    print!("{}", normalize(val));
    io::stdout().flush().expect("Failed to flush stdout");
}
登入後複製

還有!如果您沒有在意外恐慌時停用原始模式,您的終端會話將變得不可用。我安裝了自訂恐慌處理程序來捕捉這個並玩得很好。

panic::set_hook(Box::new(|info| {
    // This line is critical!! The rest of this function is just debug info, but without this
    // line, your shell will become unusable on an unexpected panic.
    let _ = terminal::disable_raw_mode();

    if let Some(s) = info.payload().downcast_ref::<&str>() {
        eprintln!("\nPanic: {s:?}");
    } else if let Some(s) = info.payload().downcast_ref::<String>() {
        eprintln!("\nPanic: {s:?}");
    } else {
        eprintln!("\nPanic occurred!");
    }

    if let Some(location) = info.location() {
        eprintln!(
            "  in file '{}' at line {}",
            location.file(),
            location.line()
        );
    } else {
        eprintln!("  in an unknown location.");
    }

    process::exit(1);
}));
登入後複製

整合測試很有趣

在我的舊 REPL(我更喜歡它,見上文)下,我可以透過執行二進位檔案並將一些 Python 程式碼傳遞到 stdin 來測試它的整合。當使用 crossterm 時,它停止工作,我認為是由於合約糾紛。老實說,我無法完全解釋它,但是 event::read() 在標準輸入輸入提供的整合測試中會超時並失敗。所以我嘲笑了它。

pub trait TerminalIO {
    fn read_event(&mut self) -> Result<Event, io::Error>;
    fn write<T: Display>(&mut self, output: T) -> io::Result<()>;
    fn writeln<T: Display>(&mut self, output: T) -> io::Result<()>;
}

/// A mock for testing that doesn't use `crossterm`.
struct MockTerminalIO {                                                        
    /// Predefined events for testing
    events: Vec<Event>,

    /// Captured output for assertions
    output: Vec<String>,
}

impl TerminalIO for MockTerminalIO {
    fn read_event(&mut self) -> Result<Event, io::Error> {
        if self.events.is_empty() {
            Err(io::Error::new(io::ErrorKind::Other, "No more events"))
        } else {
            // remove from the front (semantically similar to VecDequeue::pop_front).
            Ok(self.events.remove(0))
        }
    }

    fn write<T: Display>(&mut self, output: T) -> io::Result<()> {
        self.output.push(format!("{}", output));
        Ok(())
    }

    fn writeln<T: Display>(&mut self, output: T) -> io::Result<()> {
        self.write(output)?;
        self.write("\n")?;
        Ok(())
    }
}
登入後複製

這導致整件事情變成了單元測試?老實說我不知道。此時,如果我 a) 在另一個二進位檔案中呼叫一個二進位文件,或者 2) 在測試中啟動伺服器/開啟連接埠/偵聽套接字,我將其稱為整合測試。如果您想在評論中留下其他定義,請不要留下,因為這聽起來很煩人。

我創建了兩個實用函數來開始。

/// Run the complete flow, from input code string to return value string. If you need any Ctrl
/// modifiers, do not use this!
fn run_and_return(input: &str) -> String {
    let mut terminal = MockTerminalIO::from_str(input);
    Repl::new().run(&mut terminal);
    terminal.return_val()
}

/// Turn an input string into a list of crossterm events so we don't have to
/// hand-compile our test.
fn string_to_events(input: &str) -> Vec<Event> {
    input
        .chars()
        .map(|c| {
            let key_code = match c {
                '\n' => KeyCode::Enter,
                _ => KeyCode::Char(c),
            };
            Event::Key(KeyEvent::new(key_code, KeyModifiers::NONE))
        })
        .collect()
}
登入後複製

使用這些,我們現在可以用相當少的樣板來測試這些常見場景。

#[test]
fn test_repl_name_error() {
    let return_val = run_and_return("e\n");
    assert!(return_val.contains("NameError: name 'e' is not defined"));
}

#[test]
fn test_repl_expr() {
    let third_from_last = run_and_return("12345\n");
    assert_eq!(third_from_last, "12345");
}

#[test]
fn test_repl_statement() {
    let return_val = run_and_return("a = 5.5\n");

    // empty string because a statement does not have a return value
    assert_eq!(return_val, "");
}

#[test]
fn test_repl_function() {
    let code = r#"
def foo():
    a = 10
    return 2 * a

foo()
"#;
    let return_val = run_and_return(code);
    assert_eq!(return_val, "20");
}

#[test]
fn test_repl_ctrl_c() {
    let mut events = string_to_events("123456789\n");
    let ctrl_c = Event::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL));
    events.insert(4, ctrl_c);
    let mut terminal = MockTerminalIO::new(events);

    Repl::new().run(&mut terminal);
    assert_eq!(terminal.return_val(), "56789");
}
登入後複製

代碼入口點讓我早上起床

我添加 REPL 的動機之一是因為我相信當您添加第二個入口點時,您的程式碼會變得更好。您實際上正在成為您的圖書館的第二個用戶,這可以幫助您更接近地理解我們都在鍵盤上尋找的完美抽象。我是認真的說這一點。

“零依賴”?

REPL 現在位於功能標誌後面,作為重新獲得管理的一種方式。我在零第三方 crate 的幫助下保持解釋 Python 程式碼的能力,這意味著 crossterm 要么需要成為例外,要么我會引入一個功能標誌。現在,如果你在沒有啟用 REPL 的情況下編譯並運行“memphis”,它會禮貌地告訴你“錯誤的構建,笨蛋。”

再見

REPL 就在這裡。你可以像這樣運行它。如果你想買的話,那聽起來就像是個騙局。祝你好起來,盡快說話。


如果您想將更多類似的貼文直接發送到您的收件匣,您可以在這裡訂閱!

別處

除了指導軟體工程師之外,我還寫了我作為成人診斷自閉症患者的經歷。更少的程式碼和相同數量的笑話。

  • 英國的節制 - From Scratch dot org

以上是適合胖手指打字的 REPL的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!