A minimal readline with multiline and async support

Overview

RustyLine Async

Docs

A minimal readline with multiline and async support.

Inspired by rustyline , async-readline & termion-async-input. Built using crossterm

Features

  • Simple UTF8 (Note: currently panics when using compound characters)
  • Multiline Editing
  • Ctrl-C, Ctrl-D are returned as Err(Interrupt) and Err(Eof) respectively.
  • Ctrl-U to clear line before cursor
  • Ctrl-left & right to move between words
  • Ctrl-L clear screen

Feel free to PR to add more features!

Example:

cargo run --package readline

rustyline-async

Comments
  • Output to SharedWriter not shown when `break`ing after `writeln`

    Output to SharedWriter not shown when `break`ing after `writeln`

    Environment:

    • macOS 11.6.8
    • Terminal.app (TERM=xterm-256color)
    • Rust 1.65.0
    • rustyline-async commit f9817e7 cloned from GitHub

    When running the readline example in this repository, if I hit Ctrl-D, the program exits immediately without printing the "Exiting..." message, like so:

    Screen Shot 2022-11-19 at 15 37 18

    If I remove the break in the Err(ReadlineError::Eof) branch, the message is printed.

    Similarly, if I add a break to the Err(ReadlineError::Interrupted) branch, pressing Ctrl-C results in the "^C" message not being printed — along with an additional prompt being printed before my shell command prompt, like so:

    Screen Shot 2022-11-19 at 15 41 39

    Adding rl.flush()?; or Write::flush(&mut stdout)?; after the call to writeln doesn't help.

    Note that this isn't just limited to Eof and Interrupted, either. When running the following code:

    use rustyline_async::{Readline, ReadlineError};
    use std::io::Write;
    use std::time::Duration;
    use tokio::time::sleep;
    
    #[tokio::main]
    async fn main() -> Result<(), ReadlineError> {
        let (mut rl, mut stdout) = Readline::new("> ".into())?;
        loop {
            tokio::select! {
                _ = sleep(Duration::from_secs(3)) => {
                    writeln!(stdout, "Message received!")?;
                }
                cmd = rl.readline() => match cmd {
                    Ok(line) => {
                        writeln!(stdout, "{line:?}")?;
                        rl.add_history_entry(line.clone());
                        if line == "quit" {
                            break;
                        }
                    }
                    Err(ReadlineError::Eof) => {
                        writeln!(stdout, "<EOF>")?;
                        break;
                    }
                    Err(ReadlineError::Interrupted) => {
                        writeln!(stdout, "<Ctrl-C>")?;
                        break;
                    }
                    Err(e) => {
                        writeln!(stdout, "Error: {e:?}")?;
                        break;
                    }
                }
            }
        }
        Ok(())
    }
    

    typing "quit" will result in the program exiting without outputting "quit", yet it still outputs the next readline prompt (which ends up prepended to my shell prompt), like so:

    Screen Shot 2022-11-19 at 16 12 05
    opened by jwodder 4
  • Implement a history

    Implement a history

    This implements a simple optional history, that can be manually appended to through a function on Readline.

    History entries can be recalled by using the arrow keys. I chose to make the history unchangeable, although there are shells that allow the user to change history entries.

    Usage

    A Readline with history can be created using Readline::with_history(prompt, max_size). New history entries can be appended using Readline::add_history_entry(entry).

    Up for debate

    • Should the history really be unchangeable?
    • Should this be hidden behind a feature flag?
    • The implementation currently uses a Mutex to ensure thread-safety of appending new entries. Alternatives:
      • Use a RwLock instead, currently not really useful as there aren't really any read-only accesses.
      • Remove the thread-safety and let the user handle mutable concurrent accesses to Readline
    • Should there be a way to get (read-only) access to the history from Readline?
    • Should this be separated into its own file? (Should be a general consideration in this library :) )
    • Should the constructor be changed into accepting struct/builder for the history size (and future additional parameters)?
    • The VecDeque used is currently created through default. Technically a capacity could be specified based on the max_size. This could consume unnecessarily large memory amounts when the max_size is high, but the history's actual length is low.
    opened by Siphalor 3
  • Add bindings for jumping to the start and end

    Add bindings for jumping to the start and end

    This implements functionality when pressing Home or End keys to go the beginning and end of the input respectively.

    This also adds Ctrl-A and Ctrl-E as the well-known Emacs-based aliases for these actions.

    opened by Siphalor 3
  • Add a license

    Add a license

    Currently, this library has no explicit license, resulting in a strong copyright.

    As you're welcoming contributions in the README, I suspect this to be an oversight.

    You can check https://choosealicense.com/ for a simple overview over some free licenses.

    Personally, I prefer using the MIT or Apache 2.0 licenses, but the final choice is up to you (and probably partially me because I contributed without noticing it and therefore probably have copyright on my stuff ^^ - but I'll go with any free license).

    opened by Siphalor 2
  • Allow all key modifiers for typing text - Fixes Windows issues with special characters

    Allow all key modifiers for typing text - Fixes Windows issues with special characters

    This PR fixes an important issue: Currently this library prevents users from entering special characters on Windows!


    The previous code specifies the KeyModifiers explicitly:

    https://github.com/zyansheep/rustyline-async/blob/da40db1dd0d12babc46f74b5268aa15618433ade/src/line.rs#L167-L174

    There are two flaws with this design:

    1. A lot of international keyboard layouts rely on modifier keys to reach certain special keys. Especially AltGr is used relatively often for that on international keyboard layouts. For example on typical German layouts, the [ is only reachable via AltGr + 8. Since some terminals (for example Windows terminals) send AltGr as Ctrl + Alt, these special keys wouldn't be matched by a simple KeyModifiers::NONE | KeyModifiers::SHIFT.

    2. Building up on 1: One would think that explicitly allowing modifiers: KeyModifiers::CONTROL | KeyModifiers::ALT would match for this case. This is not the case! KeyModifiers are implemented as an enum using the bitflags crate. Since the underlying type is an enum the bitor operator (|) will only perform a logical or in a match expression. There seems to be no easy way to check for a combination of modifier keys in a match branch.

    My solution moves the whole regular input code down below the control keys. This means that input with any modifiers other than plain CONTROL will be handled as normal input.

    While Ctrl + Alt + Something isn't typically used for commands in terminals (Compare rustyline's actions), Ctrl + Shift + Something isn't that unusual and should maybe be captured in the future.

    If you decide that you want to check the modifier combinations more closely in the code, then you'll probably have to drop them from the match and resort to some ol' if-checking.

    opened by Siphalor 0
  • Simplify examples

    Simplify examples

    The examples were previously their own subprojects.

    This is arguably overcomplicated and unintuitive as they won't be easily executable with cargo run --example.

    This pr simply moves the respective main.rs files up to the examples directory and adds async-std, log and simplelog as dev-dependenvies to the Cargo.toml.

    opened by Siphalor 0
  • Prompt & input remain on screen after hitting Ctrl-C

    Prompt & input remain on screen after hitting Ctrl-C

    When a line of input is terminated by pressing Ctrl-C, the prompt and any text entered so far will remain on the screen even as new lines are printed below them. This is unlike lines terminated by pressing Enter, for which the line and prompt disappear, and it leads to a messy-looking interface.

    Program that can be used to reproduce this behavior:

    use rustyline_async::{Readline, ReadlineError};
    use std::io::Write;
    use std::time::Duration;
    use tokio::time::sleep;
    
    #[tokio::main]
    async fn main() -> Result<(), ReadlineError> {
        let (mut rl, mut stdout) = Readline::new("prompt> ".into())?;
        loop {
            tokio::select! {
                _ = sleep(Duration::from_secs(1)) => {
                    writeln!(stdout, "Message received!")?;
                }
                cmd = rl.readline() => match cmd {
                    Ok(line) => {
                        writeln!(stdout, "You entered: {line:?}")?;
                        rl.add_history_entry(line.clone());
                        if line == "quit" {
                            break;
                        }
                    }
                    Err(ReadlineError::Eof) => {
                        writeln!(stdout, "<EOF>")?;
                        break;
                    }
                    Err(ReadlineError::Interrupted) => {writeln!(stdout, "^C")?; continue; }
                    Err(e) => {
                        writeln!(stdout, "Error: {e:?}")?;
                        break;
                    }
                }
            }
        }
        rl.flush()?;
        Ok(())
    }
    

    Recording of this behavior:

    cancel-bug

    (Incidentally, if no text is printed in response to the Ctrl-C, things get a bit messed up on the next output; perhaps that should be a separate issue.)

    opened by jwodder 2
  • How should history work.

    How should history work.

    Issue on implementation details for readline history:

    Up for debate

    • Should the history really be unchangeable?
    • Should this be hidden behind a feature flag?
    • The implementation (#4) currently uses a Mutex to ensure thread-safety of appending new entries. Failing to acquire a lock (which should only be possible, if another thread panicked with the lock), will currently panic. Alternatives:
      • Pass lock errors through as a new type of error
      • Use a RwLock instead, currently not really useful as there aren't really any read-only accesses.
      • Remove the thread-safety and let the user handle mutable concurrent accesses to Readline
    • Should there be a way to get (read-only) access to the history from Readline?
    • Should this be separated into its own file? (Should be a general consideration in this library :) )
    • Should the constructor be changed into accepting struct/builder for the history size (and future additional parameters)?
    • The VecDeque used is currently created through default. Technically a capacity could be specified based on the max_size. This could consume unnecessarily large memory amounts when the max_size is high, but the history's actual length is low.

    My Thoughts

    Should the history really be unchangeable?

    Probably... A history is a history because it happened in the past, and past in unchangeable.

    Should this be hidden behind a feature flag?

    Nahh

    The implementation currently uses a Mutex to ensure thread-safety of appending new entries. Failing to acquire a lock (which should only be possible, if another thread panicked with the lock), will currently panic. Alternatives: Pass lock errors through as a new type of error Use a RwLock instead, currently not really useful as there aren't really any read-only accesses. Remove the thread-safety and let the user handle mutable concurrent accesses to Readline Should there be a way to get (read-only) access to the history from Readline?

    My "perfect" history implementation would look something along the lines of :

    1. append-from-anywhere & read-from-anywhere like boxcar
    2. storage-agnostic system that allows for either in-memory history or sync-to-file history a. The downside sync-to-file is how its done, do we use Seek to look for newlines between history entries? Or do we manually serialize & deserialize the whole file whenever we want to sync? seeking & appending would be faster, but what happens if we want max history sizes? then the whole file needs to be rewritten which may require loading the entire thing into memory.

    Should this be separated into its own file? (Should be a general consideration in this library :) )

    Definitely, maybe even its own library to allow for other people to use it.

    Should the constructor be changed into accepting struct/builder for the history size (and future additional parameters)?

    That is a good idea, and is basically what rustyline does, which goes with the theme of trying to match their api.

    Also to consider: do we want to have history searching (which would probably necessitate a BTree of some kind) or history deduplication? This is how rustyline does it, no async tho: https://docs.rs/rustyline/6.3.0/src/rustyline/history.rs.html#25-30 I'm curious how ion does it, but I cannot grok their codebase...

    opened by zyansheep 3
