Compare commits
95 Commits
ratatui-v0
...
vhs-test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af18bae1ff | ||
|
|
c68ff86c64 | ||
|
|
c337aa3d58 | ||
|
|
7cef8d2a8b | ||
|
|
96a468443e | ||
|
|
1ae449c92b | ||
|
|
71722bbf41 | ||
|
|
1b573d982e | ||
|
|
ac60de3960 | ||
|
|
91fa249cd4 | ||
|
|
73488abb45 | ||
|
|
40f13c6a6c | ||
|
|
a89d3d62ff | ||
|
|
42a4e9e9af | ||
|
|
74558dafae | ||
|
|
3c54e91420 | ||
|
|
3673d360cf | ||
|
|
28e5f35b17 | ||
|
|
ba37ecebdb | ||
|
|
a2b2f98868 | ||
|
|
e799b732b6 | ||
|
|
b08b4cbd5e | ||
|
|
881d2452e0 | ||
|
|
cedad530fe | ||
|
|
0e6c8f89be | ||
|
|
aa6d8c9200 | ||
|
|
bbe590ab08 | ||
|
|
b507b38c58 | ||
|
|
8aefc06a90 | ||
|
|
03f3f6df35 | ||
|
|
ecef506a2b | ||
|
|
e89a526aab | ||
|
|
46e7c6cbbf | ||
|
|
9a4f5af613 | ||
|
|
0a9ff761a4 | ||
|
|
632ce79adf | ||
|
|
564a9d76fc | ||
|
|
2d713d723d | ||
|
|
86cd5292dd | ||
|
|
a21501f7f4 | ||
|
|
75b78be09f | ||
|
|
719badb5b8 | ||
|
|
879e09787e | ||
|
|
381fcf3dc9 | ||
|
|
03072fa374 | ||
|
|
01067efd1c | ||
|
|
ae4f42f5ae | ||
|
|
9998000e36 | ||
|
|
8188ed3950 | ||
|
|
5e75278499 | ||
|
|
71ef65b624 | ||
|
|
c845fec765 | ||
|
|
0afb1a99af | ||
|
|
5ae224b244 | ||
|
|
cd90671a8b | ||
|
|
08040b893e | ||
|
|
cba5cca2bd | ||
|
|
c01b7d43ea | ||
|
|
9836f0760d | ||
|
|
bce6604379 | ||
|
|
c8d5d385b3 | ||
|
|
3fe3fe316e | ||
|
|
0f10944ca0 | ||
|
|
1bd3181d8e | ||
|
|
e04fef7354 | ||
|
|
bbca3870d5 | ||
|
|
1fe64de09a | ||
|
|
72334ed61c | ||
|
|
c1b8528b69 | ||
|
|
cfebd68e18 | ||
|
|
572749f388 | ||
|
|
cdf189e15a | ||
|
|
98f85b8650 | ||
|
|
0148b62f0c | ||
|
|
9bc573931c | ||
|
|
64e6d1953c | ||
|
|
c0148a00fe | ||
|
|
44727c7b4a | ||
|
|
1e9d1253f9 | ||
|
|
aae46032bc | ||
|
|
0e10170e19 | ||
|
|
40e96a2a04 | ||
|
|
02ca5870c5 | ||
|
|
8e3bd11d60 | ||
|
|
017af11b2b | ||
|
|
821611f76f | ||
|
|
ae43ea796a | ||
|
|
5fa342cc52 | ||
|
|
08b21fa55c | ||
|
|
32ccf38606 | ||
|
|
04428f6f74 | ||
|
|
84427c2857 | ||
|
|
ca5a9bb019 | ||
|
|
1f0c2ee18e | ||
|
|
6b3323fb10 |
81
.github/copilot-instructions.md
vendored
Normal file
81
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
# 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.
|
||||
```
|
||||
52
.github/workflows/calculate-alpha-release.bash
vendored
52
.github/workflows/calculate-alpha-release.bash
vendored
@@ -1,52 +0,0 @@
|
||||
#!/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,6 +1,13 @@
|
||||
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
|
||||
@@ -8,23 +15,21 @@ 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@v5
|
||||
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # 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@v2
|
||||
- uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2
|
||||
if: always() && (steps.check_pr_title.outputs.error_message != null)
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
@@ -39,40 +44,42 @@ 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@v2
|
||||
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # 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@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']
|
||||
})
|
||||
- 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']
|
||||
})
|
||||
|
||||
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,5 +1,9 @@
|
||||
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:
|
||||
@@ -11,6 +15,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check semver
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
uses: obi1kenobi/cargo-semver-checks-action@5b298c9520f7096a4683c0bd981a7ac5a7e249ae # v2
|
||||
|
||||
154
.github/workflows/ci.yml
vendored
154
.github/workflows/ci.yml
vendored
@@ -1,5 +1,9 @@
|
||||
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:
|
||||
@@ -25,11 +29,15 @@ jobs:
|
||||
name: Check Formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with: { components: rustfmt }
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@v2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
|
||||
- uses: taiki-e/install-action@4575ae687efd0e2c78240087f26013fb2484987f # v2
|
||||
with:
|
||||
tool: taplo-cli
|
||||
- run: cargo xtask format --check
|
||||
@@ -40,8 +48,10 @@ jobs:
|
||||
name: Check Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: crate-ci/typos@master
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: crate-ci/typos@85f62a8a84f939ae994ab3763f01a0296d61a7ee # master
|
||||
|
||||
# Check for any disallowed dependencies in the codebase due to license / security issues.
|
||||
# See <https://github.com/EmbarkStudios/cargo-deny>
|
||||
@@ -49,9 +59,15 @@ jobs:
|
||||
name: Check Dependencies
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@cargo-deny
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: stable
|
||||
- uses: taiki-e/install-action@4575ae687efd0e2c78240087f26013fb2484987f # v2
|
||||
with:
|
||||
tool: cargo-deny
|
||||
- run: cargo deny --log-level info --all-features check
|
||||
|
||||
# Check for any unused dependencies in the codebase.
|
||||
@@ -60,8 +76,10 @@ jobs:
|
||||
name: Check Unused Dependencies
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: bnjbvr/cargo-machete@v0.8.0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: bnjbvr/cargo-machete@7959c845782fed02ee69303126d4a12d64f1db18 # v0.9.1
|
||||
|
||||
# Run cargo clippy.
|
||||
#
|
||||
@@ -77,12 +95,14 @@ jobs:
|
||||
toolchain: ["stable", "beta"]
|
||||
continue-on-error: ${{ matrix.toolchain == 'beta' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
components: clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
|
||||
- run: cargo xtask clippy
|
||||
|
||||
# Run markdownlint on all markdown files in the repository.
|
||||
@@ -90,8 +110,10 @@ jobs:
|
||||
name: Check Markdown
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DavidAnson/markdownlint-cli2-action@v20
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e # v20
|
||||
with:
|
||||
globs: |
|
||||
'**/*.md'
|
||||
@@ -103,14 +125,19 @@ jobs:
|
||||
name: Coverage Report
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: stable
|
||||
components: llvm-tools
|
||||
- uses: taiki-e/install-action@cargo-llvm-cov
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@4575ae687efd0e2c78240087f26013fb2484987f # v2
|
||||
with:
|
||||
tool: cargo-llvm-cov
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
|
||||
- run: cargo xtask coverage
|
||||
- uses: codecov/codecov-action@v5
|
||||
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: true
|
||||
@@ -125,22 +152,30 @@ jobs:
|
||||
toolchain: ["1.85.0", "stable"]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@4575ae687efd0e2c78240087f26013fb2484987f # v2
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
|
||||
- run: cargo xtask check --all-features
|
||||
|
||||
build-no-std:
|
||||
name: Build No-Std
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: x86_64-unknown-none
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # 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
|
||||
@@ -156,9 +191,13 @@ jobs:
|
||||
name: Check README
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@cargo-rdme
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
|
||||
- uses: taiki-e/install-action@4575ae687efd0e2c78240087f26013fb2484987f # 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
|
||||
@@ -169,10 +208,19 @@ jobs:
|
||||
env:
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: dtolnay/install@cargo-docs-rs
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: nightly
|
||||
- uses: dtolnay/install@74f735cdf643820234e37ae1c4089a08fd266d8a # master
|
||||
with:
|
||||
crate: cargo-docs-rs
|
||||
- uses: taiki-e/install-action@4575ae687efd0e2c78240087f26013fb2484987f # v2
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
|
||||
- run: cargo xtask docs
|
||||
|
||||
# Run cargo test on the documentation of the crate. This will catch any code examples that don't
|
||||
@@ -181,9 +229,16 @@ jobs:
|
||||
name: Test Docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: stable
|
||||
- uses: taiki-e/install-action@4575ae687efd0e2c78240087f26013fb2484987f # v2
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
|
||||
- run: cargo xtask test-docs
|
||||
|
||||
# Run cargo test on the libraries of the crate.
|
||||
@@ -195,9 +250,16 @@ jobs:
|
||||
matrix:
|
||||
toolchain: ["1.85.0", "stable"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: stable
|
||||
- uses: taiki-e/install-action@4575ae687efd0e2c78240087f26013fb2484987f # v2
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
|
||||
- run: cargo xtask test-libs
|
||||
|
||||
# Run cargo test on all the backends.
|
||||
@@ -214,7 +276,11 @@ jobs:
|
||||
- os: windows-latest
|
||||
backend: termion
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: stable
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2
|
||||
- run: cargo xtask test-backend ${{ matrix.backend }}
|
||||
|
||||
49
.github/workflows/release-alpha.yml
vendored
49
.github/workflows/release-alpha.yml
vendored
@@ -1,49 +0,0 @@
|
||||
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
|
||||
if: github.repository == 'ratatui/ratatui'
|
||||
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
|
||||
35
.github/workflows/release-plz.yml
vendored
35
.github/workflows/release-plz.yml
vendored
@@ -1,8 +1,8 @@
|
||||
name: Release-plz
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
# 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:
|
||||
@@ -14,26 +14,38 @@ 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@v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: stable
|
||||
- uses: rust-lang/crates-io-auth-action@e919bc7605cde86df457cf5b93c5e103838bd879 # v1
|
||||
id: auth
|
||||
- name: Run release-plz
|
||||
uses: release-plz/action@v0.5
|
||||
uses: release-plz/action@acb9246af4d59a270d1d4058a8b9af8c3f3a2559 # v0.5
|
||||
with:
|
||||
command: release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_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
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'ratatui' }}
|
||||
concurrency:
|
||||
@@ -41,13 +53,16 @@ jobs:
|
||||
cancel-in-progress: false
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Run release-plz
|
||||
uses: release-plz/action@v0.5
|
||||
uses: release-plz/action@acb9246af4d59a270d1d4058a8b9af8c3f3a2559 # v0.5
|
||||
with:
|
||||
command: release-pr
|
||||
env:
|
||||
|
||||
45
.github/workflows/release-stable.yml
vendored
45
.github/workflows/release-stable.yml
vendored
@@ -1,45 +0,0 @@
|
||||
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 }}
|
||||
35
.github/workflows/vhs-test.yml
vendored
Normal file
35
.github/workflows/vhs-test.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: vhs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- vhs-test
|
||||
|
||||
jobs:
|
||||
vhs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # master
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
id: setup-ffmpeg
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "^1.13.1" # The Go version to download (if necessary) and use.
|
||||
|
||||
- run: |
|
||||
cargo build -p demo2
|
||||
sudo apt update
|
||||
sudo apt install -y ffmpeg ttyd
|
||||
go install github.com/charmbracelet/vhs@latest
|
||||
vhs ./examples/vhs/demo2.tape
|
||||
|
||||
- name: Upload GIF artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: demo2-gif
|
||||
path: ./target/demo2.gif
|
||||
26
.github/workflows/zizmor.yml
vendored
Normal file
26
.github/workflows/zizmor.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor 🌈
|
||||
uses: zizmorcore/zizmor-action@e673c3917a1aef3c65c972347ed84ccd013ecda4 # v0.2.0
|
||||
@@ -26,6 +26,7 @@ This is a quick summary of the sections below:
|
||||
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
|
||||
- [v0.29.0](#v0290)
|
||||
- `Sparkline::data` takes `IntoIterator<Item = SparklineBar>` instead of `&[u64]` and is no longer
|
||||
const
|
||||
@@ -377,6 +378,17 @@ 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])
|
||||
|
||||
935
CHANGELOG.md
935
CHANGELOG.md
@@ -1094,913 +1094,8 @@ All notable changes to this project will be documented in this file.
|
||||
>
|
||||
> BREAKING CHANGE:MSRV is now 1.81
|
||||
|
||||
### Security
|
||||
|
||||
- [3745a67](https://github.com/ratatui/ratatui/commit/3745a67ba071d5a52e378af34f8409cd90912eb0) *(deps)* Bump rand from 0.9.0 to 0.9.1 by @dependabot[bot] in [#1804](https://github.com/ratatui/ratatui/pull/1804)
|
||||
|
||||
> Bumps [rand](https://github.com/rust-random/rand) from 0.9.0 to 0.9.1.
|
||||
> <details>
|
||||
> <summary>Changelog</summary>
|
||||
> <p><em>Sourced from <a
|
||||
> href="https://github.com/rust-random/rand/blob/master/CHANGELOG.md">rand's
|
||||
> changelog</a>.</em></p>
|
||||
> <blockquote>
|
||||
> <h2>[0.9.1] - 2025-04-17</h2>
|
||||
> <h3>Security and unsafe</h3>
|
||||
> <ul>
|
||||
> <li>Revise "not a crypto library" policy again (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1565">#1565</a>)</li>
|
||||
> <li>Remove <code>zerocopy</code> dependency from <code>rand</code> (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1579">#1579</a>)</li>
|
||||
> </ul>
|
||||
> <h3>Fixes</h3>
|
||||
> <ul>
|
||||
> <li>Fix feature <code>simd_support</code> for recent nightly rust (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1586">#1586</a>)</li>
|
||||
> </ul>
|
||||
> <h3>Changes</h3>
|
||||
> <ul>
|
||||
> <li>Allow <code>fn rand::seq::index::sample_weighted</code> and <code>fn
|
||||
> IndexedRandom::choose_multiple_weighted</code> to return fewer than
|
||||
> <code>amount</code> results (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1623">#1623</a>),
|
||||
> reverting an undocumented change (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1382">#1382</a>)
|
||||
> to the previous release.</li>
|
||||
> </ul>
|
||||
> <h3>Additions</h3>
|
||||
> <ul>
|
||||
> <li>Add <code>rand::distr::Alphabetic</code> distribution. (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1587">#1587</a>)</li>
|
||||
> <li>Re-export <code>rand_core</code> (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1604">#1604</a>)</li>
|
||||
> </ul>
|
||||
> </blockquote>
|
||||
> </details>
|
||||
> <details>
|
||||
> <summary>Commits</summary>
|
||||
> <ul>
|
||||
> <li><a
|
||||
> href="https://github.com/rust-random/rand/commit/ec6d5c06a5384c14563a2164bb4a038100a5bb78"><code>ec6d5c0</code></a>
|
||||
> Prepare rand_core v0.9.1 (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1591">#1591</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/rust-random/rand/commit/6a06056e8a892bfa181ec24a8ea16aa9f2fe97d3"><code>6a06056</code></a>
|
||||
> rand_core: introduce an UnwrapMut wrapper (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1589">#1589</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/rust-random/rand/commit/8929123b4d5abb7cae349c5f8213bf2fa8583821"><code>8929123</code></a>
|
||||
> Add <code>Alphabetic</code> distribution (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1587">#1587</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/rust-random/rand/commit/06b16426bd1431e164247c8bdf109cedb67213f7"><code>06b1642</code></a>
|
||||
> Remove unnecessary underscore from `impl<T, const N: usize>
|
||||
> Distribution<[T; ...</li>
|
||||
> <li><a
|
||||
> href="https://github.com/rust-random/rand/commit/49d76cd7b4a318114cff22494997c73a56ffd95c"><code>49d76cd</code></a>
|
||||
> rename extract to extract_lane (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1586">#1586</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/rust-random/rand/commit/e0a70fd8be09196bcaf4a86e732bce501b42ef86"><code>e0a70fd</code></a>
|
||||
> Change to use <code>array::from_fn</code> in <code>Distribution\<[T;
|
||||
> N]> for StandardUniform</code> ...</li>
|
||||
> <li><a
|
||||
> href="https://github.com/rust-random/rand/commit/0bc3f652c4500406b343a517e058caedd1f095a9"><code>0bc3f65</code></a>
|
||||
> Move rand distr (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1577">#1577</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/rust-random/rand/commit/2677c49960e3a3fc2f1a8df90c1d7104089903f2"><code>2677c49</code></a>
|
||||
> Revise "not a crypto library" policy and SECURITY.md (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1565">#1565</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/rust-random/rand/commit/bfd1826c36c441236f218af478edd794bca7f23a"><code>bfd1826</code></a>
|
||||
> SeedableRng docs: add note on (lack of) reproducibility (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1572">#1572</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/rust-random/rand/commit/c01aee7a138ff77657782069771bb11f120318d7"><code>c01aee7</code></a>
|
||||
> Fix some links (<a
|
||||
> href="https://redirect.github.com/rust-random/rand/issues/1571">#1571</a>)</li>
|
||||
> <li>Additional commits viewable in <a
|
||||
> href="https://github.com/rust-random/rand/compare/0.9.0...rand_core-0.9.1">compare
|
||||
> view</a></li>
|
||||
> </ul>
|
||||
> </details>
|
||||
> <br />
|
||||
>
|
||||
>
|
||||
> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
|
||||
>
|
||||
> Dependabot will resolve any conflicts with this PR as long as you don't
|
||||
> alter it yourself. You can also trigger a rebase manually by commenting
|
||||
> `@dependabot rebase`.
|
||||
>
|
||||
> [//]:# (dependabot-automerge-start)
|
||||
>
|
||||
> [//]:# (dependabot-automerge-end)
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> <details>
|
||||
> <summary>Dependabot commands and options</summary>
|
||||
> <br />
|
||||
>
|
||||
> You can trigger Dependabot actions by commenting on this PR:
|
||||
> - `@dependabot rebase` will rebase this PR
|
||||
> - `@dependabot recreate` will recreate this PR, overwriting any edits
|
||||
> that have been made to it
|
||||
> - `@dependabot merge` will merge this PR after your CI passes on it
|
||||
> - `@dependabot squash and merge` will squash and merge this PR after
|
||||
> your CI passes on it
|
||||
> - `@dependabot cancel merge` will cancel a previously requested merge
|
||||
> and block automerging
|
||||
> - `@dependabot reopen` will reopen this PR if it is closed
|
||||
> - `@dependabot close` will close this PR and stop Dependabot recreating
|
||||
> it. You can achieve the same result by closing it manually
|
||||
> - `@dependabot show <dependency name> ignore conditions` will show all
|
||||
> of the ignore conditions of the specified dependency
|
||||
> - `@dependabot ignore this major version` will close this PR and stop
|
||||
> Dependabot creating any more for this major version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this minor version` will close this PR and stop
|
||||
> Dependabot creating any more for this minor version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this dependency` will close this PR and stop
|
||||
> Dependabot creating any more for this dependency (unless you reopen the
|
||||
> PR or upgrade to it yourself)
|
||||
>
|
||||
>
|
||||
> </details>
|
||||
|
||||
- [a03ba0d](https://github.com/ratatui/ratatui/commit/a03ba0de5c02a51726285b359a512e6de8b84622) *(deps)* Bump crossterm from 0.28.1 to 0.29.0 by @dependabot[bot] in [#1771](https://github.com/ratatui/ratatui/pull/1771)
|
||||
|
||||
> Bumps [crossterm](https://github.com/crossterm-rs/crossterm) from 0.28.1
|
||||
> to 0.29.0.
|
||||
> <details>
|
||||
> <summary>Release notes</summary>
|
||||
> <p><em>Sourced from <a
|
||||
> href="https://github.com/crossterm-rs/crossterm/releases">crossterm's
|
||||
> releases</a>.</em></p>
|
||||
> <blockquote>
|
||||
> <h2>0.29</h2>
|
||||
> <h1>Version 0.29</h1>
|
||||
> <h2>Added ⭐</h2>
|
||||
> <ul>
|
||||
> <li>Copy to clipboard using OSC52 (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/974">#974</a>)</li>
|
||||
> <li>Derive standard traits for "SetCursorStyle" (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/909">#909</a>)</li>
|
||||
> <li>Add query_keyboard_enhancement_flags to read enabled flags (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/958">#958</a>)</li>
|
||||
> <li>Add is_* and as_* methods to the event enums (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/949">#949</a>)</li>
|
||||
> <li>Add a feature flag for derive_more impls (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/970">#970</a>)</li>
|
||||
> <li>Update rustix to 1.0 (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/982">#982</a>)</li>
|
||||
> <li>Upgrade various dependencies</li>
|
||||
> </ul>
|
||||
> <h2>Breaking ⚠️</h2>
|
||||
> <ul>
|
||||
> <li>Correctly fix KeyModifiers Display impl Properly adding + in between
|
||||
> modifiers (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/979">#979</a>)</li>
|
||||
> </ul>
|
||||
> <p><a href="https://github.com/joshka"><code>@joshka</code></a> <a
|
||||
> href="https://github.com/linrongbin16"><code>@linrongbin16</code></a>
|
||||
> <a href="https://github.com/kmicklas"><code>@kmicklas</code></a> <a
|
||||
> href="https://github.com/maciek50322"><code>@maciek50322</code></a> <a
|
||||
> href="https://github.com/rosew0od"><code>@rosew0od</code></a> <a
|
||||
> href="https://github.com/sxyazi"><code>@sxyazi</code></a> <a
|
||||
> href="https://github.com/the-mikedavis"><code>@the-mikedavis</code></a>
|
||||
> <a href="https://github.com/hthuz"><code>@hthuz</code></a> <a
|
||||
> href="https://github.com/aschey"><code>@aschey</code></a> <a
|
||||
> href="https://github.com/naseschwarz"><code>@naseschwarz</code></a> <a
|
||||
> href="https://github.com/Flokkq"><code>@Flokkq</code></a> <a
|
||||
> href="https://github.com/gaesa"><code>@gaesa</code></a> <a
|
||||
> href="https://github.com/WindSoilder"><code>@WindSoilder</code></a></p>
|
||||
> </blockquote>
|
||||
> </details>
|
||||
> <details>
|
||||
> <summary>Changelog</summary>
|
||||
> <p><em>Sourced from <a
|
||||
> href="https://github.com/crossterm-rs/crossterm/blob/master/CHANGELOG.md">crossterm's
|
||||
> changelog</a>.</em></p>
|
||||
> <blockquote>
|
||||
> <h1>Unreleased</h1>
|
||||
> <h1>Version 0.29</h1>
|
||||
> <h2>Added ⭐</h2>
|
||||
> <ul>
|
||||
> <li>Copy to clipboard using OSC52 (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/974">#974</a>)</li>
|
||||
> <li>Derive standard traits for "SetCursorStyle" (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/909">#909</a>)</li>
|
||||
> <li>Add query_keyboard_enhancement_flags to read enabled flags (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/958">#958</a>)</li>
|
||||
> <li>Add is_* and as_* methods to the event enums (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/949">#949</a>)</li>
|
||||
> <li>Add a feature flag for derive_more impls (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/970">#970</a>)</li>
|
||||
> <li>Update rustix to 1.0 (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/982">#982</a>)</li>
|
||||
> </ul>
|
||||
> <h2>Breaking ⚠️</h2>
|
||||
> <ul>
|
||||
> <li>Correctly fix KeyModifiers Display impl Properly adding + in between
|
||||
> modifiers (<a
|
||||
> href="https://redirect.github.com/crossterm-rs/crossterm/issues/979">#979</a>)</li>
|
||||
> </ul>
|
||||
> </blockquote>
|
||||
> </details>
|
||||
> <details>
|
||||
> <summary>Commits</summary>
|
||||
> <ul>
|
||||
> <li>See full diff in <a
|
||||
> href="https://github.com/crossterm-rs/crossterm/commits/0.29">compare
|
||||
> view</a></li>
|
||||
> </ul>
|
||||
> </details>
|
||||
> <br />
|
||||
>
|
||||
>
|
||||
> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
|
||||
>
|
||||
> Dependabot will resolve any conflicts with this PR as long as you don't
|
||||
> alter it yourself. You can also trigger a rebase manually by commenting
|
||||
> `@dependabot rebase`.
|
||||
>
|
||||
> [//]:# (dependabot-automerge-start)
|
||||
>
|
||||
> [//]:# (dependabot-automerge-end)
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> <details>
|
||||
> <summary>Dependabot commands and options</summary>
|
||||
> <br />
|
||||
>
|
||||
> You can trigger Dependabot actions by commenting on this PR:
|
||||
> - `@dependabot rebase` will rebase this PR
|
||||
> - `@dependabot recreate` will recreate this PR, overwriting any edits
|
||||
> that have been made to it
|
||||
> - `@dependabot merge` will merge this PR after your CI passes on it
|
||||
> - `@dependabot squash and merge` will squash and merge this PR after
|
||||
> your CI passes on it
|
||||
> - `@dependabot cancel merge` will cancel a previously requested merge
|
||||
> and block automerging
|
||||
> - `@dependabot reopen` will reopen this PR if it is closed
|
||||
> - `@dependabot close` will close this PR and stop Dependabot recreating
|
||||
> it. You can achieve the same result by closing it manually
|
||||
> - `@dependabot show <dependency name> ignore conditions` will show all
|
||||
> of the ignore conditions of the specified dependency
|
||||
> - `@dependabot ignore this major version` will close this PR and stop
|
||||
> Dependabot creating any more for this major version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this minor version` will close this PR and stop
|
||||
> Dependabot creating any more for this minor version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this dependency` will close this PR and stop
|
||||
> Dependabot creating any more for this dependency (unless you reopen the
|
||||
> PR or upgrade to it yourself)
|
||||
>
|
||||
>
|
||||
> </details>
|
||||
|
||||
- [fdc1746](https://github.com/ratatui/ratatui/commit/fdc1746effadf28392f055345be10074f4117e3e) *(deps)* Bump tokio from 1.44.1 to 1.44.2 by @dependabot[bot] in [#1769](https://github.com/ratatui/ratatui/pull/1769)
|
||||
|
||||
> Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.44.1 to 1.44.2.
|
||||
> <details>
|
||||
> <summary>Release notes</summary>
|
||||
> <p><em>Sourced from <a
|
||||
> href="https://github.com/tokio-rs/tokio/releases">tokio's
|
||||
> releases</a>.</em></p>
|
||||
> <blockquote>
|
||||
> <h2>Tokio v1.44.2</h2>
|
||||
> <p>This release fixes a soundness issue in the broadcast channel. The
|
||||
> channel
|
||||
> accepts values that are <code>Send</code> but <code>!Sync</code>.
|
||||
> Previously, the channel called
|
||||
> <code>clone()</code> on these values without synchronizing. This release
|
||||
> fixes the channel
|
||||
> by synchronizing calls to <code>.clone()</code> (Thanks Austin Bonander
|
||||
> for finding and
|
||||
> reporting the issue).</p>
|
||||
> <h3>Fixed</h3>
|
||||
> <ul>
|
||||
> <li>sync: synchronize <code>clone()</code> call in broadcast channel (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7232">#7232</a>)</li>
|
||||
> </ul>
|
||||
> <p><a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7232">#7232</a>:
|
||||
> <a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/pull/7232">tokio-rs/tokio#7232</a></p>
|
||||
> </blockquote>
|
||||
> </details>
|
||||
> <details>
|
||||
> <summary>Commits</summary>
|
||||
> <ul>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/ec4b1d7215a3e1e91797ad3fb6ba0f7c7f3d2566"><code>ec4b1d7</code></a>
|
||||
> chore: forward port 1.43.x</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/e3c3a56718d201fb7bb430567f05fbb64b2ef082"><code>e3c3a56</code></a>
|
||||
> Merge branch 'tokio-1.43.x' into forward-port-1.43.x</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/a7b658c35bd40f6811e557aeb97cbb361b612c56"><code>a7b658c</code></a>
|
||||
> chore: prepare Tokio v1.43.1 release</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/c1c8d1033d637d7027fdc137ec8008c5801cbc0d"><code>c1c8d10</code></a>
|
||||
> Merge remote-tracking branch 'origin/tokio-1.38.x' into
|
||||
> forward-port-1.38.x</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/aa303bc2051f7c21b48bb7bfcafe8fd4f39afd21"><code>aa303bc</code></a>
|
||||
> chore: prepare Tokio v1.38.2 release</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/7b6ccb515ff067151ed62db835f735e5653f8784"><code>7b6ccb5</code></a>
|
||||
> chore: backport CI fixes</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/4b174ce2c95fe1d1a217917db93fcc935e17e0da"><code>4b174ce</code></a>
|
||||
> sync: fix cloning value when receiving from broadcast channel</li>
|
||||
> <li>See full diff in <a
|
||||
> href="https://github.com/tokio-rs/tokio/compare/tokio-1.44.1...tokio-1.44.2">compare
|
||||
> view</a></li>
|
||||
> </ul>
|
||||
> </details>
|
||||
> <br />
|
||||
>
|
||||
>
|
||||
> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
|
||||
>
|
||||
> Dependabot will resolve any conflicts with this PR as long as you don't
|
||||
> alter it yourself. You can also trigger a rebase manually by commenting
|
||||
> `@dependabot rebase`.
|
||||
>
|
||||
> [//]:# (dependabot-automerge-start)
|
||||
>
|
||||
> [//]:# (dependabot-automerge-end)
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> <details>
|
||||
> <summary>Dependabot commands and options</summary>
|
||||
> <br />
|
||||
>
|
||||
> You can trigger Dependabot actions by commenting on this PR:
|
||||
> - `@dependabot rebase` will rebase this PR
|
||||
> - `@dependabot recreate` will recreate this PR, overwriting any edits
|
||||
> that have been made to it
|
||||
> - `@dependabot merge` will merge this PR after your CI passes on it
|
||||
> - `@dependabot squash and merge` will squash and merge this PR after
|
||||
> your CI passes on it
|
||||
> - `@dependabot cancel merge` will cancel a previously requested merge
|
||||
> and block automerging
|
||||
> - `@dependabot reopen` will reopen this PR if it is closed
|
||||
> - `@dependabot close` will close this PR and stop Dependabot recreating
|
||||
> it. You can achieve the same result by closing it manually
|
||||
> - `@dependabot show <dependency name> ignore conditions` will show all
|
||||
> of the ignore conditions of the specified dependency
|
||||
> - `@dependabot ignore this major version` will close this PR and stop
|
||||
> Dependabot creating any more for this major version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this minor version` will close this PR and stop
|
||||
> Dependabot creating any more for this minor version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this dependency` will close this PR and stop
|
||||
> Dependabot creating any more for this dependency (unless you reopen the
|
||||
> PR or upgrade to it yourself)
|
||||
>
|
||||
>
|
||||
> </details>
|
||||
|
||||
- [352021b](https://github.com/ratatui/ratatui/commit/352021bc6f802762a7b82fcfbdfd9d248b412e1b) *(deps)* Bump tokio from 1.43.0 to 1.44.1 by @dependabot[bot] in [#1723](https://github.com/ratatui/ratatui/pull/1723)
|
||||
|
||||
> Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.43.0 to 1.44.1.
|
||||
> <details>
|
||||
> <summary>Release notes</summary>
|
||||
> <p><em>Sourced from <a
|
||||
> href="https://github.com/tokio-rs/tokio/releases">tokio's
|
||||
> releases</a>.</em></p>
|
||||
> <blockquote>
|
||||
> <h2>Tokio v1.44.1</h2>
|
||||
> <h1>1.44.1 (March 13th, 2025)</h1>
|
||||
> <h3>Fixed</h3>
|
||||
> <ul>
|
||||
> <li>rt: skip defer queue in <code>block_in_place</code> context (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7216">#7216</a>)</li>
|
||||
> </ul>
|
||||
> <p><a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7216">#7216</a>:
|
||||
> <a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/pull/7216">tokio-rs/tokio#7216</a></p>
|
||||
> <h2>Tokio v1.44.0</h2>
|
||||
> <h1>1.44.0 (March 7th, 2025)</h1>
|
||||
> <p>This release changes the <code>from_std</code> method on sockets to
|
||||
> panic if a blocking socket is provided. We determined this change is not
|
||||
> a breaking change as Tokio is not intended to operate using blocking
|
||||
> sockets. Doing so results in runtime hangs and should be considered a
|
||||
> bug. Accidentally passing a blocking socket to Tokio is one of the most
|
||||
> common user mistakes. If this change causes an issue for you, please
|
||||
> comment on <a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7172">#7172</a>.</p>
|
||||
> <h3>Added</h3>
|
||||
> <ul>
|
||||
> <li>coop: add <code>task::coop</code> module (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7116">#7116</a>)</li>
|
||||
> <li>process: add <code>Command::get_kill_on_drop()</code> (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7086">#7086</a>)</li>
|
||||
> <li>sync: add <code>broadcast::Sender::closed</code> (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/6685">#6685</a>,
|
||||
> <a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7090">#7090</a>)</li>
|
||||
> <li>sync: add <code>broadcast::WeakSender</code> (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7100">#7100</a>)</li>
|
||||
> <li>sync: add <code>oneshot::Receiver::is_empty()</code> (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7153">#7153</a>)</li>
|
||||
> <li>sync: add <code>oneshot::Receiver::is_terminated()</code> (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7152">#7152</a>)</li>
|
||||
> </ul>
|
||||
> <h3>Fixed</h3>
|
||||
> <ul>
|
||||
> <li>fs: empty reads on <code>File</code> should not start a background
|
||||
> read (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7139">#7139</a>)</li>
|
||||
> <li>process: calling <code>start_kill</code> on exited child should not
|
||||
> fail (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7160">#7160</a>)</li>
|
||||
> <li>signal: fix <code>CTRL_CLOSE</code>, <code>CTRL_LOGOFF</code>,
|
||||
> <code>CTRL_SHUTDOWN</code> on windows (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7122">#7122</a>)</li>
|
||||
> <li>sync: properly handle panic during mpsc drop (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7094">#7094</a>)</li>
|
||||
> </ul>
|
||||
> <h3>Changes</h3>
|
||||
> <ul>
|
||||
> <li>runtime: clean up magic number in registration set (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7112">#7112</a>)</li>
|
||||
> <li>coop: make coop yield using waker defer strategy (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7185">#7185</a>)</li>
|
||||
> <li>macros: make <code>select!</code> budget-aware (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7164">#7164</a>)</li>
|
||||
> <li>net: panic when passing a blocking socket to <code>from_std</code>
|
||||
> (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7166">#7166</a>)</li>
|
||||
> <li>io: clean up buffer casts (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7142">#7142</a>)</li>
|
||||
> </ul>
|
||||
> <h3>Changes to unstable APIs</h3>
|
||||
> <ul>
|
||||
> <li>rt: add before and after task poll callbacks (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7120">#7120</a>)</li>
|
||||
> <li>tracing: make the task tracing API unstable public (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/6972">#6972</a>)</li>
|
||||
> </ul>
|
||||
> <h3>Documented</h3>
|
||||
> <ul>
|
||||
> <li>docs: fix nesting of sections in top-level docs (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7159">#7159</a>)</li>
|
||||
> <li>fs: rename symlink and hardlink parameter names (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7143">#7143</a>)</li>
|
||||
> <li>io: swap reader/writer in simplex doc test (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7176">#7176</a>)</li>
|
||||
> <li>macros: docs about <code>select!</code> alternatives (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7110">#7110</a>)</li>
|
||||
> <li>net: rename the argument for <code>send_to</code> (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7146">#7146</a>)</li>
|
||||
> </ul>
|
||||
> <!-- raw HTML omitted -->
|
||||
> </blockquote>
|
||||
> <p>... (truncated)</p>
|
||||
> </details>
|
||||
> <details>
|
||||
> <summary>Commits</summary>
|
||||
> <ul>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/d413c9c02af8f2b4fea14b769b86484b12f46595"><code>d413c9c</code></a>
|
||||
> chore: prepare Tokio v1.44.1 (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7217">#7217</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/addbfb9204be25a8621feb3f20b44a7c1f00edbd"><code>addbfb9</code></a>
|
||||
> rt: skip defer queue in <code>block_in_place</code> context (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7216">#7216</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/8182ecf2628d5e80dac52b8ed1ea466dbb0925b9"><code>8182ecf</code></a>
|
||||
> chore: prepare Tokio v1.44.0 (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7202">#7202</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/a258bff7018940b438e5de3fb846588454df4e4d"><code>a258bff</code></a>
|
||||
> ci: enable printing in multi thread loom tests (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7200">#7200</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/e076d21f679a35ae2697165d46d111285d09e3b4"><code>e076d21</code></a>
|
||||
> process: clarify <code>Child::kill</code> behavior (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7162">#7162</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/042433cdccdf0dd33408c1751a80ddd50a077872"><code>042433c</code></a>
|
||||
> net: debug_assert on creating a tokio socket from a blocking one (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7166">#7166</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/0284d1b5c8ea5aff5b30c254200fb0a46c21d67c"><code>0284d1b</code></a>
|
||||
> macros: make <code>select!</code> budget-aware (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7164">#7164</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/710bc8071ea030f0ad98817414997beab2420ad2"><code>710bc80</code></a>
|
||||
> rt: coop should yield using waker defer strategy (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7185">#7185</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/a2b12bd5799f06e912b32ac05a5ffb5cf1fe31cd"><code>a2b12bd</code></a>
|
||||
> readme: adjust release schedule to once per month (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7191">#7191</a>)</li>
|
||||
> <li><a
|
||||
> href="https://github.com/tokio-rs/tokio/commit/e7b593cbee9541500cef047f3a0ee70be1c55c6f"><code>e7b593c</code></a>
|
||||
> process: fix grammar of the <code>ChildStdin</code> struct doc comment
|
||||
> (<a
|
||||
> href="https://redirect.github.com/tokio-rs/tokio/issues/7192">#7192</a>)</li>
|
||||
> <li>Additional commits viewable in <a
|
||||
> href="https://github.com/tokio-rs/tokio/compare/tokio-1.43.0...tokio-1.44.1">compare
|
||||
> view</a></li>
|
||||
> </ul>
|
||||
> </details>
|
||||
> <br />
|
||||
>
|
||||
>
|
||||
> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
|
||||
>
|
||||
> Dependabot will resolve any conflicts with this PR as long as you don't
|
||||
> alter it yourself. You can also trigger a rebase manually by commenting
|
||||
> `@dependabot rebase`.
|
||||
>
|
||||
> [//]:# (dependabot-automerge-start)
|
||||
>
|
||||
> [//]:# (dependabot-automerge-end)
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> <details>
|
||||
> <summary>Dependabot commands and options</summary>
|
||||
> <br />
|
||||
>
|
||||
> You can trigger Dependabot actions by commenting on this PR:
|
||||
> - `@dependabot rebase` will rebase this PR
|
||||
> - `@dependabot recreate` will recreate this PR, overwriting any edits
|
||||
> that have been made to it
|
||||
> - `@dependabot merge` will merge this PR after your CI passes on it
|
||||
> - `@dependabot squash and merge` will squash and merge this PR after
|
||||
> your CI passes on it
|
||||
> - `@dependabot cancel merge` will cancel a previously requested merge
|
||||
> and block automerging
|
||||
> - `@dependabot reopen` will reopen this PR if it is closed
|
||||
> - `@dependabot close` will close this PR and stop Dependabot recreating
|
||||
> it. You can achieve the same result by closing it manually
|
||||
> - `@dependabot show <dependency name> ignore conditions` will show all
|
||||
> of the ignore conditions of the specified dependency
|
||||
> - `@dependabot ignore this major version` will close this PR and stop
|
||||
> Dependabot creating any more for this major version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this minor version` will close this PR and stop
|
||||
> Dependabot creating any more for this minor version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this dependency` will close this PR and stop
|
||||
> Dependabot creating any more for this dependency (unless you reopen the
|
||||
> PR or upgrade to it yourself)
|
||||
>
|
||||
>
|
||||
> </details>
|
||||
|
||||
- [83774ee](https://github.com/ratatui/ratatui/commit/83774eecf008e95d8e1c86c92ef6c46090bd6c6e) *(deps)* Bump time from 0.3.37 to 0.3.39 by @dependabot[bot] in [#1708](https://github.com/ratatui/ratatui/pull/1708)
|
||||
|
||||
> Bumps [time](https://github.com/time-rs/time) from 0.3.37 to 0.3.39.
|
||||
> <details>
|
||||
> <summary>Release notes</summary>
|
||||
> <p><em>Sourced from <a
|
||||
> href="https://github.com/time-rs/time/releases">time's
|
||||
> releases</a>.</em></p>
|
||||
> <blockquote>
|
||||
> <h2>v0.3.39</h2>
|
||||
> <p>See the <a
|
||||
> href="https://github.com/time-rs/time/blob/main/CHANGELOG.md">changelog</a>
|
||||
> for details.</p>
|
||||
> <h2>v0.3.38</h2>
|
||||
> <p>See the <a
|
||||
> href="https://github.com/time-rs/time/blob/main/CHANGELOG.md">changelog</a>
|
||||
> for details.</p>
|
||||
> </blockquote>
|
||||
> </details>
|
||||
> <details>
|
||||
> <summary>Changelog</summary>
|
||||
> <p><em>Sourced from <a
|
||||
> href="https://github.com/time-rs/time/blob/main/CHANGELOG.md">time's
|
||||
> changelog</a>.</em></p>
|
||||
> <blockquote>
|
||||
> <h2>0.3.39 [2025-03-06]</h2>
|
||||
> <h3>Fixed</h3>
|
||||
> <ul>
|
||||
> <li>Doc tests run successfully with the default feature set.</li>
|
||||
> <li>wasm builds work again.</li>
|
||||
> </ul>
|
||||
> <p>Both of these were regressions in v0.3.38 and are now checked in
|
||||
> CI.</p>
|
||||
> <h2>0.3.38 [2025-03-05]</h2>
|
||||
> <h3>Added</h3>
|
||||
> <ul>
|
||||
> <li>
|
||||
> <p>The <code>[year]</code> component (in format descriptions) now
|
||||
> supports a <code>range</code> modifier, which can be
|
||||
> either <code>standard</code> or <code>extended</code>. The default is
|
||||
> <code>extended</code> for backwards compatibility. This is
|
||||
> intended as a manner to opt <em>out</em> of the extended range when the
|
||||
> <code>large-dates</code> feature is enabled.
|
||||
> When the <code>large-dates</code> feature is not enabled, the modifier
|
||||
> has no effect.</p>
|
||||
> </li>
|
||||
> <li>
|
||||
> <p><code>UtcDateTime</code>, which is semantically equivalent to an
|
||||
> <code>OffsetDateTime</code> with UTC as its offset. The
|
||||
> advantage is that it is the same size as a
|
||||
> <code>PrimitiveDateTime</code> and has improved operability with
|
||||
> well-known formats.</p>
|
||||
> <p>As part of this, there were some other additions:</p>
|
||||
> <ul>
|
||||
> <li><code>utc_datetime!</code> macro, which is similar to the
|
||||
> <code>datetime!</code> macro but constructs a
|
||||
> <code>UtcDateTime</code>.</li>
|
||||
> <li><code>PrimitiveDateTime::as_utc</code></li>
|
||||
> <li><code>OffsetDateTime::to_utc</code></li>
|
||||
> <li><code>OffsetDateTime::checked_to_utc</code></li>
|
||||
> </ul>
|
||||
> </li>
|
||||
> <li>
|
||||
> <p><code>time::serde::timestamp::milliseconds_i64</code>, which is a
|
||||
> module to serialize/deserialize timestamps
|
||||
> as the Unix timestamp. The pre-existing module does this as an
|
||||
> <code>i128</code> where an <code>i64</code> would
|
||||
> suffice. This new module should be preferred.</p>
|
||||
> </li>
|
||||
> </ul>
|
||||
> <h3>Changed</h3>
|
||||
> <ul>
|
||||
> <li><code>error::Format</code> has had its <code>source()</code>
|
||||
> implementation changed to no longer return a boxed value
|
||||
> from the <code>ComponentRange</code> variant. If you were explicitly
|
||||
> expecting this, you will need to update
|
||||
> your code. The method API remains unchanged.</li>
|
||||
> <li><code>[year repr:century]</code> supports single-digit values.</li>
|
||||
> <li>All <code>format_into</code> methods accept <code>?Sized</code>
|
||||
> references.</li>
|
||||
> </ul>
|
||||
> <h3>Miscellaneous</h3>
|
||||
> <ul>
|
||||
> <li>Some non-exhaustive enum variants that are no longer used have been
|
||||
> modified to be statically
|
||||
> proven as uninhabited. The relevant fields are doc-hidden and not
|
||||
> semver-guaranteed to remain as
|
||||
> such, though it is unlikely to change.</li>
|
||||
> <li>An unnecessary check when parsing RFC 2822 has been removed.</li>
|
||||
> <li>Various methods have had their implementations changed, resulting in
|
||||
> significant performance
|
||||
> gains. Among the methods changed are
|
||||
> <ul>
|
||||
> <li><code>util::is_leap_year</code></li>
|
||||
> <li><code>util::weeks_in_year</code></li>
|
||||
> <li><code>Month::length</code></li>
|
||||
> <li><code>Date::to_calendar_date</code></li>
|
||||
> </ul>
|
||||
> </li>
|
||||
> </ul>
|
||||
> <!-- raw HTML omitted -->
|
||||
> </blockquote>
|
||||
> <p>... (truncated)</p>
|
||||
> </details>
|
||||
> <details>
|
||||
> <summary>Commits</summary>
|
||||
> <ul>
|
||||
> <li><a
|
||||
> href="https://github.com/time-rs/time/commit/7949d2c2e8ee441d39d4acf4b9653739727b6b8b"><code>7949d2c</code></a>
|
||||
> v0.3.39 release</li>
|
||||
> <li><a
|
||||
> href="https://github.com/time-rs/time/commit/f51623b653f31579e230998b23f2e659b6fef46e"><code>f51623b</code></a>
|
||||
> Fix breakage from v0.3.38</li>
|
||||
> <li><a
|
||||
> href="https://github.com/time-rs/time/commit/1a31c0595b232df9f397f4e5e6768c7eb4220c27"><code>1a31c05</code></a>
|
||||
> v0.3.38 release</li>
|
||||
> <li><a
|
||||
> href="https://github.com/time-rs/time/commit/addf231ef500789dae31e715795d8a33cb658bbf"><code>addf231</code></a>
|
||||
> Permit unsized writers for <code>format_into</code></li>
|
||||
> <li><a
|
||||
> href="https://github.com/time-rs/time/commit/338f84f54522441ff3e7d0f5c5877314f0725608"><code>338f84f</code></a>
|
||||
> Allow clippy::ref_option lint for serde::format_description.</li>
|
||||
> <li><a
|
||||
> href="https://github.com/time-rs/time/commit/f8ecd81e8f14e14f5456eef49e7184f365b56949"><code>f8ecd81</code></a>
|
||||
> feat: timestamp::milliseconds_i64 serializer</li>
|
||||
> <li><a
|
||||
> href="https://github.com/time-rs/time/commit/ce03bcab8fc0aaaa6e9a27ad335abf8fab7eeec1"><code>ce03bca</code></a>
|
||||
> Update Unicode license for cargo-audit</li>
|
||||
> <li><a
|
||||
> href="https://github.com/time-rs/time/commit/3d0b981381de87d2be924129af3fdb9626bfe049"><code>3d0b981</code></a>
|
||||
> Add parentheses for clarity</li>
|
||||
> <li><a
|
||||
> href="https://github.com/time-rs/time/commit/3096301eb3fb5f5104d4182d35184551ce064d5a"><code>3096301</code></a>
|
||||
> Remove specific year from license</li>
|
||||
> <li><a
|
||||
> href="https://github.com/time-rs/time/commit/ec327a26dbe8f3e3db612e8c82b9e35a39361f1c"><code>ec327a2</code></a>
|
||||
> Optimize Julian day calculations</li>
|
||||
> <li>Additional commits viewable in <a
|
||||
> href="https://github.com/time-rs/time/compare/v0.3.37...v0.3.39">compare
|
||||
> view</a></li>
|
||||
> </ul>
|
||||
> </details>
|
||||
> <br />
|
||||
>
|
||||
>
|
||||
> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
|
||||
>
|
||||
> Dependabot will resolve any conflicts with this PR as long as you don't
|
||||
> alter it yourself. You can also trigger a rebase manually by commenting
|
||||
> `@dependabot rebase`.
|
||||
>
|
||||
> [//]:# (dependabot-automerge-start)
|
||||
>
|
||||
> [//]:# (dependabot-automerge-end)
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> <details>
|
||||
> <summary>Dependabot commands and options</summary>
|
||||
> <br />
|
||||
>
|
||||
> You can trigger Dependabot actions by commenting on this PR:
|
||||
> - `@dependabot rebase` will rebase this PR
|
||||
> - `@dependabot recreate` will recreate this PR, overwriting any edits
|
||||
> that have been made to it
|
||||
> - `@dependabot merge` will merge this PR after your CI passes on it
|
||||
> - `@dependabot squash and merge` will squash and merge this PR after
|
||||
> your CI passes on it
|
||||
> - `@dependabot cancel merge` will cancel a previously requested merge
|
||||
> and block automerging
|
||||
> - `@dependabot reopen` will reopen this PR if it is closed
|
||||
> - `@dependabot close` will close this PR and stop Dependabot recreating
|
||||
> it. You can achieve the same result by closing it manually
|
||||
> - `@dependabot show <dependency name> ignore conditions` will show all
|
||||
> of the ignore conditions of the specified dependency
|
||||
> - `@dependabot ignore this major version` will close this PR and stop
|
||||
> Dependabot creating any more for this major version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this minor version` will close this PR and stop
|
||||
> Dependabot creating any more for this minor version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this dependency` will close this PR and stop
|
||||
> Dependabot creating any more for this dependency (unless you reopen the
|
||||
> PR or upgrade to it yourself)
|
||||
>
|
||||
>
|
||||
> </details>
|
||||
|
||||
- [5710b7a](https://github.com/ratatui/ratatui/commit/5710b7a8d9630eb36fe3d87b748bf5cf2c7b1ec3) *(deps)* Bump rstest from 0.24.0 to 0.25.0 by @dependabot[bot] in [#1695](https://github.com/ratatui/ratatui/pull/1695)
|
||||
|
||||
> Bumps [rstest](https://github.com/la10736/rstest) from 0.24.0 to 0.25.0.
|
||||
> <details>
|
||||
> <summary>Release notes</summary>
|
||||
> <p><em>Sourced from <a
|
||||
> href="https://github.com/la10736/rstest/releases">rstest's
|
||||
> releases</a>.</em></p>
|
||||
> <blockquote>
|
||||
> <h2>0.25.0</h2>
|
||||
> <h2>What's Changed</h2>
|
||||
> <ul>
|
||||
> <li>Append generated test macro so next test macros are aware of it by
|
||||
> <a href="https://github.com/kezhuw"><code>@kezhuw</code></a> in <a
|
||||
> href="https://redirect.github.com/la10736/rstest/pull/291">la10736/rstest#291</a></li>
|
||||
> <li>feat: add <code>include_str</code> and <code>include_bytes</code>
|
||||
> file input behaviour by <a
|
||||
> href="https://github.com/lucascool12"><code>@lucascool12</code></a> in
|
||||
> <a
|
||||
> href="https://redirect.github.com/la10736/rstest/pull/297">la10736/rstest#297</a></li>
|
||||
> </ul>
|
||||
> <h2>New Contributors</h2>
|
||||
> <ul>
|
||||
> <li><a href="https://github.com/kezhuw"><code>@kezhuw</code></a> made
|
||||
> their first contribution in <a
|
||||
> href="https://redirect.github.com/la10736/rstest/pull/291">la10736/rstest#291</a></li>
|
||||
> <li><a
|
||||
> href="https://github.com/lucascool12"><code>@lucascool12</code></a>
|
||||
> made their first contribution in <a
|
||||
> href="https://redirect.github.com/la10736/rstest/pull/297">la10736/rstest#297</a></li>
|
||||
> </ul>
|
||||
> <p><strong>Full Changelog</strong>: <a
|
||||
> href="https://github.com/la10736/rstest/compare/v0.24.0...v0.25.0">https://github.com/la10736/rstest/compare/v0.24.0...v0.25.0</a></p>
|
||||
> </blockquote>
|
||||
> </details>
|
||||
> <details>
|
||||
> <summary>Changelog</summary>
|
||||
> <p><em>Sourced from <a
|
||||
> href="https://github.com/la10736/rstest/blob/master/CHANGELOG.md">rstest's
|
||||
> changelog</a>.</em></p>
|
||||
> <blockquote>
|
||||
> <h2>[0.25.0] 2025/3/2</h2>
|
||||
> <h3>Changed</h3>
|
||||
> <ul>
|
||||
> <li>Append generated test macro so next test macros are aware of it
|
||||
> (see <a
|
||||
> href="https://redirect.github.com/la10736/rstest/pull/291">#291</a>
|
||||
> thanks to <a
|
||||
> href="https://github.com/kezhuw"><code>@kezhuw</code></a>).</li>
|
||||
> </ul>
|
||||
> <h3>Add</h3>
|
||||
> <ul>
|
||||
> <li>Added a <code>#[mode = ...]</code> attribute to be used with the
|
||||
> <code>#[files(...)]</code> attribute to change the way
|
||||
> the files get passed to the test.
|
||||
> (see <a
|
||||
> href="https://redirect.github.com/la10736/rstest/issues/295">#295</a>
|
||||
> thanks to <a
|
||||
> href="https://github.com/lucascool12"><code>@lucascool12</code></a>)</li>
|
||||
> </ul>
|
||||
> </blockquote>
|
||||
> </details>
|
||||
> <details>
|
||||
> <summary>Commits</summary>
|
||||
> <ul>
|
||||
> <li><a
|
||||
> href="https://github.com/la10736/rstest/commit/8d80cea3896753c8674ad138ab620e8b17b923d4"><code>8d80cea</code></a>
|
||||
> Prepare release</li>
|
||||
> <li><a
|
||||
> href="https://github.com/la10736/rstest/commit/eb1f228eacde50d589d95509417a2ccb4f8d0c28"><code>eb1f228</code></a>
|
||||
> Make clippy happy</li>
|
||||
> <li><a
|
||||
> href="https://github.com/la10736/rstest/commit/f570b0605b192990f6723a6fc3300585a86b45d1"><code>f570b06</code></a>
|
||||
> Avoid concurrent manifest changes in integration tests</li>
|
||||
> <li><a
|
||||
> href="https://github.com/la10736/rstest/commit/8551eb8e29870d466b0b95a7a64fc676e476aba4"><code>8551eb8</code></a>
|
||||
> feat: add <code>include_str</code> and <code>include_bytes</code> file
|
||||
> input behaviour</li>
|
||||
> <li><a
|
||||
> href="https://github.com/la10736/rstest/commit/e0b735e9c2ee96b55cd3e1c1073235169698ca0b"><code>e0b735e</code></a>
|
||||
> Append generated test macro so next test macros are aware of it</li>
|
||||
> <li><a
|
||||
> href="https://github.com/la10736/rstest/commit/154d0b0d3f4766cf4107441ce26936711559fc75"><code>154d0b0</code></a>
|
||||
> Prepare develop</li>
|
||||
> <li>See full diff in <a
|
||||
> href="https://github.com/la10736/rstest/compare/v0.24.0...v0.25.0">compare
|
||||
> view</a></li>
|
||||
> </ul>
|
||||
> </details>
|
||||
> <br />
|
||||
>
|
||||
>
|
||||
> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
|
||||
>
|
||||
> Dependabot will resolve any conflicts with this PR as long as you don't
|
||||
> alter it yourself. You can also trigger a rebase manually by commenting
|
||||
> `@dependabot rebase`.
|
||||
>
|
||||
> [//]:# (dependabot-automerge-start)
|
||||
>
|
||||
> [//]:# (dependabot-automerge-end)
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> <details>
|
||||
> <summary>Dependabot commands and options</summary>
|
||||
> <br />
|
||||
>
|
||||
> You can trigger Dependabot actions by commenting on this PR:
|
||||
> - `@dependabot rebase` will rebase this PR
|
||||
> - `@dependabot recreate` will recreate this PR, overwriting any edits
|
||||
> that have been made to it
|
||||
> - `@dependabot merge` will merge this PR after your CI passes on it
|
||||
> - `@dependabot squash and merge` will squash and merge this PR after
|
||||
> your CI passes on it
|
||||
> - `@dependabot cancel merge` will cancel a previously requested merge
|
||||
> and block automerging
|
||||
> - `@dependabot reopen` will reopen this PR if it is closed
|
||||
> - `@dependabot close` will close this PR and stop Dependabot recreating
|
||||
> it. You can achieve the same result by closing it manually
|
||||
> - `@dependabot show <dependency name> ignore conditions` will show all
|
||||
> of the ignore conditions of the specified dependency
|
||||
> - `@dependabot ignore this major version` will close this PR and stop
|
||||
> Dependabot creating any more for this major version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this minor version` will close this PR and stop
|
||||
> Dependabot creating any more for this minor version (unless you reopen
|
||||
> the PR or upgrade to it yourself)
|
||||
> - `@dependabot ignore this dependency` will close this PR and stop
|
||||
> Dependabot creating any more for this dependency (unless you reopen the
|
||||
> PR or upgrade to it yourself)
|
||||
>
|
||||
>
|
||||
> </details>
|
||||
|
||||
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/ratatui/ratatui/compare/ratatui-v0.30.0-alpha.2...ratatui-v0.30.0-alpha.3
|
||||
|
||||
|
||||
|
||||
## ratatui-termion - [0.1.0-alpha.3](https://github.com/ratatui/ratatui/compare/ratatui-termion-v0.1.0-alpha.2...ratatui-termion-v0.1.0-alpha.3) - 2025-05-13
|
||||
|
||||
### Features
|
||||
@@ -2310,17 +1405,8 @@ All notable changes to this project will be documented in this file.
|
||||
>
|
||||
> BREAKING CHANGE:MSRV is now 1.81
|
||||
|
||||
### Build
|
||||
|
||||
- [daeba85](https://github.com/ratatui/ratatui/commit/daeba85f144ead00803c7540fa39ff6d623321c7) *(deps)* Bump `kasuari` and `line-clipping` by @j-g00da in [#1844](https://github.com/ratatui/ratatui/pull/1844)
|
||||
|
||||
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/ratatui/ratatui/compare/ratatui-widgets-v0.3.0-alpha.2...ratatui-widgets-v0.3.0-alpha.3
|
||||
|
||||
|
||||
|
||||
## ratatui-crossterm - [0.1.0-alpha.3](https://github.com/ratatui/ratatui/compare/ratatui-crossterm-v0.1.0-alpha.2...ratatui-crossterm-v0.1.0-alpha.3) - 2025-05-13
|
||||
|
||||
### Features
|
||||
@@ -2605,29 +1691,8 @@ All notable changes to this project will be documented in this file.
|
||||
>
|
||||
> Fixes:https://github.com/ratatui/ratatui/issues/1712
|
||||
|
||||
### Build
|
||||
|
||||
- [daeba85](https://github.com/ratatui/ratatui/commit/daeba85f144ead00803c7540fa39ff6d623321c7) *(deps)* Bump `kasuari` and `line-clipping` by @j-g00da in [#1844](https://github.com/ratatui/ratatui/pull/1844)
|
||||
|
||||
- [fc4b996](https://github.com/ratatui/ratatui/commit/fc4b996c596aec8316427bc71677fbfcce68caed) *(deps)* Update compact_str requirement from 0.8.1 to 0.9.0 by @musicinmybrain in [#1783](https://github.com/ratatui/ratatui/pull/1783)
|
||||
|
||||
> Looking at
|
||||
> https://github.com/ParkMyCar/compact_str/blob/v0.9.0/CHANGELOG.md#090,
|
||||
> there are a few API changes, but it doesn’t seem like anything there
|
||||
> should be a problem given that `cargo test` still passes in
|
||||
> `ratatui-core/`.
|
||||
|
||||
- [3d5b250](https://github.com/ratatui/ratatui/commit/3d5b250e74fc83fb580f50b617472be7cfb4fd4b) *(deps)* Use kasuari instead of cassowary by @joshka in [#1758](https://github.com/ratatui/ratatui/pull/1758)
|
||||
|
||||
> [Kasuari](https://github.com/ratatui/kasuari) is a maintained fork of Cassowary.
|
||||
|
||||
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/ratatui/ratatui/compare/ratatui-core-v0.1.0-alpha.3...ratatui-core-v0.1.0-alpha.4
|
||||
|
||||
|
||||
|
||||
## ratatui - [0.30.0-alpha.2](https://github.com/ratatui/ratatui/compare/ratatui-v0.30.0-alpha.1...ratatui-v0.30.0-alpha.2) - 2025-03-01
|
||||
|
||||
### Features
|
||||
|
||||
@@ -6,6 +6,23 @@ 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),
|
||||
@@ -29,6 +46,26 @@ change becomes a place where a bug may have been introduced. Consider splitting
|
||||
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
|
||||
@@ -41,6 +78,10 @@ to be installed when running rustfmt. You can install the nightly version of Rus
|
||||
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.
|
||||
|
||||
### Search `tui-rs` for similar work
|
||||
|
||||
The original fork of Ratatui, [`tui-rs`](https://github.com/fdehau/tui-rs/), has a large amount of
|
||||
@@ -67,6 +108,15 @@ Running `cargo xtask ci` before pushing will perform the same checks that we do
|
||||
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
|
||||
@@ -74,6 +124,25 @@ 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
|
||||
@@ -97,7 +166,13 @@ For an understanding of the crate organization and design decisions, see [ARCHIT
|
||||
document explains the modular workspace structure introduced in version 0.30.0 and provides
|
||||
guidance on which crate to use for different use cases.
|
||||
|
||||
[ARCHITECTURE.md]: ./ARCHITECTURE.md
|
||||
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
|
||||
|
||||
@@ -106,7 +181,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 [tests/widgets_block.rs](./tests/widget_block.rs) for an example.
|
||||
See `widgets_block_renders` in [ratatui/tests/widgets_block.rs](./ratatui/tests/widgets_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.
|
||||
@@ -115,6 +190,10 @@ 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
|
||||
@@ -182,6 +261,14 @@ 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
|
||||
|
||||
846
Cargo.lock
generated
846
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
27
Cargo.toml
@@ -30,32 +30,34 @@ rust-version = "1.85.0"
|
||||
[workspace.dependencies]
|
||||
anstyle = "1"
|
||||
bitflags = "2.9"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
color-eyre = "0.6"
|
||||
compact_str = { version = "0.9", default-features = false }
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
criterion = { version = "0.7", features = ["html_reports"] }
|
||||
crossterm = "0.29"
|
||||
document-features = "0.2"
|
||||
fakeit = "1"
|
||||
futures = "0.3"
|
||||
hashbrown = "0.15"
|
||||
hashbrown = "0.16"
|
||||
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.14"
|
||||
lru = "0.16"
|
||||
octocrab = "0.44"
|
||||
palette = "0.7"
|
||||
pretty_assertions = "1"
|
||||
rand = "0.9"
|
||||
rand_chacha = "0.9"
|
||||
ratatui = { path = "ratatui", version = "0.30.0-alpha.5" }
|
||||
ratatui-core = { path = "ratatui-core", version = "0.1.0-alpha.6" }
|
||||
ratatui-crossterm = { path = "ratatui-crossterm", version = "0.1.0-alpha.5" }
|
||||
ratatui-macros = { path = "ratatui-macros", version = "0.7.0-alpha.4" }
|
||||
ratatui-termion = { path = "ratatui-termion", version = "0.1.0-alpha.5" }
|
||||
ratatui-termwiz = { path = "ratatui-termwiz", version = "0.1.0-alpha.5" }
|
||||
ratatui-widgets = { path = "ratatui-widgets", version = "0.3.0-alpha.5" }
|
||||
rstest = "0.25"
|
||||
ratatui = { path = "ratatui", version = "0.30.0-beta.0" }
|
||||
ratatui-core = { path = "ratatui-core", version = "0.1.0-beta.0" }
|
||||
ratatui-crossterm = { path = "ratatui-crossterm", version = "0.1.0-beta.0" }
|
||||
ratatui-macros = { path = "ratatui-macros", version = "0.7.0-beta.0" }
|
||||
ratatui-termion = { path = "ratatui-termion", version = "0.1.0-beta.0" }
|
||||
ratatui-termwiz = { path = "ratatui-termwiz", version = "0.1.0-beta.0" }
|
||||
ratatui-widgets = { path = "ratatui-widgets", version = "0.3.0-beta.0" }
|
||||
rstest = "0.26"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
strum = { version = "0.27", default-features = false, features = ["derive"] }
|
||||
@@ -64,6 +66,7 @@ termwiz = "0.23"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
time = { version = "0.3", default-features = false }
|
||||
tokio = "1"
|
||||
tokio-stream = "0.1"
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.2"
|
||||
tracing-subscriber = "0.3"
|
||||
@@ -71,7 +74,7 @@ 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"
|
||||
unicode-width = ">=0.2.0, <=0.2.1"
|
||||
|
||||
# Improve benchmark consistency
|
||||
[profile.bench]
|
||||
|
||||
10
README.md
10
README.md
@@ -12,7 +12,7 @@
|
||||
|
||||
</details>
|
||||
|
||||

|
||||

|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -113,15 +113,16 @@ There is also a [Matrix](https://matrix.org/) bridge available at
|
||||
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].
|
||||
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/)
|
||||
[](https://ratatui.rs/)
|
||||
```
|
||||
|
||||
[](https://ratatui.rs/)
|
||||
[](https://ratatui.rs/)
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
@@ -152,6 +153,7 @@ This project is licensed under the [MIT License][License].
|
||||
[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
|
||||
[Contributing]: https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md
|
||||
[AI Contributions]: https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#ai-generated-content
|
||||
[Crate]: https://crates.io/crates/ratatui
|
||||
[tui-rs]: https://crates.io/crates/tui
|
||||
[Sponsors]: https://github.com/sponsors/ratatui
|
||||
|
||||
1
assets/logo-simple.svg
Normal file
1
assets/logo-simple.svg
Normal file
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 831 B |
1
assets/logo.svg
Normal file
1
assets/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
18
bacon.toml
18
bacon.toml
@@ -60,6 +60,22 @@ 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/"]
|
||||
|
||||
# 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.)
|
||||
@@ -74,3 +90,5 @@ 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"
|
||||
|
||||
23
cliff.toml
23
cliff.toml
@@ -24,7 +24,11 @@ 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 %}
|
||||
{% endif -%}
|
||||
|
||||
{% macro commit(commit) -%}
|
||||
@@ -59,14 +63,22 @@ body = """
|
||||
|
||||
{% if version %}
|
||||
{% if previous.version %}
|
||||
**Full Changelog**: {{ release_link }}
|
||||
{%- 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
|
||||
@@ -114,8 +126,9 @@ commit_parsers = [
|
||||
{ message = "^chore\\(deps\\)", skip = true },
|
||||
{ message = "^chore\\(changelog\\)", skip = true },
|
||||
{ message = "^[cC]hore", group = "<!-- 07 -->Miscellaneous Tasks" },
|
||||
{ body = ".*security", group = "<!-- 08 -->Security" },
|
||||
{ message = "^build", group = "<!-- 09 -->Build" },
|
||||
{ message = "^build\\(deps\\)", skip = true },
|
||||
{ message = "^build", group = "<!-- 08 -->Build" },
|
||||
{ body = ".*security", group = "<!-- 09 -->Security" },
|
||||
{ message = "^ci", group = "<!-- 10 -->Continuous Integration" },
|
||||
{ message = "^revert", group = "<!-- 11 -->Reverted Commits" },
|
||||
# handle some old commits styles from pre 0.4
|
||||
@@ -130,9 +143,9 @@ filter_commits = false
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
skip_tags = "v0.1.0-rc.1"
|
||||
skip_tags = "beta|alpha|v0.1.0-rc.1"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = "alpha"
|
||||
ignore_tags = "rc"
|
||||
# sort the tags topologically
|
||||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
|
||||
@@ -14,9 +14,9 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
color-eyre = "0.6.5"
|
||||
color-eyre.workspace = true
|
||||
crossterm = { workspace = true, features = ["event-stream"] }
|
||||
octocrab = "0.44.0"
|
||||
octocrab.workspace = true
|
||||
ratatui.workspace = true
|
||||
tokio = { version = "1.45.1", features = ["rt-multi-thread", "macros"] }
|
||||
tokio-stream = "0.1.17"
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
tokio-stream.workspace = true
|
||||
|
||||
@@ -9,7 +9,7 @@ rust-version.workspace = true
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
ratatui.workspace = true
|
||||
time = { version = "0.3.39", features = ["formatting", "parsing"] }
|
||||
time = { workspace = true, features = ["formatting", "parsing"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -8,7 +8,7 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
palette = "0.7.6"
|
||||
palette.workspace = true
|
||||
ratatui.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -12,9 +12,9 @@ termion = ["ratatui/termion", "dep:termion"]
|
||||
termwiz = ["ratatui/termwiz", "dep:termwiz"]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.40", features = ["derive"] }
|
||||
clap.workspace = true
|
||||
crossterm = { workspace = true, optional = true }
|
||||
rand = "0.9.1"
|
||||
rand.workspace = true
|
||||
ratatui.workspace = true
|
||||
termwiz = { workspace = true, optional = true }
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
color-eyre = "0.6.5"
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
indoc.workspace = true
|
||||
itertools.workspace = true
|
||||
palette = "0.7.6"
|
||||
rand = "0.9.1"
|
||||
rand_chacha = "0.9.0"
|
||||
palette.workspace = true
|
||||
rand.workspace = true
|
||||
rand_chacha.workspace = true
|
||||
ratatui = { workspace = true, features = ["all-widgets"] }
|
||||
strum.workspace = true
|
||||
time = "0.3.39"
|
||||
unicode-width = "0.2.0"
|
||||
time.workspace = true
|
||||
unicode-width.workspace = true
|
||||
|
||||
@@ -8,7 +8,7 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
rand = "0.9.1"
|
||||
rand.workspace = true
|
||||
ratatui.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -9,8 +9,8 @@ rust-version.workspace = true
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
## a collection of line drawing algorithms (e.g. Bresenham's line algorithm)
|
||||
line_drawing = "1.0.1"
|
||||
rand = "0.9.1"
|
||||
line_drawing = "1"
|
||||
rand.workspace = true
|
||||
ratatui.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
13
examples/apps/release-header/Cargo.toml
Normal file
13
examples/apps/release-header/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "release-header"
|
||||
publish = false
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
color-eyre.workspace = true
|
||||
ratatui.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
13
examples/apps/release-header/README.md
Normal file
13
examples/apps/release-header/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Release Header
|
||||
|
||||
Generates a banner for Ratatui releases featuring a Ratatui logo, version info, and a list of crates.
|
||||
|
||||
Used for README.md, documentation, and release materials. Updated for every release starting with v0.30.0 "Bryndza".
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
cargo run --p release-header
|
||||
```
|
||||
|
||||
Press any key to exit. Creates a fixed 68x16 terminal viewport.
|
||||
171
examples/apps/release-header/src/main.rs
Normal file
171
examples/apps/release-header/src/main.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
//! Generates a terminal banner for Ratatui releases featuring a Ratatui logo, version info, and
|
||||
//! a list of crates.
|
||||
//!
|
||||
//! Used for README.md, documentation, and release materials. Updated for every release starting
|
||||
//! with v0.30.0 "Bryndza".
|
||||
//!
|
||||
//! 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;
|
||||
use std::iter::zip;
|
||||
|
||||
use ratatui::crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
|
||||
use ratatui::crossterm::{event, execute};
|
||||
use ratatui::layout::{Constraint, Flex, Layout, Margin, Rect, Spacing};
|
||||
use ratatui::style::{Color, Stylize};
|
||||
use ratatui::symbols::merge::MergeStrategy;
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{Block, BorderType, Padding, Paragraph, RatatuiLogo};
|
||||
use ratatui::{DefaultTerminal, Frame, TerminalOptions, Viewport};
|
||||
|
||||
const SEMVER: &str = "0.30.0";
|
||||
const RELEASE_NAME: &str = "Bryndza";
|
||||
|
||||
const MAIN_DISHES: [&str; 4] = [
|
||||
"> ratatui",
|
||||
"> ratatui-core",
|
||||
"> ratatui-widgets",
|
||||
"> ratatui-macros",
|
||||
];
|
||||
const BACKENDS: [&str; 3] = [
|
||||
"> ratatui-crossterm",
|
||||
"> ratatui-termion",
|
||||
"> ratatui-termwiz",
|
||||
];
|
||||
|
||||
const FG_COLOR: Color = Color::Rgb(246, 214, 187); // #F6D6BB
|
||||
const BG_COLOR: Color = Color::Rgb(20, 20, 50); // #141432
|
||||
const MENU_BORDER_COLOR: Color = Color::Rgb(255, 255, 160); // #FFFFA0
|
||||
|
||||
enum Rainbow {
|
||||
Red,
|
||||
Orange,
|
||||
Yellow,
|
||||
Green,
|
||||
Blue,
|
||||
Indigo,
|
||||
Violet,
|
||||
}
|
||||
|
||||
fn main() -> color_eyre::Result<()> {
|
||||
color_eyre::install()?;
|
||||
let viewport = Viewport::Fixed(Rect::new(0, 0, 68, 16));
|
||||
let terminal = ratatui::init_with_options(TerminalOptions { viewport });
|
||||
execute!(stdout(), EnterAlternateScreen).expect("failed to enter alternate screen");
|
||||
let result = run(terminal);
|
||||
execute!(stdout(), LeaveAlternateScreen).expect("failed to leave alternate screen");
|
||||
ratatui::restore();
|
||||
result
|
||||
}
|
||||
|
||||
fn run(mut terminal: DefaultTerminal) -> color_eyre::Result<()> {
|
||||
loop {
|
||||
terminal.draw(render)?;
|
||||
if event::read()?.is_key_press() {
|
||||
break Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(frame: &mut Frame) {
|
||||
let area = frame.area();
|
||||
frame.buffer_mut().set_style(area, (FG_COLOR, BG_COLOR));
|
||||
|
||||
let logo_width = 29;
|
||||
let menu_width = 23;
|
||||
let padding = 2; // Padding between logo and menu
|
||||
let menu_borders = 3;
|
||||
let height = MAIN_DISHES.len() as u16 + BACKENDS.len() as u16 + menu_borders;
|
||||
let width = logo_width + menu_width + padding;
|
||||
let center_area = area.centered(Constraint::Length(width), Constraint::Length(height));
|
||||
let layout = Layout::horizontal(Constraint::from_lengths([logo_width, padding, menu_width]));
|
||||
let [logo_area, _, menu_area] = center_area.layout(&layout);
|
||||
|
||||
render_logo(frame, logo_area);
|
||||
render_menu(frame, menu_area);
|
||||
}
|
||||
|
||||
fn render_logo(frame: &mut Frame, area: Rect) {
|
||||
let area = area.inner(Margin::new(1, 0));
|
||||
let layout = Layout::vertical(Constraint::from_lengths([6, 2, 1])).flex(Flex::End);
|
||||
let [shadow_area, logo_area, version_area] = area.layout(&layout);
|
||||
|
||||
// Divide the logo into letter sections for individual coloring, then render a block for each
|
||||
// letter with a color based on the row index.
|
||||
let letter_layout = Layout::horizontal(Constraint::from_lengths([5, 4, 4, 4, 4, 5, 1]));
|
||||
for (row_index, row) in shadow_area.rows().enumerate() {
|
||||
for (rainbow, letter_area) in zip(Rainbow::ROYGBIV, row.layout_vec(&letter_layout)) {
|
||||
let color = rainbow.gradient_color(row_index);
|
||||
frame.render_widget(Block::new().style(color), letter_area);
|
||||
}
|
||||
// Render the Ratatui logo truncated.
|
||||
frame.render_widget(RatatuiLogo::small(), row);
|
||||
}
|
||||
|
||||
frame.render_widget(Block::new().style(FG_COLOR), logo_area);
|
||||
frame.render_widget(RatatuiLogo::small(), logo_area);
|
||||
frame.render_widget(format!("v{SEMVER} \"{RELEASE_NAME}\"").dim(), version_area);
|
||||
}
|
||||
|
||||
impl Rainbow {
|
||||
const RED_GRADIENT: [u8; 6] = [41, 43, 50, 68, 104, 156];
|
||||
const GREEN_GRADIENT: [u8; 6] = [24, 30, 41, 65, 105, 168];
|
||||
const BLUE_GRADIENT: [u8; 6] = [55, 57, 62, 78, 113, 166];
|
||||
const AMBIENT_GRADIENT: [u8; 6] = [17, 18, 20, 25, 40, 60];
|
||||
|
||||
const ROYGBIV: [Self; 7] = [
|
||||
Self::Red,
|
||||
Self::Orange,
|
||||
Self::Yellow,
|
||||
Self::Green,
|
||||
Self::Blue,
|
||||
Self::Indigo,
|
||||
Self::Violet,
|
||||
];
|
||||
|
||||
fn gradient_color(&self, row: usize) -> Color {
|
||||
let ambient = Self::AMBIENT_GRADIENT[row];
|
||||
let red = Self::RED_GRADIENT[row];
|
||||
let green = Self::GREEN_GRADIENT[row];
|
||||
let blue = Self::BLUE_GRADIENT[row];
|
||||
let blue_sat = Self::AMBIENT_GRADIENT[row].saturating_mul(6 - row as u8);
|
||||
let (r, g, b) = match self {
|
||||
Self::Red => (red, ambient, blue_sat),
|
||||
Self::Orange => (red, green / 2, blue_sat),
|
||||
Self::Yellow => (red, green, blue_sat),
|
||||
Self::Green => (ambient, green, blue_sat),
|
||||
Self::Blue => (ambient, ambient, blue.max(blue_sat)),
|
||||
Self::Indigo => (blue, ambient, blue.max(blue_sat)),
|
||||
Self::Violet => (red, ambient, blue.max(blue_sat)),
|
||||
};
|
||||
Color::Rgb(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
fn render_menu(frame: &mut Frame, area: Rect) {
|
||||
let layout = Layout::vertical(Constraint::from_lengths([
|
||||
MAIN_DISHES.len() as u16 + 2,
|
||||
BACKENDS.len() as u16 + 2,
|
||||
]))
|
||||
.spacing(Spacing::Overlap(1)); // Overlap to merge borders
|
||||
let [main_dishes_area, backends_area] = area.layout(&layout);
|
||||
|
||||
render_menu_block(frame, main_dishes_area, "Main Courses", &MAIN_DISHES);
|
||||
render_menu_block(frame, backends_area, "Pairings", &BACKENDS);
|
||||
}
|
||||
|
||||
fn render_menu_block(frame: &mut Frame, area: Rect, title: &str, menu_items: &[&str]) {
|
||||
let menu_block = Block::bordered()
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(MENU_BORDER_COLOR)
|
||||
.padding(Padding::horizontal(1))
|
||||
.merge_borders(MergeStrategy::Fuzzy)
|
||||
.title(title);
|
||||
|
||||
let menu_lines: Vec<Line> = menu_items.iter().map(|&item| Line::from(item)).collect();
|
||||
frame.render_widget(Paragraph::new(menu_lines).block(menu_block), area);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
fakeit = "1.1"
|
||||
fakeit.workspace = true
|
||||
itertools.workspace = true
|
||||
ratatui.workspace = true
|
||||
unicode-width.workspace = true
|
||||
|
||||
@@ -9,9 +9,9 @@ rust-version.workspace = true
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
ratatui.workspace = true
|
||||
tracing = "0.1.40"
|
||||
tracing-appender = "0.2.3"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
tracing.workspace = true
|
||||
tracing-appender.workspace = true
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -8,7 +8,7 @@ rust-version.workspace = true
|
||||
[dependencies]
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
rand = "0.9.1"
|
||||
rand.workspace = true
|
||||
ratatui.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -6,6 +6,6 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
color-eyre = "0.6"
|
||||
crossterm = "0.29"
|
||||
color-eyre.workspace = true
|
||||
crossterm.workspace = true
|
||||
ratatui = { workspace = true, features = ["unstable-widget-ref"] }
|
||||
|
||||
19
examples/vhs/release-header.tape
Normal file
19
examples/vhs/release-header.tape
Normal file
@@ -0,0 +1,19 @@
|
||||
# This is a vhs script. See https://github.com/charmbracelet/vhs for more info.
|
||||
# To run this script, install vhs and run `vhs ./examples/vhs/release-header.tape`
|
||||
# NOTE: Requires VHS 0.6.1 or later for Screenshot support
|
||||
# The reason for this strange size is that the social preview image for this
|
||||
# demo is 1280x64 with 80 pixels of padding on each side. We want a version
|
||||
# without the padding for README.md, etc.
|
||||
Set Theme {"background": "#141432"}
|
||||
Set FontSize 25
|
||||
Set Width 1120
|
||||
Set Height 480
|
||||
Set Padding 0
|
||||
Hide
|
||||
Type "cargo run -p release-header"
|
||||
Enter
|
||||
Sleep 1s
|
||||
Show
|
||||
Sleep 1s
|
||||
Screenshot assets/release-header.png
|
||||
Sleep 1s
|
||||
@@ -4,7 +4,7 @@ description = """
|
||||
Core types and traits for the Ratatui Terminal UI library.
|
||||
Widget libraries should use this crate. Applications should use the main Ratatui crate.
|
||||
"""
|
||||
version = "0.1.0-alpha.6"
|
||||
version = "0.1.0-beta.0"
|
||||
readme = "README.md"
|
||||
authors.workspace = true
|
||||
documentation.workspace = true
|
||||
@@ -43,6 +43,9 @@ anstyle = ["dep:anstyle"]
|
||||
## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color).
|
||||
palette = ["std", "dep:palette"]
|
||||
|
||||
## enables portable-atomic integration for targets that don't support atomic types.
|
||||
portable-atomic = ["kasuari/portable-atomic"]
|
||||
|
||||
## enables the backend code that sets the underline color. Underline color is only supported by
|
||||
## the Crossterm backend, and is not supported on Windows 7.
|
||||
underline-color = []
|
||||
|
||||
@@ -495,6 +495,38 @@ impl Buffer {
|
||||
if !current.skip && (current != previous || invalidated > 0) && to_skip == 0 {
|
||||
let (x, y) = self.pos_of(i);
|
||||
updates.push((x, y, &next_buffer[i]));
|
||||
|
||||
// If the current cell is multi-width, ensure the trailing cells are explicitly
|
||||
// cleared when they previously contained non-blank content. Some terminals do not
|
||||
// reliably clear the trailing cell(s) when printing a wide grapheme, which can
|
||||
// result in visual artifacts (e.g., leftover characters). Emitting an explicit
|
||||
// update for the trailing cells avoids this.
|
||||
let symbol = current.symbol();
|
||||
let cell_width = symbol.width();
|
||||
// Work around terminals that fail to clear the trailing cell of certain
|
||||
// emoji presentation sequences (those containing VS16 / U+FE0F).
|
||||
// Only emit explicit clears for such sequences to avoid bloating diffs
|
||||
// for standard wide characters (e.g., CJK), which terminals handle well.
|
||||
let contains_vs16 = symbol.chars().any(|c| c == '\u{FE0F}');
|
||||
if cell_width > 1 && contains_vs16 {
|
||||
for k in 1..cell_width {
|
||||
let j = i + k;
|
||||
// Make sure that we are still inside the buffer.
|
||||
if j >= next_buffer.len() || j >= previous_buffer.len() {
|
||||
break;
|
||||
}
|
||||
let prev_trailing = &previous_buffer[j];
|
||||
let next_trailing = &next_buffer[j];
|
||||
if !next_trailing.skip && prev_trailing != next_trailing {
|
||||
let (tx, ty) = self.pos_of(j);
|
||||
// Push an explicit update for the trailing cell.
|
||||
// This is expected to be a blank cell, but we use the actual
|
||||
// content from the next buffer to handle cases where
|
||||
// the user has explicitly set something else.
|
||||
updates.push((tx, ty, next_trailing));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
to_skip = current.symbol().width().saturating_sub(1);
|
||||
@@ -1248,6 +1280,9 @@ mod tests {
|
||||
// Both eye and speech bubble include a 'display as emoji' variation selector
|
||||
// Prior to unicode-width 0.2, this was incorrectly detected as width 4 for some reason
|
||||
#[case::eye_speechbubble("👁️🗨️", "👁️🗨️xxxxx")]
|
||||
// Keyboard keycap emoji: base symbol + VS16 for emoji presentation
|
||||
// This should render as a single grapheme with width 2.
|
||||
#[case::keyboard_emoji("⌨️", "⌨️xxxxx")]
|
||||
fn renders_emoji(#[case] input: &str, #[case] expected: &str) {
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
@@ -1297,4 +1332,34 @@ mod tests {
|
||||
assert_eq!(buffer.index_of(255, 256), 65791);
|
||||
assert_eq!(buffer.pos_of(65791), (255, 256)); // previously (255, 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_clears_trailing_cell_for_wide_grapheme() {
|
||||
// Reproduce: write "ab", then overwrite with a wide emoji like "⌨️"
|
||||
let prev = Buffer::with_lines(["ab"]); // width 2 area inferred
|
||||
assert_eq!(prev.area.width, 2);
|
||||
|
||||
let mut next = Buffer::with_lines([" "]); // start with blanks
|
||||
next.set_string(0, 0, "⌨️", Style::new());
|
||||
|
||||
// The next buffer contains a wide grapheme occupying cell 0 and implicitly cell 1.
|
||||
// The debug formatting shows the hidden trailing space.
|
||||
let expected_next = Buffer::with_lines(["⌨️"]);
|
||||
assert_eq!(next, expected_next);
|
||||
|
||||
// The diff should include an update for (0,0) to draw the emoji. Depending on
|
||||
// terminal behavior, it may or may not be necessary to explicitly clear (1,0).
|
||||
// At minimum, ensure the first cell is updated and nothing incorrect is emitted.
|
||||
let diff = prev.diff(&next);
|
||||
assert!(
|
||||
diff.iter()
|
||||
.any(|(x, y, c)| *x == 0 && *y == 0 && c.symbol() == "⌨️")
|
||||
);
|
||||
// And it should explicitly clear the trailing cell (1,0) to avoid leftovers on terminals
|
||||
// that don't automatically clear the following cell for wide characters.
|
||||
assert!(
|
||||
diff.iter()
|
||||
.any(|(x, y, c)| *x == 1 && *y == 0 && c.symbol() == " ")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::style::{Color, Modifier, Style};
|
||||
use crate::symbols::merge::MergeStrategy;
|
||||
|
||||
/// A buffer cell
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Cell {
|
||||
/// The string to be drawn in the cell.
|
||||
@@ -15,7 +15,7 @@ pub struct Cell {
|
||||
/// buffer for short strings.
|
||||
///
|
||||
/// See <https://github.com/ratatui/ratatui/pull/601> for more information.
|
||||
symbol: CompactString,
|
||||
symbol: Option<CompactString>,
|
||||
|
||||
/// The foreground color of the cell.
|
||||
pub fg: Color,
|
||||
@@ -36,30 +36,35 @@ pub struct Cell {
|
||||
|
||||
impl Cell {
|
||||
/// An empty `Cell`
|
||||
pub const EMPTY: Self = Self::new(" ");
|
||||
pub const EMPTY: Self = Self {
|
||||
symbol: None,
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: Color::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
skip: false,
|
||||
};
|
||||
|
||||
/// Creates a new `Cell` with the given symbol.
|
||||
///
|
||||
/// This works at compile time and puts the symbol onto the stack. Fails to build when the
|
||||
/// symbol doesnt fit onto the stack and requires to be placed on the heap. Use
|
||||
/// symbol doesn't fit onto the stack and requires to be placed on the heap. Use
|
||||
/// `Self::default().set_symbol()` in that case. See [`CompactString::const_new`] for more
|
||||
/// details on this.
|
||||
pub const fn new(symbol: &'static str) -> Self {
|
||||
Self {
|
||||
symbol: CompactString::const_new(symbol),
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: Color::Reset,
|
||||
modifier: Modifier::empty(),
|
||||
skip: false,
|
||||
symbol: Some(CompactString::const_new(symbol)),
|
||||
..Self::EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the symbol of the cell.
|
||||
///
|
||||
/// If the cell has no symbol, returns a single space character.
|
||||
#[must_use]
|
||||
pub fn symbol(&self) -> &str {
|
||||
self.symbol.as_str()
|
||||
self.symbol.as_ref().map_or(" ", |s| s.as_str())
|
||||
}
|
||||
|
||||
/// Merges the symbol of the cell with the one already on the cell, using the provided
|
||||
@@ -73,6 +78,9 @@ impl Cell {
|
||||
/// produce a valid character. [`MergeStrategy`] defines how to handle such cases, e.g.,
|
||||
/// `Exact` for valid merges only, or `Fuzzy` for close matches.
|
||||
///
|
||||
/// If the cell has no symbol set, it will set the symbol to the provided one rather than
|
||||
/// merging.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@@ -97,14 +105,17 @@ impl Cell {
|
||||
/// [border collapsing]: https://ratatui.rs/recipes/layout/collapse-borders/
|
||||
/// [Box Drawing Unicode block]: https://en.wikipedia.org/wiki/Box_Drawing
|
||||
pub fn merge_symbol(&mut self, symbol: &str, strategy: MergeStrategy) -> &mut Self {
|
||||
let merged = strategy.merge(self.symbol(), symbol);
|
||||
self.symbol = CompactString::new(merged);
|
||||
let merged_symbol = self
|
||||
.symbol
|
||||
.as_ref()
|
||||
.map_or(symbol, |s| strategy.merge(s, symbol));
|
||||
self.symbol = Some(CompactString::new(merged_symbol));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the symbol of the cell.
|
||||
pub fn set_symbol(&mut self, symbol: &str) -> &mut Self {
|
||||
self.symbol = CompactString::new(symbol);
|
||||
self.symbol = Some(CompactString::new(symbol));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -112,14 +123,14 @@ impl Cell {
|
||||
///
|
||||
/// This is particularly useful for adding zero-width characters to the cell.
|
||||
pub(crate) fn append_symbol(&mut self, symbol: &str) -> &mut Self {
|
||||
self.symbol.push_str(symbol);
|
||||
self.symbol.get_or_insert_default().push_str(symbol);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the symbol of the cell to a single character.
|
||||
pub fn set_char(&mut self, ch: char) -> &mut Self {
|
||||
let mut buf = [0; 4];
|
||||
self.symbol = CompactString::new(ch.encode_utf8(&mut buf));
|
||||
self.symbol = Some(CompactString::new(ch.encode_utf8(&mut buf)));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -180,21 +191,49 @@ impl Cell {
|
||||
|
||||
/// Resets the cell to the empty state.
|
||||
pub fn reset(&mut self) {
|
||||
self.symbol = CompactString::const_new(" ");
|
||||
self.fg = Color::Reset;
|
||||
self.bg = Color::Reset;
|
||||
#[cfg(feature = "underline-color")]
|
||||
{
|
||||
self.underline_color = Color::Reset;
|
||||
}
|
||||
self.modifier = Modifier::empty();
|
||||
self.skip = false;
|
||||
*self = Self::EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Cell {
|
||||
fn default() -> Self {
|
||||
Self::EMPTY
|
||||
impl PartialEq for Cell {
|
||||
/// Compares two `Cell`s for equality.
|
||||
///
|
||||
/// Note that cells with no symbol (i.e., `Cell::EMPTY`) are considered equal to cells with a
|
||||
/// single space symbol. This is to ensure that empty cells are treated uniformly,
|
||||
/// regardless of how they were created
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Treat None and Some(" ") as equal
|
||||
let symbols_eq = self.symbol() == other.symbol();
|
||||
|
||||
#[cfg(feature = "underline-color")]
|
||||
let underline_color_eq = self.underline_color == other.underline_color;
|
||||
#[cfg(not(feature = "underline-color"))]
|
||||
let underline_color_eq = true;
|
||||
|
||||
symbols_eq
|
||||
&& underline_color_eq
|
||||
&& self.fg == other.fg
|
||||
&& self.bg == other.bg
|
||||
&& self.modifier == other.modifier
|
||||
&& self.skip == other.skip
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Cell {}
|
||||
|
||||
impl core::hash::Hash for Cell {
|
||||
/// Hashes the cell.
|
||||
///
|
||||
/// This treats symbols with Some(" ") as equal to None, so that empty cells are
|
||||
/// treated uniformly, regardless of how they were created.
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.symbol().hash(state);
|
||||
self.fg.hash(state);
|
||||
self.bg.hash(state);
|
||||
#[cfg(feature = "underline-color")]
|
||||
self.underline_color.hash(state);
|
||||
self.modifier.hash(state);
|
||||
self.skip.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +255,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
cell,
|
||||
Cell {
|
||||
symbol: CompactString::const_new("あ"),
|
||||
symbol: Some(CompactString::const_new("あ")),
|
||||
fg: Color::Reset,
|
||||
bg: Color::Reset,
|
||||
#[cfg(feature = "underline-color")]
|
||||
|
||||
@@ -626,7 +626,7 @@ impl Layout {
|
||||
.expect("invalid number of rects")
|
||||
}
|
||||
|
||||
/// Wrapper function around the cassowary solver to be able to split a given area into
|
||||
/// Wrapper function around the [`kasuari`] solver to be able to split a given area into
|
||||
/// smaller ones based on the preferred widths or heights and the direction.
|
||||
///
|
||||
/// Note that the constraints are applied to the whole area that is to be split, so using
|
||||
@@ -664,7 +664,7 @@ impl Layout {
|
||||
self.split_with_spacers(area).0
|
||||
}
|
||||
|
||||
/// Wrapper function around the cassowary solver that splits the given area into smaller ones
|
||||
/// Wrapper function around the [`kasuari`] solver that splits the given area into smaller ones
|
||||
/// based on the preferred widths or heights and the direction, with the ability to include
|
||||
/// spacers between the areas.
|
||||
///
|
||||
@@ -726,7 +726,7 @@ impl Layout {
|
||||
}
|
||||
|
||||
fn try_split(&self, area: Rect) -> Result<(Segments, Spacers), AddConstraintError> {
|
||||
// To take advantage of all of cassowary features, we would want to store the `Solver` in
|
||||
// To take advantage of all of [`kasuari`] features, we would want to store the `Solver` in
|
||||
// one of the fields of the Layout struct. And we would want to set it up such that we could
|
||||
// add or remove constraints as and when needed.
|
||||
// The advantage of doing it as described above is that it would allow users to
|
||||
|
||||
@@ -164,8 +164,7 @@ impl Rect {
|
||||
}
|
||||
}
|
||||
|
||||
/// The area of the `Rect`. If the area is larger than the maximum value of `u16`, it will be
|
||||
/// clamped to `u16::MAX`.
|
||||
/// The area of the `Rect`.
|
||||
pub const fn area(self) -> u32 {
|
||||
(self.width as u32) * (self.height as u32)
|
||||
}
|
||||
@@ -646,6 +645,18 @@ impl From<(Position, Size)> for Rect {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size> for Rect {
|
||||
/// Creates a new `Rect` with the given size at [`Position::ORIGIN`] (0, 0).
|
||||
fn from(size: Size) -> Self {
|
||||
Self {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::string::ToString;
|
||||
@@ -956,6 +967,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_size() {
|
||||
let size = Size {
|
||||
width: 3,
|
||||
height: 4,
|
||||
};
|
||||
assert_eq!(
|
||||
Rect::from(size),
|
||||
Rect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 3,
|
||||
height: 4
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn centered_horizontally() {
|
||||
let rect = Rect::new(0, 0, 5, 5);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#![no_std]
|
||||
// show the feature flags in the generated documentation
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/logo.png",
|
||||
html_favicon_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/favicon.ico"
|
||||
|
||||
@@ -238,15 +238,26 @@ impl fmt::Debug for Modifier {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Style {
|
||||
/// The foreground color.
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub fg: Option<Color>,
|
||||
/// The background color.
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub bg: Option<Color>,
|
||||
/// The underline color.
|
||||
#[cfg(feature = "underline-color")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub underline_color: Option<Color>,
|
||||
/// The modifiers to add.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(default, skip_serializing_if = "Modifier::is_empty")
|
||||
)]
|
||||
pub add_modifier: Modifier,
|
||||
/// The modifiers to remove.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(default, skip_serializing_if = "Modifier::is_empty")
|
||||
)]
|
||||
pub sub_modifier: Modifier,
|
||||
}
|
||||
|
||||
@@ -923,4 +934,59 @@ mod tests {
|
||||
.remove_modifier(Modifier::DIM)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn serialize_then_deserialize() {
|
||||
let style = Style {
|
||||
fg: Some(Color::Rgb(255, 0, 255)),
|
||||
bg: Some(Color::White),
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: Some(Color::Indexed(3)),
|
||||
add_modifier: Modifier::UNDERLINED,
|
||||
sub_modifier: Modifier::CROSSED_OUT,
|
||||
};
|
||||
|
||||
let json_str = serde_json::to_string(&style).unwrap();
|
||||
let json_value: serde_json::Value = serde_json::from_str(&json_str).unwrap();
|
||||
|
||||
let mut expected_json = serde_json::json!({
|
||||
"fg": "#FF00FF",
|
||||
"bg": "White",
|
||||
"add_modifier": "UNDERLINED",
|
||||
"sub_modifier": "CROSSED_OUT"
|
||||
});
|
||||
|
||||
#[cfg(feature = "underline-color")]
|
||||
{
|
||||
expected_json
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("underline_color".into(), "3".into());
|
||||
}
|
||||
|
||||
assert_eq!(json_value, expected_json);
|
||||
|
||||
let deserialized: Style = serde_json::from_str(&json_str).unwrap();
|
||||
assert_eq!(deserialized, style);
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn deserialize_defaults() {
|
||||
let style = Style {
|
||||
fg: None,
|
||||
bg: None,
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: None,
|
||||
add_modifier: Modifier::empty(),
|
||||
sub_modifier: Modifier::empty(),
|
||||
};
|
||||
|
||||
let json_str = serde_json::to_string(&style).unwrap();
|
||||
assert_eq!(json_str, "{}");
|
||||
|
||||
let deserialized: Style = serde_json::from_str(&json_str).unwrap();
|
||||
assert_eq!(deserialized, style);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,8 @@ impl From<anstyle::Style> for Style {
|
||||
Self {
|
||||
fg: style.get_fg_color().map(Color::from),
|
||||
bg: style.get_bg_color().map(Color::from),
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: style.get_underline_color().map(Color::from),
|
||||
add_modifier: style.get_effects().into(),
|
||||
..Default::default()
|
||||
}
|
||||
@@ -207,6 +209,11 @@ impl From<Style> for anstyle::Style {
|
||||
let bg = anstyle::Color::from(bg);
|
||||
anstyle_style = anstyle_style.bg_color(Some(bg));
|
||||
}
|
||||
#[cfg(feature = "underline-color")]
|
||||
if let Some(underline) = style.underline_color {
|
||||
let underline = anstyle::Color::from(underline);
|
||||
anstyle_style = anstyle_style.underline_color(Some(underline));
|
||||
}
|
||||
anstyle_style = anstyle_style.effects(style.add_modifier.into());
|
||||
anstyle_style
|
||||
}
|
||||
@@ -300,10 +307,13 @@ mod tests {
|
||||
let anstyle_style = anstyle::Style::new()
|
||||
.fg_color(Some(anstyle::Color::Ansi(AnsiColor::Red)))
|
||||
.bg_color(Some(anstyle::Color::Ansi(AnsiColor::Blue)))
|
||||
.underline_color(Some(anstyle::Color::Ansi(AnsiColor::Green)))
|
||||
.effects(Effects::BOLD | Effects::ITALIC);
|
||||
let style = Style::from(anstyle_style);
|
||||
assert_eq!(style.fg, Some(Color::Red));
|
||||
assert_eq!(style.bg, Some(Color::Blue));
|
||||
#[cfg(feature = "underline-color")]
|
||||
assert_eq!(style.underline_color, Some(Color::Green));
|
||||
assert!(style.add_modifier.contains(Modifier::BOLD));
|
||||
assert!(style.add_modifier.contains(Modifier::ITALIC));
|
||||
}
|
||||
@@ -313,6 +323,8 @@ mod tests {
|
||||
let style = Style {
|
||||
fg: Some(Color::Red),
|
||||
bg: Some(Color::Blue),
|
||||
#[cfg(feature = "underline-color")]
|
||||
underline_color: Some(Color::Green),
|
||||
add_modifier: Modifier::BOLD | Modifier::ITALIC,
|
||||
..Default::default()
|
||||
};
|
||||
@@ -325,6 +337,11 @@ mod tests {
|
||||
anstyle_style.get_bg_color(),
|
||||
Some(anstyle::Color::Ansi(AnsiColor::Blue))
|
||||
);
|
||||
#[cfg(feature = "underline-color")]
|
||||
assert_eq!(
|
||||
anstyle_style.get_underline_color(),
|
||||
Some(anstyle::Color::Ansi(AnsiColor::Green))
|
||||
);
|
||||
assert!(anstyle_style.get_effects().contains(Effects::BOLD));
|
||||
assert!(anstyle_style.get_effects().contains(Effects::ITALIC));
|
||||
}
|
||||
|
||||
@@ -300,15 +300,20 @@ impl MergeStrategy {
|
||||
/// [Box Drawing Unicode block]: https://en.wikipedia.org/wiki/Box_Drawing
|
||||
/// [`Cell::merge_symbol`]: crate::buffer::Cell::merge_symbol
|
||||
pub fn merge<'a>(self, prev: &'a str, next: &'a str) -> &'a str {
|
||||
let (Ok(prev_symbol), Ok(next_symbol)) =
|
||||
(BorderSymbol::from_str(prev), BorderSymbol::from_str(next))
|
||||
else {
|
||||
// Replace should always just return the last symbol.
|
||||
if self == Self::Replace {
|
||||
return next;
|
||||
};
|
||||
if let Ok(merged) = prev_symbol.merge(next_symbol, self).try_into() {
|
||||
return merged;
|
||||
}
|
||||
next
|
||||
|
||||
match (BorderSymbol::from_str(prev), BorderSymbol::from_str(next)) {
|
||||
(Ok(prev_symbol), Ok(next_symbol)) => prev_symbol
|
||||
.merge(next_symbol, self)
|
||||
.try_into()
|
||||
.unwrap_or(next),
|
||||
// Non-border symbols take precedence in strategies other than Replace.
|
||||
(Err(_), Ok(_)) => prev,
|
||||
(_, Err(_)) => next,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,7 +528,6 @@ macro_rules! define_symbols {
|
||||
}
|
||||
|
||||
define_symbols!(
|
||||
" " => (Nothing, Nothing, Nothing, Nothing),
|
||||
"─" => (Plain, Nothing, Plain, Nothing),
|
||||
"━" => (Thick, Nothing, Thick, Nothing),
|
||||
"│" => (Nothing, Plain, Nothing, Plain),
|
||||
@@ -666,7 +670,7 @@ mod tests {
|
||||
"╄", "╅", "╆", "╇", "╈", "╉", "╊", "╋", "╌", "╍", "╎", "╏", "═", "║", "╒", "╓", "╔",
|
||||
"╕", "╖", "╗", "╘", "╙", "╚", "╛", "╜", "╝", "╞", "╟", "╠", "╡", "╢", "╣", "╤", "╥",
|
||||
"╦", "╧", "╨", "╩", "╪", "╫", "╬", "╭", "╮", "╯", "╰", "╴", "╵", "╶", "╷", "╸", "╹",
|
||||
"╺", "╻", "╼", "╽", "╾", "╿", " ",
|
||||
"╺", "╻", "╼", "╽", "╾", "╿", " ", "a", "b",
|
||||
];
|
||||
|
||||
for a in symbols {
|
||||
@@ -695,8 +699,8 @@ mod tests {
|
||||
assert_eq!(strategy.merge("┵", "┝"), "┿");
|
||||
assert_eq!(strategy.merge("│", "━"), "┿");
|
||||
assert_eq!(strategy.merge("┵", "╞"), "╞");
|
||||
assert_eq!(strategy.merge(" ", "╠"), "╠");
|
||||
assert_eq!(strategy.merge("╠", " "), "╠");
|
||||
assert_eq!(strategy.merge(" ", "╠"), " ");
|
||||
assert_eq!(strategy.merge("╠", " "), " ");
|
||||
assert_eq!(strategy.merge("╎", "╧"), "╧");
|
||||
assert_eq!(strategy.merge("╛", "╒"), "╪");
|
||||
assert_eq!(strategy.merge("│", "═"), "╪");
|
||||
@@ -704,6 +708,9 @@ mod tests {
|
||||
assert_eq!(strategy.merge("╡", "╞"), "╪");
|
||||
assert_eq!(strategy.merge("┌", "╭"), "╭");
|
||||
assert_eq!(strategy.merge("┘", "╭"), "╭");
|
||||
assert_eq!(strategy.merge("┌", "a"), "a");
|
||||
assert_eq!(strategy.merge("a", "╭"), "a");
|
||||
assert_eq!(strategy.merge("a", "b"), "b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -711,7 +718,7 @@ mod tests {
|
||||
let strategy = MergeStrategy::Fuzzy;
|
||||
assert_eq!(strategy.merge("┄", "╴"), "─");
|
||||
assert_eq!(strategy.merge("│", "┆"), "┆");
|
||||
assert_eq!(strategy.merge(" ", "┉"), "┉");
|
||||
assert_eq!(strategy.merge(" ", "┉"), " ");
|
||||
assert_eq!(strategy.merge("┋", "┋"), "┋");
|
||||
assert_eq!(strategy.merge("╷", "╶"), "┌");
|
||||
assert_eq!(strategy.merge("╭", "┌"), "┌");
|
||||
@@ -725,8 +732,8 @@ mod tests {
|
||||
assert_eq!(strategy.merge("┘", "┌"), "┼");
|
||||
assert_eq!(strategy.merge("┘", "╭"), "┼");
|
||||
assert_eq!(strategy.merge("╎", "┉"), "┿");
|
||||
assert_eq!(strategy.merge(" ", "╠"), "╠");
|
||||
assert_eq!(strategy.merge("╠", " "), "╠");
|
||||
assert_eq!(strategy.merge(" ", "╠"), " ");
|
||||
assert_eq!(strategy.merge("╠", " "), " ");
|
||||
assert_eq!(strategy.merge("┵", "╞"), "╪");
|
||||
assert_eq!(strategy.merge("╛", "╒"), "╪");
|
||||
assert_eq!(strategy.merge("│", "═"), "╪");
|
||||
@@ -734,5 +741,8 @@ mod tests {
|
||||
assert_eq!(strategy.merge("╡", "╞"), "╪");
|
||||
assert_eq!(strategy.merge("╎", "╧"), "╪");
|
||||
assert_eq!(strategy.merge("┌", "╭"), "╭");
|
||||
assert_eq!(strategy.merge("┌", "a"), "a");
|
||||
assert_eq!(strategy.merge("a", "╭"), "a");
|
||||
assert_eq!(strategy.merge("a", "b"), "b");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,14 @@ where
|
||||
{
|
||||
/// Creates a new [`Terminal`] with the given [`Backend`] with a full screen viewport.
|
||||
///
|
||||
/// Note that unlike `ratatui::init`, this does not install a panic hook, so it is recommended
|
||||
/// to do that manually when using this function, otherwise any panic messages will be printed
|
||||
/// to the alternate screen and the terminal may be left in an unusable state.
|
||||
///
|
||||
/// See [how to set up panic hooks](https://ratatui.rs/recipes/apps/panic-hooks/) and
|
||||
/// [`better-panic` example](https://ratatui.rs/recipes/apps/better-panic/) for more
|
||||
/// information.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,ignore
|
||||
@@ -137,6 +145,13 @@ where
|
||||
///
|
||||
/// let backend = CrosstermBackend::new(stdout());
|
||||
/// let terminal = Terminal::new(backend)?;
|
||||
///
|
||||
/// // Optionally set up a panic hook to restore the terminal on panic.
|
||||
/// let old_hook = std::panic::take_hook();
|
||||
/// std::panic::set_hook(Box::new(move |info| {
|
||||
/// ratatui::restore();
|
||||
/// old_hook(info);
|
||||
/// }));
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
pub fn new(backend: B) -> Result<Self, B::Error> {
|
||||
@@ -164,9 +179,7 @@ where
|
||||
/// ```
|
||||
pub fn with_options(mut backend: B, options: TerminalOptions) -> Result<Self, B::Error> {
|
||||
let area = match options.viewport {
|
||||
Viewport::Fullscreen | Viewport::Inline(_) => {
|
||||
Rect::from((Position::ORIGIN, backend.size()?))
|
||||
}
|
||||
Viewport::Fullscreen | Viewport::Inline(_) => backend.size()?.into(),
|
||||
Viewport::Fixed(area) => area,
|
||||
};
|
||||
let (viewport_area, cursor_pos) = match options.viewport {
|
||||
@@ -265,7 +278,7 @@ where
|
||||
pub fn autoresize(&mut self) -> Result<(), B::Error> {
|
||||
// fixed viewports do not get autoresized
|
||||
if matches!(self.viewport, Viewport::Fullscreen | Viewport::Inline(_)) {
|
||||
let area = Rect::from((Position::ORIGIN, self.size()?));
|
||||
let area = self.size()?.into();
|
||||
if area != self.last_known_area {
|
||||
self.resize(area)?;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
|
||||
use unicode_truncate::UnicodeTruncateStr;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::{Alignment, Rect};
|
||||
@@ -435,8 +436,9 @@ impl<'a> Line<'a> {
|
||||
/// let line = Line::from(vec!["Hello".blue(), " world!".green()]);
|
||||
/// assert_eq!(12, line.width());
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn width(&self) -> usize {
|
||||
self.spans.iter().map(Span::width).sum()
|
||||
UnicodeWidthStr::width(self)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the graphemes held by this line.
|
||||
@@ -562,6 +564,16 @@ impl<'a> Line<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl UnicodeWidthStr for Line<'_> {
|
||||
fn width(&self) -> usize {
|
||||
self.spans.iter().map(UnicodeWidthStr::width).sum()
|
||||
}
|
||||
|
||||
fn width_cjk(&self) -> usize {
|
||||
self.spans.iter().map(UnicodeWidthStr::width_cjk).sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for Line<'a> {
|
||||
type Item = Span<'a>;
|
||||
type IntoIter = alloc::vec::IntoIter<Span<'a>>;
|
||||
|
||||
@@ -269,7 +269,7 @@ impl<'a> Span<'a> {
|
||||
|
||||
/// Returns the unicode width of the content held by this span.
|
||||
pub fn width(&self) -> usize {
|
||||
self.content.width()
|
||||
UnicodeWidthStr::width(self)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the graphemes held by this span.
|
||||
@@ -376,6 +376,16 @@ impl<'a> Span<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl UnicodeWidthStr for Span<'_> {
|
||||
fn width(&self) -> usize {
|
||||
self.content.width()
|
||||
}
|
||||
|
||||
fn width_cjk(&self) -> usize {
|
||||
self.content.width_cjk()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<T> for Span<'a>
|
||||
where
|
||||
T: Into<Cow<'a, str>>,
|
||||
|
||||
@@ -5,6 +5,8 @@ use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt;
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
use crate::layout::{Alignment, Rect};
|
||||
use crate::style::{Style, Styled};
|
||||
@@ -284,7 +286,7 @@ impl<'a> Text<'a> {
|
||||
/// assert_eq!(15, text.width());
|
||||
/// ```
|
||||
pub fn width(&self) -> usize {
|
||||
self.iter().map(Line::width).max().unwrap_or_default()
|
||||
UnicodeWidthStr::width(self)
|
||||
}
|
||||
|
||||
/// Returns the height.
|
||||
@@ -559,6 +561,25 @@ impl<'a> Text<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl UnicodeWidthStr for Text<'_> {
|
||||
/// Returns the max width of all the lines.
|
||||
fn width(&self) -> usize {
|
||||
self.lines
|
||||
.iter()
|
||||
.map(UnicodeWidthStr::width)
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn width_cjk(&self) -> usize {
|
||||
self.lines
|
||||
.iter()
|
||||
.map(UnicodeWidthStr::width_cjk)
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for Text<'a> {
|
||||
type Item = Line<'a>;
|
||||
type IntoIter = alloc::vec::IntoIter<Self::Item>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ratatui-crossterm"
|
||||
version = "0.1.0-alpha.5"
|
||||
version = "0.1.0-beta.0"
|
||||
description = "Crossterm backend for the Ratatui Terminal UI library."
|
||||
documentation = "https://docs.rs/ratatui-crossterm/"
|
||||
readme = "README.md"
|
||||
@@ -14,11 +14,31 @@ exclude.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["document-features"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = ["underline-color"]
|
||||
default = ["underline-color", "crossterm_0_29"]
|
||||
|
||||
#! One of the following versions of crossterm must be enabled. By default, the latest version is
|
||||
#! enabled. This will always default to the most recent version of crossterm, but you can override
|
||||
#! this by specifying the version you want to use in your Cargo.toml file. The purpose of these
|
||||
#! features is to allow widget libraries to depend on specific versions of crossterm in a way that
|
||||
#! doesn't cause version conflicts. A compiler error will be raised if you don't enable at least one
|
||||
#! of the following features.
|
||||
|
||||
## Enables crossterm 0.28.x
|
||||
crossterm_0_28 = ["dep:crossterm_0_28"]
|
||||
## Enables crossterm 0.29.x
|
||||
crossterm_0_29 = ["dep:crossterm_0_29"]
|
||||
|
||||
#! The following features are optional and can be enabled to add additional functionality to the
|
||||
#! library. These features are not required for the library to function, but they may be useful in
|
||||
#! certain situations.
|
||||
|
||||
## Enables serde for crossterm dependency
|
||||
serde = ["crossterm/serde"]
|
||||
serde = ["crossterm_0_28?/serde", "crossterm_0_29?/serde"]
|
||||
|
||||
## enables the backend code that sets the underline color.
|
||||
## Underline color is not supported on Windows 7.
|
||||
@@ -37,13 +57,14 @@ unstable-backend-writer = []
|
||||
|
||||
|
||||
[dependencies]
|
||||
crossterm.workspace = true
|
||||
cfg-if = "1.0.1"
|
||||
crossterm_0_28 = { version = "0.28", package = "crossterm", optional = true }
|
||||
crossterm_0_29 = { version = "0.29", package = "crossterm", optional = true }
|
||||
document-features = { workspace = true, optional = true }
|
||||
instability.workspace = true
|
||||
ratatui-core = { workspace = true }
|
||||
ratatui-core.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ratatui = { path = "../ratatui", features = ["crossterm"] }
|
||||
rstest.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -2,10 +2,41 @@
|
||||
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
This module provides the [`CrosstermBackend`] implementation for the [`Backend`] trait. It uses
|
||||
the [Crossterm] crate to interact with the terminal.
|
||||
This crate provides [`CrosstermBackend`], an implementation of the [`Backend`] trait for the
|
||||
[Ratatui] library. It uses the [Crossterm] library for all terminal manipulation.
|
||||
|
||||
### Crossterm Version and Re-export
|
||||
|
||||
`ratatui-crossterm` requires you to specify a version of the [Crossterm] library to be used.
|
||||
This is managed via feature flags. The highest enabled feature flag of the available
|
||||
`crossterm_0_xx` features (e.g., `crossterm_0_28`, `crossterm_0_29`) takes precedence. These
|
||||
features determine which version of Crossterm is compiled and used by the backend. Feature
|
||||
unification may mean that any crate in your dependency graph that chooses to depend on a
|
||||
specific version of Crossterm may be affected by the feature flags you enable.
|
||||
|
||||
Ratatui will support at least the two most recent versions of Crossterm (though we may increase
|
||||
this if crossterm release cadence increases). We will remove support for older versions in major
|
||||
(0.x) releases of `ratatui-crossterm`, and we may add support for newer versions in minor
|
||||
(0.x.y) releases.
|
||||
|
||||
To promote interoperability within the [Ratatui] ecosystem, the selected Crossterm crate is
|
||||
re-exported as `ratatui_crossterm::crossterm`. This re-export is essential for authors of widget
|
||||
libraries or any applications that need to perform direct Crossterm operations while ensuring
|
||||
compatibility with the version used by `ratatui-crossterm`. By using
|
||||
`ratatui_crossterm::crossterm` for such operations, developers can avoid version conflicts and
|
||||
ensure that all parts of their application use a consistent set of Crossterm types and
|
||||
functions.
|
||||
|
||||
For example, if your application's `Cargo.toml` enables the `crossterm_0_29` feature for
|
||||
`ratatui-crossterm`, then any code using `ratatui_crossterm::crossterm` will refer to the 0.29
|
||||
version of Crossterm.
|
||||
|
||||
For more information on how to use the backend, see the documentation for the
|
||||
[`CrosstermBackend`] struct.
|
||||
|
||||
[Ratatui]: https://ratatui.rs
|
||||
[Crossterm]: https://crates.io/crates/crossterm
|
||||
[`Backend`]: ratatui_core::backend::Backend
|
||||
|
||||
## Crate Organization
|
||||
|
||||
|
||||
@@ -1,15 +1,45 @@
|
||||
// show the feature flags in the generated documentation
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/logo.png",
|
||||
html_favicon_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/favicon.ico"
|
||||
)]
|
||||
#![warn(missing_docs)]
|
||||
//! This module provides the [`CrosstermBackend`] implementation for the [`Backend`] trait. It uses
|
||||
//! the [Crossterm] crate to interact with the terminal.
|
||||
//! This crate provides [`CrosstermBackend`], an implementation of the [`Backend`] trait for the
|
||||
//! [Ratatui] library. It uses the [Crossterm] library for all terminal manipulation.
|
||||
//!
|
||||
//! ## Crossterm Version and Re-export
|
||||
//!
|
||||
//! `ratatui-crossterm` requires you to specify a version of the [Crossterm] library to be used.
|
||||
//! This is managed via feature flags. The highest enabled feature flag of the available
|
||||
//! `crossterm_0_xx` features (e.g., `crossterm_0_28`, `crossterm_0_29`) takes precedence. These
|
||||
//! features determine which version of Crossterm is compiled and used by the backend. Feature
|
||||
//! unification may mean that any crate in your dependency graph that chooses to depend on a
|
||||
//! specific version of Crossterm may be affected by the feature flags you enable.
|
||||
//!
|
||||
//! Ratatui will support at least the two most recent versions of Crossterm (though we may increase
|
||||
//! this if crossterm release cadence increases). We will remove support for older versions in major
|
||||
//! (0.x) releases of `ratatui-crossterm`, and we may add support for newer versions in minor
|
||||
//! (0.x.y) releases.
|
||||
//!
|
||||
//! To promote interoperability within the [Ratatui] ecosystem, the selected Crossterm crate is
|
||||
//! re-exported as `ratatui_crossterm::crossterm`. This re-export is essential for authors of widget
|
||||
//! libraries or any applications that need to perform direct Crossterm operations while ensuring
|
||||
//! compatibility with the version used by `ratatui-crossterm`. By using
|
||||
//! `ratatui_crossterm::crossterm` for such operations, developers can avoid version conflicts and
|
||||
//! ensure that all parts of their application use a consistent set of Crossterm types and
|
||||
//! functions.
|
||||
//!
|
||||
//! For example, if your application's `Cargo.toml` enables the `crossterm_0_29` feature for
|
||||
//! `ratatui-crossterm`, then any code using `ratatui_crossterm::crossterm` will refer to the 0.29
|
||||
//! version of Crossterm.
|
||||
//!
|
||||
//! For more information on how to use the backend, see the documentation for the
|
||||
//! [`CrosstermBackend`] struct.
|
||||
//!
|
||||
//! [Ratatui]: https://ratatui.rs
|
||||
//! [Crossterm]: https://crates.io/crates/crossterm
|
||||
//! [`Backend`]: ratatui_core::backend::Backend
|
||||
//!
|
||||
//! # Crate Organization
|
||||
//!
|
||||
@@ -36,7 +66,6 @@
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub use crossterm;
|
||||
use crossterm::cursor::{Hide, MoveTo, Show};
|
||||
#[cfg(feature = "underline-color")]
|
||||
use crossterm::style::SetUnderlineColor;
|
||||
@@ -47,6 +76,19 @@ use crossterm::style::{
|
||||
};
|
||||
use crossterm::terminal::{self, Clear};
|
||||
use crossterm::{execute, queue};
|
||||
cfg_if::cfg_if! {
|
||||
// Re-export the selected Crossterm crate making sure to choose the latest version. We do this
|
||||
// to make it possible to easily enable all features when compiling `ratatui-crossterm`.
|
||||
if #[cfg(feature = "crossterm_0_29")] {
|
||||
pub use crossterm_0_29 as crossterm;
|
||||
} else if #[cfg(feature = "crossterm_0_28")] {
|
||||
pub use crossterm_0_28 as crossterm;
|
||||
} else {
|
||||
compile_error!(
|
||||
"At least one crossterm feature must be enabled. See the crate docs for more information."
|
||||
);
|
||||
}
|
||||
}
|
||||
use ratatui_core::backend::{Backend, ClearType, WindowSize};
|
||||
use ratatui_core::buffer::Cell;
|
||||
use ratatui_core::layout::{Position, Size};
|
||||
@@ -70,7 +112,7 @@ use ratatui_core::style::{Color, Modifier, Style};
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```rust,ignore
|
||||
/// use std::io::{stderr, stdout};
|
||||
///
|
||||
/// use crossterm::ExecutableCommand;
|
||||
@@ -126,7 +168,7 @@ where
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```rust,ignore
|
||||
/// use std::io::stdout;
|
||||
///
|
||||
/// use ratatui::backend::CrosstermBackend;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ratatui-macros"
|
||||
version = "0.7.0-alpha.4"
|
||||
version = "0.7.0-beta.0"
|
||||
edition.workspace = true
|
||||
authors = ["The Ratatui Developers"]
|
||||
description = "Macros for Ratatui"
|
||||
|
||||
@@ -9,9 +9,9 @@ error: No rules expected the token `,` while trying to match the end of the macr
|
||||
error: unexpected end of macro invocation
|
||||
--> tests/ui/fails.rs:8:18
|
||||
|
|
||||
8 | let [a, b] = constraints![
|
||||
8 | let [a, b] = constraints![
|
||||
| __________________^
|
||||
9 | | == 1/2,
|
||||
9 | | == 1/2,
|
||||
10 | | == 2,
|
||||
11 | | ];
|
||||
| |_____^ missing tokens in macro arguments
|
||||
@@ -50,9 +50,3 @@ error: argument never used
|
||||
| ------- ^^^^^^^^^^^^^ argument never used
|
||||
| |
|
||||
| formatting specifier missing
|
||||
|
||||
error[E0527]: pattern requires 2 elements but array has 3
|
||||
--> tests/ui/fails.rs:8:9
|
||||
|
|
||||
8 | let [a, b] = constraints![
|
||||
| ^^^^^^ expected 3 elements
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ratatui-termion"
|
||||
version = "0.1.0-alpha.5"
|
||||
version = "0.1.0-beta.0"
|
||||
description = "Termion backend for the Ratatui Terminal UI library."
|
||||
documentation = "https://docs.rs/ratatui-termion/"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// show the feature flags in the generated documentation
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/logo.png",
|
||||
html_favicon_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/favicon.ico"
|
||||
@@ -66,7 +65,7 @@ use termion::{color as tcolor, style as tstyle};
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```rust,ignore
|
||||
/// use std::io::{stderr, stdout};
|
||||
///
|
||||
/// use ratatui::Terminal;
|
||||
@@ -113,7 +112,7 @@ where
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```rust,ignore
|
||||
/// use std::io::stdout;
|
||||
///
|
||||
/// use ratatui::backend::TermionBackend;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ratatui-termwiz"
|
||||
version = "0.1.0-alpha.5"
|
||||
version = "0.1.0-beta.0"
|
||||
description = "Termwiz backend for the Ratatui Terminal UI library."
|
||||
documentation = "https://docs.rs/ratatui-termwiz/"
|
||||
readme = "README.md"
|
||||
@@ -27,7 +27,7 @@ serde = ["termwiz/use_serde"]
|
||||
|
||||
## Enables the backend code that sets the underline color.
|
||||
## Underline color is not supported on Windows 7.
|
||||
underline-color = []
|
||||
underline-color = ["ratatui-core/underline-color"]
|
||||
|
||||
## Use terminal scrolling regions to make Terminal::insert_before less prone to flickering.
|
||||
scrolling-regions = ["ratatui-core/scrolling-regions"]
|
||||
@@ -38,7 +38,7 @@ ratatui-core = { workspace = true }
|
||||
termwiz.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ratatui = { path = "../ratatui", features = ["termwiz"] }
|
||||
ratatui = { path = "../ratatui", features = ["termwiz"], default-features = false }
|
||||
rstest.workspace = true
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// show the feature flags in the generated documentation
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/logo.png",
|
||||
html_favicon_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/favicon.ico"
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "ratatui-widgets"
|
||||
description = "A collection of Ratatui widgets for building terminal user interfaces using Ratatui."
|
||||
# Note that this started at 0.3.0 as there was a previous crate using the name `ratatui-widgets`.
|
||||
# <https://github.com/joshka/ratatui-widgets/issues/46>
|
||||
version = "0.3.0-alpha.5"
|
||||
version = "0.3.0-beta.0"
|
||||
readme = "README.md"
|
||||
authors.workspace = true
|
||||
documentation.workspace = true
|
||||
|
||||
117
ratatui-widgets/examples/collapsed-borders.rs
Normal file
117
ratatui-widgets/examples/collapsed-borders.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
//! # [Ratatui] [`Block`] with collapsed borders example
|
||||
//!
|
||||
//! The latest version of this example is available in the [widget examples] folder in the
|
||||
//! repository.
|
||||
//!
|
||||
//! Please note that the examples are designed to be run against the `main` branch of the Github
|
||||
//! repository. This means that you may not be able to compile with the latest release version on
|
||||
//! crates.io, or the one that you have installed locally.
|
||||
//!
|
||||
//! See the [examples readme] for more information on finding examples that match the version of the
|
||||
//! library you are using.
|
||||
//!
|
||||
//! [Ratatui]: https://github.com/ratatui/ratatui
|
||||
//! [widget examples]: https://github.com/ratatui/ratatui/blob/main/ratatui-widgets/examples
|
||||
//! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event;
|
||||
use ratatui::Frame;
|
||||
use ratatui::layout::{Constraint, Layout, Rect, Spacing};
|
||||
use ratatui::style::{Color, Stylize};
|
||||
use ratatui::symbols::merge::MergeStrategy;
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::{Block, BorderType};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
ratatui::run(|terminal| {
|
||||
let mut selected_pane = Pane::Top;
|
||||
loop {
|
||||
terminal.draw(|frame| render(frame, selected_pane))?;
|
||||
if let Some(key_event) = event::read()?.as_key_press_event() {
|
||||
match key_event.code {
|
||||
event::KeyCode::Up => selected_pane = Pane::Top,
|
||||
event::KeyCode::Left => selected_pane = Pane::Left,
|
||||
event::KeyCode::Right => selected_pane = Pane::Right,
|
||||
event::KeyCode::Down => selected_pane = Pane::Bottom,
|
||||
event::KeyCode::Char('q') => return Ok(()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Derive Eq, PartialEq, Hash for Pane to use as HashMap key
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
enum Pane {
|
||||
Top,
|
||||
Left,
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
/// Render the UI with various blocks.
|
||||
fn render(frame: &mut Frame, selected_pane: Pane) {
|
||||
let [title, blocks] = frame.area().layout(&Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Fill(1),
|
||||
]));
|
||||
|
||||
render_title(frame, title);
|
||||
render_blocks(selected_pane, frame, blocks);
|
||||
}
|
||||
|
||||
fn render_title(frame: &mut Frame<'_>, area: Rect) {
|
||||
let title = Line::from_iter([
|
||||
"Block With Collapsed Borders".bold(),
|
||||
" (Press 'q' to quit)".into(),
|
||||
]);
|
||||
frame.render_widget(title.centered(), area);
|
||||
}
|
||||
|
||||
fn render_blocks(selected_pane: Pane, frame: &mut Frame<'_>, area: Rect) {
|
||||
// The recipe to achieve collapsed borders is as follows:
|
||||
// 1. Use `MergeStrategy::Exact` (or `MergeStrategy::Fuzzy`) to merge borders of adjacent
|
||||
// blocks.
|
||||
// 2. Use a layout with `Spacing::Overlap(1)` to ensure that the borders overlap
|
||||
// 3. Use `BorderType::Thick` for the selected pane to make it visually distinct.
|
||||
// 4. Render the selected pane last so it appears on top of the others.
|
||||
let [top, middle, bottom] =
|
||||
area.layout(&Layout::vertical([Constraint::Fill(1); 3]).spacing(Spacing::Overlap(1)));
|
||||
let [left, right] =
|
||||
middle.layout(&Layout::horizontal([Constraint::Fill(1); 2]).spacing(Spacing::Overlap(1)));
|
||||
|
||||
// Store pane areas and titles in a single HashMap indexed by Pane. A real application might
|
||||
// store actual data or widgets in these areas instead (and use `WidgetRef` to handle
|
||||
// heterogeneous widgets).
|
||||
let mut panes = HashMap::new();
|
||||
panes.insert(Pane::Top, (top, "Top Block"));
|
||||
panes.insert(Pane::Left, (left, "Left Block"));
|
||||
panes.insert(Pane::Right, (right, "Right Block"));
|
||||
panes.insert(Pane::Bottom, (bottom, "Bottom Block"));
|
||||
|
||||
// Render all panes except the selected one first
|
||||
for (&pane, &(area, title)) in &panes {
|
||||
if pane != selected_pane {
|
||||
// MergeStrategy::Exact causes the borders to collapse
|
||||
let block = Block::bordered()
|
||||
.merge_borders(MergeStrategy::Exact)
|
||||
.title(title);
|
||||
frame.render_widget(block, area);
|
||||
}
|
||||
}
|
||||
// Render the selected pane last (so it appears on top) with a thick border
|
||||
if let Some(&(area, title)) = panes.get(&selected_pane) {
|
||||
// MergeStrategy::Exact causes the borders to collapse
|
||||
let block = Block::bordered()
|
||||
.merge_borders(MergeStrategy::Exact)
|
||||
.border_type(BorderType::Thick)
|
||||
.border_style(Color::Yellow)
|
||||
.title(title);
|
||||
frame.render_widget(block, area);
|
||||
}
|
||||
}
|
||||
17
ratatui-widgets/examples/vhs/collapsed-borders.tape
Normal file
17
ratatui-widgets/examples/vhs/collapsed-borders.tape
Normal file
@@ -0,0 +1,17 @@
|
||||
# This is a vhs script. See https://github.com/charmbracelet/vhs for more info.
|
||||
# To run this script, install vhs and run `vhs ./examples/vhs/gauge.tape`
|
||||
Output "target/collapsed-borders.gif"
|
||||
Set Theme "Aardvark Blue"
|
||||
Set Width 1200
|
||||
Set Height 800
|
||||
Hide
|
||||
Type "cargo run -p ratatui-widgets --example collapsed-borders"
|
||||
Enter
|
||||
Sleep 2s
|
||||
Show
|
||||
Sleep 1s
|
||||
Left Sleep 1s
|
||||
Right Sleep 1s
|
||||
Down Sleep 1s
|
||||
Hide
|
||||
Type "q"
|
||||
@@ -515,7 +515,7 @@ impl BarChart<'_> {
|
||||
let margin = u16::from(label_size != 0);
|
||||
Rect {
|
||||
x: area.x + label_size + margin,
|
||||
width: area.width - label_size - margin,
|
||||
width: area.width.saturating_sub(label_size).saturating_sub(margin),
|
||||
..area
|
||||
}
|
||||
};
|
||||
@@ -579,10 +579,10 @@ impl BarChart<'_> {
|
||||
}
|
||||
|
||||
fn render_vertical(&self, buf: &mut Buffer, area: Rect) {
|
||||
let label_info = self.label_info(area.height - 1);
|
||||
let label_info = self.label_info(area.height.saturating_sub(1));
|
||||
|
||||
let bars_area = Rect {
|
||||
height: area.height - label_info.height,
|
||||
height: area.height.saturating_sub(label_info.height),
|
||||
..area
|
||||
};
|
||||
|
||||
@@ -722,6 +722,7 @@ mod tests {
|
||||
use ratatui_core::layout::Alignment;
|
||||
use ratatui_core::style::{Color, Modifier, Stylize};
|
||||
use ratatui_core::text::Span;
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
use crate::borders::BorderType;
|
||||
@@ -1472,4 +1473,35 @@ mod tests {
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::horizontal(Direction::Horizontal)]
|
||||
#[case::vertical(Direction::Vertical)]
|
||||
fn render_in_minimal_buffer(#[case] direction: Direction) {
|
||||
let chart = BarChart::default()
|
||||
.data(&[("A", 1), ("B", 2)])
|
||||
.bar_width(3)
|
||||
.bar_gap(1)
|
||||
.direction(direction);
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
// This should not panic, even if the buffer is too small to render the chart.
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::horizontal(Direction::Horizontal)]
|
||||
#[case::vertical(Direction::Vertical)]
|
||||
fn render_in_zero_size_buffer(#[case] direction: Direction) {
|
||||
let chart = BarChart::default()
|
||||
.data(&[("A", 1), ("B", 2)])
|
||||
.bar_width(3)
|
||||
.bar_gap(1)
|
||||
.direction(direction);
|
||||
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -933,41 +933,74 @@ impl Block<'_> {
|
||||
}
|
||||
|
||||
/// Render titles in the center of the block
|
||||
///
|
||||
/// Currently this method aligns the titles to the left inside a centered area. This is not
|
||||
/// ideal and should be fixed in the future to align the titles to the center of the block and
|
||||
/// truncate both sides of the titles if the block is too small to fit all titles.
|
||||
#[expect(clippy::similar_names)]
|
||||
fn render_center_titles(&self, position: TitlePosition, area: Rect, buf: &mut Buffer) {
|
||||
let area = self.titles_area(area, position);
|
||||
let titles = self
|
||||
.filtered_titles(position, Alignment::Center)
|
||||
.collect_vec();
|
||||
// titles are rendered with a space after each title except the last one
|
||||
let total_width = titles
|
||||
.iter()
|
||||
.map(|title| title.width() as u16 + 1) // space between titles
|
||||
.map(|title| title.width() as u16 + 1)
|
||||
.sum::<u16>()
|
||||
.saturating_sub(1); // no space for the last title
|
||||
.saturating_sub(1);
|
||||
|
||||
let titles_area = self.titles_area(area, position);
|
||||
let mut titles_area = Rect {
|
||||
x: titles_area.left() + (titles_area.width.saturating_sub(total_width) / 2),
|
||||
..titles_area
|
||||
};
|
||||
if total_width <= area.width {
|
||||
self.render_centered_titles_without_truncation(titles, total_width, area, buf);
|
||||
} else {
|
||||
self.render_centered_titles_with_truncation(titles, total_width, area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_centered_titles_without_truncation(
|
||||
&self,
|
||||
titles: Vec<&Line<'_>>,
|
||||
total_width: u16,
|
||||
area: Rect,
|
||||
buf: &mut Buffer,
|
||||
) {
|
||||
// titles fit in the area, center them
|
||||
let x = area.left() + area.width.saturating_sub(total_width) / 2;
|
||||
let mut area = Rect { x, ..area };
|
||||
for title in titles {
|
||||
if titles_area.is_empty() {
|
||||
break;
|
||||
}
|
||||
let title_width = title.width() as u16;
|
||||
let title_area = Rect {
|
||||
width: title_width.min(titles_area.width),
|
||||
..titles_area
|
||||
};
|
||||
let width = title.width() as u16;
|
||||
let title_area = Rect { width, ..area };
|
||||
buf.set_style(title_area, self.titles_style);
|
||||
title.render(title_area, buf);
|
||||
// Move the rendering cursor to the right, leaving 1 column space.
|
||||
area.x = area.x.saturating_add(width + 1);
|
||||
area.width = area.width.saturating_sub(width + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// bump the titles area to the right and reduce its width
|
||||
titles_area.x = titles_area.x.saturating_add(title_width + 1);
|
||||
titles_area.width = titles_area.width.saturating_sub(title_width + 1);
|
||||
fn render_centered_titles_with_truncation(
|
||||
&self,
|
||||
titles: Vec<&Line<'_>>,
|
||||
total_width: u16,
|
||||
mut area: Rect,
|
||||
buf: &mut Buffer,
|
||||
) {
|
||||
// titles do not fit in the area, truncate the left side using an offset. The right side
|
||||
// is truncated by the area width.
|
||||
let mut offset = total_width.saturating_sub(area.width) / 2;
|
||||
for title in titles {
|
||||
if area.is_empty() {
|
||||
break;
|
||||
}
|
||||
let width = area.width.min(title.width() as u16).saturating_sub(offset);
|
||||
let title_area = Rect { width, ..area };
|
||||
buf.set_style(title_area, self.titles_style);
|
||||
if offset > 0 {
|
||||
// truncate the left side of the title to fit the area
|
||||
title.clone().right_aligned().render(title_area, buf);
|
||||
offset = offset.saturating_sub(width).saturating_sub(1);
|
||||
} else {
|
||||
// truncate the right side of the title to fit the area if needed
|
||||
title.clone().left_aligned().render(title_area, buf);
|
||||
}
|
||||
// Leave 1 column of spacing between titles.
|
||||
area.x = area.x.saturating_add(width + 1);
|
||||
area.width = area.width.saturating_sub(width + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1936,6 +1969,92 @@ mod tests {
|
||||
pretty_assertions::assert_eq!(Buffer::with_lines(expected.lines()), buffer);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::replace(MergeStrategy::Replace, Buffer::with_lines([
|
||||
"┏block top━━┓",
|
||||
"┃ ┃",
|
||||
"┗━━━━━━━━━━━┛",
|
||||
"│ │",
|
||||
"└───────────┘",
|
||||
])
|
||||
)]
|
||||
#[case::replace(MergeStrategy::Exact, Buffer::with_lines([
|
||||
"┏block top━━┓",
|
||||
"┃ ┃",
|
||||
"┡block btm━━┩",
|
||||
"│ │",
|
||||
"└───────────┘",
|
||||
])
|
||||
)]
|
||||
#[case::replace(MergeStrategy::Fuzzy, Buffer::with_lines([
|
||||
"┏block top━━┓",
|
||||
"┃ ┃",
|
||||
"┡block btm━━┩",
|
||||
"│ │",
|
||||
"└───────────┘",
|
||||
])
|
||||
)]
|
||||
fn merged_titles_bottom_first(#[case] strategy: MergeStrategy, #[case] expected: Buffer) {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
|
||||
Block::bordered()
|
||||
.title("block btm")
|
||||
.render(Rect::new(0, 2, 13, 3), &mut buffer);
|
||||
Block::bordered()
|
||||
.title("block top")
|
||||
.border_type(BorderType::Thick)
|
||||
.merge_borders(strategy)
|
||||
.render(Rect::new(0, 0, 13, 3), &mut buffer);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::replace(MergeStrategy::Replace, Buffer::with_lines([
|
||||
"┏block top━━┓",
|
||||
"┃ ┃",
|
||||
"┌block btm──┐",
|
||||
"│ │",
|
||||
"└───────────┘",
|
||||
])
|
||||
)]
|
||||
#[case::replace(MergeStrategy::Exact, Buffer::with_lines([
|
||||
"┏block top━━┓",
|
||||
"┃ ┃",
|
||||
"┞block btm──┦",
|
||||
"│ │",
|
||||
"└───────────┘",
|
||||
])
|
||||
)]
|
||||
#[case::replace(MergeStrategy::Fuzzy, Buffer::with_lines([
|
||||
"┏block top━━┓",
|
||||
"┃ ┃",
|
||||
"┞block btm──┦",
|
||||
"│ │",
|
||||
"└───────────┘",
|
||||
])
|
||||
)]
|
||||
fn merged_titles_top_first(#[case] strategy: MergeStrategy, #[case] expected: Buffer) {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 13, 5));
|
||||
Block::bordered()
|
||||
.title("block top")
|
||||
.border_type(BorderType::Thick)
|
||||
.render(Rect::new(0, 0, 13, 3), &mut buffer);
|
||||
Block::bordered()
|
||||
.title("block btm")
|
||||
.merge_borders(strategy)
|
||||
.render(Rect::new(0, 2, 13, 3), &mut buffer);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_titles() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||
Block::new()
|
||||
.title("L12")
|
||||
.title("L34")
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines(["L12 L34 "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn left_titles_truncated() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||
@@ -1946,12 +2065,16 @@ mod tests {
|
||||
assert_eq!(buffer, Buffer::with_lines(["L12345 L67"]));
|
||||
}
|
||||
|
||||
/// Note: this test is probably not what you'd expect, but it is how it works in the current
|
||||
/// implementation. Update this if the behavior changes.
|
||||
///
|
||||
/// This probably should render the titles centered as a whole and then truncate both titles
|
||||
/// to fit, but instead it renders each title and truncates them individually. This causes the
|
||||
/// left title to be displayed in full, while the right title is truncated.
|
||||
#[test]
|
||||
fn center_titles() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||
Block::new()
|
||||
.title(Line::from("C12").centered())
|
||||
.title(Line::from("C34").centered())
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" C12 C34 "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn center_titles_truncated() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||
@@ -1959,7 +2082,17 @@ mod tests {
|
||||
.title(Line::from("C12345").centered())
|
||||
.title(Line::from("C67890").centered())
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines(["C12345 678"]));
|
||||
assert_eq!(buffer, Buffer::with_lines(["12345 C678"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_titles() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
|
||||
Block::new()
|
||||
.title(Line::from("R12").right_aligned())
|
||||
.title(Line::from("R34").right_aligned())
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" R12 R34"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2001,4 +2134,25 @@ mod tests {
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" C1R67890"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
// This should not panic, even if the buffer is too small to render the block.
|
||||
Block::bordered()
|
||||
.title("I'm too big for this buffer")
|
||||
.padding(Padding::uniform(10))
|
||||
.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines(["┌"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
Block::bordered()
|
||||
.title("I'm too big for this buffer")
|
||||
.padding(Padding::uniform(10))
|
||||
.render(buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ bitflags! {
|
||||
#[derive(Default, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Borders: u8 {
|
||||
/// Show no border (default)
|
||||
const NONE = 0b0000;
|
||||
/// Show the top border
|
||||
const TOP = 0b0001;
|
||||
/// Show the right border
|
||||
@@ -25,6 +23,11 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
impl Borders {
|
||||
/// Show no border (default)
|
||||
pub const NONE: Self = Self::empty();
|
||||
}
|
||||
|
||||
/// The type of border of a [`Block`](crate::block::Block).
|
||||
///
|
||||
/// See the [`borders`](crate::block::Block::borders) method of `Block` to configure its borders.
|
||||
@@ -172,13 +175,11 @@ impl BorderType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement the `Debug` trait for the `Borders` bitflags. This is a manual implementation to
|
||||
/// display the flags in a more readable way. The default implementation would display the
|
||||
/// flags as 'Border(0x0)' for `Borders::NONE` for example.
|
||||
impl fmt::Debug for Borders {
|
||||
/// Display the Borders bitflags as a list of names. For example, `Borders::NONE` will be
|
||||
/// displayed as `NONE` and `Borders::ALL` will be displayed as `ALL`. If multiple flags are
|
||||
/// set, they will be displayed separated by a pipe character.
|
||||
/// Display the Borders bitflags as a list of names.
|
||||
///
|
||||
/// `Borders::NONE` is displayed as `NONE` and `Borders::ALL` is displayed as `ALL`. If multiple
|
||||
/// flags are set, they are otherwise displayed separated by a pipe character.
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_empty() {
|
||||
return write!(f, "NONE");
|
||||
@@ -186,17 +187,12 @@ impl fmt::Debug for Borders {
|
||||
if self.is_all() {
|
||||
return write!(f, "ALL");
|
||||
}
|
||||
let mut first = true;
|
||||
for (name, border) in self.iter_names() {
|
||||
if border == Self::NONE {
|
||||
continue;
|
||||
}
|
||||
if first {
|
||||
write!(f, "{name}")?;
|
||||
first = false;
|
||||
} else {
|
||||
write!(f, " | {name}")?;
|
||||
}
|
||||
let mut names = self.iter_names().map(|(name, _)| name);
|
||||
if let Some(first) = names.next() {
|
||||
write!(f, "{first}")?;
|
||||
}
|
||||
for name in names {
|
||||
write!(f, " | {name}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -302,4 +302,27 @@ mod tests {
|
||||
fn test_today() {
|
||||
CalendarEventStore::today(Style::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let calendar = Monthly::new(
|
||||
Date::from_calendar_date(1984, Month::January, 1).unwrap(),
|
||||
CalendarEventStore::default(),
|
||||
);
|
||||
// This should not panic, even if the buffer is too small to render the calendar.
|
||||
calendar.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let calendar = Monthly::new(
|
||||
Date::from_calendar_date(1984, Month::January, 1).unwrap(),
|
||||
CalendarEventStore::default(),
|
||||
);
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
calendar.render(buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,4 +976,27 @@ mod tests {
|
||||
b_grid.paint(usize::MAX, usize::MAX, Color::Red);
|
||||
c_grid.paint(usize::MAX, usize::MAX, Color::Red);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let canvas = Canvas::default()
|
||||
.x_bounds([0.0, 10.0])
|
||||
.y_bounds([0.0, 10.0])
|
||||
.paint(|_ctx| {});
|
||||
// This should not panic, even if the buffer is too small to render the canvas.
|
||||
canvas.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let canvas = Canvas::default()
|
||||
.x_bounds([0.0, 10.0])
|
||||
.y_bounds([0.0, 10.0])
|
||||
.paint(|_ctx| {});
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
canvas.render(buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1547,4 +1547,25 @@ mod tests {
|
||||
]);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let chart = Chart::new(vec![Dataset::default().data(&[(0.0, 0.0), (1.0, 1.0)])])
|
||||
.x_axis(Axis::default().bounds([0.0, 1.0]))
|
||||
.y_axis(Axis::default().bounds([0.0, 1.0]));
|
||||
// This should not panic, even if the buffer is too small to render the chart.
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines(["•"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let chart = Chart::new(vec![Dataset::default().data(&[(0.0, 0.0), (1.0, 1.0)])])
|
||||
.x_axis(Axis::default().bounds([0.0, 1.0]))
|
||||
.y_axis(Axis::default().bounds([0.0, 1.0]));
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
chart.render(buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ impl Widget for &LineGauge<'_> {
|
||||
}
|
||||
|
||||
let ratio = self.ratio;
|
||||
let default_label = Line::from(format!("{:.0}%", ratio * 100.0));
|
||||
let default_label = Line::from(format!("{:3.0}%", ratio * 100.0));
|
||||
let label = self.label.as_ref().unwrap_or(&default_label);
|
||||
let (col, row) = buf.set_line(gauge_area.left(), gauge_area.top(), label, gauge_area.width);
|
||||
let start = col + 1;
|
||||
@@ -586,4 +586,38 @@ mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer_gauge() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let gauge = Gauge::default().percent(50);
|
||||
// This should not panic, even if the buffer is too small to render the gauge.
|
||||
gauge.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines(["5"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer_line_gauge() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let line_gauge = LineGauge::default().ratio(0.5);
|
||||
// This should not panic, even if the buffer is too small to render the line gauge.
|
||||
line_gauge.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer_gauge() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let gauge = Gauge::default().percent(50);
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
gauge.render(buffer.area, &mut buffer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer_line_gauge() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let line_gauge = LineGauge::default().ratio(0.5);
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
line_gauge.render(buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#![no_std]
|
||||
// show the feature flags in the generated documentation
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/logo.png",
|
||||
html_favicon_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/favicon.ico"
|
||||
|
||||
@@ -625,4 +625,33 @@ mod tests {
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let mut state = ListState::default().with_selected(None);
|
||||
let items = vec![
|
||||
ListItem::new("Item 1"),
|
||||
ListItem::new("Item 2"),
|
||||
ListItem::new("Item 3"),
|
||||
];
|
||||
let list = List::new(items);
|
||||
// This should not panic, even if the buffer is too small to render the list.
|
||||
list.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(buffer, Buffer::with_lines(["I"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let mut state = ListState::default().with_selected(None);
|
||||
let items = vec![
|
||||
ListItem::new("Item 1"),
|
||||
ListItem::new("Item 2"),
|
||||
ListItem::new("Item 3"),
|
||||
];
|
||||
let list = List::new(items);
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
list.render(buffer.area, &mut buffer, &mut state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,4 +236,25 @@ mod tests {
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::tiny(Size::Tiny, Buffer::with_lines(["▛"]))]
|
||||
#[case::small(Size::Small, Buffer::with_lines(["█"]))]
|
||||
fn render_in_minimal_buffer(#[case] size: Size, #[case] expected: Buffer) {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let logo = RatatuiLogo::new(size);
|
||||
// This should not panic, even if the buffer is too small to render the logo.
|
||||
logo.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::tiny(Size::Tiny)]
|
||||
#[case::small(Size::Small)]
|
||||
fn render_in_zero_size_buffer(#[case] size: Size) {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let logo = RatatuiLogo::new(size);
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
logo.render(buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,10 +135,21 @@ impl Widget for RatatuiMascot {
|
||||
/// The logo colors are hardcorded in the widget.
|
||||
/// The eye color depends on whether it's open / blinking
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let area = area.intersection(buf.area);
|
||||
if area.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (y, (line1, line2)) in RATATUI_MASCOT.lines().tuples().enumerate() {
|
||||
for (x, (ch1, ch2)) in line1.chars().zip(line2.chars()).enumerate() {
|
||||
let x = area.left() + x as u16;
|
||||
let y = area.top() + y as u16;
|
||||
|
||||
// Check if coordinates are within the buffer area
|
||||
if x >= area.right() || y >= area.bottom() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cell = &mut buf[(x, y)];
|
||||
// given two cells which make up the top and bottom of the character,
|
||||
// Foreground color should be the non-space, non-terminal
|
||||
@@ -229,4 +240,21 @@ mod tests {
|
||||
.collect::<String>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let mascot = RatatuiMascot::new();
|
||||
// This should not panic, even if the buffer is too small to render the mascot.
|
||||
mascot.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let mascot = RatatuiMascot::new();
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
mascot.render(buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ impl<'a> Paragraph<'a> {
|
||||
/// convention across the crate.
|
||||
///
|
||||
/// For more information about future scrolling design and concerns, see [RFC: Design of
|
||||
/// Scrollable Widgets](https://github.com/ratatui/ratatui/issues/174) on GitHub.
|
||||
/// Scrollable Widgets](https://github.com/ratatui/ratatui/discussions/1924) on GitHub.
|
||||
#[must_use = "method moves the value of self and returns the modified value"]
|
||||
pub const fn scroll(mut self, offset: (Vertical, Horizontal)) -> Self {
|
||||
self.scroll = Position {
|
||||
@@ -406,6 +406,7 @@ impl Widget for Paragraph<'_> {
|
||||
|
||||
impl Widget for &Paragraph<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let area = area.intersection(buf.area);
|
||||
buf.set_style(area, self.style);
|
||||
self.block.as_ref().render(area, buf);
|
||||
let inner = self.block.inner_if_some(area);
|
||||
@@ -500,6 +501,7 @@ mod tests {
|
||||
use ratatui_core::style::{Color, Modifier, Style, Stylize};
|
||||
use ratatui_core::text::{Line, Span, Text};
|
||||
use ratatui_core::widgets::Widget;
|
||||
use rstest::rstest;
|
||||
|
||||
use super::*;
|
||||
use crate::block::TitlePosition;
|
||||
@@ -1193,4 +1195,45 @@ mod tests {
|
||||
expected.set_style(Rect::new(1, 1, 11, 1), Style::default().fg(Color::Green));
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::bottom(Rect::new(0, 5, 15, 1))]
|
||||
#[case::right(Rect::new(20, 0, 15, 1))]
|
||||
#[case::bottom_right(Rect::new(20, 5, 15, 1))]
|
||||
fn test_render_paragraph_out_of_bounds(#[case] area: Rect) {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 3));
|
||||
Paragraph::new("Beyond the pale").render(area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines(vec![" "; 3]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_out_of_bounds() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 15, 3));
|
||||
Paragraph::new("Hello World").render(Rect::new(10, 0, 10, 3), &mut buffer);
|
||||
assert_eq!(
|
||||
buffer,
|
||||
Buffer::with_lines(vec![
|
||||
" Hello",
|
||||
" ",
|
||||
" ",
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let paragraph = Paragraph::new("Lorem ipsum");
|
||||
// This should not panic, even if the buffer is too small to render the paragraph.
|
||||
paragraph.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines(["L"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let paragraph = Paragraph::new("Lorem ipsum");
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
paragraph.render(buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,6 +970,8 @@ mod tests {
|
||||
#[case::position_8("<---#####>", 8, 10)]
|
||||
#[case::position_9("<----####>", 9, 10)]
|
||||
#[case::position_one_out_of_bounds("<----####>", 10, 10)]
|
||||
#[case::position_few_out_of_bounds("<----####>", 15, 10)]
|
||||
#[case::position_very_many_out_of_bounds("<----####>", 500, 10)]
|
||||
fn render_scrollbar_vertical_left(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
@@ -1000,7 +1002,9 @@ mod tests {
|
||||
#[case::position_8("<---#####>", 8, 10)]
|
||||
#[case::position_9("<----####>", 9, 10)]
|
||||
#[case::position_one_out_of_bounds("<----####>", 10, 10)]
|
||||
fn render_scrollbar_vertical_rightl(
|
||||
#[case::position_few_out_of_bounds("<----####>", 15, 10)]
|
||||
#[case::position_very_many_out_of_bounds("<----####>", 500, 10)]
|
||||
fn render_scrollbar_vertical_right(
|
||||
#[case] expected: &str,
|
||||
#[case] position: usize,
|
||||
#[case] content_length: usize,
|
||||
@@ -1089,4 +1093,31 @@ mod tests {
|
||||
let mut state = ScrollbarState::new(10);
|
||||
scrollbar.render(zero_width_area, &mut buffer, &mut state);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::vertical_left(ScrollbarOrientation::VerticalLeft)]
|
||||
#[case::vertical_right(ScrollbarOrientation::VerticalRight)]
|
||||
#[case::horizontal_top(ScrollbarOrientation::HorizontalTop)]
|
||||
#[case::horizontal_bottom(ScrollbarOrientation::HorizontalBottom)]
|
||||
fn render_in_minimal_buffer(#[case] orientation: ScrollbarOrientation) {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let scrollbar = Scrollbar::new(orientation);
|
||||
let mut state = ScrollbarState::new(10).position(5);
|
||||
// This should not panic, even if the buffer is too small to render the scrollbar.
|
||||
scrollbar.render(buffer.area, &mut buffer, &mut state);
|
||||
assert_eq!(buffer, Buffer::with_lines([" "]));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::vertical_left(ScrollbarOrientation::VerticalLeft)]
|
||||
#[case::vertical_right(ScrollbarOrientation::VerticalRight)]
|
||||
#[case::horizontal_top(ScrollbarOrientation::HorizontalTop)]
|
||||
#[case::horizontal_bottom(ScrollbarOrientation::HorizontalBottom)]
|
||||
fn render_in_zero_size_buffer(#[case] orientation: ScrollbarOrientation) {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let scrollbar = Scrollbar::new(orientation);
|
||||
let mut state = ScrollbarState::new(10).position(5);
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
scrollbar.render(buffer.area, &mut buffer, &mut state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,4 +699,25 @@ mod tests {
|
||||
.remove_modifier(Modifier::DIM)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let sparkline = Sparkline::default()
|
||||
.data([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
||||
.max(10);
|
||||
// This should not panic, even if the buffer is too small to render the sparkline.
|
||||
sparkline.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let sparkline = Sparkline::default()
|
||||
.data([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
|
||||
.max(10);
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
sparkline.render(buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2273,4 +2273,33 @@ mod tests {
|
||||
let column_count = table.column_count();
|
||||
assert_eq!(column_count, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let rows = vec![
|
||||
Row::new(vec!["Cell1", "Cell2", "Cell3"]),
|
||||
Row::new(vec!["Cell4", "Cell5", "Cell6"]),
|
||||
];
|
||||
let table = Table::new(rows, [Constraint::Length(10); 3])
|
||||
.header(Row::new(vec!["Header1", "Header2", "Header3"]))
|
||||
.footer(Row::new(vec!["Footer1", "Footer2", "Footer3"]));
|
||||
// This should not panic, even if the buffer is too small to render the table.
|
||||
Widget::render(table, buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let rows = vec![
|
||||
Row::new(vec!["Cell1", "Cell2", "Cell3"]),
|
||||
Row::new(vec!["Cell4", "Cell5", "Cell6"]),
|
||||
];
|
||||
let table = Table::new(rows, [Constraint::Length(10); 3])
|
||||
.header(Row::new(vec!["Header1", "Header2", "Header3"]))
|
||||
.footer(Row::new(vec!["Footer1", "Footer2", "Footer3"]));
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
Widget::render(table, buffer.area, &mut buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use ratatui_core::style::{Style, Styled};
|
||||
use ratatui_core::symbols;
|
||||
use ratatui_core::text::{Line, Span};
|
||||
use ratatui_core::widgets::Widget;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::block::{Block, BlockExt};
|
||||
|
||||
@@ -446,6 +447,64 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl UnicodeWidthStr for Tabs<'_> {
|
||||
/// Returns the width of the rendered tabs.
|
||||
///
|
||||
/// The width includes the titles, dividers, and padding. It does not include any borders added
|
||||
/// by the optional block.
|
||||
///
|
||||
/// Characters in the Ambiguous category are considered single-width.
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::Tabs;
|
||||
/// use unicode_width::UnicodeWidthStr;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3"]);
|
||||
/// assert_eq!(tabs.width(), 20); // " Tab1 │ Tab2 │ Tab3 "
|
||||
/// ```
|
||||
fn width(&self) -> usize {
|
||||
let titles_width = self.titles.iter().map(Line::width).sum::<usize>();
|
||||
let title_count = self.titles.len();
|
||||
let divider_count = title_count.saturating_sub(1);
|
||||
let divider_width = divider_count.saturating_mul(self.divider.width());
|
||||
let left_padding_width = title_count.saturating_mul(self.padding_left.width());
|
||||
let right_padding_width = title_count.saturating_mul(self.padding_right.width());
|
||||
titles_width + divider_width + left_padding_width + right_padding_width
|
||||
}
|
||||
|
||||
/// Returns the width of the rendered tabs, accounting for CJK characters.
|
||||
///
|
||||
/// This is probably the wrong method to use in most contexts that Ratatui applications care
|
||||
/// about as it doesn't correlate with the visual representation of most terminals. Consider
|
||||
/// using [`Tabs::width`] instead.
|
||||
///
|
||||
/// The width includes the titles, dividers, and padding. It does not include any borders added
|
||||
/// by the optional block.
|
||||
///
|
||||
/// Characters in the Ambiguous category are considered double-width.
|
||||
///
|
||||
/// ```
|
||||
/// use ratatui::widgets::Tabs;
|
||||
/// use unicode_width::UnicodeWidthStr;
|
||||
///
|
||||
/// let tabs = Tabs::new(vec!["你", "好", "世界"]);
|
||||
/// assert_eq!("你".width_cjk(), 2);
|
||||
/// assert_eq!("好".width_cjk(), 2);
|
||||
/// assert_eq!("世界".width_cjk(), 4);
|
||||
/// assert_eq!("│".width_cjk(), 2); // this is correct for cjk
|
||||
/// assert_eq!(tabs.width_cjk(), 18); // " 你 │ 好 │ 世界 "
|
||||
/// ```
|
||||
fn width_cjk(&self) -> usize {
|
||||
let titles_width = self.titles.iter().map(Line::width_cjk).sum::<usize>();
|
||||
let title_count = self.titles.len();
|
||||
let divider_count = title_count.saturating_sub(1);
|
||||
let divider_width = divider_count.saturating_mul(self.divider.width_cjk());
|
||||
let left_padding_width = title_count.saturating_mul(self.padding_left.width_cjk());
|
||||
let right_padding_width = title_count.saturating_mul(self.padding_right.width_cjk());
|
||||
titles_width + divider_width + left_padding_width + right_padding_width
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::{format, vec};
|
||||
@@ -676,4 +735,71 @@ mod tests {
|
||||
Style::default().black().on_white().bold().not_italic()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_minimal_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, 1, 1));
|
||||
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"])
|
||||
.select(1)
|
||||
.divider("|");
|
||||
// This should not panic, even if the buffer is too small to render the tabs.
|
||||
tabs.render(buffer.area, &mut buffer);
|
||||
assert_eq!(buffer, Buffer::with_lines([" "]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_in_zero_size_buffer() {
|
||||
let mut buffer = Buffer::empty(Rect::ZERO);
|
||||
let tabs = Tabs::new(vec!["Tab1", "Tab2", "Tab3", "Tab4"])
|
||||
.select(1)
|
||||
.divider("|");
|
||||
// This should not panic, even if the buffer has zero size.
|
||||
tabs.render(buffer.area, &mut buffer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_basic() {
|
||||
let tabs = Tabs::new(vec!["A", "BB", "CCC"]);
|
||||
let rendered = " A │ BB │ CCC ";
|
||||
assert_eq!(tabs.width(), rendered.width());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_no_padding() {
|
||||
let tabs = Tabs::new(vec!["A", "BB", "CCC"]).padding("", "");
|
||||
let rendered = "A│BB│CCC";
|
||||
assert_eq!(tabs.width(), rendered.width());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_custom_divider_and_padding() {
|
||||
let tabs = Tabs::new(vec!["A", "BB", "CCC"])
|
||||
.divider("--")
|
||||
.padding("X", "YY");
|
||||
let rendered = "XAYY--XBBYY--XCCCYY";
|
||||
assert_eq!(tabs.width(), rendered.width());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_empty_titles() {
|
||||
let tabs = Tabs::new(Vec::<&str>::new());
|
||||
let rendered = "";
|
||||
assert_eq!(tabs.width(), rendered.width());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_cjk() {
|
||||
let tabs = Tabs::new(vec!["你", "好", "世界"]);
|
||||
let rendered = " 你 │ 好 │ 世界 ";
|
||||
assert_eq!(tabs.width_cjk(), UnicodeWidthStr::width_cjk(rendered));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_width_cjk_custom_padding_and_divider() {
|
||||
let tabs = Tabs::new(vec!["你", "好", "世界"])
|
||||
.divider("分")
|
||||
.padding("左", "右");
|
||||
let rendered = "左你右分左好右分左世界右";
|
||||
assert_eq!(tabs.width_cjk(), UnicodeWidthStr::width_cjk(rendered));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "ratatui"
|
||||
description = "A library that's all about cooking up terminal user interfaces"
|
||||
version = "0.30.0-alpha.5"
|
||||
version = "0.30.0-beta.0"
|
||||
authors.workspace = true
|
||||
documentation.workspace = true
|
||||
repository.workspace = true
|
||||
@@ -59,6 +59,9 @@ layout-cache = ["std", "ratatui-core/layout-cache"]
|
||||
## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color).
|
||||
palette = ["std", "ratatui-core/palette", "dep:palette"]
|
||||
|
||||
## enables portable-atomic integration for targets that don't support atomic types.
|
||||
portable-atomic = ["ratatui-core/portable-atomic"]
|
||||
|
||||
## Use terminal scrolling regions to make some operations less prone to
|
||||
## flickering. (i.e. Terminal::insert_before).
|
||||
scrolling-regions = [
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod main {
|
||||
pub mod barchart;
|
||||
pub mod block;
|
||||
pub mod buffer;
|
||||
pub mod constraints;
|
||||
pub mod line;
|
||||
pub mod list;
|
||||
pub mod paragraph;
|
||||
@@ -21,4 +22,5 @@ criterion::criterion_main!(
|
||||
rect::benches,
|
||||
sparkline::benches,
|
||||
table::benches,
|
||||
constraints::benches,
|
||||
);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use criterion::{BenchmarkId, Criterion, black_box};
|
||||
use std::hint::black_box;
|
||||
|
||||
use criterion::{BenchmarkId, Criterion};
|
||||
use ratatui::buffer::{Buffer, Cell};
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::text::Line;
|
||||
|
||||
60
ratatui/benches/main/constraints.rs
Normal file
60
ratatui/benches/main/constraints.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use std::hint::black_box;
|
||||
use std::rc::Rc;
|
||||
|
||||
use criterion::{Criterion, criterion_group};
|
||||
use ratatui::layout::Constraint::{Fill, Length, Max, Min, Percentage, Ratio};
|
||||
use ratatui::layout::{Layout, Rect};
|
||||
|
||||
const SPLIT_BY: u16 = 10;
|
||||
|
||||
fn layout_split(criterion: &mut Criterion) {
|
||||
for size in [16, 64, 256] {
|
||||
let mut group = criterion.benchmark_group(format!("constraints {size}x{size}"));
|
||||
let area = Rect::new(0, 0, size, size);
|
||||
group.bench_function("Fill", |bencher| {
|
||||
bencher.iter(|| layout_fill(black_box(area)));
|
||||
});
|
||||
group.bench_function("Length", |bencher| {
|
||||
bencher.iter(|| layout_length(black_box(area)));
|
||||
});
|
||||
group.bench_function("Max", |bencher| {
|
||||
bencher.iter(|| layout_max(black_box(area)));
|
||||
});
|
||||
group.bench_function("Min", |bencher| {
|
||||
bencher.iter(|| layout_min(black_box(area)));
|
||||
});
|
||||
group.bench_function("Percentage", |bencher| {
|
||||
bencher.iter(|| layout_percentage(black_box(area)));
|
||||
});
|
||||
group.bench_function("Ratio", |bencher| {
|
||||
bencher.iter(|| layout_ratio(black_box(area)));
|
||||
});
|
||||
group.finish();
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_fill(area: Rect) -> Rc<[Rect]> {
|
||||
Layout::vertical([Fill(1); SPLIT_BY as usize]).split(area)
|
||||
}
|
||||
|
||||
fn layout_length(area: Rect) -> Rc<[Rect]> {
|
||||
Layout::vertical([Length(area.width / SPLIT_BY); SPLIT_BY as usize]).split(area)
|
||||
}
|
||||
|
||||
fn layout_max(area: Rect) -> Rc<[Rect]> {
|
||||
Layout::vertical([Max(area.width / SPLIT_BY); SPLIT_BY as usize]).split(area)
|
||||
}
|
||||
|
||||
fn layout_min(area: Rect) -> Rc<[Rect]> {
|
||||
Layout::vertical([Min(area.width / SPLIT_BY); SPLIT_BY as usize]).split(area)
|
||||
}
|
||||
|
||||
fn layout_percentage(area: Rect) -> Rc<[Rect]> {
|
||||
Layout::vertical([Percentage(100 / SPLIT_BY); SPLIT_BY as usize]).split(area)
|
||||
}
|
||||
|
||||
fn layout_ratio(area: Rect) -> Rc<[Rect]> {
|
||||
Layout::vertical([Ratio(1, SPLIT_BY.into()); SPLIT_BY as usize]).split(area)
|
||||
}
|
||||
|
||||
criterion_group!(benches, layout_split);
|
||||
@@ -1,4 +1,6 @@
|
||||
use criterion::{BatchSize, Bencher, BenchmarkId, Criterion, black_box, criterion_group};
|
||||
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};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use criterion::{BatchSize, Bencher, BenchmarkId, Criterion, black_box, criterion_group};
|
||||
use std::hint::black_box;
|
||||
|
||||
use criterion::{BatchSize, Bencher, BenchmarkId, Criterion, criterion_group};
|
||||
use ratatui::layout::Rect;
|
||||
|
||||
fn rect_iters_benchmark(c: &mut Criterion) {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
#![no_std]
|
||||
// show the feature flags in the generated documentation
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/logo.png",
|
||||
html_favicon_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/favicon.ico"
|
||||
)]
|
||||
#![warn(missing_docs)]
|
||||
//! 
|
||||
//! 
|
||||
//!
|
||||
//! <div align="center">
|
||||
//!
|
||||
|
||||
@@ -233,20 +233,20 @@ fn widgets_line_gauge_renders() {
|
||||
})
|
||||
.unwrap();
|
||||
let mut expected = Buffer::with_lines([
|
||||
"43% ────────────────",
|
||||
" 43% ───────────────",
|
||||
"┌Gauge 2───────────┐",
|
||||
"│21% ━━━━━━━━━━━━━━│",
|
||||
"│ 21% ━━━━━━━━━━━━━│",
|
||||
"└──────────────────┘",
|
||||
"50% ──────── ",
|
||||
"80% ████████████░░░░",
|
||||
" 50% ─────── ",
|
||||
" 80% ████████████░░░",
|
||||
]);
|
||||
for col in 4..10 {
|
||||
for col in 5..11 {
|
||||
expected[(col, 0)].set_fg(Color::Green);
|
||||
}
|
||||
for col in 10..20 {
|
||||
for col in 11..20 {
|
||||
expected[(col, 0)].set_fg(Color::White);
|
||||
}
|
||||
for col in 5..7 {
|
||||
for col in 6..8 {
|
||||
expected[(col, 2)].set_fg(Color::Green);
|
||||
}
|
||||
terminal.backend().assert_buffer(&expected);
|
||||
|
||||
21
rustfmt.toml
21
rustfmt.toml
@@ -1,12 +1,23 @@
|
||||
# configuration for https://rust-lang.github.io/rustfmt/
|
||||
# Configuration for https://rust-lang.github.io/rustfmt/
|
||||
edition = "2024"
|
||||
|
||||
# E.g. Foo { a, b, c }
|
||||
use_field_init_shorthand = true
|
||||
|
||||
# unstable options
|
||||
# Unstable options. These require a nightly compiler to use (cargo +nightly fmt).
|
||||
|
||||
# We choose a wider comment width, as this tends to make comments more readable than truncating at
|
||||
# 80 characters.
|
||||
wrap_comments = true
|
||||
comment_width = 100
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_matchers = true
|
||||
|
||||
# `#[doc = "..."]` -> `/// ...`
|
||||
normalize_doc_attributes = true
|
||||
|
||||
# We choose module granularity for imports as this tends to have the best balance between low
|
||||
# linecount while making it simple to understand diffs
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Module"
|
||||
normalize_doc_attributes = true
|
||||
wrap_comments = true
|
||||
|
||||
format_macro_matchers = true
|
||||
|
||||
@@ -5,12 +5,9 @@ publish = false
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cargo_metadata = "0.20"
|
||||
clap = { version = "4.5.40", features = ["derive"] }
|
||||
clap-cargo = { version = "0.15.1", features = ["cargo_metadata"] }
|
||||
clap-verbosity-flag = { version = "3.0.3", default-features = false, features = ["tracing"] }
|
||||
color-eyre = "0.6.5"
|
||||
duct = "1.0.0"
|
||||
itertools.workspace = true
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18" }
|
||||
clap.workspace = true
|
||||
clap-verbosity-flag = { version = "3", default-features = false, features = ["tracing"] }
|
||||
color-eyre.workspace = true
|
||||
duct = "1"
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
@@ -13,7 +13,7 @@ use self::clippy::Clippy;
|
||||
use self::docs::Docs;
|
||||
use self::format::Format;
|
||||
use self::typos::Typos;
|
||||
use crate::{ExpressionExt, Run, run_cargo};
|
||||
use crate::{CROSSTERM_COMMON_FEATURES, ExpressionExt, Run, run_cargo};
|
||||
|
||||
mod backend;
|
||||
mod check;
|
||||
@@ -22,6 +22,7 @@ mod coverage;
|
||||
mod docs;
|
||||
mod format;
|
||||
mod rdme;
|
||||
mod test_docs;
|
||||
mod typos;
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
@@ -110,7 +111,7 @@ impl Run for Command {
|
||||
Command::LintMarkdown => lint_markdown(),
|
||||
Command::Test => test(),
|
||||
Command::TestBackend(command) => command.run(),
|
||||
Command::TestDocs => test_docs(),
|
||||
Command::TestDocs => test_docs::test_docs(),
|
||||
Command::TestLibs => test_libs(),
|
||||
Command::Hack => hack(),
|
||||
}
|
||||
@@ -154,18 +155,36 @@ fn test() -> Result<()> {
|
||||
for backend in [Backend::Crossterm, Backend::Termion, Backend::Termwiz] {
|
||||
TestBackend { backend }.run()?;
|
||||
}
|
||||
test_docs()?; // run last because it's slow
|
||||
test_docs::test_docs()?; // run last because it's slow
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run doc tests for the workspace's default packages
|
||||
fn test_docs() -> Result<()> {
|
||||
run_cargo(vec!["test", "--doc", "--all-features"])
|
||||
}
|
||||
|
||||
/// Run lib tests for the workspace's default packages
|
||||
fn test_libs() -> Result<()> {
|
||||
run_cargo(vec!["test", "--lib", "--all-targets", "--all-features"])
|
||||
run_cargo(vec![
|
||||
"hack",
|
||||
"--ignore-private", // exclude packages that are libraries
|
||||
"--exclude",
|
||||
"ratatui-crossterm",
|
||||
"test",
|
||||
"--lib",
|
||||
"--all-targets",
|
||||
"--all-features",
|
||||
])?;
|
||||
let crossterm_feature = CROSSTERM_COMMON_FEATURES.join(",");
|
||||
for crossterm_version in crate::CROSSTERM_VERSION_FEATURES {
|
||||
let features = format!("{crossterm_feature},{crossterm_version}");
|
||||
run_cargo(vec![
|
||||
"test",
|
||||
"--package",
|
||||
"ratatui-crossterm",
|
||||
"--lib",
|
||||
"--no-default-features",
|
||||
"--features",
|
||||
features.as_str(),
|
||||
])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run cargo hack to test each feature in isolation
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use color_eyre::Result;
|
||||
|
||||
use crate::{Run, run_cargo};
|
||||
use crate::{CROSSTERM_COMMON_FEATURES, CROSSTERM_VERSION_FEATURES, Run, run_cargo};
|
||||
|
||||
/// Run cargo check
|
||||
#[derive(Clone, Debug, clap::Args)]
|
||||
@@ -13,7 +13,35 @@ pub struct Check {
|
||||
impl Run for Check {
|
||||
fn run(self) -> Result<()> {
|
||||
if self.all_features {
|
||||
run_cargo(vec!["check", "--all-targets", "--all-features"])
|
||||
let common_features = CROSSTERM_COMMON_FEATURES.join(",");
|
||||
|
||||
// Run `cargo check` on `ratatui-crossterm` with specific crossterm versions
|
||||
for crossterm_feature in CROSSTERM_VERSION_FEATURES {
|
||||
let features = format!("{common_features},{crossterm_feature}");
|
||||
let command = vec![
|
||||
"check",
|
||||
"--all-targets",
|
||||
"--package",
|
||||
"ratatui-crossterm",
|
||||
"--no-default-features",
|
||||
"--features",
|
||||
features.as_str(),
|
||||
];
|
||||
run_cargo(command)?;
|
||||
}
|
||||
|
||||
run_cargo(vec![
|
||||
"hack",
|
||||
"--exclude",
|
||||
"ratatui-crossterm",
|
||||
#[cfg(windows)]
|
||||
"--exclude",
|
||||
#[cfg(windows)]
|
||||
"ratatui-termion",
|
||||
"check",
|
||||
"--all-targets",
|
||||
"--all-features",
|
||||
])
|
||||
} else {
|
||||
run_cargo(vec!["check", "--all-targets"])
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use color_eyre::Result;
|
||||
|
||||
use crate::{Run, run_cargo};
|
||||
use crate::{CROSSTERM_VERSION_FEATURES, Run, run_cargo};
|
||||
|
||||
/// Run clippy on the project
|
||||
#[derive(Clone, Debug, clap::Args)]
|
||||
@@ -12,19 +12,57 @@ pub struct Clippy {
|
||||
|
||||
impl Run for Clippy {
|
||||
fn run(self) -> Result<()> {
|
||||
let mut args = vec![
|
||||
"clippy",
|
||||
"--all-targets",
|
||||
// Arguments that go before feature flags (e.g., "clippy", "--fix")
|
||||
let mut clippy_command = vec!["clippy"];
|
||||
if self.fix {
|
||||
clippy_command.push("--fix");
|
||||
}
|
||||
|
||||
// Define common non-version-specific features for ratatui-crossterm. These will be enabled
|
||||
// for both crossterm 0.28 and 0.29 runs. `underline-color`` is part of default, but with
|
||||
// `--no-default-features``, we must add it.
|
||||
let common_features = [
|
||||
"serde",
|
||||
"underline-color",
|
||||
"scrolling-regions",
|
||||
"unstable",
|
||||
"unstable-backend-writer",
|
||||
]
|
||||
.join(",");
|
||||
|
||||
let clippy_options = ["--", "-D", "warnings"];
|
||||
|
||||
// Run Clippy on `ratatui-crossterm` with `crossterm_0_28` and `crossterm_0_29`
|
||||
for crossterm_feature in CROSSTERM_VERSION_FEATURES {
|
||||
let mut command = clippy_command.clone();
|
||||
let features = format!("{common_features},{crossterm_feature}");
|
||||
// Note that adding --tests or --benches causes clippy to pick up the default features.
|
||||
// I'm not sure why this is the case (JM 2025-05-10).
|
||||
command.extend(vec![
|
||||
"--package",
|
||||
"ratatui-crossterm",
|
||||
"--no-default-features",
|
||||
"--features",
|
||||
features.as_str(),
|
||||
]);
|
||||
command.extend(clippy_options);
|
||||
run_cargo(command)?;
|
||||
}
|
||||
|
||||
// Run Clippy on all other workspace packages with --all-features
|
||||
let mut command = clippy_command.clone();
|
||||
command.extend(vec![
|
||||
"--all-features",
|
||||
"--all-targets",
|
||||
"--tests",
|
||||
"--benches",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings",
|
||||
];
|
||||
if self.fix {
|
||||
args.push("--fix");
|
||||
}
|
||||
run_cargo(args)
|
||||
"--workspace",
|
||||
"--exclude",
|
||||
"ratatui-crossterm",
|
||||
]);
|
||||
command.extend(clippy_options);
|
||||
run_cargo(command)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,29 @@ impl Run for Coverage {
|
||||
fn run(self) -> Result<()> {
|
||||
let mut args = vec![
|
||||
"llvm-cov",
|
||||
"--lcov",
|
||||
"--output-path",
|
||||
"target/lcov.info",
|
||||
"--workspace",
|
||||
"--exclude",
|
||||
"ratatui-crossterm",
|
||||
"--all-features",
|
||||
"--no-report",
|
||||
];
|
||||
if self.lib {
|
||||
args.push("--lib");
|
||||
}
|
||||
run_cargo(args)
|
||||
run_cargo(args)?;
|
||||
|
||||
let mut args = vec!["llvm-cov", "--package", "ratatui-crossterm", "--no-report"];
|
||||
if self.lib {
|
||||
args.push("--lib");
|
||||
}
|
||||
run_cargo(args)?;
|
||||
|
||||
run_cargo(vec![
|
||||
"llvm-cov",
|
||||
"report",
|
||||
"--lcov",
|
||||
"--output-path",
|
||||
"target/lcov.info",
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use color_eyre::Result;
|
||||
use itertools::{Itertools, Position};
|
||||
|
||||
use crate::{Run, run_cargo_nightly, workspace_libs};
|
||||
use crate::{Run, run_cargo_nightly};
|
||||
|
||||
/// Check documentation for errors and warnings
|
||||
#[derive(Clone, Debug, clap::Args)]
|
||||
@@ -13,14 +12,11 @@ pub struct Docs {
|
||||
|
||||
impl Run for Docs {
|
||||
fn run(self) -> Result<()> {
|
||||
let packages = workspace_libs()?;
|
||||
for (position, package) in packages.iter().with_position() {
|
||||
let mut args = vec!["docs-rs", "--package", &package];
|
||||
if self.open && matches!(position, Position::Last | Position::Only) {
|
||||
args.push("--open");
|
||||
}
|
||||
run_cargo_nightly(args)?;
|
||||
// cargo +nightly hack --all --ignore-private docs-rs
|
||||
let mut args = vec!["hack", "--all", "--ignore-private", "docs-rs"];
|
||||
if self.open {
|
||||
args.push("--open");
|
||||
}
|
||||
Ok(())
|
||||
run_cargo_nightly(args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use color_eyre::Result;
|
||||
|
||||
use crate::{Run, run_cargo, workspace_libs};
|
||||
use crate::{Run, run_cargo};
|
||||
|
||||
/// Check if README.md is up-to-date (using cargo-rdme)
|
||||
#[derive(Clone, Debug, clap::Args)]
|
||||
@@ -10,22 +10,30 @@ pub struct Readme {
|
||||
check: bool,
|
||||
}
|
||||
|
||||
/// The projects that should have their README.md generated from the source code.
|
||||
///
|
||||
/// Notably, we removed `ratatui` from this list as we have a more specifically crafted README for
|
||||
/// the main crate.
|
||||
const PROJECTS: &[&str] = &[
|
||||
"ratatui-core",
|
||||
"ratatui-crossterm",
|
||||
"ratatui-macros",
|
||||
"ratatui-termion",
|
||||
"ratatui-termwiz",
|
||||
"ratatui-widgets",
|
||||
];
|
||||
|
||||
impl Run for Readme {
|
||||
fn run(self) -> Result<()> {
|
||||
let args = if self.check {
|
||||
vec!["rdme", "--check"]
|
||||
} else {
|
||||
vec!["rdme"]
|
||||
};
|
||||
for package in workspace_libs()? {
|
||||
if package == "ratatui" {
|
||||
// Skip the main crate as we removed rdme
|
||||
continue;
|
||||
// This would be simpler perhaps with cargo-hack, however cargo-rdme does not support the
|
||||
// `--manifest-path` option that is required for this to work, so it's easiest to hard code
|
||||
// the package names here. See https://github.com/orium/cargo-rdme/issues/261
|
||||
for package in PROJECTS {
|
||||
let mut args = vec!["rdme", "--workspace-project", package];
|
||||
if self.check {
|
||||
args.push("--check");
|
||||
}
|
||||
let mut package_args = args.clone();
|
||||
package_args.push("--workspace-project");
|
||||
package_args.push(&package);
|
||||
run_cargo(package_args)?;
|
||||
run_cargo(args)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
29
xtask/src/commands/test_docs.rs
Normal file
29
xtask/src/commands/test_docs.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::{CROSSTERM_COMMON_FEATURES, CROSSTERM_VERSION_FEATURES, Result, run_cargo};
|
||||
|
||||
/// Run doc tests for the workspace's default packages
|
||||
pub fn test_docs() -> Result<()> {
|
||||
run_cargo(vec![
|
||||
"hack",
|
||||
"--workspace",
|
||||
"--ignore-private", // exclude packages that are libraries
|
||||
"--exclude",
|
||||
"ratatui-crossterm",
|
||||
"test",
|
||||
"--doc",
|
||||
"--all-features",
|
||||
])?;
|
||||
let common_features = CROSSTERM_COMMON_FEATURES.join(",");
|
||||
for crossterm_version in CROSSTERM_VERSION_FEATURES {
|
||||
let features = format!("{common_features},{crossterm_version}");
|
||||
run_cargo(vec![
|
||||
"test",
|
||||
"--package",
|
||||
"ratatui-crossterm",
|
||||
"--doc",
|
||||
"--no-default-features",
|
||||
"--features",
|
||||
features.as_str(),
|
||||
])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -7,16 +7,31 @@
|
||||
use std::io;
|
||||
use std::process::Output;
|
||||
|
||||
use cargo_metadata::{MetadataCommand, TargetKind};
|
||||
use clap::Parser;
|
||||
use clap::builder::styling::{AnsiColor, Styles};
|
||||
use clap_verbosity_flag::{InfoLevel, Verbosity};
|
||||
use color_eyre::Result;
|
||||
use color_eyre::eyre::Context;
|
||||
use commands::Command;
|
||||
use duct::cmd;
|
||||
|
||||
mod commands;
|
||||
|
||||
/// The available feature flags for ratatui-crossterm.
|
||||
///
|
||||
/// These will be enabled for both crossterm 0.28 and 0.29 runs. `underline-color` is part of
|
||||
/// default features for ratatui-crossterm, but with `--no-default-features`, we must add it
|
||||
/// explicitly if desired.
|
||||
const CROSSTERM_COMMON_FEATURES: &[&str] = &[
|
||||
"serde",
|
||||
"underline-color",
|
||||
"scrolling-regions",
|
||||
"unstable",
|
||||
"unstable-backend-writer",
|
||||
];
|
||||
|
||||
/// The available feature flags for crossterm versions.
|
||||
const CROSSTERM_VERSION_FEATURES: [&str; 2] = ["crossterm_0_28", "crossterm_0_29"];
|
||||
|
||||
pub trait Run {
|
||||
fn run(self) -> Result<()>;
|
||||
}
|
||||
@@ -39,8 +54,18 @@ fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Matches the clap styling
|
||||
pub const HELP_STYLES: Styles = Styles::styled()
|
||||
.header(AnsiColor::Green.on_default().bold())
|
||||
.usage(AnsiColor::Green.on_default().bold())
|
||||
.literal(AnsiColor::Cyan.on_default().bold())
|
||||
.placeholder(AnsiColor::Cyan.on_default())
|
||||
.error(AnsiColor::Red.on_default().bold())
|
||||
.valid(AnsiColor::Cyan.on_default().bold())
|
||||
.invalid(AnsiColor::Yellow.on_default().bold());
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(bin_name = "cargo xtask", styles = clap_cargo::style::CLAP_STYLING)]
|
||||
#[command(bin_name = "cargo xtask", styles = HELP_STYLES)]
|
||||
struct Args {
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
@@ -49,20 +74,6 @@ struct Args {
|
||||
verbosity: Verbosity<InfoLevel>,
|
||||
}
|
||||
|
||||
/// Return the available libs in the workspace
|
||||
fn workspace_libs() -> Result<Vec<String>> {
|
||||
let meta = MetadataCommand::new()
|
||||
.exec()
|
||||
.wrap_err("failed to get cargo metadata")?;
|
||||
let packages = meta
|
||||
.workspace_packages()
|
||||
.iter()
|
||||
.filter(|v| v.targets.iter().any(|t| t.kind.contains(&TargetKind::Lib)))
|
||||
.map(|v| v.name.to_string())
|
||||
.collect();
|
||||
Ok(packages)
|
||||
}
|
||||
|
||||
/// Run a cargo subcommand with the default toolchain
|
||||
fn run_cargo(args: Vec<&str>) -> Result<()> {
|
||||
cmd("cargo", args).run_with_trace()?;
|
||||
|
||||
Reference in New Issue
Block a user