diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000..a26351a --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,52 @@ +name: Deploy GitHub Pages + +on: + push: + branches: + - master + paths: + - docs/** + - .github/workflows/gh-pages.yml + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Setup Pages + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + - name: Install mdbook + run: | + curl --fail --location --silent --show-error \ + https://github.com/rust-lang/mdBook/releases/download/v0.4.52/mdbook-v0.4.52-x86_64-unknown-linux-gnu.tar.gz \ + --output "${RUNNER_TEMP}/mdbook.tar.gz" + tar -xzf "${RUNNER_TEMP}/mdbook.tar.gz" -C "${RUNNER_TEMP}" + echo "${RUNNER_TEMP}" >> "${GITHUB_PATH}" + - name: Build with mdbook + run: mdbook build docs + - name: Upload artifact + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: docs/book + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 diff --git a/Cargo.toml b/Cargo.toml index 3062da0..4fd5e19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "sauva" version = "0.3.0" description = "Terminal Unicode Explorer" authors = ["Kyosuke Fujimoto "] -homepage = "https://github.com/lusingander/sauva" +homepage = "https://lusingander.github.io/sauva/" repository = "https://github.com/lusingander/sauva" readme = "README.md" license = "MIT" @@ -12,7 +12,7 @@ categories = ["command-line-utilities"] edition = "2024" rust-version = "1.88.0" -exclude = ["/.github", "/img", "/data", "/tools"] +exclude = ["/.github", "/docs", "/img", "/data", "/tools"] [dependencies] arboard = { version = "3.6.1", default-features = false, features = ["wayland-data-control"] } diff --git a/README.md b/README.md index 8d02c5d..3a754bb 100644 --- a/README.md +++ b/README.md @@ -5,45 +5,71 @@ Terminal Unicode Explorer 🪄 - +Sauva browsing and search demo ## About Sauva is a terminal application for searching and browsing Unicode code points. It shows Unicode properties and a glyph preview in a responsive TUI. -The bundled data is based on Unicode 17.0.0. +- Inspect Unicode properties, encodings, and decomposition mappings. +- Search by character, code point, Unicode name, or formal name alias. +- Browse planes, ranges, blocks, and code points. +- Analyze strings by code point and grapheme cluster in Sequence mode. +- Compare NFC, NFD, NFKC, and NFKD normalization forms. +- Customize colors, glyph preview fonts, and keybindings. + +The bundled data is based on Unicode 17.0.0. See [Features](https://lusingander.github.io/sauva/features/) for details and screenshots. + +## Documentation + +For detailed usage, configuration, and terminal support, see the [full documentation](https://lusingander.github.io/sauva/). ## Installation -### [Cargo](https://crates.io/crates/sauva) +If you're using Cargo: -``` +```sh cargo install --locked sauva ``` -### [Homebrew (macOS)](https://github.com/lusingander/homebrew-tap/blob/master/sauva.rb) +Homebrew and pre-built binaries are also available. See [Installation](https://lusingander.github.io/sauva/getting-started/installation.html) for all options. -``` -brew install lusingander/tap/sauva -``` +## Usage -### Downloading a binary +### Inspect a code point -Pre-built binaries are available from the [releases page](https://github.com/lusingander/sauva/releases). +Run `sauva` without arguments, or specify a character or code point: -## Usage +```sh +sauva +sauva あ +sauva U+2192 +``` -### Basic +### Analyze text in Sequence mode -Run sauva without arguments to open the default code point: +Pass a string to inspect its code points and grapheme clusters: +```sh +sauva 'Á👩‍💻' ``` -sauva + +Press Enter to inspect a selected code point, or n to compare normalization forms. + +Sauva Sequence and normalization demo + +Use `--text` when the input would otherwise be interpreted as code point notation, or `--text -` to read literal UTF-8 text from stdin: + +```sh +sauva --text 41 +printf 'Á👩‍💻' | sauva --text - ``` +See [Sequence](https://lusingander.github.io/sauva/features/sequence.html), [Normalization](https://lusingander.github.io/sauva/features/normalization.html), and [Basic Usage](https://lusingander.github.io/sauva/getting-started/basic-usage.html) for more examples. + ### Options -``` +```text Sauva - Terminal Unicode Explorer 🪄 Usage: sauva [OPTIONS] [INPUT] @@ -59,208 +85,41 @@ Options: -V, --version Print version ``` -#### Specifying `INPUT` - -A single character can be inspected directly. Text sequences can be analyzed by code point and grapheme cluster, and compared with their NFC, NFD, NFKC, and NFKD normalized forms. - -``` -sauva あ -sauva 'Á👩‍💻' -``` - -Code points can also be given in `U+` notation, `0x` notation, or as two to six hexadecimal digits. - -``` -sauva U+2192 -sauva 0x1F600 -sauva 1F600 -``` - -A one-character argument is treated as the character itself. Use a prefix for a one-digit hexadecimal value, such as `U+A` or `0xA`. - -An input made entirely of hexadecimal digits keeps the code point interpretation. Use `--text` to force literal text when the input would otherwise be interpreted as notation: - -``` -sauva -t 41 -sauva --text U+2192 -``` - -Empty text is rejected. - -#### Reading from stdin - -Pass `-` to read the input from stdin. It accepts the same text and code point formats as `INPUT`. Use `--text -` (or `-t -`) to treat stdin as literal text without code point notation parsing. - -``` -printf 'U+2192' | sauva - -printf 'Á👩‍💻' | sauva - -printf '41' | sauva --text - -printf 'A\nB\n' | sauva - -sauva --text - < input.txt -``` - -Sauva reads UTF-8 input until EOF before opening the TUI. All whitespace, including trailing LF and CRLF, is preserved. For code point notation, use `printf` without a trailing newline: `echo U+2192 | sauva -` is rejected because the newline is part of the notation. Use `--text -` to inspect that input as literal text, including its newline. Empty input and invalid UTF-8 are rejected. - -Stdin is read only when `-` is explicitly specified. Keyboard input continues to come from the terminal, so an interactive terminal is still required. Since `-` selects stdin, use `sauva U+002D` to inspect the hyphen character itself. - -#### `-g, --graphics ` - -- `auto` enables the Kitty graphics protocol for detected kitty and Ghostty terminals. -- `force` uses the Kitty graphics protocol without terminal detection. -- `iterm2` uses the iTerm2 inline image protocol. -- `off` disables glyph images. - -See [Compatibility](#compatibility) for terminal support. - -### Config - -sauva loads the first applicable configuration path in this order: - -1. The path specified by `SAUVA_CONFIG_FILE` -2. `$XDG_CONFIG_HOME/sauva/config.toml` -3. `$HOME/.config/sauva/config.toml` - -If the default file does not exist, built-in settings are used. A missing file specified by `SAUVA_CONFIG_FILE`, or an invalid configuration file, causes startup to fail. - -The complete built-in configuration can be printed without starting the TUI or loading a local configuration file: - -``` -sauva --print-default-config -sauva --print-default-config > config.toml -``` - -All settings are optional. The following example shows the main configuration areas: - -
-Configuration example - -```toml -[color] -fg = "reset" -bg = "reset" -muted = "darkgray" -accent = "cyan" -heading = "blue" -border = "darkgray" -match = "yellow" -key = "yellow" -link = "blue" - -[color.selection] -fg = "black" -bg = "cyan" - -[color.difference] -fg = "black" -bg = "yellow" - -[color.status] -info = "green" -warning = "yellow" - -[glyph_preview] -font_families = ["Iosevka", "Noto Sans"] -emoji_font_families = ["Noto Color Emoji"] -fg = "#f5f7fa" -bg = "#00000000" - -[ui] -selection_cursor = "▸" -input_cursor = { text = "|" } -``` - -UI colors accept ANSI color names, `#RRGGBB`, or an indexed color from `0` to `255`. Glyph image colors accept `#RRGGBB` and `#RRGGBBAA`. - -
- -The complete configuration structure and defaults are defined in [config.schema.json](config.schema.json). +For input interpretation, stdin handling, and graphics modes, see [Command Line Options](https://lusingander.github.io/sauva/getting-started/command-line-options.html). ### Keybindings -These are the main built-in controls. The available controls depend on the current view. - | Key | Description | | --- | --- | | F1 | Open or close contextual help | -| Ctrl+c | Quit from any view | -| q | Quit from the Inspector, Sequence, or Browse view | -| Esc | Cancel or go back; quit from the Inspector or input Sequence | -| h j k l or arrow keys | Move the selection | -| / | Search by character, code point, Unicode name, or formal name alias | -| p r b c | Browse planes, ranges, blocks, or code points | -| Enter | Open the selected item; inspect a code point from Sequence or Browse | -| Backspace | Go back in Browse; return from Inspector to its Sequence | -| y | Copy the selected Inspector value | - -Press F1 to view all controls for the current screen. +| Ctrl-C | Quit from any view | +| j / k or arrow keys | Move the selection; use Up / Down in Search | +| / | Open Search from the Inspector | +| p, r, b, c | Open Browse from the Inspector | +| Enter | Open the selected item or inspect a code point | +| Backspace | Go back in Browse or return from the Inspector to its Sequence | +| y | Copy the selected Inspector value or normalization result | +| n | Compare normalization forms from Sequence | -#### Custom keybindings +See [Keybindings](https://lusingander.github.io/sauva/keybindings/) for all controls and [Custom Keybindings](https://lusingander.github.io/sauva/keybindings/custom-keybindings.html) to customize them. -Keybindings can be replaced per command and context. A command can have multiple keys, and an empty array disables it in that context. +### Config -
-Keybinding example and syntax +Sauva loads the configuration selected by `SAUVA_CONFIG_FILE`, or `$XDG_CONFIG_HOME/sauva/config.toml` (using `~/.config` when `XDG_CONFIG_HOME` is unset or empty). -```toml -[keybindings.global] -help = ["f2"] +Print the complete built-in configuration with: -[keybindings.inspector] -next_code_point = ["l", "right", "n"] -back = ["backspace"] -browse_planes = [] +```sh +sauva --print-default-config ``` -Keys may be a single character, a named key such as `enter`, `esc`, `left`, or `f1`, or a modifier followed by one character, such as `ctrl-n`, `alt-j`, or `shift-g`. Conflicting and invalid bindings are rejected at startup. - -See [config.schema.json](config.schema.json) for all contexts and command names. - -
+See [Configurations](https://lusingander.github.io/sauva/configurations/) and [Config File Format](https://lusingander.github.io/sauva/configurations/config-file-format.html) for settings and defaults. ## Compatibility -### Operating systems - -macOS and Linux are supported. macOS uses Core Text for system font lookup. Linux uses Fontconfig when available and falls back to a best-effort font database lookup if Fontconfig cannot be loaded. - -### Terminal emulators - -Glyph previews use the [Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/) and its [Unicode placeholder](https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders) feature. The supported terminals are: - -- [kitty](https://sw.kovidgoyal.net/kitty/) -- [Ghostty](https://ghostty.org) - -Use `--graphics force` to try the Kitty protocol in another compatible terminal or when automatic detection does not enable it. - -The [iTerm2 inline image protocol](https://iterm2.com/documentation-images.html) is available through `--graphics iterm2`, but compatibility and future support are not guaranteed. It is never selected automatically. - -### Glyph preview fonts - -Sauva does not bundle fonts. Available glyphs and the selected font therefore depend on the fonts installed on the system. - -For each code point, sauva tries configured normal fonts, the system default text font, configured emoji fonts, and then system fallback fonts. Preferred fonts can be set with `glyph_preview.font_families` and `glyph_preview.emoji_font_families`. - -## Screenshots - -### Inspector - -Inspector view - -### Browse Plane / Range / Code point - -Plane browser Range browser Code point browser - -### Browse Block - -Block browser Block code point browser - -### Search - -Search results - -### Sequence +macOS and Linux are supported. Glyph images are automatically enabled in kitty and Ghostty, using the Kitty graphics protocol. The iTerm2 inline image protocol is available through `--graphics iterm2` with no guarantee of compatibility or future support. -Sequence Sequence inspector view Sequence normalization Sequence normalization result +Text information and analysis remain available without glyph images. Sauva uses installed system fonts and does not bundle fonts. See [Compatibility](https://lusingander.github.io/sauva/getting-started/compatibility.html) for details. ## License diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..7585238 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +book diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 0000000..19ea61e --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,11 @@ +[book] +authors = ["Kyosuke Fujimoto"] +language = "en" +src = "src" +title = "Sauva" +description = "Sauva Documentation" + +[output.html] +git-repository-url = "https://github.com/lusingander/sauva" +site-url = "/sauva/" +default-theme = "rust" diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 0000000..3e05f04 --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,21 @@ +# Summary + +- [Introduction](./introduction/index.md) +- [Getting Started](./getting-started/index.md) + - [Requirements](./getting-started/requirements.md) + - [Installation](./getting-started/installation.md) + - [Basic Usage](./getting-started/basic-usage.md) + - [Command Line Options](./getting-started/command-line-options.md) + - [Compatibility](./getting-started/compatibility.md) +- [Configurations](./configurations/index.md) + - [Config File Format](./configurations/config-file-format.md) + - [Glyph Preview](./configurations/glyph-preview.md) +- [Keybindings](./keybindings/index.md) + - [Custom Keybindings](./keybindings/custom-keybindings.md) +- [Features](./features/index.md) + - [Inspector](./features/inspector.md) + - [Search](./features/search.md) + - [Browse](./features/browse.md) + - [Sequence](./features/sequence.md) + - [Normalization](./features/normalization.md) +- [FAQ](./faq/index.md) diff --git a/docs/src/configurations/config-file-format.md b/docs/src/configurations/config-file-format.md new file mode 100644 index 0000000..7b2275c --- /dev/null +++ b/docs/src/configurations/config-file-format.md @@ -0,0 +1,141 @@ +# Config File Format + +All configuration settings are optional. The following example contains the default colors, glyph preview settings, and UI settings: + +```toml +[color] +fg = "reset" +bg = "reset" +muted = "darkgray" +accent = "cyan" +heading = "blue" +border = "darkgray" +match = "yellow" +key = "yellow" +link = "blue" + +[color.selection] +fg = "black" +bg = "cyan" + +[color.difference] +fg = "black" +bg = "yellow" + +[color.status] +info = "green" +warning = "yellow" + +[glyph_preview] +font_families = [] +emoji_font_families = [] +fg = "#f5f7fa" +bg = "#00000000" + +[ui] +selection_cursor = "" +input_cursor = "native" +``` + +Use `sauva --print-default-config` to print the full configuration, including every default keybinding. + +The configuration structure is also described by [config.schema.json](https://github.com/lusingander/sauva/blob/master/config.schema.json). Sauva validates the configuration at startup, including terminal cell widths and keybinding conflicts. + +## `color` + +These settings control the TUI's colors: + +| Setting | Default | Use | +| --- | --- | --- | +| `fg` | `"reset"` | Main text | +| `bg` | `"reset"` | Main background | +| `muted` | `"darkgray"` | Secondary text and labels | +| `accent` | `"cyan"` | Accented text and grapheme markers | +| `heading` | `"blue"` | Section headings | +| `border` | `"darkgray"` | Borders and scrollbars | +| `match` | `"yellow"` | Search matches | +| `key` | `"yellow"` | Key labels in help and the footer | +| `link` | `"blue"` | Link text | +| `selection.fg` | `"black"` | Selected item text | +| `selection.bg` | `"cyan"` | Selected item background | +| `difference.fg` | `"black"` | Text in changed normalization spans | +| `difference.bg` | `"yellow"` | Background of changed normalization spans | +| `status.info` | `"green"` | Informational status messages | +| `status.warning` | `"yellow"` | Warning status messages | + +Changed normalization spans are also underlined. Their colors are configured separately from the selection colors. + +UI color values are strings in one of these formats: + +- ANSI names: `reset`, `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `gray`, `darkgray`, `light-red`, `light-green`, `light-yellow`, `light-blue`, `light-magenta`, `light-cyan`, or `white`. +- RGB hexadecimal: `"#RRGGBB"`. +- Indexed color: a quoted number from `"0"` to `"255"`. + +`reset` uses the terminal's default color. UI colors do not accept an alpha channel. + +For example: + +```toml +[color] +heading = "#89b4fa" +muted = "245" + +[color.selection] +fg = "black" +bg = "light-blue" +``` + +## `glyph_preview` + +| Setting | Type | Default | +| --- | --- | --- | +| `font_families` | Array of font family names | `[]` | +| `emoji_font_families` | Array of font family names | `[]` | +| `fg` | RGB or RGBA color string | `"#f5f7fa"` | +| `bg` | RGB or RGBA color string | `"#00000000"` | + +Font lists are tried in the order given. Empty or whitespace-only family names are rejected. Image colors accept `#RRGGBB` or `#RRGGBBAA`, including an alpha channel. + +See [Glyph Preview](./glyph-preview.md) for examples and the complete font selection order. + +## `ui` + +### `selection_cursor` + +The marker shown before a selected Inspector property, list entry, or code point. + +- Type: string. +- Default: `""` (no visible marker). +- Must be empty or occupy exactly one terminal cell, without control characters. + +```toml +[ui] +selection_cursor = "▸" +``` + +### `input_cursor` + +The cursor shown in the search input. + +- Default: `"native"`, using the terminal's native cursor. +- Use `{ text = "..." }` for a text cursor. Its text must occupy exactly one terminal cell, without control characters. + +```toml +[ui] +input_cursor = { text = "|" } +``` + +## `keybindings` + +Bindings are grouped by screen context. Each command accepts an array of key strings that replaces its built-in binding. An empty array disables the command in that context. + +```toml +[keybindings.global] +help = ["f2"] + +[keybindings.inspector] +next_code_point = ["l", "right", "n"] +browse_planes = [] +``` + +See [Custom Keybindings](../keybindings/custom-keybindings.md) for contexts, command names, key formats, and validation rules. diff --git a/docs/src/configurations/glyph-preview.md b/docs/src/configurations/glyph-preview.md new file mode 100644 index 0000000..38a48d1 --- /dev/null +++ b/docs/src/configurations/glyph-preview.md @@ -0,0 +1,59 @@ +# Glyph Preview + +The glyph preview renders the selected code point as an image using a font installed on your system. When space is available, the preview panel also shows the font family, style, and version. + +The image font is selected independently of the terminal's text font. Configure it in the `[glyph_preview]` section: + +```toml +[glyph_preview] +font_families = ["Iosevka", "Noto Sans"] +emoji_font_families = ["Noto Color Emoji"] +fg = "#f5f7fa" +bg = "#00000000" +``` + +## Font Selection + +For each code point, Sauva tries: + +1. Configured normal fonts in `font_families`, in order. +2. The system default text font. +3. Configured emoji fonts in `emoji_font_families`, in order. +4. System fallback fonts. + +Missing families and fonts without a usable glyph are skipped. Both lists default to `[]`, so system fonts are used without any configuration. + +Sauva does not install fonts. Specify family names for fonts already installed on the machine. The chosen font can vary between code points and between operating systems. + +For OS-specific font lookup and terminal support, see [Compatibility](../getting-started/compatibility.md). + +## Image Colors + +- `fg` is the glyph foreground color; the default is `"#f5f7fa"`. +- `bg` is the image background color; the default is `"#00000000"`, fully transparent. + +Values accept `#RRGGBB` or `#RRGGBBAA`. For example, use an opaque background on a light terminal: + +```toml +[glyph_preview] +fg = "#202020" +bg = "#ffffff" +``` + +Color font glyphs, such as emoji, can use their own palette. + +## Special Characters + +Combining marks can be shown with a dotted circle to make their position visible. The dotted circle is a preview aid; it is not part of the character's value or the text copied from the Inspector. + +Some code points cannot produce a visible image: + +| Message | Meaning | +| --- | --- | +| `No visible pixels` | The resolved glyph is blank, as can happen with a space or invisible character | +| `Glyph unavailable` | No usable glyph was found in the available fonts | +| `Not a Unicode scalar value` | The selected code point is a surrogate and cannot be rendered | +| `Graphics disabled` | Images were disabled with `--graphics off` | +| `Graphics unavailable` | Automatic detection did not select a graphics protocol | + +Unicode details remain available in these cases. See [FAQ](../faq/index.md) for troubleshooting. diff --git a/docs/src/configurations/index.md b/docs/src/configurations/index.md new file mode 100644 index 0000000..34ad82a --- /dev/null +++ b/docs/src/configurations/index.md @@ -0,0 +1,49 @@ +# Configurations + +Sauva uses a TOML configuration file to customize colors, glyph preview fonts, presentation, and keybindings. + +## Configuration File Location + +The configuration path is selected in this order: + +1. `$SAUVA_CONFIG_FILE`, if set. +2. `$XDG_CONFIG_HOME/sauva/config.toml`, if `XDG_CONFIG_HOME` is nonempty. +3. `$HOME/.config/sauva/config.toml`. + +Only the selected file is loaded. For example, when `XDG_CONFIG_HOME` is set, Sauva does not look in `$HOME/.config` if that file is missing. + +If the default file does not exist, all built-in settings are used. An empty `SAUVA_CONFIG_FILE`, a missing file explicitly selected with it, or an invalid configuration causes startup to fail. + +All settings are optional. Unspecified settings keep their built-in defaults. Unknown fields are rejected, so misspelled setting names cause an error instead of being ignored. + +## Create a Configuration + +Print the complete default configuration: + +```sh +sauva --print-default-config +``` + +To create a file at the default location when `XDG_CONFIG_HOME` is unset: + +```sh +mkdir -p ~/.config/sauva +sauva --print-default-config > ~/.config/sauva/config.toml +``` + +This writes to the specified file; use a new file path if you already have a configuration you want to keep. + +You can also use a custom path: + +```sh +sauva --print-default-config > ./config.toml +SAUVA_CONFIG_FILE=./config.toml sauva +``` + +Printing defaults does not load the local configuration or start the TUI, so it also works when an existing configuration is invalid. + +---- + +- [Config File Format](./config-file-format.md): all configuration sections, values, and defaults. +- [Glyph Preview](./glyph-preview.md): font selection and image colors. +- [Custom Keybindings](../keybindings/custom-keybindings.md): command bindings for each screen. diff --git a/docs/src/faq/index.md b/docs/src/faq/index.md new file mode 100644 index 0000000..8aa2906 --- /dev/null +++ b/docs/src/faq/index.md @@ -0,0 +1,79 @@ +# FAQ + +## Why is the glyph preview missing? + +First, check [Compatibility](../getting-started/compatibility.md). Automatic graphics detection enables images for kitty and Ghostty. If a compatible terminal is not detected, try `sauva --graphics force`. For the iTerm2 inline image protocol, explicitly use `sauva --graphics iterm2`. + +Also check the preview's message: + +- **Graphics disabled**: start without `--graphics off`. +- **Graphics unavailable**: automatic detection did not select a protocol. +- **Glyph unavailable**: install a font with a usable glyph, or adjust the [preferred fonts](../configurations/glyph-preview.md). +- **No visible pixels**: the resolved glyph is blank. Spaces and invisible characters can have this result. +- **Not a Unicode scalar value**: surrogates cannot be rendered. +- **Preview area unavailable**: resize the terminal and try again. + +Some preview panels are hidden at smaller terminal widths. Enlarge the terminal to make room for them. Text information remains available without images. + +## Why does the glyph use a different font from my terminal? + +Sauva selects fonts independently for glyph images. It tries configured normal fonts, the system default text font, configured emoji fonts, and system fallback fonts, in that order. + +Set `glyph_preview.font_families` and `glyph_preview.emoji_font_families` to installed family names. The preview's Font and Version fields identify the font used. See [Glyph Preview](../configurations/glyph-preview.md). + +## Why does `sauva 41` open one character? + +An input containing only hexadecimal digits is interpreted as code point notation. `41` selects `U+0041`, the letter A. + +Use `sauva --text 41` to open the two literal characters in Sequence mode. A single-character argument is always treated as that character; use `sauva U+A` to inspect code point `U+000A`. + +The search field has its own input rules. See [Command Line Options](../getting-started/command-line-options.md) and [Search](../features/search.md). + +## Why does `echo U+2192 | sauva -` fail? + +`echo` adds a newline, and Sauva preserves that newline as part of stdin. It therefore cannot parse the input as code point notation. + +Use: + +```sh +printf 'U+2192' | sauva - +``` + +To examine the literal text and its newline, use: + +```sh +echo U+2192 | sauva --text - +``` + +Stdin must contain nonempty, valid UTF-8. It is read only when `-` is explicitly supplied, and Sauva waits for EOF before starting. Keyboard input still requires an interactive terminal. + +## Why does an emoji appear as several rows? + +Sequence has one row per code point. An emoji such as `👩‍💻` contains multiple code points grouped into one grapheme cluster. The gutter connects those rows and shows their shared cluster number. + +The glyph image previews the selected code point. See [Sequence](../features/sequence.md) for an example. + +## Why does copying fail? + +Sauva uses the system clipboard. The footer reports when it is unavailable, busy, or unable to accept the text. Check that a system clipboard is available in the current desktop session; an SSH or headless session may not provide one. + +A field marked unavailable, such as the encoding of a surrogate, has no copyable value. In Normalization, press y in the form comparison screen to copy the whole normalized result. + +## Why does my configuration prevent startup? + +The startup error identifies the configuration file and the problem. Check for: + +- An empty `SAUVA_CONFIG_FILE` or a nonexistent file selected by it. +- Invalid TOML or unknown field names. +- Unsupported color formats or cursor text wider than one terminal cell. +- Invalid, duplicate, conflicting, or input-reserved keybindings. + +`sauva --print-default-config` works without loading the local file and provides a valid starting point. See [Configurations](../configurations/index.md) and [Custom Keybindings](../keybindings/custom-keybindings.md). + +## Can I use Sauva without glyph images? + +Yes. Run `sauva --graphics off` to use properties, Search, Browse, Sequence, and Normalization without image previews. + +## Which Unicode version does Sauva use? + +The bundled data is based on Unicode 17.0.0. The Inspector's Unicode Version field shows this data version, while Age shows the version in which the selected character was introduced. An installed font may cover a different set of characters. diff --git a/docs/src/features/browse.md b/docs/src/features/browse.md new file mode 100644 index 0000000..ec05e3c --- /dev/null +++ b/docs/src/features/browse.md @@ -0,0 +1,52 @@ +# Browse + +Browse Unicode by numeric location or by a named block. From the Inspector, use these shortcuts: + +| Key | Starting view | +| --- | --- | +| p | Planes | +| r | Ranges in the current plane | +| b | Blocks | +| c | Code points in the current range | + +## Planes and Ranges + +This path explores Unicode by code point value: + +```text +Plane → Range → Code Point → Inspector +``` + +A plane contains 65,536 code points. Sauva divides each plane into ranges of 256 code points for browsing. These ranges are numeric subdivisions, independent of named Unicode blocks. + +![Plane browser](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/plane.png) + +![Range browser](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/range.png) + +![Code point browser](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/code-points.png) + +## Blocks + +This path explores named blocks from the Unicode Character Database: + +```text +Block → Code Point → Inspector +``` + +Blocks have different sizes and may leave gaps between them. When opening the block list from a code point in a gap, Sauva selects the next block. + +![Block browser](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/block.png) + +![Block code point browser](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/block-code-points.png) + +## Operations + +- Use j / k or Down / Up in lists. +- Use h, j, k, l or the arrow keys in a code point grid. +- Press Enter to open the next level or inspect the selected code point. +- Press g / G to jump to the first / last item. In a grid, this is the first / last code point in the range or block. +- Use Ctrl-U / Ctrl-D for larger steps in ranges, blocks, and code point grids. + +Backspace returns to the previous browse level. At the level where browsing was opened, it returns to the Inspector. Esc cancels browsing immediately and returns to the Inspector without changing its selected code point. Press q to quit. + +Code point grids include unassigned and special code points, so an entry may have no visible glyph. The Inspector provides the available property details for each entry. diff --git a/docs/src/features/index.md b/docs/src/features/index.md new file mode 100644 index 0000000..1e61a50 --- /dev/null +++ b/docs/src/features/index.md @@ -0,0 +1,9 @@ +# Features + +- [Inspector](./inspector.md): inspect one code point and copy its properties. +- [Search](./search.md): find a character by value, name, or formal alias. +- [Browse](./browse.md): explore planes, ranges, blocks, and code points. +- [Sequence](./sequence.md): examine a string's code points and grapheme clusters. +- [Normalization](./normalization.md): compare and inspect normalized text. + +Press F1 in any screen for contextual help. See [Keybindings](../keybindings/index.md) for the complete controls. diff --git a/docs/src/features/inspector.md b/docs/src/features/inspector.md new file mode 100644 index 0000000..33e497c --- /dev/null +++ b/docs/src/features/inspector.md @@ -0,0 +1,57 @@ +# Inspector + +The Inspector shows the properties of one Unicode code point. Open it at startup with a character or code point argument, or select a code point from Search, Browse, or Sequence. + +```sh +sauva あ +sauva U+2192 +``` + +![Inspector](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/inspector.png) + +## Properties + +| Section | Fields | +| --- | --- | +| Identity | Character, Code Point, Primary Name, Aliases, Plane, Block | +| Classification | General Category, Script, Age, East Asian Width, Canonical Combining Class, Bidi Class, Default Ignorable | +| Encoding | UTF-8, UTF-16, HTML Decimal, HTML Hex, Rust char, Unicode Escape | +| Normalization | Decomposition Type, Decomposition | +| Data | Unicode Version | + +Some useful distinctions: + +- **Age** is the Unicode version in which the character was introduced. **Unicode Version** is the version of Sauva's bundled data. +- **Aliases** lists formal Unicode name aliases and their types. These aliases can also be searched. +- **East Asian Width** is a Unicode property. Actual terminal cell width also depends on the terminal and its rendering rules. +- **Decomposition** shows the character's Unicode decomposition mapping. To compare the normalized forms of a string, use [Normalization](./normalization.md) from Sequence. + +The glyph panel renders the selected code point and shows the font used, when space is available. See [Glyph Preview](../configurations/glyph-preview.md) for font settings and special cases. + +## Operations + +| Keys | Action | +| --- | --- | +| h / l, Left / Right | Inspect the previous / next code point | +| j / k, Down / Up | Select a property | +| Ctrl-U / Ctrl-D | Move through properties by page | +| g / G | Select the first / last property | +| y | Copy the selected value | +| / | Open Search | +| p, r, b, c | Open Browse at the corresponding level | +| Backspace | Return to the original or normalized sequence, when one is open | +| q, Esc | Quit | + +Adjacent navigation follows code point values, even when the Inspector was opened from a sequence. Backspace returns to the sequence's existing selection. + +## Copying Values + +Select a property and press y to copy it to the system clipboard. Selecting **Character** copies the actual character, including invisible characters and combining marks. Display aids such as a dotted circle or a `` label are not included in that value. + +Unavailable values, such as an encoding for a surrogate, cannot be copied. A status message reports whether copying succeeded or why it failed. + +## Special Code Points + +Sauva can inspect the full code point range, including unassigned code points, private-use characters, and surrogates. + +Surrogates are not Unicode scalar values, so UTF-8 / UTF-16 encodings and character images are unavailable for them. Empty or invisible characters may have property data without visible pixels in the preview. diff --git a/docs/src/features/normalization.md b/docs/src/features/normalization.md new file mode 100644 index 0000000..56ac060 --- /dev/null +++ b/docs/src/features/normalization.md @@ -0,0 +1,65 @@ +# Normalization + +From [Sequence](./sequence.md), press n to compare the input with its NFC, NFD, NFKC, and NFKD normalized forms. + +```sh +sauva 'Á①ffi' +``` + +![Normalization comparison](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/sequence-normalization.png) + +## Forms + +| Form | Transformation | +| --- | --- | +| NFC | Canonical decomposition followed by canonical composition | +| NFD | Canonical decomposition | +| NFKC | Compatibility decomposition followed by canonical composition | +| NFKD | Compatibility decomposition | + +Canonical normalization handles equivalent representations, such as a precomposed accented letter and a letter followed by a combining mark. Compatibility normalization also converts characters such as circled digits and ligatures, and can remove distinctions in their original presentation. See [Unicode Normalization Forms](https://www.unicode.org/reports/tr15/) for the specification. + +Examples of individual transformations: + +| Input | Form | Result | +| --- | --- | --- | +| `A` + `U+0301` | NFC | `Á` (`U+00C1`) | +| `Á` (`U+00C1`) | NFD | `A` + `U+0301` | +| `①` (`U+2460`) | NFKC | `1` (`U+0031`) | +| `ffi` (`U+FB03`) | NFKD | `ffi` | + +## Comparison + +The comparison screen shows: + +- The original text and its code point sequence. +- Whether each form changes the input. +- Code point (`CP`), UTF-8 byte (`Bytes`), and grapheme cluster (`GC`) counts for each form. +- The selected form's result, code point sequence, and number of changed regions. + +Changed regions use the configured difference colors and underlining. Spaces and invisible characters are represented with display aids; the copied result contains the actual normalized text. + +## Inspect a Result + +Select a form and press Enter to open its code point list. + +![Normalization result](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/sequence-normalization-result.png) + +The result is shown alongside the original code point list. As you select a result code point, the corresponding region in the original is highlighted and brought into view. This makes compositions and expansions easier to follow. + +Press Enter again to inspect a result code point. Backspace in the Inspector returns to the result list; Backspace or Esc in the result list returns to the form comparison. + +The original input is preserved throughout these operations. + +## Operations + +| Keys | In form comparison | In a result list | +| --- | --- | --- | +| j / k, Down / Up | Select a form | Select a code point | +| g / G | Select NFC / NFKD | Select the first / last code point | +| Enter | Open the selected result | Inspect the selected code point | +| y | Copy the exact normalized text | — | +| Backspace, Esc | Return to the original sequence | Return to form comparison | +| q | Quit | Quit | + +Copying includes whitespace and newlines in the normalized result. Select a form and press y in the comparison screen to copy the whole result. diff --git a/docs/src/features/search.md b/docs/src/features/search.md new file mode 100644 index 0000000..d121299 --- /dev/null +++ b/docs/src/features/search.md @@ -0,0 +1,43 @@ +# Search + +Press / in the Inspector to search by character, code point, Unicode name, or formal name alias. + +![Search results](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/filter.png) + +## Queries + +| Query | Behavior | +| --- | --- | +| `あ` | Find the literal character | +| `A` | Find the letter A | +| `U+2192` | Find that code point | +| `U+A` | Find `U+000A` | +| `2192` | Find that code point and matching names or aliases | +| `arrow` | Match Unicode names and formal aliases containing the query | +| `LF` | Find matching formal aliases and names | + +Surrounding ASCII whitespace is removed from a search query. Name and alias matching ignores ASCII case and normalizes runs of ASCII whitespace to a single space. + +A `U+` prefix explicitly selects code point notation and accepts one to six hexadecimal digits. A bare hexadecimal query is interpreted as notation only when it contains four to six digits; its name and alias matches are included as well. + +Search notation differs from [command line input](../getting-started/command-line-options.md): the search field does not recognize the `0x` prefix, and a query such as `41` searches names and aliases. Use `U+0041` for an unambiguous code point query. + +Invalid `U+` notation and out-of-range code points display an error. A valid query with no matching results displays an empty result list. + +## Result Order + +Direct character or code point matches appear first. Name and alias matches follow, ordered by exact match, prefix match, and substring match. For equal match types, primary name matches precede alias matches, and code point order breaks remaining ties. + +A code point appears only once even if both its name and an alias match. Matching text is highlighted. + +## Operations + +| Keys | Action | +| --- | --- | +| Up / Down, Ctrl-P / Ctrl-N | Select the previous / next result | +| Enter | Open the selected result in the Inspector | +| Esc | Close Search | +| F1 | Open contextual help | +| Ctrl-C | Quit | + +Character keys edit the query, so j, k, and q are entered as text. Search results update as the query changes. Reopening Search keeps the previous query and selection. diff --git a/docs/src/features/sequence.md b/docs/src/features/sequence.md new file mode 100644 index 0000000..735b4dc --- /dev/null +++ b/docs/src/features/sequence.md @@ -0,0 +1,70 @@ +# Sequence + +Sequence mode analyzes a string as an ordered list of code points, grouped into grapheme clusters. It is useful for examining combining marks, emoji sequences, and invisible characters. + +## Start in Sequence Mode + +Pass text containing multiple code points: + +```sh +sauva 'Á👩‍💻' +``` + +Use `--text` to prevent code point notation parsing: + +```sh +sauva --text 41 +sauva --text U+2192 +``` + +Read text from a pipe or file: + +```sh +printf 'Á👩‍💻' | sauva --text - +sauva --text - < input.txt +``` + +A single code point opens the Inspector. Sequence opens for multiple code points, even when they form a single visible character. Empty input is rejected. + +See [Command Line Options](../getting-started/command-line-options.md) for the complete input rules. + +![Sequence and normalization demo](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/demo-sequence.gif) + +## Code Points and Grapheme Clusters + +A code point is one Unicode value, such as `U+0041`. A grapheme cluster groups code points into a unit that often corresponds to a user-perceived character. Sauva uses extended grapheme cluster boundaries as described in [Unicode Text Segmentation](https://www.unicode.org/reports/tr29/). + +For example, the string `Á👩‍💻` contains five code points and two grapheme clusters: + +| Cluster | Code points | +| --- | --- | +| `Á` | `U+0041` LATIN CAPITAL LETTER A + `U+0301` COMBINING ACUTE ACCENT | +| `👩‍💻` | `U+1F469` WOMAN + `U+200D` ZERO WIDTH JOINER + `U+1F4BB` PERSONAL COMPUTER | + +The accented A in the command above is `U+0041` followed by `U+0301`. + +## Reading the Screen + +![Sequence](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/sequence.png) + +- Each row shows the input position, code point, display representation, and Unicode name. +- The left gutter numbers grapheme clusters and connects their member code points. A dot marks a cluster containing one code point. +- The selected cluster is emphasized in the gutter. +- The heading shows code point and grapheme counts, abbreviated as `CP` and `GC` in compact layouts. +- When space is available, the selection panel shows the selected code point's properties and position within its cluster. The glyph image renders that individual code point. + +Input order, repeated characters, spaces, and newlines are preserved. Labels and dotted circles make otherwise hard-to-see characters visible in the list. + +## Operations + +| Keys | Action | +| --- | --- | +| j / k, Down / Up | Select a code point | +| g / G | Select the first / last code point | +| Enter | Open the selected code point in the Inspector | +| n | Compare normalization forms | +| q, Esc | Quit | + +![Sequence Inspector](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/sequence-inspector.png) + +Backspace in the Inspector returns to the sequence. From Sequence, press n to open [Normalization](./normalization.md). diff --git a/docs/src/getting-started/basic-usage.md b/docs/src/getting-started/basic-usage.md new file mode 100644 index 0000000..ea3892e --- /dev/null +++ b/docs/src/getting-started/basic-usage.md @@ -0,0 +1,64 @@ +# Basic Usage + +## Inspect a Code Point + +Run Sauva without arguments to open the default code point: + +```sh +sauva +``` + +You can also specify a character or code point: + +```sh +sauva あ +sauva U+2192 +sauva 0x1F600 +``` + +The [Inspector](../features/inspector.md) shows the selected code point's properties and glyph preview. + +- Use h / l or Left / Right to inspect adjacent code points. +- Use j / k or Down / Up to select a property, then y to copy its value. +- Press / to [search](../features/search.md), or p, r, b, or c to [browse](../features/browse.md). +- Press F1 for help with the current screen. +- Press q or Esc to quit from the Inspector. Ctrl-C quits from any screen. + +## Analyze a String + +Pass a string containing multiple code points to open [Sequence](../features/sequence.md) mode: + +```sh +sauva 'Á👩‍💻' +``` + +The list preserves the input order and groups code points into grapheme clusters. + +1. Use j / k or Down / Up to select a code point. +2. Press Enter to open it in the Inspector, then Backspace to return. +3. Press n in Sequence to compare [normalization forms](../features/normalization.md). + +![Sequence and normalization demo](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/demo-sequence.gif) + +Use `--text` when a string would otherwise be interpreted as code point notation: + +```sh +sauva --text 41 +sauva --text U+2192 +``` + +A single code point opens the Inspector, even with `--text`. + +## Read from a Pipe or File + +Specify `-` to read UTF-8 input from stdin: + +```sh +printf 'Á👩‍💻' | sauva - +printf 'U+2192' | sauva - +sauva --text - < input.txt +``` + +Sauva waits for EOF before opening the TUI. Whitespace and trailing newlines are preserved. For code point notation, use `printf` without a trailing newline; use `--text -` to inspect text exactly as supplied. + +For all input rules and options, see [Command Line Options](./command-line-options.md). The complete controls are listed in [Keybindings](../keybindings/index.md). diff --git a/docs/src/getting-started/command-line-options.md b/docs/src/getting-started/command-line-options.md new file mode 100644 index 0000000..a9ad766 --- /dev/null +++ b/docs/src/getting-started/command-line-options.md @@ -0,0 +1,100 @@ +# Command Line Options + +```text +Sauva - Terminal Unicode Explorer 🪄 + +Usage: sauva [OPTIONS] [INPUT] + +Arguments: + [INPUT] Text or code point to inspect, or - to read stdin + +Options: + -t, --text Treat the input as literal text, without code point notation parsing; - reads stdin + -g, --graphics Control glyph preview graphics [default: auto] [possible values: auto, force, iterm2, off] + --print-default-config Print the complete default configuration to standard output + -h, --help Print help + -V, --version Print version +``` + +## `INPUT` + +Input is interpreted in this order: + +1. A single character opens that character in the Inspector. +2. `U+` or `0x` followed by one to six hexadecimal digits selects a code point. The prefixes are case-insensitive. +3. An argument made entirely of hexadecimal digits is interpreted as a code point. It must contain two to six digits. +4. Other nonempty text opens Sequence mode if it contains multiple code points. + +Code point values must be between `U+0000` and `U+10FFFF`. + +| Example | Interpretation | +| --- | --- | +| `sauva あ` | Inspect `U+3042` | +| `sauva A` | Inspect the letter A (`U+0041`) | +| `sauva U+A` | Inspect `U+000A` | +| `sauva 41` | Inspect `U+0041` | +| `sauva 1F600` | Inspect `U+1F600` | +| `sauva 'Á👩‍💻'` | Analyze a sequence of code points | + +A hexadecimal-only argument that fails notation parsing is rejected; it does not fall back to literal text. For example, use `--text` for a string of seven hexadecimal digits. + +## `-t, --text ` + +Treat the input as literal text without parsing code point notation: + +```sh +sauva --text 41 +sauva -t U+2192 +``` + +These inputs open Sequence with the characters `4`, `1`, or `U`, `+`, `2`, `1`, `9`, `2`, respectively. A single character still opens the Inspector. Empty text is rejected. + +`INPUT` and `--text` cannot be supplied together. + +## Reading from Stdin + +Use `-` as `INPUT` to read stdin using the same interpretation rules. Use `--text -` to read literal text instead. + +```sh +printf 'U+2192' | sauva - +printf 'Á👩‍💻' | sauva - +printf '41' | sauva --text - +printf 'A\nB\n' | sauva - +sauva --text - < input.txt +``` + +Sauva reads UTF-8 input until EOF before starting the TUI. All whitespace, including trailing LF and CRLF, is preserved. Empty input and invalid UTF-8 are rejected. + +In particular, `echo U+2192 | sauva -` is rejected because the newline is part of the code point notation. Use `printf 'U+2192' | sauva -` for notation, or `echo U+2192 | sauva --text -` to inspect the literal text including its newline. + +Stdin is read only when `-` is explicitly supplied. An interactive terminal is still required for keyboard input. + +Since `-` selects stdin, use `sauva U+002D` to inspect the hyphen character itself. + +## `-g, --graphics ` + +| Mode | Behavior | +| --- | --- | +| `auto` | Use the Kitty graphics protocol in detected kitty and Ghostty terminals; otherwise omit glyph images | +| `force` | Use the Kitty graphics protocol without terminal detection | +| `iterm2` | Use the iTerm2 inline image protocol; never selected automatically | +| `off` | Disable glyph images | + +See [Compatibility](./compatibility.md) for support details. + +## `--print-default-config` + +Print the complete built-in configuration, including default keybindings, and exit: + +```sh +sauva --print-default-config +sauva --print-default-config > config.toml +``` + +This option does not start the TUI or load your local configuration. It must be used on its own. + +See [Configurations](../configurations/index.md) for where to put the file. + +## `-h, --help` and `-V, --version` + +Print command line help or the installed application version and exit. diff --git a/docs/src/getting-started/compatibility.md b/docs/src/getting-started/compatibility.md new file mode 100644 index 0000000..f0101ee --- /dev/null +++ b/docs/src/getting-started/compatibility.md @@ -0,0 +1,54 @@ +# Compatibility + +## Operating Systems + +macOS and Linux are supported. Windows is not currently supported. + +## Terminal Graphics + +Sauva uses the [Kitty graphics protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/) with [Unicode placeholders](https://sw.kovidgoyal.net/kitty/graphics-protocol/#unicode-placeholders) for glyph images. + +| Terminal | Graphics mode | Support | +| --- | --- | --- | +| [kitty](https://sw.kovidgoyal.net/kitty/) | `auto` | Supported | +| [Ghostty](https://ghostty.org) | `auto` | Supported | +| Other terminals supporting Kitty graphics and Unicode placeholders | `force` | Can be tried; compatibility is not guaranteed | +| Terminals supporting the iTerm2 inline image protocol | `iterm2` | Explicit opt-in; compatibility and future support are not guaranteed | +| Other terminals | `off` or `auto` | Text information remains available without glyph images | + +Automatic detection recognizes `TERM=xterm-kitty` and `TERM=xterm-ghostty`. + +If a compatible terminal is not detected, try: + +```sh +sauva --graphics force +``` + +This skips detection; it does not add graphics support to the terminal. A terminal multiplexer or a changed `TERM` value may affect detection and image display. + +The [iTerm2 inline image protocol](https://iterm2.com/documentation-images.html) is available only when selected explicitly: + +```sh +sauva --graphics iterm2 +``` + +Sixel is not implemented. To disable images, use: + +```sh +sauva --graphics off +``` + +## Fonts + +Sauva does not bundle fonts. The available glyphs and their appearance depend on the fonts installed on the system. + +- macOS uses Core Text to find system fonts. +- Linux uses Fontconfig when available. If Fontconfig cannot be loaded, Sauva uses a best-effort font database lookup. + +Preferred text and emoji fonts can be configured separately. See [Glyph Preview](../configurations/glyph-preview.md) for the selection order and settings. + +The font used for a glyph image can differ from the terminal's own text font. Terminal-rendered characters and the image preview can therefore look different. + +## Layout + +The TUI adapts to the terminal size. Some preview panels are hidden at smaller widths, while the main text view remains available. See [Requirements](./requirements.md) for the minimum size. diff --git a/docs/src/getting-started/index.md b/docs/src/getting-started/index.md new file mode 100644 index 0000000..80bbca1 --- /dev/null +++ b/docs/src/getting-started/index.md @@ -0,0 +1,7 @@ +# Getting Started + +- [Requirements](./requirements.md): supported systems and terminal requirements. +- [Installation](./installation.md): install with Cargo, Homebrew, or a release binary. +- [Basic Usage](./basic-usage.md): inspect a character or analyze a string. +- [Command Line Options](./command-line-options.md): input formats and startup options. +- [Compatibility](./compatibility.md): terminal graphics and system fonts. diff --git a/docs/src/getting-started/installation.md b/docs/src/getting-started/installation.md new file mode 100644 index 0000000..34afda8 --- /dev/null +++ b/docs/src/getting-started/installation.md @@ -0,0 +1,40 @@ +# Installation + +## Cargo + +Install from [crates.io](https://crates.io/crates/sauva): + +```sh +cargo install --locked sauva +``` + +See [Requirements](./requirements.md) for the minimum Rust version. + +## Homebrew (macOS) + +```sh +brew install lusingander/tap/sauva +``` + +The formula is available in [lusingander/homebrew-tap](https://github.com/lusingander/homebrew-tap/blob/master/sauva.rb). + +## Release Binaries + +Download an archive for your operating system and architecture from the [releases page](https://github.com/lusingander/sauva/releases). + +| Operating system | Architecture | Target | +| --- | --- | --- | +| macOS | Apple Silicon | `aarch64-apple-darwin` | +| macOS | Intel | `x86_64-apple-darwin` | +| Linux | ARM64 | `aarch64-unknown-linux-gnu` or `aarch64-unknown-linux-musl` | +| Linux | x86-64 | `x86_64-unknown-linux-gnu` or `x86_64-unknown-linux-musl` | + +Extract the archive and put the `sauva` executable in a directory on your `PATH`. + +## Verify the Installation + +```sh +sauva --version +``` + +Continue with [Basic Usage](./basic-usage.md). diff --git a/docs/src/getting-started/requirements.md b/docs/src/getting-started/requirements.md new file mode 100644 index 0000000..c4ab906 --- /dev/null +++ b/docs/src/getting-started/requirements.md @@ -0,0 +1,23 @@ +# Requirements + +## Operating Systems + +Sauva supports macOS and Linux. Windows is not currently supported. + +## Terminal + +An interactive terminal is required, including when the input is read from a pipe or file. Sauva reads the supplied input first, then accepts keyboard input from the terminal. + +The minimum terminal size is 60 columns by 16 rows. A larger terminal, such as 100 columns by 30 rows, provides room for additional preview panels. + +## Glyph Images + +Glyph images require a compatible terminal and fonts installed on your system. Sauva does not bundle fonts. + +kitty and Ghostty are supported for automatic graphics detection. See [Compatibility](./compatibility.md) for the available protocols and modes. + +Unicode properties, browsing, searching, and text analysis remain available when glyph images are disabled or unavailable. + +## Building from Source + +Installing with Cargo requires Rust 1.88.0 or later. A release binary does not require Rust to be installed. diff --git a/docs/src/introduction/index.md b/docs/src/introduction/index.md new file mode 100644 index 0000000..831849d --- /dev/null +++ b/docs/src/introduction/index.md @@ -0,0 +1,32 @@ +# Introduction + +Sauva is a terminal application for searching and browsing Unicode code points. It shows Unicode properties and a glyph preview in a responsive TUI. + +![Sauva demo](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/demo.gif) + +![Sequence and normalization demo](https://raw.githubusercontent.com/lusingander/sauva/refs/heads/master/img/demo-sequence.gif) + +## Key Features + +- Inspect Unicode names, properties, encodings, and decomposition mappings. +- Search by character, code point, Unicode name, or formal name alias. +- Browse Unicode planes, ranges, blocks, and code points. +- Analyze text as code points and grapheme clusters in Sequence mode. +- Compare NFC, NFD, NFKC, and NFKD normalization forms. +- Preview glyphs using installed fonts and a terminal graphics protocol. +- Customize colors, fonts, and keybindings. + +For example, use Sauva to identify an invisible character, find a symbol by name, or examine how an emoji or accented letter is represented in a string. + +## Unicode Data + +Sauva bundles data based on Unicode 17.0.0. Browsing and property lookup work offline, and the bundled data version is shown in the Inspector. + +Glyphs depend on the fonts installed on your system. A character can have Unicode data even when no installed font can display it. + +---- + +Built with Rust and [Ratatui](https://github.com/ratatui/ratatui). +Sauva is available on [GitHub](https://github.com/lusingander/sauva) under the [MIT License](https://github.com/lusingander/sauva/blob/master/LICENSE). + +The generated Unicode data is distributed under the [Unicode License v3](https://github.com/lusingander/sauva/blob/master/UNICODE-LICENSE.txt). See the [third-party notices](https://github.com/lusingander/sauva/blob/master/THIRD_PARTY_NOTICES.md) for details. diff --git a/docs/src/keybindings/custom-keybindings.md b/docs/src/keybindings/custom-keybindings.md new file mode 100644 index 0000000..be704af --- /dev/null +++ b/docs/src/keybindings/custom-keybindings.md @@ -0,0 +1,100 @@ +# Custom Keybindings + +Customize bindings in the `[keybindings.]` sections of [the configuration file](../configurations/index.md). + +```toml +[keybindings.global] +help = ["f2"] + +[keybindings.inspector] +next_code_point = ["l", "right", "n"] +back = ["backspace"] +browse_planes = [] +``` + +- Each array replaces the keys for one command in one context. +- Omitted commands keep their built-in bindings. +- Multiple keys can be assigned to one command. +- An empty array disables that command in that context. +- Help and the footer display the resulting bindings. + +Use `sauva --print-default-config` to see every context, command, and default binding. The command names are also listed in [Keybindings](./index.md). + +## Contexts + +| Context | Applies to | +| --- | --- | +| `global` | Every screen; supports `quit` and `help` | +| `inspector` | Code point properties | +| `search` | Search results | +| `sequence` | The original input sequence | +| `normalization` | Normalization form comparison | +| `normalization_result` | A normalized sequence | +| `browse_plane` | Plane list | +| `browse_range` | Range list | +| `browse_block` | Block list | +| `browse_code_points` | Code point grid | +| `help` | Contextual help | + +Global bindings remain active in all contexts. When help is open, only its own bindings and the global bindings are active. + +Disabling a local command does not disable the global binding for the same action. For example, disabling `keybindings.inspector.quit` leaves the default global Ctrl-C binding available. + +## Key Formats + +### Characters + +Use a single non-whitespace character, such as a, 1, /, or -. + +Uppercase ASCII letters represent Shift plus that letter. For example, `G` and `shift-g` both describe Shift-G. + +### Named Keys + +| Name | Key | +| --- | --- | +| `space` | Space | +| `enter` | Enter | +| `esc` | Escape | +| `tab` | Tab | +| `backspace` | Backspace | +| `delete` | Delete | +| `left`, `right`, `up`, `down` | Left, Right, Up, Down | +| `home`, `end` | Home and End | +| `pageup`, `pagedown` | Page Up and Page Down | +| `f1` through `f12` | F1 through F12 | + +Names and modifier prefixes are lowercase. + +### Modifiers + +Use `ctrl-`, `alt-`, or `shift-` followed by a single character. Modifiers can be combined, without repeating the same modifier: + +```toml +[keybindings.inspector] +next_code_point = ["ctrl-n", "alt-l"] +last = ["shift-g"] +``` + +Modifiers apply only to character keys. Forms such as `ctrl-left`, `shift-tab`, or `alt-f1` are not supported. Use a lowercase ASCII letter after a modifier; `ctrl-G` is invalid. + +The terminal must deliver the chosen key combination to Sauva. Some combinations are handled by the terminal itself. + +## Conflicts and Validation + +Sauva rejects invalid bindings at startup, including: + +- Unknown contexts, command names, or key names. +- Duplicate keys for a command, including equivalent forms such as `G` and `shift-g`. +- A key assigned to two different commands in the same context. +- A local binding that uses a global key for a different command. +- Global or search bindings that would intercept search input editing. + +For example, binding j to `search.next_result` is rejected because j is needed for typing the query. Use an arrow key or another combination that is not an input editing key. + +When moving a key from one command to another, also remove it from the original command's binding. For example: + +```toml +[keybindings.inspector] +next_code_point = ["right"] +copy_value = ["y", "l"] +``` diff --git a/docs/src/keybindings/index.md b/docs/src/keybindings/index.md new file mode 100644 index 0000000..c372140 --- /dev/null +++ b/docs/src/keybindings/index.md @@ -0,0 +1,152 @@ +# Keybindings + +Press F1 to open or close contextual help. Help and the footer show the active bindings, including your custom settings. + +The tables below list the built-in bindings and the command names used in [Custom Keybindings](./custom-keybindings.md). + +## Global + +These bindings apply in every screen, including help. + +| Command | Keys | Action | +| --- | --- | --- | +| `quit` | Ctrl-C | Quit Sauva | +| `help` | F1 | Open or close help | + +## Inspector + +Context: `inspector`. + +| Command | Keys | Action | +| --- | --- | --- | +| `quit` | q, Esc | Quit | +| `previous_code_point` | h, Left | Inspect the previous code point | +| `next_code_point` | l, Right | Inspect the next code point | +| `move_up` | k, Up | Select the previous property | +| `move_down` | j, Down | Select the next property | +| `page_up` | Ctrl-U | Move properties up one page | +| `page_down` | Ctrl-D | Move properties down one page | +| `first` | g | Select the first property | +| `last` | G | Select the last property | +| `copy_value` | y | Copy the selected property value | +| `search` | / | Open search | +| `browse_planes` | p | Browse planes | +| `browse_ranges` | r | Browse ranges in the current plane | +| `browse_blocks` | b | Browse blocks | +| `browse_code_points` | c | Browse code points in the current range | +| `back` | Backspace | Return to the sequence, when one is open | + +## Search + +Context: `search`. + +| Command | Keys | Action | +| --- | --- | --- | +| `previous_result` | Up, Ctrl-P | Select the previous result | +| `next_result` | Down, Ctrl-N | Select the next result | +| `inspect_result` | Enter | Open the selected result in the Inspector | +| `close` | Esc | Close search | + +Character keys edit the query, including j, k, and q. Use the arrow keys or Ctrl-P / Ctrl-N to select results. Search input editing keys are reserved and cannot be assigned to commands. + +## Sequence + +Context: `sequence`. + +| Command | Keys | Action | +| --- | --- | --- | +| `quit` | q, Esc | Quit | +| `move_up` | k, Up | Select the previous code point | +| `move_down` | j, Down | Select the next code point | +| `first` | g | Select the first code point | +| `last` | G | Select the last code point | +| `activate` | Enter | Inspect the selected code point | +| `normalize` | n | Compare normalization forms | + +## Normalization + +Context: `normalization`. + +| Command | Keys | Action | +| --- | --- | --- | +| `quit` | q | Quit | +| `close` | Esc | Return to the original sequence | +| `back` | Backspace | Return to the original sequence | +| `move_up` | k, Up | Select the previous normalization form | +| `move_down` | j, Down | Select the next normalization form | +| `first` | g | Select NFC | +| `last` | G | Select NFKD | +| `activate` | Enter | Inspect the selected normalization result | +| `copy_value` | y | Copy the exact normalized text | + +## Normalization Result + +Context: `normalization_result`. + +| Command | Keys | Action | +| --- | --- | --- | +| `quit` | q | Quit | +| `close` | Esc | Return to normalization comparison | +| `back` | Backspace | Return to normalization comparison | +| `move_up` | k, Up | Select the previous code point | +| `move_down` | j, Down | Select the next code point | +| `first` | g | Select the first code point | +| `last` | G | Select the last code point | +| `activate` | Enter | Inspect the selected code point | + +Use Backspace in the Inspector to return to this result. + +## Browse Lists + +Contexts: `browse_plane`, `browse_range`, and `browse_block`. + +| Command | Keys | Action | +| --- | --- | --- | +| `quit` | q | Quit | +| `close` | Esc | Cancel browsing and return to the Inspector | +| `back` | Backspace | Return to the previous browse level, or to the Inspector at the top | +| `move_up` | k, Up | Select the previous item | +| `move_down` | j, Down | Select the next item | +| `first` | g | Select the first item | +| `last` | G | Select the last item | +| `activate` | Enter | Open ranges in a plane, or code points in a range or block | + +`browse_range` and `browse_block` additionally support: + +| Command | Keys | Action | +| --- | --- | --- | +| `page_up` | Ctrl-U | Move backward by a large step | +| `page_down` | Ctrl-D | Move forward by a large step | + +## Browse Code Points + +Context: `browse_code_points`. + +| Command | Keys | Action | +| --- | --- | --- | +| `quit` | q | Quit | +| `close` | Esc | Cancel browsing and return to the Inspector | +| `back` | Backspace | Return to the previous browse level | +| `move_left` | h, Left | Move left | +| `move_right` | l, Right | Move right | +| `move_up` | k, Up | Move up one row | +| `move_down` | j, Down | Move down one row | +| `page_up` | Ctrl-U | Move backward by a large step | +| `page_down` | Ctrl-D | Move forward by a large step | +| `first` | g | Select the first code point in the range or block | +| `last` | G | Select the last code point in the range or block | +| `activate` | Enter | Inspect the selected code point | + +## Help + +Context: `help`. The underlying screen's commands are inactive while help is open; global bindings remain active. + +| Command | Keys | Action | +| --- | --- | --- | +| `close` | Esc | Close help | +| `move_up` | k, Up | Scroll up one line | +| `move_down` | j, Down | Scroll down one line | +| `page_up` | Ctrl-U | Scroll up one page | +| `page_down` | Ctrl-D | Scroll down one page | +| `first` | g | Go to the beginning | +| `last` | G | Go to the end |