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.
| 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.
With a current stable Rust toolchain installed:
git clone https://github.com/necessary-nu/nshedit.git
cd nshedit
cargo run -p nshedit --example replThe 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.ReadDriveryields typedReadStepeffects for input, prompts, history, completion, signals, and external commands.- The embedding application performs those effects and resumes the driver.
Textpreserves Unicode scalar values, raw bytes, and opaque code points without forcing them through a C string representation.
Build the shared and static ABI artifacts with:
cargo +nightly build -p nshedit-abi --releaseOn 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 releaseInstalling 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.
| 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.
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-targetsThe 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 --workspaceGenerate the native Rust API documentation with every rustdoc warning denied:
RUSTDOCFLAGS='-D warnings' cargo doc --workspace --no-depsThe 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.shThe 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.shNative 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.shCI 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.shThe Windows script requires a Windows host and NSHEDIT_REPL_EXE pointing to
the built repl.exe, as configured by the workflow.
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.
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.