ci: migrate project automation to Just (#442)
Deploy docs to GitHub Pages / deploy (push) Has been cancelled
Site / build-site (push) Has been cancelled
Install.sh / build (macos-latest) (push) Has been cancelled
Install.sh / build (ubuntu-latest) (push) Has been cancelled
CI / toolchain (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / install-scripts (push) Has been cancelled
CI / crates-ubuntu-latest (push) Has been cancelled
CI / crates-windows-latest (push) Has been cancelled
CI / doc (push) Has been cancelled
CI / deny (push) Has been cancelled
CI / crates-macos-latest (push) Has been cancelled

* ci: migrate project automation to Just

Centralize build, test, release, dependency, hook, and website commands in Just recipes. Pin workflow tooling, use the repository toolchain, and run the complete validation set in CI.

* ci: codex being codex

* docs: update CLAUDE.md for just task runner migration

Reflect the switch to just recipes for build/test/clippy/fmt, note
dprint replacing raw rustfmt, and add a cross-platform code requirement.

* fix: resolve clippy warnings breaking CI on ubuntu and windows

Use clone() instead of implicit to_string() on already-owned String
values, gate the windows-only unused make_file_at import behind
cfg(posix), and fix unused mut / manual assign-op in the windows-only
localhost test.

* fix: fmt
This commit is contained in:
Christian Visintin
2026-08-28 19:29:58 +02:00
committed by GitHub
parent 08c51a32cc
commit afbc74113f
54 changed files with 889 additions and 404 deletions
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
#
# termscp pre-commit hook.
#
# Runs three gates before a commit is recorded:
# 1. trufflehog -- scan the staged tree for verified/unknown secrets
# 2. dprint -- check formatting (Markdown, TOML, YAML, Rust)
# 3. cargo-deny -- advisories, licenses, bans, and sources
#
# Install with `just setup_githooks` (sets core.hooksPath to .githooks).
# Bypass in an emergency with `git commit --no-verify`.
set -euo pipefail
fail() {
echo "pre-commit: $1" >&2
exit 1
}
command -v just >/dev/null 2>&1 || fail "just not found; install it to run the pre-commit checks"
if ! git diff --cached --quiet --diff-filter=ACMR --; then
command -v trufflehog >/dev/null 2>&1 || fail "trufflehog not found; install it or commit with --no-verify"
# Check the exact tree that will be committed, not possibly different
# working-tree contents. The trailing slash is required by checkout-index.
index_tree="$(mktemp -d)"
cleanup() {
rm -rf -- "$index_tree"
}
trap cleanup EXIT
git checkout-index --all --prefix="$index_tree/"
echo "pre-commit: scanning staged tree for secrets"
(
cd "$index_tree"
just scan_secrets . --fail-on-scan-errors
)
echo "pre-commit: checking staged-tree formatting"
(
cd "$index_tree"
just fmt_check
)
echo "pre-commit: checking staged-tree dependencies"
(
cd "$index_tree"
just deny
)
fi
echo "pre-commit: all checks passed"
-1
View File
@@ -4,7 +4,6 @@ about: Create a report of the bug you've encountered
title: "[BUG] - ISSUE_TITLE" title: "[BUG] - ISSUE_TITLE"
labels: bug labels: bug
assignees: veeso assignees: veeso
--- ---
## Description ## Description
-1
View File
@@ -4,7 +4,6 @@ about: Report a typo/error in a repository document
title: "[COPY] - ISSUE_TITLE" title: "[COPY] - ISSUE_TITLE"
labels: documentation labels: documentation
assignees: veeso assignees: veeso
--- ---
## Report ## Report
@@ -4,7 +4,6 @@ about: Suggest an idea to improve termscp
title: "[Feature Request] - FEATURE_TITLE" title: "[Feature Request] - FEATURE_TITLE"
labels: "new feature" labels: "new feature"
assignees: veeso assignees: veeso
--- ---
## Description ## Description
-1
View File
@@ -4,5 +4,4 @@ about: Ask what you want about the project
title: "[QUESTION] - TITLE" title: "[QUESTION] - TITLE"
labels: question labels: question
assignees: veeso assignees: veeso
--- ---
-1
View File
@@ -4,7 +4,6 @@ about: Create a report of a security vulnerability
title: "[SECURITY] - ISSUE_TITLE" title: "[SECURITY] - ISSUE_TITLE"
labels: security labels: security
assignees: veeso assignees: veeso
--- ---
## Description ## Description
+2 -2
View File
@@ -29,12 +29,12 @@ Please select relevant options.
- [ ] I formatted the code with `cargo fmt` - [ ] I formatted the code with `cargo fmt`
- [ ] I checked my code using `cargo clippy` and reports no warnings - [ ] I checked my code using `cargo clippy` and reports no warnings
- [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have introduced no new *C-bindings* - [ ] I have introduced no new _C-bindings_
- [ ] The changes I've made are Windows, MacOS, UNIX, Linux compatible (or I've handled them using `cfg target_os`) - [ ] The changes I've made are Windows, MacOS, UNIX, Linux compatible (or I've handled them using `cfg target_os`)
- [ ] I increased or maintained the code coverage for the project, compared to the previous commit - [ ] I increased or maintained the code coverage for the project, compared to the previous commit
## Acceptance tests ## Acceptance tests
wait for a *project maintainer* to fulfill this section... wait for a _project maintainer_ to fulfill this section...
- [ ] regression test: ... - [ ] regression test: ...
+97 -27
View File
@@ -5,12 +5,12 @@ on:
branches: [main] branches: [main]
paths-ignore: paths-ignore:
- "*.md" - "*.md"
- "./site/**/*" - "site/**"
push: push:
branches: [main] branches: [main]
paths-ignore: paths-ignore:
- "*.md" - "*.md"
- "./site/**/*" - "site/**"
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
@@ -19,50 +19,120 @@ permissions:
contents: read contents: read
jobs: jobs:
build: toolchain:
name: build-(${{ matrix.os }}) runs-on: ubuntu-latest
outputs:
channel: ${{ steps.extract.outputs.result }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Extract toolchain channel from rust-toolchain.toml
id: extract
uses: mikefarah/yq@c14f446382944492701b16c1ddb48bb9dbe683e3 # v4.53.6
with:
cmd: yq '.toolchain.channel' rust-toolchain.toml
fmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust (nightly)
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
toolchain: nightly
components: rustfmt
- name: Check formatting
uses: dprint/check@9cb3a2b17a8e606d37aae341e49df3654933fc23 # v2.3
install-scripts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Check install scripts
run: just check_install_scripts
crates:
needs: toolchain
name: crates-${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
persist-credentials: false persist-credentials: false
- name: Install Linux dependencies - name: Install Linux dependencies
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: sudo apt update && sudo apt install -y libdbus-1-dev libsmbclient-dev run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev libsmbclient-dev
- name: Install macOS dependencies - name: Install macOS dependencies
if: runner.os == 'macOS' if: runner.os == 'macOS'
run: | run: |
brew update brew update
brew install \ brew install \
pkg-config \ pkg-config \
samba samba
brew link --force samba brew link --force samba
- name: Install nightly toolchain - name: Install Rust
if: runner.os == 'Linux' uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with: with:
toolchain: nightly toolchain: ${{ needs.toolchain.outputs.channel }}
components: rustfmt
- name: Format
if: runner.os == 'Linux'
run: cargo +nightly fmt --all -- --check
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: stable
components: clippy components: clippy
- name: Install just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Build - name: Build
if: runner.os != 'Linux' run: just build_crates
run: cargo build - name: Test (Linux)
- name: Run tests (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: cargo test --no-default-features --features github-actions --no-fail-fast run: just test "--no-default-features --features github-actions --no-fail-fast"
- name: Run tests - name: Test
if: runner.os != 'Linux' if: runner.os != 'Linux'
run: cargo test --verbose --features github-actions run: just test "--verbose --features github-actions"
- name: Clippy - name: Clippy
run: cargo clippy -- -Dwarnings run: just clippy "-- -D warnings"
doc:
needs: toolchain
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Linux dependencies
run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev libsmbclient-dev
- name: Install Rust
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
toolchain: ${{ needs.toolchain.outputs.channel }}
- name: Install just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Build documentation
run: just doc
deny:
needs: toolchain
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install Rust
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
with:
toolchain: ${{ needs.toolchain.outputs.channel }}
- name: Install cargo-deny
uses: taiki-e/install-action@37f7c5781271959fb65b6b35224e28652ff2b63d # v2.87.0
with:
tool: cargo-deny
- name: Install just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Check dependencies
run: just deny
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
persist-credentials: false persist-credentials: false
- name: Install termscp from script - name: Install termscp from script
+3 -3
View File
@@ -26,7 +26,7 @@ jobs:
name: github-pages name: github-pages
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
persist-credentials: false persist-credentials: false
- name: Install mdBook - name: Install mdBook
@@ -58,9 +58,9 @@ jobs:
<a href="./en-US/">termscp documentation</a> <a href="./en-US/">termscp documentation</a>
HTML HTML
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with: with:
path: site_out path: site_out
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
+41 -23
View File
@@ -22,23 +22,35 @@ jobs:
outputs: outputs:
version: ${{ inputs.version }} version: ${{ inputs.version }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
token: ${{ secrets.RELEASE_PAT }} token: ${{ secrets.RELEASE_PAT }}
persist-credentials: true persist-credentials: true
fetch-depth: 0 fetch-depth: 0
fetch-tags: true fetch-tags: true
- name: Validate version
env:
VERSION: ${{ inputs.version }}
run: |
if [[ ! "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "invalid release version: $VERSION (expected MAJOR.MINOR.PATCH)" >&2
exit 2
fi
- name: Configure git identity - name: Configure git identity
run: | run: |
git config user.name "veeso" git config user.name "veeso"
git config user.email "christian.visintin@veeso.dev" git config user.email "christian.visintin@veeso.dev"
- name: Install git-cliff - name: Install git-cliff
uses: taiki-e/install-action@56545b37b57562edd73171cb6c62cc509db4c34e # v2 uses: taiki-e/install-action@37f7c5781271959fb65b6b35224e28652ff2b63d # v2.87.0
with: with:
tool: git-cliff tool: git-cliff
- name: Install just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Bump version - name: Bump version
env: env:
VERSION: ${{ inputs.version }} VERSION: ${{ inputs.version }}
@@ -47,7 +59,7 @@ jobs:
- name: Generate CHANGELOG - name: Generate CHANGELOG
env: env:
VERSION: ${{ inputs.version }} VERSION: ${{ inputs.version }}
run: git-cliff --tag "v$VERSION" -o CHANGELOG.md run: just changelog "$VERSION"
- name: Generate release notes - name: Generate release notes
env: env:
@@ -55,7 +67,7 @@ jobs:
run: git-cliff --unreleased --tag "v$VERSION" --strip header -o RELEASE_NOTES.md run: git-cliff --unreleased --tag "v$VERSION" --strip header -o RELEASE_NOTES.md
- name: Upload release notes - name: Upload release notes
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: release-notes name: release-notes
path: RELEASE_NOTES.md path: RELEASE_NOTES.md
@@ -111,15 +123,20 @@ jobs:
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
FEATURES: ${{ matrix.features }} FEATURES: ${{ matrix.features }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
ref: ${{ inputs.dry_run && github.sha || 'main' }} ref: ${{ inputs.dry_run && github.sha || 'main' }}
persist-credentials: false persist-credentials: false
fetch-depth: 0 fetch-depth: 0
fetch-tags: true fetch-tags: true
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - name: Install just
with: uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
targets: ${{ matrix.target }} - name: Install Rust target
if: matrix.kind != 'windows'
run: rustup target add "$TARGET"
- name: Install Rust target
if: matrix.kind == 'windows'
run: rustup target add "$env:TARGET"
# ---- Linux: native per-arch build (x86_64 on ubuntu-latest, aarch64 on ubuntu-24.04-arm) ---- # ---- Linux: native per-arch build (x86_64 on ubuntu-latest, aarch64 on ubuntu-24.04-arm) ----
- name: Install dependencies (Linux) - name: Install dependencies (Linux)
@@ -156,10 +173,10 @@ jobs:
cargo install cargo-deb cargo install cargo-deb
- name: Build (Linux) - name: Build (Linux)
if: matrix.kind == 'linux' if: matrix.kind == 'linux'
run: cargo build --release --features smb-vendored --target "$TARGET" run: just build_release "$TARGET" "--features smb-vendored"
- name: Build deb (Linux) - name: Build deb (Linux)
if: matrix.kind == 'linux' if: matrix.kind == 'linux'
run: cargo deb --no-build --target "$TARGET" --features smb-vendored run: just package_deb "$TARGET"
# ---- macOS ---- # ---- macOS ----
- name: Install deps (macOS) - name: Install deps (macOS)
@@ -173,12 +190,12 @@ jobs:
cpanm Parse::Yapp::Driver cpanm Parse::Yapp::Driver
- name: Build (macOS) - name: Build (macOS)
if: matrix.kind == 'macos' if: matrix.kind == 'macos'
run: cargo build --release $FEATURES --target "$TARGET" run: just build_release "$TARGET" "$FEATURES"
# ---- Windows ---- # ---- Windows ----
- name: Build (Windows) - name: Build (Windows)
if: matrix.kind == 'windows' if: matrix.kind == 'windows'
run: cargo build --release --features smb-vendored --target "$env:TARGET" run: just build_release "$env:TARGET" "--features smb-vendored"
# ---- Package posix (tar.gz) ---- # ---- Package posix (tar.gz) ----
- name: Package (posix) - name: Package (posix)
@@ -204,7 +221,7 @@ jobs:
run: cp target/"$TARGET"/debian/*.deb artifact/ run: cp target/"$TARGET"/debian/*.deb artifact/
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: build-${{ matrix.target }} name: build-${{ matrix.target }}
path: artifact/* path: artifact/*
@@ -218,14 +235,14 @@ jobs:
VERSION: ${{ needs.prepare.outputs.version }} VERSION: ${{ needs.prepare.outputs.version }}
steps: steps:
- name: Download build artifacts - name: Download build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
pattern: build-* pattern: build-*
path: dl path: dl
merge-multiple: true merge-multiple: true
- name: Checkout homebrew tap - name: Checkout homebrew tap
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
repository: veeso/homebrew-termscp repository: veeso/homebrew-termscp
token: ${{ secrets.RELEASE_PAT }} token: ${{ secrets.RELEASE_PAT }}
@@ -316,21 +333,21 @@ jobs:
env: env:
VERSION: ${{ needs.prepare.outputs.version }} VERSION: ${{ needs.prepare.outputs.version }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
token: ${{ secrets.RELEASE_PAT }} token: ${{ secrets.RELEASE_PAT }}
ref: ${{ inputs.dry_run && github.sha || 'main' }} ref: ${{ inputs.dry_run && github.sha || 'main' }}
persist-credentials: true persist-credentials: true
- name: Download build artifacts - name: Download build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
pattern: build-* pattern: build-*
path: dl path: dl
merge-multiple: true merge-multiple: true
- name: Download release notes - name: Download release notes
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
name: release-notes name: release-notes
path: notes path: notes
@@ -355,7 +372,7 @@ jobs:
- name: Upload assets artifact (dry run) - name: Upload assets artifact (dry run)
if: ${{ inputs.dry_run }} if: ${{ inputs.dry_run }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: release-assets-dryrun name: release-assets-dryrun
path: out/* path: out/*
@@ -380,13 +397,14 @@ jobs:
env: env:
VERSION: ${{ needs.prepare.outputs.version }} VERSION: ${{ needs.prepare.outputs.version }}
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
ref: main ref: main
persist-credentials: false persist-credentials: false
fetch-depth: 0 fetch-depth: 0
fetch-tags: true fetch-tags: true
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - name: Install just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Install dependencies (Linux) - name: Install dependencies (Linux)
run: | run: |
@@ -421,12 +439,12 @@ jobs:
- name: Authenticate to crates.io - name: Authenticate to crates.io
id: auth id: auth
uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4 uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5
- name: Publish to crates.io - name: Publish to crates.io
env: env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: cargo publish --features smb-vendored run: just publish_crate
publish-choco: publish-choco:
needs: [prepare, release] needs: [prepare, release]
+9 -11
View File
@@ -9,30 +9,28 @@ on:
permissions: permissions:
contents: read contents: read
defaults:
run:
working-directory: site
jobs: jobs:
build-site: build-site:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version: 24 node-version: 24
cache: npm cache: npm
cache-dependency-path: site/package-lock.json cache-dependency-path: site/package-lock.json
- name: Install just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Install dependencies - name: Install dependencies
run: npm ci run: just site_install
- name: Format - name: Format
run: npm run format:check run: just site_fmt_check
- name: Lint - name: Lint
run: npm run check run: just site_check
- name: Test - name: Test
run: npm test --if-present run: just site_test
- name: Build - name: Build
run: npm run build run: just site_build
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
issues: write issues: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@a20b814fb01b71def3bd6f56e7494d667ddf28da # v4.1.1 - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with: with:
days-before-issue-stale: 30 days-before-issue-stale: 30
days-before-issue-close: 7 days-before-issue-close: 7
Symlink
+1
View File
@@ -0,0 +1 @@
CLAUDE.md
+66 -34
View File
@@ -1,3 +1,5 @@
# Changelog
## 1.1.1 ## 1.1.1
Released on 2026-06-08 Released on 2026-06-08
@@ -10,6 +12,7 @@ Released on 2026-06-08
> crates.io versions are immutable (no overwrite) and Chocolatey's > crates.io versions are immutable (no overwrite) and Chocolatey's
> moderation queue blocks a fast re-push, so a clean 1.1.1 without vergen > moderation queue blocks a fast re-push, so a clean 1.1.1 without vergen
> is the only fix. > is the only fix.
## 1.1.0 ## 1.1.0
Released on 2026-06-08 Released on 2026-06-08
@@ -29,7 +32,7 @@ Released on 2026-06-08
- **install:** add Windows PowerShell installer and copy buttons on site - **install:** add Windows PowerShell installer and copy buttons on site
> Add install.ps1 mirroring install.sh for Windows: arch detection, > Add install.ps1 mirroring install.sh for Windows: arch detection,
> release zip download, binary extraction, user PATH update. > release zip download, binary extraction, user PATH update.
> >
> - copy install.ps1 to site public/ at build time (copy-install.mjs) > - copy install.ps1 to site public/ at build time (copy-install.mjs)
> - serve /install.ps1 with text/plain Content-Type (vercel.json) > - serve /install.ps1 with text/plain Content-Type (vercel.json)
> - add PowerShell one-liner to install page and README > - add PowerShell one-liner to install page and README
@@ -38,11 +41,11 @@ Released on 2026-06-08
- **config:** move config dir to ~/.config/termscp on macOS and %USERPROFILE%\.termscp on Windows - **config:** move config dir to ~/.config/termscp on macOS and %USERPROFILE%\.termscp on Windows
> Resolve the config directory through a single per-platform config_dir() > Resolve the config directory through a single per-platform config_dir()
> function instead of relying on dirs::config_dir everywhere: > function instead of relying on dirs::config_dir everywhere:
> >
> - macOS: ~/.config/termscp (was ~/Library/Application Support/termscp) > - macOS: ~/.config/termscp (was ~/Library/Application Support/termscp)
> - Windows: %USERPROFILE%\.termscp (was roaming %APPDATA%\termscp) > - Windows: %USERPROFILE%\.termscp (was roaming %APPDATA%\termscp)
> - Linux/other: /termscp (unchanged) > - Linux/other: /termscp (unchanged)
> >
> Existing users are migrated automatically on first run: when the new > Existing users are migrated automatically on first run: when the new
> directory is absent and the legacy location exists, the whole config > directory is absent and the legacy location exists, the whole config
> directory is moved to the new path. The cache directory stays at the > directory is moved to the new path. The cache directory stays at the
@@ -54,7 +57,7 @@ Released on 2026-06-08
> Single workflow_dispatch (version, dry_run) that bumps versions, regenerates > Single workflow_dispatch (version, dry_run) that bumps versions, regenerates
> CHANGELOG via git-cliff, rebuilds site CSS, builds all targets, creates the > CHANGELOG via git-cliff, rebuilds site CSS, builds all targets, creates the
> GitHub release, updates the Homebrew tap and publishes Chocolatey. > GitHub release, updates the Homebrew tap and publishes Chocolatey.
> >
> - dist/release/bump_version.sh: version replacer across all tracked locations (+tests) > - dist/release/bump_version.sh: version replacer across all tracked locations (+tests)
> - .github/workflows/release.yml: prepare -> build matrix -> homebrew/release -> choco > - .github/workflows/release.yml: prepare -> build matrix -> homebrew/release -> choco
> - retire build-artifacts.yml (merged into release.yml) > - retire build-artifacts.yml (merged into release.yml)
@@ -66,7 +69,7 @@ Released on 2026-06-08
- fix release notes generation in release workflow - fix release notes generation in release workflow
> prepare job failed: git-cliff --latest crashed with 'trim_start_matches on > prepare job failed: git-cliff --latest crashed with 'trim_start_matches on
> null' because the checkout was shallow (no tags/history) so no release existed. > null' because the checkout was shallow (no tags/history) so no release existed.
> >
> - checkout prepare with fetch-depth: 0 + fetch-tags so git-cliff sees full > - checkout prepare with fetch-depth: 0 + fetch-tags so git-cliff sees full
> history and tags (also fixes an otherwise-truncated CHANGELOG) > history and tags (also fixes an otherwise-truncated CHANGELOG)
> - generate release notes with --unreleased --tag v$VERSION instead of --latest: > - generate release notes with --unreleased --tag v$VERSION instead of --latest:
@@ -143,7 +146,7 @@ Released on 2026-06-08
> Migrate the transfer progress UI to tuirealm 4, where the stdlib > Migrate the transfer progress UI to tuirealm 4, where the stdlib
> `ProgressBar` widget was dropped, by rebuilding the dual-bar panel on > `ProgressBar` widget was dropped, by rebuilding the dual-bar panel on
> top of `Gauge`. > top of `Gauge`.
> >
> - Restore the unified two-bar look: the full bar (top) and partial bar > - Restore the unified two-bar look: the full bar (top) and partial bar
> (bottom) draw joined borders so they read as a single panel; a single > (bottom) draw joined borders so they read as a single panel; a single
> file shows one fully-bordered bar. > file shows one fully-bordered bar.
@@ -159,19 +162,19 @@ Released on 2026-06-08
> its top border is dropped to join the seam with the full bar. That left > its top border is dropped to join the seam with the full bar. That left
> the partial bar with one inner row while the full bar kept two, so the > the partial bar with one inner row while the full bar kept two, so the
> two gauges rendered at unequal heights. > two gauges rendered at unequal heights.
> >
> - Move the filename from the partial bar's title into its gauge label. > - Move the filename from the partial bar's title into its gauge label.
> - Skip setting an empty title so no phantom top-positioned title triggers > - Skip setting an empty title so no phantom top-positioned title triggers
> the inset. > the inset.
> - Put the panel title on the top (full) bar for multi-file transfers. > - Put the panel title on the top (full) bar for multi-file transfers.
> - Bump the two-bar popup height to fit the joined panel. > - Bump the two-bar popup height to fit the joined panel.
> >
> Also bump Cargo.lock and adapt the embedded terminal to the new vt100 > Also bump Cargo.lock and adapt the embedded terminal to the new vt100
> `screen_mut()` API. > `screen_mut()` API.
- **copy:** prevent emptying file when copy destination is empty (#421) - **copy:** prevent emptying file when copy destination is empty (#421)
> An empty copy destination resolved to the source file's own path, so > An empty copy destination resolved to the source file's own path, so
> std::fs::copy truncated the original file to 0 bytes. > std::fs::copy truncated the original file to 0 bytes.
> >
> - localhost::copy now refuses to copy a file onto itself, returning an > - localhost::copy now refuses to copy a file onto itself, returning an
> error instead of truncating it (root cause). > error instead of truncating it (root cause).
> - action_copy treats an empty/whitespace destination as a cancel. > - action_copy treats an empty/whitespace destination as a cancel.
@@ -180,7 +183,7 @@ Released on 2026-06-08
> path. Downstream upload logic treats the queued destination as the full > path. Downstream upload logic treats the queued destination as the full
> file path and passes it straight to create_file, so transfers failed > file path and passes it straight to create_file, so transfers failed
> with a Failure error when the remote target resolved to a directory. > with a Failure error when the remote target resolved to a directory.
> >
> Append each entry's file name to the destination directory at enqueue > Append each entry's file name to the destination directory at enqueue
> time in both enqueue_file and enqueue_all, matching the single-file > time in both enqueue_file and enqueue_all, matching the single-file
> transfer path which already builds the full target path. > transfer path which already builds the full target path.
@@ -191,6 +194,7 @@ Released on 2026-06-08
> The old manual dist/build/* scripts and dist/{deb,rpm}.sh are superseded by the > The old manual dist/build/* scripts and dist/{deb,rpm}.sh are superseded by the
> automated release workflow. Add the chocolatey package consumed by release.yml. > automated release workflow. Add the chocolatey package consumed by release.yml.
- **site:** copy install.sh from repo root at build time (single source) - **site:** copy install.sh from repo root at build time (single source)
## 1.0.0 ## 1.0.0
Released on 2026-04-18 Released on 2026-04-18
@@ -243,6 +247,7 @@ Released on 2026-04-18
> `fs_pane_mut()`. This eliminates most `is_local_tab()` branching across > `fs_pane_mut()`. This eliminates most `is_local_tab()` branching across
> 15+ action files. > 15+ action files.
> Key changes: > Key changes:
>
> - Add `fs: Box<dyn HostBridge>` to Pane, remove from FileTransferActivity > - Add `fs: Box<dyn HostBridge>` to Pane, remove from FileTransferActivity
> - Replace per-side method pairs with unified pane-dispatched methods > - Replace per-side method pairs with unified pane-dispatched methods
> - Unify navigation (changedir, reload, scan, file_exists, has_file_changed) > - Unify navigation (changedir, reload, scan, file_exists, has_file_changed)
@@ -250,7 +255,7 @@ Released on 2026-04-18
> - Replace assert!/panic!/unreachable! with proper error handling > - Replace assert!/panic!/unreachable! with proper error handling
> - Fix typo "filetransfer_activiy" across ~29 files > - Fix typo "filetransfer_activiy" across ~29 files
> - Add unit tests for Pane > - Add unit tests for Pane
> >
> Net result: -473 lines, single code path for most file operations. > Net result: -473 lines, single code path for most file operations.
- replace lazy_static with std::sync::LazyLock - replace lazy_static with std::sync::LazyLock
- migrate from mod.rs to named module files - migrate from mod.rs to named module files
@@ -278,14 +283,14 @@ Released on 2026-04-18
> encryption (authenticated, with random nonces) while keeping a legacy > encryption (authenticated, with random nonces) while keeping a legacy
> AES-128-CBC decryption path to transparently handle existing bookmarks. > AES-128-CBC decryption path to transparently handle existing bookmarks.
- replace recursive byte-counting with entry-based transfer progress (#395) - replace recursive byte-counting with entry-based transfer progress (#395)
> * fix: replace recursive byte-counting with entry-based transfer progress > - fix: replace recursive byte-counting with entry-based transfer progress
> >
> Replace the expensive recursive `get_total_transfer_size` pre-calculation > Replace the expensive recursive `get_total_transfer_size` pre-calculation
> with a lightweight entry-based counter (`TransferProgress`) for the > with a lightweight entry-based counter (`TransferProgress`) for the
> overall progress bar. This avoids deep `list_dir` traversals before > overall progress bar. This avoids deep `list_dir` traversals before
> transfers begin, which could cause FTP idle-timeout disconnections on > transfers begin, which could cause FTP idle-timeout disconnections on
> large directory trees. > large directory trees.
> >
> The per-file byte-level progress bar (`ProgressStates`) remains > The per-file byte-level progress bar (`ProgressStates`) remains
> unchanged. Bytes are still tracked via `TransferStates::add_bytes` for > unchanged. Bytes are still tracked via `TransferStates::add_bytes` for
> notification threshold logic. > notification threshold logic.
@@ -302,7 +307,7 @@ Released on 2026-04-18
- sync browsing when entering a directory from filtered/fuzzy view - sync browsing when entering a directory from filtered/fuzzy view
- stabilize core error handling - stabilize core error handling
> Remove production panic and unwrap paths from core modules. > Remove production panic and unwrap paths from core modules.
> >
> Propagate bookmark encryption failures, harden file watcher and temp mapped file handling, and clean up dead code in shared utilities. > Propagate bookmark encryption failures, harden file watcher and temp mapped file handling, and clean up dead code in shared utilities.
- normalize localhost relative path checks - normalize localhost relative path checks
- use time-based redraw interval instead of progress-delta threshold - use time-based redraw interval instead of progress-delta threshold
@@ -341,9 +346,9 @@ Released on 2026-04-18
> Upgrade tuirealm (3.x -> 4.0.0), tui-realm-stdlib (3 -> 4), tui-term > Upgrade tuirealm (3.x -> 4.0.0), tui-realm-stdlib (3 -> 4), tui-term
> (0.2 -> 0.3). Apply all breaking changes from the 4.0 migration guide > (0.2 -> 0.3). Apply all breaking changes from the 4.0 migration guide
> across the termscp UI. > across the termscp UI.
> >
> Key changes: > Key changes:
> >
> - Root-level re-exports removed; imports moved to module-qualified > - Root-level re-exports removed; imports moved to module-qualified
> paths (`tuirealm::application`, `::component`, `::event`, `::props`, > paths (`tuirealm::application`, `::component`, `::event`, `::props`,
> `::state`, `::subscription`, `::listener`, `::ratatui`). Same for > `::state`, `::subscription`, `::listener`, `::ratatui`). Same for
@@ -394,6 +399,7 @@ Released on 2026-04-18
### Style ### Style
- linter - linter
## 0.19.1 ## 0.19.1
Released on 2025-12-20 Released on 2025-12-20
@@ -407,6 +413,7 @@ Released on 2025-12-20
- install.sh deb name - install.sh deb name
- install.sh deb name - install.sh deb name
- Updated dependencies to allow build on NetBSD - Updated dependencies to allow build on NetBSD
## 0.19.0 ## 0.19.0
Released on 2025-11-11 Released on 2025-11-11
@@ -414,13 +421,13 @@ Released on 2025-11-11
### Added ### Added
- Import bookmarks from ssh config with a CLI command (#364) - Import bookmarks from ssh config with a CLI command (#364)
> * feat: Import bookmarks from ssh config with a CLI command > - feat: Import bookmarks from ssh config with a CLI command
> >
> Use import-ssh-hosts to import all the possible hosts by the configured ssh config or the default one on your machine > Use import-ssh-hosts to import all the possible hosts by the configured ssh config or the default one on your machine
- Changed file overwrite behaviour (#366) - Changed file overwrite behaviour (#366)
> Now the user can choose for each file whether to overwrite, skip or overwrite all/skip all. > Now the user can choose for each file whether to overwrite, skip or overwrite all/skip all.
- Added `<CTRL+S>` keybinding to get the total size of selected paths. (#367) - Added `<CTRL+S>` keybinding to get the total size of selected paths. (#367)
> * feat: Added `<CTRL+S>` keybinding to get the total size of selected paths. > - feat: Added `<CTRL+S>` keybinding to get the total size of selected paths.
- Merge branch '0.19.0' - Merge branch '0.19.0'
### CI ### CI
@@ -440,8 +447,8 @@ Released on 2025-11-11
- typo in file open error message (#349) - typo in file open error message (#349)
- SMB support for MacOS with vendored build of libsmbclient. - SMB support for MacOS with vendored build of libsmbclient.
- Report a message while calculating total size of files to transfer. (#362) - Report a message while calculating total size of files to transfer. (#362)
> * fix: Report a message while calculating total size of files to transfer. > - fix: Report a message while calculating total size of files to transfer.
> >
> Currently, in case of huge transfers the app may look frozen while calculating the transfer size. We should at least report to the user we are actually doing something. > Currently, in case of huge transfers the app may look frozen while calculating the transfer size. We should at least report to the user we are actually doing something.
- Issues with update checks (#363) - Issues with update checks (#363)
> Removed error popup message if failed to check for updates. > Removed error popup message if failed to check for updates.
@@ -456,6 +463,7 @@ Released on 2025-11-11
- 0.19 deps - 0.19 deps
- remotefs-ssh 0.7.1 - remotefs-ssh 0.7.1
> This version fixes compatibility with hosts which don't use bash/sh as the default shell. > This version fixes compatibility with hosts which don't use bash/sh as the default shell.
## 0.18.0 ## 0.18.0
Released on 2025-06-10 Released on 2025-06-10
@@ -464,7 +472,7 @@ Released on 2025-06-10
- **Updated dependencies** and updated the Rust edition to `2024` - **Updated dependencies** and updated the Rust edition to `2024`
- Replaced the `Exec` popup with a fully functional terminal emulator (#348) - Replaced the `Exec` popup with a fully functional terminal emulator (#348)
> * feat: Replaced the `Exec` popup with a fully functional terminal emulator > - feat: Replaced the `Exec` popup with a fully functional terminal emulator
- 0.18 - 0.18
### Fixed ### Fixed
@@ -475,6 +483,7 @@ Released on 2025-06-10
### Style ### Style
- catppuccin themes - catppuccin themes
## 0.17.0 ## 0.17.0
Released on 2025-03-23 Released on 2025-03-23
@@ -523,6 +532,7 @@ Released on 2025-03-23
- aws-s3 0.4.2 - aws-s3 0.4.2
- build docker for x86 - build docker for x86
- so apparently native-tls vendored tries to build openssl on windows, wtf guys? - so apparently native-tls vendored tries to build openssl on windows, wtf guys?
## 0.16.1 ## 0.16.1
Released on 2024-11-12 Released on 2024-11-12
@@ -532,6 +542,7 @@ Released on 2024-11-12
- cfg unix forbidden in rust .82 - cfg unix forbidden in rust .82
- gg rust 1.82 for introducing a nice breaking change in config which was not mentioned in changelog - gg rust 1.82 for introducing a nice breaking change in config which was not mentioned in changelog
- 0.16.1 - 0.16.1
## 0.16.0 ## 0.16.0
Released on 2024-10-14 Released on 2024-10-14
@@ -550,6 +561,7 @@ Released on 2024-10-14
- issue 292 New version alert was not displayed due to a semver regex issue. (#300) - issue 292 New version alert was not displayed due to a semver regex issue. (#300)
- 0.16 - 0.16
- tiny ui issue - tiny ui issue
## 0.15.0 ## 0.15.0
Released on 2024-10-03 Released on 2024-10-03
@@ -572,14 +584,14 @@ Released on 2024-10-03
- issue 277 Fix a bug in the configuration page, which caused being stuck if the added SSH key was empty - issue 277 Fix a bug in the configuration page, which caused being stuck if the added SSH key was empty
- popup texts - popup texts
- `isolated-tests` feature to run tests for releasing on distributions which run in isolated environments (#286) - `isolated-tests` feature to run tests for releasing on distributions which run in isolated environments (#286)
> * fix: `isolated-tests` feature to run tests for releasing on distributions which run in isolated environments > - fix: `isolated-tests` feature to run tests for releasing on distributions which run in isolated environments
> > - fix: cond
> * fix: cond
- set date - set date
- github ci is stable and reliable (one worker broken each 2 weeks) - github ci is stable and reliable (one worker broken each 2 weeks)
- ci - ci
- readme - readme
- include build.rs - include build.rs
## 0.14.0 ## 0.14.0
Released on 2024-07-17 Released on 2024-07-17
@@ -608,6 +620,7 @@ Released on 2024-07-17
- german manual - german manual
- removed support for RPM - removed support for RPM
- changelog - changelog
## 0.13.0 ## 0.13.0
Released on 2024-03-02 Released on 2024-03-02
@@ -624,6 +637,7 @@ Released on 2024-03-02
- debian script - debian script
- debian script - debian script
- lint??? - lint???
## 0.12.2 ## 0.12.2
Released on 2023-10-01 Released on 2023-10-01
@@ -636,6 +650,7 @@ Released on 2023-10-01
- fmt - fmt
- panic if the terminal screen is too small - panic if the terminal screen is too small
## 0.12.1 ## 0.12.1
Released on 2023-07-06 Released on 2023-07-06
@@ -659,6 +674,7 @@ Released on 2023-07-06
- don't run CI on site/.md change - don't run CI on site/.md change
- rustup target - rustup target
- don't update path breadcrumb if enter/scan dir failed (#203) - don't update path breadcrumb if enter/scan dir failed (#203)
## 0.12.0 ## 0.12.0
Released on 2023-05-16 Released on 2023-05-16
@@ -677,6 +693,7 @@ Released on 2023-05-16
- pavao 0.2.3 - pavao 0.2.3
- macos script - macos script
- release date - release date
## 0.11.3 ## 0.11.3
Released on 2023-04-19 Released on 2023-04-19
@@ -688,6 +705,7 @@ Released on 2023-04-19
### Fixed ### Fixed
- relative paths windows (#167) - relative paths windows (#167)
## 0.11.2 ## 0.11.2
Released on 2023-04-18 Released on 2023-04-18
@@ -696,6 +714,7 @@ Released on 2023-04-18
- dependencies up-to-date - dependencies up-to-date
- site 0.11.2 - site 0.11.2
## 0.8.1 ## 0.8.1
Released on 2022-03-22 Released on 2022-03-22
@@ -703,6 +722,7 @@ Released on 2022-03-22
### Fixed ### Fixed
- footer listed "Delete" shortcut as "Make Dir" - footer listed "Delete" shortcut as "Make Dir"
## 0.8.0 ## 0.8.0
Released on 2022-01-06 Released on 2022-01-06
@@ -710,6 +730,7 @@ Released on 2022-01-06
### Arch ### Arch
- install rust only if not found on local system - install rust only if not found on local system
## 0.7.0 ## 0.7.0
Released on 2021-10-12 Released on 2021-10-12
@@ -717,6 +738,7 @@ Released on 2021-10-12
### Option ### Option
- prompt user when about to replace an existing file caused by a file transfer - prompt user when about to replace an existing file caused by a file transfer
## 0.6.1 ## 0.6.1
Released on 2021-08-30 Released on 2021-08-30
@@ -724,6 +746,7 @@ Released on 2021-08-30
### Fixed ### Fixed
- When copying files with tricky copy, the upper progress bar shows no text - When copying files with tricky copy, the upper progress bar shows no text
## 0.5.1 ## 0.5.1
Released on 2021-06-21 Released on 2021-06-21
@@ -731,6 +754,7 @@ Released on 2021-06-21
### Fix ### Fix
- target_family unix means also macos and linux; use BSD target_os - target_family unix means also macos and linux; use BSD target_os
## 0.5.0 ## 0.5.0
Released on 2021-05-23 Released on 2021-05-23
@@ -743,6 +767,7 @@ Released on 2021-05-23
### Grcov ### Grcov
- exclude activities - exclude activities
## 0.4.1 ## 0.4.1
Released on 2021-04-06 Released on 2021-04-06
@@ -755,18 +780,19 @@ Released on 2021-04-06
### Readme ### Readme
- one-liner for Homebrew - one-liner for Homebrew
> The one-liner command > The one-liner command
> >
> brew install veeso/termscp/termscp > brew install veeso/termscp/termscp
> >
> is equivalent to the two commands > is equivalent to the two commands
> >
> brew tap veeso/termscp > brew tap veeso/termscp
> brew install termscp > brew install termscp
### SCP ### SCP
- fixed symlink not properly detected - fixed symlink not properly detected
## 0.4.0 ## 0.4.0
Released on 2021-03-27 Released on 2021-03-27
@@ -786,6 +812,7 @@ Released on 2021-03-27
### View ### View
- return String instead of id - return String instead of id
## 0.3.3 ## 0.3.3
Released on 2021-02-28 Released on 2021-02-28
@@ -793,6 +820,7 @@ Released on 2021-02-28
### Git ### Git
- check for new updates (utils) - check for new updates (utils)
## 0.3.2 ## 0.3.2
Released on 2021-01-24 Released on 2021-01-24
@@ -800,6 +828,7 @@ Released on 2021-01-24
### Testing ### Testing
- don't run on windows - don't run on windows
## 0.3.0 ## 0.3.0
Released on 2021-01-10 Released on 2021-01-10
@@ -834,6 +863,7 @@ Released on 2021-01-10
### SetupActivity ### SetupActivity
- <CTRL+E> as <DEL> - <CTRL+E> as <DEL>
## 0.2.0 ## 0.2.0
Released on 2020-12-21 Released on 2020-12-21
@@ -845,6 +875,7 @@ Released on 2020-12-21
### Scp ### Scp
- when username was not provided, it didn't fallback to current username - when username was not provided, it didn't fallback to current username
## 0.1.2 ## 0.1.2
Released on 2020-12-13 Released on 2020-12-13
@@ -852,6 +883,7 @@ Released on 2020-12-13
### FsEntry ### FsEntry
- :*::symlink is now a Option<Box<FsEntry>>; this improved symlinks, which gave errors some times - :*::symlink is now a Option<Box<FsEntry>>; this improved symlinks, which gave errors some times
## 0.1.0 ## 0.1.0
Released on 2020-12-06 Released on 2020-12-06
+27 -22
View File
@@ -12,28 +12,31 @@ termscp is a terminal file transfer client with a TUI (Terminal User Interface),
## Build & Development Commands ## Build & Development Commands
Task runner is `just` (modular recipes under `just/*.just`, imported by root `justfile`). Run `just --list` for the full set.
```bash ```bash
# Build # Build
cargo build just build_crates # cargo build --workspace
cargo build --release just build_crates "--release"
cargo build --no-default-features # minimal build without SMB/keyring cargo build --no-default-features # minimal build without SMB/keyring (no just recipe)
# Test (CI-equivalent) # Test (CI-equivalent)
cargo test --no-default-features --features github-actions --no-fail-fast just test "--no-default-features --features github-actions --no-fail-fast"
# Run a single test # Run a single test / a module (use cargo directly, just recipes don't take test names)
cargo test <test_name> -- --nocapture cargo test <test_name> -- --nocapture
# Run tests for a module
cargo test --lib filetransfer:: cargo test --lib filetransfer::
cargo test --lib config::params::tests cargo test --lib config::params::tests
# Lint # Lint
cargo clippy -- -Dwarnings just clippy "-- -D warnings"
# Format # Format (dprint: Markdown, TOML, YAML, and Rust via nightly rustfmt)
cargo fmt --all -- --check # check only just fmt_check # check only
cargo fmt --all # fix just fmt # fix
# All code checks at once (fmt_check, clippy -D warnings, doc, deny, install script lint)
just check_code
``` ```
### System Dependencies (for building) ### System Dependencies (for building)
@@ -66,16 +69,16 @@ main.rs → parse CLI args → ActivityManager::new() → ActivityManager::run()
### Key Modules ### Key Modules
| Module | Path | Purpose | | Module | Path | Purpose |
|--------|------|---------| | -------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| **activity_manager** | `src/activity_manager.rs` | Orchestrates activity lifecycle and transitions | | **activity_manager** | `src/activity_manager.rs` | Orchestrates activity lifecycle and transitions |
| **ui/activities** | `src/ui/activities/{auth,filetransfer,setup}/` | Three main screens, each implementing the `Activity` trait | | **ui/activities** | `src/ui/activities/{auth,filetransfer,setup}/` | Three main screens, each implementing the `Activity` trait |
| **ui/context** | `src/ui/context.rs` | Shared `Context` struct (terminal, config, bookmarks, theme) | | **ui/context** | `src/ui/context.rs` | Shared `Context` struct (terminal, config, bookmarks, theme) |
| **filetransfer** | `src/filetransfer/` | Protocol enum, `RemoteFsBuilder`, connection parameters | | **filetransfer** | `src/filetransfer/` | Protocol enum, `RemoteFsBuilder`, connection parameters |
| **host** | `src/host/` | `HostBridge` trait — abstracts local (`Localhost`) and remote (`RemoteBridged`) file operations | | **host** | `src/host/` | `HostBridge` trait — abstracts local (`Localhost`) and remote (`RemoteBridged`) file operations |
| **explorer** | `src/explorer/` | `FileExplorer` — directory navigation, sorting, filtering, transfer queue | | **explorer** | `src/explorer/` | `FileExplorer` — directory navigation, sorting, filtering, transfer queue |
| **system** | `src/system/` | `BookmarksClient`, `ConfigClient`, `ThemeProvider`, `SshKeyStorage`, `KeyStorage` trait | | **system** | `src/system/` | `BookmarksClient`, `ConfigClient`, `ThemeProvider`, `SshKeyStorage`, `KeyStorage` trait |
| **config** | `src/config/` | TOML-based serialization for themes, bookmarks, user params | | **config** | `src/config/` | TOML-based serialization for themes, bookmarks, user params |
### Core Traits ### Core Traits
@@ -86,6 +89,7 @@ main.rs → parse CLI args → ActivityManager::new() → ActivityManager::run()
### Conditional Compilation ### Conditional Compilation
The `build.rs` defines cfg aliases via `cfg_aliases`: The `build.rs` defines cfg aliases via `cfg_aliases`:
- `posix`, `macos`, `linux`, `win` — platform shortcuts - `posix`, `macos`, `linux`, `win` — platform shortcuts
- `smb`, `smb_unix`, `smb_windows` — feature + platform combinations - `smb`, `smb_unix`, `smb_windows` — feature + platform combinations
@@ -106,6 +110,7 @@ Platform-specific dependencies: SSH and FTP crates use different TLS backends on
## Other conventions ## Other conventions
- Always run `cargo +nightly fmt --all` and `cargo clippy --no-default-features -- -Dwarnings` after modifying Rust code - Always run `just fmt` and `just clippy "-- -D warnings"` after modifying Rust code
- Always put plans to `./.claude/plans/` - Always put plans to `./.claude/plans/`
- When changing behavior that is documented under `docs/` (paths, config keys, commands, flags, etc.), update BOTH `docs/en-US/` and `docs/zh-CN/` to keep the translations in sync - When changing behavior that is documented under `docs/` (paths, config keys, commands, flags, etc.), update BOTH `docs/en-US/` and `docs/zh-CN/` to keep the translations in sync
- All code must be cross-platform compatible (Windows, macOS, Linux) — avoid POSIX-only APIs, hardcoded path separators, or shell-specific behavior unless gated behind the `posix`/`win` cfg aliases
+10 -10
View File
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our Examples of behavior that contributes to a positive environment for our
community include: community include:
* Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall - Focusing on what is best not just for us as individuals, but for the overall
community community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of - The use of sexualized language or imagery, and sexual attention or advances of
any kind any kind
* Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or email address, - Publishing others' private information, such as a physical or email address,
without their explicit permission without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a - Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
## Enforcement Responsibilities ## Enforcement Responsibilities
+1 -1
View File
@@ -150,5 +150,5 @@ You can view the developer guide [here](https://docs.termscp.rs/en-US/developer/
--- ---
Thank you for any contribution! Thank you for any contribution!\
Christian Visintin Christian Visintin
Generated
+35 -35
View File
@@ -514,7 +514,7 @@ dependencies = [
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types", "aws-smithy-types",
"h2 0.3.27", "h2 0.3.27",
"h2 0.4.14", "h2 0.4.19",
"http 0.2.12", "http 0.2.12",
"http 1.4.2", "http 1.4.2",
"http-body 0.4.6", "http-body 0.4.6",
@@ -924,9 +924,9 @@ dependencies = [
[[package]] [[package]]
name = "chacha20" name = "chacha20"
version = "0.10.0" version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cipher 0.5.2", "cipher 0.5.2",
@@ -1117,7 +1117,7 @@ dependencies = [
"crc", "crc",
"digest 0.10.7", "digest 0.10.7",
"rustversion", "rustversion",
"spin 0.10.0", "spin 0.10.1",
] ]
[[package]] [[package]]
@@ -1147,9 +1147,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-epoch" name = "crossbeam-epoch"
version = "0.9.18" version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
@@ -1213,9 +1213,9 @@ dependencies = [
[[package]] [[package]]
name = "crypto-bigint" name = "crypto-bigint"
version = "0.7.3" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3" checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271"
dependencies = [ dependencies = [
"cpubits", "cpubits",
"ctutils", "ctutils",
@@ -1256,7 +1256,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21f41f23de7d24cdbda7f0c4d9c0351f99a4ceb258ef30e5c1927af8987ffe5a" checksum = "21f41f23de7d24cdbda7f0c4d9c0351f99a4ceb258ef30e5c1927af8987ffe5a"
dependencies = [ dependencies = [
"crypto-bigint 0.7.3", "crypto-bigint 0.7.5",
"libm", "libm",
"rand_core 0.10.1", "rand_core 0.10.1",
] ]
@@ -1568,7 +1568,7 @@ dependencies = [
"libc", "libc",
"option-ext", "option-ext",
"redox_users", "redox_users",
"windows-sys 0.61.2", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -1770,7 +1770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "102d3643d30dd8b559613c5cced68317199597fffb278cdc88daa2ef7fafc935" checksum = "102d3643d30dd8b559613c5cced68317199597fffb278cdc88daa2ef7fafc935"
dependencies = [ dependencies = [
"base16ct 1.0.0", "base16ct 1.0.0",
"crypto-bigint 0.7.3", "crypto-bigint 0.7.5",
"crypto-common 0.2.2", "crypto-common 0.2.2",
"digest 0.11.3", "digest 0.11.3",
"ff 0.14.0", "ff 0.14.0",
@@ -1826,7 +1826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.61.2", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -2238,9 +2238,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.14" version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -2492,7 +2492,7 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"h2 0.4.14", "h2 0.4.19",
"http 1.4.2", "http 1.4.2",
"http-body 1.0.1", "http-body 1.0.1",
"httparse", "httparse",
@@ -2599,7 +2599,7 @@ dependencies = [
"libc", "libc",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2 0.6.4", "socket2 0.5.10",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
@@ -3088,7 +3088,7 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [ dependencies = [
"spin 0.9.8", "spin 0.9.9",
] ]
[[package]] [[package]]
@@ -4269,7 +4269,7 @@ version = "0.14.0-rc.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f845ec3240cd5ed5e1e31cf3ff633a5bf47c698dc4092ba9e767415b3d393406" checksum = "f845ec3240cd5ed5e1e31cf3ff633a5bf47c698dc4092ba9e767415b3d393406"
dependencies = [ dependencies = [
"crypto-bigint 0.7.3", "crypto-bigint 0.7.5",
"crypto-common 0.2.2", "crypto-common 0.2.2",
"ff 0.14.0", "ff 0.14.0",
"rand_core 0.10.1", "rand_core 0.10.1",
@@ -4335,7 +4335,7 @@ dependencies = [
"quinn-udp", "quinn-udp",
"rustc-hash", "rustc-hash",
"rustls 0.23.40", "rustls 0.23.40",
"socket2 0.6.4", "socket2 0.5.10",
"thiserror 2.0.18", "thiserror 2.0.18",
"tokio", "tokio",
"tracing", "tracing",
@@ -4372,7 +4372,7 @@ dependencies = [
"cfg_aliases", "cfg_aliases",
"libc", "libc",
"once_cell", "once_cell",
"socket2 0.6.4", "socket2 0.5.10",
"tracing", "tracing",
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
@@ -4425,7 +4425,7 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
dependencies = [ dependencies = [
"chacha20 0.10.0", "chacha20 0.10.2",
"getrandom 0.4.2", "getrandom 0.4.2",
"rand_core 0.10.1", "rand_core 0.10.1",
] ]
@@ -4781,7 +4781,7 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2 0.4.14", "h2 0.4.19",
"http 1.4.2", "http 1.4.2",
"http-body 1.0.1", "http-body 1.0.1",
"http-body-util", "http-body-util",
@@ -4895,7 +4895,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf"
dependencies = [ dependencies = [
"const-oid 0.10.2", "const-oid 0.10.2",
"crypto-bigint 0.7.3", "crypto-bigint 0.7.5",
"crypto-primes", "crypto-primes",
"digest 0.11.3", "digest 0.11.3",
"pkcs1 0.8.0-rc.4", "pkcs1 0.8.0-rc.4",
@@ -4931,7 +4931,7 @@ dependencies = [
"bytes", "bytes",
"cbc 0.2.1", "cbc 0.2.1",
"cipher 0.5.2", "cipher 0.5.2",
"crypto-bigint 0.7.3", "crypto-bigint 0.7.5",
"ctr 0.10.1", "ctr 0.10.1",
"curve25519-dalek 5.0.0-rc.0", "curve25519-dalek 5.0.0-rc.0",
"data-encoding", "data-encoding",
@@ -5150,7 +5150,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.12.1", "linux-raw-sys 0.12.1",
"windows-sys 0.61.2", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -5796,20 +5796,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.61.2", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.9.8" version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.10.0" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3"
[[package]] [[package]]
name = "spki" name = "spki"
@@ -5868,7 +5868,7 @@ dependencies = [
"aes 0.9.1", "aes 0.9.1",
"aes-gcm 0.11.0-rc.4", "aes-gcm 0.11.0-rc.4",
"cbc 0.2.1", "cbc 0.2.1",
"chacha20 0.10.0", "chacha20 0.10.2",
"cipher 0.5.2", "cipher 0.5.2",
"ctr 0.10.1", "ctr 0.10.1",
"ctutils", "ctutils",
@@ -5898,7 +5898,7 @@ checksum = "7abf34aa716da5d5b4c496936d042ea282ab392092cd68a72ef6a8863ff8c96a"
dependencies = [ dependencies = [
"base64ct", "base64ct",
"bytes", "bytes",
"crypto-bigint 0.7.3", "crypto-bigint 0.7.5",
"ctutils", "ctutils",
"digest 0.11.3", "digest 0.11.3",
"pem-rfc7468 1.0.0", "pem-rfc7468 1.0.0",
@@ -6117,10 +6117,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"getrandom 0.4.2", "getrandom 0.3.4",
"once_cell", "once_cell",
"rustix 1.1.4", "rustix 1.1.4",
"windows-sys 0.61.2", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -7031,7 +7031,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
+9 -23
View File
@@ -1,17 +1,17 @@
[package] [package]
name = "termscp" name = "termscp"
version = "1.1.1" version = "1.1.1"
edition = "2024"
authors = ["Christian Visintin <christian.visintin@veeso.dev>"] authors = ["Christian Visintin <christian.visintin@veeso.dev>"]
description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV"
license = "MIT"
repository = "https://github.com/veeso/termscp"
categories = ["command-line-utilities"] categories = ["command-line-utilities"]
edition = "2024"
homepage = "https://termscp.rs" homepage = "https://termscp.rs"
include = ["/src/**/*", "/build.rs", "/LICENSE", "/README.md", "/CHANGELOG.md"] include = ["/src/**/*", "/build.rs", "/LICENSE", "/README.md", "/CHANGELOG.md"]
keywords = ["terminal", "ftp", "scp", "sftp", "tui"] keywords = ["terminal", "ftp", "scp", "sftp", "tui"]
license = "MIT"
readme = "README.md" readme = "README.md"
rust-version = "1.89.0" repository = "https://github.com/veeso/termscp"
rust-version = "1.98.0"
description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV"
[package.metadata.rpm] [package.metadata.rpm]
package = "termscp" package = "termscp"
@@ -62,18 +62,10 @@ remotefs = "0.3"
remotefs-aws-s3 = "0.4" remotefs-aws-s3 = "0.4"
remotefs-kube = "0.4" remotefs-kube = "0.4"
remotefs-smb = { version = "0.3", optional = true } remotefs-smb = { version = "0.3", optional = true }
remotefs-ssh = { version = "0.8", default-features = false, features = [ remotefs-ssh = { version = "0.8", default-features = false, features = ["russh"] }
"russh",
] }
remotefs-webdav = "0.2" remotefs-webdav = "0.2"
rpassword = "7" rpassword = "7"
self_update = { version = "0.42", default-features = false, features = [ self_update = { version = "0.42", default-features = false, features = ["archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate", "rustls"] }
"archive-tar",
"archive-zip",
"compression-flate2",
"compression-zip-deflate",
"rustls",
] }
semver = "1" semver = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
shellexpand = "3" shellexpand = "3"
@@ -91,16 +83,10 @@ whoami = "2"
wildmatch = "2" wildmatch = "2"
[target."cfg(any(target_os = \"linux\", target_os = \"freebsd\"))".dependencies] [target."cfg(any(target_os = \"linux\", target_os = \"freebsd\"))".dependencies]
dbus-secret-service-keyring-store = { version = "1", features = [ dbus-secret-service-keyring-store = { version = "1", features = ["crypto-rust", "vendored"] }
"crypto-rust",
"vendored",
] }
[target."cfg(target_family = \"unix\")".dependencies] [target."cfg(target_family = \"unix\")".dependencies]
remotefs-ftp = { version = "0.4", features = [ remotefs-ftp = { version = "0.4", features = ["native-tls", "native-tls-vendored"] }
"native-tls",
"native-tls-vendored",
] }
uzers = "0.12" uzers = "0.12"
[target."cfg(target_family = \"windows\")".dependencies] [target."cfg(target_family = \"windows\")".dependencies]
+10
View File
@@ -0,0 +1,10 @@
import "./just/build.just"
import "./just/changelog.just"
import "./just/code_check.just"
import "./just/publish.just"
import "./just/site.just"
import "./just/test.just"
# Lists all the available commands
default:
@just --list
+19 -19
View File
@@ -43,7 +43,7 @@ Termscp is a feature rich terminal file transfer and explorer, with support for
## Features 🎁 ## Features 🎁
- 📁 Different communication protocols - 📁 Different communication protocols
- **SFTP** - **SFTP**
- **SCP** - **SCP**
- **FTP** and **FTPS** - **FTP** and **FTPS**
@@ -51,31 +51,31 @@ Termscp is a feature rich terminal file transfer and explorer, with support for
- **S3** - **S3**
- **SMB** - **SMB**
- **WebDAV** - **WebDAV**
- 🖥 Explore and operate on the remote and on the local machine file system with a handy UI - 🖥 Explore and operate on the remote and on the local machine file system with a handy UI
- Create, remove, rename, search, view and edit files - Create, remove, rename, search, view and edit files
- Connect to your favourite hosts through built-in bookmarks and recent connections - ⭐ Connect to your favourite hosts through built-in bookmarks and recent connections
- 📝 View and edit files with your favourite applications - 📝 View and edit files with your favourite applications
- 💁 SFTP/SCP authentication with SSH keys and username/password - 💁 SFTP/SCP authentication with SSH keys and username/password
- 🐧 Compatible with Windows, Linux, FreeBSD, NetBSD and MacOS - 🐧 Compatible with Windows, Linux, FreeBSD, NetBSD and MacOS
- 🐚 Embedded terminal for executing commands on the system. - 🐚 Embedded terminal for executing commands on the system.
- 🎨 Make it yours! - 🎨 Make it yours!
- Themes - Themes
- Custom file explorer format - Custom file explorer format
- Customizable text editor - Customizable text editor
- Customizable file sorting - Customizable file sorting
- and many other parameters... - and many other parameters...
- 📫 Get notified via Desktop Notifications when a large file has been transferred - 📫 Get notified via Desktop Notifications when a large file has been transferred
- 🔭 Keep file changes synchronized with the remote host - 🔭 Keep file changes synchronized with the remote host
- 🔐 Save your password in your operating system key vault - 🔐 Save your password in your operating system key vault
- 🦀 Rust-powered - 🦀 Rust-powered
- 👀 Developed keeping an eye on performance - 👀 Developed keeping an eye on performance
- 🦄 Frequent awesome updates - 🦄 Frequent awesome updates
--- ---
## Get started 🚀 ## Get started 🚀
If you're considering to install termscp I want to thank you 💜 ! I hope you will enjoy termscp! If you're considering to install termscp I want to thank you 💜 ! I hope you will enjoy termscp!\
If you want to contribute to this project, don't forget to check out our [contribute guide](CONTRIBUTING.md). If you want to contribute to this project, don't forget to check out our [contribute guide](CONTRIBUTING.md).
If you are a Linux, a FreeBSD or a MacOS user this simple shell script will install termscp on your system with a single command: If you are a Linux, a FreeBSD or a MacOS user this simple shell script will install termscp on your system with a single command:
@@ -131,10 +131,10 @@ These requirements are not forced required to run termscp, but to enjoy all of i
- **Linux/FreeBSD** users: - **Linux/FreeBSD** users:
- To **open** files via `V` (at least one of these) - To **open** files via `V` (at least one of these)
- *xdg-open* - _xdg-open_
- *gio* - _gio_
- *gnome-open* - _gnome-open_
- *kde-open* - _kde-open_
- **Linux** users: - **Linux** users:
- A keyring manager: read more in the [User manual](https://docs.termscp.rs/en-US/configuration/password-security.html#linux-keyring) - A keyring manager: read more in the [User manual](https://docs.termscp.rs/en-US/configuration/password-security.html#linux-keyring)
- **WSL** users - **WSL** users
+92
View File
@@ -0,0 +1,92 @@
# cargo-deny configuration for termscp.
#
# Run locally with `just deny`. The same command runs in CI and enforces
# advisories, licenses, bans, and dependency sources.
#
# Docs: https://embarkstudios.github.io/cargo-deny/
[graph]
all-features = true
[output]
feature-depth = 1
[advisories]
db-urls = ["https://github.com/rustsec/advisory-db"]
yanked = "deny"
unmaintained = "all"
# These are temporary, reachable transitive-risk acceptances owned by the
# termscp maintainers. Review or remove every exception by 2026-12-31. The
# upgrade target is a remotefs/self_update release line that accepts each fixed
# dependency version; until then, connections to untrusted endpoints remain an
# acknowledged denial-of-service and certificate-validation risk.
ignore = [
# h2 0.3 is retained transitively by the reqwest 0.11/AWS HTTP stack; its
# dependency constraints cannot select the fixed h2 0.4 release.
{ id = "RUSTSEC-2026-0258", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade reqwest/AWS stack to fixed h2" },
# number_prefix is retained by self_update through indicatif. It is
# unmaintained, but has no reported vulnerability or compatible replacement.
{ id = "RUSTSEC-2025-0119", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade self_update/indicatif" },
# Both affected quick-xml lines are constrained by remotefs-webdav and
# self_update to versions older than the fixed 0.41 release.
{ id = "RUSTSEC-2026-0194", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade remotefs-webdav/self_update to quick-xml >=0.41" },
{ id = "RUSTSEC-2026-0195", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade remotefs-webdav/self_update to quick-xml >=0.41" },
# The SSH stack requires RSA, for which RustSec reports no safe upgrade.
{ id = "RUSTSEC-2023-0071", reason = "owner: termscp maintainers; review by 2026-12-31; replace RSA dependency when upstream fix exists" },
# russh-keys retains an older russh-cryptovec line and cannot select the
# fixed 0.60.3 release without an upstream dependency update.
{ id = "RUSTSEC-2026-0153", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade remotefs-ssh/russh-keys to russh-cryptovec >=0.60.3" },
# rustls-pemfile is retained by the WebDAV and Kubernetes clients. Both
# versions are unmaintained but have no reported vulnerability.
{ id = "RUSTSEC-2025-0134", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade WebDAV/Kubernetes clients away from rustls-pemfile" },
# The AWS HTTP stack retains rustls 0.21 and rustls-webpki 0.101, which cannot
# select the fixed rustls-webpki release.
{ id = "RUSTSEC-2026-0098", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade AWS HTTP stack to fixed rustls-webpki" },
{ id = "RUSTSEC-2026-0099", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade AWS HTTP stack to fixed rustls-webpki" },
{ id = "RUSTSEC-2026-0104", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade AWS HTTP stack to fixed rustls-webpki" },
]
[licenses]
allow = [
"Apache-2.0",
"BSD-1-Clause",
"BSD-2-Clause",
"BSD-3-Clause",
"BSL-1.0",
"CC0-1.0",
"CDLA-Permissive-2.0",
"ISC",
"MIT",
"MIT-0",
"MPL-2.0",
"Unicode-3.0",
"Unlicense",
"Zlib",
]
confidence-threshold = 0.8
exceptions = [
# The WebDAV backend currently depends on rustydav, which declares GPL-3.0.
# Keep this exception crate-scoped so no other GPL dependency is admitted.
{ allow = ["GPL-3.0"], crate = "rustydav" },
]
[licenses.private]
ignore = false
[bans]
multiple-versions = "warn"
wildcards = "deny"
allow-wildcard-paths = true
highlight = "all"
workspace-default-features = "allow"
external-default-features = "allow"
allow = []
deny = []
skip = []
skip-tree = []
[sources]
unknown-registry = "deny"
unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []
+4
View File
@@ -4,6 +4,10 @@
set -euo pipefail set -euo pipefail
VERSION="${1:?usage: bump_version.sh <version> [date] [root]}" VERSION="${1:?usage: bump_version.sh <version> [date] [root]}"
if [[ ! "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "invalid release version: $VERSION (expected MAJOR.MINOR.PATCH)" >&2
exit 2
fi
DATE="${2:-$(date +%F)}" DATE="${2:-$(date +%F)}"
ROOT="${3:-$(git rev-parse --show-toplevel)}" ROOT="${3:-$(git rev-parse --show-toplevel)}"
+8 -8
View File
@@ -47,20 +47,20 @@ are two quick fixes:
1. Re-import the official theme. After each release the official themes are 1. Re-import the official theme. After each release the official themes are
patched, so download the updated theme from the repository and re-import it: patched, so download the updated theme from the repository and re-import it:
```sh ```sh
termscp theme <theme.toml> termscp theme <theme.toml>
``` ```
2. Edit your theme by hand. If you use a custom theme, edit the file and add the 2. Edit your theme by hand. If you use a custom theme, edit the file and add the
missing key. The theme is located at `$CONFIG_DIR/theme.toml`, where missing key. The theme is located at `$CONFIG_DIR/theme.toml`, where
`$CONFIG_DIR` is: `$CONFIG_DIR` is:
- FreeBSD/Linux: `$HOME/.config/termscp` - FreeBSD/Linux: `$HOME/.config/termscp`
- macOS: `$HOME/.config/termscp` - macOS: `$HOME/.config/termscp`
- Windows: `%USERPROFILE%\.termscp` - Windows: `%USERPROFILE%\.termscp`
Missing keys are reported in the CHANGELOG under `BREAKING CHANGES` for the Missing keys are reported in the CHANGELOG under `BREAKING CHANGES` for the
version you have just installed. version you have just installed.
## Styles ## Styles
+2 -2
View File
@@ -63,8 +63,8 @@ works best from different frameworks:
more, read <https://github.com/veeso/tui-realm>. more, read <https://github.com/veeso/tui-realm>.
- **Components**: components are built around tui in order to reuse widgets. This - **Components**: components are built around tui in order to reuse widgets. This
is achieved through the `Component` trait, inspired by is achieved through the `Component` trait, inspired by
[React](https://reactjs.org/). Each component has its *Properties* and can have [React](https://reactjs.org/). Each component has its _Properties_ and can have
its *States*. Each component must handle input events, accept new properties, its _States_. Each component must handle input events, accept new properties,
and provide a method to **render** itself. This logic now lives in and provide a method to **render** itself. This logic now lives in
[tui-realm](https://github.com/veeso/tui-realm). [tui-realm](https://github.com/veeso/tui-realm).
- **Messages: an Elm-based approach**: input events are handled with an approach - **Messages: an Elm-based approach**: input events are handled with an approach
+19 -19
View File
@@ -43,7 +43,7 @@ termscp 是一个功能丰富的终端文件浏览和传输工具,支持 SCP/S
## 特性 🎁 ## 特性 🎁
- 📁 支持多种通信协议 - 📁 支持多种通信协议
- **SFTP** - **SFTP**
- **SCP** - **SCP**
- **FTP** 和 **FTPS** - **FTP** 和 **FTPS**
@@ -51,31 +51,31 @@ termscp 是一个功能丰富的终端文件浏览和传输工具,支持 SCP/S
- **S3** - **S3**
- **SMB** - **SMB**
- **WebDAV** - **WebDAV**
- 🖥 使用便捷的 UI 在远程和本地文件系统上浏览和操作 - 🖥 使用便捷的 UI 在远程和本地文件系统上浏览和操作
- 创建、删除、重命名、搜索、查看和编辑文件 - 创建、删除、重命名、搜索、查看和编辑文件
- 通过“内置书签”和“最近连接”快速连接到您喜爱的主机 - ⭐ 通过“内置书签”和“最近连接”快速连接到您喜爱的主机
- 📝 使用您喜欢的应用程序查看和编辑文件 - 📝 使用您喜欢的应用程序查看和编辑文件
- 💁 使用 SSH 密钥和用户名/密码进行 SFTP/SCP 身份验证 - 💁 使用 SSH 密钥和用户名/密码进行 SFTP/SCP 身份验证
- 🐧 兼容 Windows、Linux、FreeBSD、NetBSD 和 MacOS 操作系统 - 🐧 兼容 Windows、Linux、FreeBSD、NetBSD 和 MacOS 操作系统
- 🐚 内置终端,可在系统上执行命令。 - 🐚 内置终端,可在系统上执行命令。
- 🎨 丰富的个性化设置! - 🎨 丰富的个性化设置!
- 主题 - 主题
- 自定义文件浏览器格式 - 自定义文件浏览器格式
- 可自定义的文本编辑器 - 可自定义的文本编辑器
- 可自定义的文件排序 - 可自定义的文件排序
- 以及许多其他参数... - 以及许多其他参数...
- 📫 传输大文件时通过桌面通知获得提醒 - 📫 传输大文件时通过桌面通知获得提醒
- 🔭 与远程主机文件更改保持同步 - 🔭 与远程主机文件更改保持同步
- 🔐 将密码保存在操作系统密钥保管库中 - 🔐 将密码保存在操作系统密钥保管库中
- 🦀 由 Rust 提供强力支持 - 🦀 由 Rust 提供强力支持
- 👀 开发时更注重性能 - 👀 开发时更注重性能
- 🦄 频繁的精彩更新 - 🦄 频繁的精彩更新
--- ---
## 开始 🚀 ## 开始 🚀
如果您正在考虑安装 termscp,我想对您表示感谢 💜 ! 希望您会喜欢 termscp! 如果您正在考虑安装 termscp,我想对您表示感谢 💜 ! 希望您会喜欢 termscp!\
如果您想为此项目做出贡献,请不要忘记查看我们的[贡献指南](CONTRIBUTING.md)。 如果您想为此项目做出贡献,请不要忘记查看我们的[贡献指南](CONTRIBUTING.md)。
如果您是 Linux、FreeBSD 或 MacOS 用户,使用以下简单的 shell 脚本即可通过单行指令在您的系统上安装 termscp: 如果您是 Linux、FreeBSD 或 MacOS 用户,使用以下简单的 shell 脚本即可通过单行指令在您的系统上安装 termscp:
@@ -131,10 +131,10 @@ pacman -S termscp
- **Linux/FreeBSD** 用户: - **Linux/FreeBSD** 用户:
-`V` **打开**文件(至少其中之一) -`V` **打开**文件(至少其中之一)
- *xdg-open* - _xdg-open_
- *gio* - _gio_
- *gnome-open* - _gnome-open_
- *kde-open* - _kde-open_
- **Linux** 用户: - **Linux** 用户:
- 密钥环管理器:在[用户手册](https://docs.termscp.rs/zh-CN/configuration/password-security.html#linux-密钥环)中阅读更多内容 - 密钥环管理器:在[用户手册](https://docs.termscp.rs/zh-CN/configuration/password-security.html#linux-密钥环)中阅读更多内容
- **WSL** 用户 - **WSL** 用户
+10 -10
View File
@@ -18,16 +18,16 @@ termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir]
## 选项 ## 选项
| Key | 说明 | | Key | 说明 |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | -------------------- | ------------------------------------------------ |
| `-b <bookmark-name>` | 将位置地址参数解析为书签名称。重复使用该标志可以打开多个书签。 | | `-b <bookmark-name>` | 将位置地址参数解析为书签名称。重复使用该标志可以打开多个书签。 |
| `-D` | 启用 `TRACE` 日志级别(调试/详细日志)。 | | `-D` | 启用 `TRACE` 日志级别(调试/详细日志)。 |
| `-P <password>` | 从命令行提供密码。重复使用该标志可为多个远程主机提供密码;其顺序必须与地址参数一致。不推荐使用。 | | `-P <password>` | 从命令行提供密码。重复使用该标志可为多个远程主机提供密码;其顺序必须与地址参数一致。不推荐使用。 |
| `-q` | 禁用日志记录。 | | `-q` | 禁用日志记录。 |
| `-T <ticks>` | 设置 UI 的 tick 间隔(以毫秒为单位)。默认值为 `10` | | `-T <ticks>` | 设置 UI 的 tick 间隔(以毫秒为单位)。默认值为 `10`。 |
| `--wno-keyring` | 禁用系统 keyring 支持。 | | `--wno-keyring` | 禁用系统 keyring 支持。 |
| `-v` | 打印版本信息。 | | `-v` | 打印版本信息。 |
| `--help` | 打印帮助页面。 | | `--help` | 打印帮助页面。 |
不推荐使用 `-P` 选项,因为密码可能会保留在 shell 历史记录中。请参阅书签和密码安全章节,了解更安全的凭据提供方式。 不推荐使用 `-P` 选项,因为密码可能会保留在 shell 历史记录中。请参阅书签和密码安全章节,了解更安全的凭据提供方式。
+12 -12
View File
@@ -20,18 +20,18 @@
以下是格式化器支持的键: 以下是格式化器支持的键:
| 键 | 说明 | | 键 | 说明 |
| --------- | ------------------------------------------------------------------------------------------------ | | --------- | --------------------------------------------------------------- |
| `ATIME` | 最后访问时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{ATIME:8:%H:%M}` | | `ATIME` | 最后访问时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{ATIME:8:%H:%M}` |
| `CTIME` | 创建时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{CTIME:8:%H:%M}` | | `CTIME` | 创建时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{CTIME:8:%H:%M}` |
| `GROUP` | 所属组 | | `GROUP` | 所属组 |
| `MTIME` | 最后修改时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{MTIME:8:%H:%M}` | | `MTIME` | 最后修改时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{MTIME:8:%H:%M}` |
| `NAME` | 文件名(如果根目录与首个祖先目录之间的文件夹长度超过 `LENGTH`,则会被省略) | | `NAME` | 文件名(如果根目录与首个祖先目录之间的文件夹长度超过 `LENGTH`,则会被省略) |
| `PATH` | 文件绝对路径(如果根目录与首个祖先目录之间的文件夹长度超过 `LENGTH`,则会被省略) | | `PATH` | 文件绝对路径(如果根目录与首个祖先目录之间的文件夹长度超过 `LENGTH`,则会被省略) |
| `PEX` | 文件权限(UNIX 格式) | | `PEX` | 文件权限(UNIX 格式) |
| `SIZE` | 文件大小(目录省略) | | `SIZE` | 文件大小(目录省略) |
| `SYMLINK` | 符号链接目标(如果有,`-> {FILE_PATH}` | | `SYMLINK` | 符号链接目标(如果有,`-> {FILE_PATH}`) |
| `USER` | 所属用户 | | `USER` | 所属用户 |
## 默认格式 ## 默认格式
+38 -38
View File
@@ -36,17 +36,17 @@ termscp 接受以下颜色格式:
1. 重新导入官方主题。每次发布后,官方主题都会被修补,因此从仓库下载更新后的主题并重新导入: 1. 重新导入官方主题。每次发布后,官方主题都会被修补,因此从仓库下载更新后的主题并重新导入:
```sh ```sh
termscp theme <theme.toml> termscp theme <theme.toml>
``` ```
2. 手动编辑你的主题。如果你使用自定义主题,请编辑该文件并添加缺失的键。主题位于 `$CONFIG_DIR/theme.toml`,其中 `$CONFIG_DIR` 为: 2. 手动编辑你的主题。如果你使用自定义主题,请编辑该文件并添加缺失的键。主题位于 `$CONFIG_DIR/theme.toml`,其中 `$CONFIG_DIR` 为:
- FreeBSD/Linux`$HOME/.config/termscp` - FreeBSD/Linux`$HOME/.config/termscp`
- macOS`$HOME/.config/termscp` - macOS`$HOME/.config/termscp`
- Windows`%USERPROFILE%\.termscp` - Windows`%USERPROFILE%\.termscp`
缺失的键会在你刚安装的版本的 CHANGELOG 中 `BREAKING CHANGES` 部分列出。 缺失的键会在你刚安装的版本的 CHANGELOG 中 `BREAKING CHANGES` 部分列出。
## 样式 ## 样式
@@ -54,44 +54,44 @@ termscp 接受以下颜色格式:
### 认证页面 ### 认证页面
| 键 | 说明 | | 键 | 说明 |
| ---------------- | -------------------------- | | ---------------- | ----------- |
| `auth_address` | IP 地址输入框的颜色 | | `auth_address` | IP 地址输入框的颜色 |
| `auth_bookmarks` | 书签面板的颜色 | | `auth_bookmarks` | 书签面板的颜色 |
| `auth_password` | 密码输入框的颜色 | | `auth_password` | 密码输入框的颜色 |
| `auth_port` | 端口号输入框的颜色 | | `auth_port` | 端口号输入框的颜色 |
| `auth_protocol` | 协议单选框组的颜色 | | `auth_protocol` | 协议单选框组的颜色 |
| `auth_recents` | 最近记录面板的颜色 | | `auth_recents` | 最近记录面板的颜色 |
| `auth_username` | 用户名输入框的颜色 | | `auth_username` | 用户名输入框的颜色 |
### 传输页面 ### 传输页面
| 键 | 说明 | | 键 | 说明 |
| -------------------------------------- | -------------------------------------------------- | | -------------------------------------- | ------------------------------- |
| `transfer_local_explorer_background` | 本地主机浏览器的背景色 | | `transfer_local_explorer_background` | 本地主机浏览器的背景色 |
| `transfer_local_explorer_foreground` | 本地主机浏览器的前景色 | | `transfer_local_explorer_foreground` | 本地主机浏览器的前景色 |
| `transfer_local_explorer_highlighted` | 本地主机浏览器的边框及高亮颜色 | | `transfer_local_explorer_highlighted` | 本地主机浏览器的边框及高亮颜色 |
| `transfer_remote_explorer_background` | 远程浏览器的背景色 | | `transfer_remote_explorer_background` | 远程浏览器的背景色 |
| `transfer_remote_explorer_foreground` | 远程浏览器的前景色 | | `transfer_remote_explorer_foreground` | 远程浏览器的前景色 |
| `transfer_remote_explorer_highlighted` | 远程浏览器的边框及高亮颜色 | | `transfer_remote_explorer_highlighted` | 远程浏览器的边框及高亮颜色 |
| `transfer_log_background` | 日志面板的背景色 | | `transfer_log_background` | 日志面板的背景色 |
| `transfer_log_window` | 日志面板的窗口颜色 | | `transfer_log_window` | 日志面板的窗口颜色 |
| `transfer_progress_bar_partial` | 部分进度条的颜色 | | `transfer_progress_bar_partial` | 部分进度条的颜色 |
| `transfer_progress_bar_total` | 总进度条的颜色 | | `transfer_progress_bar_total` | 总进度条的颜色 |
| `transfer_status_hidden` | 状态栏 "hidden" 标签的颜色 | | `transfer_status_hidden` | 状态栏 "hidden" 标签的颜色 |
| `transfer_status_sorting` | 状态栏 "sorting" 标签的颜色;也适用于文件排序对话框 | | `transfer_status_sorting` | 状态栏 "sorting" 标签的颜色;也适用于文件排序对话框 |
| `transfer_status_sync_browsing` | 状态栏 "sync browsing" 标签的颜色 | | `transfer_status_sync_browsing` | 状态栏 "sync browsing" 标签的颜色 |
### 杂项 ### 杂项
这些样式适用于应用程序的不同部分。 这些样式适用于应用程序的不同部分。
| 键 | 说明 | | 键 | 说明 |
| ------------------- | -------------------------------- | | ------------------- | ---------------- |
| `misc_error_dialog` | 错误消息的颜色 | | `misc_error_dialog` | 错误消息的颜色 |
| `misc_info_dialog` | 信息对话框的颜色 | | `misc_info_dialog` | 信息对话框的颜色 |
| `misc_input_dialog` | 输入对话框的颜色(例如复制文件) | | `misc_input_dialog` | 输入对话框的颜色(例如复制文件) |
| `misc_keys` | 按键文本的颜色 | | `misc_keys` | 按键文本的颜色 |
| `misc_quit_dialog` | 退出对话框的颜色 | | `misc_quit_dialog` | 退出对话框的颜色 |
| `misc_save_dialog` | 保存对话框的颜色 | | `misc_save_dialog` | 保存对话框的颜色 |
| `misc_warn_dialog` | 警告对话框的颜色 | | `misc_warn_dialog` | 警告对话框的颜色 |
+1 -1
View File
@@ -30,7 +30,7 @@ termscp 支持以下协议:SFTP、SCP、FTP/FTPS、Kube、S3、SMB 和 WebDAV
- **顶层的 Activities**:每个“视图”都是一个 Activity,并由 `Activity Manager` 来处理它们。这种方法受 Android 启发。它适用于具有不同视图的 ui,每个视图都有自己的组件和逻辑。Activities 与 `Context` 协作,`Context` 是用于在 activities 之间共享数据的数据持有者。 - **顶层的 Activities**:每个“视图”都是一个 Activity,并由 `Activity Manager` 来处理它们。这种方法受 Android 启发。它适用于具有不同视图的 ui,每个视图都有自己的组件和逻辑。Activities 与 `Context` 协作,`Context` 是用于在 activities 之间共享数据的数据持有者。
- **Activities 显示 Applications**:每个 activity 可以显示不同的 **Applications**。一个 application 包含一个 **View**,它基本上是一个 **components** 列表,每个组件都有其属性。view 是组件的门面,同时也处理焦点,即当前处于活动状态的组件。你不能拥有多个活动组件,因此必须对此进行处理;与此同时,如果当前组件被销毁,焦点必须交还给之前处于活动状态的组件。**Application** 负责处理所有这些工作。要了解更多信息,请阅读 <https://github.com/veeso/tui-realm>。 - **Activities 显示 Applications**:每个 activity 可以显示不同的 **Applications**。一个 application 包含一个 **View**,它基本上是一个 **components** 列表,每个组件都有其属性。view 是组件的门面,同时也处理焦点,即当前处于活动状态的组件。你不能拥有多个活动组件,因此必须对此进行处理;与此同时,如果当前组件被销毁,焦点必须交还给之前处于活动状态的组件。**Application** 负责处理所有这些工作。要了解更多信息,请阅读 <https://github.com/veeso/tui-realm>。
- **Components**components 是围绕 tui 构建的,以便复用控件。这是通过 `Component` trait 实现的,该 trait 受 [React](https://reactjs.org/) 启发。每个组件都有其 *Properties*,并且可以拥有其 *States*。每个组件必须处理输入事件、接受新的属性,并提供一个用于**渲染**自身的方法。这一逻辑现在位于 [tui-realm](https://github.com/veeso/tui-realm) 中。 - **Components**components 是围绕 tui 构建的,以便复用控件。这是通过 `Component` trait 实现的,该 trait 受 [React](https://reactjs.org/) 启发。每个组件都有其 _Properties_,并且可以拥有其 _States_。每个组件必须处理输入事件、接受新的属性,并提供一个用于**渲染**自身的方法。这一逻辑现在位于 [tui-realm](https://github.com/veeso/tui-realm) 中。
- **Messages:基于 Elm 的方法**:输入事件采用受 [Elm](https://elm-lang.org/) 启发的方法来处理。在 Elm 中,你使用三个基本函数来实现 ui**update**、**view** 和 **init**。termscp 将 Elm update 函数的等价实现编写为一个递归函数内部的大型 match 分支,你可以在每个 activity 的 `update.rs` 文件中找到它。这个 match 分支处理组件为响应传入的输入事件而产生的消息,并促使 activity 改变其状态。 - **Messages:基于 Elm 的方法**:输入事件采用受 [Elm](https://elm-lang.org/) 启发的方法来处理。在 Elm 中,你使用三个基本函数来实现 ui**update**、**view** 和 **init**。termscp 将 Elm update 函数的等价实现编写为一个递归函数内部的大型 match 分支,你可以在每个 activity 的 `update.rs` 文件中找到它。这个 match 分支处理组件为响应传入的输入事件而产生的消息,并促使 activity 改变其状态。
termscp 实现了一个名为 `Activity` 的 trait,它是 Android activity 的一个大幅精简版本。该 trait 提供以下方法: termscp 实现了一个名为 `Activity` 的 trait,它是 Android activity 的一个大幅精简版本。该 trait 提供以下方法:
+45 -45
View File
@@ -2,48 +2,48 @@
以下按键可在文件浏览器中使用。随时按 `<H|F1>` 可打开应用内帮助。 以下按键可在文件浏览器中使用。随时按 `<H|F1>` 可打开应用内帮助。
| 按键 | 操作 | | 按键 | 操作 |
| -------------- | ---------------------------------------------------------------------- | | -------------- | --------------------- |
| `<ESC>` | 断开与远程的连接并返回认证页面 | | `<ESC>` | 断开与远程的连接并返回认证页面 |
| `<BACKSPACE>` | 返回导航栈中的上一个目录 | | `<BACKSPACE>` | 返回导航栈中的上一个目录 |
| `<TAB>` | 切换当前活动的浏览器选项卡 | | `<TAB>` | 切换当前活动的浏览器选项卡 |
| `<RIGHT>` | 移动到远程浏览器选项卡 | | `<RIGHT>` | 移动到远程浏览器选项卡 |
| `<LEFT>` | 移动到本地浏览器选项卡 | | `<LEFT>` | 移动到本地浏览器选项卡 |
| `<UP>` | 在所选列表中向上移动 | | `<UP>` | 在所选列表中向上移动 |
| `<DOWN>` | 在所选列表中向下移动 | | `<DOWN>` | 在所选列表中向下移动 |
| `<PGUP>` | 在所选列表中向上移动 8 行 | | `<PGUP>` | 在所选列表中向上移动 8 行 |
| `<PGDOWN>` | 在所选列表中向下移动 8 行 | | `<PGDOWN>` | 在所选列表中向下移动 8 行 |
| `<ENTER>` | 进入所选目录 | | `<ENTER>` | 进入所选目录 |
| `<SPACE>` | 上传或下载所选文件 | | `<SPACE>` | 上传或下载所选文件 |
| `<BACKTAB>` | 在日志选项卡与浏览器之间切换 | | `<BACKTAB>` | 在日志选项卡与浏览器之间切换 |
| `<A>` | 切换是否显示隐藏文件 | | `<A>` | 切换是否显示隐藏文件 |
| `<B>` | 选择文件的排序方式 | | `<B>` | 选择文件的排序方式 |
| `<C\|F5>` | 复制所选文件或目录 | | `<C\|F5>` | 复制所选文件或目录 |
| `<D\|F7>` | 新建目录 | | `<D\|F7>` | 新建目录 |
| `<E\|F8\|DEL>` | 删除所选文件 | | `<E\|F8\|DEL>` | 删除所选文件 |
| `<F>` | 搜索文件(支持通配符匹配) | | `<F>` | 搜索文件(支持通配符匹配) |
| `<G>` | 跳转到指定路径 | | `<G>` | 跳转到指定路径 |
| `<H\|F1>` | 显示帮助 | | `<H\|F1>` | 显示帮助 |
| `<I>` | 显示所选文件或目录的信息 | | `<I>` | 显示所选文件或目录的信息 |
| `<K>` | 创建指向当前所选条目的符号链接 | | `<K>` | 创建指向当前所选条目的符号链接 |
| `<L>` | 重新加载当前目录的内容,或清除当前选择 | | `<L>` | 重新加载当前目录的内容,或清除当前选择 |
| `<M>` | 选择一个文件 | | `<M>` | 选择一个文件 |
| `<N>` | 使用提供的名称创建新文件 | | `<N>` | 使用提供的名称创建新文件 |
| `<O\|F4>` | 在文本编辑器中编辑所选文件 | | `<O\|F4>` | 在文本编辑器中编辑所选文件 |
| `<P>` | 打开日志面板 | | `<P>` | 打开日志面板 |
| `<Q\|F10>` | 退出 termscp | | `<Q\|F10>` | 退出 termscp |
| `<R\|F6>` | 重命名所选文件 | | `<R\|F6>` | 重命名所选文件 |
| `<S\|F2>` | 将所选文件另存为新名称 | | `<S\|F2>` | 将所选文件另存为新名称 |
| `<T>` | 将所选路径上的更改同步到远程 | | `<T>` | 将所选路径上的更改同步到远程 |
| `<U>` | 进入上级目录 | | `<U>` | 进入上级目录 |
| `<V\|F3>` | 使用该文件类型的默认程序打开所选文件 | | `<V\|F3>` | 使用该文件类型的默认程序打开所选文件 |
| `<W>` | 使用你指定的程序打开所选文件 | | `<W>` | 使用你指定的程序打开所选文件 |
| `<X>` | 执行命令 | | `<X>` | 执行命令 |
| `<Y>` | 切换同步浏览 | | `<Y>` | 切换同步浏览 |
| `<Z>` | 更改文件模式 | | `<Z>` | 更改文件模式 |
| `</>` | 过滤文件(同时支持正则表达式和通配符匹配) | | `</>` | 过滤文件(同时支持正则表达式和通配符匹配) |
| `<CTRL+A>` | 选择所有文件 | | `<CTRL+A>` | 选择所有文件 |
| `<ALT+A>` | 取消选择所有文件 | | `<ALT+A>` | 取消选择所有文件 |
| `<CTRL+C>` | 中止文件传输过程 | | `<CTRL+C>` | 中止文件传输过程 |
| `<CTRL+S>` | 获取所选路径的总大小 | | `<CTRL+S>` | 获取所选路径的总大小 |
| `<CTRL+T>` | 显示所有已同步的路径 | | `<CTRL+T>` | 显示所有已同步的路径 |
+39
View File
@@ -0,0 +1,39 @@
{
"$schema": "https://dprint.dev/schemas/v0.json",
"indentWidth": 2,
"lineWidth": 80,
"newLineKind": "lf",
"markdown": {
"textWrap": "maintain"
},
"toml": {},
"yaml": {},
"exec": {
"cwd": "${configDir}",
"commands": [
{
"command": "rustup run nightly rustfmt --edition 2024",
"exts": ["rs"],
"cacheKeyFiles": ["rustfmt.toml", "rust-toolchain.toml"]
}
]
},
"excludes": [
"**/target",
"**/node_modules",
"**/*-lock.json",
"Cargo.lock",
"docs/book",
"docs/zh-CN/cli/cli.md",
"docs/zh-CN/configuration/explorer-format.md",
"docs/zh-CN/configuration/themes.md",
"docs/zh-CN/usage/keyboard-shortcuts.md",
"**/tests/fixtures"
],
"plugins": [
"https://plugins.dprint.dev/markdown-0.22.1.wasm@4906fbb038977732aae0e216e4b0b957e2722f8d79282660ccb36d27dd051f17",
"https://plugins.dprint.dev/toml-0.7.0.wasm@0126c8112691542d30b52a639076ecc83e07bace877638cee7c6915fd36b8629",
"https://plugins.dprint.dev/g-plane/pretty_yaml-v0.6.0.wasm@40a2fdda7040317eb1b23520f3a00769a5571eedb049c4ca9175c1b9eeba01ae",
"https://plugins.dprint.dev/dprint/exec-0.6.2.json@df98f54ffd3092b8a841aedd6d098a2651f16d0a796a40535774f1a8b4b9d463"
]
}
+34
View File
@@ -0,0 +1,34 @@
# Build everything
[group('build')]
build_all: build_crates
# Build the Rust crate
[group('build')]
build_crates args="":
cargo build --workspace {{ args }}
# Build all Rust crates in release mode
[group('build')]
build_crates_release:
just build_crates "--release"
# Build a release binary for a target triple
[group('build')]
build_release target features="":
cargo build --locked --release --target {{ target }} {{ features }}
# Package an already-built Linux release as a Debian package
[group('build')]
package_deb target:
cargo deb --no-build --target {{ target }} --features smb-vendored
# Update Cargo.lock; pass cargo update arguments to scope the update.
[group('build')]
update_lock args="":
cargo update {{ args }}
# Clean build artifacts
[group('build')]
[confirm("Are you sure you want to clean the build artifacts?")]
clean:
cargo clean
+44
View File
@@ -0,0 +1,44 @@
set positional-arguments
# Print the unreleased changelog section to stdout (preview only), e.g. `just changelog_preview 8.1.0`
[group('changelog')]
changelog_preview version:
git-cliff --config cliff.toml --unreleased --tag "v$1" --strip all
# Add a new entry to CHANGELOG.md from the unreleased conventional commits, e.g. `just changelog 8.1.0`
[group('changelog')]
changelog version:
#!/usr/bin/env bash
set -euo pipefail
version="$1"
if [[ ! "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "invalid release version: $version (expected MAJOR.MINOR.PATCH)" >&2
exit 2
fi
tag="v$version"
section="$(git-cliff --config cliff.toml --unreleased --tag "$tag" --strip all)"
if [ -z "$section" ]; then
echo "No unreleased conventional commits found; nothing to add." >&2
exit 0
fi
anchor="$(printf '%s' "$version" | tr -d '.')"
secfile="$(mktemp)"
tmp="$(mktemp)"
printf '%s\n' "$section" > "$secfile"
# Insert a TOC entry before the first existing version entry and the rendered
# section before the first existing version heading, keeping the title + TOC.
awk -v secfile="$secfile" -v ver="$version" -v anc="$anchor" '
!toc_done && /^[[:space:]]*- \[[0-9]/ {
print " - [" ver "](#" anc ")"
toc_done = 1
}
!body_done && /^## [0-9]/ {
while ((getline line < secfile) > 0) print line
print ""
body_done = 1
}
{ print }
' CHANGELOG.md > "$tmp"
rm -f "$secfile"
mv "$tmp" CHANGELOG.md
echo "CHANGELOG.md updated for $tag"
+57
View File
@@ -0,0 +1,57 @@
alias lint := clippy
# Format all sources (Markdown, TOML, YAML and Rust via nightly rustfmt) with dprint
[group('code_check')]
fmt args="":
dprint fmt {{ args }}
# Check formatting of all sources with dprint (no writes)
[group('code_check')]
fmt_check args="":
dprint check {{ args }}
# Run clippy on all targets
[group('code_check')]
clippy args="":
cargo clippy --workspace --all-targets {{ args }}
# Build the crate documentation, denying warnings
[group('code_check')]
doc args="":
RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps {{ args }}
# Check dependencies for advisories, licenses, bans and sources (cargo-deny)
[group('code_check')]
deny args="":
cargo deny check {{ args }}
# Scan for secrets with trufflehog (defaults to the whole working tree)
[group('code_check')]
scan_secrets *args=".":
trufflehog filesystem {{ args }} --results=verified,unknown --fail --no-update
# Point git at the tracked .githooks directory (installs the pre-commit hook)
[group('code_check')]
setup_githooks:
git config core.hooksPath .githooks
@echo "git hooks installed: core.hooksPath = .githooks"
# Lint the install scripts (shellcheck; PowerShell parse when pwsh is available)
[group('code_check')]
check_install_scripts:
sh -n install.sh
shellcheck install.sh
@if command -v pwsh >/dev/null 2>&1; then \
pwsh -NoProfile -Command '$t = $null; $e = $null; $null = [System.Management.Automation.Language.Parser]::ParseFile("install.ps1", [ref]$t, [ref]$e); if ($e) { $e; exit 1 }'; \
else \
echo "pwsh not found: skipping install.ps1 parse check"; \
fi
# Run all code checks. Fails if any check fails
[group('code_check')]
check_code:
just fmt_check
just clippy "-- -D warnings"
just doc
just deny
just check_install_scripts
+4
View File
@@ -0,0 +1,4 @@
# Publish the termscp crate
[group('publish')]
publish_crate args="":
cargo publish --locked --features smb-vendored {{ args }}
+28
View File
@@ -0,0 +1,28 @@
# Install website dependencies from the lockfile
[group('site')]
site_install:
cd site && npm ci
# Check website formatting
[group('site')]
site_fmt_check:
cd site && npm run format:check
# Run Astro and TypeScript checks
[group('site')]
site_check:
cd site && npm run check
# Run website tests when the package defines them
[group('site')]
site_test:
cd site && npm test --if-present
# Build the website
[group('site')]
site_build:
cd site && npm run build
# Run every website validation step
[group('site')]
site_ci: site_fmt_check site_check site_test site_build
+13
View File
@@ -0,0 +1,13 @@
# Run all tests
[group('test')]
test_all: test
# Run the Rust test suite
[group('test')]
test args="":
cargo test --workspace {{ args }}
# Generate an LCOV code coverage report for the Rust workspace (requires cargo-llvm-cov)
[group('test')]
coverage output="lcov.info":
cargo llvm-cov --workspace --lcov --output-path {{ output }}
+3
View File
@@ -0,0 +1,3 @@
[toolchain]
channel = "1.98.0"
components = ["clippy", "rustfmt"]
+2 -2
View File
@@ -200,7 +200,7 @@ mod tests {
.unwrap(), .unwrap(),
PathBuf::from("/home/omar/.ssh/beaglebone.key") PathBuf::from("/home/omar/.ssh/beaglebone.key")
); );
assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none()); assert!(!cfg.remote.ssh_keys.contains_key("1.1.1.1"));
} }
#[test] #[test]
@@ -240,7 +240,7 @@ mod tests {
.unwrap(), .unwrap(),
PathBuf::from("/home/omar/.ssh/beaglebone.key") PathBuf::from("/home/omar/.ssh/beaglebone.key")
); );
assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none()); assert!(!cfg.remote.ssh_keys.contains_key("1.1.1.1"));
} }
#[test] #[test]
+8 -8
View File
@@ -401,7 +401,7 @@ mod tests {
assert_eq!(explorer.dirstack.len(), 2); assert_eq!(explorer.dirstack.len(), 2);
assert_eq!(*explorer.dirstack.get(1).unwrap(), PathBuf::from("/dev")); assert_eq!(*explorer.dirstack.get(1).unwrap(), PathBuf::from("/dev"));
assert_eq!( assert_eq!(
*explorer.dirstack.get(0).unwrap(), *explorer.dirstack.front().unwrap(),
PathBuf::from("/home/omar") PathBuf::from("/home/omar")
); );
} }
@@ -425,7 +425,7 @@ mod tests {
assert!(explorer.get(100).is_none()); assert!(explorer.get(100).is_none());
//assert_eq!(explorer.count(), 6); //assert_eq!(explorer.count(), 6);
// Verify (files are sorted by name) // Verify (files are sorted by name)
assert_eq!(explorer.files.get(0).unwrap().name(), ".git"); assert_eq!(explorer.files.first().unwrap().name(), ".git");
// Iter files (all) // Iter files (all)
assert_eq!(explorer.iter_files_all().count(), 6); assert_eq!(explorer.iter_files_all().count(), 6);
// Iter files (hidden excluded) (.git, .gitignore are hidden) // Iter files (hidden excluded) (.git, .gitignore are hidden)
@@ -453,7 +453,7 @@ mod tests {
]); ]);
explorer.sort_by(FileSorting::Name); explorer.sort_by(FileSorting::Name);
// First entry should be "Cargo.lock" // First entry should be "Cargo.lock"
assert_eq!(explorer.files.get(0).unwrap().name(), "Cargo.lock"); assert_eq!(explorer.files.first().unwrap().name(), "Cargo.lock");
// Last should be "src" // Last should be "src"
assert_eq!(explorer.files.get(8).unwrap().name(), "src"); assert_eq!(explorer.files.get(8).unwrap().name(), "src");
} }
@@ -469,7 +469,7 @@ mod tests {
explorer.set_files(vec![entry1, entry2]); explorer.set_files(vec![entry1, entry2]);
explorer.sort_by(FileSorting::ModifyTime); explorer.sort_by(FileSorting::ModifyTime);
// First entry should be "CODE_OF_CONDUCT.md" // First entry should be "CODE_OF_CONDUCT.md"
assert_eq!(explorer.files.get(0).unwrap().name(), "CODE_OF_CONDUCT.md"); assert_eq!(explorer.files.first().unwrap().name(), "CODE_OF_CONDUCT.md");
// Last should be "src" // Last should be "src"
assert_eq!(explorer.files.get(1).unwrap().name(), "README.md"); assert_eq!(explorer.files.get(1).unwrap().name(), "README.md");
} }
@@ -485,7 +485,7 @@ mod tests {
explorer.set_files(vec![entry1, entry2]); explorer.set_files(vec![entry1, entry2]);
explorer.sort_by(FileSorting::CreationTime); explorer.sort_by(FileSorting::CreationTime);
// First entry should be "CODE_OF_CONDUCT.md" // First entry should be "CODE_OF_CONDUCT.md"
assert_eq!(explorer.files.get(0).unwrap().name(), "CODE_OF_CONDUCT.md"); assert_eq!(explorer.files.first().unwrap().name(), "CODE_OF_CONDUCT.md");
// Last should be "src" // Last should be "src"
assert_eq!(explorer.files.get(1).unwrap().name(), "README.md"); assert_eq!(explorer.files.get(1).unwrap().name(), "README.md");
} }
@@ -501,7 +501,7 @@ mod tests {
]); ]);
explorer.sort_by(FileSorting::Size); explorer.sort_by(FileSorting::Size);
// Directory has size 4096 // Directory has size 4096
assert_eq!(explorer.files.get(0).unwrap().name(), "src"); assert_eq!(explorer.files.first().unwrap().name(), "src");
assert_eq!(explorer.files.get(1).unwrap().name(), "README.md"); assert_eq!(explorer.files.get(1).unwrap().name(), "README.md");
assert_eq!(explorer.files.get(2).unwrap().name(), "CONTRIBUTING.md"); assert_eq!(explorer.files.get(2).unwrap().name(), "CONTRIBUTING.md");
} }
@@ -525,7 +525,7 @@ mod tests {
explorer.sort_by(FileSorting::Name); explorer.sort_by(FileSorting::Name);
explorer.group_dirs_by(Some(GroupDirs::First)); explorer.group_dirs_by(Some(GroupDirs::First));
// First entry should be "docs" // First entry should be "docs"
assert_eq!(explorer.files.get(0).unwrap().name(), "docs"); assert_eq!(explorer.files.first().unwrap().name(), "docs");
assert_eq!(explorer.files.get(1).unwrap().name(), "src"); assert_eq!(explorer.files.get(1).unwrap().name(), "src");
// 3rd is file first for alphabetical order // 3rd is file first for alphabetical order
assert_eq!(explorer.files.get(2).unwrap().name(), "Cargo.lock"); assert_eq!(explorer.files.get(2).unwrap().name(), "Cargo.lock");
@@ -555,7 +555,7 @@ mod tests {
assert_eq!(explorer.files.get(8).unwrap().name(), "docs"); assert_eq!(explorer.files.get(8).unwrap().name(), "docs");
assert_eq!(explorer.files.get(9).unwrap().name(), "src"); assert_eq!(explorer.files.get(9).unwrap().name(), "src");
// first is file for alphabetical order // first is file for alphabetical order
assert_eq!(explorer.files.get(0).unwrap().name(), "Cargo.lock"); assert_eq!(explorer.files.first().unwrap().name(), "Cargo.lock");
// Last in files should be "README.md" (last file for alphabetical ordening) // Last in files should be "README.md" (last file for alphabetical ordening)
assert_eq!(explorer.files.get(7).unwrap().name(), "README.md"); assert_eq!(explorer.files.get(7).unwrap().name(), "README.md");
} }
+17 -15
View File
@@ -602,9 +602,11 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::*; use super::*;
use crate::utils::test_helpers::create_sample_file;
#[cfg(posix)]
use crate::utils::test_helpers::make_file_at;
#[cfg(posix)] #[cfg(posix)]
use crate::utils::test_helpers::make_fsentry; use crate::utils::test_helpers::make_fsentry;
use crate::utils::test_helpers::{create_sample_file, make_file_at};
#[test] #[test]
fn test_host_error_new() { fn test_host_error_new() {
@@ -632,13 +634,13 @@ mod tests {
#[test] #[test]
#[cfg(win)] #[cfg(win)]
fn test_host_localhost_new() { fn test_host_localhost_new() {
let mut host: Localhost = Localhost::new(PathBuf::from("C:\\users")).ok().unwrap(); let host: Localhost = Localhost::new(PathBuf::from("C:\\users")).ok().unwrap();
assert_eq!(host.wrkdir, PathBuf::from("C:\\users")); assert_eq!(host.wrkdir, PathBuf::from("C:\\users"));
// Scan dir // Scan dir
let entries = std::fs::read_dir(PathBuf::from("C:\\users").as_path()).unwrap(); let entries = std::fs::read_dir(PathBuf::from("C:\\users").as_path()).unwrap();
let mut counter: usize = 0; let mut counter: usize = 0;
for _ in entries { for _ in entries {
counter = counter + 1; counter += 1;
} }
assert_eq!(host.files.len(), counter); assert_eq!(host.files.len(), counter);
} }
@@ -769,7 +771,7 @@ mod tests {
let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let files: Vec<File> = host.files.clone(); let files: Vec<File> = host.files.clone();
// Verify files // Verify files
let file_0: &File = files.get(0).unwrap(); let file_0: &File = files.first().unwrap();
if file_0.name() == *"foo.txt" { if file_0.name() == *"foo.txt" {
assert!(file_0.metadata.symlink.is_none()); assert!(file_0.metadata.symlink.is_none());
} else { } else {
@@ -827,7 +829,7 @@ mod tests {
let files: Vec<File> = host.files.clone(); let files: Vec<File> = host.files.clone();
assert_eq!(files.len(), 1); // There should be 1 file now assert_eq!(files.len(), 1); // There should be 1 file now
// Remove file // Remove file
assert!(host.remove(files.get(0).unwrap()).is_ok()); assert!(host.remove(files.first().unwrap()).is_ok());
// There should be 0 files now // There should be 0 files now
let files: Vec<File> = host.files.clone(); let files: Vec<File> = host.files.clone();
assert_eq!(files.len(), 0); // There should be 0 files now assert_eq!(files.len(), 0); // There should be 0 files now
@@ -836,7 +838,7 @@ mod tests {
// Delete directory // Delete directory
let files: Vec<File> = host.files.clone(); let files: Vec<File> = host.files.clone();
assert_eq!(files.len(), 1); // There should be 1 file now assert_eq!(files.len(), 1); // There should be 1 file now
assert!(host.remove(files.get(0).unwrap()).is_ok()); assert!(host.remove(files.first().unwrap()).is_ok());
// Remove unexisting directory // Remove unexisting directory
assert!( assert!(
host.remove(&make_fsentry(PathBuf::from("/a/b/c/d"), true)) host.remove(&make_fsentry(PathBuf::from("/a/b/c/d"), true))
@@ -859,22 +861,22 @@ mod tests {
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let files: Vec<File> = host.files.clone(); let files: Vec<File> = host.files.clone();
assert_eq!(files.len(), 1); // There should be 1 file now assert_eq!(files.len(), 1); // There should be 1 file now
assert_eq!(files.get(0).unwrap().name(), "foo.txt"); assert_eq!(files.first().unwrap().name(), "foo.txt");
// Rename file // Rename file
let dst_path: PathBuf = let dst_path: PathBuf =
PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str()); PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str());
assert!( assert!(
host.rename(files.get(0).unwrap(), dst_path.as_path()) host.rename(files.first().unwrap(), dst_path.as_path())
.is_ok() .is_ok()
); );
// There should be still 1 file now, but named bar.txt // There should be still 1 file now, but named bar.txt
let files: Vec<File> = host.files.clone(); let files: Vec<File> = host.files.clone();
assert_eq!(files.len(), 1); // There should be 0 files now assert_eq!(files.len(), 1); // There should be 0 files now
assert_eq!(files.get(0).unwrap().name(), "bar.txt"); assert_eq!(files.first().unwrap().name(), "bar.txt");
// Fail // Fail
let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu"); let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu");
assert!( assert!(
host.rename(files.get(0).unwrap(), bad_path.as_path()) host.rename(files.first().unwrap(), bad_path.as_path())
.is_err() .is_err()
); );
} }
@@ -939,7 +941,7 @@ mod tests {
file2_path.push("bar.txt"); file2_path.push("bar.txt");
// Create host // Create host
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let file1_entry: File = host.files.get(0).unwrap().clone(); let file1_entry: File = host.files.first().unwrap().clone();
assert_eq!(file1_entry.name(), String::from("foo.txt")); assert_eq!(file1_entry.name(), String::from("foo.txt"));
// Copy // Copy
assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok());
@@ -969,7 +971,7 @@ mod tests {
let file2_path: PathBuf = PathBuf::from("bar.txt"); let file2_path: PathBuf = PathBuf::from("bar.txt");
// Create host // Create host
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let file1_entry: File = host.files.get(0).unwrap().clone(); let file1_entry: File = host.files.first().unwrap().clone();
assert_eq!(file1_entry.name(), String::from("foo.txt")); assert_eq!(file1_entry.name(), String::from("foo.txt"));
// Copy // Copy
assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok());
@@ -989,7 +991,7 @@ mod tests {
assert!(file1.write_all(b"Hello world!\n").is_ok()); assert!(file1.write_all(b"Hello world!\n").is_ok());
// Create host // Create host
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let file1_entry: File = host.files.get(0).unwrap().clone(); let file1_entry: File = host.files.first().unwrap().clone();
assert_eq!(file1_entry.name(), String::from("foo.txt")); assert_eq!(file1_entry.name(), String::from("foo.txt"));
// Copy with empty destination -> must fail and leave file untouched // Copy with empty destination -> must fail and leave file untouched
assert!( assert!(
@@ -1022,7 +1024,7 @@ mod tests {
dir_dest.push("test_dest_dir/"); dir_dest.push("test_dest_dir/");
// Create host // Create host
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let dir_src_entry: File = host.files.get(0).unwrap().clone(); let dir_src_entry: File = host.files.first().unwrap().clone();
assert_eq!(dir_src_entry.name(), String::from("test_dir")); assert_eq!(dir_src_entry.name(), String::from("test_dir"));
// Copy // Copy
assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok());
@@ -1052,7 +1054,7 @@ mod tests {
let dir_dest: PathBuf = PathBuf::from("test_dest_dir/"); let dir_dest: PathBuf = PathBuf::from("test_dest_dir/");
// Create host // Create host
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
let dir_src_entry: File = host.files.get(0).unwrap().clone(); let dir_src_entry: File = host.files.first().unwrap().clone();
assert_eq!(dir_src_entry.name(), String::from("test_dir")); assert_eq!(dir_src_entry.name(), String::from("test_dir"));
// Copy // Copy
assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok());
+1 -7
View File
@@ -88,13 +88,7 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
// Match ticks // Match ticks
run_opts.ticks = Duration::from_millis(args.ticks); run_opts.ticks = Duration::from_millis(args.ticks);
// Remote argument // Remote argument
match RemoteArgs::try_from(&args) { run_opts.remote = RemoteArgs::try_from(&args)?;
Err(err) => return Err(err),
Ok(remote) => {
// Set params
run_opts.remote = remote;
}
}
// set activity based on remote state // set activity based on remote state
run_opts.task = if run_opts.remote.remote.is_none() { run_opts.task = if run_opts.remote.remote.is_none() {
+2 -1
View File
@@ -68,7 +68,8 @@ impl Update {
} }
/// Returns whether a new version of termscp is available /// Returns whether a new version of termscp is available
/// In case of success returns Ok(Option<Release>), where the Option is Some(new_version); /// In case of success returns `Ok(Option<Release>)`, where the option is
/// `Some(new_version)`;
/// otherwise if no version is available, return None /// otherwise if no version is available, return None
/// In case of error returns Error with the error description /// In case of error returns Error with the error description
pub fn is_new_version_available() -> Result<Option<Release>, UpdateError> { pub fn is_new_version_available() -> Result<Option<Release>, UpdateError> {
+3 -3
View File
@@ -767,7 +767,7 @@ mod tests {
// Limit is 2 // Limit is 2
assert_eq!(client.iter_recents().count(), 2); assert_eq!(client.iter_recents().count(), 2);
// Check that 192.168.1.1 has been removed // Check that 192.168.1.1 has been removed
let key: String = client.iter_recents().next().unwrap().to_string(); let key: String = client.iter_recents().next().unwrap().clone();
assert!(matches!( assert!(matches!(
client client
.hosts .hosts
@@ -781,7 +781,7 @@ mod tests {
.as_str(), .as_str(),
"192.168.1.2" | "192.168.1.3" "192.168.1.2" | "192.168.1.3"
)); ));
let key: String = client.iter_recents().nth(1).unwrap().to_string(); let key: String = client.iter_recents().nth(1).unwrap().clone();
assert!(matches!( assert!(matches!(
client client
.hosts .hosts
@@ -938,7 +938,7 @@ mod tests {
let protocol = params.protocol; let protocol = params.protocol;
let p = params.params.generic_params().unwrap(); let p = params.params.generic_params().unwrap();
( (
p.address.to_string(), p.address.clone(),
p.port, p.port,
protocol, protocol,
p.username.as_ref().cloned().unwrap_or_default(), p.username.as_ref().cloned().unwrap_or_default(),
+2 -2
View File
@@ -595,7 +595,7 @@ mod tests {
client.set_local_file_fmt(String::from("{NAME}")); client.set_local_file_fmt(String::from("{NAME}"));
assert_eq!(client.get_local_file_fmt().unwrap(), String::from("{NAME}")); assert_eq!(client.get_local_file_fmt().unwrap(), String::from("{NAME}"));
// Delete // Delete
client.set_local_file_fmt(String::from("")); client.set_local_file_fmt(String::new());
assert_eq!(client.get_local_file_fmt(), None); assert_eq!(client.get_local_file_fmt(), None);
} }
@@ -613,7 +613,7 @@ mod tests {
String::from("{NAME}") String::from("{NAME}")
); );
// Delete // Delete
client.set_remote_file_fmt(String::from("")); client.set_remote_file_fmt(String::new());
assert_eq!(client.get_remote_file_fmt(), None); assert_eq!(client.get_remote_file_fmt(), None);
} }
+1
View File
@@ -207,6 +207,7 @@ mod tests {
let mut f: File = OpenOptions::new() let mut f: File = OpenOptions::new()
.create(true) .create(true)
.write(true) .write(true)
.truncate(true)
.open(conf_dir.as_path()) .open(conf_dir.as_path())
.ok() .ok()
.unwrap(); .unwrap();
+2 -2
View File
@@ -294,7 +294,7 @@ mod test {
); );
// unwatch // unwatch
assert!(watcher.unwatch(tempdir.path()).is_ok()); assert!(watcher.unwatch(tempdir.path()).is_ok());
assert!(watcher.paths.get(tempdir.path()).is_none()); assert!(!watcher.paths.contains_key(tempdir.path()));
// close tempdir // close tempdir
assert!(tempdir.close().is_ok()); assert!(tempdir.close().is_ok());
} }
@@ -315,7 +315,7 @@ mod test {
watcher.unwatch(subdir.as_path()).unwrap().as_path(), watcher.unwatch(subdir.as_path()).unwrap().as_path(),
Path::new(tempdir.path()) Path::new(tempdir.path())
); );
assert!(watcher.paths.get(tempdir.path()).is_none()); assert!(!watcher.paths.contains_key(tempdir.path()));
// close tempdir // close tempdir
assert!(tempdir.close().is_ok()); assert!(tempdir.close().is_ok());
} }
+1 -3
View File
@@ -28,8 +28,6 @@ impl Pane {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::path::PathBuf;
use super::*; use super::*;
use crate::explorer::builder::FileExplorerBuilder; use crate::explorer::builder::FileExplorerBuilder;
use crate::host::Localhost; use crate::host::Localhost;
@@ -52,6 +50,6 @@ mod tests {
fn test_pane_pwd() { fn test_pane_pwd() {
let mut pane = make_pane(); let mut pane = make_pane();
let pwd = pane.fs.pwd().unwrap(); let pwd = pane.fs.pwd().unwrap();
assert_eq!(pwd, PathBuf::from(std::env::temp_dir())); assert_eq!(pwd, std::env::temp_dir());
} }
} }
@@ -211,7 +211,8 @@ impl FileTransferActivity {
/// Shared scan walk. `remote_side` selects which pane lists directories /// Shared scan walk. `remote_side` selects which pane lists directories
/// (remote for downloads, local for uploads). /// (remote for downloads, local for uploads).
/// ///
/// The walk is abortable via [`crate::ui::activities::filetransfer::lib::TransferStates::aborted`] /// The walk is abortable via
/// [`TransferStates::aborted`](crate::ui::activities::filetransfer::lib::transfer::TransferStates::aborted)
/// and periodically redraws a "Scanning…" popup to keep the UI responsive. /// and periodically redraws a "Scanning…" popup to keep the UI responsive.
fn scan_worklist( fn scan_worklist(
&mut self, &mut self,
+2 -2
View File
@@ -15,8 +15,8 @@ use super::{Id, IdSsh, IdTheme, SetupActivity, ViewLayout};
use crate::config::themes::Theme; use crate::config::themes::Theme;
impl SetupActivity { impl SetupActivity {
/// On <ESC>, if there are changes in the configuration, the quit dialog must be shown, otherwise /// On `ESC`, if there are changes in the configuration, the quit dialog
/// we can exit without any problem /// must be shown; otherwise, we can exit without any problem.
pub(super) fn action_on_esc(&mut self) { pub(super) fn action_on_esc(&mut self) {
if self.config_changed() { if self.config_changed() {
self.mount_quit(); self.mount_quit();
+4 -2
View File
@@ -43,13 +43,15 @@ pub fn fmt_millis(duration: Duration) -> String {
} }
/// Elide a path if longer than width /// Elide a path if longer than width
/// In this case, the path is formatted to {ANCESTOR[0]}/…/{PARENT[0]}/{BASENAME} /// In this case, the path is formatted to
/// `{ANCESTOR[0]}/…/{PARENT[0]}/{BASENAME}`.
pub fn fmt_path_elide(p: &Path, width: usize) -> String { pub fn fmt_path_elide(p: &Path, width: usize) -> String {
fmt_path_elide_ex(p, width, 0) fmt_path_elide_ex(p, width, 0)
} }
/// Elide a path if longer than width /// Elide a path if longer than width
/// In this case, the path is formatted to {ANCESTOR[0]}/…/{PARENT[0]}/{BASENAME} /// In this case, the path is formatted to
/// `{ANCESTOR[0]}/…/{PARENT[0]}/{BASENAME}`.
/// This function allows to specify an extra length to consider to elide path /// This function allows to specify an extra length to consider to elide path
pub fn fmt_path_elide_ex(p: &Path, width: usize, extra_len: usize) -> String { pub fn fmt_path_elide_ex(p: &Path, width: usize, extra_len: usize) -> String {
let fmt_path: String = format!("{}", p.display()); let fmt_path: String = format!("{}", p.display());
+4 -4
View File
@@ -68,7 +68,7 @@ static BYTESIZE_REGEX: Lazy<Regex> = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B
/// SFTP => 22 /// SFTP => 22
/// FTP => 21 /// FTP => 21
/// The option string has the following syntax /// The option string has the following syntax
/// [protocol://][username@]{address}[:port][:path] /// `[protocol://][username@]{address}[:port][:path]`
/// The only argument which is mandatory is address /// The only argument which is mandatory is address
/// NOTE: possible strings /// NOTE: possible strings
/// - 172.26.104.1 /// - 172.26.104.1
@@ -80,17 +80,17 @@ static BYTESIZE_REGEX: Lazy<Regex> = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B
/// ///
/// For s3: /// For s3:
/// ///
/// s3://<bucket-name>@<region>[:profile][:/wrkdir] /// `s3://<bucket-name>@<region>[:profile][:/wrkdir]`
/// ///
/// For SMB: /// For SMB:
/// ///
/// on UNIX derived (macos, linux, ...) /// on UNIX derived (macos, linux, ...)
/// ///
/// smb://[username@]<address>[:port]/<share>[/path] /// `smb://[username@]<address>[:port]/<share>[/path]`
/// ///
/// on Windows /// on Windows
/// ///
/// \\<address>\<share>[\path] /// `\\<address>\<share>[\path]`
/// ///
pub fn parse_remote_opt(s: &str) -> Result<FileTransferParams, String> { pub fn parse_remote_opt(s: &str) -> Result<FileTransferParams, String> {
remote::parse_remote_opt(s) remote::parse_remote_opt(s)