Releases(v0.3.0)
Owner
Zyansheep
Zyansheep
A readline-like library in Rust.

liner A Rust library offering readline-like functionality. CONTRIBUTING.md Featues Autosuggestions Emacs and Vi keybindings Multi-line editing History

Liam 70 Jun 19, 2022
Readline Implementation in Rust

RustyLine Readline implementation in Rust that is based on Antirez' Linenoise Supported Platforms Unix (tested on FreeBSD, Linux and macOS) Windows cm

Katsu Kawakami 1.1k Dec 29, 2022
A readline replacement written in Rust

A readline replacement written in Rust Basic example // Create a default reedline object to handle user input use reedline::{DefaultPrompt, Reedline,

JT 292 Jan 9, 2023
Cross-platform pseudoterminal (PTY/ConPTY) implementation with async support

pseudoterminal The pseudoterminal crate is a versatile pseudoterminal (PTY) implementation designed for Rust, offering asynchronous capabilities. This

Michael 3 Sep 15, 2023
A hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf.

xplr A hackable, minimal, fast TUI file explorer, stealing ideas from nnn and fzf. [Quickstart] [Features] [Plugins] [Documentation] [Upgrade Guide] [

Arijit Basu 2.6k Jan 1, 2023
⚡️ Lightning-fast and minimal calendar command line. Written in Rust 🦀

⚡️ Lightning-fast and minimal calendar command line. It's similar to cal. Written in Rust ??

Arthur Henrique 36 Jan 1, 2023
☄🌌️ The minimal, blazing-fast, and infinitely customizable prompt for any shell

☄??️ The minimal, blazing-fast, and infinitely customizable prompt for any shell

Starship Command 31.6k Dec 30, 2022
82 fun and easy to use, lightweight, spinners for Rust, with minimal overhead.

Spinners for Rust 82 fun and easy to use, lightweight, spinners for Rust, with minimal overhead, all the way from simple dots, to fun emoji based "spi

Juliette Cordor 2 May 17, 2022
Minimal and blazing-fast file server. For real, this time.

Zy Minimal and blazing-fast file server. For real, this time. Features Single Page Application support Partial responses (Range support) Cross-Origin

Miraculous Owonubi 17 Dec 18, 2022
MinMon - an opinionated minimal monitoring and alarming tool

MinMon - an opinionated minimal monitoring and alarming tool (for Linux) This tool is just a single binary and a config file. No database, no GUI, no

Florian Wickert 177 Jan 5, 2023
A minimal browser with a super simple rendering engine for HTML and CSS, using Rust.

minimal-browser A minimal browser with a super simple rendering engine for HTML and CSS, using Rust. Credit: https://github.com/mbrubeck and https://l

Federico Baldini 3 Jan 15, 2023
Style terminal outputs in a minimal, macro-based, and dead simple way.

sty ?? $\mathbb{\color{red}{Style \ } \color{lightblue}{terminal}\ \color{black}{outputs \ }\color{gray}{\ in\ a} \color{magenta}{\ minimal}\color{gra

斯人 5 Apr 18, 2024
Russh - Async (tokio) SSH2 client and server rimplementation

Russh Async (tokio) SSH2 client and server rimplementation. This is a fork of Thrussh by Pierre-Étienne Meunier which adds: More safety guarantees AES

Warptech Industries 113 Dec 28, 2022
A simple Rust library for OpenAI API, free from complex async operations and redundant dependencies.

OpenAI API for Rust A community-maintained library provides a simple and convenient way to interact with the OpenAI API. No complex async and redundan

null 6 Apr 4, 2023
Fully-typed, async, reusable state management and synchronization for Dioxus 🧬

dioxus-query ?? ⚡ Fully-typed, async, reusable state management and synchronization for Dioxus ??. Inspired by TanStack Query. ⚠️ Work in progress ⚠️

Marc Espín 9 Aug 7, 2023
Rust Server Components. JSX-like syntax and async out of the box.

RSCx - Rust Server Components RSCx is a server-side HTML rendering engine library with a neat developer experience and great performance. Features: al

Antonio Pitasi 21 Sep 19, 2023
A parser combinator that is fully statically dispatched and supports both sync/async.

XParse A parser combinator that is fully statically dispatched and supports both sync & async parsing. How to use An example of a very simple JSON par

Alsein 4 Nov 27, 2023
A minimal CLI framework written in Rust

seahorse A minimal CLI framework written in Rust Features Easy to use No dependencies Typed flags(Bool, String, Int, Float) Documentation Here Usage T

Keisuke Toyota 223 Dec 30, 2022
A minimal argument parser

Pieces An argument parser built with control in mind. Parsing The results you get are dependent on what order you parse in. If you want to say only pa

ibx34 3 Sep 30, 2021