Compare commits
5 Commits
main
...
jm/buffer-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ff292742c | ||
|
|
e084c9f013 | ||
|
|
f08640c53b | ||
|
|
2b391ac15d | ||
|
|
99ef8651aa |
@@ -1,2 +0,0 @@
|
||||
[alias]
|
||||
xtask = "run --package xtask --"
|
||||
115
.cliffignore
115
.cliffignore
@@ -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
|
||||
22
.cz.toml
22
.cz.toml
@@ -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
2
.github/FUNDING.yml
vendored
@@ -1,2 +0,0 @@
|
||||
github: ratatui
|
||||
open_collective: ratatui
|
||||
17
.github/ISSUE_TEMPLATE/bug_report.md
vendored
17
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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.
|
||||
|
||||
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -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.
|
||||
-->
|
||||
|
||||
81
.github/copilot-instructions.md
vendored
81
.github/copilot-instructions.md
vendored
@@ -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
25
.github/workflows/bench_base.yml
vendored
Normal 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
25
.github/workflows/bench_run_fork_pr.yml
vendored
Normal 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 }}
|
||||
75
.github/workflows/bench_track_fork_pr.yml
vendored
Normal file
75
.github/workflows/bench_track_fork_pr.yml
vendored
Normal 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"
|
||||
52
.github/workflows/calculate-alpha-release.bash
vendored
Executable file
52
.github/workflows/calculate-alpha-release.bash
vendored
Executable 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}"
|
||||
65
.github/workflows/check-pr.yml
vendored
65
.github/workflows/check-pr.yml
vendored
@@ -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') }}
|
||||
|
||||
10
.github/workflows/check-semver.yml
vendored
10
.github/workflows/check-semver.yml
vendored
@@ -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
|
||||
|
||||
279
.github/workflows/ci.yml
vendored
279
.github/workflows/ci.yml
vendored
@@ -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
48
.github/workflows/release-alpha.yml
vendored
Normal 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
|
||||
71
.github/workflows/release-plz.yml
vendored
71
.github/workflows/release-plz.yml
vendored
@@ -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
45
.github/workflows/release-stable.yml
vendored
Normal 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 }}
|
||||
26
.github/workflows/zizmor.yml
vendored
26
.github/workflows/zizmor.yml
vendored
@@ -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
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
target
|
||||
Cargo.lock
|
||||
*.log
|
||||
*.rs.rustfmt
|
||||
.gdb_history
|
||||
.idea/
|
||||
.env
|
||||
|
||||
12
.taplo.toml
12
.taplo.toml
@@ -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
|
||||
203
ARCHITECTURE.md
203
ARCHITECTURE.md
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
|
||||
2576
CHANGELOG.md
2576
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
124
CONTRIBUTING.md
124
CONTRIBUTING.md
@@ -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
4681
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
410
Cargo.toml
410
Cargo.toml
@@ -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"]
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -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
|
||||
|
||||
@@ -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
154
Makefile.toml
Normal 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
493
README.md
@@ -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>
|
||||
|
||||

|
||||
<!-- cargo-rdme start -->
|
||||
|
||||

|
||||
|
||||
<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-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
|
||||
[](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);
|
||||
}
|
||||
```
|
||||
|
||||
[](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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
@@ -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 |
98
bacon.toml
98
bacon.toml
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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] {
|
||||
@@ -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.
|
||||
@@ -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
24
benches/main/rect.rs
Normal 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);
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
50
cliff.toml
50
cliff.toml
@@ -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
|
||||
|
||||
16
clippy.toml
16
clippy.toml
@@ -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",
|
||||
]
|
||||
|
||||
29
deny.toml
29
deny.toml
@@ -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 }]
|
||||
|
||||
@@ -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/).
|
||||
|
||||

|
||||
[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/).
|
||||
|
||||

|
||||
```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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -1,9 +0,0 @@
|
||||
# Constraints demo
|
||||
|
||||
This example shows different types of constraints.
|
||||
|
||||
To run this demo:
|
||||
|
||||
```shell
|
||||
cargo run -p constraints
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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 }
|
||||
@@ -1,25 +0,0 @@
|
||||
# Demo example
|
||||
|
||||
This is the original demo that was developed for Tui-rs (the library that Ratatui was forked from).
|
||||
|
||||

|
||||
|
||||
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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
|
||||

|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -1,9 +0,0 @@
|
||||
# Gauge demo
|
||||
|
||||
This example shows different types of gauges.
|
||||
|
||||
To run this demo:
|
||||
|
||||
```shell
|
||||
cargo run -p gauge
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,9 +0,0 @@
|
||||
# Minimal demo
|
||||
|
||||
This example shows how to create a minimal application.
|
||||
|
||||
To run this demo:
|
||||
|
||||
```shell
|
||||
cargo run -p minimal
|
||||
```
|
||||
@@ -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(());
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
@@ -1,9 +0,0 @@
|
||||
# Modifiers demo
|
||||
|
||||
This example shows different types of modifiers.
|
||||
|
||||
To run this demo:
|
||||
|
||||
```shell
|
||||
cargo run -p modifiers
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
Reference in New Issue
Block a user