https://github.com/trananhtung/slice-ansi
Slice a string by terminal display column, preserving ANSI styling and wide (CJK) characters. A Rust take on Node's slice-ansi.
https://github.com/trananhtung/slice-ansi
ansi no-std rust slice substring terminal unicode width
Last synced: 16 days ago
JSON representation
Slice a string by terminal display column, preserving ANSI styling and wide (CJK) characters. A Rust take on Node's slice-ansi.
- Host: GitHub
- URL: https://github.com/trananhtung/slice-ansi
- Owner: trananhtung
- License: apache-2.0
- Created: 2026-06-22T02:12:14.000Z (20 days ago)
- Default Branch: master
- Last Pushed: 2026-06-22T14:47:30.000Z (19 days ago)
- Last Synced: 2026-06-22T16:26:43.531Z (19 days ago)
- Topics: ansi, no-std, rust, slice, substring, terminal, unicode, width
- Language: Rust
- Size: 15.6 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE-APACHE
Awesome Lists containing this project
README
# slice-ansi
[](#contributors-)
[](https://crates.io/crates/slice-ansi)
[](https://docs.rs/slice-ansi)
[](https://github.com/trananhtung/slice-ansi/actions/workflows/ci.yml)
[](#license)
**Slice a string by terminal display column** — correctly handling **wide (CJK)
characters** (two columns) and **ANSI escape sequences** (zero columns). Styling
active at the cut is re-applied to the slice and reset at the end, so the result
renders exactly like that part of the original. A Rust take on Node's
[`slice-ansi`](https://www.npmjs.com/package/slice-ansi).
```rust
use slice_ansi::{slice, slice_from, width};
assert_eq!(slice("hello", 1, 3), "el");
assert_eq!(slice("古池や蛙", 0, 4), "古池"); // wide chars
assert_eq!(slice("\u{1b}[31mhello\u{1b}[0m", 1, 3), "\u{1b}[31mel\u{1b}[0m"); // style preserved
assert_eq!(width("\u{1b}[31mhi\u{1b}[0m"), 2); // ANSI = zero width
```
## Why slice-ansi?
`ansi-width` measures a styled string's width, but there's no crate to *extract* a
column range from one. Naively slicing bytes splits escape sequences and wide
characters and drops the active color. `slice-ansi` is the focused piece — pull a
window out of a styled, wide-character line for tables, columns, scrolling viewports,
and TUIs. It pairs with [`cli-truncate`](https://crates.io/crates/cli-truncate)
(which keeps a width-bounded prefix).
```toml
[dependencies]
slice-ansi = "0.1"
```
## API
| Item | Purpose |
| --- | --- |
| `slice(text, start, end)` | Substring covering display columns `[start, end)` |
| `slice_from(text, start)` | Slice from `start` to the end of the string |
| `width(text)` | Display width in columns (ANSI-ignored, wide chars = 2) |
## Behavior
- Columns are half-open `[start, end)`; `end` is clamped to the string width and
`start >= end` yields `""`. Output width never exceeds `end - start`.
- A wide character that straddles either boundary is excluded, so the result is
never a broken half-glyph.
- SGR styling active at `start` is re-emitted at the front of the slice, and a reset
(`\x1b[0m`) is appended when styling is still open at the end — color can't leak,
and a reset already inside the slice isn't duplicated. An OSC 8 hyperlink open at
the cut is likewise re-opened and closed, so links stay balanced.
- Escape sequences (CSI, OSC, DCS/etc., two-byte/nF escapes, 8-bit C1) are
recognized as zero width and never split. Combining marks travel with their base
character.
- Limitations: attribute-off SGR codes (`39`/`49`, `22`–`29`) aren't collapsed, so a
slice may carry a redundant (but correctly rendering) style; and a cut *inside* a
multi-codepoint grapheme cluster (a ZWJ emoji) may split it.
## `no_std`
`#![no_std]` (needs only `alloc`); the single dependency is `unicode-width`.
## Contributors ✨
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome — code, docs, bug reports, ideas, reviews! See the [emoji key](https://allcontributors.org/docs/en/emoji-key) for how each contribution is recognized, and open a PR or issue to get involved.
Thanks goes to these wonderful people:
## License
Licensed under either of [Apache-2.0](LICENSE-APACHE) or [MIT](LICENSE-MIT) at
your option.