Skip to content

Repository files navigation

nshedit

nshedit is a safe Rust reimplementation of libedit: line editing, history, tokenization, completion, terminal rendering, and the histedit.h C API.

The Rust-native editor is the implementation. C representations, callbacks, and lifetime obligations are isolated in a separate ABI adapter; operating system calls are isolated in a small platform crate. The detailed compatibility corpus is retained under docs/spec/port; there is no second C implementation in the repository.

Status

Target Rust API C compatibility
Linux (x86_64-unknown-linux-gnu) Supported and conformance-gated ELF shared/static library, generated headers, libedit compatibility names, and end-to-end loader tests
macOS (x86_64 and Apple silicon) Supported and native-acceptance-gated Mach-O shared/static library, generated headers, libedit compatibility names, and native install, link, and runtime tests
Windows Supported and native-acceptance-gated Not provided; the libedit C ABI remains POSIX-only

The Linux row is an exact target contract, not an any Linux promise. Other architectures, x32, musl, and Android are unsupported and rejected at compile time; adding one requires its own platform layouts and acceptance evidence. In particular, x86_64-unknown-linux-musl drops the cdylib required by the Linux C compatibility product.

The exported Readline functions are libedit's Readline compatibility surface, not a complete implementation of GNU Readline. In particular, nshedit does not install itself as libreadline.so.8.

The project is pre-release (0.0.0). Pin a Git revision when using it as a dependency.

Quick start

With a current stable Rust toolchain installed:

git clone https://github.com/necessary-nu/nshedit.git
cd nshedit
cargo run -p nshedit --example repl

The repository deliberately has no toolchain override. The native crates (nshedit, nshedit-plat, and nshterm) build on current stable Rust and do not declare an MSRV. Only nshedit-abi declares an MSRV: Rust 1.99, for its C-variadic exports. On a host whose default stable toolchain is older than 1.99, use an installed current nightly explicitly for commands that select the ABI crate or the whole workspace. Once the selected stable compiler is 1.99 or newer, the corresponding unqualified cargo commands work as well.

The example is a complete safe Rust consumer with editing, history, completion, terminal resize handling, and explicit terminal restoration. See crates/nshedit/examples/repl.rs.

To use the native crate directly from Git:

[dependencies]
nshedit = { git = "https://github.com/necessary-nu/nshedit" }

The native API is intentionally host-driven:

  • Editor<TerminalControl> owns line state and the terminal lifecycle.
  • ReadDriver yields typed ReadStep effects for input, prompts, history, completion, signals, and external commands.
  • The embedding application performs those effects and resumes the driver.
  • Text preserves Unicode scalar values, raw bytes, and opaque code points without forcing them through a C string representation.

C library

Build the shared and static ABI artifacts with:

cargo +nightly build -p nshedit-abi --release

On x86_64-unknown-linux-gnu this produces target/release/libnshedit.so; on macOS it produces target/release/libnshedit.dylib. Both supported POSIX targets also produce target/release/libnshedit.a. The committed, generated headers are:

The committed C export contract, shared by ELF and Mach-O builds, is crates/nshedit-abi/exports.txt.

The installer lays out the platform's versioned library, headers, pkg-config metadata, and libedit compatibility names. Linux receives libedit.so, libedit.so.0, and libedit.so.2; macOS receives libedit.dylib and libedit.3.dylib. Pass --no-compat to install only the libnshedit names. Preview a staged installation before writing it:

./packaging/install.sh \
  --prefix "$PWD/target/stage" \
  --profile release \
  --dry-run

./packaging/install.sh \
  --prefix "$PWD/target/stage" \
  --profile release

Installing the compatibility names into a system library directory is meant to shadow the system libedit for newly started processes. Use a staging prefix first, inspect the printed links, and use --no-compat when only libnshedit should be visible.

Workspace

Crate Purpose
nshedit Safe Rust editor, history, tokenizer, completion, and rendering API
nshedit-abi Opaque C adapter exporting the libedit and libedit-provided Readline ABIs
nshedit-plat Typed platform boundary for terminal, signal, and user database operations
nshterm Pure-Rust terminfo discovery, parsing, capability lookup, and parameter expansion

The detailed libedit compatibility rules live in docs/spec/port. They document the contract implemented by the Rust crates and exercised through the generated C interface.

Testing

The native crates' ordinary stable quality gates are:

cargo fmt --all -- --check
cargo clippy -p nshedit -p nshedit-plat -p nshterm --all-targets -- -D warnings
cargo build -p nshedit -p nshedit-plat -p nshterm
cargo test -p nshedit -p nshedit-plat -p nshterm --all-targets

The full workspace adds nshedit-abi. While the default stable toolchain is older than its Rust 1.99 MSRV, run those gates with an installed nightly:

cargo +nightly clippy --workspace --all-targets -- -D warnings
cargo +nightly build --workspace
cargo +nightly test --workspace

Generate the native Rust API documentation with every rustdoc warning denied:

RUSTDOCFLAGS='-D warnings' cargo doc --workspace --no-deps

The nshedit-abi library target is intentionally omitted from rustdoc. It is a C-only adapter whose public documentation is the generated headers and export manifest above; the native nshedit crate owns the Rust API docs.

The x86_64-unknown-linux-gnu ABI tests run by default. They compare the built symbol table with the committed export contract, compile direct C consumers against the generated headers, exercise defined handling of historically unsafe inputs, and verify the staged installer and loader layout.

For the same checks with a stage-by-stage report:

rustup run nightly ./conformance/run.sh

The full conformance harness requires an x86_64-unknown-linux-gnu host. It expects a C compiler, pkg-config, standard ELF/binutils tools, and installed terminfo entries. It does not require Autotools or a system libedit. The rustup run wrapper selects nightly for the unqualified workspace Cargo command inside the script; it is unnecessary when the default compiler is Rust 1.99 or newer.

From a Linux development host, compile every workspace crate and test target for both supported Darwin architectures with:

rustup run nightly ./ci/darwin-cross-check.sh

Native macOS acceptance builds and tests the workspace, checks the Mach-O export set and install name, stages both installer modes, and compiles, links, and runs the unchanged C consumer through -ledit:

rustup run nightly ./ci/macos-acceptance.sh

CI runs that script on both Apple silicon and Intel macOS hosts. Windows CI likewise exercises the native editor against a real console, ConPTY, and redirected streams with:

./ci/windows-acceptance.sh

The Windows script requires a Windows host and NSHEDIT_REPL_EXE pointing to the built repl.exe, as configured by the workflow.

Compatibility policy

The source of truth is the detailed compatibility corpus plus the actual Rust implementation. Generated headers and the export manifest freeze the C-facing shape; Rust tests and direct C consumers exercise maintained behaviour. Reference-defined no-ops and unsupported Readline operations remain compatible no-ops. Deliberate safety fixes and divergences are recorded in docs/errata.md.

The architecture and compatibility decisions are kept under plan/decisions, and the executable specification is under docs/spec.

License

nshedit, nshedit-abi, nshedit-plat, and the libedit-derived Rust code are available under the BSD 3-Clause License.

nshterm is derived from Rust's term crate and remains dual-licensed under MIT or Apache-2.0.

About

A Rust port of NetBSD libedit, providing line editing for nsh

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages