Compare commits

..

5 Commits

Author SHA1 Message Date
Josh McKinney
1ff292742c fix: change Cell::EMPTY to Cell::empty() and fix underline_color bug 2024-10-14 18:25:06 -07:00
Josh McKinney
e084c9f013 add breaking change doc 2024-10-14 18:25:06 -07:00
Josh McKinney
f08640c53b feat: cache the symbol width in the cell
This leads to more than a 50% speedup
2024-10-14 18:25:06 -07:00
Josh McKinney
2b391ac15d perf: only calculate current symbol width once
This is a 20% performance improvement on buffer diff on my M2 MBP.

```
buffer/diff             time:   [100.26 µs 100.69 µs 101.15 µs]
                        change: [-18.007% -17.489% -16.929%] (p = 0.00 < 0.05)
                        Performance has improved.
```
2024-10-14 18:25:06 -07:00
Josh McKinney
99ef8651aa perf: add buffer diff benchmark 2024-10-14 18:23:55 -07:00
442 changed files with 14939 additions and 42116 deletions

View File

@@ -1,2 +0,0 @@
[alias]
xtask = "run --package xtask --"

View File

@@ -1,115 +0,0 @@
# skip entries from <https://github.com/ratatui/ratatui/pull/1652>
50af9a5d80ed5446f3e6cc554911f606580edde9
272e9709c6eed45cd7e0c183624b7898f4e0ae69
adc8fdc35aa57d6dad2ae8dd30ec2e9256576c09
31711dbf82a4c7bb3b78692da34d9f469725dd6e
b6356aa7a529a491d63dd6628b8985adae337f16
885558b6f89df642317d39c5b44c94c742d1e0c8
6440eb9d76379340953420629ab0a3d9039d6c48
3870583ea868452191857f9bda97a3d5c35d0a4f
487edc8399683fb8a9a66359729c015780d248f2
250c222cc4aaab09184a28efc68f75ca03133794
590a392ab11c1a215767614931036781f4cf6a29
6443f7408af4a8834bc68cd35d2ba9be47e45f38
8339cce10a51c9c951b3c9750d527d80168626eb
ac342231c344e893f2f630ee38167aab28c736a6
0fb103680028a6e26a1923f87b60bde51acaec4f
4335a90a00aeeedb69a92442c7f2711727944017
71480242a926f98e9081ed6e2dc8c381757b3a42
e1a31db55913bd690bcaef380e9dbc3b6a5dc175
38490ff8da6f11e309ba1bcb3e88a562f7c953c5
a4bb143e4767137d6a9e9927d3da66562611f86f
bc73f2dcbe5ea48fc4d1555a8e931f40d7b0e03f
77dea441e5637b1c428c2aa71ea67fb3aac20c12
06af14107e70e49a4cba3babb8ab0a0c57b4bdf8
9acdab32df69517b93dd2b861b85586d47c71540
2e684c6500be61fbe69744d183c8086564ac0051
b7f8ec0ff9659473d936eae53a57cd9de38cec1f
31a2c4548c304270a8c852f19baa7a4eaac5e75c
57b681b053c019b66e0ed92959638997fea731b1
131b9ec41751163d43d94564363247b60f031486
8b32f82b4dd526580d00fa13f053bf507e8ea933
76d1e5b1733d47a7f05acf563db26cb1a66b540d
57a0a34f924e0d488c9e9f917900e677c3488dc4
12aa58601ddd0704256c56019bd2c7139d41f7a2
2dc81833c60951d16f9bd60f3da003edfc9a11f5
20e41f1d1da6db8abbc2504814531d4d97bdf94b
68b55a12a29e70ebfcc063c2d5d5845de3b5a27e
693003314a25e792ffc5d53146b28bfb6a4582e3
63441e259bc38b56e0369197bed14788b2cb3d54
fa88152c808eeb6c9d9b3662361aab1e57e1b1bf
30d9daa59b1843786cde00e25c3e69cfe818b80b
92540b2f6ab25f3a5400aebb28af3c498ac793a4
29edc3a7a38c512611a80cf5d8d42027558419b2
819499d6ffc0e8453ed3220067645933a4882a74
4756526829a4e849d9e256b6cf821eb66afe3ade
8f35437d5ad78d31cd45c4af888f20f0b5ab4196
39bd72b1f702dadb1ebbaf4e77ad2fada166ac49
c6d2cee3e967c9234176c5229858512b0c79d6a9
bd90e3d928e0f9f0b915933ebbc32c2256fe8cfa
b820c0c7e4c576c1e39b5e482a8aac08076a039c
74194759756bb111b4da3e9a5cdb968275a2fab0
38b2f27efe0e1829bc503df7fd64b94b7bb80d97
21aa3232d762d6e3f81f15fc5b66ba462385ac05
903bb0ae32d22393783edfda96db900739864f0a
5cee13ab6d9c49751cf9283d9099e37f0cc3632c
0a0997702dd4cf2217160f5652f5c39cbd4a1010
778f2f5ec511bef431b54157242b91d083ea9840
7d23bd2ceaf96e81972b5f746fdcba0d17f6391f
2c02a56bce31519386303571e0b66b7d4beb378e
f33d51e7d9ccf9fe52ec3289d04d97c722d9ee17
91cd81aaa032887bb2327bc3fe3cad6b3c9fbacf
5d5a1ccb0b4e2f293f215ce026fba33f1c069689
6b9417db5f2adbdc60e9dd8dc5acbbe2a1f77ba9
dce1e4b138eb1333c9e773bacd579a8cdddd73fb
b4aacb045e2200896b0d0136a2b8688b47828d73
dcba0bcd5d5d6e33ddc1fa94ebb94819fdda600e
6f52350ecfb62e3a5bac16f0824e74b757cc6cd2
2c6f324b9aa5034771e00758b143fd8df94d859d
bf0210602948f8d26ae323996fe7b22fb218a446
07aff91b015b5e7e0504680c12edbce70d7dba1e
f6d49dde14af73ed467d75d8f6ec0f502db2908f
9a7467b30576d5cb7491ea6e09efcae97eadf9bb
a0c35f1d7bcce10e092582b95f5b0a3f20ad7bf3
d24747d46982192b575a40b8cc18d1c948fac3d7
8060f7bc578b29dde6ca0c4c64569f9c73218f46
0dc5b2d2e0aa6438ffc1b3965b1ab31c721adbcc
8ecdd892f53d7db95bbb53a61700d36e3fcefdd4
570c35868147a2400a13331e85d562d1ef96a011
3855c3a84a77037aeee40dbe9e52454fb1f9afee
93372f35c1669da0138ca776890f3ff3d38a6539
6cf08d4a2f0398856fd593f50bf077fd59b08230
f78d3bfec32d07c1124eee8d0249477ce3fb0884
204307fa50aaaa373946342084f7fd3af39f3cd3
c50b01d098e5ab405a50c3e14e858da27d606e8f
f71d1ac73e8290f37d55a67d6a6507a3653ec174
ae2868c0e0b1ac8b5126fd43269383fa533d87b5
be8def963956c605bca28bcd8df673bd7ec3740b
4ac4d9d3ab97176d71e287bcdd6d41e66f2f7ccb
fafabb8dab84e9460a076199ea646262e51c855b
2f97d35bd8618e8c0cc006cb1d4a9b151c1b9b4a
39d5a745acbbd3510de707d4e7c471c17e02ae59
a1acdcdc4c002390a76f01699cfa006a36cf3f56
ad54cf29ad1a4335ba208fb94a8fc5dfbba260e3
c7649575e7b199794be4252f79da80aaecdcee28
8913e2ce1f40d451ddb4527f08ec75f198d5063c
1b9e310300f22bfc72364f027a9caeddadf61a99
89d7dd46031511f0556b2d29ab34035f42e3a24c
ca4fa0b9bf5ba707aa0447ba7c38fbacdadb7eec
8cecfdf2f6dd5b0de507f79b469517cc0fb42add
7eeb6afb3dbc56e52f9387a74f826b186cd19137
d0f75eb371a96f8d5f174e23de074efd840e9e44
f8a70ea9da8e6df2bf7a5f74cce45615fc292afe
f28b9730061bffadb9d87ad63edf7d10b245d2c1
afc5cf2140f22fea6bd6933dd0f9c302229a1980
b75df78cdca58d5dca0c51fb8e106067aa6cb752
28f5a6dbd4091aa3efa86eed6767eeb44a655f0f
345e6a1ebd853858463a33953585ce407a60378c
c45a4de47c601554f6b981d211181468b4798e41
bbaa9a5432ff6ad5518344123c3b56f349347e99
f804c90f96221f334371ccd01b0e6df7b1cfc1e8
16ba867c5877d8c97968987ecb5f8bff966d0a82
38a1474ca12aa6a796afc1e277882d997a999e14
92c4078413fc79fcc83f5d3d8708abb58696ff1a
d4415204e1eb3aed2a74a722aeaaa274975dd2d7
e48bcf5f21f14acb27996fdc02231c140f5b817c

View File

@@ -32,17 +32,17 @@ schema_pattern = "(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)
type = "list"
name = "change_type"
choices = [
{ value = "build", name = "build: Changes that affect the build system or external dependencies (example scopes: pip, docker, npm)", key = "b" },
{ value = "chore", name = "chore: A modification that generally does not fall into any other category", key = "c" },
{ value = "ci", name = "ci: Changes to our CI configuration files and scripts (example scopes: GitLabCI)", key = "i" },
{ value = "docs", name = "docs: Documentation only changes", key = "d" },
{ value = "feat", name = "feat: A new feature.", key = "f" },
{ value = "fix", name = "fix: A bug fix.", key = "x" },
{ value = "perf", name = "perf: A code change that improves performance", key = "p" },
{ value = "refactor", name = "refactor: A code change that neither fixes a bug nor adds a feature", key = "r" },
{ value = "revert", name = "revert: Revert previous commits", key = "v" },
{ value = "style", name = "style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)", key = "s" },
{ value = "test", name = "test: Adding missing or correcting existing tests", key = "t" },
{ value = "build", name = "build: Changes that affect the build system or external dependencies (example scopes: pip, docker, npm)", key = "b" },
{ value = "chore", name = "chore: A modification that generally does not fall into any other category", key = "c" },
{ value = "ci", name = "ci: Changes to our CI configuration files and scripts (example scopes: GitLabCI)", key = "i" },
{ value = "docs", name = "docs: Documentation only changes", key = "d" },
{ value = "feat", name = "feat: A new feature.", key = "f" },
{ value = "fix", name = "fix: A bug fix.", key = "x" },
{ value = "perf", name = "perf: A code change that improves performance", key = "p" },
{ value = "refactor", name = "refactor: A code change that neither fixes a bug nor adds a feature", key = "r" },
{ value = "revert", name = "revert: Revert previous commits", key = "v" },
{ value = "style", name = "style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)", key = "s" },
{ value = "test", name = "test: Adding missing or correcting existing tests", key = "t" },
]
message = "Select the type of change you are committing"

2
.github/FUNDING.yml vendored
View File

@@ -1,2 +0,0 @@
github: ratatui
open_collective: ratatui

View File

@@ -13,43 +13,27 @@ A detailed and complete issue is more likely to be processed quickly.
-->
## Description
<!--
A clear and concise description of what the bug is.
-->
## To Reproduce
<!--
Try to reduce the issue to a simple code sample exhibiting the problem.
Ideally, fork the project and add a test or an example.
-->
## Expected behavior
<!--
A clear and concise description of what you expected to happen.
-->
## Screenshots
<!--
If applicable, add screenshots, gifs or videos to help explain your problem.
-->
## Are you willing to contribute a fix?
<!--
If you would like to work on a fix, check one of the boxes below. Maintainers can help point
you to the right place in the codebase.
-->
- [ ] I am willing to open a PR for this bug.
- [ ] I can try to investigate, but I will need guidance.
- [ ] I am not able to work on a fix right now.
## Environment
<!--
Add a description of the systems where you are observing the issue. For example:
- OS: Linux
@@ -66,7 +50,6 @@ Add a description of the systems where you are observing the issue. For example:
- Backend:
## Additional context
<!--
Add any other context about the problem here.
If you already looked into the issue, include all the leads you have explored.

View File

@@ -8,13 +8,11 @@ assignees: ''
---
## Problem
<!--
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-->
## Solution
<!--
A clear and concise description of what you want to happen.
Things to consider:
@@ -24,24 +22,11 @@ Things to consider:
-->
## Alternatives
<!--
A clear and concise description of any alternative solutions or features you've considered.
-->
## Are you willing to contribute an implementation?
<!--
If you would like to work on this, check one of the boxes below. Maintainers can help refine
the scope and discuss approach.
-->
- [ ] I am willing to open a PR implementing this.
- [ ] I can try to implement it, but I will need guidance.
- [ ] I am not able to implement this right now.
## Additional context
<!--
Add any other context or screenshots about the feature request here.
-->

View File

@@ -1,81 +0,0 @@
# GitHub Copilot Code Review Instructions
## General Review Principles
### Pull Request Size and Scope
- **Flag large PRs**: Comment if a PR changes more than 500 lines or touches many unrelated areas
- **Suggest splitting**: Recommend breaking large changes into smaller, focused PRs
- **Question scope creep**: Ask about unrelated changes that seem outside the PR's stated purpose
### Code Quality and Style
- **Check adherence to style guidelines**: Verify changes follow the project's Rust conventions in [CONTRIBUTING.md](https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#code-formatting)
- **Verify xtask compliance**: Ensure `cargo xtask format` and `cargo xtask lint` would pass
- **Look for AI-generated patterns**: Be suspicious of verbose, overly-commented, or non-idiomatic code
### Architectural Considerations
- **Reference ARCHITECTURE.md**: Point to [ARCHITECTURE.md](https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md) for changes affecting crate boundaries
- **Question fundamental changes**: Flag modifications to core configuration, linting rules, or build setup without clear justification
- **Verify appropriate crate placement**: Ensure changes are in the correct crate per the modular structure
### Breaking Changes and Deprecation
- **Require deprecation**: Insist on deprecation warnings rather than immediate removal of public APIs
- **Ask for migration path**: Request clear upgrade instructions for breaking changes
- **Suggest feature flags**: Recommend feature flags for experimental or potentially disruptive changes
- **Reference versioning policy**: Point to the requirement of at least one version notice before removal
### Testing and Documentation
- **Verify test coverage**: Ensure new functionality includes appropriate tests
- **Check for test removal**: Question any removal of existing tests without clear justification
- **Require documentation**: Ensure public APIs are documented with examples
- **Validate examples**: Check that code examples are minimal and follow project style
### Specific Areas of Concern
#### Configuration Changes
- **Lint configuration**: Question changes to `.clippy.toml`, `rustfmt.toml`, or CI configuration
- **Cargo.toml modifications**: Scrutinize dependency changes or workspace modifications
- **Build system changes**: Require justification for xtask or build process modifications
#### Large Code Additions
- **Question necessity**: Ask if large code additions could be implemented more simply
- **Check for duplication**: Look for code that duplicates existing functionality
- **Verify integration**: Ensure new code integrates well with existing patterns
#### File Organization
- **Validate module structure**: Ensure new modules follow the project's organization
- **Check import organization**: Verify imports follow the std/external/local grouping pattern
- **Review file placement**: Confirm files are in appropriate locations per ARCHITECTURE.md
## Comment Templates
### For Large PRs
```
This PR seems quite large with changes across multiple areas. Consider splitting it into smaller, focused PRs:
- Core functionality changes
- Documentation updates
- Test additions
- Configuration changes
See our [contribution guidelines](https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#keep-prs-small-intentional-and-focused) for more details.
```
### For Breaking Changes
```
This appears to introduce breaking changes. Please consider:
- Adding deprecation warnings instead of immediate removal
- Providing a clear migration path in the PR description
- Following our [deprecation policy](https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#deprecation-notice)
```
### For Configuration Changes
```
Changes to project configuration (linting, formatting, build) should be discussed first. Please explain:
- Why this change is necessary
- What problem it solves
- Whether it affects contributor workflow
```
### For Style Issues
```
Please run `cargo xtask format` and `cargo xtask lint` to ensure code follows our style guidelines. See [CONTRIBUTING.md](https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#code-formatting) for details.
```

25
.github/workflows/bench_base.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Run Benchmarks
on:
push:
branches:
- main
jobs:
benchmark_base_branch:
name: Continuous Benchmarking with Bencher
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: bencherdev/bencher@main
- name: Track base branch benchmarks with Bencher
run: |
bencher run \
--project ratatui-org \
--token '${{ secrets.BENCHER_API_TOKEN }}' \
--branch main \
--testbed ubuntu-latest \
--adapter rust_criterion \
--err \
cargo bench

25
.github/workflows/bench_run_fork_pr.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Run and Cache Benchmarks
on:
pull_request:
types: [opened, reopened, edited, synchronize]
jobs:
benchmark_fork_pr_branch:
name: Run Fork PR Benchmarks
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run Benchmarks
run: cargo bench > benchmark_results.txt
- name: Upload Benchmark Results
uses: actions/upload-artifact@v4
with:
name: benchmark_results.txt
path: ./benchmark_results.txt
- name: Upload GitHub Pull Request Event
uses: actions/upload-artifact@v4
with:
name: event.json
path: ${{ github.event_path }}

View File

@@ -0,0 +1,75 @@
name: Track Benchmarks with Bencher
on:
workflow_run:
workflows: [Run and Cache Benchmarks]
types: [completed]
permissions:
contents: read
pull-requests: write
jobs:
track_fork_pr_branch:
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
env:
BENCHMARK_RESULTS: benchmark_results.txt
PR_EVENT: event.json
steps:
- name: Download Benchmark Results
uses: actions/github-script@v7
with:
script: |
async function downloadArtifact(artifactName) {
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == artifactName
})[0];
if (!matchArtifact) {
core.setFailed(`Failed to find artifact: ${artifactName}`);
}
let download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
let fs = require('fs');
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/${artifactName}.zip`, Buffer.from(download.data));
}
await downloadArtifact(process.env.BENCHMARK_RESULTS);
await downloadArtifact(process.env.PR_EVENT);
- name: Unzip Benchmark Results
run: |
unzip $BENCHMARK_RESULTS.zip
unzip $PR_EVENT.zip
- name: Export PR Event Data
uses: actions/github-script@v7
with:
script: |
let fs = require('fs');
let prEvent = JSON.parse(fs.readFileSync(process.env.PR_EVENT, {encoding: 'utf8'}));
core.exportVariable("PR_HEAD", `${prEvent.number}/merge`);
core.exportVariable("PR_BASE", prEvent.pull_request.base.ref);
core.exportVariable("PR_BASE_SHA", prEvent.pull_request.base.sha);
core.exportVariable("PR_NUMBER", prEvent.number);
- uses: bencherdev/bencher@main
- name: Track Benchmarks with Bencher
run: |
bencher run \
--project ratatui-org \
--token '${{ secrets.BENCHER_API_TOKEN }}' \
--branch '${{ env.PR_HEAD }}' \
--branch-start-point '${{ env.PR_BASE }}' \
--branch-start-point-hash '${{ env.PR_BASE_SHA }}' \
--testbed ubuntu-latest \
--adapter rust_criterion \
--err \
--github-actions '${{ secrets.GITHUB_TOKEN }}' \
--ci-number '${{ env.PR_NUMBER }}' \
--file "$BENCHMARK_RESULTS"

View File

@@ -0,0 +1,52 @@
#!/bin/bash
# Exit on error. Append "|| true" if you expect an error.
set -o errexit
# Exit on error inside any functions or subshells.
set -o errtrace
# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR
set -o nounset
# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip`
set -o pipefail
# Turn on traces, useful while debugging but commented out by default
# set -o xtrace
last_release="$(git tag --sort=committerdate | grep -P "v0+\.\d+\.\d+$" | tail -1)"
echo "🐭 Last release: ${last_release}"
# detect breaking changes
if [ -n "$(git log --oneline ${last_release}..HEAD | grep '!:')" ]; then
echo "🐭 Breaking changes detected since ${last_release}"
git log --oneline ${last_release}..HEAD | grep '!:'
# increment the minor version
minor="${last_release##v0.}"
minor="${minor%.*}"
next_minor="$((minor + 1))"
next_release="v0.${next_minor}.0"
else
# increment the patch version
patch="${last_release##*.}"
next_patch="$((patch + 1))"
next_release="${last_release/%${patch}/${next_patch}}"
fi
echo "🐭 Next release: ${next_release}"
suffix="alpha"
last_tag="$(git tag --sort=committerdate | tail -1)"
if [[ "${last_tag}" = "${next_release}-${suffix}"* ]]; then
echo "🐭 Last alpha release: ${last_tag}"
# increment the alpha version
# e.g. v0.22.1-alpha.12 -> v0.22.1-alpha.13
alpha="${last_tag##*-${suffix}.}"
next_alpha="$((alpha + 1))"
next_tag="${last_tag/%${alpha}/${next_alpha}}"
else
# increment the patch and start the alpha version from 0
# e.g. v0.22.0 -> v0.22.1-alpha.0
next_tag="${next_release}-${suffix}.0"
fi
# update the crate version
msg="# crate version"
sed -E -i "s/^version = .* ${msg}$/version = \"${next_tag#v}\" ${msg}/" Cargo.toml
echo "NEXT_TAG=${next_tag}" >> $GITHUB_ENV
echo "🐭 Next alpha release: ${next_tag}"

View File

@@ -1,13 +1,6 @@
name: Check Pull Requests
# Set the permissions of the github token to the minimum and only enable what is needed
# See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions
permissions: {}
on:
# this workflow is required to be run on pull_request_target as it modifies the PR comments
# care should be taken that the jobs do not run any untrusted input
# zizmor: ignore[dangerous-triggers]
pull_request_target:
types:
- opened
@@ -15,21 +8,23 @@ on:
- synchronize
- labeled
- unlabeled
merge_group:
permissions:
pull-requests: write
jobs:
check-title:
permissions:
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Check PR title
if: github.event_name == 'pull_request_target'
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v5
uses: amannn/action-semantic-pull-request@v5
id: check_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Add comment indicating we require pull request titles to follow conventional commits specification
- uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2
- uses: marocchino/sticky-pull-request-comment@v2
if: always() && (steps.check_pr_title.outputs.error_message != null)
with:
header: pr-title-lint-error
@@ -44,42 +39,40 @@ jobs:
# Delete a previous comment when the issue has been resolved
- if: ${{ steps.check_pr_title.outputs.error_message == null }}
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2
uses: marocchino/sticky-pull-request-comment@v2
with:
header: pr-title-lint-error
delete: true
check-breaking-change-label:
permissions:
pull-requests: write
runs-on: ubuntu-latest
env:
# use an environment variable to pass untrusted input to the script
# see https://securitylab.github.com/research/github-actions-untrusted-input/
PR_TITLE: ${{ github.event.pull_request.title }}
steps:
- name: Check breaking change label
id: check_breaking_change
run: |
pattern='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\(\w+\))?!:'
# Check if pattern matches
if echo "${PR_TITLE}" | grep -qE "$pattern"; then
echo "breaking_change=true" >> $GITHUB_OUTPUT
else
echo "breaking_change=false" >> $GITHUB_OUTPUT
fi
- name: Add label
if: steps.check_breaking_change.outputs.breaking_change == 'true'
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['Type: Breaking Change']
})
- name: Check breaking change label
id: check_breaking_change
run: |
pattern='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\(\w+\))?!:'
# Check if pattern matches
if echo "${PR_TITLE}" | grep -qE "$pattern"; then
echo "breaking_change=true" >> $GITHUB_OUTPUT
else
echo "breaking_change=false" >> $GITHUB_OUTPUT
fi
- name: Add label
if: steps.check_breaking_change.outputs.breaking_change == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['Type: Breaking Change']
})
do-not-merge:
if: ${{ contains(github.event.*.labels.*.name, 'do not merge') }}

View File

@@ -1,9 +1,5 @@
name: Check Semver
# Set the permissions of the github token to the minimum and only enable what is needed
# See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions
permissions: {}
on:
pull_request:
branches:
@@ -15,8 +11,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
uses: actions/checkout@v4
- name: Check semver
uses: obi1kenobi/cargo-semver-checks-action@5b298c9520f7096a4683c0bd981a7ac5a7e249ae # v2
uses: obi1kenobi/cargo-semver-checks-action@v2

View File

@@ -1,9 +1,5 @@
name: Continuous Integration
# Set the permissions of the github token to the minimum and only enable what is needed
# See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions
permissions: {}
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
@@ -13,6 +9,7 @@ on:
pull_request:
branches:
- main
merge_group:
# ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel
# and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency
@@ -24,272 +21,140 @@ concurrency:
# typos, and missing tests as early as possible. This allows us to fix these and resubmit the PR
# without having to wait for the comprehensive matrix of tests to complete.
jobs:
# Lint the formatting of the codebase.
lint-formatting:
name: Check Formatting
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
with:
toolchain: nightly
components: rustfmt
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
- uses: taiki-e/install-action@3522286d40783523f9c7880e33f785905b4c20d0 # v2
with:
tool: taplo-cli
- run: cargo xtask format --check
- run: cargo +nightly fmt --all --check
# Check for typos in the codebase.
# See <https://github.com/crate-ci/typos/>
lint-typos:
name: Check Typos
typos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: crate-ci/typos@bb4666ad77b539a6b4ce4eda7ebb6de553704021 # master
- uses: actions/checkout@v4
- uses: crate-ci/typos@master
# Check for any disallowed dependencies in the codebase due to license / security issues.
# See <https://github.com/EmbarkStudios/cargo-deny>
cargo-deny:
dependencies:
runs-on: ubuntu-latest
strategy:
matrix:
checks:
- advisories
- bans licenses sources
# Prevent sudden announcement of a new advisory from failing ci:
continue-on-error: ${{ matrix.checks == 'advisories' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2
with:
rust-toolchain: stable
log-level: info
arguments: --all-features --exclude-unpublished
command: check ${{ matrix.checks }}
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
# Check for any unused dependencies in the codebase.
# See <https://github.com/bnjbvr/cargo-machete/>
cargo-machete:
name: Check Unused Dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: bnjbvr/cargo-machete@7959c845782fed02ee69303126d4a12d64f1db18 # v0.9.1
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- uses: bnjbvr/cargo-machete@v0.7.0
# Run cargo clippy.
#
# We check for clippy warnings on beta, but these are not hard failures. They should often be
# fixed to prevent clippy failing on the next stable release, but don't block PRs on them unless
# they are introduced by the PR.
lint-clippy:
name: Check Clippy
clippy:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain: ["stable", "beta"]
continue-on-error: ${{ matrix.toolchain == 'beta' }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
with:
toolchain: ${{ matrix.toolchain }}
components: clippy
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
- run: cargo xtask clippy
env:
RUSTUP_TOOLCHAIN: ${{ matrix.toolchain }}
- uses: taiki-e/install-action@cargo-make
- uses: Swatinem/rust-cache@v2
- run: cargo make clippy
# Run markdownlint on all markdown files in the repository.
lint-markdown:
name: Check Markdown
markdownlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: DavidAnson/markdownlint-cli2-action@07035fd053f7be764496c0f8d8f9f41f98305101 # v21
- uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v17
with:
globs: |
'**/*.md'
'!target'
# Run cargo coverage. This will generate a coverage report and upload it to codecov.
# <https://app.codecov.io/gh/ratatui/ratatui>
coverage:
name: Coverage Report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
with:
toolchain: stable
components: llvm-tools
- uses: taiki-e/install-action@3522286d40783523f9c7880e33f785905b4c20d0 # v2
- uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
- run: cargo xtask coverage
- uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
tool: cargo-llvm-cov,cargo-make
- uses: Swatinem/rust-cache@v2
- run: cargo make coverage
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
# Run cargo check. This is a fast way to catch any obvious errors in the code.
check:
name: Check ${{ matrix.os }} ${{ matrix.toolchain }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
toolchain: ["1.86.0", "stable"]
toolchain: ["1.74.0", "stable"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}
- uses: taiki-e/install-action@3522286d40783523f9c7880e33f785905b4c20d0 # v2
with:
tool: cargo-hack
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
- run: cargo xtask check --all-features
- uses: taiki-e/install-action@cargo-make
- uses: Swatinem/rust-cache@v2
- run: cargo make check
env:
RUSTUP_TOOLCHAIN: ${{ matrix.toolchain }}
RUST_BACKTRACE: full
build-no-std:
name: Build No-Std
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
with:
toolchain: stable
targets: x86_64-unknown-none
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
# This makes it easier to debug the exact versions of the dependencies
- run: cargo tree --target x86_64-unknown-none -p ratatui-core
- run: cargo tree --target x86_64-unknown-none -p ratatui-widgets
- run: cargo tree --target x86_64-unknown-none -p ratatui-macros
- run: cargo tree --target x86_64-unknown-none -p ratatui --no-default-features
- run: cargo build --target x86_64-unknown-none -p ratatui-core
- run: cargo build --target x86_64-unknown-none -p ratatui-widgets
- run: cargo build --target x86_64-unknown-none -p ratatui-macros
- run: cargo build --target x86_64-unknown-none -p ratatui --no-default-features
# Check if README.md is up-to-date with the crate's documentation.
check-readme:
name: Check README
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
- uses: taiki-e/install-action@3522286d40783523f9c7880e33f785905b4c20d0 # v2
with:
tool: cargo-rdme
- run: cargo xtask readme --check
# Run cargo rustdoc with the same options that would be used by docs.rs, taking into account the
# package.metadata.docs.rs configured in Cargo.toml. https://github.com/dtolnay/cargo-docs-rs
lint-docs:
name: Check Docs
runs-on: ubuntu-latest
env:
RUSTDOCFLAGS: -Dwarnings
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
with:
toolchain: nightly
- uses: dtolnay/install@74f735cdf643820234e37ae1c4089a08fd266d8a # master
with:
crate: cargo-docs-rs
- uses: taiki-e/install-action@3522286d40783523f9c7880e33f785905b4c20d0 # v2
with:
tool: cargo-hack
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
- run: cargo xtask docs
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/install@cargo-docs-rs
- uses: Swatinem/rust-cache@v2
# Run cargo rustdoc with the same options that would be used by docs.rs, taking into account
# the package.metadata.docs.rs configured in Cargo.toml.
# https://github.com/dtolnay/cargo-docs-rs
- run: cargo +nightly docs-rs
# Run cargo test on the documentation of the crate. This will catch any code examples that don't
# compile, or any other issues in the documentation.
test-docs:
name: Test Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
with:
toolchain: stable
- uses: taiki-e/install-action@3522286d40783523f9c7880e33f785905b4c20d0 # v2
with:
tool: cargo-hack
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
- run: cargo xtask test-docs
# Run cargo test on the libraries of the crate.
test-libs:
name: Test Libs ${{ matrix.toolchain }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
toolchain: ["1.86.0", "stable"]
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
with:
toolchain: stable
- uses: taiki-e/install-action@3522286d40783523f9c7880e33f785905b4c20d0 # v2
with:
tool: cargo-hack
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
- run: cargo xtask test-libs
# Run cargo test on all the backends.
test-backends:
name: Test ${{matrix.backend}} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
test-doc:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-make
- uses: Swatinem/rust-cache@v2
- run: cargo make test-doc
env:
RUST_BACKTRACE: full
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
toolchain: ["1.74.0", "stable"]
backend: [crossterm, termion, termwiz]
exclude:
# termion is not supported on windows
- os: windows-latest
backend: termion
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
toolchain: ${{ matrix.toolchain }}
- uses: taiki-e/install-action@v2
with:
toolchain: stable
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
- run: cargo xtask test-backend ${{ matrix.backend }}
tool: cargo-make,nextest
- uses: Swatinem/rust-cache@v2
- run: cargo make test-backend ${{ matrix.backend }}
env:
RUST_BACKTRACE: full

48
.github/workflows/release-alpha.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Release alpha version
on:
workflow_dispatch:
schedule:
# At 00:00 on Saturday
# https://crontab.guru/#0_0_*_*_6
- cron: "0 0 * * 6"
defaults:
run:
shell: bash
jobs:
publish-alpha:
name: Create an alpha release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Calculate the next release
run: .github/workflows/calculate-alpha-release.bash
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Publish
run: cargo publish --allow-dirty --token ${{ secrets.CARGO_TOKEN }}
- name: Generate a changelog
uses: orhun/git-cliff-action@v4
with:
config: cliff.toml
args: --unreleased --tag ${{ env.NEXT_TAG }} --strip header
env:
OUTPUT: BODY.md
- name: Publish on GitHub
uses: ncipollo/release-action@v1
with:
tag: ${{ env.NEXT_TAG }}
prerelease: true
bodyFile: BODY.md

View File

@@ -1,71 +0,0 @@
name: Release-plz
# Set the permissions of the github token to the minimum and only enable what is needed
# See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions
permissions: {}
on:
push:
branches:
- main
workflow_dispatch:
jobs:
# Release unpublished packages.
release-plz-release:
name: Release-plz release
environment: release
permissions:
pull-requests: write
contents: write
id-token: write
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ratatui' }}
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
with:
toolchain: stable
- uses: rust-lang/crates-io-auth-action@b7e9a28eded4986ec6b1fa40eeee8f8f165559ec # v1
id: auth
- name: Run release-plz
uses: release-plz/action@487eb7b5c085a664d5c5ca05f4159bd9b591182a # v0.5
with:
command: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
# Create a PR with the new versions and changelog, preparing the next release.
release-plz-pr:
name: Release-plz PR
permissions:
pull-requests: write
contents: write
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ratatui' }}
concurrency:
group: release-plz-${{ github.ref }}
cancel-in-progress: false
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # master
with:
toolchain: stable
- name: Run release-plz
uses: release-plz/action@487eb7b5c085a664d5c5ca05f4159bd9b591182a # v0.5
with:
command: release-pr
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }}

45
.github/workflows/release-stable.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: Release stable version
on:
push:
tags:
- "v*.*.*"
jobs:
publish-stable:
name: Create an stable release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate a changelog
uses: orhun/git-cliff-action@v4
with:
config: cliff.toml
args: --latest --strip header
env:
OUTPUT: BODY.md
- name: Publish on GitHub
uses: ncipollo/release-action@v1
with:
prerelease: false
bodyFile: BODY.md
publish-crate:
name: Publish crate
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Publish
run: cargo publish --token ${{ secrets.CARGO_TOKEN }}

View File

@@ -1,26 +0,0 @@
name: GitHub Actions Security Analysis with zizmor 🌈
# docs https://docs.zizmor.sh/integrations/#github-actions
on:
push:
branches: ["main"]
pull_request:
branches: ["**"]
permissions: {}
jobs:
zizmor:
name: Run zizmor 🌈
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@e639db99335bc9038abc0e066dfcd72e23d26fb4 # v0.3.0

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
target
Cargo.lock
*.log
*.rs.rustfmt
.gdb_history
.idea/
.env

View File

@@ -1,12 +0,0 @@
include = ["**/*.toml"]
[formatting]
column_width = 100
reorder_keys = false
[[rule]]
include = ["**/Cargo.toml"]
keys = ["dependencies", "dev-dependencies", "build-dependencies", "workspace.dependencies"]
[rule.formatting]
reorder_keys = true

View File

@@ -1,203 +0,0 @@
# Ratatui Architecture
This document provides a comprehensive overview of Ratatui's architecture and crate
organization, introduced in version 0.30.0.
## Overview
Starting with Ratatui 0.30.0, the project was reorganized from a single monolithic crate into
a modular workspace consisting of multiple specialized crates. This architectural decision was
made to improve modularity, reduce compilation times, enable more flexible dependency
management, and provide better API stability for third-party widget libraries.
## Crate Organization
The Ratatui project is now organized as a Cargo workspace containing the following crates:
### Core Crates
#### `ratatui` (Main Crate)
- **Purpose**: The main entry point that most applications should use
- **Contents**: Re-exports everything from other crates for convenience, plus experimental features
- **Target Users**: Application developers building terminal UIs
- **Key Features**:
- Complete widget ecosystem
- Backend implementations
- Layout system
- Terminal management
- Experimental `WidgetRef` and `StatefulWidgetRef` traits
#### `ratatui-core`
- **Purpose**: Foundational types and traits for the Ratatui ecosystem
- **Contents**: Core widget traits, text types, buffer, layout, style, and symbols
- **Target Users**: Widget library authors, minimalist projects
- **Key Features**:
- `Widget` and `StatefulWidget` traits
- Text rendering (`Text`, `Line`, `Span`)
- Buffer management
- Layout system
- Style and color definitions
- Symbol collections
#### `ratatui-widgets`
- **Purpose**: Built-in widget implementations
- **Contents**: All standard widgets like `Block`, `Paragraph`, `List`, `Chart`, etc.
- **Target Users**: Applications needing standard widgets, widget library authors
- **Key Features**:
- Complete set of built-in widgets
- Optimized implementations
- Comprehensive documentation and examples
### Backend Crates
#### `ratatui-crossterm`
- **Purpose**: Crossterm backend implementation
- **Contents**: Cross-platform terminal backend using the `crossterm` crate
- **Target Users**: Applications targeting multiple platforms
#### `ratatui-termion`
- **Purpose**: Termion backend implementation
- **Contents**: Unix-specific terminal backend using the `termion` crate
- **Target Users**: Unix-specific applications requiring low-level control
#### `ratatui-termwiz`
- **Purpose**: Termwiz backend implementation
- **Contents**: Terminal backend using the `termwiz` crate
- **Target Users**: Applications needing advanced terminal features
### Utility Crates
#### `ratatui-macros`
- **Purpose**: Declarative macros for common patterns and boilerplate reduction
- **Contents**: Macros for common patterns and boilerplate reduction
- **Target Users**: Applications and libraries wanting macro support
## Dependency Relationships
```text
ratatui
├── ratatui-core
├── ratatui-widgets → ratatui-core
├── ratatui-crossterm → ratatui-core
├── ratatui-termion → ratatui-core
├── ratatui-termwiz → ratatui-core
└── ratatui-macros
```
### Key Dependencies
- **ratatui-core**: Foundation for all other crates
- **ratatui-widgets**: Depends on `ratatui-core` for widget traits and types
- **Backend crates**: Each depends on `ratatui-core` for backend traits and types
- **ratatui**: Depends on all other crates and re-exports their public APIs
## Design Principles
### Stability and Compatibility
The modular architecture provides different levels of API stability:
- **ratatui-core**: Designed for maximum stability to minimize breaking changes for widget
libraries
- **ratatui-widgets**: Focused on widget implementations with moderate stability requirements
- **Backend crates**: Isolated from core changes, allowing backend-specific updates
- **ratatui**: Main crate that can evolve more freely while maintaining backward compatibility
through re-exports
### Compilation Performance
The split architecture enables:
- **Reduced compilation times**: Widget libraries only need to compile core types
- **Parallel compilation**: Different crates can be compiled in parallel
- **Selective compilation**: Applications can exclude unused backends or widgets
### Ecosystem Benefits
- **Widget Library Authors**: Can depend on stable `ratatui-core` without frequent updates
- **Application Developers**: Can use the convenient `ratatui` crate with everything included
- **Minimalist Projects**: Can use only `ratatui-core` for lightweight applications
## Migration Guide
### For Application Developers
Most applications should continue using the main `ratatui` crate with minimal changes:
```rust
// No changes needed - everything is re-exported
use ratatui::{
widgets::{Block, Paragraph},
layout::{Layout, Constraint},
Terminal,
};
```
### For Widget Library Authors
Consider migrating to `ratatui-core` for better stability:
```rust
// Before (0.29.x and earlier)
use ratatui::{
widgets::{Widget, StatefulWidget},
buffer::Buffer,
layout::Rect,
};
// After (0.30.0+)
use ratatui_core::{
widgets::{Widget, StatefulWidget},
buffer::Buffer,
layout::Rect,
};
```
### Backwards Compatibility
All existing code using the `ratatui` crate will continue to work unchanged, as the main crate
re-exports all public APIs from the specialized crates.
## Future Considerations
### Potential Enhancements
- **Widget-specific crates**: Further split widgets into individual crates for even more
granular dependencies
- **Plugin system**: Enable dynamic widget loading and third-party widget ecosystems
- **Feature flags**: More granular feature flags for compile-time customization
### Version Synchronization
Currently, all crates are versioned together for simplicity. Future versions may adopt
independent versioning once the API stabilizes further.
## Related Issues and PRs
This architecture was developed through extensive discussion and implementation across multiple
PRs:
- [Issue #1388](https://github.com/ratatui/ratatui/issues/1388): Original RFC for modularization
- [PR #1459](https://github.com/ratatui/ratatui/pull/1459): Move ratatui crate into workspace
folder
- [PR #1460](https://github.com/ratatui/ratatui/pull/1460): Move core types to ratatui-core
- [PR #1474](https://github.com/ratatui/ratatui/pull/1474): Move widgets into ratatui-widgets
crate
## Contributing
When contributing to the Ratatui project, please consider:
- **Core changes**: Submit PRs against `ratatui-core` for fundamental improvements
- **Widget changes**: Submit PRs against `ratatui-widgets` for widget-specific improvements
- **Backend changes**: Submit PRs against the appropriate backend crate
- **Integration changes**: Submit PRs against the main `ratatui` crate
See the [CONTRIBUTING.md](CONTRIBUTING.md) guide for more details on the contribution process.

View File

@@ -10,36 +10,12 @@ GitHub with a [breaking change] label.
This is a quick summary of the sections below:
- [v0.30.1](#v0301)
- Adding `AsRef` impls for widgets may affect type inference in rare cases
- [v0.30.0](#v0300)
- `Flex::SpaceAround` now mirrors flexbox: space between items is twice the size of the outer gaps
are twice the size of first and last elements
- `block::Title` no longer exists
- The `From` impls for backend types are now replaced with more specific traits
- `FrameExt` trait for `unstable-widget-ref` feature
- `List::highlight_symbol` now accepts `Into<Line>` instead of `&str`
- 'layout::Alignment' is renamed to 'layout::HorizontalAlignment'
- MSRV is now 1.86.0
- `Backend` now requires an associated `Error` type and `clear_region` method
- `TestBackend` now uses `core::convert::Infallible` for error handling instead of `std::io::Error`
- Disabling `default-features` will now disable layout cache, which can have a negative impact on performance
- `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache`
feature is enabled
- Disabling `default-features` suppresses the error message if `show_cursor()` fails when dropping
`Terminal`
- Support a broader range for `unicode-width` version
- `Marker` is now non-exhaustive
- `symbols::braille::BLANK` and `symbols::braille::DOTS` have been removed in favor of an ordered
array of all Braille characters
- [v0.29.0](#v0290)
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer
const
- [v0.29.0](#unreleased)
- `Terminal`, `Buffer`, `Cell`, `Frame` are no longer `Sync` / `RefUnwindSafe`
- Removed public fields from `Rect` iterators
- `Line` now implements `From<Cow<str>`
- `Table::highlight_style` is now `Table::row_highlight_style`
- `Tabs::select` now accepts `Into<Option<usize>>`
- `Color::from_hsl` is now behind the `palette` feature
- [v0.28.0](#v0280)
- `Backend::size` returns `Size` instead of `Rect`
- `Backend` trait migrates to `get/set_cursor_position`
@@ -95,384 +71,26 @@ This is a quick summary of the sections below:
- MSRV is now 1.63.0
- `List` no longer ignores empty strings
## [v0.30.1](https://github.com/ratatui/ratatui/releases/tag/ratatui-v0.30.1)
## Unreleased
### Adding `AsRef` impls for widgets may affect type inference ([#2297])
### `Terminal`, `Buffer`, `Cell`, `Frame` are no longer `Sync` / `RefUnwindSafe` [#1339]
[#2297]: https://github.com/ratatui/ratatui/pull/2297
[#1339]: https://github.com/ratatui/ratatui/pull/1339
Adding `AsRef<Self>` for built-in widgets can change type inference outcomes in rare cases where
`AsRef` is part of a trait bound, and can also conflict with downstream blanket or manual `AsRef`
impls for widget types. If you hit new ambiguity errors, add explicit type annotations or specify
the concrete widget type to guide inference, and remove any redundant `AsRef` impls.
In #1339, we added a cache of the Cell width which uses a std::cell::Cell. This causes `Cell` and
all types that contain this (`Terminal`, `Buffer`, `Frame`, `CompletedFrame`, `TestBackend`) to no
longer be `Sync`
## [v0.30.0](https://github.com/ratatui/ratatui/releases/tag/ratatui-v0.30.0)
This change is unlikely to cause problems as these types likely should not be sent between threads
regardless due to their interaction with various things which mutated externally (e.g. stdio).
### `Marker` is now non-exhaustive ([#2236])
[#2236]: https://github.com/ratatui/ratatui/pull/2236
The `Marker` enum is now marked as `#[non_exhaustive]`, if you were matching on `Marker` exhaustively,
you will need to add a wildcard arm:
```diff
match marker {
Marker::Dot => { /* ... */ }
Marker::Block => { /* ... */ }
Marker::Bar => { /* ... */ }
Marker::Braille => { /* ... */ }
Marker::HalfBlock => { /* ... */ }
+ _ => { /* ... */ }
}
```
### `Flex::SpaceAround` now mirrors flexbox: space between items is twice the size of the outer gaps ([#1952])
[#1952]: https://github.com/ratatui/ratatui/pull/1952
The old `Flex::SpaceAround` behavior has been changed to distribute space evenly around each
element, with the middle spacers being twice the size of the first and last one. The old
behavior can be achieved by using `Flex::SpaceEvenly` instead.
```diff
- let rects = Layout::horizontal([Length(1), Length(2)]).flex(Flex::SpaceAround).split(area);
+ let rects = Layout::horizontal([Length(1), Length(2)]).flex(Flex::SpaceEvenly).split(area);
```
### `block::Title` no longer exists ([#1926])
[#1926]: https://github.com/ratatui/ratatui/pull/1926
The title alignment is better expressed in the `Line` as this fits more coherently with the rest of
the library.
- `widgets::block` is no longer exported
- `widgets::block::Title` no longer exists
- `widgets::block::Position` is now `widgets::TitlePosition`
- `Block::title()` now accepts `Into::<Line>` instead of `Into<Title>`
- `BlockExt` is now exported at widgets::`BlockExt` instead of `widgets::block::BlockExt`
```diff
- use ratatui::widgets::{Block, block::{Title, Position}};
+ use ratatui::widgets::{Block, TitlePosition};
let block = Block::default()
- .title(Title::from("Hello"))
- .title(Title::from("Hello").position(Position::Bottom).alignment(Alignment::Center))
- .title_position(Position::Bottom);
+ .title(Line::from("Hello"))
+ .title_bottom(Line::from("Hello").centered());
+ .title_position(TitlePosition::Bottom);
- use ratatui::widgets::block::BlockExt;
+ use ratatui::widgets::BlockExt;
struct MyWidget {
block: Option<Block>,
}
impl Widget for &MyWidget {
fn render(self, area: Rect, buf: &mut Buffer) {
self.block.as_ref().render(area, buf);
let area = self.block.inner_if_some();
// ...
}
}
```
### `Style` no longer implements `Styled` ([#1572])
[#1572]: https://github.com/ratatui/ratatui/pull/1572
Any calls to methods implemented by the blanket implementation of `Stylize` are now defined directly
on `Style`. Remove the `Stylize` import if it is no longer used by your code.
```diff
- use ratatui::style::Stylize;
let style = Style::new().red();
```
The `reset()` method does not have a direct replacement, as it clashes with the existing `reset()`
method. Use the `Style::reset()` method instead.
```diff
- some_style.reset();
+ Style::reset();
```
### Disabling `default-features` suppresses the error message if `show_cursor()` fails when dropping `Terminal` ([#1794])
[#1794]: https://github.com/ratatui/ratatui/pull/1794
Since disabling `default-features` disables `std`, printing to stderr is not possible. It is
recommended to re-enable `std` when not using Ratatui in `no_std` environment.
### `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` are now only available if `layout-cache` feature is enabled ([#1795])
[#1795]: https://github.com/ratatui/ratatui/pull/1795
Previously, `Layout::init_cache` and `Layout::DEFAULT_CACHE_SIZE` were available independently of
enabled feature flags.
### Disabling `default-features` will now disable layout cache, which can have a negative impact on performance ([#1795])
Layout cache is now opt-in in `ratatui-core` and enabled by default in `ratatui`. If app doesn't
make use of `no_std`-compatibility, and disables `default-feature`, it is recommended to explicitly
re-enable layout cache. Not doing so may impact performance.
```diff
- ratatui = { version = "0.29.0", default-features = false }
+ ratatui = { version = "0.30.0", default-features = false, features = ["layout-cache"] }
```
### `TestBackend` now uses `core::convert::Infallible` for error handling instead of `std::io::Error` ([#1823])
[#1823]: https://github.com/ratatui/ratatui/pull/1823
Since `TestBackend` never fails, it now uses `Infallible` as associated `Error`. This may require
changes in test cases that use `TestBackend`.
### `Backend` now requires an associated `Error` type and `clear_region` method ([#1778])
[#1778]: https://github.com/ratatui/ratatui/pull/1778
Custom `Backend` implementations must now define an associated `Error` type for method `Result`s
and implement the `clear_region` method, which no longer has a default implementation.
This change was made to provide greater flexibility for custom backends, particularly to remove the
explicit dependency on `std::io` for backends that want to support `no_std` targets.
If your app or library uses the `Backend` trait directly - for example, by providing a generic
implementation for many backends - you may need to update the referenced error type.
```diff
- fn run<B: Backend>(mut terminal: Terminal<B>) -> io::Result<()> {
+ fn run<B: Backend>(mut terminal: Terminal<B>) -> Result<(), B::Error> {
```
Alternatively, you can explicitly require the associated error to be `std::io::Error`. This approach
may require fewer changes in user code but is generally not recommended, as it limits compatibility
with third-party backends. Additionally, the error type used by built-in backends may or may not
change in the future, making this approach less future-proof compared to the previous one.
```diff
- fn run<B: Backend>(mut terminal: Terminal<B>) -> io::Result<()> {
+ fn run<B: Backend<Error = io::Error>>(mut terminal: Terminal<B>) -> io::Result<()> {
```
If your application uses a concrete backend implementation, prefer specifying it explicitly
instead.
```diff
- fn run<B: Backend>(mut terminal: Terminal<B>) -> io::Result<()> {
+ fn run(mut terminal: DefaultTerminal) -> io::Result<()> {
```
### MSRV is now 1.86.0 ([#2230])
[#2230]: https://github.com/ratatui/ratatui/pull/2230
The minimum supported Rust version (MSRV) is now 1.86.0.
### `layout::Alignment` is renamed to `layout::HorizontalAlignment` ([#1735])
[#1735]: https://github.com/ratatui/ratatui/pull/1691
The `Alignment` enum has been renamed to `HorizontalAlignment` to better reflect its purpose. A type
alias has been added to maintain backwards compatibility, however there are some cases where type
aliases are not enough to maintain backwards compatibility. E.g. when using glob imports to import
all the enum variants.
We don't expect to remove or deprecate the type alias in the near future, but it is recommended to
update your imports to use the new name.
```diff
- use ratatui::layout::Alignment;
+ use ratatui::layout::HorizontalAlignment;
- use Alignment::*;
+ use HorizontalAlignment::*;
```
### `List::highlight_symbol` accepts `Into<Line>` ([#1595])
[#1595]: https://github.com/ratatui/ratatui/pull/1595
Previously `List::highlight_symbol` accepted `&str`. Any code that uses conversion methods will need
to be rewritten. Since `Into::into` is not const, this function cannot be called in const context.
### `FrameExt` trait for `unstable-widget-ref` feature ([#1530])
[#1530]: https://github.com/ratatui/ratatui/pull/1530
To call `Frame::render_widget_ref()` or `Frame::render_stateful_widget_ref()` you now need to:
1. Import the `FrameExt` trait from `ratatui::widgets`.
2. Enable the `unstable-widget-ref` feature.
For example:
```rust
use ratatui::{
layout::Rect,
widgets::{Block, FrameExt},
};
let block = Block::new();
let area = Rect::new(0, 0, 5, 5);
frame.render_widget_ref(&block, area);
```
### `WidgetRef` no longer has a blanket implementation of Widget
Previously there was a blanket implementation of Widget for WidgetRef. This has been reversed to
instead be a blanket implementation of WidgetRef for all &W where W: Widget. Any widgets that
previously implemented WidgetRef directly should now instead implement Widget for a reference to the
type.
```diff
-impl WidgetRef for Foo {
- fn render_ref(&self, area: Rect, buf: &mut Buffer)
+impl Widget for &Foo {
+ fn render(self, area: Rect, buf: &mut Buffer)
}
```
### The `From` impls for backend types are now replaced with more specific traits [#1464]
[#1464]: https://github.com/ratatui/ratatui/pull/1464
Crossterm gains `ratatui::backend::crossterm::{FromCrossterm, IntoCrossterm}`
Termwiz gains `ratatui::backend::termwiz::{FromTermwiz, IntoTermwiz}`
This is necessary in order to avoid the orphan rule when implementing `From` for crossterm types
once the crossterm types are moved to a separate crate.
```diff
+ use ratatui::backend::crossterm::{FromCrossterm, IntoCrossterm};
let crossterm_color = crossterm::style::Color::Black;
- let ratatui_color = crossterm_color.into();
- let ratatui_color = ratatui::style::Color::from(crossterm_color);
+ let ratatui_color = ratatui::style::Color::from_crossterm(crossterm_color);
- let crossterm_color = ratatui_color.into();
- let crossterm_color = crossterm::style::Color::from(ratatui_color);
+ let crossterm_color = ratatui_color.into_crossterm();
let crossterm_attribute = crossterm::style::types::Attribute::Bold;
- let ratatui_modifier = crossterm_attribute.into();
- let ratatui_modifier = ratatui::style::Modifier::from(crossterm_attribute);
+ let ratatui_modifier = ratatui::style::Modifier::from_crossterm(crossterm_attribute);
- let crossterm_attribute = ratatui_modifier.into();
- let crossterm_attribute = crossterm::style::types::Attribute::from(ratatui_modifier);
+ let crossterm_attribute = ratatui_modifier.into_crossterm();
```
Similar conversions for `ContentStyle` -> `Style` and `Attributes` -> `Modifier` exist for
Crossterm and the various Termion and Termwiz types as well.
### `Bar::label()` and `BarGroup::label()` now accepts `Into<Line<'a>>`. ([#1471])
[#1471]: https://github.com/ratatui/ratatui/pull/1471
Previously `Bar::label()` and `BarGroup::label()` accepted `Line<'a>`, but they now accepts `Into<Line<'a>>`.
for `Bar::label()`:
```diff
- Bar::default().label("foo".into());
+ Bar::default().label("foo");
```
for `BarGroup::label()`:
```diff
- BarGroup::default().label("bar".into());
+ BarGroup::default().label("bar");
```
### `Bar::text_value` now accepts `Into<String>` ([#1471])
Previously `Bar::text_value` accepted `String`, but now it accepts `Into<String>`.
for `Bar::text_value()`:
```diff
- Bar::default().text_value("foobar".into());
+ Bar::default().text_value("foobar");
```
### `termwiz` is upgraded to 0.23.0 ([#1682])
[#1682]: https://github.com/ratatui/ratatui/pull/1682
The `termwiz` backend is upgraded from 0.22.0 to 0.23.0.
This release has a few fixes for hyperlinks and input handling, plus some dependency updates.
See the [commits](https://github.com/wezterm/wezterm/commits/main/termwiz) for more details.
### Support a broader range for `unicode-width` version ([#1999])
[#1999]: https://github.com/ratatui/ratatui/pull/1999
Ratatui's dependency on `unicode-width`, previously pinned to 0.2.0, has
expanded to allow version 0.2.1. This comes with 2 behavior changes described in
[unicode-width#61] and [unicode-width#74].
[unicode-width#61]: https://github.com/unicode-rs/unicode-width/pull/61
[unicode-width#74]: https://github.com/unicode-rs/unicode-width/pull/74
## [v0.29.0](https://github.com/ratatui/ratatui/releases/tag/v0.29.0)
### `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer const ([#1326])
[#1326]: https://github.com/ratatui/ratatui/pull/1326
The `Sparkline::data` method has been modified to accept `IntoIterator<Item = SparklineBar>`
instead of `&[u64]`.
`SparklineBar` is a struct that contains an `Option<u64>` value, which represents an possible
_absent_ value, as distinct from a `0` value. This change allows the `Sparkline` to style
data points differently, depending on whether they are present or absent.
`SparklineBar` also contains an `Option<Style>` that will be used to apply a style the bar in
addition to any other styling applied to the `Sparkline`.
Several `From` implementations have been added to `SparklineBar` to support existing callers who
provide `&[u64]` and other types that can be converted to `SparklineBar`, such as `Option<u64>`.
If you encounter any type inference issues, you may need to provide an explicit type for the data
passed to `Sparkline::data`. For example, if you are passing a single value, you may need to use
`into()` to convert it to form that can be used as a `SparklineBar`:
```diff
let value = 1u8;
- Sparkline::default().data(&[value.into()]);
+ Sparkline::default().data(&[u64::from(value)]);
```
As a consequence of this change, the `data` method is no longer a `const fn`.
### `Color::from_hsl` is now behind the `palette` feature and accepts `palette::Hsl` ([#1418])
[#1418]: https://github.com/ratatui/ratatui/pull/1418
Previously `Color::from_hsl` accepted components as individual f64 parameters. It now accepts a
single `palette::Hsl` value and is gated behind a `palette` feature flag.
```diff
- Color::from_hsl(360.0, 100.0, 100.0)
+ Color::from_hsl(Hsl::new(360.0, 100.0, 100.0))
```
### Removed public fields from `Rect` iterators ([#1358], [#1424])
### Removed public fields from `Rect` iterators ([#1358])
[#1358]: https://github.com/ratatui/ratatui/pull/1358
[#1424]: https://github.com/ratatui/ratatui/pull/1424
The `pub` modifier has been removed from fields on the `Columns`,`Rows`, and `Positions` iterators.
These fields were not intended to be public and should not have been accessed directly.
The `pub` modifier has been removed from fields on the `layout::rect::Columns` and
`layout::rect::Rows`. These fields were not intended to be public and should not have been accessed
directly.
### `Rect::area()` now returns u32 instead of u16 ([#1378])
@@ -524,7 +142,7 @@ implementation on `TableState`) may have to be refactored if the "selected_colum
accounted for. This does not affect users who rely on the `Deserialize`, or `Serialize`
implementation on the state.
## [v0.28.0](https://github.com/ratatui/ratatui/releases/tag/v0.28.0)
## v0.28.0
### `Backend::size` returns `Size` instead of `Rect` ([#1254])
@@ -1121,7 +739,7 @@ previously did not need to use type annotations to fail to compile. To fix this,
[#133]: https://github.com/ratatui/ratatui/issues/133
Code using the `Block` marker that previously rendered using a half block character (`'▀'`) now
Code using the `Block` marker that previously rendered using a half block character (`'▀'``) now
renders using the full block character (`'█'`). A new marker variant`Bar` is introduced to replace
the existing code.

File diff suppressed because it is too large Load Diff

View File

@@ -6,23 +6,6 @@ If your contribution is not straightforward, please first discuss the change you
creating a new issue before making the change, or starting a discussion on
[discord](https://discord.gg/pMCEU9hNEj).
## AI Generated Content
We welcome high quality PRs, whether they are human generated or made with the assistance of AI
tools, but we ask that you follow these guidelines:
- **Attribution**: Tell us about your use of AI tools, don't make us guess whether you're using it.
- **Review**: Make sure you review every line of AI generated content for correctness and relevance.
- **Quality**: AI-generated content should meet the same quality standards as human-written content.
- **Quantity**: Avoid submitting large amounts of AI generated content in a single PR. Remember that
quality is usually more important than quantity.
- **License**: Ensure that the AI-generated content is compatible with Ratatui's
[License](./LICENSE).
> [!IMPORTANT]
> Remember that AI tools can assist in generating content, but you are responsible for the final
> quality and accuracy of the contributions and communicating about it.
## Reporting issues
Before reporting an issue on the [issue tracker](https://github.com/ratatui/ratatui/issues),
@@ -39,48 +22,17 @@ on the crate or its users should ideally be discussed in a "Feature Request" iss
### Keep PRs small, intentional, and focused
Try to do one pull request per change. The time taken to review a PR grows exponentially with the size
of the change. Small focused PRs will generally be much faster to review. PRs that include both
Try to do one pull request per change. The time taken to review a PR grows exponential with the size
of the change. Small focused PRs will generally be much more faster to review. PRs that include both
refactoring (or reformatting) with actual changes are more difficult to review as every line of the
change becomes a place where a bug may have been introduced. Consider splitting refactoring /
reformatting changes into a separate PR from those that make a behavioral change, as the tests help
guarantee that the behavior is unchanged.
Guidelines for PR size:
- Aim for PRs under 500 lines of changes when possible.
- Split large features into incremental PRs that build on each other.
- Separate refactoring, formatting, and functional changes into different PRs.
- If a large PR is unavoidable, clearly explain why in the PR description.
### Breaking changes and backwards compatibility
We prioritize maintaining backwards compatibility and minimizing disruption to users:
- **Prefer deprecation over removal**: Add deprecation warnings rather than immediately removing
public APIs
- **Provide migration paths**: Include clear upgrade instructions for any breaking changes
- **Follow our deprecation policy**: Wait at least two versions before removing deprecated items
- **Consider feature flags**: Use feature flags for experimental or potentially disruptive changes
- **Document breaking changes**: Clearly mark breaking changes in commit messages and PR descriptions
See our [deprecation notice policy](#deprecation-notice) for more details.
### Code formatting
Run `cargo xtask format` before committing to ensure that code is consistently formatted with
rustfmt. Configuration is in [`rustfmt.toml`](./rustfmt.toml). We use some unstable formatting
options as they lead to subjectively better formatting. These require a nightly version of Rust
to be installed when running rustfmt. You can install the nightly version of Rust using
[`rustup`](https://rustup.rs/):
```shell
rustup install nightly
```
> [!IMPORTANT]
> Do not modify formatting configuration (`rustfmt.toml`, `.clippy.toml`) without
> prior discussion. These changes affect all contributors and should be carefully considered.
Run `cargo make format` before committing to ensure that code is consistently formatted with
rustfmt. Configuration is in [`rustfmt.toml`](./rustfmt.toml).
### Search `tui-rs` for similar work
@@ -104,19 +56,10 @@ documented.
### Run CI tests before pushing a PR
Running `cargo xtask ci` before pushing will perform the same checks that we do in the CI process.
Running `cargo make ci` before pushing will perform the same checks that we do in the CI process.
It's not mandatory to do this before pushing, however it may save you time to do so instead of
waiting for GitHub to run the checks.
Available xtask commands:
- `cargo xtask ci` - Run all CI checks
- `cargo xtask format` - Format code
- `cargo xtask lint` - Run linting checks
- `cargo xtask test` - Run all tests
Run `cargo xtask --help` to see all available commands.
### Sign your commits
We use commit signature verification, which will block commits from being merged via the UI unless
@@ -124,56 +67,23 @@ they are signed. To set up your machine to sign commits, see [managing commit si
verification](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)
in GitHub docs.
### Configuration and build system changes
Changes to project configuration files require special consideration:
- Linting configuration (`.clippy.toml`, `rustfmt.toml`): Affects all contributors.
- CI configuration (`.github/workflows/`): Affects build and deployment.
- Build system (`xtask/`, `Cargo.toml` workspace config): Affects development workflow.
- Dependencies: Consider MSRV compatibility and licensing.
Please discuss these changes in an issue before implementing them.
### Collaborative development
We may occasionally make changes directly to your branch—such as force-pushes—to help move a PR
forward, speed up review, or ensure it meets our quality standards. If you would prefer we do not do
this, or if your workflow depends on us avoiding force-pushes (for example, if your app points to
your branch in `Cargo.toml`), please mention this in your PR description and we will respect your
preference.
## Implementation Guidelines
### Setup
TL;DR: Clone the repo and build it using `cargo xtask`.
Clone the repo and build it using [cargo-make](https://sagiegurari.github.io/cargo-make/)
Ratatui is an ordinary Rust project where common tasks are managed with
[cargo-xtask](https://github.com/matklad/cargo-xtask). It wraps common `cargo` commands with sane
[cargo-make](https://github.com/sagiegurari/cargo-make/). It wraps common `cargo` commands with sane
defaults depending on your platform of choice. Building the project should be as easy as running
`cargo xtask build`.
`cargo make build`.
```shell
git clone https://github.com/ratatui/ratatui.git
cd ratatui
cargo xtask build
cargo make build
```
### Architecture
For an understanding of the crate organization and design decisions, see [ARCHITECTURE.md]. This
document explains the modular workspace structure introduced in version 0.30.0 and provides
guidance on which crate to use for different use cases.
When making changes, consider:
- Which crate should contain your changes per the modular structure,
- Whether your changes affect the public API of `ratatui-core` (requires extra care),
- And how your changes fit into the overall architecture.
[ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md
### Tests
The [test coverage](https://app.codecov.io/gh/ratatui/ratatui) of the crate is reasonably
@@ -181,7 +91,7 @@ good, but this can always be improved. Focus on keeping the tests simple and obv
tests for all new or modified code. Beside the usual doc and unit tests, one of the most valuable
test you can write for Ratatui is a test against the `TestBackend`. It allows you to assert the
content of the output buffer that would have been flushed to the terminal after a given draw call.
See `widgets_block_renders` in [ratatui/tests/widgets_block.rs](./ratatui/tests/widgets_block.rs) for an example.
See `widgets_block_renders` in [tests/widgets_block.rs](./tests/widget_block.rs) for an example.
When writing tests, generally prefer to write unit tests and doc tests directly in the code file
being tested rather than integration tests in the `tests/` folder.
@@ -190,10 +100,6 @@ If an area that you're making a change in is not tested, write tests to characte
behavior before changing it. This helps ensure that we don't introduce bugs to existing software
using Ratatui (and helps make it easy to migrate apps still using `tui-rs`).
> [!IMPORTANT]
> Do not remove existing tests without clear justification. If tests need to be
> modified due to API changes, explain why in your PR description.
For coverage, we have two [bacon](https://dystroy.org/bacon/) jobs (one for all tests, and one for
unit tests, keyboard shortcuts `v` and `u` respectively) that run
[cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov) to report the coverage. Several plugins
@@ -261,14 +167,6 @@ We generally want to wait at least two versions before removing deprecated items
time to update. However, if a deprecation is blocking for us to implement a new feature we may
*consider* removing it in a one version notice.
Deprecation process:
1. Add `#[deprecated]` attribute with a clear message.
2. Update documentation to point to the replacement.
3. Add an entry to `BREAKING-CHANGES.md` if applicable.
4. Wait at least two versions before removal.
5. Consider the impact on the ecosystem before removing.
### Use of unsafe for optimization purposes
We don't currently use any unsafe code in Ratatui, and would like to keep it that way. However, there
@@ -284,7 +182,7 @@ We use GitHub Actions for the CI where we perform the following checks:
- The code should conform to the default format enforced by `rustfmt`.
- The code should not contain common style issues `clippy`.
You can also check most of those things yourself locally using `cargo xtask ci` which will offer you
You can also check most of those things yourself locally using `cargo make ci` which will offer you
a shorter feedback loop than pushing to github.
## Relationship with `tui-rs`

4681
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,8 @@
[workspace]
resolver = "2"
members = ["ratatui", "ratatui-*", "xtask", "examples/apps/*", "examples/concepts/*"]
default-members = [
"ratatui",
"ratatui-core",
"ratatui-crossterm",
# this is not included as it doesn't compile on windows
# "ratatui-termion",
"ratatui-macros",
"ratatui-termwiz",
"ratatui-widgets",
"examples/apps/*",
"examples/concepts/*",
]
[workspace.package]
[package]
name = "ratatui"
version = "0.28.1" # crate version
authors = ["Florian Dehau <work@fdehau.com>", "The Ratatui Developers"]
description = "A library that's all about cooking up terminal user interfaces"
documentation = "https://docs.rs/ratatui/latest/ratatui/"
repository = "https://github.com/ratatui/ratatui"
homepage = "https://ratatui.rs"
@@ -23,68 +10,71 @@ keywords = ["tui", "terminal", "dashboard"]
categories = ["command-line-interface"]
readme = "README.md"
license = "MIT"
exclude = ["assets/*", ".github", "Makefile.toml", "CONTRIBUTING.md", "*.log", "tags"]
edition = "2024"
rust-version = "1.86.0"
exclude = [
"assets/*",
".github",
"Makefile.toml",
"CONTRIBUTING.md",
"*.log",
"tags",
]
edition = "2021"
rust-version = "1.74.0"
[workspace.dependencies]
anstyle = "1"
bitflags = "2.10"
clap = { version = "4.5", features = ["derive"] }
color-eyre = "0.6"
compact_str = { version = "0.9", default-features = false }
criterion = { version = "0.8", features = ["html_reports"] }
crossterm = "0.29"
document-features = "0.2"
fakeit = "1"
futures = "0.3"
hashbrown = "0.16"
[dependencies]
bitflags = "2.3"
cassowary = "0.3"
compact_str = "0.8.0"
crossterm = { version = "0.28.1", optional = true }
document-features = { version = "0.2.7", optional = true }
indoc = "2"
instability = "0.3"
itertools = { version = "0.14", default-features = false, features = ["use_alloc"] }
kasuari = { version = "0.4", default-features = false }
line-clipping = "0.3"
lru = "0.16"
octocrab = "0.49"
palette = "0.7"
pretty_assertions = "1"
rand = "0.9"
rand_chacha = "0.9"
ratatui = { path = "ratatui", version = "0.30.0" }
ratatui-core = { path = "ratatui-core", version = "0.1.0" }
ratatui-crossterm = { path = "ratatui-crossterm", version = "0.1.0" }
ratatui-macros = { path = "ratatui-macros", version = "0.7.0" }
ratatui-termion = { path = "ratatui-termion", version = "0.1.0" }
ratatui-termwiz = { path = "ratatui-termwiz", version = "0.1.0" }
ratatui-widgets = { path = "ratatui-widgets", version = "0.3.0" }
rstest = "0.26"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
strum = { version = "0.27", default-features = false, features = ["derive"] }
termion = "4"
termwiz = "0.23"
thiserror = { version = "2", default-features = false }
time = { version = "0.3.37", default-features = false }
tokio = "1"
tokio-stream = "0.1"
tracing = "0.1"
tracing-appender = "0.2"
tracing-subscriber = "0.3"
trybuild = "1"
unicode-segmentation = "1"
unicode-truncate = { version = "2", default-features = false }
# See <https://github.com/ratatui/ratatui/issues/1271> for information about why we pin unicode-width
unicode-width = ">=0.2.0, <=0.2.2"
instability = "0.3.1"
itertools = "0.13"
lru = "0.12.0"
paste = "1.0.2"
palette = { version = "0.7.6", optional = true }
serde = { version = "1", optional = true, features = ["derive"] }
strum = { version = "0.26.3", features = ["derive"] }
termwiz = { version = "0.22.0", optional = true }
time = { version = "0.3.11", optional = true, features = ["local-offset"] }
unicode-segmentation = "1.10"
unicode-truncate = "1"
unicode-width = "=0.1.13"
# Improve benchmark consistency
[profile.bench]
codegen-units = 1
lto = true
[target.'cfg(not(windows))'.dependencies]
# termion is not supported on Windows
termion = { version = "4.0.0", optional = true }
[workspace.lints.rust]
[dev-dependencies]
argh = "0.1.12"
color-eyre = "0.6.2"
criterion = { version = "0.5.1", features = ["html_reports"] }
crossterm = { version = "0.28.1", features = ["event-stream"] }
fakeit = "1.1"
font8x8 = "0.3.1"
futures = "0.3.30"
indoc = "2"
octocrab = "0.41.0"
pretty_assertions = "1.4.0"
rand = "0.8.5"
rand_chacha = "0.3.1"
rstest = "0.23.0"
serde_json = "1.0.109"
tokio = { version = "1.39.2", features = [
"rt",
"macros",
"time",
"rt-multi-thread",
] }
tracing = "0.1.40"
tracing-appender = "0.2.3"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[lints.rust]
unsafe_code = "forbid"
[workspace.lints.clippy]
[lints.clippy]
cargo = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
cast_possible_truncation = "allow"
cast_possible_wrap = "allow"
@@ -107,7 +97,6 @@ empty_line_after_doc_comments = "warn"
equatable_if_let = "warn"
fn_to_numeric_cast_any = "warn"
format_push_string = "warn"
implicit_clone = "warn"
map_err_ignore = "warn"
missing_const_for_fn = "warn"
mixed_read_write_in_expression = "warn"
@@ -119,5 +108,278 @@ redundant_type_annotations = "warn"
rest_pat_in_fully_bound_structs = "warn"
string_lit_chars_any = "warn"
string_slice = "warn"
string_to_string = "warn"
unnecessary_self_imports = "warn"
use_self = "warn"
[features]
#! The crate provides a set of optional features that can be enabled in your `cargo.toml` file.
#!
## By default, we enable the crossterm backend as this is a reasonable choice for most applications
## as it is supported on Linux/Mac/Windows systems. We also enable the `underline-color` feature
## which allows you to set the underline color of text.
default = ["crossterm", "underline-color"]
#! Generally an application will only use one backend, so you should only enable one of the following features:
## enables the [`CrosstermBackend`](backend::CrosstermBackend) backend and adds a dependency on [`crossterm`].
crossterm = ["dep:crossterm"]
## enables the [`TermionBackend`](backend::TermionBackend) backend and adds a dependency on [`termion`].
termion = ["dep:termion"]
## enables the [`TermwizBackend`](backend::TermwizBackend) backend and adds a dependency on [`termwiz`].
termwiz = ["dep:termwiz"]
#! The following optional features are available for all backends:
## enables serialization and deserialization of style and color types using the [`serde`] crate.
## This is useful if you want to save themes to a file.
serde = ["dep:serde", "bitflags/serde", "compact_str/serde"]
## enables the [`border!`] macro.
macros = []
## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color).
palette = ["dep:palette"]
## Use terminal scrolling regions to make some operations less prone to
## flickering. (i.e. Terminal::insert_before).
scrolling-regions = []
## enables all widgets.
all-widgets = ["widget-calendar"]
#! Widgets that add dependencies are gated behind feature flags to prevent unused transitive
#! dependencies. The available features are:
## enables the [`calendar`](widgets::calendar) widget module and adds a dependency on [`time`].
widget-calendar = ["dep:time"]
#! The following optional features are only available for some backends:
## enables the backend code that sets the underline color.
## Underline color is only supported by the [`CrosstermBackend`](backend::CrosstermBackend) backend,
## and is not supported on Windows 7.
underline-color = ["dep:crossterm"]
#! The following features are unstable and may change in the future:
## Enable all unstable features.
unstable = [
"unstable-rendered-line-info",
"unstable-widget-ref",
"unstable-backend-writer",
]
## Enables the [`Paragraph::line_count`](widgets::Paragraph::line_count)
## [`Paragraph::line_width`](widgets::Paragraph::line_width) methods
## which are experimental and may change in the future.
## See [Issue 293](https://github.com/ratatui/ratatui/issues/293) for more details.
unstable-rendered-line-info = []
## Enables the [`WidgetRef`](widgets::WidgetRef) and [`StatefulWidgetRef`](widgets::StatefulWidgetRef) traits which are experimental and may change in
## the future.
unstable-widget-ref = []
## Enables getting access to backends' writers.
unstable-backend-writer = []
[package.metadata.docs.rs]
all-features = true
# see https://doc.rust-lang.org/nightly/rustdoc/scraped-examples.html
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
rustdoc-args = ["--cfg", "docsrs"]
# Improve benchmark consistency
[profile.bench]
codegen-units = 1
lto = true
[lib]
bench = false
[[bench]]
name = "main"
harness = false
[[example]]
name = "async"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "barchart"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "barchart-grouped"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "block"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "calendar"
required-features = ["crossterm", "widget-calendar"]
doc-scrape-examples = true
[[example]]
name = "canvas"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "chart"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "colors"
required-features = ["crossterm"]
# this example is a bit verbose, so we don't want to include it in the docs
doc-scrape-examples = false
[[example]]
name = "colors_rgb"
required-features = ["crossterm", "palette"]
doc-scrape-examples = true
[[example]]
name = "constraint-explorer"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "constraints"
required-features = ["crossterm"]
doc-scrape-examples = false
[[example]]
name = "custom_widget"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "demo"
# this runs for all of the terminal backends, so it can't be built using --all-features or scraped
doc-scrape-examples = false
[[example]]
name = "demo2"
required-features = ["crossterm", "palette", "widget-calendar"]
doc-scrape-examples = true
[[example]]
name = "docsrs"
required-features = ["crossterm"]
doc-scrape-examples = false
[[example]]
name = "flex"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "gauge"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "hello_world"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "inline"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "layout"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "line_gauge"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "hyperlink"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "list"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "minimal"
required-features = ["crossterm"]
# prefer to show the more featureful examples in the docs
doc-scrape-examples = false
[[example]]
name = "modifiers"
required-features = ["crossterm"]
# this example is a bit verbose, so we don't want to include it in the docs
doc-scrape-examples = false
[[example]]
name = "panic"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "paragraph"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "popup"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "ratatui-logo"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "scrollbar"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "sparkline"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "table"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "tabs"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "tracing"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "user_input"
required-features = ["crossterm"]
doc-scrape-examples = true
[[example]]
name = "widget_impl"
required-features = ["crossterm", "unstable-widget-ref"]
doc-scrape-examples = true
[[test]]
name = "state_serde"
required-features = ["serde"]

View File

@@ -1,7 +1,7 @@
The MIT License (MIT)
Copyright (c) 2016-2022 Florian Dehau
Copyright (c) 2023-2025 The Ratatui Developers
Copyright (c) 2023-2024 The Ratatui Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -5,7 +5,7 @@ This file documents current and past maintainers.
- [orhun](https://github.com/orhun)
- [joshka](https://github.com/joshka)
- [kdheepak](https://github.com/kdheepak)
- [j-g00da](https://github.com/j-g00da)
- [Valentin271](https://github.com/Valentin271)
## Past Maintainers
@@ -13,4 +13,3 @@ This file documents current and past maintainers.
- [mindoodoo](https://github.com/mindoodoo)
- [sayanarijit](https://github.com/sayanarijit)
- [EdJoPaTo](https://github.com/EdJoPaTo)
- [Valentin271](https://github.com/Valentin271)

154
Makefile.toml Normal file
View File

@@ -0,0 +1,154 @@
# configuration for https://github.com/sagiegurari/cargo-make
[config]
skip_core_tasks = true
[env]
# all features except the backend ones
NON_BACKEND_FEATURES = "all-widgets,macros,serde"
[tasks.default]
alias = "ci"
[tasks.ci]
description = "Run continuous integration tasks"
dependencies = ["lint", "clippy", "check", "test"]
[tasks.lint]
description = "Lint code style (formatting, typos, docs, markdown)"
dependencies = ["lint-format", "lint-typos", "lint-docs"]
[tasks.lint-format]
description = "Lint code formatting"
toolchain = "nightly"
command = "cargo"
args = ["fmt", "--all", "--check"]
[tasks.format]
description = "Fix code formatting"
toolchain = "nightly"
command = "cargo"
args = ["fmt", "--all"]
[tasks.lint-typos]
description = "Run typo checks"
install_crate = { crate_name = "typos-cli", binary = "typos", test_arg = "--version" }
command = "typos"
[tasks.lint-docs]
description = "Check documentation for errors and warnings"
toolchain = "nightly"
command = "cargo"
args = [
"rustdoc",
"--all-features",
"--",
"-Zunstable-options",
"--check",
"-Dwarnings",
]
[tasks.lint-markdown]
description = "Check markdown files for errors and warnings"
command = "markdownlint-cli2"
args = ["**/*.md", "!target"]
[tasks.check]
description = "Check code for errors and warnings"
command = "cargo"
args = ["check", "--all-targets", "--all-features"]
[tasks.build]
description = "Compile the project"
command = "cargo"
args = ["build", "--all-targets", "--all-features"]
[tasks.clippy]
description = "Run Clippy for linting"
command = "cargo"
args = [
"clippy",
"--all-targets",
"--all-features",
"--tests",
"--benches",
"--",
"-D",
"warnings",
]
[tasks.install-nextest]
description = "Install cargo-nextest"
install_crate = { crate_name = "cargo-nextest", binary = "cargo-nextest", test_arg = "--help" }
[tasks.test]
description = "Run tests"
run_task = { name = ["test-lib", "test-doc"] }
[tasks.test-lib]
description = "Run default tests"
dependencies = ["install-nextest"]
command = "cargo"
args = ["nextest", "run", "--all-targets", "--all-features"]
[tasks.test-doc]
description = "Run documentation tests"
command = "cargo"
args = ["test", "--doc", "--all-features"]
[tasks.test-backend]
# takes a command line parameter to specify the backend to test (e.g. "crossterm")
description = "Run backend-specific tests"
dependencies = ["install-nextest"]
command = "cargo"
args = [
"nextest",
"run",
"--all-targets",
"--no-default-features",
"--features",
"${NON_BACKEND_FEATURES},${@}",
]
[tasks.coverage]
description = "Generate code coverage report"
command = "cargo"
args = [
"llvm-cov",
"--lcov",
"--output-path",
"target/lcov.info",
"--all-features",
]
[tasks.run-example]
private = true
condition = { env_set = ["TUI_EXAMPLE_NAME"] }
command = "cargo"
args = [
"run",
"--release",
"--example",
"${TUI_EXAMPLE_NAME}",
"--features",
"all-widgets",
]
[tasks.build-examples]
description = "Compile project examples"
command = "cargo"
args = ["build", "--examples", "--release", "--features", "all-widgets"]
[tasks.run-examples]
description = "Run project examples"
dependencies = ["build-examples"]
script = '''
#!@duckscript
files = glob_array ./examples/*.rs
for file in ${files}
name = basename ${file}
name = substring ${name} -3
set_env TUI_EXAMPLE_NAME ${name}
cm_run_task run-example
end
'''

493
README.md
View File

@@ -1,182 +1,433 @@
<details>
<summary>Table of Contents</summary>
- [Quickstart](#quickstart)
- [Documentation](#documentation)
- [Templates](#templates)
- [Built with Ratatui](#built-with-ratatui)
- [Alternatives](#alternatives)
- [Contributing](#contributing)
- [Acknowledgements](#acknowledgements)
- [License](#license)
- [Ratatui](#ratatui)
- [Installation](#installation)
- [Introduction](#introduction)
- [Other documentation](#other-documentation)
- [Quickstart](#quickstart)
- [Initialize and restore the terminal](#initialize-and-restore-the-terminal)
- [Drawing the UI](#drawing-the-ui)
- [Handling events](#handling-events)
- [Example](#example)
- [Layout](#layout)
- [Text and styling](#text-and-styling)
- [Status of this fork](#status-of-this-fork)
- [Widgets](#widgets)
- [Built in](#built-in)
- [Third-party libraries, bootstrapping templates and widgets](#third-party-libraries-bootstrapping-templates-and-widgets)
- [Apps](#apps)
- [Alternatives](#alternatives)
- [Acknowledgments](#acknowledgments)
- [License](#license)
</details>
![Release header](https://github.com/ratatui/ratatui/blob/b23480adfa9430697071c906c7ba4d4f9bd37a73/assets/release-header.png?raw=true)
<!-- cargo-rdme start -->
![Demo](https://github.com/ratatui/ratatui/blob/87ae72dbc756067c97f6400d3e2a58eeb383776e/examples/demo2-destroy.gif?raw=true)
<div align="center">
[![Crate Badge]][Crate] [![Repo Badge]][Repo] [![Docs Badge]][Docs] [![License Badge]][License] \
[![CI Badge]][CI] [![Deps Badge]][Deps] [![Codecov Badge]][Codecov] [![Sponsors Badge]][Sponsors] \
[Ratatui Website] · [Docs] · [Widget Examples] · [App Examples] · [Changelog] \
[Breaking Changes] · [Contributing] · [Report a bug] · [Request a Feature]
[![Crate Badge]][Crate] [![Docs Badge]][API Docs] [![CI Badge]][CI Workflow] [![Deps.rs
Badge]][Deps.rs]<br> [![Codecov Badge]][Codecov] [![License Badge]](./LICENSE) [![Sponsors
Badge]][GitHub Sponsors]<br> [![Discord Badge]][Discord Server] [![Matrix Badge]][Matrix]
[![Forum Badge]][Forum]<br>
[Ratatui Website] · [API Docs] · [Examples] · [Changelog] · [Breaking Changes]<br>
[Contributing] · [Report a bug] · [Request a Feature] · [Create a Pull Request]
</div>
[Ratatui][Ratatui Website] (_ˌræ.təˈtu.i_) is a Rust crate for cooking up terminal user interfaces
(TUIs). It provides a simple and flexible way to create text-based user interfaces in the terminal,
which can be used for command-line applications, dashboards, and other interactive console programs.
# Ratatui
## Quickstart
[Ratatui][Ratatui Website] is a crate for cooking up terminal user interfaces in Rust. It is a
lightweight library that provides a set of widgets and utilities to build complex Rust TUIs.
Ratatui was forked from the [tui-rs] crate in 2023 in order to continue its development.
Ratatui has [templates] available to help you get started quickly. You can use the
[`cargo-generate`] command to create a new project with Ratatui:
## Installation
Add `ratatui` as a dependency to your cargo.toml:
```shell
cargo install --locked cargo-generate
cargo generate ratatui/templates
cargo add ratatui
```
Selecting the Hello World template produces the following application:
Ratatui uses [Crossterm] by default as it works on most platforms. See the [Installation]
section of the [Ratatui Website] for more details on how to use other backends ([Termion] /
[Termwiz]).
```rust
use color_eyre::Result;
use crossterm::event::{self, Event};
use ratatui::{DefaultTerminal, Frame};
## Introduction
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = run(terminal);
ratatui::restore();
result
}
Ratatui is based on the principle of immediate rendering with intermediate buffers. This means
that for each frame, your app must render all widgets that are supposed to be part of the UI.
This is in contrast to the retained mode style of rendering where widgets are updated and then
automatically redrawn on the next frame. See the [Rendering] section of the [Ratatui Website]
for more info.
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if matches!(event::read()?, Event::Key(_)) {
break Ok(());
}
}
}
You can also watch the [FOSDEM 2024 talk] about Ratatui which gives a brief introduction to
terminal user interfaces and showcases the features of Ratatui, along with a hello world demo.
fn render(frame: &mut Frame) {
frame.render_widget("hello world", frame.area());
}
```
## Other documentation
## Documentation
- [Docs] - the full API documentation for the library on docs.rs.
- [Ratatui Website] - explains the library's concepts and provides step-by-step tutorials.
- [Ratatui Forum] - a place to ask questions and discuss the library.
- [Widget Examples] - a collection of examples that demonstrate how to use the library.
- [App Examples] - a collection of more complex examples that demonstrate how to build apps.
- [ARCHITECTURE.md] - explains the crate organization and modular workspace structure.
- [Ratatui Website] - explains the library's concepts and provides step-by-step tutorials
- [Ratatui Forum][Forum] - a place to ask questions and discuss the library
- [API Docs] - the full API documentation for the library on docs.rs.
- [Examples] - a collection of examples that demonstrate how to use the library.
- [Contributing] - Please read this if you are interested in contributing to the project.
- [Changelog] - generated by [git-cliff] utilizing [Conventional Commits].
- [Breaking Changes] - a list of breaking changes in the library.
You can also watch the [EuroRust 2024 talk] to learn about common concepts in Ratatui and what's
possible to build with it.
## Quickstart
## Templates
The following example demonstrates the minimal amount of code necessary to setup a terminal and
render "Hello World!". The full code for this example which contains a little more detail is in
the [Examples] directory. For more guidance on different ways to structure your application see
the [Application Patterns] and [Hello World tutorial] sections in the [Ratatui Website] and the
various [Examples]. There are also several starter templates available in the [templates]
repository.
If you're looking to get started quickly, you can use one of the available templates from the
[templates] repository using [`cargo-generate`]:
Every application built with `ratatui` needs to implement the following steps:
```shell
cargo generate ratatui/templates
- Initialize the terminal
- A main loop to:
- Handle input events
- Draw the UI
- Restore the terminal state
The library contains a [`prelude`] module that re-exports the most commonly used traits and
types for convenience. Most examples in the documentation will use this instead of showing the
full path of each type.
### Initialize and restore the terminal
The [`Terminal`] type is the main entry point for any Ratatui application. It is a light
abstraction over a choice of [`Backend`] implementations that provides functionality to draw
each frame, clear the screen, hide the cursor, etc. It is parametrized over any type that
implements the [`Backend`] trait which has implementations for [Crossterm], [Termion] and
[Termwiz].
Most applications should enter the Alternate Screen when starting and leave it when exiting and
also enable raw mode to disable line buffering and enable reading key events. See the [`backend`
module] and the [Backends] section of the [Ratatui Website] for more info.
### Drawing the UI
The drawing logic is delegated to a closure that takes a [`Frame`] instance as argument. The
[`Frame`] provides the size of the area to draw to and allows the app to render any [`Widget`]
using the provided [`render_widget`] method. After this closure returns, a diff is performed and
only the changes are drawn to the terminal. See the [Widgets] section of the [Ratatui Website]
for more info.
### Handling events
Ratatui does not include any input handling. Instead event handling can be implemented by
calling backend library methods directly. See the [Handling Events] section of the [Ratatui
Website] for more info. For example, if you are using [Crossterm], you can use the
[`crossterm::event`] module to handle events.
### Example
```rust
use std::io::{self, stdout};
use ratatui::{
backend::CrosstermBackend,
crossterm::{
event::{self, Event, KeyCode},
terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
},
ExecutableCommand,
},
widgets::{Block, Paragraph},
Frame, Terminal,
};
fn main() -> io::Result<()> {
enable_raw_mode()?;
stdout().execute(EnterAlternateScreen)?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
let mut should_quit = false;
while !should_quit {
terminal.draw(ui)?;
should_quit = handle_events()?;
}
disable_raw_mode()?;
stdout().execute(LeaveAlternateScreen)?;
Ok(())
}
fn handle_events() -> io::Result<bool> {
if event::poll(std::time::Duration::from_millis(50))? {
if let Event::Key(key) = event::read()? {
if key.kind == event::KeyEventKind::Press && key.code == KeyCode::Char('q') {
return Ok(true);
}
}
}
Ok(false)
}
fn ui(frame: &mut Frame) {
frame.render_widget(
Paragraph::new("Hello World!").block(Block::bordered().title("Greeting")),
frame.area(),
);
}
```
## Built with Ratatui
Running this example produces the following output:
[![Awesome](https://awesome.re/badge-flat2.svg)][awesome-ratatui]
![docsrs-hello]
Check out the [showcase] section of the website, or the [awesome-ratatui] repository for a curated
list of awesome apps and libraries built with Ratatui!
## Layout
## Alternatives
The library comes with a basic yet useful layout management object called [`Layout`] which
allows you to split the available space into multiple areas and then render widgets in each
area. This lets you describe a responsive terminal UI by nesting layouts. See the [Layout]
section of the [Ratatui Website] for more info.
- [Cursive](https://crates.io/crates/cursive) - a ncurses-based TUI library.
- [iocraft](https://crates.io/crates/iocraft) - a declarative TUI library.
```rust
use ratatui::{
layout::{Constraint, Layout},
widgets::Block,
Frame,
};
## Contributing
fn ui(frame: &mut Frame) {
let [title_area, main_area, status_area] = Layout::vertical([
Constraint::Length(1),
Constraint::Min(0),
Constraint::Length(1),
])
.areas(frame.area());
let [left_area, right_area] =
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.areas(main_area);
[![Discord Badge]][Discord Server] [![Matrix Badge]][Matrix] [![Forum Badge]][Ratatui Forum]
Feel free to join our [Discord server](https://discord.gg/pMCEU9hNEj) for discussions and questions!
There is also a [Matrix](https://matrix.org/) bridge available at
[#ratatui:matrix.org](https://matrix.to/#/#ratatui:matrix.org). We have also recently launched the
[Ratatui Forum].
We rely on GitHub for [bugs][Report a bug] and [feature requests][Request a Feature].
Please make sure you read the [contributing](./CONTRIBUTING.md) guidelines before [creating a pull
request][Create a Pull Request]. We accept AI generated code, but please read the [AI Contributions]
guidelines to ensure compliance.
If you'd like to show your support, you can add the Ratatui badge to your project's README:
```md
[![Built With Ratatui](https://img.shields.io/badge/Built_With_Ratatui-000?logo=ratatui&logoColor=fff)](https://ratatui.rs/)
frame.render_widget(Block::bordered().title("Title Bar"), title_area);
frame.render_widget(Block::bordered().title("Status Bar"), status_area);
frame.render_widget(Block::bordered().title("Left"), left_area);
frame.render_widget(Block::bordered().title("Right"), right_area);
}
```
[![Built With Ratatui](https://img.shields.io/badge/Built_With_Ratatui-000?logo=ratatui&logoColor=fff)](https://ratatui.rs/)
Running this example produces the following output:
## Acknowledgements
![docsrs-layout]
Ratatui was forked from the [tui-rs] crate in 2023 in order to continue its development. None of
this could be possible without [Florian Dehau] who originally created [tui-rs] which inspired many
Rust TUIs.
## Text and styling
Special thanks to [Pavel Fomchenkov] for his work in designing an awesome logo for the Ratatui
project and organization.
The [`Text`], [`Line`] and [`Span`] types are the building blocks of the library and are used in
many places. [`Text`] is a list of [`Line`]s and a [`Line`] is a list of [`Span`]s. A [`Span`]
is a string with a specific style.
## License
The [`style` module] provides types that represent the various styling options. The most
important one is [`Style`] which represents the foreground and background colors and the text
attributes of a [`Span`]. The [`style` module] also provides a [`Stylize`] trait that allows
short-hand syntax to apply a style to widgets and text. See the [Styling Text] section of the
[Ratatui Website] for more info.
This project is licensed under the [MIT License][License].
```rust
use ratatui::{
layout::{Constraint, Layout},
style::{Color, Modifier, Style, Stylize},
text::{Line, Span},
widgets::{Block, Paragraph},
Frame,
};
fn ui(frame: &mut Frame) {
let areas = Layout::vertical([Constraint::Length(1); 4]).split(frame.area());
let line = Line::from(vec![
Span::raw("Hello "),
Span::styled(
"World",
Style::new()
.fg(Color::Green)
.bg(Color::White)
.add_modifier(Modifier::BOLD),
),
"!".red().on_light_yellow().italic(),
]);
frame.render_widget(line, areas[0]);
// using the short-hand syntax and implicit conversions
let paragraph = Paragraph::new("Hello World!".red().on_white().bold());
frame.render_widget(paragraph, areas[1]);
// style the whole widget instead of just the text
let paragraph = Paragraph::new("Hello World!").style(Style::new().red().on_white());
frame.render_widget(paragraph, areas[2]);
// use the simpler short-hand syntax
let paragraph = Paragraph::new("Hello World!").blue().on_yellow();
frame.render_widget(paragraph, areas[3]);
}
```
Running this example produces the following output:
![docsrs-styling]
[Repo]: https://github.com/ratatui/ratatui
[Ratatui Website]: https://ratatui.rs/
[Ratatui Forum]: https://forum.ratatui.rs
[Docs]: https://docs.rs/ratatui
[Widget Examples]: https://github.com/ratatui/ratatui/tree/main/ratatui-widgets/examples
[App Examples]: https://github.com/ratatui/ratatui/tree/main/examples
[ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md
[Changelog]: https://github.com/ratatui/ratatui/blob/main/CHANGELOG.md
[git-cliff]: https://git-cliff.org
[Conventional Commits]: https://www.conventionalcommits.org
[Breaking Changes]: https://github.com/ratatui/ratatui/blob/main/BREAKING-CHANGES.md
[EuroRust 2024 talk]: https://www.youtube.com/watch?v=hWG51Mc1DlM
[Installation]: https://ratatui.rs/installation/
[Rendering]: https://ratatui.rs/concepts/rendering/
[Application Patterns]: https://ratatui.rs/concepts/application-patterns/
[Hello World tutorial]: https://ratatui.rs/tutorials/hello-world/
[Backends]: https://ratatui.rs/concepts/backends/
[Widgets]: https://ratatui.rs/how-to/widgets/
[Handling Events]: https://ratatui.rs/concepts/event-handling/
[Layout]: https://ratatui.rs/how-to/layout/
[Styling Text]: https://ratatui.rs/how-to/render/style-text/
[templates]: https://github.com/ratatui/templates/
[Examples]: https://github.com/ratatui/ratatui/tree/main/examples/README.md
[Report a bug]: https://github.com/ratatui/ratatui/issues/new?labels=bug&projects=&template=bug_report.md
[Request a Feature]: https://github.com/ratatui/ratatui/issues/new?labels=enhancement&projects=&template=feature_request.md
[Create a Pull Request]: https://github.com/ratatui/ratatui/compare
[git-cliff]: https://git-cliff.org
[Conventional Commits]: https://www.conventionalcommits.org
[API Docs]: https://docs.rs/ratatui
[Changelog]: https://github.com/ratatui/ratatui/blob/main/CHANGELOG.md
[Contributing]: https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md
[AI Contributions]: https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#ai-generated-content
[Breaking Changes]: https://github.com/ratatui/ratatui/blob/main/BREAKING-CHANGES.md
[FOSDEM 2024 talk]: https://www.youtube.com/watch?v=NU0q6NOLJ20
[docsrs-hello]: https://github.com/ratatui/ratatui/blob/c3c3c289b1eb8d562afb1931adb4dc719cd48490/examples/docsrs-hello.png?raw=true
[docsrs-layout]: https://github.com/ratatui/ratatui/blob/c3c3c289b1eb8d562afb1931adb4dc719cd48490/examples/docsrs-layout.png?raw=true
[docsrs-styling]: https://github.com/ratatui/ratatui/blob/c3c3c289b1eb8d562afb1931adb4dc719cd48490/examples/docsrs-styling.png?raw=true
[`Frame`]: terminal::Frame
[`render_widget`]: terminal::Frame::render_widget
[`Widget`]: widgets::Widget
[`Layout`]: layout::Layout
[`Text`]: text::Text
[`Line`]: text::Line
[`Span`]: text::Span
[`Style`]: style::Style
[`style` module]: style
[`Stylize`]: style::Stylize
[`Backend`]: backend::Backend
[`backend` module]: backend
[`crossterm::event`]: https://docs.rs/crossterm/latest/crossterm/event/index.html
[Crate]: https://crates.io/crates/ratatui
[Crossterm]: https://crates.io/crates/crossterm
[Termion]: https://crates.io/crates/termion
[Termwiz]: https://crates.io/crates/termwiz
[tui-rs]: https://crates.io/crates/tui
[Sponsors]: https://github.com/sponsors/ratatui
[Crate Badge]: https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square&color=E05D44
[Repo Badge]: https://img.shields.io/badge/repo-ratatui/ratatui-1370D3?style=flat-square&logo=github
[GitHub Sponsors]: https://github.com/sponsors/ratatui
[Crate Badge]: https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square&logoColor=E05D44&color=E05D44
[License Badge]: https://img.shields.io/crates/l/ratatui?style=flat-square&color=1370D3
[CI Badge]: https://img.shields.io/github/actions/workflow/status/ratatui/ratatui/ci.yml?style=flat-square&logo=github
[CI]: https://github.com/ratatui/ratatui/actions/workflows/ci.yml
[Codecov Badge]: https://img.shields.io/codecov/c/github/ratatui/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST&color=C43AC3
[CI Workflow]: https://github.com/ratatui/ratatui/actions/workflows/ci.yml
[Codecov Badge]: https://img.shields.io/codecov/c/github/ratatui/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST&color=C43AC3&logoColor=C43AC3
[Codecov]: https://app.codecov.io/gh/ratatui/ratatui
[Deps Badge]: https://deps.rs/repo/github/ratatui/ratatui/status.svg?path=ratatui&style=flat-square
[Deps]: https://deps.rs/repo/github/ratatui/ratatui?path=ratatui
[Deps.rs Badge]: https://deps.rs/repo/github/ratatui/ratatui/status.svg?style=flat-square
[Deps.rs]: https://deps.rs/repo/github/ratatui/ratatui
[Discord Badge]: https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square&color=1370D3&logoColor=1370D3
[Discord Server]: https://discord.gg/pMCEU9hNEj
[Docs Badge]: https://img.shields.io/badge/docs-ratatui-1370D3?style=flat-square&logo=rust
[Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square&logoColor=E05D44
[Matrix Badge]: https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix&color=C43AC3
[Matrix]: https://matrix.to/#/#ratatui:matrix.org
[Forum Badge]: https://img.shields.io/discourse/likes?server=https%3A%2F%2Fforum.ratatui.rs&style=flat-square&logo=discourse&label=forum&color=C43AC3
[Forum]: https://forum.ratatui.rs
[Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui?logo=github&style=flat-square&color=1370D3
[templates]: https://github.com/ratatui/templates/
[showcase]: https://ratatui.rs/showcase/
[awesome-ratatui]: https://github.com/ratatui/awesome-ratatui
[Pavel Fomchenkov]: https://github.com/nawok
[Florian Dehau]: https://github.com/fdehau
[`cargo-generate`]: https://crates.io/crates/cargo-generate
[License]: ./LICENSE
<!-- cargo-rdme end -->
## Status of this fork
In response to the original maintainer [**Florian Dehau**](https://github.com/fdehau)'s issue
regarding the [future of `tui-rs`](https://github.com/fdehau/tui-rs/issues/654), several members of
the community forked the project and created this crate. We look forward to continuing the work
started by Florian 🚀
In order to organize ourselves, we currently use a [Discord server](https://discord.gg/pMCEU9hNEj),
feel free to join and come chat! There is also a [Matrix](https://matrix.org/) bridge available at
[#ratatui:matrix.org](https://matrix.to/#/#ratatui:matrix.org).
While we do utilize Discord for coordinating, it's not essential for contributing. We have recently
launched the [Ratatui Forum][Forum], and our primary open-source workflow is centered around GitHub.
For bugs and features, we rely on GitHub. Please [Report a bug], [Request a Feature] or [Create a
Pull Request].
Please make sure you read the updated [contributing](./CONTRIBUTING.md) guidelines, especially if
you are interested in working on a PR or issue opened in the previous repository.
## Widgets
### Built in
The library comes with the following
[widgets](https://docs.rs/ratatui/latest/ratatui/widgets/index.html):
- [BarChart](https://docs.rs/ratatui/latest/ratatui/widgets/struct.BarChart.html)
- [Block](https://docs.rs/ratatui/latest/ratatui/widgets/block/struct.Block.html)
- [Calendar](https://docs.rs/ratatui/latest/ratatui/widgets/calendar/index.html)
- [Canvas](https://docs.rs/ratatui/latest/ratatui/widgets/canvas/struct.Canvas.html) which allows
rendering [points, lines, shapes and a world
map](https://docs.rs/ratatui/latest/ratatui/widgets/canvas/index.html)
- [Chart](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Chart.html)
- [Clear](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Clear.html)
- [Gauge](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Gauge.html)
- [List](https://docs.rs/ratatui/latest/ratatui/widgets/struct.List.html)
- [Paragraph](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Paragraph.html)
- [Scrollbar](https://docs.rs/ratatui/latest/ratatui/widgets/scrollbar/struct.Scrollbar.html)
- [Sparkline](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Sparkline.html)
- [Table](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Table.html)
- [Tabs](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Tabs.html)
Each widget has an associated example which can be found in the [Examples] folder. Run each example
with cargo (e.g. to run the gauge example `cargo run --example gauge`), and quit by pressing `q`.
You can also run all examples by running `cargo make run-examples` (requires `cargo-make` that can
be installed with `cargo install cargo-make`).
### Third-party libraries, bootstrapping templates and widgets
- [ansi-to-tui](https://github.com/uttarayan21/ansi-to-tui) — Convert ansi colored text to
`ratatui::text::Text`
- [color-to-tui](https://github.com/uttarayan21/color-to-tui) — Parse hex colors to
`ratatui::style::Color`
- [templates](https://github.com/ratatui/templates) — Starter templates for
bootstrapping a Rust TUI application with Ratatui & crossterm
- [tui-builder](https://github.com/jkelleyrtp/tui-builder) — Batteries-included MVC framework for
Tui-rs + Crossterm apps
- [tui-clap](https://github.com/kegesch/tui-clap-rs) — Use clap-rs together with Tui-rs
- [tui-log](https://github.com/kegesch/tui-log-rs) — Example of how to use logging with Tui-rs
- [tui-logger](https://github.com/gin66/tui-logger) — Logger and Widget for Tui-rs
- [tui-realm](https://github.com/veeso/tui-realm) — Tui-rs framework to build stateful applications
with a React/Elm inspired approach
- [tui-realm-treeview](https://github.com/veeso/tui-realm-treeview) — Treeview component for
Tui-realm
- [tui-rs-tree-widgets](https://github.com/EdJoPaTo/tui-rs-tree-widget) — Widget for tree data
structures.
- [tui-windows](https://github.com/markatk/tui-windows-rs) — Tui-rs abstraction to handle multiple
windows and their rendering
- [tui-textarea](https://github.com/rhysd/tui-textarea) — Simple yet powerful multi-line text editor
widget supporting several key shortcuts, undo/redo, text search, etc.
- [tui-input](https://github.com/sayanarijit/tui-input) — TUI input library supporting multiple
backends and tui-rs.
- [tui-term](https://github.com/a-kenji/tui-term) — A pseudoterminal widget library
that enables the rendering of terminal applications as ratatui widgets.
## Apps
Check out [awesome-ratatui](https://github.com/ratatui/awesome-ratatui) for a curated list of
awesome apps/libraries built with `ratatui`!
## Alternatives
You might want to checkout [Cursive](https://github.com/gyscos/Cursive) for an alternative solution
to build text user interfaces in Rust.
## Acknowledgments
Special thanks to [**Pavel Fomchenkov**](https://github.com/nawok) for his work in designing **an
awesome logo** for the ratatui project and ratatui organization.
## License
[MIT](./LICENSE)

View File

@@ -36,7 +36,7 @@ actions](.github/workflows/cd.yml) and triggered by pushing a tag.
## Alpha Releases
Alpha releases are automatically released every Saturday via [cd.yml](./.github/workflows/cd.yml)
and can be manually created when necessary by triggering the [Continuous
and can be manually be created when necessary by triggering the [Continuous
Deployment](https://github.com/ratatui/ratatui/actions/workflows/cd.yml) workflow.
We automatically release an alpha release with a patch level bump + alpha.num weekly (and when we

View File

@@ -1 +0,0 @@
<svg role="img" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><title>Ratatui</title><path d="M17 29h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1h1v1H5v-1H4v-1H3v-1H2v-1H1v-1H0v-2h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h2zm-2 14h1v1h1v1h1v-2h-1v-1h-1v-1h-1zm0-6h-3v1h1v1h4v-4h-1v-1h-1zm35-21h-1v4h-1v1h-1v1h-1v1h-2v1h-1v1h-1v1h-1v1h-2v9h5v1h1v9h-1v1h-1v2h-1v1h-1v-1h-1v-1h-1v-1h1v-1h1v-1h1v-5h-1v1h-3v1h-1v3h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-2h1v-2h1v-1h-2v1h-4v-1h-1v-1h-1v-1h-1v-2h1v-1h1v-1h2v-1h7v-1h1v-1h1v-1h1v-1h1v-1h1v-1h4v-1h3v-1h10zm-17 2h-1v2h1v1h2v-1h1v-2h-1v-1h-2zM29 1h1v9h1v1h1v2h1v2h-1v1h-1v1h-1v1h-1v1h-1v1h-3v-1h-1v-1h-2v-1h-1v-1h-1v-1h-6v-1h-1V9h1V8h1V7h1V6h1V5h1V4h1V3h1V2h1V1h2V0h6z"/></svg>

Before

Width:  |  Height:  |  Size: 831 B

View File

@@ -1 +0,0 @@
<svg role="img" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><title>Ratatui</title><path d="M50 16h-1v4h-1v1h-1v1h-1v1h-2v1h-1v1h-1v1h-1v1h-2v9h5v1h1v9h-1v1h-1v2h-1v1h-1v-1h-1v1h-2v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-2v1h-1v1h-1v1h-1v1h-1v1h-1v1h-1v1H9v1H8v1H7v1H6v1H5v1H4v1H3v1H2v2h1v1h1v1h1v1h1v1h1v1H5v-1H4v-1H3v-1H2v-1H1v-1H0v-2h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h2v1h1v-1h-1v-1h-1v-2h1v-1h1v-1h2v-1h7v-1h1v-1h1v-1h1v-1h1v-1h1v-1h4v-1h3v-1h10zM39 49h1v-1h-1zm2-8h-3v1h-1v3h-1v-1h-1v1h1v1h1v1h1v1h1v-1h1v-1h1v-1h1v-5h-1zm-7 3h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-1-1h1v-1h-1zm-5-5h1v1h1v1h1v1h1v1h1v-2h1v-2h1v-1h-2v1h-4v-1h-1zm14-11h-1v2h1v1h2v-1h1v-2h-1v-1h-2zM18 40h1v1h1v2h-1v-1h-1v-1h-1v-2h1zm0-7h1v4h-4v-1h-1v-1h3v-3h1zM29 1h1v9h1v1h1v2h1v2h-1v1h-1v1h-1v1h-1v1h-1v1h-3v-1h-1v-1h-2v-1h-1v-1h-1v-1h-6v-1h-1V9h1V8h1V7h1V6h1V5h1V4h1V3h1V2h1V1h2V0h6z"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -8,80 +8,101 @@
default_job = "check"
[jobs.check]
command = ["cargo", "xtask", "check"]
command = ["cargo", "check", "--all-features", "--color", "always"]
need_stdout = false
[jobs.check-all]
command = ["cargo", "xtask", "check", "--all-features"]
command = ["cargo", "check", "--all-targets", "--all-features", "--color", "always"]
need_stdout = false
[jobs.check-crossterm]
command = ["cargo", "xtask", "check-backend", "crossterm"]
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "crossterm"]
need_stdout = false
[jobs.check-termion]
command = ["cargo", "xtask", "check-backend", "termion"]
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "termion"]
need_stdout = false
[jobs.check-termwiz]
command = ["cargo", "xtask", "check-backend", "termwiz"]
command = ["cargo", "check", "--color", "always", "--all-targets", "--no-default-features", "--features", "termwiz"]
need_stdout = false
[jobs.clippy-all]
command = ["cargo", "xtask", "clippy"]
[jobs.clippy]
command = [
"cargo", "clippy",
"--all-targets",
"--color", "always",
]
need_stdout = false
[jobs.test]
command = ["cargo", "xtask", "test"]
command = [
"cargo", "test",
"--all-features",
"--color", "always",
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.test-unit]
command = ["cargo", "xtask", "test-libs"]
command = [
"cargo", "test",
"--lib",
"--all-features",
"--color", "always",
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.doc]
command = ["cargo", "xtask", "docs"]
command = [
"cargo", "+nightly", "doc",
"-Zunstable-options", "-Zrustdoc-scrape-examples",
"--all-features",
"--color", "always",
"--no-deps",
]
env.RUSTDOCFLAGS = "--cfg docsrs"
need_stdout = false
# If the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "xtask", "docs", "--open"]
on_success = "job:doc"
command = [
"cargo", "+nightly", "doc",
"-Zunstable-options", "-Zrustdoc-scrape-examples",
"--all-features",
"--color", "always",
"--no-deps",
"--open",
]
env.RUSTDOCFLAGS = "--cfg docsrs"
need_stdout = false
on_success = "job:doc" # so that we don't open the browser at each change
[jobs.coverage]
command = ["cargo", "xtask", "coverage"]
command = [
"cargo", "llvm-cov",
"--lcov", "--output-path", "target/lcov.info",
"--all-features",
"--color", "always",
]
[jobs.coverage-unit-tests-only]
command = ["cargo", "xtask", "coverage", "--lib"]
[jobs.hack]
command = ["cargo", "xtask", "hack"]
[jobs.format]
command = ["cargo", "xtask", "format"]
[jobs.zizmor-offline]
# zizmor checks the workflow files for security issues. The offline version is generally faster, but
# checks for fewer issues.
command = ["zizmor", "--color", "always", ".github/workflows", "--offline"]
need_stdout = true
default_watch = false
watch = [".github/workflows/"]
[jobs.zizmor-online]
# zizmor checks the workflow files for security issues. The online version is a bit slower, but it
# checks for more issues
command = ["zizmor", "--color", "always", ".github/workflows"]
need_stdout = true
default_watch = false
watch = [".github/workflows/"]
command = [
"cargo", "llvm-cov",
"--lcov", "--output-path", "target/lcov.info",
"--lib",
"--all-features",
"--color", "always",
]
# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
ctrl-h = "job:hack"
# alt-m = "job:my-job"
ctrl-c = "job:check-crossterm"
ctrl-t = "job:check-termion"
ctrl-w = "job:check-termwiz"
@@ -89,6 +110,3 @@ v = "job:coverage"
ctrl-v = "job:coverage-unit-tests-only"
u = "job:test-unit"
n = "job:nextest"
f = "job:format"
z = "job:zizmor-offline"
shift-z = "job:zizmor-online"

View File

@@ -2,15 +2,12 @@ pub mod main {
pub mod barchart;
pub mod block;
pub mod buffer;
pub mod constraints;
pub mod gauge;
pub mod line;
pub mod list;
pub mod paragraph;
pub mod rect;
pub mod sparkline;
pub mod table;
pub mod text;
}
pub use main::*;
@@ -24,7 +21,4 @@ criterion::criterion_main!(
rect::benches,
sparkline::benches,
table::benches,
text::benches,
constraints::benches,
gauge::benches,
);

View File

@@ -1,20 +1,22 @@
use criterion::{Bencher, BenchmarkId, Criterion, criterion_group};
use criterion::{criterion_group, Bencher, BenchmarkId, Criterion};
use rand::Rng;
use ratatui::buffer::Buffer;
use ratatui::layout::{Direction, Rect};
use ratatui::widgets::{Bar, BarChart, BarGroup, Widget};
use ratatui::{
buffer::Buffer,
layout::{Direction, Rect},
widgets::{Bar, BarChart, BarGroup, Widget},
};
/// Benchmark for rendering a barchart.
fn barchart(c: &mut Criterion) {
let mut group = c.benchmark_group("barchart");
let mut rng = rand::rng();
let mut rng = rand::thread_rng();
for data_count in [64, 256, 2048] {
let data: Vec<Bar> = (0..data_count)
.map(|i| {
Bar::default()
.label(format!("B{i}"))
.value(rng.random_range(0..data_count))
.label(format!("B{i}").into())
.value(rng.gen_range(0..data_count))
})
.collect();
@@ -52,7 +54,7 @@ fn barchart(c: &mut Criterion) {
group.finish();
}
/// Render the widget in a classical size buffer.
/// Render the widget in a classical size buffer
fn render(bencher: &mut Bencher, barchart: &BarChart) {
let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50));
// We use `iter_batched` to clone the value in the setup function.

View File

@@ -1,8 +1,10 @@
use criterion::{BatchSize, Bencher, Criterion, criterion_group};
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect};
use ratatui::text::Line;
use ratatui::widgets::{Block, Padding, Widget};
use criterion::{criterion_group, BatchSize, Bencher, Criterion};
use ratatui::{
buffer::Buffer,
layout::{Alignment, Rect},
text::Line,
widgets::{Block, Padding, Widget},
};
/// Benchmark for rendering a block.
fn block(c: &mut Criterion) {
@@ -36,7 +38,7 @@ fn block(c: &mut Criterion) {
group.finish();
}
/// Render the block into a buffer of the given `size`.
/// render the block into a buffer of the given `size`
fn render(bencher: &mut Bencher, block: &Block, size: Rect) {
let mut buffer = Buffer::empty(size);
// We use `iter_batched` to clone the value in the setup function.

View File

@@ -1,11 +1,14 @@
use std::hint::black_box;
use std::iter::zip;
use criterion::{BenchmarkId, Criterion};
use ratatui::buffer::{Buffer, Cell};
use ratatui::layout::Rect;
use ratatui::text::Line;
use criterion::{black_box, BenchmarkId, Criterion};
use ratatui::{
buffer::{Buffer, Cell},
layout::Rect,
text::Line,
widgets::Widget,
};
criterion::criterion_group!(benches, empty, filled, with_lines);
criterion::criterion_group!(benches, empty, filled, with_lines, diff);
const fn rect(size: u16) -> Rect {
Rect::new(0, 0, size, size)
@@ -58,3 +61,37 @@ fn with_lines(c: &mut Criterion) {
}
group.finish();
}
fn diff(c: &mut Criterion) {
const AREA: Rect = Rect {
x: 0,
y: 0,
width: 200,
height: 50,
};
c.bench_function("buffer/diff", |b| {
let buffer_1 = create_random_buffer(AREA);
let buffer_2 = create_random_buffer(AREA);
b.iter(|| {
let _ = black_box(&buffer_1).diff(black_box(&buffer_2));
});
});
}
fn create_random_buffer(area: Rect) -> Buffer {
const PARAGRAPH_COUNT: i64 = 15;
const SENTENCE_COUNT: i64 = 5;
const WORD_COUNT: i64 = 20;
const SEPARATOR: &str = "\n\n";
let paragraphs = fakeit::words::paragraph(
PARAGRAPH_COUNT,
SENTENCE_COUNT,
WORD_COUNT,
SEPARATOR.to_string(),
);
let mut buffer = Buffer::empty(area);
for (line, row) in zip(paragraphs.lines(), area.rows()) {
Line::from(line).render(row, &mut buffer);
}
buffer
}

View File

@@ -1,11 +1,13 @@
use std::hint::black_box;
use criterion::{Criterion, criterion_group};
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect};
use ratatui::style::Stylize;
use ratatui::text::Line;
use ratatui::widgets::Widget;
use criterion::{criterion_group, Criterion};
use ratatui::{
buffer::Buffer,
layout::{Alignment, Rect},
style::Stylize,
text::Line,
widgets::Widget,
};
fn line_render(criterion: &mut Criterion) {
for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] {

View File

@@ -1,7 +1,9 @@
use criterion::{BatchSize, Bencher, BenchmarkId, Criterion, criterion_group};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::{List, ListItem, ListState, StatefulWidget, Widget};
use criterion::{criterion_group, BatchSize, Bencher, BenchmarkId, Criterion};
use ratatui::{
buffer::Buffer,
layout::Rect,
widgets::{List, ListItem, ListState, StatefulWidget, Widget},
};
/// Benchmark for rendering a list.
/// It only benchmarks the render with a different amount of items.
@@ -39,7 +41,7 @@ fn list(c: &mut Criterion) {
group.finish();
}
/// Render the list into a common size buffer.
/// render the list into a common size buffer
fn render(bencher: &mut Bencher, list: &List) {
let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50));
// We use `iter_batched` to clone the value in the setup function.

View File

@@ -1,26 +1,25 @@
use std::hint::black_box;
use criterion::{BatchSize, Bencher, BenchmarkId, Criterion, criterion_group};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::{Paragraph, Widget, Wrap};
use criterion::{black_box, criterion_group, BatchSize, Bencher, BenchmarkId, Criterion};
use ratatui::{
buffer::Buffer,
layout::Rect,
widgets::{Paragraph, Widget, Wrap},
};
/// because the scroll offset is a u16, the maximum number of lines that can be scrolled is 65535.
/// This is a limitation of the current implementation and may be fixed by changing the type of the
/// scroll offset to a u32.
const MAX_SCROLL_OFFSET: u16 = u16::MAX;
const NO_WRAP_WIDTH: u16 = 200;
const WRAP_WIDTH: u16 = 100;
const PARAGRAPH_DEFAULT_HEIGHT: u16 = 50;
/// Benchmark for rendering a paragraph with a given number of lines. The design of this benchmark
/// allows comparison of the performance of rendering a paragraph with different numbers of lines.
/// as well as comparing with the various settings on the scroll and wrap features.
fn paragraph(c: &mut Criterion) {
let mut group = c.benchmark_group("paragraph");
for line_count in [64, 2048, u16::MAX] {
for line_count in [64, 2048, MAX_SCROLL_OFFSET] {
let lines = random_lines(line_count);
let lines = lines.as_str();
let y_scroll = line_count - PARAGRAPH_DEFAULT_HEIGHT;
// benchmark that measures the overhead of creating a paragraph separately from rendering
group.bench_with_input(BenchmarkId::new("new", line_count), lines, |b, lines| {
@@ -37,14 +36,14 @@ fn paragraph(c: &mut Criterion) {
// scroll the paragraph by half the number of lines and render
group.bench_with_input(
BenchmarkId::new("render_scroll_half", line_count),
&Paragraph::new(lines).scroll((y_scroll / 2, 0)),
&Paragraph::new(lines).scroll((0, line_count / 2)),
|bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH),
);
// scroll the paragraph by the full number of lines and render
group.bench_with_input(
BenchmarkId::new("render_scroll_full", line_count),
&Paragraph::new(lines).scroll((y_scroll, 0)),
&Paragraph::new(lines).scroll((0, line_count)),
|bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH),
);
@@ -60,16 +59,16 @@ fn paragraph(c: &mut Criterion) {
BenchmarkId::new("render_wrap_scroll_full", line_count),
&Paragraph::new(lines)
.wrap(Wrap { trim: false })
.scroll((y_scroll, 0)),
.scroll((0, line_count)),
|bencher, paragraph| render(bencher, paragraph, WRAP_WIDTH),
);
}
group.finish();
}
/// Render the paragraph into a buffer with the given width.
/// render the paragraph into a buffer with the given width
fn render(bencher: &mut Bencher, paragraph: &Paragraph, width: u16) {
let mut buffer = Buffer::empty(Rect::new(0, 0, width, PARAGRAPH_DEFAULT_HEIGHT));
let mut buffer = Buffer::empty(Rect::new(0, 0, width, 50));
// We use `iter_batched` to clone the value in the setup function.
// See https://github.com/ratatui/ratatui/pull/377.
bencher.iter_batched(

24
benches/main/rect.rs Normal file
View File

@@ -0,0 +1,24 @@
use criterion::{black_box, criterion_group, BenchmarkId, Criterion};
use ratatui::layout::Rect;
fn rect_rows_benchmark(c: &mut Criterion) {
let rect_sizes = vec![
Rect::new(0, 0, 1, 16),
Rect::new(0, 0, 1, 1024),
Rect::new(0, 0, 1, 65535),
];
let mut group = c.benchmark_group("rect_rows");
for rect in rect_sizes {
group.bench_with_input(BenchmarkId::new("rows", rect.height), &rect, |b, rect| {
b.iter(|| {
for row in rect.rows() {
// Perform any necessary operations on each row
black_box(row);
}
});
});
}
group.finish();
}
criterion_group!(benches, rect_rows_benchmark);

View File

@@ -1,17 +1,19 @@
use criterion::{Bencher, BenchmarkId, Criterion, criterion_group};
use criterion::{criterion_group, Bencher, BenchmarkId, Criterion};
use rand::Rng;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::widgets::{Sparkline, Widget};
use ratatui::{
buffer::Buffer,
layout::Rect,
widgets::{Sparkline, Widget},
};
/// Benchmark for rendering a sparkline.
fn sparkline(c: &mut Criterion) {
let mut group = c.benchmark_group("sparkline");
let mut rng = rand::rng();
let mut rng = rand::thread_rng();
for data_count in [64, 256, 2048] {
let data: Vec<u64> = (0..data_count)
.map(|_| rng.random_range(0..data_count))
.map(|_| rng.gen_range(0..data_count))
.collect();
// Render a basic sparkline
@@ -25,7 +27,7 @@ fn sparkline(c: &mut Criterion) {
group.finish();
}
/// Render the block into a buffer of the given `size`.
/// render the block into a buffer of the given `size`
fn render(bencher: &mut Bencher, sparkline: &Sparkline) {
let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50));
// We use `iter_batched` to clone the value in the setup function.

View File

@@ -1,7 +1,9 @@
use criterion::{BatchSize, Bencher, BenchmarkId, Criterion, criterion_group};
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Rect};
use ratatui::widgets::{Row, StatefulWidget, Table, TableState, Widget};
use criterion::{criterion_group, BatchSize, Bencher, BenchmarkId, Criterion};
use ratatui::{
buffer::Buffer,
layout::{Constraint, Rect},
widgets::{Row, StatefulWidget, Table, TableState, Widget},
};
/// Benchmark for rendering a table.
/// It only benchmarks the render with a different number of rows, and columns.

View File

@@ -24,18 +24,14 @@ body = """
{%- if not version %}
## [unreleased]
{% else -%}
{%- if package -%} {# release-plz specific variable #}
## {{ package }} - [{{ version }}]({{ release_link }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{%- else -%}
## [{{ version }}]({{ self::remote_url() }}/releases/tag/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{%- endif %}
## [{{ version }}](https://github.com/ratatui/ratatui/releases/tag/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{% endif -%}
{% macro commit(commit) -%}
- [{{ commit.id | truncate(length=7, end="") }}]({{ "https://github.com/ratatui/ratatui/commit/" ~ commit.id }}) \
*({{commit.scope | default(value = "uncategorized") | lower }})* {{ commit.message | upper_first | trim }}\
{% if commit.remote.username %} by `@{{ commit.remote.username }}`{%- endif -%}\
{% if commit.remote.pr_number %} in [#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}){%- endif %}\
{% if commit.github.username %} by @{{ commit.github.username }}{%- endif -%}\
{% if commit.github.pr_number %} in [#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }}){%- endif %}\
{%- if commit.breaking %} [**breaking**]{% endif %}
{%- if commit.body %}\n\n{{ commit.body | indent(prefix=" > ", first=true, blank=true) }}
{%- endif %}
@@ -61,7 +57,6 @@ body = """
{%- endfor -%}
{%- endfor %}
{%- if not release_link -%}
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
### New Contributors
{%- endif %}\
@@ -71,31 +66,27 @@ body = """
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
{%- endif %}
{%- endfor -%}
{%- endif -%}
{% if version %}
{% if previous.version %}
{%- if release_link -%}
**Full Changelog**: {{ release_link }} {# release-plz specific variable #}
{%- else -%}
**Full Changelog**: {{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}
{% endif %}
{% endif %}
{% else -%}
{% raw %}\n{% endraw %}
{% endif %}
{%- macro remote_url() -%}
{%- if remote.owner -%} {# release-plz specific variable #}
https://github.com/{{ remote.owner }}/{{ remote.repo }}\
{%- else -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\
{%- endif -%}
{% endmacro %}
"""
# remove the leading and trailing whitespace from the template
trim = false
# postprocessors for the changelog body
# changelog footer
footer = """
<!-- generated by git-cliff -->
"""
postprocessors = [
{ pattern = '<!-- Please read CONTRIBUTING.md before submitting any pull request. -->', replace = "" },
{ pattern = '>---+\n', replace = '' },
@@ -116,21 +107,9 @@ commit_preprocessors = [
{ pattern = '(Clarify README.md)', replace = "docs(readme): ${1}" },
{ pattern = '(Update README.md)', replace = "docs(readme): ${1}" },
{ pattern = '(fix typos|Fix typos)', replace = "fix: ${1}" },
# Typos that squeaked through and which would otherwise trigger the typos linter.
# Regex obsfucation is to avoid triggering the linter in this file until there's a per file config
# See https://github.com/crate-ci/typos/issues/724
{ pattern = '\<[d]eatil\>', replace = "detail" },
{ pattern = '\<[f]eatuers\>', replace = "features" },
{ pattern = '\<[s]pecically\>', replace = "specially" },
{ pattern = '\<[g]ague\>', replace = "gauge" },
{ pattern = '\<[a]rithmentic\>', replace = "arithmetic" },
{ pattern = '\<[i]ntructions\>', replace = "instructions" },
{ pattern = '\<[i]mplementated\>', replace = "implemented" },
]
# regex for parsing and grouping commits
commit_parsers = [
# release-plz adds 000000 as a placeholder for release commits
{ field = "id", pattern = "0000000", skip = true },
{ message = "^feat", group = "<!-- 00 -->Features" },
{ message = "^[fF]ix", group = "<!-- 01 -->Bug Fixes" },
{ message = "^refactor", group = "<!-- 02 -->Refactor" },
@@ -139,16 +118,13 @@ commit_parsers = [
{ message = "^style", group = "<!-- 05 -->Styling" },
{ message = "^test", group = "<!-- 06 -->Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore: release", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore\\(pull\\)", skip = true },
{ message = "^chore\\(deps\\)", skip = true },
{ message = "^chore\\(changelog\\)", skip = true },
{ message = "^[cC]hore", group = "<!-- 07 -->Miscellaneous Tasks" },
{ message = "^build\\(deps\\)", skip = true },
{ message = "^build\\(release\\)", skip = true },
{ message = "^build", group = "<!-- 08 -->Build" },
{ body = ".*security", group = "<!-- 09 -->Security" },
{ body = ".*security", group = "<!-- 08 -->Security" },
{ message = "^build", group = "<!-- 09 -->Build" },
{ message = "^ci", group = "<!-- 10 -->Continuous Integration" },
{ message = "^revert", group = "<!-- 11 -->Reverted Commits" },
# handle some old commits styles from pre 0.4
@@ -163,9 +139,9 @@ filter_commits = false
# glob pattern for matching git tags
tag_pattern = "v[0-9]*"
# regex for skipping tags
skip_tags = "beta|alpha|v0.1.0-rc.1"
skip_tags = "v0.1.0-rc.1"
# regex for ignoring tags
ignore_tags = "rc"
ignore_tags = "alpha"
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order

View File

@@ -1 +1,17 @@
avoid-breaking-exported-api = false
# https://rust-lang.github.io/rust-clippy/master/index.html#/multiple_crate_versions
# ratatui -> bitflags v2.3
# termwiz -> wezterm-blob-leases -> mac_address -> nix -> bitflags v1.3.2
# crossterm -> all the windows- deps https://github.com/ratatui/ratatui/pull/1064#issuecomment-2078848980
allowed-duplicate-crates = [
"bitflags",
"windows-targets",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]

View File

@@ -4,15 +4,14 @@
version = 2
confidence-threshold = 0.8
allow = [
"Apache-2.0",
"BSD-2-Clause",
"BSD-3-Clause",
"ISC",
"MIT",
"Unicode-3.0",
"Unicode-DFS-2016",
"WTFPL",
"Zlib",
"Apache-2.0",
"BSD-2-Clause",
"BSD-3-Clause",
"ISC",
"MIT",
"Unicode-DFS-2016",
"WTFPL",
"Zlib",
]
[advisories]
@@ -25,15 +24,3 @@ multiple-versions = "allow"
unknown-registry = "deny"
unknown-git = "warn"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
[[licenses.clarify]]
crate = "ring"
# SPDX considers OpenSSL to encompass both the OpenSSL and SSLeay licenses
# https://spdx.org/licenses/OpenSSL.html
# ISC - Both BoringSSL and ring use this for their new files
# MIT - "Files in third_party/ have their own licenses, as described therein. The MIT
# license, for third_party/fiat, which, unlike other third_party directories, is
# compiled into non-test libraries, is included below."
# OpenSSL - Obviously
expression = "ISC AND MIT AND OpenSSL"
license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]

View File

@@ -1,21 +1,6 @@
# Examples
This folder contains examples that are more application focused.
> [!TIP]
> There are also [widget examples] in `ratatui-widgets`.
[widget examples]: ../ratatui-widgets/examples
You can run these examples using:
```shell
cargo run -p example-name
```
> [!NOTE]
> This folder might use unreleased code. Consider viewing the examples in the `latest` branch instead
> of the `main` branch for code which is guaranteed to work with the released Ratatui version.
This folder might use unreleased code. View the examples for the latest release instead.
> [!WARNING]
>
@@ -26,7 +11,7 @@ cargo run -p example-name
>
> - View the examples as they were when the latest version was release by selecting the tag that
> matches that version. E.g. <https://github.com/ratatui/ratatui/tree/v0.26.1/examples>.
> - If you're viewing this file on GitHub, there is a combo box at the top of this page which
> - If you're viewing this file on GitHub, there is a combo box at the top of this page which
> allows you to select any previous tagged version.
> - To view the code locally, checkout the tag. E.g. `git switch --detach v0.26.1`.
> - Use the latest [alpha version of Ratatui] in your app. These are released weekly on Saturdays.
@@ -43,196 +28,408 @@ cargo run -p example-name
The examples contain some opinionated choices in order to make it easier for newer rustaceans to
easily be productive in creating applications:
- Each example has an `App` struct, with methods that implement a main loop, handle events and drawing
- Each example has an App struct, with methods that implement a main loop, handle events and drawing
the UI.
- We use `color_eyre` for handling errors and panics. See [How to use color-eyre with Ratatui] on the
- We use color_eyre for handling errors and panics. See [How to use color-eyre with Ratatui] on the
website for more information about this.
- Common code is not extracted into a separate file. This makes each example self-contained and easy
to read as a whole.
[How to use color-eyre with Ratatui]: https://ratatui.rs/recipes/apps/color-eyre/
Not every example has been updated with all these points in mind yet, however over time they will
be. None of the above choices are strictly necessary for Ratatui apps, but these choices make
examples easier to run, maintain and explain. These choices are designed to help newer users fall
into the pit of success when incorporating example code into their own apps. We may also eventually
move some of these design choices into the core of Ratatui to simplify apps.
## Demo
This is the original demo example from the main README. It is available for each of the backends.
[Source](./apps/demo/).
![Demo](demo.gif)
[How to use color-eyre with Ratatui]: https://ratatui.rs/how-to/develop-apps/color_eyre/
## Demo2
This is the demo example from the main README and crate page. [Source](./apps/demo2/).
This is the demo example from the main README and crate page. Source: [demo2](./demo2/).
![Demo2](demo2.gif)
```shell
cargo run --example=demo2 --features="crossterm widget-calendar"
```
## Async GitHub
![Demo2][demo2.gif]
Shows how to fetch data from GitHub API asynchronously. [Source](./apps/async-github/).
## Demo
![Async GitHub demo][async-github.gif]
This is the previous demo example from the main README. It is available for each of the backends. Source:
[demo.rs](./demo/).
## Calendar Explorer
```shell
cargo run --example=demo --features=crossterm
cargo run --example=demo --no-default-features --features=termion
cargo run --example=demo --no-default-features --features=termwiz
```
Shows how to render a calendar with different styles. [Source](./apps/calendar-explorer/).
![Calendar explorer demo][calendar-explorer.gif]
## Canvas
Shows how to render a canvas with different shapes. [Source](./apps/canvas/).
![Canvas demo][canvas.gif]
## Chart
Shows how to render line, bar, and scatter charts. [Source](./apps/chart/).
![Chart demo][chart.gif]
## Color Explorer
Shows how to handle the supported colors. [Source](./apps/color-explorer/).
![Color explorer demo][color-explorer.gif]
## Colors-RGB demo
Shows the full range of RGB colors in an animation. [Source](./apps/colors-rgb/).
![Colors-RGB demo][colors-rgb.gif]
## Constraint Explorer
Shows how different constraints can be used to layout widgets. [Source](./apps/constraint-explorer/).
![Constraint Explorer demo][constraint-explorer.gif]
## Constraints
Shows different types of constraints. [Source](./apps/constraints/).
![Constraints demo][constraints.gif]
## Custom Widget
Shows how to create a custom widget that can be interacted with the mouse. [Source](./apps/custom-widget/).
![Custom widget demo][custom-widget.gif]
## Hyperlink
Shows how to render hyperlinks in a terminal using [OSC
8](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda). [Source](./apps/hyperlink/).
![Hyperlink demo][hyperlink.gif]
## Flex
Shows how to use the flex layouts. [Source](./apps/flex/).
![Flex demo][flex.gif]
![Demo][demo.gif]
## Hello World
Shows how to create a simple TUI with a text. [Source](./apps/hello-world/).
This is a pretty boring example, but it contains some good documentation
on writing tui apps. Source: [hello_world.rs](./hello_world.rs).
![Hello World demo][hello-world.gif]
```shell
cargo run --example=hello_world --features=crossterm
```
![Hello World][hello_world.gif]
## Barchart
Demonstrates the [`BarChart`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.BarChart.html)
widget. Source: [barchart.rs](./barchart.rs).
```shell
cargo run --example=barchart --features=crossterm
```
![Barchart][barchart.gif]
## Barchart (Grouped)
Demonstrates the [`BarChart`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.BarChart.html)
widget with groups. Source: [barchart-grouped.rs](./barchart-grouped.rs).
```shell
cargo run --example=barchart-grouped --features=crossterm
```
![Barchart Grouped][barchart-grouped.gif]
## Block
Demonstrates the [`Block`](https://docs.rs/ratatui/latest/ratatui/widgets/block/struct.Block.html)
widget. Source: [block.rs](./block.rs).
```shell
cargo run --example=block --features=crossterm
```
![Block][block.gif]
## Calendar
Demonstrates the [`Calendar`](https://docs.rs/ratatui/latest/ratatui/widgets/calendar/index.html)
widget. Source: [calendar.rs](./calendar.rs).
```shell
cargo run --example=calendar --features="crossterm widget-calendar"
```
![Calendar][calendar.gif]
## Canvas
Demonstrates the [`Canvas`](https://docs.rs/ratatui/latest/ratatui/widgets/canvas/index.html) widget
and related shapes in the
[`canvas`](https://docs.rs/ratatui/latest/ratatui/widgets/canvas/index.html) module. Source:
[canvas.rs](./canvas.rs).
```shell
cargo run --example=canvas --features=crossterm
```
![Canvas][canvas.gif]
## Chart
Demonstrates the [`Chart`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Chart.html) widget.
Source: [chart.rs](./chart.rs).
```shell
cargo run --example=chart --features=crossterm
```
![Chart][chart.gif]
## Colors
Demonstrates the available [`Color`](https://docs.rs/ratatui/latest/ratatui/style/enum.Color.html)
options. These can be used in any style field. Source: [colors.rs](./colors.rs).
```shell
cargo run --example=colors --features=crossterm
```
![Colors][colors.gif]
## Colors (RGB)
Demonstrates the available RGB
[`Color`](https://docs.rs/ratatui/latest/ratatui/style/enum.Color.html) options. These can be used
in any style field. Source: [colors_rgb.rs](./colors_rgb.rs). Uses a half block technique to render
two square-ish pixels in the space of a single rectangular terminal cell.
```shell
cargo run --example=colors_rgb --features=crossterm
```
Note: VHs renders full screen animations poorly, so this is a screen capture rather than the output
of the VHS tape.
<https://github.com/ratatui/ratatui/assets/381361/485e775a-e0b5-4133-899b-1e8aeb56e774>
## Constraint Explorer
Demonstrates the behaviour of each
[`Constraint`](https://docs.rs/ratatui/latest/ratatui/layout/enum.Constraint.html) option with
respect to each other across different `Flex` modes.
```shell
cargo run --example=constraint-explorer --features=crossterm
```
![Constraint Explorer][constraint-explorer.gif]
## Constraints
Demonstrates how to use
[`Constraint`](https://docs.rs/ratatui/latest/ratatui/layout/enum.Constraint.html) options for
defining layout element sizes.
![Constraints][constraints.gif]
```shell
cargo run --example=constraints --features=crossterm
```
## Custom Widget
Demonstrates how to implement the
[`Widget`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.Widget.html) trait. Also shows mouse
interaction. Source: [custom_widget.rs](./custom_widget.rs).
```shell
cargo run --example=custom_widget --features=crossterm
```
![Custom Widget][custom_widget.gif]
## Gauge
Shows different types of gauges. [Source](./apps/gauge/).
Demonstrates the [`Gauge`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Gauge.html) widget.
Source: [gauge.rs](./gauge.rs).
```shell
cargo run --example=gauge --features=crossterm
```
![Gauge][gauge.gif]
## Flex
Demonstrates the different [`Flex`](https://docs.rs/ratatui/latest/ratatui/layout/enum.Flex.html)
modes for controlling layout space distribution.
```shell
cargo run --example=flex --features=crossterm
```
![Flex][flex.gif]
## Line Gauge
Demonstrates the [`Line
Gauge`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.LineGauge.html) widget. Source:
[line_gauge.rs](./line_gauge.rs).
```shell
cargo run --example=line_gauge --features=crossterm
```
![LineGauge][line_gauge.gif]
## Hyperlink
Demonstrates how to use OSC 8 to create hyperlinks in the terminal.
```shell
cargo run --example=hyperlink --features="crossterm unstable-widget-ref"
```
![Hyperlink][hyperlink.gif]
## Inline
Shows how to use the inlined viewport to render in a specific area of the screen. [Source](./apps/inline/).
Demonstrates how to use the
[`Inline`](https://docs.rs/ratatui/latest/ratatui/terminal/enum.Viewport.html#variant.Inline)
Viewport mode for ratatui apps. Source: [inline.rs](./inline.rs).
![Inline demo][inline.gif]
```shell
cargo run --example=inline --features=crossterm
```
## Input Form
![Inline][inline.gif]
Shows how to render a form with input fields. [Source](./apps/input-form/).
## Layout
Demonstrates the [`Layout`](https://docs.rs/ratatui/latest/ratatui/layout/struct.Layout.html) and
interaction between each constraint. Source: [layout.rs](./layout.rs).
```shell
cargo run --example=layout --features=crossterm
```
![Layout][layout.gif]
## List
Demonstrates the [`List`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.List.html) widget.
Source: [list.rs](./list.rs).
```shell
cargo run --example=list --features=crossterm
```
![List][list.gif]
## Modifiers
Shows different types of modifiers. [Source](./apps/modifiers/).
Demonstrates the style
[`Modifiers`](https://docs.rs/ratatui/latest/ratatui/style/struct.Modifier.html). Source:
[modifiers.rs](./modifiers.rs).
![Modifiers demo][modifiers.gif]
```shell
cargo run --example=modifiers --features=crossterm
```
## Mouse Drawing
Shows how to handle mouse events. [Source](./apps/mouse-drawing/).
![Modifiers][modifiers.gif]
## Minimal
Shows how to create a minimal application. [Source](./apps/minimal/).
Demonstrates how to create a minimal `Hello World!` program.
![Minimal demo][minimal.gif]
```shell
cargo run --example=minimal --features=crossterm
```
![Minimal][minimal.gif]
## Panic
Shows how to handle panics. [Source](./apps/panic/).
Demonstrates how to handle panics by ensuring that panic messages are written correctly to the
screen. Source: [panic.rs](./panic.rs).
![Panic demo][panic.gif]
```shell
cargo run --example=panic --features=crossterm
```
![Panic][panic.gif]
## Paragraph
Demonstrates the [`Paragraph`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Paragraph.html)
widget. Source: [paragraph.rs](./paragraph.rs)
```shell
cargo run --example=paragraph --features=crossterm
```
![Paragraph][paragraph.gif]
## Popup
Shows how to handle popups. [Source](./apps/popup/).
Demonstrates how to render a widget over the top of previously rendered widgets using the
[`Clear`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Clear.html) widget. Source:
[popup.rs](./popup.rs).
![Popup demo][popup.gif]
>
```shell
cargo run --example=popup --features=crossterm
```
![Popup][popup.gif]
## Ratatui-logo
A fun example of using half blocks to render graphics Source:
[ratatui-logo.rs](./ratatui-logo.rs).
>
```shell
cargo run --example=ratatui-logo --features=crossterm
```
![Ratatui Logo][ratatui-logo.gif]
## Scrollbar
Shows how to render different types of scrollbars. [Source](./apps/scrollbar/).
Demonstrates the [`Scrollbar`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Scrollbar.html)
widget. Source: [scrollbar.rs](./scrollbar.rs).
![Scrollbar demo][scrollbar.gif]
```shell
cargo run --example=scrollbar --features=crossterm
```
![Scrollbar][scrollbar.gif]
## Sparkline
Demonstrates the [`Sparkline`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Sparkline.html)
widget. Source: [sparkline.rs](./sparkline.rs).
```shell
cargo run --example=sparkline --features=crossterm
```
![Sparkline][sparkline.gif]
## Table
Shows how to create an interactive table. [Source](./apps/table/).
Demonstrates the [`Table`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Table.html) widget.
Source: [table.rs](./table.rs).
![Table demo][table.gif]
```shell
cargo run --example=table --features=crossterm
```
## Todo List
![Table][table.gif]
Shows how to create a simple todo list application. [Source](./apps/todo-list/).
## Tabs
![Todo List demo][todo-list.gif]
Demonstrates the [`Tabs`](https://docs.rs/ratatui/latest/ratatui/widgets/struct.Tabs.html) widget.
Source: [tabs.rs](./tabs.rs).
```shell
cargo run --example=tabs --features=crossterm
```
![Tabs][tabs.gif]
## Tracing
Shows how to use the [tracing](https://crates.io/crates/tracing) crate to log to a file. [Source](./apps/tracing/).
Demonstrates how to use the [tracing crate](https://crates.io/crates/tracing) for logging. Creates
a file named `tracing.log` in the current directory.
![Tracing demo][tracing.gif]
```shell
cargo run --example=tracing --features=crossterm
```
![Tracing][tracing.gif]
## User Input
Shows how to handle user input. [Source](./apps/user-input/).
Demonstrates one approach to accepting user input. Source [user_input.rs](./user_input.rs).
![User input demo][user-input.gif]
> [!NOTE]
> Consider using [`tui-textarea`](https://crates.io/crates/tui-textarea) or
> [`tui-input`](https://crates.io/crates/tui-input) crates for more functional text entry UIs.
## Weather
```shell
cargo run --example=user_input --features=crossterm
```
Shows how to render weather data using barchart widget. [Source](./apps/weather/).
![User Input][user_input.gif]
## WidgetRef Container
Shows how to use [`WidgetRef`](https://docs.rs/ratatui/latest/ratatui/widgets/trait.WidgetRef.html) to store widgets in a container. [Source](./apps/widget-ref-container/).
## Advanced Widget Implementation
Shows how to render the `Widget` trait in different ways.
![Advanced widget impl demo][advanced-widget-impl.gif]
---
<details>
<summary>How to update these examples?</summary>
## How to update these examples
These gifs were created using [VHS](https://github.com/charmbracelet/vhs). Each example has a
corresponding `.tape` file that holds instructions for how to generate the images. Note that the
images themselves are stored in a separate `images` git branch to avoid bloating the `main`
branch.
images themselves are stored in a separate `images` git branch to avoid bloating the main
repository.
<!--
@@ -240,36 +437,42 @@ Links to images to make them easier to update in bulk. Use the following script
the examples to the images branch. (Requires push access to the branch).
```shell
vhs/generate.bash
examples/vhs/generate.bash
```
-->
</details>
[advanced-widget-impl.gif]: https://github.com/ratatui/ratatui/blob/images/examples/advanced-widget-impl.gif?raw=true
[async-github.gif]: https://github.com/ratatui/ratatui/blob/images/examples/async-github.gif?raw=true
[calendar-explorer.gif]: https://github.com/ratatui/ratatui/blob/images/examples/calendar-explorer.gif?raw=true
[barchart.gif]: https://github.com/ratatui/ratatui/blob/images/examples/barchart.gif?raw=true
[barchart-grouped.gif]: https://github.com/ratatui/ratatui/blob/images/examples/barchart-grouped.gif?raw=true
[block.gif]: https://github.com/ratatui/ratatui/blob/images/examples/block.gif?raw=true
[calendar.gif]: https://github.com/ratatui/ratatui/blob/images/examples/calendar.gif?raw=true
[canvas.gif]: https://github.com/ratatui/ratatui/blob/images/examples/canvas.gif?raw=true
[chart.gif]: https://github.com/ratatui/ratatui/blob/images/examples/chart.gif?raw=true
[color-explorer.gif]: https://github.com/ratatui/ratatui/blob/images/examples/color-explorer.gif?raw=true
[colors-rgb.gif]: https://github.com/ratatui/ratatui/blob/images/examples/colors-rgb.gif?raw=true
[colors.gif]: https://github.com/ratatui/ratatui/blob/images/examples/colors.gif?raw=true
[constraint-explorer.gif]: https://github.com/ratatui/ratatui/blob/images/examples/constraint-explorer.gif?raw=true
[constraints.gif]: https://github.com/ratatui/ratatui/blob/images/examples/constraints.gif?raw=true
[custom-widget.gif]: https://github.com/ratatui/ratatui/blob/images/examples/custom-widget.gif?raw=true
[demo2-destroy.gif]: https://github.com/ratatui/ratatui/blob/images/examples/demo2-destroy.gif?raw=true
[demo2-social.gif]: https://github.com/ratatui/ratatui/blob/images/examples/demo2-social.gif?raw=true
[demo2.gif]: https://github.com/ratatui/ratatui/blob/images/examples/demo2.gif?raw=true
[custom_widget.gif]: https://github.com/ratatui/ratatui/blob/images/examples/custom_widget.gif?raw=true
[demo.gif]: https://github.com/ratatui/ratatui/blob/images/examples/demo.gif?raw=true
[demo2.gif]: https://github.com/ratatui/ratatui/blob/images/examples/demo2.gif?raw=true
[flex.gif]: https://github.com/ratatui/ratatui/blob/images/examples/flex.gif?raw=true
[hello-world.gif]: https://github.com/ratatui/ratatui/blob/images/examples/hello-world.gif?raw=true
[gauge.gif]: https://github.com/ratatui/ratatui/blob/images/examples/gauge.gif?raw=true
[hello_world.gif]: https://github.com/ratatui/ratatui/blob/images/examples/hello_world.gif?raw=true
[hyperlink.gif]: https://github.com/ratatui/ratatui/blob/images/examples/hyperlink.gif?raw=true
[inline.gif]: https://github.com/ratatui/ratatui/blob/images/examples/inline.gif?raw=true
[layout.gif]: https://github.com/ratatui/ratatui/blob/images/examples/layout.gif?raw=true
[list.gif]: https://github.com/ratatui/ratatui/blob/images/examples/list.gif?raw=true
[line_gauge.gif]: https://github.com/ratatui/ratatui/blob/images/examples/line_gauge.gif?raw=true
[minimal.gif]: https://github.com/ratatui/ratatui/blob/images/examples/minimal.gif?raw=true
[modifiers.gif]: https://github.com/ratatui/ratatui/blob/images/examples/modifiers.gif?raw=true
[panic.gif]: https://github.com/ratatui/ratatui/blob/images/examples/panic.gif?raw=true
[paragraph.gif]: https://github.com/ratatui/ratatui/blob/images/examples/paragraph.gif?raw=true
[popup.gif]: https://github.com/ratatui/ratatui/blob/images/examples/popup.gif?raw=true
[ratatui-logo.gif]: https://github.com/ratatui/ratatui/blob/images/examples/ratatui-logo.gif?raw=true
[scrollbar.gif]: https://github.com/ratatui/ratatui/blob/images/examples/scrollbar.gif?raw=true
[table.gif]: https://github.com/ratatui/ratatui/blob/images/examples/table.gif?raw=true
[todo-list.gif]: https://github.com/ratatui/ratatui/blob/images/examples/todo-list.gif?raw=true
[sparkline.gif]: https://github.com/ratatui/ratatui/blob/images/examples/sparkline.gif?raw=true
[table.gif]: https://vhs.charm.sh/vhs-6njXBytDf0rwPufUtmSSpI.gif
[tabs.gif]: https://github.com/ratatui/ratatui/blob/images/examples/tabs.gif?raw=true
[tracing.gif]: https://github.com/ratatui/ratatui/blob/images/examples/tracing.gif?raw=true
[user-input.gif]: https://github.com/ratatui/ratatui/blob/images/examples/user-input.gif?raw=true
[user_input.gif]: https://github.com/ratatui/ratatui/blob/images/examples/user_input.gif?raw=true
[alpha version of Ratatui]: https://crates.io/crates/ratatui/versions
[BREAKING-CHANGES.md]: https://github.com/ratatui/ratatui/blob/main/BREAKING-CHANGES.md

View File

@@ -1,14 +0,0 @@
[package]
name = "advanced-widget-impl"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
ratatui = { workspace = true, features = ["unstable-widget-ref"] }
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Advanced Widget Implementation demo
This example shows how to render the `Widget` trait in different ways.
To run this demo:
```shell
cargo run -p advanced-widget-impl
```

View File

@@ -1,22 +0,0 @@
[package]
name = "async-github"
publish = false
authors.workspace = true
documentation.workspace = true
repository.workspace = true
homepage.workspace = true
keywords.workspace = true
categories.workspace = true
readme.workspace = true
license.workspace = true
exclude.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm = { workspace = true, features = ["event-stream"] }
octocrab.workspace = true
ratatui.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tokio-stream.workspace = true

View File

@@ -1,9 +0,0 @@
# Async GitHub demo
This example demonstrates how to use Ratatui with widgets that fetch data from GitHub API asynchronously.
To run this demo:
```shell
cargo run -p async-github
```

View File

@@ -1,15 +0,0 @@
[package]
name = "calendar-explorer"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
ratatui.workspace = true
time = { workspace = true, features = ["formatting", "parsing"] }
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Calendar explorer demo
This example shows how to render a calendar with different styles.
To run this demo:
```shell
cargo run -p calendar-explorer
```

View File

@@ -1,236 +0,0 @@
//! A Ratatui example that demonstrates how to render calendar with different styles.
//!
//! Marks the holidays and seasons on the calendar.
//!
//! This example runs with the Ratatui library code in the branch that you are currently reading.
//! See the [`latest`] branch for the code which works with the most recent Ratatui release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//! [`BarChart`]: https://docs.rs/ratatui/latest/ratatui/widgets/struct.BarChart.html
use std::fmt;
use color_eyre::Result;
use crossterm::event::{self, KeyCode};
use ratatui::layout::{Constraint, Layout, Margin, Rect};
use ratatui::style::{Color, Modifier, Style, Stylize};
use ratatui::text::{Line, Text};
use ratatui::widgets::calendar::{CalendarEventStore, Monthly};
use ratatui::{DefaultTerminal, Frame};
use time::ext::NumericalDuration;
use time::{Date, Month, OffsetDateTime};
fn main() -> Result<()> {
color_eyre::install()?;
ratatui::run(run)
}
/// Run the application.
fn run(terminal: &mut DefaultTerminal) -> Result<()> {
let mut selected_date = OffsetDateTime::now_local()?.date();
let mut calendar_style = StyledCalendar::Default;
loop {
terminal.draw(|frame| render(frame, calendar_style, selected_date))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('q') => break Ok(()),
KeyCode::Char('s') => calendar_style = calendar_style.next(),
KeyCode::Char('n') | KeyCode::Tab => selected_date = next_month(selected_date),
KeyCode::Char('p') | KeyCode::BackTab => selected_date = prev_month(selected_date),
KeyCode::Char('h') | KeyCode::Left => selected_date -= 1.days(),
KeyCode::Char('j') | KeyCode::Down => selected_date += 1.weeks(),
KeyCode::Char('k') | KeyCode::Up => selected_date -= 1.weeks(),
KeyCode::Char('l') | KeyCode::Right => selected_date += 1.days(),
_ => {}
}
}
}
}
fn next_month(date: Date) -> Date {
if date.month() == Month::December {
date.replace_month(Month::January)
.unwrap()
.replace_year(date.year() + 1)
.unwrap()
} else {
date.replace_month(date.month().next()).unwrap()
}
}
fn prev_month(date: Date) -> Date {
if date.month() == Month::January {
date.replace_month(Month::December)
.unwrap()
.replace_year(date.year() - 1)
.unwrap()
} else {
date.replace_month(date.month().previous()).unwrap()
}
}
/// Render the UI with a calendar.
fn render(frame: &mut Frame, calendar_style: StyledCalendar, selected_date: Date) {
let header = Text::from_iter([
Line::from("Calendar Example".bold()),
Line::from(
"<q> Quit | <s> Change Style | <n> Next Month | <p> Previous Month, <hjkl> Move",
),
Line::from(format!(
"Current date: {selected_date} | Current style: {calendar_style}"
)),
]);
let [text_area, area] = frame.area().layout(&Layout::vertical([
Constraint::Length(header.height() as u16),
Constraint::Fill(1),
]));
frame.render_widget(header.centered(), text_area);
calendar_style
.render_year(frame, area, selected_date)
.unwrap();
}
#[derive(Debug, Clone, Copy)]
enum StyledCalendar {
Default,
Surrounding,
WeekdaysHeader,
SurroundingAndWeekdaysHeader,
MonthHeader,
MonthAndWeekdaysHeader,
}
impl StyledCalendar {
// Cycle through the different styles.
const fn next(self) -> Self {
match self {
Self::Default => Self::Surrounding,
Self::Surrounding => Self::WeekdaysHeader,
Self::WeekdaysHeader => Self::SurroundingAndWeekdaysHeader,
Self::SurroundingAndWeekdaysHeader => Self::MonthHeader,
Self::MonthHeader => Self::MonthAndWeekdaysHeader,
Self::MonthAndWeekdaysHeader => Self::Default,
}
}
}
impl fmt::Display for StyledCalendar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Default => write!(f, "Default"),
Self::Surrounding => write!(f, "Show Surrounding"),
Self::WeekdaysHeader => write!(f, "Show Weekdays Header"),
Self::SurroundingAndWeekdaysHeader => write!(f, "Show Surrounding and Weekdays Header"),
Self::MonthHeader => write!(f, "Show Month Header"),
Self::MonthAndWeekdaysHeader => write!(f, "Show Month Header and Weekdays Header"),
}
}
}
impl StyledCalendar {
fn render_year(self, frame: &mut Frame, area: Rect, date: Date) -> Result<()> {
let events = events(date)?;
let vertical = Layout::vertical([Constraint::Ratio(1, 3); 3]);
let horizontal = &Layout::horizontal([Constraint::Ratio(1, 4); 4]);
let areas = area
.inner(Margin::new(1, 1))
.layout_vec(&vertical)
.into_iter()
.flat_map(|row| row.layout_vec(horizontal));
for (i, area) in areas.enumerate() {
let month = date
.replace_day(1)
.unwrap()
.replace_month(Month::try_from(i as u8 + 1).unwrap())
.unwrap();
self.render_month(frame, area, month, &events);
}
Ok(())
}
fn render_month(self, frame: &mut Frame, area: Rect, date: Date, events: &CalendarEventStore) {
let calendar = match self {
Self::Default => Monthly::new(date, events)
.default_style(Style::new().bold().bg(Color::Rgb(50, 50, 50)))
.show_month_header(Style::default()),
Self::Surrounding => Monthly::new(date, events)
.default_style(Style::new().bold().bg(Color::Rgb(50, 50, 50)))
.show_month_header(Style::default())
.show_surrounding(Style::new().dim()),
Self::WeekdaysHeader => Monthly::new(date, events)
.default_style(Style::new().bold().bg(Color::Rgb(50, 50, 50)))
.show_month_header(Style::default())
.show_weekdays_header(Style::new().bold().green()),
Self::SurroundingAndWeekdaysHeader => Monthly::new(date, events)
.default_style(Style::new().bold().bg(Color::Rgb(50, 50, 50)))
.show_month_header(Style::default())
.show_surrounding(Style::new().dim())
.show_weekdays_header(Style::new().bold().green()),
Self::MonthHeader => Monthly::new(date, events)
.default_style(Style::new().bold().bg(Color::Rgb(50, 50, 50)))
.show_month_header(Style::default())
.show_month_header(Style::new().bold().green()),
Self::MonthAndWeekdaysHeader => Monthly::new(date, events)
.default_style(Style::new().bold().bg(Color::Rgb(50, 50, 50)))
.show_month_header(Style::default())
.show_weekdays_header(Style::new().bold().dim().light_yellow()),
};
frame.render_widget(calendar, area);
}
}
/// Makes a list of dates for the current year.
fn events(selected_date: Date) -> Result<CalendarEventStore> {
const SELECTED: Style = Style::new()
.fg(Color::White)
.bg(Color::Red)
.add_modifier(Modifier::BOLD);
const HOLIDAY: Style = Style::new()
.fg(Color::Red)
.add_modifier(Modifier::UNDERLINED);
const SEASON: Style = Style::new()
.fg(Color::Green)
.bg(Color::Black)
.add_modifier(Modifier::UNDERLINED);
let mut list = CalendarEventStore::today(
Style::default()
.add_modifier(Modifier::BOLD)
.bg(Color::Blue),
);
let y = selected_date.year();
// new year's
list.add(Date::from_calendar_date(y, Month::January, 1)?, HOLIDAY);
// next new_year's for December "show surrounding"
list.add(Date::from_calendar_date(y + 1, Month::January, 1)?, HOLIDAY);
// groundhog day
list.add(Date::from_calendar_date(y, Month::February, 2)?, HOLIDAY);
// april fool's
list.add(Date::from_calendar_date(y, Month::April, 1)?, HOLIDAY);
// earth day
list.add(Date::from_calendar_date(y, Month::April, 22)?, HOLIDAY);
// star wars day
list.add(Date::from_calendar_date(y, Month::May, 4)?, HOLIDAY);
// festivus
list.add(Date::from_calendar_date(y, Month::December, 23)?, HOLIDAY);
// new year's eve
list.add(Date::from_calendar_date(y, Month::December, 31)?, HOLIDAY);
// seasons
// spring equinox
list.add(Date::from_calendar_date(y, Month::March, 22)?, SEASON);
// summer solstice
list.add(Date::from_calendar_date(y, Month::June, 21)?, SEASON);
// fall equinox
list.add(Date::from_calendar_date(y, Month::September, 22)?, SEASON);
// winter solstice
list.add(Date::from_calendar_date(y, Month::December, 21)?, SEASON);
// selected date
list.add(selected_date, SELECTED);
Ok(list)
}

View File

@@ -1,15 +0,0 @@
[package]
name = "canvas"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
itertools.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Canvas demo
This example shows how to render various shapes and a map on a canvas.
To run this demo:
```shell
cargo run -p canvas
```

View File

@@ -1,263 +0,0 @@
/// A Ratatui example that demonstrates how to draw on a canvas.
///
/// This example demonstrates how to draw various shapes such as rectangles, circles, and lines
/// on a canvas. It also demonstrates how to draw a map.
///
/// This example runs with the Ratatui library code in the branch that you are currently
/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui
/// release.
///
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
use std::{
io::stdout,
time::{Duration, Instant},
};
use color_eyre::Result;
use crossterm::ExecutableCommand;
use crossterm::event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, MouseEventKind,
};
use itertools::Itertools;
use ratatui::layout::{Constraint, Layout, Position, Rect};
use ratatui::style::{Color, Stylize};
use ratatui::symbols::Marker;
use ratatui::text::Text;
use ratatui::widgets::canvas::{Canvas, Circle, Map, MapResolution, Points, Rectangle};
use ratatui::widgets::{Block, Widget};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
stdout().execute(EnableMouseCapture)?;
let terminal = ratatui::init();
let app_result = App::new().run(terminal);
ratatui::restore();
stdout().execute(DisableMouseCapture)?;
app_result
}
struct App {
exit: bool,
x: f64,
y: f64,
ball: Circle,
playground: Rect,
vx: f64,
vy: f64,
marker: Marker,
points: Vec<Position>,
is_drawing: bool,
}
impl App {
const fn new() -> Self {
Self {
exit: false,
x: 0.0,
y: 0.0,
ball: Circle {
x: 20.0,
y: 40.0,
radius: 10.0,
color: Color::Yellow,
},
playground: Rect::new(10, 10, 200, 100),
vx: 1.0,
vy: 1.0,
marker: Marker::Dot,
points: vec![],
is_drawing: false,
}
}
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
let tick_rate = Duration::from_millis(16);
let mut last_tick = Instant::now();
while !self.exit {
terminal.draw(|frame| self.render(frame))?;
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if !event::poll(timeout)? {
self.on_tick();
last_tick = Instant::now();
continue;
}
match event::read()? {
Event::Key(key) => self.handle_key_event(key),
Event::Mouse(event) => self.handle_mouse_event(event),
_ => (),
}
}
Ok(())
}
fn handle_key_event(&mut self, key: KeyEvent) {
if !key.is_press() {
return;
}
match key.code {
KeyCode::Char('q') | KeyCode::Esc => self.exit = true,
KeyCode::Char('j') | KeyCode::Down => self.y += 1.0,
KeyCode::Char('k') | KeyCode::Up => self.y -= 1.0,
KeyCode::Char('l') | KeyCode::Right => self.x += 1.0,
KeyCode::Char('h') | KeyCode::Left => self.x -= 1.0,
KeyCode::Enter => self.cycle_marker(),
_ => {}
}
}
fn handle_mouse_event(&mut self, event: event::MouseEvent) {
match event.kind {
MouseEventKind::Down(_) => self.is_drawing = true,
MouseEventKind::Up(_) => self.is_drawing = false,
MouseEventKind::Drag(_) => {
self.points.push(Position::new(event.column, event.row));
}
_ => {}
}
}
const fn cycle_marker(&mut self) {
self.marker = match self.marker {
Marker::Dot => Marker::Braille,
Marker::Braille => Marker::Block,
Marker::Block => Marker::HalfBlock,
Marker::HalfBlock => Marker::Quadrant,
Marker::Quadrant => Marker::Sextant,
Marker::Sextant => Marker::Octant,
Marker::Octant => Marker::Bar,
Marker::Bar => Marker::Dot,
_ => unreachable!(),
};
}
fn on_tick(&mut self) {
// bounce the ball by flipping the velocity vector
let ball = &self.ball;
let playground = self.playground;
if ball.x - ball.radius < f64::from(playground.left())
|| ball.x + ball.radius > f64::from(playground.right())
{
self.vx = -self.vx;
}
if ball.y - ball.radius < f64::from(playground.top())
|| ball.y + ball.radius > f64::from(playground.bottom())
{
self.vy = -self.vy;
}
self.ball.x += self.vx;
self.ball.y += self.vy;
}
fn render(&self, frame: &mut Frame) {
let header = Text::from_iter([
"Canvas Example".bold(),
"<q> Quit | <enter> Change Marker | <hjkl> Move".into(),
]);
let vertical = Layout::vertical([
Constraint::Length(header.height() as u16),
Constraint::Fill(1),
Constraint::Fill(1),
]);
let [text_area, up, down] = frame.area().layout(&vertical);
frame.render_widget(header.centered(), text_area);
let horizontal = Layout::horizontal([Constraint::Fill(1); 2]);
let [draw, pong] = up.layout(&horizontal);
let [map, boxes] = down.layout(&horizontal);
frame.render_widget(self.map_canvas(), map);
frame.render_widget(self.draw_canvas(draw), draw);
frame.render_widget(self.pong_canvas(), pong);
frame.render_widget(self.boxes_canvas(boxes), boxes);
}
fn map_canvas(&self) -> impl Widget + '_ {
Canvas::default()
.block(Block::bordered().title("World"))
.marker(self.marker)
.paint(|ctx| {
ctx.draw(&Map {
color: Color::Green,
resolution: MapResolution::High,
});
ctx.print(self.x, -self.y, "You are here".yellow());
})
.x_bounds([-180.0, 180.0])
.y_bounds([-90.0, 90.0])
}
fn draw_canvas(&self, area: Rect) -> impl Widget + '_ {
Canvas::default()
.block(Block::bordered().title("Draw here"))
.marker(self.marker)
.x_bounds([0.0, f64::from(area.width)])
.y_bounds([0.0, f64::from(area.height)])
.paint(move |ctx| {
let points = self
.points
.iter()
.map(|p| {
(
f64::from(p.x) - f64::from(area.left()),
f64::from(area.bottom()) - f64::from(p.y),
)
})
.collect_vec();
ctx.draw(&Points {
coords: &points,
color: Color::White,
});
})
}
fn pong_canvas(&self) -> impl Widget + '_ {
Canvas::default()
.block(Block::bordered().title("Pong"))
.marker(self.marker)
.paint(|ctx| {
ctx.draw(&self.ball);
})
.x_bounds([10.0, 210.0])
.y_bounds([10.0, 110.0])
}
fn boxes_canvas(&self, area: Rect) -> impl Widget {
let left = 0.0;
let right = f64::from(area.width);
let bottom = 0.0;
let top = f64::from(area.height).mul_add(2.0, -4.0);
Canvas::default()
.block(Block::bordered().title("Rects"))
.marker(self.marker)
.x_bounds([left, right])
.y_bounds([bottom, top])
.paint(|ctx| {
for i in 0..=11 {
ctx.draw(&Rectangle {
x: f64::from(i * i + 3 * i) / 2.0 + 2.0,
y: 2.0,
width: f64::from(i),
height: f64::from(i),
color: Color::Red,
});
ctx.draw(&Rectangle {
x: f64::from(i * i + 3 * i) / 2.0 + 2.0,
y: 21.0,
width: f64::from(i),
height: f64::from(i),
color: Color::Blue,
});
}
for i in 0..100 {
if i % 10 != 0 {
ctx.print(f64::from(i) + 1.0, 0.0, format!("{i}", i = i % 10));
}
if i % 2 == 0 && i % 10 != 0 {
ctx.print(0.0, f64::from(i), format!("{i}", i = i % 10));
}
}
})
}
}

View File

@@ -1,14 +0,0 @@
[package]
name = "chart"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Chart demo
This example shows how to render line, bar, and scatter charts.
To run this demo:
```shell
cargo run -p chart
```

View File

@@ -1,15 +0,0 @@
[package]
name = "color-explorer"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
itertools.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Color explorer demo
This example shows how to handle the supported colors.
To run this demo:
```shell
cargo run -p color-explorer
```

View File

@@ -1,15 +0,0 @@
[package]
name = "colors-rgb"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
palette.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Colors-RGB demo
This example shows the full range of RGB colors in an animation.
To run this demo:
```shell
cargo run -p colors-rgb
```

View File

@@ -1,16 +0,0 @@
[package]
name = "constraint-explorer"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
itertools.workspace = true
ratatui.workspace = true
strum.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Constraint explorer demo
This interactive example shows how different constraints can be used to layout widgets.
To run this demo:
```shell
cargo run -p constraint-explorer
```

View File

@@ -1,15 +0,0 @@
[package]
name = "constraints"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
ratatui.workspace = true
strum.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Constraints demo
This example shows different types of constraints.
To run this demo:
```shell
cargo run -p constraints
```

View File

@@ -1,14 +0,0 @@
[package]
name = "custom-widget"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Custom widget demo
This example shows how to create a custom widget that can be interacted with the mouse.
To run this demo:
```shell
cargo run -p custom-widget
```

View File

@@ -1,22 +0,0 @@
[package]
name = "demo"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[features]
default = ["crossterm"]
crossterm = ["ratatui/crossterm", "dep:crossterm"]
termion = ["ratatui/termion", "dep:termion"]
termwiz = ["ratatui/termwiz", "dep:termwiz"]
[dependencies]
clap.workspace = true
crossterm = { workspace = true, optional = true }
rand.workspace = true
ratatui.workspace = true
termwiz = { workspace = true, optional = true }
[target.'cfg(not(windows))'.dependencies]
termion = { workspace = true, optional = true }

View File

@@ -1,25 +0,0 @@
# Demo example
This is the original demo that was developed for Tui-rs (the library that Ratatui was forked from).
![demo.gif](https://github.com/ratatui/ratatui/blob/images/examples/demo.gif?raw=true)
This example is available for each backend. To run it:
## crossterm
```shell
cargo run -p demo
```
## termion
```shell
cargo run -p demo --no-default-features --features termion
```
## termwiz
```shell
cargo run -p demo --no-default-features --features termwiz
```

View File

@@ -1,19 +0,0 @@
[package]
name = "demo2"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
indoc.workspace = true
itertools.workspace = true
palette.workspace = true
rand.workspace = true
rand_chacha.workspace = true
ratatui = { workspace = true, features = ["all-widgets"] }
strum.workspace = true
time.workspace = true
unicode-width.workspace = true

View File

@@ -1,9 +0,0 @@
## Demo2
This is the demo example from the main README and crate page. Source: [demo2](./demo2/).
```shell
cargo run -p demo2
```
![Demo2](https://github.com/ratatui/ratatui/blob/images/examples/demo2.gif?raw=true)

View File

@@ -1,73 +0,0 @@
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Constraint, Layout, Margin, Rect};
use ratatui::widgets::{
Block, Borders, Clear, MascotEyeColor, Padding, Paragraph, RatatuiMascot, Widget, Wrap,
};
use crate::{RgbSwatch, THEME};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct AboutTab {
row_index: usize,
}
impl AboutTab {
pub fn prev_row(&mut self) {
self.row_index = self.row_index.saturating_sub(1);
}
pub fn next_row(&mut self) {
self.row_index = self.row_index.saturating_add(1);
}
}
impl Widget for AboutTab {
fn render(self, area: Rect, buf: &mut Buffer) {
RgbSwatch.render(area, buf);
let layout = Layout::horizontal([Constraint::Length(34), Constraint::Min(0)]);
let [logo_area, description] = area.layout(&layout);
render_crate_description(description, buf);
let eye_state = if self.row_index % 2 == 0 {
MascotEyeColor::Default
} else {
MascotEyeColor::Red
};
RatatuiMascot::default().set_eye(eye_state).render(
logo_area.inner(Margin {
vertical: 0,
horizontal: 2,
}),
buf,
);
}
}
fn render_crate_description(area: Rect, buf: &mut Buffer) {
let area = area.inner(Margin {
vertical: 4,
horizontal: 2,
});
Clear.render(area, buf); // clear out the color swatches
Block::new().style(THEME.content).render(area, buf);
let area = area.inner(Margin {
vertical: 1,
horizontal: 2,
});
let text = "- cooking up terminal user interfaces -
Ratatui is a Rust crate that provides widgets (e.g. Paragraph, Table) and draws them to the \
screen efficiently every frame.";
Paragraph::new(text)
.style(THEME.description)
.block(
Block::new()
.title(" Ratatui ")
.title_alignment(Alignment::Center)
.borders(Borders::TOP)
.border_style(THEME.description_title)
.padding(Padding::new(0, 0, 0, 0)),
)
.wrap(Wrap { trim: true })
.scroll((0, 0))
.render(area, buf);
}

View File

@@ -1,15 +0,0 @@
[package]
name = "flex"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
ratatui.workspace = true
strum.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Flex demo
This interactive example shows how to use the flex layouts.
To run this demo:
```shell
cargo run -p flex
```

View File

@@ -1,14 +0,0 @@
[package]
name = "gauge"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Gauge demo
This example shows different types of gauges.
To run this demo:
```shell
cargo run -p gauge
```

View File

@@ -1,14 +0,0 @@
[package]
name = "hello-world"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Hello World demo
This example shows how to create a simple TUI with a text.
To run this demo:
```shell
cargo run -p hello-world
```

View File

@@ -1,15 +0,0 @@
[package]
name = "hyperlink"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
itertools.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Hyperlink demo
This example shows how to render hyperlinks in a terminal using [OSC 8](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda).
To run this demo:
```shell
cargo run -p hyperlink
```

View File

@@ -1,72 +0,0 @@
/// A Ratatui example that how to create hyperlinks in the terminal using [OSC 8].
///
/// This example runs with the Ratatui library code in the branch that you are currently
/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui
/// release.
///
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
/// [OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
use color_eyre::Result;
use crossterm::event;
use itertools::Itertools;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Stylize;
use ratatui::text::{Line, Text};
use ratatui::widgets::Widget;
fn main() -> Result<()> {
color_eyre::install()?;
let text = Line::from(vec!["Example ".into(), "hyperlink".blue()]);
let hyperlink = Hyperlink::new(text, "https://example.com");
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| frame.render_widget(&hyperlink, frame.area()))?;
if event::read()?.is_key_press() {
break Ok(());
}
}
})
}
/// A hyperlink widget that renders a hyperlink in the terminal using [OSC 8].
///
/// [OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
struct Hyperlink<'content> {
text: Text<'content>,
url: String,
}
impl<'content> Hyperlink<'content> {
fn new(text: impl Into<Text<'content>>, url: impl Into<String>) -> Self {
Self {
text: text.into(),
url: url.into(),
}
}
}
impl Widget for &Hyperlink<'_> {
fn render(self, area: Rect, buffer: &mut Buffer) {
(&self.text).render(area, buffer);
// this is a hacky workaround for https://github.com/ratatui/ratatui/issues/902, a bug
// in the terminal code that incorrectly calculates the width of ANSI escape sequences. It
// works by rendering the hyperlink as a series of 2-character chunks, which is the
// calculated width of the hyperlink text.
for (i, two_chars) in self
.text
.to_string()
.chars()
.chunks(2)
.into_iter()
.enumerate()
{
let text = two_chars.collect::<String>();
let hyperlink = format!("\x1B]8;;{}\x07{}\x1B]8;;\x07", self.url, text);
buffer[(area.x + i as u16 * 2, area.y)].set_symbol(hyperlink.as_str());
}
}
}

View File

@@ -1,15 +0,0 @@
[package]
name = "inline"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
rand.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Inline demo
This example shows how to use the inlined viewport to render in a specific area of the screen.
To run this demo:
```shell
cargo run -p inline
```

View File

@@ -1,16 +0,0 @@
[package]
name = "input-form"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
ratatui.workspace = true
serde.workspace = true
serde_json.workspace = true
[lints]
workspace = true

View File

@@ -1,27 +0,0 @@
# Input Form example
This example demonstrates how to handle input across several form fields (2 strings and an number).
It uses an enum to track the focused field, and sends keyboard events to one which is current.
Run this example with:
```shell
cargo run -p input-form
```
This example does not handle things like cursor movement within the line (just keys and backspace).
Most apps would benefit from using the following crates for text input rather than directly using
strings:
- [`tui-input`](https://crates.io/crates/tui-input)
- [`tui-prompts`](https://crates.io/crates/tui-prompts)
- [`tui-textarea`](https://crates.io/crates/tui-textarea)
- [`rat-salsa`](https://crates.io/crates/rat-salsa)
Some more ideas for handling focus can be found in:
- [`focusable`](https://crates.io/crates/focusable) (see also [Ratatui forum
post](https://forum.ratatui.rs/t/focusable-crate-manage-focus-state-for-your-widgets/73))
- [`rat-focus`](https://crates.io/crates/rat-focus)
- A useful [`Bevy` discussion](https://github.com/bevyengine/bevy/discussions/15374) about focus
more generally.

View File

@@ -1,263 +0,0 @@
//! A Ratatui example that demonstrates how to handle input form focus
//!
//! This example demonstrates how to handle cursor and input focus between multiple fields in a
//! form. You can navigate between fields using the Tab key.
//!
//! This does not handle cursor movement etc. This is just a simple example. In a real application,
//! consider using [`tui-input`], or [`tui-prompts`], or [`tui-textarea`].
//!
//! This example runs with the Ratatui library code in the branch that you are currently reading.
//! See the [`latest`] branch for the code which works with the most recent Ratatui release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//! [`tui-input`]: https://crates.io/crates/tui-input
//! [`tui-prompts`]: https://crates.io/crates/tui-prompts
//! [`tui-textarea`]: https://crates.io/crates/tui-textarea
use color_eyre::Result;
use crossterm::event::{self, KeyCode, KeyEvent};
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Layout, Offset, Rect};
use ratatui::style::Stylize;
use ratatui::text::Line;
use ratatui::widgets::Widget;
use ratatui::{DefaultTerminal, Frame};
use serde::Serialize;
fn main() -> Result<()> {
color_eyre::install()?;
// serialize the form to JSON if the user submitted it, otherwise print "Canceled"
match ratatui::run(|terminal| App::default().run(terminal)) {
Ok(Some(form)) => println!("{}", serde_json::to_string_pretty(&form)?),
Ok(None) => println!("Canceled"),
Err(err) => eprintln!("{err}"),
}
Ok(())
}
#[derive(Default)]
struct App {
state: AppState,
form: InputForm,
}
#[derive(Default, PartialEq, Eq)]
enum AppState {
#[default]
Running,
Cancelled,
Submitted,
}
impl App {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<Option<InputForm>> {
while self.state == AppState::Running {
terminal.draw(|frame| self.render(frame))?;
self.handle_events()?;
}
match self.state {
AppState::Cancelled => Ok(None),
AppState::Submitted => Ok(Some(self.form)),
AppState::Running => unreachable!(),
}
}
fn render(&self, frame: &mut Frame) {
self.form.render(frame);
}
fn handle_events(&mut self) -> Result<()> {
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Esc => self.state = AppState::Cancelled,
KeyCode::Enter => self.state = AppState::Submitted,
_ => self.form.on_key_press(key),
}
}
Ok(())
}
}
#[derive(Serialize)]
struct InputForm {
#[serde(skip)]
focus: Focus,
first_name: StringField,
last_name: StringField,
age: AgeField,
}
impl Default for InputForm {
fn default() -> Self {
Self {
focus: Focus::FirstName,
first_name: StringField::new("First Name"),
last_name: StringField::new("Last Name"),
age: AgeField::new("Age"),
}
}
}
impl InputForm {
// Handle focus navigation or pass the event to the focused field.
fn on_key_press(&mut self, event: KeyEvent) {
match event.code {
KeyCode::Tab => self.focus = self.focus.next(),
_ => match self.focus {
Focus::FirstName => self.first_name.on_key_press(event),
Focus::LastName => self.last_name.on_key_press(event),
Focus::Age => self.age.on_key_press(event),
},
}
}
/// Render the form with the current focus.
///
/// The cursor is placed at the end of the focused field.
fn render(&self, frame: &mut Frame) {
let layout = Layout::vertical(Constraint::from_lengths([1, 1, 1]));
let [first_name_area, last_name_area, age_area] = frame.area().layout(&layout);
frame.render_widget(&self.first_name, first_name_area);
frame.render_widget(&self.last_name, last_name_area);
frame.render_widget(&self.age, age_area);
let cursor_position = match self.focus {
Focus::FirstName => first_name_area + self.first_name.cursor_offset(),
Focus::LastName => last_name_area + self.last_name.cursor_offset(),
Focus::Age => age_area + self.age.cursor_offset(),
};
frame.set_cursor_position(cursor_position);
}
}
#[derive(Default, PartialEq, Eq)]
enum Focus {
#[default]
FirstName,
LastName,
Age,
}
impl Focus {
// Round-robin focus order.
const fn next(&self) -> Self {
match self {
Self::FirstName => Self::LastName,
Self::LastName => Self::Age,
Self::Age => Self::FirstName,
}
}
}
/// A new-type representing a string field with a label.
#[derive(Debug, Serialize)]
struct StringField {
#[serde(skip)]
label: &'static str,
value: String,
}
impl StringField {
const fn new(label: &'static str) -> Self {
Self {
label,
value: String::new(),
}
}
/// Handle input events for the string input.
fn on_key_press(&mut self, event: KeyEvent) {
match event.code {
KeyCode::Char(c) => self.value.push(c),
KeyCode::Backspace => {
self.value.pop();
}
_ => {}
}
}
fn cursor_offset(&self) -> Offset {
let x = (self.label.len() + self.value.len() + 2) as i32;
Offset::new(x, 0)
}
}
impl Widget for &StringField {
fn render(self, area: Rect, buf: &mut Buffer) {
let layout = Layout::horizontal([
Constraint::Length(self.label.len() as u16 + 2),
Constraint::Fill(1),
]);
let [label_area, value_area] = area.layout(&layout);
let label = Line::from_iter([self.label, ": "]).bold();
label.render(label_area, buf);
self.value.clone().render(value_area, buf);
}
}
/// A new-type representing a person's age in years (0-130).
#[derive(Default, Clone, Copy, Serialize)]
struct AgeField {
#[serde(skip)]
label: &'static str,
value: u8,
}
impl AgeField {
const MAX: u8 = 130;
const fn new(label: &'static str) -> Self {
Self { label, value: 0 }
}
/// Handle input events for the age input.
///
/// Digits are accepted as input, with any input which would exceed the maximum age being
/// ignored. The up/down arrow keys and 'j'/'k' keys can be used to increment/decrement the
/// age.
fn on_key_press(&mut self, event: KeyEvent) {
match event.code {
KeyCode::Char(digit @ '0'..='9') => {
let value = self
.value
.saturating_mul(10)
.saturating_add(digit as u8 - b'0');
if value <= Self::MAX {
self.value = value;
}
}
KeyCode::Backspace => self.value /= 10,
KeyCode::Up | KeyCode::Char('k') => self.increment(),
KeyCode::Down | KeyCode::Char('j') => self.decrement(),
_ => {}
}
}
fn increment(&mut self) {
self.value = self.value.saturating_add(1).min(Self::MAX);
}
const fn decrement(&mut self) {
self.value = self.value.saturating_sub(1);
}
fn cursor_offset(&self) -> Offset {
let x = (self.label.len() + self.value.to_string().len() + 2) as i32;
Offset::new(x, 0)
}
}
impl Widget for &AgeField {
fn render(self, area: Rect, buf: &mut Buffer) {
let layout = Layout::horizontal([
Constraint::Length(self.label.len() as u16 + 2),
Constraint::Fill(1),
]);
let [label_area, value_area] = area.layout(&layout);
let label = Line::from_iter([self.label, ": "]).bold();
let value = self.value.to_string();
label.render(label_area, buf);
value.render(value_area, buf);
}
}

View File

@@ -1,13 +0,0 @@
[package]
name = "minimal"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
crossterm.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Minimal demo
This example shows how to create a minimal application.
To run this demo:
```shell
cargo run -p minimal
```

View File

@@ -1,23 +0,0 @@
//! A minimal example of a Ratatui application.
//!
//! This is a bare minimum example. There are many approaches to running an application loop,
//! so this is not meant to be prescriptive. See the [examples] folder for more complete
//! examples. In particular, the [hello-world] example is a good starting point.
//!
//! This example runs with the Ratatui library code in the branch that you are currently
//! reading. See the [`latest`] branch for the code which works with the most recent Ratatui
//! release.
//!
//! [`latest`]: https://github.com/ratatui/ratatui/tree/latest
//! [examples]: https://github.com/ratatui/ratatui/blob/main/examples
//! [hello-world]: https://github.com/ratatui/ratatui/blob/main/examples/apps/hello-world
fn main() -> Result<(), Box<dyn std::error::Error>> {
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| frame.render_widget("Hello World!", frame.area()))?;
if crossterm::event::read()?.is_key_press() {
break Ok(());
}
}
})
}

View File

@@ -1,15 +0,0 @@
[package]
name = "modifiers"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
itertools.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Modifiers demo
This example shows different types of modifiers.
To run this demo:
```shell
cargo run -p modifiers
```

View File

@@ -1,17 +0,0 @@
[package]
name = "mouse-drawing"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
## a collection of line drawing algorithms (e.g. Bresenham's line algorithm)
line_drawing = "1"
rand.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Mouse drawing demo
This example shows how to receive mouse and handle mouse events.
To run this demo:
```shell
cargo run -p mouse-drawing
```

View File

@@ -1,118 +0,0 @@
/// A Ratatui example that demonstrates how to handle mouse events.
///
/// This example demonstrates how to handle mouse events in Ratatui. You can draw lines by
/// clicking and dragging the mouse.
///
/// This example runs with the Ratatui library code in the branch that you are currently
/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui
/// release.
///
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
use color_eyre::Result;
use crossterm::event::{
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, MouseEvent,
MouseEventKind,
};
use crossterm::execute;
use ratatui::layout::{Position, Rect, Size};
use ratatui::style::{Color, Stylize};
use ratatui::text::Line;
use ratatui::{DefaultTerminal, Frame, symbols};
fn main() -> Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| MouseDrawingApp::default().run(terminal))
}
#[derive(Default)]
struct MouseDrawingApp {
// Whether the app should exit
pub should_exit: bool,
// The last known mouse position
pub mouse_position: Option<Position>,
// The points that have been clicked / drawn by dragging the mouse
pub points: Vec<(Position, Color)>,
// The color to draw with
pub current_color: Color,
}
impl MouseDrawingApp {
fn run(mut self, terminal: &mut DefaultTerminal) -> Result<()> {
execute!(std::io::stdout(), EnableMouseCapture)?;
while !self.should_exit {
terminal.draw(|frame| self.render(frame))?;
self.handle_events()?;
}
execute!(std::io::stdout(), DisableMouseCapture)?;
Ok(())
}
fn handle_events(&mut self) -> Result<()> {
match event::read()? {
Event::Key(event) => self.on_key_event(event),
Event::Mouse(event) => self.on_mouse_event(event),
_ => {}
}
Ok(())
}
/// Quit the app if the user presses 'q' or 'Esc'
fn on_key_event(&mut self, key: KeyEvent) {
if !key.is_press() {
return;
}
match key.code {
KeyCode::Char(' ') => {
self.current_color = Color::Rgb(rand::random(), rand::random(), rand::random());
}
KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true,
_ => {}
}
}
/// Adds any points which were clicked or dragged to the `points` vector.
fn on_mouse_event(&mut self, event: MouseEvent) {
let position = Position::new(event.column, event.row);
match event.kind {
MouseEventKind::Down(_) => self.points.push((position, self.current_color)),
MouseEventKind::Drag(_) => self.draw_line(position),
_ => {}
}
self.mouse_position = Some(position);
}
/// Draw a line between the last point and the given position
fn draw_line(&mut self, position: Position) {
if let Some(start) = self.points.last() {
let (x0, y0) = (i32::from(start.0.x), i32::from(start.0.y));
let (x1, y1) = (i32::from(position.x), i32::from(position.y));
for (x, y) in line_drawing::Bresenham::new((x0, y0), (x1, y1)) {
let point = (Position::new(x as u16, y as u16), self.current_color);
self.points.push(point);
}
}
}
fn render(&self, frame: &mut Frame) {
// call order is important here as later elements are drawn on top of earlier elements
self.render_points(frame);
self.render_mouse_cursor(frame);
let value = "Mouse Example ('Esc' to quit. Click / drag to draw. 'Space' to change color)";
let title = Line::from(value).centered();
frame.render_widget(title, frame.area());
}
fn render_points(&self, frame: &mut Frame<'_>) {
for (position, color) in &self.points {
let area = Rect::from((*position, Size::new(1, 1))).clamp(frame.area());
frame.render_widget(symbols::block::FULL.fg(*color), area);
}
}
fn render_mouse_cursor(&self, frame: &mut Frame<'_>) {
if let Some(position) = self.mouse_position {
let area = Rect::from((position, Size::new(1, 1))).clamp(frame.area());
frame.render_widget("".bg(self.current_color), area);
}
}
}

View File

@@ -1,14 +0,0 @@
[package]
name = "panic"
publish = false
license.workspace = true
edition.workspace = true
rust-version.workspace = true
[dependencies]
color-eyre.workspace = true
crossterm.workspace = true
ratatui.workspace = true
[lints]
workspace = true

View File

@@ -1,9 +0,0 @@
# Panic demo
This example shows how to handle panics in your application.
To run this demo:
```shell
cargo run -p panic
```

View File

@@ -1,88 +0,0 @@
/// A Ratatui example that demonstrates how to handle panics in your application.
///
/// Prior to Ratatui 0.28.1, a panic hook had to be manually set up to ensure that the terminal
/// was reset when a panic occurred. This was necessary because a panic would interrupt the
/// normal control flow and leave the terminal in a distorted state.
///
/// Starting with Ratatui 0.28.1, the panic hook is automatically set up by the new
/// `ratatui::init` function, so you no longer need to manually set up the panic hook. This
/// example now demonstrates how the panic hook acts when it is enabled by default.
///
/// When exiting normally or when handling `Result::Err`, we can reset the terminal manually at
/// the end of `main` just before we print the error.
///
/// Because a panic interrupts the normal control flow, manually resetting the terminal at the
/// end of `main` won't do us any good. Instead, we need to make sure to set up a panic hook
/// that first resets the terminal before handling the panic. This both reuses the standard
/// panic hook to ensure a consistent panic handling UX and properly resets the terminal to not
/// distort the output.
///
/// That's why this example is set up to show both situations, with and without the panic hook,
/// to see the difference.
///
/// For more information on how to set this up manually, see the [Color Eyre recipe] in the
/// Ratatui website.
///
/// This example runs with the Ratatui library code in the branch that you are currently
/// reading. See the [`latest`] branch for the code which works with the most recent Ratatui
/// release.
///
/// [`latest`]: https://github.com/ratatui/ratatui/tree/latest
/// [Color Eyre recipe]: https://ratatui.rs/recipes/apps/color-eyre
use color_eyre::{Result, eyre::bail};
use crossterm::event::{self, KeyCode};
use ratatui::Frame;
use ratatui::text::Line;
use ratatui::widgets::{Block, Paragraph};
#[derive(Debug)]
enum PanicHandlerState {
Enabled,
Disabled,
}
fn main() -> Result<()> {
color_eyre::install()?;
let mut panic_hook_state = PanicHandlerState::Enabled;
ratatui::run(|terminal| {
loop {
terminal.draw(|frame| render(frame, &panic_hook_state))?;
if let Some(key) = event::read()?.as_key_press_event() {
match key.code {
KeyCode::Char('p') => panic!("intentional demo panic"),
KeyCode::Char('e') => bail!("intentional demo error"),
KeyCode::Char('h') => {
let _ = std::panic::take_hook();
panic_hook_state = PanicHandlerState::Disabled;
}
KeyCode::Char('q') => return Ok(()),
_ => {}
}
}
}
})
}
fn render(frame: &mut Frame, state: &PanicHandlerState) {
let text = vec![
Line::from(format!("Panic hook is currently: {state:?}")),
Line::from(""),
Line::from("Press `p` to cause a panic"),
Line::from("Press `e` to cause an error"),
Line::from("Press `h` to disable the panic hook"),
Line::from("Press `q` to quit"),
Line::from(""),
Line::from("When your app panics without a panic hook, you will likely have to"),
Line::from("reset your terminal afterwards with the `reset` command"),
Line::from(""),
Line::from("Try first with the panic handler enabled, and then with it disabled"),
Line::from("to see the difference"),
];
let paragraph = Paragraph::new(text)
.block(Block::bordered().title("Panic Handler Demo"))
.centered();
frame.render_widget(paragraph, frame.area());
}

Some files were not shown because too many files have changed in this diff Show More