mirror of
https://github.com/lotabout/skim.git
synced 2026-09-10 07:16:23 -04:00
feat: windows support (#1010)
* wip: windows support * feat: windows support * feat: add windows target to CI * chore: generate completions & manpage * Update src/util.rs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: cleanup & doc * chore: generate completions & manpage * chore: generate dist * fix: reduplicate default test * chore: regate tmux * chore: remove useless test-utils feature * fix(windows): ignore dirs in default_command * docs: update shell docs for windows * chore: generate completions & manpage * chore(justfile): do not ignore failed tests * fix: upload correct junit after profile change * fix: always execute exit commands * fix: windows-specific ctrl-c handling * chore: misc docs & other updates Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: generate completions & manpage * chore: include license in MSI installer --------- Co-authored-by: Skim bot <skim-bot@skim-rs.github.io> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Your Name <you@example.com>
This commit is contained in:
parent
65c2dcde11
commit
9d12e9d420
|
|
@ -18,15 +18,18 @@ command = [
|
||||||
|
|
||||||
[profile.default]
|
[profile.default]
|
||||||
fail-fast = false
|
fail-fast = false
|
||||||
retries = 9
|
retries = 2
|
||||||
[[profile.default.scripts]]
|
[[profile.default.scripts]]
|
||||||
platform = "cfg(unix)"
|
platform = "cfg(unix)"
|
||||||
setup = ["stop-tmux", "start-tmux"]
|
setup = ["stop-tmux", "start-tmux"]
|
||||||
[profile.default.junit]
|
[profile.default.junit]
|
||||||
path = "junit.xml"
|
path = "junit.xml"
|
||||||
|
|
||||||
|
[profile.ci]
|
||||||
|
retries = 9
|
||||||
|
|
||||||
# Valgrind profile for memory leak detection
|
# Valgrind profile for memory leak detection
|
||||||
# Usage: cargo nextest run --profile valgrind --features test-utils
|
# Usage: cargo nextest run --profile valgrind
|
||||||
#
|
#
|
||||||
# Note: Valgrind can detect memory leaks but does NOT detect dangling threads.
|
# Note: Valgrind can detect memory leaks but does NOT detect dangling threads.
|
||||||
# For thread leak detection, use ThreadSanitizer instead (see below).
|
# For thread leak detection, use ThreadSanitizer instead (see below).
|
||||||
|
|
@ -42,9 +45,9 @@ run-wrapper = "valgrind"
|
||||||
# ThreadSanitizer profile for detecting data races and thread issues
|
# ThreadSanitizer profile for detecting data races and thread issues
|
||||||
# Usage:
|
# Usage:
|
||||||
# 1. First build with sanitizer (rebuilds stdlib and all deps):
|
# 1. First build with sanitizer (rebuilds stdlib and all deps):
|
||||||
# RUSTFLAGS="-Zsanitizer=thread" cargo +nightly build --tests --features test-utils -Zbuild-std --target x86_64-unknown-linux-gnu
|
# RUSTFLAGS="-Zsanitizer=thread" cargo +nightly build --tests -Zbuild-std --target x86_64-unknown-linux-gnu
|
||||||
# 2. Then run tests:
|
# 2. Then run tests:
|
||||||
# TSAN_OPTIONS="detect_deadlocks=1" cargo +nightly nextest run --profile tsan --features test-utils --target x86_64-unknown-linux-gnu
|
# TSAN_OPTIONS="detect_deadlocks=1" cargo +nightly nextest run --profile tsan --target x86_64-unknown-linux-gnu
|
||||||
#
|
#
|
||||||
# Note: ThreadSanitizer can detect:
|
# Note: ThreadSanitizer can detect:
|
||||||
# - Data races (concurrent unsynchronized access to memory)
|
# - Data races (concurrent unsynchronized access to memory)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
|
|
||||||
cargo fmt --check --all
|
cargo fmt --check --all
|
||||||
cargo clippy --all-targets --features test-utils -- -Dwarnings
|
cargo clippy --all-targets -- -Dwarnings
|
||||||
cargo check --no-default-features
|
cargo check --no-default-features
|
||||||
|
|
|
||||||
49
.github/CONTRIBUTING.md
vendored
49
.github/CONTRIBUTING.md
vendored
|
|
@ -1,20 +1,63 @@
|
||||||
# Contributor Guide
|
# Contributor Guide
|
||||||
|
|
||||||
|
## Development environment
|
||||||
|
|
||||||
|
A [Nix flake](../flake.nix) is provided with opt-in package groups. The default shell contains only the base build tools (`rustup`, `just`); richer environments are available as named shells:
|
||||||
|
|
||||||
|
| Shell | Extra packages |
|
||||||
|
|---|---|
|
||||||
|
| `nix develop` | `rustup`, `just` |
|
||||||
|
| `nix develop .#tests` | + nextest, cargo-insta, cargo-llvm-cov, tmux |
|
||||||
|
| `nix develop .#utils` | + hyperfine, cargo-edit, cargo-public-api, git-cliff |
|
||||||
|
| `nix develop .#gungraun` | + valgrind, libclang, binutils |
|
||||||
|
| `nix develop .#bench` | + uv, matplotlib, requests (for `bench.py`) |
|
||||||
|
| `nix develop .#vagrant` | + vagrant, rsync (for Windows testing) |
|
||||||
|
| `nix develop .#full` | everything above |
|
||||||
|
|
||||||
## Running tests
|
## Running tests
|
||||||
|
|
||||||
All tests can be run by using [cargo-nextest](https://nexte.st/), which can be installed using `cargo install cargo-nextest` of following the instructions on the website.
|
All tests can be run by using [cargo-nextest](https://nexte.st/), which can be installed using `cargo install cargo-nextest` of following the instructions on the website.
|
||||||
You will need `tmux` to run some integration tests.
|
You will need `tmux` to run some integration tests.
|
||||||
|
|
||||||
You can then run `cargo nextest run --release --features test-utils`, which should automatically build a release binary, run the unit tests and the integration tests.
|
You can then run `cargo nextest run --release`, which should automatically build a release binary, run the unit tests and the integration tests.
|
||||||
|
|
||||||
|
|
||||||
Most integration tests use [cargo insta](https://insta.rs). If you need to add some tests or re-review them, you will need to install it, and run tests with `cargo insta test --features test-utils --tests --review`, which will let you review snapshots.
|
Most integration tests use [cargo insta](https://insta.rs). If you need to add some tests or re-review them, you will need to install it, and run tests with `cargo insta test --tests --review`, which will let you review snapshots.
|
||||||
|
|
||||||
Note: you can run the tests without `--release`, but expect more flaky tests since the timings will be looser. I would advise testing manually any debug test failure if you have doubts. However, the tests won't run without the `test-utils` feature, used to create test backends.
|
Note: you can run the tests without `--release`, but expect more flaky tests since the timings will be looser. I would advise testing manually any debug test failure if you have doubts.
|
||||||
|
|
||||||
Note2: A dockerfile is available if you want to run the tests inside docker. There is little to no cache, so the test will need to rebuild most of the application after each change.
|
Note2: A dockerfile is available if you want to run the tests inside docker. There is little to no cache, so the test will need to rebuild most of the application after each change.
|
||||||
To use it, build the image with `docker build -f test.dockerfile . -t skim-test` then run it using `docker run --rm -it skim-test`.
|
To use it, build the image with `docker build -f test.dockerfile . -t skim-test` then run it using `docker run --rm -it skim-test`.
|
||||||
|
|
||||||
|
## Windows testing
|
||||||
|
|
||||||
|
A [Vagrantfile](../Vagrantfile) is provided to spin up a headless Windows Server 2022 Core VM via KVM/libvirt, letting you test Windows compatibility without a GUI.
|
||||||
|
|
||||||
|
**Host prerequisites (NixOS):**
|
||||||
|
|
||||||
|
```nix
|
||||||
|
virtualisation.libvirtd.enable = true;
|
||||||
|
users.users.<you>.extraGroups = [ "libvirtd" ]; # log out/in after applying
|
||||||
|
```
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix develop .#vagrant
|
||||||
|
vagrant up # first boot: ~15-20 min, downloads box + provisions
|
||||||
|
vagrant ssh # connect to the VM
|
||||||
|
vagrant halt # stop the VM
|
||||||
|
vagrant destroy # delete the VM
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the VM the project root is synced to `C:\vagrant`. Re-sync after local changes with `vagrant rsync`. To build:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd C:\vagrant
|
||||||
|
cargo build
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
## Submitting code
|
## Submitting code
|
||||||
|
|
||||||
To avoid using up CI minutes uselessly, make sure that :
|
To avoid using up CI minutes uselessly, make sure that :
|
||||||
|
|
|
||||||
34
.github/workflows/release.yml
vendored
34
.github/workflows/release.yml
vendored
|
|
@ -56,7 +56,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
@ -64,9 +64,9 @@ jobs:
|
||||||
# we specify bash to get pipefail; it guards against the `curl` command
|
# we specify bash to get pipefail; it guards against the `curl` command
|
||||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||||
shell: bash
|
shell: bash
|
||||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.3/cargo-dist-installer.sh | sh"
|
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.4/cargo-dist-installer.sh | sh"
|
||||||
- name: Cache dist
|
- name: Cache dist
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/dist
|
path: ~/.cargo/bin/dist
|
||||||
|
|
@ -82,7 +82,7 @@ jobs:
|
||||||
cat plan-dist-manifest.json
|
cat plan-dist-manifest.json
|
||||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||||
- name: "Upload dist-manifest.json"
|
- name: "Upload dist-manifest.json"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: artifacts-plan-dist-manifest
|
name: artifacts-plan-dist-manifest
|
||||||
path: plan-dist-manifest.json
|
path: plan-dist-manifest.json
|
||||||
|
|
@ -121,7 +121,7 @@ jobs:
|
||||||
- name: enable windows longpaths
|
- name: enable windows longpaths
|
||||||
run: |
|
run: |
|
||||||
git config --global core.longpaths true
|
git config --global core.longpaths true
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
@ -136,7 +136,7 @@ jobs:
|
||||||
run: ${{ matrix.install_dist.run }}
|
run: ${{ matrix.install_dist.run }}
|
||||||
# Get the dist-manifest
|
# Get the dist-manifest
|
||||||
- name: Fetch local artifacts
|
- name: Fetch local artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
|
|
@ -163,7 +163,7 @@ jobs:
|
||||||
|
|
||||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||||
- name: "Upload artifacts"
|
- name: "Upload artifacts"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
|
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
|
||||||
path: |
|
path: |
|
||||||
|
|
@ -180,19 +180,19 @@ jobs:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install cached dist
|
- name: Install cached dist
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/
|
path: ~/.cargo/bin/
|
||||||
- run: chmod +x ~/.cargo/bin/dist
|
- run: chmod +x ~/.cargo/bin/dist
|
||||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||||
- name: Fetch local artifacts
|
- name: Fetch local artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
|
|
@ -210,7 +210,7 @@ jobs:
|
||||||
|
|
||||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||||
- name: "Upload artifacts"
|
- name: "Upload artifacts"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: artifacts-build-global
|
name: artifacts-build-global
|
||||||
path: |
|
path: |
|
||||||
|
|
@ -230,19 +230,19 @@ jobs:
|
||||||
outputs:
|
outputs:
|
||||||
val: ${{ steps.host.outputs.manifest }}
|
val: ${{ steps.host.outputs.manifest }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install cached dist
|
- name: Install cached dist
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/
|
path: ~/.cargo/bin/
|
||||||
- run: chmod +x ~/.cargo/bin/dist
|
- run: chmod +x ~/.cargo/bin/dist
|
||||||
# Fetch artifacts from scratch-storage
|
# Fetch artifacts from scratch-storage
|
||||||
- name: Fetch artifacts
|
- name: Fetch artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
|
|
@ -255,14 +255,14 @@ jobs:
|
||||||
cat dist-manifest.json
|
cat dist-manifest.json
|
||||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||||
- name: "Upload dist-manifest.json"
|
- name: "Upload dist-manifest.json"
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
# Overwrite the previous copy
|
# Overwrite the previous copy
|
||||||
name: artifacts-dist-manifest
|
name: artifacts-dist-manifest
|
||||||
path: dist-manifest.json
|
path: dist-manifest.json
|
||||||
# Create a GitHub Release while uploading all files to it
|
# Create a GitHub Release while uploading all files to it
|
||||||
- name: "Download GitHub Artifacts"
|
- name: "Download GitHub Artifacts"
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
@ -310,7 +310,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
|
||||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
|
|
@ -21,7 +21,7 @@ jobs:
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
build: [linux, macos]
|
build: [linux, macos, windows]
|
||||||
include:
|
include:
|
||||||
- build: linux
|
- build: linux
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
|
|
@ -29,6 +29,9 @@ jobs:
|
||||||
- build: macos
|
- build: macos
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
|
- build: windows
|
||||||
|
os: windows-latest
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
steps:
|
steps:
|
||||||
- name: "[linux] Install dependencies"
|
- name: "[linux] Install dependencies"
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -61,7 +64,7 @@ jobs:
|
||||||
run: cargo test --doc
|
run: cargo test --doc
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
# Do not use `--all-targets` to avoid running benches
|
# Do not use `--all-targets` to avoid running benches
|
||||||
run: cargo llvm-cov nextest --release --features test-utils --bins --lib --examples --tests --codecov --output-path codecov.json
|
run: cargo llvm-cov nextest --release --profile ci --bins --lib --examples --tests --codecov --output-path codecov.json
|
||||||
env:
|
env:
|
||||||
LC_ALL: en_US.UTF-8
|
LC_ALL: en_US.UTF-8
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
|
|
@ -77,7 +80,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
report_type: test_results
|
report_type: test_results
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
files: target/nextest/default/junit.xml
|
files: target/nextest/ci/junit.xml
|
||||||
fail_ci_if_error: false
|
fail_ci_if_error: false
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -32,3 +32,6 @@ cachegrind.out.*
|
||||||
|
|
||||||
/scripts/data/
|
/scripts/data/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
||||||
|
# Vagrant
|
||||||
|
.vagrant/
|
||||||
|
|
|
||||||
12
AGENTS.md
12
AGENTS.md
|
|
@ -3,13 +3,13 @@
|
||||||
## Build/Test/Lint Commands
|
## Build/Test/Lint Commands
|
||||||
- Build: `cargo build [--release]`
|
- Build: `cargo build [--release]`
|
||||||
- Run: `cargo run [--release]`
|
- Run: `cargo run [--release]`
|
||||||
- Test (all): `cargo nextest --features test-utils`
|
- Test (all): `cargo nextest`
|
||||||
- Test (single): `cargo nextest test_name --features test-utils`
|
- Test (single): `cargo nextest test_name`
|
||||||
- Integration/E2E tests: `cargo nextest --tests --features test-utils` (will need tmux under the hood)
|
- Integration/E2E tests: `cargo nextest --tests` (will need tmux under the hood)
|
||||||
- Memory leak detection: `cargo nextest run --profile valgrind --features test-utils`
|
- Memory leak detection: `cargo nextest run --profile valgrind`
|
||||||
- Thread leak/race detection:
|
- Thread leak/race detection:
|
||||||
1. Build: `RUSTFLAGS="-Zsanitizer=thread" cargo +nightly build --tests --features test-utils -Zbuild-std --target x86_64-unknown-linux-gnu`
|
1. Build: `RUSTFLAGS="-Zsanitizer=thread" cargo +nightly build --tests -Zbuild-std --target x86_64-unknown-linux-gnu`
|
||||||
2. Run: `TSAN_OPTIONS="detect_deadlocks=1" cargo +nightly nextest run --profile tsan --features test-utils --target x86_64-unknown-linux-gnu`
|
2. Run: `TSAN_OPTIONS="detect_deadlocks=1" cargo +nightly nextest run --profile tsan --target x86_64-unknown-linux-gnu`
|
||||||
- Lint: `cargo clippy`
|
- Lint: `cargo clippy`
|
||||||
- Format: `cargo fmt` (check only: `cargo fmt --check`)
|
- Format: `cargo fmt` (check only: `cargo fmt --check`)
|
||||||
|
|
||||||
|
|
|
||||||
12
Cargo.toml
12
Cargo.toml
|
|
@ -12,6 +12,12 @@ license = "MIT"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.91"
|
rust-version = "1.91"
|
||||||
|
|
||||||
|
[package.metadata.wix]
|
||||||
|
upgrade-guid = "6DDAED06-EBE2-41C4-94F5-CB03F2A4B92E"
|
||||||
|
path-guid = "05D1A327-19E0-4C82-B5EC-6B28E40C691B"
|
||||||
|
license = false
|
||||||
|
eula = false
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
@ -76,15 +82,13 @@ derive_more = { version = "=2.1.1", features = ["debug", "eq"] }
|
||||||
portable-pty = "=0.9.0"
|
portable-pty = "=0.9.0"
|
||||||
tui-term = "=0.3.2"
|
tui-term = "=0.3.2"
|
||||||
kanal = "=0.1.1"
|
kanal = "=0.1.1"
|
||||||
mimalloc = { version = "0.1.48", features = ["v3"] }
|
|
||||||
gungraun = { version = "0.17.2", optional = true }
|
gungraun = { version = "0.17.2", optional = true }
|
||||||
|
mimalloc = { version = "0.1.48", features = ["v3"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["cli"]
|
default = ["cli"]
|
||||||
# Everyting needed to use skim as a cli (argument parsing, shell integrations...)
|
# Everyting needed to use skim as a cli (argument parsing, shell integrations...)
|
||||||
cli = ["dep:clap", "dep:clap_complete", "dep:shlex", "dep:env_logger", "dep:clap_mangen"]
|
cli = ["dep:clap", "dep:clap_complete", "dep:shlex", "dep:env_logger", "dep:clap_mangen"]
|
||||||
# Enable test utilities (e.g., Tui::new_for_test)
|
|
||||||
test-utils = []
|
|
||||||
# Enable gungraun (Valgrind-based) benchmarks
|
# Enable gungraun (Valgrind-based) benchmarks
|
||||||
gungraun = ["dep:gungraun"]
|
gungraun = ["dep:gungraun"]
|
||||||
|
|
||||||
|
|
@ -93,7 +97,7 @@ criterion = { version = "0.8.2", features = ["async_tokio"] }
|
||||||
insta = "1.46"
|
insta = "1.46"
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("test-utils", "gungraun"))', 'cfg(coverage, coverage_nightly)'] }
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage, coverage_nightly)'] }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "read_and_match"
|
name = "read_and_match"
|
||||||
|
|
|
||||||
31
README.md
31
README.md
|
|
@ -68,6 +68,7 @@ Skim provides a single executable called `sk`. Think of it as a smarter alternat
|
||||||
* [Some files are not shown in Vim plugin](#some-files-are-not-shown-in-vim-plugin)
|
* [Some files are not shown in Vim plugin](#some-files-are-not-shown-in-vim-plugin)
|
||||||
- [Differences from fzf](#differences-from-fzf)
|
- [Differences from fzf](#differences-from-fzf)
|
||||||
- [How to contribute](#how-to-contribute)
|
- [How to contribute](#how-to-contribute)
|
||||||
|
* [Windows compatibility testing](#windows-compatibility-testing)
|
||||||
- [Troubleshooting](#troubleshooting)
|
- [Troubleshooting](#troubleshooting)
|
||||||
* [No line feed issues with nix, FreeBSD, termux](#no-line-feed-issues-with-nix-freebsd-termux)
|
* [No line feed issues with nix, FreeBSD, termux](#no-line-feed-issues-with-nix-freebsd-termux)
|
||||||
|
|
||||||
|
|
@ -199,8 +200,6 @@ To enable these features, source the `key-bindings.{shell}` file and set up comp
|
||||||
|
|
||||||
You can generate shell completions for your preferred shell using the `--shell` flag with one of the supported shells: `bash`, `zsh`, `fish`, `powershell`, or `elvish`:
|
You can generate shell completions for your preferred shell using the `--shell` flag with one of the supported shells: `bash`, `zsh`, `fish`, `powershell`, or `elvish`:
|
||||||
|
|
||||||
> **Note:** While PowerShell completions are supported, Windows is not supported for now.
|
|
||||||
|
|
||||||
#### Option 1: Source directly in your current shell session
|
#### Option 1: Source directly in your current shell session
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
@ -688,6 +687,34 @@ The goal is to keep `skim` as feature-full as `fzf` is, but the command flags mi
|
||||||
[Create new issues](https://github.com/skim-rs/skim/issues/new) if you encounter any bugs
|
[Create new issues](https://github.com/skim-rs/skim/issues/new) if you encounter any bugs
|
||||||
or have any ideas. Pull requests are warmly welcomed.
|
or have any ideas. Pull requests are warmly welcomed.
|
||||||
|
|
||||||
|
## Windows compatibility testing
|
||||||
|
|
||||||
|
A `Vagrantfile` is included to spin up a headless Windows Server 2022 Core VM for testing
|
||||||
|
Windows compatibility without needing a GUI. It requires [VirtualBox](https://www.virtualbox.org/)
|
||||||
|
and [Vagrant](https://www.vagrantup.com/) on your host (`vagrant` is included in the Nix dev
|
||||||
|
shell via `flake.nix`).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
vagrant up # First boot: downloads the box and provisions (~15–20 min)
|
||||||
|
ssh -p 2222 vagrant@localhost # Password: vagrant
|
||||||
|
```
|
||||||
|
|
||||||
|
Inside the VM, the project root is mounted at `C:\vagrant`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd C:\vagrant
|
||||||
|
cargo build
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
Subsequent boots are fast — provisioning only runs once:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
vagrant halt # Stop the VM
|
||||||
|
vagrant up # Resume
|
||||||
|
vagrant destroy # Delete the VM entirely
|
||||||
|
```
|
||||||
|
|
||||||
# Troubleshooting
|
# Troubleshooting
|
||||||
|
|
||||||
To troubleshoot what's happening, you can set the environment variable `SKIM_LOG` or the flag `--log-level` to either `debug` or even `trace`, and set the environment variable `SKIM_LOG_FILE` or the flag `--log-file` to a path. You can then read those logs during or after the execution to better understand what's happening. Don't hesitate to add those logs to an issue if you need help.
|
To troubleshoot what's happening, you can set the environment variable `SKIM_LOG` or the flag `--log-level` to either `debug` or even `trace`, and set the environment variable `SKIM_LOG_FILE` or the flag `--log-file` to a path. You can then read those logs during or after the execution to better understand what's happening. Don't hesitate to add those logs to an issue if you need help.
|
||||||
|
|
|
||||||
121
Vagrantfile
vendored
Normal file
121
Vagrantfile
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
# -*- mode: ruby -*-
|
||||||
|
# vi: set ft=ruby :
|
||||||
|
#
|
||||||
|
# Windows development VM for testing skim on Windows without a GUI.
|
||||||
|
#
|
||||||
|
# Prerequisites (host, NixOS):
|
||||||
|
# virtualisation.libvirtd.enable = true;
|
||||||
|
# users.users.<you>.extraGroups = [ "libvirtd" ]; # then log out/in
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# vagrant up # First boot: downloads box, provisions (~15-20 min)
|
||||||
|
# vagrant up --provision # Re-run provisioning on existing VM
|
||||||
|
# vagrant ssh # SSH in via vagrant
|
||||||
|
# vagrant ssh-config # Show IP/key if you prefer a raw ssh command
|
||||||
|
# vagrant halt # Stop the VM
|
||||||
|
# vagrant destroy # Delete the VM
|
||||||
|
#
|
||||||
|
# Inside the VM:
|
||||||
|
# cd C:\vagrant # Project root (synced from host, see note below)
|
||||||
|
# cargo build # Build skim
|
||||||
|
# cargo test # Run tests
|
||||||
|
#
|
||||||
|
# Note: The first `vagrant up` requires internet access on the VM to install
|
||||||
|
# packages via Chocolatey.
|
||||||
|
|
||||||
|
Vagrant.configure("2") do |config|
|
||||||
|
# Windows Server 2022 Core — minimal footprint, no desktop GUI.
|
||||||
|
# Box source: https://app.vagrantup.com/gusztavvargadr/boxes/windows-server-2022-standard-core
|
||||||
|
config.vm.box = "gusztavvargadr/windows-server-2022-standard-core"
|
||||||
|
|
||||||
|
# Vagrant manages the VM via WinRM (the Windows default).
|
||||||
|
config.vm.communicator = "winrm"
|
||||||
|
config.winrm.username = "vagrant"
|
||||||
|
config.winrm.password = "vagrant"
|
||||||
|
config.winrm.timeout = 600 # provisioning can take a while on first boot
|
||||||
|
|
||||||
|
config.vm.provider "libvirt" do |lv|
|
||||||
|
lv.driver = "kvm"
|
||||||
|
lv.memory = 2048
|
||||||
|
lv.cpus = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
# rsync is used for the synced folder because libvirt has no native
|
||||||
|
# shared-folder support for Windows guests. rsync must be present on the
|
||||||
|
# guest, so the folder is disabled on boot and synced via a post-provision
|
||||||
|
# trigger (after Chocolatey installs rsync below).
|
||||||
|
# Re-sync manually at any time with: vagrant rsync
|
||||||
|
# rsync is used because libvirt has no native shared-folder support for
|
||||||
|
# Windows guests. On a brand-new VM the very first `vagrant up` will fail
|
||||||
|
# the rsync step (rsync not yet installed on the guest); run
|
||||||
|
# `vagrant provision && vagrant rsync` to recover, or just
|
||||||
|
# `vagrant destroy && vagrant up` after the box is cached locally.
|
||||||
|
# cwRsync (the Windows rsync from Chocolatey) uses Cygwin paths, so the
|
||||||
|
# guest path must use /cygdrive/c/... rather than a bare /vagrant.
|
||||||
|
config.vm.synced_folder ".", "/cygdrive/c/vagrant", type: "rsync",
|
||||||
|
rsync__exclude: [".git/", "target/", ".jj/"],
|
||||||
|
rsync__args: ["--verbose", "--archive", "--delete", "--copy-links", "--no-owner", "--no-group"]
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Provisioning: configure OpenSSH + install Rust toolchain via Chocolatey.
|
||||||
|
# The box ships with Win32-OpenSSH already present, so we only configure it.
|
||||||
|
# Runs once on `vagrant up`; re-run with `vagrant provision`.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
config.vm.provision "shell", privileged: true, inline: <<-'POWERSHELL'
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# --- Chocolatey -------------------------------------------------------------
|
||||||
|
Write-Host "==> Installing Chocolatey..."
|
||||||
|
if (-not (Get-Command choco -ErrorAction SilentlyContinue)) {
|
||||||
|
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||||
|
[System.Net.ServicePointManager]::SecurityProtocol =
|
||||||
|
[System.Net.ServicePointManager]::SecurityProtocol -bor 3072
|
||||||
|
Invoke-Expression (
|
||||||
|
(New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- OpenSSH Server ---------------------------------------------------------
|
||||||
|
# The box ships with Win32-OpenSSH binaries at C:\Program Files\OpenSSH-Win64.
|
||||||
|
# Re-run install-sshd.ps1 to register the service (idempotent; safe to
|
||||||
|
# re-run if the service is already present).
|
||||||
|
Write-Host "==> Registering and starting sshd..."
|
||||||
|
& "C:\Program Files\OpenSSH-Win64\install-sshd.ps1"
|
||||||
|
Set-Service -Name sshd -StartupType Automatic
|
||||||
|
Start-Service -Name sshd
|
||||||
|
|
||||||
|
# Use PowerShell as the default shell for SSH sessions.
|
||||||
|
$regPath = "HKLM:\SOFTWARE\OpenSSH"
|
||||||
|
if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null }
|
||||||
|
Set-ItemProperty -Path $regPath -Name DefaultShell `
|
||||||
|
-Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||||
|
|
||||||
|
# Allow inbound SSH through the Windows firewall.
|
||||||
|
$rule = Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue
|
||||||
|
if (-not $rule) {
|
||||||
|
New-NetFirewallRule -Name "OpenSSH-Server-In-TCP" `
|
||||||
|
-DisplayName "OpenSSH Server (sshd)" `
|
||||||
|
-Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Rust, Git, rsync -------------------------------------------------------
|
||||||
|
Write-Host "==> Installing Rust, Git, rsync, and MinGW..."
|
||||||
|
choco install -y rust git rsync mingw
|
||||||
|
|
||||||
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") +
|
||||||
|
";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||||
|
|
||||||
|
# Add MinGW bin to the persistent system PATH so dlltool.exe is found in
|
||||||
|
# SSH sessions (which don't run the Chocolatey shim refresh).
|
||||||
|
$mingwBin = "C:\ProgramData\mingw64\mingw64\bin"
|
||||||
|
$machinePath = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
|
||||||
|
if ($machinePath -notlike "*$mingwBin*") {
|
||||||
|
[System.Environment]::SetEnvironmentVariable("Path", "$machinePath;$mingwBin", "Machine")
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "==> Provisioning complete."
|
||||||
|
Write-Host " SSH into the VM: vagrant ssh-config (then ssh to the reported IP)"
|
||||||
|
Write-Host " Build skim: cd C:\vagrant && cargo build"
|
||||||
|
POWERSHELL
|
||||||
|
end
|
||||||
|
|
@ -4,18 +4,22 @@ members = ["cargo:."]
|
||||||
# Config for 'dist'
|
# Config for 'dist'
|
||||||
[dist]
|
[dist]
|
||||||
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
|
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
|
||||||
cargo-dist-version = "0.30.3"
|
cargo-dist-version = "0.30.4"
|
||||||
# CI backends to support
|
# CI backends to support
|
||||||
ci = "github"
|
ci = "github"
|
||||||
# The installers to generate for each app
|
# The installers to generate for each app
|
||||||
installers = ["shell"]
|
installers = ["shell", "powershell", "msi"]
|
||||||
# Target platforms to build apps for (Rust target-triple syntax)
|
# Target platforms to build apps for (Rust target-triple syntax)
|
||||||
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"]
|
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "aarch64-pc-windows-msvc", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"]
|
||||||
# Path that installers should place binaries in
|
# Path that installers should place binaries in
|
||||||
install-path = "CARGO_HOME"
|
install-path = "CARGO_HOME"
|
||||||
# Whether to install an updater program
|
# Whether to install an updater program
|
||||||
install-updater = false
|
install-updater = false
|
||||||
|
# Extra static files to include in each App (path relative to this Cargo.toml's dir)
|
||||||
include = ["./man/", "./shell/"]
|
include = ["./man/", "./shell/"]
|
||||||
|
# Plan jobs to run in CI
|
||||||
plan-jobs = ["./test"]
|
plan-jobs = ["./test"]
|
||||||
|
# Publish jobs to run in CI
|
||||||
publish-jobs = ["./publish"]
|
publish-jobs = ["./publish"]
|
||||||
|
# Whether to publish prereleases to package managers
|
||||||
publish-prereleases = true
|
publish-prereleases = true
|
||||||
|
|
|
||||||
64
flake.nix
64
flake.nix
|
|
@ -7,37 +7,43 @@
|
||||||
inherit (inputs.nixpkgs) lib;
|
inherit (inputs.nixpkgs) lib;
|
||||||
systems = lib.systems.flakeExposed;
|
systems = lib.systems.flakeExposed;
|
||||||
eachSystem = lib.genAttrs systems;
|
eachSystem = lib.genAttrs systems;
|
||||||
pkgsFor = inputs.nixpkgs.legacyPackages;
|
pkgsFor = system: import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "vagrant" ];
|
||||||
|
};
|
||||||
in {
|
in {
|
||||||
devShells = eachSystem (system: {
|
devShells = eachSystem (system: let
|
||||||
default = pkgsFor.${system}.mkShellNoCC {
|
pkgs = pkgsFor system;
|
||||||
packages = with pkgsFor.${system}; [
|
|
||||||
cargo-nextest
|
# --- package groups -------------------------------------------------------
|
||||||
cargo-insta
|
base = with pkgs; [ rustup just ];
|
||||||
cargo-llvm-cov
|
tests = with pkgs; [ cargo-nextest cargo-insta cargo-llvm-cov tmux ];
|
||||||
cargo-edit
|
utils = with pkgs; [ hyperfine cargo-edit cargo-public-api git-cliff cargo-dist ];
|
||||||
cargo-public-api
|
gungraun = with pkgs; [ valgrind libclang binutils ];
|
||||||
git-cliff
|
bench = with pkgs; [ uv python313Packages.matplotlib python313Packages.requests ];
|
||||||
libclang
|
vagrantDeps = with pkgs; [ vagrant rsync ];
|
||||||
binutils
|
|
||||||
tmux
|
# --- shell hooks (only groups that need env vars) -------------------------
|
||||||
rustup
|
gungraunHook = ''
|
||||||
just
|
export LIBCLANG_PATH="${pkgs.libclang.lib}/lib"
|
||||||
hyperfine
|
export LD_LIBRARY_PATH="${pkgs.valgrind.out}/lib:$LD_LIBRARY_PATH"
|
||||||
uv
|
'';
|
||||||
valgrind
|
vagrantHook = ''
|
||||||
python313Packages.matplotlib
|
export VAGRANT_LIBVIRT_OVMF_CODE="${pkgs.OVMF.fd}/FV/OVMF_CODE.fd"
|
||||||
python313Packages.requests
|
'';
|
||||||
];
|
|
||||||
shellHook = let
|
mkShell = packages: shellHook: pkgs.mkShellNoCC { inherit packages shellHook; };
|
||||||
pkgs = pkgsFor.${system};
|
in {
|
||||||
in ''
|
default = mkShell base "";
|
||||||
export LIBCLANG_PATH="${pkgs.libclang.lib}/lib"
|
tests = mkShell (base ++ tests) "";
|
||||||
export LD_LIBRARY_PATH="${pkgs.valgrind.out}/lib:$LD_LIBRARY_PATH"
|
utils = mkShell (base ++ utils) "";
|
||||||
'';
|
gungraun = mkShell (base ++ gungraun) gungraunHook;
|
||||||
};
|
bench = mkShell (base ++ bench) "";
|
||||||
|
vagrant = mkShell (base ++ vagrantDeps) vagrantHook;
|
||||||
|
full = mkShell (base ++ tests ++ utils ++ gungraun ++ bench ++ vagrantDeps)
|
||||||
|
(gungraunHook + vagrantHook);
|
||||||
});
|
});
|
||||||
|
|
||||||
formatter = eachSystem (system: pkgsFor.${system}.nixfmt);
|
formatter = eachSystem (system: (pkgsFor system).nixfmt);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
justfile
2
justfile
|
|
@ -28,5 +28,5 @@ auto-release:
|
||||||
|
|
||||||
test target="":
|
test target="":
|
||||||
cargo test --doc
|
cargo test --doc
|
||||||
-cargo nextest run --features test-utils {{ target }}
|
cargo nextest run {{ target }}
|
||||||
tmux kill-session -t skim_e2e
|
tmux kill-session -t skim_e2e
|
||||||
|
|
|
||||||
|
|
@ -235,7 +235,7 @@ Disable mouse
|
||||||
\fB\-c\fR, \fB\-\-cmd\fR \fI<CMD>\fR
|
\fB\-c\fR, \fB\-\-cmd\fR \fI<CMD>\fR
|
||||||
Command to invoke dynamically in interactive mode
|
Command to invoke dynamically in interactive mode
|
||||||
|
|
||||||
Will be invoked using sh \-c
|
Will be invoked using sh \-c on unix\-like systems and cmd /c on Windows
|
||||||
.TP
|
.TP
|
||||||
\fB\-i\fR, \fB\-\-interactive\fR
|
\fB\-i\fR, \fB\-\-interactive\fR
|
||||||
Start skim in interactive mode
|
Start skim in interactive mode
|
||||||
|
|
@ -450,6 +450,7 @@ Format: `sk \-\-tmux <center|top|bottom|left|right>[,SIZE[%]][,SIZE[%]]`
|
||||||
Depending on the direction, the order and behavior of the sizes varies:
|
Depending on the direction, the order and behavior of the sizes varies:
|
||||||
|
|
||||||
Default: center,50%
|
Default: center,50%
|
||||||
|
Ignored on Windows
|
||||||
.SH HISTORY
|
.SH HISTORY
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-history\fR \fI<HISTORY_FILE>\fR
|
\fB\-\-history\fR \fI<HISTORY_FILE>\fR
|
||||||
|
|
@ -587,8 +588,6 @@ Generate shell completion script
|
||||||
Generate completion script for the specified shell: bash, zsh, fish, etc. The output can be directly sourced or saved to a file for automatic loading. Examples: source <(sk \-\-shell bash) (immediate use) sk \-\-shell bash >> ~/.bash_completion (persistent use)
|
Generate completion script for the specified shell: bash, zsh, fish, etc. The output can be directly sourced or saved to a file for automatic loading. Examples: source <(sk \-\-shell bash) (immediate use) sk \-\-shell bash >> ~/.bash_completion (persistent use)
|
||||||
|
|
||||||
Supported shells: bash, zsh, fish, powershell, elvish
|
Supported shells: bash, zsh, fish, powershell, elvish
|
||||||
|
|
||||||
Note: While PowerShell completions are supported, Windows is not supported for now.
|
|
||||||
.br
|
.br
|
||||||
|
|
||||||
.br
|
.br
|
||||||
|
|
@ -952,7 +951,11 @@ It will expand to the corresponding fields, separated by the `\-\-delimiter|\-d`
|
||||||
.SH "ENVIRONMENT VARIABLES"
|
.SH "ENVIRONMENT VARIABLES"
|
||||||
|
|
||||||
.SS SKIM_DEFAULT_COMMAND
|
.SS SKIM_DEFAULT_COMMAND
|
||||||
If set, skim will collect items with this command if no input is piped in (defaults to `find .` if not set)
|
|
||||||
|
.br
|
||||||
|
If set, skim will collect items with this command if no input is piped in.
|
||||||
|
.br
|
||||||
|
If not set, defaults to `find .` on unix\-like systems and `dir /s /b /A:\-D` on Windows.
|
||||||
.br
|
.br
|
||||||
|
|
||||||
.SS SKIM_DEFAULT_OPTIONS
|
.SS SKIM_DEFAULT_OPTIONS
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,10 @@ fn sk_main(mut opts: SkimOptions) -> Result<i32> {
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// output
|
// output
|
||||||
|
|
||||||
let Some(result) = (if opts.tmux.is_some() && env::var("TMUX").is_ok() {
|
let Some(result) = (if opts.tmux.is_some() && env::var("TMUX").is_ok() && cfg!(unix) {
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
unreachable!("tmux is ignored on windows");
|
||||||
|
#[cfg(unix)]
|
||||||
crate::tmux::run_with(&opts)
|
crate::tmux::run_with(&opts)
|
||||||
} else {
|
} else {
|
||||||
// read from pipe or command
|
// read from pipe or command
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
use std::process::{Child, Command, Stdio};
|
use std::process::{Child, Stdio};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
@ -400,8 +400,8 @@ type CommandOutput = (Option<Child>, Box<dyn BufRead + Send>);
|
||||||
|
|
||||||
fn get_command_output(cmd: &str, send_error: bool) -> Result<CommandOutput, Box<dyn Error>> {
|
fn get_command_output(cmd: &str, send_error: bool) -> Result<CommandOutput, Box<dyn Error>> {
|
||||||
let (reader, writer) = std::io::pipe()?;
|
let (reader, writer) = std::io::pipe()?;
|
||||||
let mut sh = Command::new("sh");
|
let mut command = crate::shell_cmd(cmd);
|
||||||
let command = sh.arg("-c").arg(cmd).stdout(writer.try_clone()?);
|
command.stdout(writer.try_clone()?);
|
||||||
if send_error {
|
if send_error {
|
||||||
trace!("redirecting stderr to the output");
|
trace!("redirecting stderr to the output");
|
||||||
command.stderr(writer);
|
command.stderr(writer);
|
||||||
|
|
|
||||||
20
src/lib.rs
20
src/lib.rs
|
|
@ -34,6 +34,7 @@ static GLOBAL_ALLOCATOR: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::process::Command;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::fuzzy_matcher::MatchIndices;
|
use crate::fuzzy_matcher::MatchIndices;
|
||||||
|
|
@ -66,6 +67,7 @@ mod skim;
|
||||||
mod skim_item;
|
mod skim_item;
|
||||||
pub mod spinlock;
|
pub mod spinlock;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
#[cfg(unix)]
|
||||||
pub mod tmux;
|
pub mod tmux;
|
||||||
pub mod tui;
|
pub mod tui;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
@ -75,6 +77,24 @@ pub mod manpage;
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
pub mod shell;
|
pub mod shell;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
const SKIM_DEFAULT_COMMAND: &str = "find .";
|
||||||
|
#[cfg(windows)]
|
||||||
|
const SKIM_DEFAULT_COMMAND: &str = "dir /s /b /A:-D";
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn shell_cmd(cmd: &str) -> Command {
|
||||||
|
let mut c = Command::new("sh");
|
||||||
|
c.arg("-c").arg(cmd);
|
||||||
|
c
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn shell_cmd(cmd: &str) -> Command {
|
||||||
|
let mut c = Command::new("cmd");
|
||||||
|
c.arg("/c").arg(cmd);
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
/// Trait for downcasting to concrete types from trait objects
|
/// Trait for downcasting to concrete types from trait objects
|
||||||
pub trait AsAny {
|
pub trait AsAny {
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,9 @@ It will expand to the corresponding fields, separated by the `--delimiter|-d` op
|
||||||
subsection(
|
subsection(
|
||||||
&mut custom,
|
&mut custom,
|
||||||
"SKIM_DEFAULT_COMMAND",
|
"SKIM_DEFAULT_COMMAND",
|
||||||
"If set, skim will collect items with this command if no input is piped in (defaults to `find .` if not set)",
|
"
|
||||||
|
If set, skim will collect items with this command if no input is piped in.
|
||||||
|
If not set, defaults to `find .` on unix-like systems and `dir /s /b /A:-D` on Windows.",
|
||||||
);
|
);
|
||||||
subsection(
|
subsection(
|
||||||
&mut custom,
|
&mut custom,
|
||||||
|
|
|
||||||
|
|
@ -312,7 +312,7 @@ pub struct SkimOptions {
|
||||||
|
|
||||||
/// Command to invoke dynamically in interactive mode
|
/// Command to invoke dynamically in interactive mode
|
||||||
///
|
///
|
||||||
/// Will be invoked using `sh -c`
|
/// Will be invoked using `sh -c` on unix-like systems and `cmd /c` on Windows
|
||||||
#[cfg_attr(feature = "cli", arg(short, long, help_heading = "Interface"))]
|
#[cfg_attr(feature = "cli", arg(short, long, help_heading = "Interface"))]
|
||||||
pub cmd: Option<String>,
|
pub cmd: Option<String>,
|
||||||
|
|
||||||
|
|
@ -731,8 +731,6 @@ pub struct SkimOptions {
|
||||||
/// `sk --shell bash >> ~/.bash_completion` (persistent use)
|
/// `sk --shell bash >> ~/.bash_completion` (persistent use)
|
||||||
///
|
///
|
||||||
/// Supported shells: bash, zsh, fish, powershell, elvish
|
/// Supported shells: bash, zsh, fish, powershell, elvish
|
||||||
///
|
|
||||||
/// Note: While `PowerShell` completions are supported, Windows is not supported for now.
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "cli",
|
feature = "cli",
|
||||||
|
|
@ -775,6 +773,7 @@ pub struct SkimOptions {
|
||||||
/// Depending on the direction, the order and behavior of the sizes varies:
|
/// Depending on the direction, the order and behavior of the sizes varies:
|
||||||
///
|
///
|
||||||
/// Default: center,50%
|
/// Default: center,50%
|
||||||
|
/// Ignored on Windows
|
||||||
#[cfg_attr(feature = "cli", arg(long, verbatim_doc_comment, help_heading = "Display", default_missing_value = "center,50%", num_args=0..))]
|
#[cfg_attr(feature = "cli", arg(long, verbatim_doc_comment, help_heading = "Display", default_missing_value = "center,50%", num_args=0..))]
|
||||||
pub tmux: Option<String>,
|
pub tmux: Option<String>,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,6 @@ where
|
||||||
///
|
///
|
||||||
/// Returns an error if parsing the height or other options fails.
|
/// Returns an error if parsing the height or other options fails.
|
||||||
pub fn init(options: SkimOptions, source: Option<SkimItemReceiver>) -> Result<Self> {
|
pub fn init(options: SkimOptions, source: Option<SkimItemReceiver>) -> Result<Self> {
|
||||||
const SKIM_DEFAULT_COMMAND: &str = "find .";
|
|
||||||
let height = Size::try_from(options.height.as_str())?;
|
let height = Size::try_from(options.height.as_str())?;
|
||||||
|
|
||||||
// application state
|
// application state
|
||||||
|
|
@ -143,7 +142,7 @@ where
|
||||||
let theme = Arc::new(crate::theme::ColorTheme::init_from_options(&options));
|
let theme = Arc::new(crate::theme::ColorTheme::init_from_options(&options));
|
||||||
let reader = Reader::from_options(&options).source(source);
|
let reader = Reader::from_options(&options).source(source);
|
||||||
let default_command = String::from(match env::var("SKIM_DEFAULT_COMMAND").as_deref() {
|
let default_command = String::from(match env::var("SKIM_DEFAULT_COMMAND").as_deref() {
|
||||||
Err(_) | Ok("") => SKIM_DEFAULT_COMMAND,
|
Err(_) | Ok("") => crate::SKIM_DEFAULT_COMMAND,
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
});
|
});
|
||||||
let cmd = options.cmd.clone().unwrap_or(default_command);
|
let cmd = options.cmd.clone().unwrap_or(default_command);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::process::{Command, Stdio};
|
use std::process::Stdio;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
@ -756,10 +756,9 @@ impl App {
|
||||||
self.input.move_to_end();
|
self.input.move_to_end();
|
||||||
}
|
}
|
||||||
Execute(cmd) => {
|
Execute(cmd) => {
|
||||||
let mut command = Command::new("sh");
|
|
||||||
let expanded_cmd = self.expand_cmd(cmd, true);
|
let expanded_cmd = self.expand_cmd(cmd, true);
|
||||||
debug!("execute: {expanded_cmd}");
|
debug!("execute: {expanded_cmd}");
|
||||||
command.args(["-c", &expanded_cmd]);
|
let mut command = crate::shell_cmd(&expanded_cmd);
|
||||||
let in_raw_mode = crossterm::terminal::is_raw_mode_enabled()?;
|
let in_raw_mode = crossterm::terminal::is_raw_mode_enabled()?;
|
||||||
if in_raw_mode {
|
if in_raw_mode {
|
||||||
crossterm::terminal::disable_raw_mode()?;
|
crossterm::terminal::disable_raw_mode()?;
|
||||||
|
|
@ -781,11 +780,10 @@ impl App {
|
||||||
return Ok(vec![Event::Redraw]);
|
return Ok(vec![Event::Redraw]);
|
||||||
}
|
}
|
||||||
ExecuteSilent(cmd) => {
|
ExecuteSilent(cmd) => {
|
||||||
let mut command = Command::new("sh");
|
|
||||||
let expanded_cmd = self.expand_cmd(cmd, true);
|
let expanded_cmd = self.expand_cmd(cmd, true);
|
||||||
command.args(["-c", &expanded_cmd]);
|
debug!("execute-silent: {expanded_cmd}");
|
||||||
command.stdout(Stdio::null());
|
let mut command = crate::shell_cmd(&expanded_cmd);
|
||||||
command.stderr(Stdio::null());
|
command.stdout(Stdio::null()).stderr(Stdio::null());
|
||||||
let _ = command.spawn();
|
let _ = command.spawn();
|
||||||
}
|
}
|
||||||
First | Top => {
|
First | Top => {
|
||||||
|
|
|
||||||
|
|
@ -110,29 +110,6 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new TUI for testing with a fullscreen viewport.
|
|
||||||
///
|
|
||||||
/// This constructor skips terminal-specific operations (cursor detection,
|
|
||||||
/// raw mode, scrolling) that don't work with `TestBackend`. Use this when
|
|
||||||
/// writing snapshot tests or other tests that need to render the UI.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// Returns an error if the terminal cannot be initialized with the given backend.
|
|
||||||
#[cfg(any(test, feature = "test-utils"))]
|
|
||||||
pub fn new_for_test(backend: B) -> Result<Self> {
|
|
||||||
let event_channel = channel(1024 * 1024);
|
|
||||||
Ok(Self {
|
|
||||||
terminal: ratatui::Terminal::new(backend)?,
|
|
||||||
task: None,
|
|
||||||
event_rx: event_channel.1,
|
|
||||||
event_tx: event_channel.0,
|
|
||||||
tick_rate: TICK_RATE,
|
|
||||||
cancellation_token: CancellationToken::default(),
|
|
||||||
is_fullscreen: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enters the TUI by enabling raw mode and starting event handling
|
/// Enters the TUI by enabling raw mode and starting event handling
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
|
@ -140,6 +117,10 @@ where
|
||||||
/// Returns an error if enabling raw mode or mouse capture fails.
|
/// Returns an error if enabling raw mode or mouse capture fails.
|
||||||
pub fn enter(&mut self) -> Result<()> {
|
pub fn enter(&mut self) -> Result<()> {
|
||||||
crossterm::terminal::enable_raw_mode()?;
|
crossterm::terminal::enable_raw_mode()?;
|
||||||
|
// On Windows, install a console ctrl handler so that CTRL_C_EVENT
|
||||||
|
// performs terminal cleanup instead of killing the process abruptly.
|
||||||
|
#[cfg(windows)]
|
||||||
|
super::windows::install_ctrl_c_handler()?;
|
||||||
crossterm::execute!(std::io::stderr(), EnableMouseCapture, EnableBracketedPaste)?;
|
crossterm::execute!(std::io::stderr(), EnableMouseCapture, EnableBracketedPaste)?;
|
||||||
if self.is_fullscreen {
|
if self.is_fullscreen {
|
||||||
crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?;
|
crossterm::execute!(std::io::stderr(), EnterAlternateScreen, cursor::Hide)?;
|
||||||
|
|
@ -155,16 +136,10 @@ where
|
||||||
/// Returns an error if disabling raw mode or mouse capture fails.
|
/// Returns an error if disabling raw mode or mouse capture fails.
|
||||||
pub fn exit(&mut self) -> Result<()> {
|
pub fn exit(&mut self) -> Result<()> {
|
||||||
self.stop();
|
self.stop();
|
||||||
if crossterm::terminal::is_raw_mode_enabled()? {
|
cleanup_terminal()?;
|
||||||
crossterm::execute!(
|
// Remove our console ctrl handler now that raw mode is off.
|
||||||
std::io::stderr(),
|
#[cfg(windows)]
|
||||||
DisableMouseCapture,
|
super::windows::uninstall_ctrl_c_handler();
|
||||||
DisableBracketedPaste,
|
|
||||||
LeaveAlternateScreen,
|
|
||||||
cursor::Show
|
|
||||||
)?;
|
|
||||||
crossterm::terminal::disable_raw_mode()?;
|
|
||||||
}
|
|
||||||
// When using the inline layout, we want to remove all previous output
|
// When using the inline layout, we want to remove all previous output
|
||||||
// -> reset cursor at the top of the drawing area
|
// -> reset cursor at the top of the drawing area
|
||||||
if !self.is_fullscreen {
|
if !self.is_fullscreen {
|
||||||
|
|
@ -275,8 +250,28 @@ fn set_panic_hook() {
|
||||||
PANIC_HOOK_SET.call_once(|| {
|
PANIC_HOOK_SET.call_once(|| {
|
||||||
let hook = std::panic::take_hook();
|
let hook = std::panic::take_hook();
|
||||||
std::panic::set_hook(Box::new(move |panic_info| {
|
std::panic::set_hook(Box::new(move |panic_info| {
|
||||||
ratatui::restore(); // ignore any errors as we are already failing
|
let _ = cleanup_terminal();
|
||||||
|
#[cfg(windows)]
|
||||||
|
super::windows::uninstall_ctrl_c_handler();
|
||||||
hook(panic_info);
|
hook(panic_info);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Perform terminal cleanup: disable mouse capture, bracketed paste,
|
||||||
|
/// leave alternate screen, show cursor, and disable raw mode.
|
||||||
|
///
|
||||||
|
/// This is safe to call from any thread since:
|
||||||
|
/// - Escape sequences are written atomically to stderr
|
||||||
|
/// - `SetConsoleMode` (used by `disable_raw_mode`) is thread-safe on Windows
|
||||||
|
pub(crate) fn cleanup_terminal() -> std::io::Result<()> {
|
||||||
|
crossterm::execute!(
|
||||||
|
std::io::stderr(),
|
||||||
|
DisableMouseCapture,
|
||||||
|
DisableBracketedPaste,
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
cursor::Show
|
||||||
|
)?;
|
||||||
|
crossterm::terminal::disable_raw_mode()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ pub use widget::{SkimRender, SkimWidget};
|
||||||
mod app;
|
mod app;
|
||||||
mod backend;
|
mod backend;
|
||||||
pub(crate) mod util;
|
pub(crate) mod util;
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod windows;
|
||||||
pub use backend::Tui;
|
pub use backend::Tui;
|
||||||
/// Event handling and action definitions
|
/// Event handling and action definitions
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,10 @@ use ratatui::{
|
||||||
use tui_term::vt100;
|
use tui_term::vt100;
|
||||||
use tui_term::widget::PseudoTerminal;
|
use tui_term::widget::PseudoTerminal;
|
||||||
|
|
||||||
use std::io::Read;
|
|
||||||
use std::process::Command;
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
|
use std::{env, io::Read};
|
||||||
|
|
||||||
use super::util::{find_csi_end, find_osc_end, handle_csi_query, handle_osc_query};
|
use super::util::{find_csi_end, find_osc_end, handle_csi_query, handle_osc_query};
|
||||||
use super::widget::{SkimRender, SkimWidget};
|
use super::widget::{SkimRender, SkimWidget};
|
||||||
|
|
@ -304,12 +303,13 @@ impl Preview {
|
||||||
);
|
);
|
||||||
self.init_pty();
|
self.init_pty();
|
||||||
trace!("initialized pty");
|
trace!("initialized pty");
|
||||||
|
// no PTY on windows, we can keep sh
|
||||||
let mut shell_cmd = portable_pty::CommandBuilder::new("/bin/sh");
|
let mut shell_cmd = portable_pty::CommandBuilder::new("/bin/sh");
|
||||||
shell_cmd.env("ROWS", self.rows.to_string());
|
shell_cmd.env("ROWS", self.rows.to_string());
|
||||||
shell_cmd.env("COLUMNS", self.cols.to_string());
|
shell_cmd.env("COLUMNS", self.cols.to_string());
|
||||||
shell_cmd.env("PAGER", "");
|
shell_cmd.env("PAGER", "");
|
||||||
shell_cmd.arg("-c");
|
shell_cmd.arg("-c");
|
||||||
if let Ok(cwd) = nix::unistd::getcwd() {
|
if let Ok(cwd) = env::current_dir() {
|
||||||
shell_cmd.cwd(cwd);
|
shell_cmd.cwd(cwd);
|
||||||
}
|
}
|
||||||
shell_cmd.arg(cmd);
|
shell_cmd.arg(cmd);
|
||||||
|
|
@ -392,14 +392,12 @@ impl Preview {
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
trace!("spawning preview cmd {cmd}");
|
trace!("spawning preview cmd {cmd}");
|
||||||
let mut shell_cmd = Command::new("/bin/sh");
|
let mut shell_cmd = crate::shell_cmd(cmd);
|
||||||
shell_cmd
|
shell_cmd
|
||||||
.env("ROWS", self.rows.to_string())
|
.env("ROWS", self.rows.to_string())
|
||||||
.env("COLUMNS", self.cols.to_string())
|
.env("COLUMNS", self.cols.to_string())
|
||||||
.env("PAGER", "")
|
.env("PAGER", "");
|
||||||
.arg("-c")
|
if let Ok(cwd) = env::current_dir() {
|
||||||
.arg(cmd);
|
|
||||||
if let Ok(cwd) = nix::unistd::getcwd() {
|
|
||||||
shell_cmd.current_dir(cwd);
|
shell_cmd.current_dir(cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -452,7 +450,7 @@ impl Drop for Preview {
|
||||||
|
|
||||||
impl SkimWidget for Preview {
|
impl SkimWidget for Preview {
|
||||||
fn from_options(options: &SkimOptions, theme: Arc<ColorTheme>) -> Self {
|
fn from_options(options: &SkimOptions, theme: Arc<ColorTheme>) -> Self {
|
||||||
#[cfg_attr(target_os = "macos", allow(unused_mut))]
|
#[cfg_attr(not(target_os = "linux"), allow(unused_mut))]
|
||||||
let mut res = Self {
|
let mut res = Self {
|
||||||
theme,
|
theme,
|
||||||
border: options.border,
|
border: options.border,
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,14 @@ use ratatui::{
|
||||||
style::Style,
|
style::Style,
|
||||||
text::{Line, Span, Text},
|
text::{Line, Span, Text},
|
||||||
};
|
};
|
||||||
use std::fs::OpenOptions;
|
use std::io::{self, Write};
|
||||||
use std::io::{self, Read, Write};
|
#[cfg(unix)]
|
||||||
use std::os::fd::{AsFd, AsRawFd};
|
use std::{
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
fs::OpenOptions,
|
||||||
|
io::Read,
|
||||||
|
os::fd::{AsFd, AsRawFd},
|
||||||
|
os::unix::fs::OpenOptionsExt as _,
|
||||||
|
};
|
||||||
use unicode_display_width::is_double_width;
|
use unicode_display_width::is_double_width;
|
||||||
|
|
||||||
// Directly taken from https://docs.rs/unicode-display-width/0.3.0/src/unicode_display_width/lib.rs.html#77-81
|
// Directly taken from https://docs.rs/unicode-display-width/0.3.0/src/unicode_display_width/lib.rs.html#77-81
|
||||||
|
|
@ -219,6 +223,7 @@ impl Drop for RawMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
pub(crate) fn cursor_pos_from_tty() -> io::Result<(u16, u16)> {
|
pub(crate) fn cursor_pos_from_tty() -> io::Result<(u16, u16)> {
|
||||||
let _guard = RawMode::new()?;
|
let _guard = RawMode::new()?;
|
||||||
let mut tty = OpenOptions::new()
|
let mut tty = OpenOptions::new()
|
||||||
|
|
@ -274,6 +279,12 @@ pub(crate) fn cursor_pos_from_tty() -> io::Result<(u16, u16)> {
|
||||||
Ok((cx, cy))
|
Ok((cx, cy))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub(crate) fn cursor_pos_from_tty() -> io::Result<(u16, u16)> {
|
||||||
|
let _guard = RawMode::new()?;
|
||||||
|
crossterm::cursor::position()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
59
src/tui/windows.rs
Normal file
59
src/tui/windows.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
//! Windows-specific console control handler.
|
||||||
|
//!
|
||||||
|
//! On Windows, pressing Ctrl-C generates a `CTRL_C_EVENT` that by default
|
||||||
|
//! terminates the process immediately — before any destructors (and therefore
|
||||||
|
//! terminal cleanup) can run. This module installs a custom handler via
|
||||||
|
//! `SetConsoleCtrlHandler` that performs terminal cleanup (disable mouse
|
||||||
|
//! capture, bracketed paste, alternate screen, raw mode) and then exits
|
||||||
|
//! with code 130 (the conventional "interrupted" exit code).
|
||||||
|
|
||||||
|
use super::backend::cleanup_terminal;
|
||||||
|
|
||||||
|
// Windows console control event constants.
|
||||||
|
const CTRL_C_EVENT: u32 = 0;
|
||||||
|
const CTRL_BREAK_EVENT: u32 = 1;
|
||||||
|
const CTRL_CLOSE_EVENT: u32 = 2;
|
||||||
|
|
||||||
|
unsafe extern "system" {
|
||||||
|
fn SetConsoleCtrlHandler(handler_routine: Option<unsafe extern "system" fn(u32) -> i32>, add: i32) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Console ctrl handler callback invoked by Windows on `CTRL_C_EVENT` (and
|
||||||
|
/// other control events like `CTRL_BREAK_EVENT`, `CTRL_CLOSE_EVENT`, etc.).
|
||||||
|
///
|
||||||
|
/// The handler cleans up the terminal state so that mouse capture, raw mode,
|
||||||
|
/// and the alternate screen are properly disabled, then exits with code 130.
|
||||||
|
///
|
||||||
|
/// Returning `TRUE` (1) tells Windows that the event has been handled and the
|
||||||
|
/// default handler (which would call `ExitProcess` without cleanup) should
|
||||||
|
/// **not** run.
|
||||||
|
unsafe extern "system" fn ctrl_c_handler(ctrl_type: u32) -> i32 {
|
||||||
|
match ctrl_type {
|
||||||
|
CTRL_C_EVENT | CTRL_BREAK_EVENT | CTRL_CLOSE_EVENT => {
|
||||||
|
let _ = cleanup_terminal();
|
||||||
|
// 130 = 128 + SIGINT, the conventional exit code for Ctrl-C.
|
||||||
|
std::process::exit(130);
|
||||||
|
}
|
||||||
|
_ => 0, // Let the next handler deal with it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install our console ctrl handler so that `CTRL_C_EVENT` triggers terminal
|
||||||
|
/// cleanup instead of an abrupt process termination.
|
||||||
|
pub(crate) fn install_ctrl_c_handler() -> std::io::Result<()> {
|
||||||
|
// SAFETY: `SetConsoleCtrlHandler` with a valid handler and `TRUE` is a
|
||||||
|
// well-defined Windows API call.
|
||||||
|
unsafe {
|
||||||
|
if SetConsoleCtrlHandler(Some(ctrl_c_handler), 1) == 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove our console ctrl handler, restoring default behaviour.
|
||||||
|
pub(crate) fn uninstall_ctrl_c_handler() {
|
||||||
|
unsafe {
|
||||||
|
SetConsoleCtrlHandler(Some(ctrl_c_handler), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/util.rs
21
src/util.rs
|
|
@ -97,8 +97,13 @@ pub fn printf<'a>(
|
||||||
current: &Option<MatchedItem>,
|
current: &Option<MatchedItem>,
|
||||||
query: &str,
|
query: &str,
|
||||||
command_query: &str,
|
command_query: &str,
|
||||||
quote_args: bool,
|
mut quote_args: bool,
|
||||||
) -> String {
|
) -> String {
|
||||||
|
// Windows uses different shell quoting conventions (double quotes, caret escaping)
|
||||||
|
// that are incompatible with the Unix-style single-quote escaping implemented here.
|
||||||
|
if cfg!(windows) {
|
||||||
|
quote_args = false;
|
||||||
|
}
|
||||||
let escape_arg = |s: &str, quote: bool| {
|
let escape_arg = |s: &str, quote: bool| {
|
||||||
let mut res = s.replace('\0', "\\0").clone();
|
let mut res = s.replace('\0', "\\0").clone();
|
||||||
if quote && quote_args {
|
if quote && quote_args {
|
||||||
|
|
@ -315,7 +320,11 @@ mod test {
|
||||||
"cmd query",
|
"cmd query",
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
"[1] 'item 2' [2] 'item 2' [3] '2' [4] 'item 1' 'item 2' 'item 3' 'item 4' [5] 'query' [6] 'cmd query' [7] 'item 1, item 2, item 3, item 4' [8] '0','0','0','0'"
|
if cfg!(unix) {
|
||||||
|
"[1] 'item 2' [2] 'item 2' [3] '2' [4] 'item 1' 'item 2' 'item 3' 'item 4' [5] 'query' [6] 'cmd query' [7] 'item 1, item 2, item 3, item 4' [8] '0','0','0','0'"
|
||||||
|
} else {
|
||||||
|
"[1] item 2 [2] item 2 [3] 2 [4] item 1 item 2 item 3 item 4 [5] query [6] cmd query [7] item 1, item 2, item 3, item 4 [8] 0','0','0','0"
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -331,7 +340,7 @@ mod test {
|
||||||
"cq",
|
"cq",
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
"'1' '2'"
|
if cfg!(unix) { "'1' '2'" } else { "1 2" }
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
printf(
|
printf(
|
||||||
|
|
@ -344,7 +353,7 @@ mod test {
|
||||||
"cq",
|
"cq",
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
"'1'"
|
if cfg!(unix) { "'1'" } else { "1" }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -360,7 +369,7 @@ mod test {
|
||||||
"cq",
|
"cq",
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
"'{..2}'"
|
if cfg!(unix) { "'{..2}'" } else { "{..2}" }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -376,7 +385,7 @@ mod test {
|
||||||
"cq",
|
"cq",
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
"{} '1'"
|
if cfg!(unix) { "{} '1'" } else { "{} 1" }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ RUN cargo install cargo-nextest
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
CMD ["cargo", "nextest", "run", "--release", "--features", "test-utils"]
|
CMD ["cargo", "nextest", "run", "--release"]
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
use common::tmux::Keys::*;
|
use common::tmux::Keys::*;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
sk_test!(test_ansi_flag_enabled, @cmd "echo -e 'plain\\n\\x1b[31mred\\x1b[0m\\n\\x1b[32mgreen\\x1b[0m'", &["--ansi", "--color", "current_match_bg:1,current_bg:2"], {
|
sk_test!(test_ansi_flag_enabled, @cmd "echo -e 'plain\\n\\x1b[31mred\\x1b[0m\\n\\x1b[32mgreen\\x1b[0m'", &["--ansi", "--color", "current_match_bg:1,current_bg:2"], {
|
||||||
@capture[0] starts_with(">");
|
@capture[0] starts_with(">");
|
||||||
@lines |l| (l.len() >= 3 && l.iter().any(|line| line.contains("plain")));
|
@lines |l| (l.len() >= 3 && l.iter().any(|line| line.contains("plain")));
|
||||||
|
|
@ -17,6 +19,7 @@ sk_test!(test_ansi_flag_enabled, @cmd "echo -e 'plain\\n\\x1b[31mred\\x1b[0m\\n\
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
sk_test!(test_ansi_flag_disabled, @cmd "echo -e 'plain\\n\\x1b[31mred\\x1b[0m\\n\\x1b[32mgreen\\x1b[0m'", &[], {
|
sk_test!(test_ansi_flag_disabled, @cmd "echo -e 'plain\\n\\x1b[31mred\\x1b[0m\\n\\x1b[32mgreen\\x1b[0m'", &[], {
|
||||||
@capture[0] starts_with(">");
|
@capture[0] starts_with(">");
|
||||||
@capture[*] contains("plain");
|
@capture[*] contains("plain");
|
||||||
|
|
@ -28,6 +31,7 @@ sk_test!(test_ansi_flag_disabled, @cmd "echo -e 'plain\\n\\x1b[31mred\\x1b[0m\\n
|
||||||
@keys Enter;
|
@keys Enter;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
sk_test!(test_ansi_matching_on_stripped_text, @cmd "echo -e '\\x1b[32mgreen\\x1b[0m text\\n\\x1b[31mred\\x1b[0m text\\nplain text'", &["--ansi"], {
|
sk_test!(test_ansi_matching_on_stripped_text, @cmd "echo -e '\\x1b[32mgreen\\x1b[0m text\\n\\x1b[31mred\\x1b[0m text\\nplain text'", &["--ansi"], {
|
||||||
@capture[0] starts_with(">");
|
@capture[0] starts_with(">");
|
||||||
@lines |l| (l.len() >= 3 && l.iter().any(|line| line.contains("plain")));
|
@lines |l| (l.len() >= 3 && l.iter().any(|line| line.contains("plain")));
|
||||||
|
|
@ -44,6 +48,7 @@ sk_test!(test_ansi_matching_on_stripped_text, @cmd "echo -e '\\x1b[32mgreen\\x1b
|
||||||
@lines |l| (l.len() == 3);
|
@lines |l| (l.len() == 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
sk_test!(test_ansi_flag_no_strip, @cmd "echo -e 'plain\\n\\x1b[31mred\\x1b[0m\\n\\x1b[32mgreen\\x1b[0m'", &["--ansi", "--no-strip-ansi", "--color", "current_match_bg:1,current_bg:2"], {
|
sk_test!(test_ansi_flag_no_strip, @cmd "echo -e 'plain\\n\\x1b[31mred\\x1b[0m\\n\\x1b[32mgreen\\x1b[0m'", &["--ansi", "--no-strip-ansi", "--color", "current_match_bg:1,current_bg:2"], {
|
||||||
@capture[0] starts_with(">");
|
@capture[0] starts_with(">");
|
||||||
@lines |l| (l.len() >= 3 && l.iter().any(|line| line.contains("plain")));
|
@lines |l| (l.len() >= 3 && l.iter().any(|line| line.contains("plain")));
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use ratatui::backend::TestBackend;
|
||||||
use skim::{
|
use skim::{
|
||||||
Skim, SkimItemReceiver,
|
Skim, SkimItemReceiver,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
tui::{Event, Tui, event::Action},
|
tui::{Event, Size, Tui, event::Action},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A test harness for running skim TUI tests with insta snapshots.
|
/// A test harness for running skim TUI tests with insta snapshots.
|
||||||
|
|
@ -359,7 +359,7 @@ fn enter_sized_with_source(
|
||||||
source: Option<SkimItemReceiver>,
|
source: Option<SkimItemReceiver>,
|
||||||
) -> Result<TestHarness> {
|
) -> Result<TestHarness> {
|
||||||
let backend = TestBackend::new(width, height);
|
let backend = TestBackend::new(width, height);
|
||||||
let tui = Tui::new_for_test(backend)?;
|
let tui = Tui::new_with_height_and_backend(backend, Size::Percent(100))?;
|
||||||
let mut skim = Skim::<TestBackend>::init(options, source)?;
|
let mut skim = Skim::<TestBackend>::init(options, source)?;
|
||||||
skim.init_tui_with(tui);
|
skim.init_tui_with(tui);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,25 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod insta;
|
pub mod insta;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
#[cfg(unix)]
|
||||||
pub mod tmux;
|
pub mod tmux;
|
||||||
|
|
||||||
#[cfg(all(debug_assertions, coverage))]
|
#[cfg(all(unix, debug_assertions, coverage))]
|
||||||
pub static SK: &str =
|
pub static SK: &str =
|
||||||
"SKIM_DEFAULT_OPTIONS= SKIM_DEFAULT_COMMAND= SKIM_OPTIONS_FILE= ./target/llvm-cov-target/debug/sk";
|
"SKIM_DEFAULT_OPTIONS= SKIM_DEFAULT_COMMAND= SKIM_OPTIONS_FILE= ./target/llvm-cov-target/debug/sk";
|
||||||
#[cfg(all(debug_assertions, not(coverage)))]
|
#[cfg(all(unix, debug_assertions, not(coverage)))]
|
||||||
pub static SK: &str = "SKIM_DEFAULT_OPTIONS= SKIM_DEFAULT_COMMAND= SKIM_OPTIONS_FILE= ./target/debug/sk";
|
pub static SK: &str = "SKIM_DEFAULT_OPTIONS= SKIM_DEFAULT_COMMAND= SKIM_OPTIONS_FILE= ./target/debug/sk";
|
||||||
#[cfg(all(not(debug_assertions), coverage))]
|
#[cfg(all(unix, not(debug_assertions), coverage))]
|
||||||
pub static SK: &str =
|
pub static SK: &str =
|
||||||
"SKIM_DEFAULT_OPTIONS= SKIM_DEFAULT_COMMAND= SKIM_OPTIONS_FILE= ./target/llvm-cov-target/release/sk";
|
"SKIM_DEFAULT_OPTIONS= SKIM_DEFAULT_COMMAND= SKIM_OPTIONS_FILE= ./target/llvm-cov-target/release/sk";
|
||||||
#[cfg(all(not(debug_assertions), not(coverage)))]
|
#[cfg(all(unix, not(debug_assertions), not(coverage)))]
|
||||||
pub static SK: &str = "SKIM_DEFAULT_OPTIONS= SKIM_DEFAULT_COMMAND= SKIM_OPTIONS_FILE= ./target/release/sk";
|
pub static SK: &str = "SKIM_DEFAULT_OPTIONS= SKIM_DEFAULT_COMMAND= SKIM_OPTIONS_FILE= ./target/release/sk";
|
||||||
|
|
||||||
|
#[cfg(all(windows, debug_assertions, coverage))]
|
||||||
|
pub static SK: &str = r".\target\llvm-cov-target\debug\sk.exe";
|
||||||
|
#[cfg(all(windows, debug_assertions, not(coverage)))]
|
||||||
|
pub static SK: &str = r".\target\debug\sk.exe";
|
||||||
|
#[cfg(all(windows, not(debug_assertions), coverage))]
|
||||||
|
pub static SK: &str = r".\target\llvm-cov-target\release\sk.exe";
|
||||||
|
#[cfg(all(windows, not(debug_assertions), not(coverage)))]
|
||||||
|
pub static SK: &str = r".\target\release\sk.exe";
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ insta_test!(
|
||||||
&[]
|
&[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
insta_test!(interactive_mode_command_execution, @interactive, &["-i", "--cmd", "echo 'foo {q}'"], {
|
insta_test!(interactive_mode_command_execution, @interactive, &["-i", "--cmd", "echo 'foo {q}'"], {
|
||||||
@snap;
|
@snap;
|
||||||
@type "bar";
|
@type "bar";
|
||||||
|
|
@ -26,6 +27,15 @@ insta_test!(interactive_mode_command_execution, @interactive, &["-i", "--cmd", "
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
insta_test!(interactive_mode_command_execution, @interactive, &["-i", "--cmd", "echo foo {q}"], {
|
||||||
|
@snap;
|
||||||
|
@type "bar";
|
||||||
|
@snap;
|
||||||
|
@type "baz";
|
||||||
|
@snap;
|
||||||
|
});
|
||||||
|
|
||||||
insta_test!(unicode_input, [""], &["-q", ""], {
|
insta_test!(unicode_input, [""], &["-q", ""], {
|
||||||
@snap;
|
@snap;
|
||||||
@type "|";
|
@type "|";
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![cfg(unix)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod common;
|
mod common;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![cfg(unix)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// TODO: automate listen tests on windows
|
||||||
|
// Maybe using smaller tests ? actions processing is already tested, only the IPC part needs testing
|
||||||
|
#![cfg(unix)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod common;
|
mod common;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ insta_test!(opt_min_query_length, ["line1", "line2", "line3"], &["--min-query-le
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use info=hidden to hide the spinner
|
// Use info=hidden to hide the spinner
|
||||||
|
#[cfg(unix)]
|
||||||
insta_test!(opt_min_query_length_interactive, @interactive, &["-i", "--min-query-length", "3", "--cmd", "printf 'line1\\nline2\\nline3'", "--info", "hidden"], {
|
insta_test!(opt_min_query_length_interactive, @interactive, &["-i", "--min-query-length", "3", "--cmd", "printf 'line1\\nline2\\nline3'", "--info", "hidden"], {
|
||||||
@snap;
|
@snap;
|
||||||
@type "li";
|
@type "li";
|
||||||
|
|
@ -27,6 +28,15 @@ insta_test!(opt_min_query_length_interactive, @interactive, &["-i", "--min-query
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
insta_test!(opt_min_query_length_interactive, @interactive, &["-i", "--min-query-length", "3", "--cmd", "echo line1 & echo line2 & echo line3", "--info", "hidden"], {
|
||||||
|
@snap;
|
||||||
|
@type "li";
|
||||||
|
@snap;
|
||||||
|
@char 'n';
|
||||||
|
@snap;
|
||||||
|
});
|
||||||
|
|
||||||
insta_test!(opt_with_nth_1, ["f1,f2,f3,f4"], &["--delimiter", ",", "--with-nth", "1"], {
|
insta_test!(opt_with_nth_1, ["f1,f2,f3,f4"], &["--delimiter", ",", "--with-nth", "1"], {
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
@ -319,12 +329,20 @@ insta_test!(opt_pre_select_pat, ["a", "b", "c"], &["-m", "--pre-select-pat", "[b
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
insta_test!(opt_no_clear_if_empty, @interactive, &["-i", "--no-clear-if-empty", "-c", "printf {q}", "--cmd-query", "xxxx"], {
|
insta_test!(opt_no_clear_if_empty, @interactive, &["-i", "--no-clear-if-empty", "-c", "printf {q}", "--cmd-query", "xxxx"], {
|
||||||
@snap;
|
@snap;
|
||||||
@ctrl 'w';
|
@ctrl 'w';
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
insta_test!(opt_no_clear_if_empty, @interactive, &["-i", "--no-clear-if-empty", "-c", "if not [{q}]==[] echo.{q}", "--cmd-query", "xxxx"], {
|
||||||
|
@snap;
|
||||||
|
@ctrl 'w';
|
||||||
|
@snap;
|
||||||
|
});
|
||||||
|
|
||||||
insta_test!(opt_tac, ["a", "b"], &["--tac"], {
|
insta_test!(opt_tac, ["a", "b"], &["--tac"], {
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
@ -386,18 +404,36 @@ insta_test!(opt_multiple_flags_prompt, [""], &["--prompt", "a", "--prompt", "b",
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
insta_test!(opt_multiple_flags_cmd_prompt, @interactive, &["-i", "--cmd-prompt", "a", "--cmd-prompt", "c", "--cmd", "echo"], {
|
insta_test!(opt_multiple_flags_cmd_prompt, @interactive, &["-i", "--cmd-prompt", "a", "--cmd-prompt", "c", "--cmd", "echo"], {
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
insta_test!(opt_multiple_flags_cmd_prompt, @interactive, &["-i", "--cmd-prompt", "a", "--cmd-prompt", "c", "--cmd", "echo."], {
|
||||||
|
@snap;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
insta_test!(opt_multiple_flags_cmd_query, @interactive, &["-i", "--cmd-query", "a", "--cmd-query", "b", "--cmd", "echo"], {
|
insta_test!(opt_multiple_flags_cmd_query, @interactive, &["-i", "--cmd-query", "a", "--cmd-query", "b", "--cmd", "echo"], {
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
insta_test!(opt_multiple_flags_cmd_query, @interactive, &["-i", "--cmd-query", "a", "--cmd-query", "b", "--cmd", "echo."], {
|
||||||
|
@snap;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
insta_test!(opt_multiple_flags_interactive, @interactive, &["-i", "--interactive", "--interactive", "--cmd", "echo"], {
|
insta_test!(opt_multiple_flags_interactive, @interactive, &["-i", "--interactive", "--interactive", "--cmd", "echo"], {
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
insta_test!(opt_multiple_flags_interactive, @interactive, &["-i", "--interactive", "--interactive", "--cmd", "echo."], {
|
||||||
|
@snap;
|
||||||
|
});
|
||||||
|
|
||||||
insta_test!(opt_multiple_flags_reverse, [""], &["--reverse", "--reverse"], {
|
insta_test!(opt_multiple_flags_reverse, [""], &["--reverse", "--reverse"], {
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
@ -468,6 +504,7 @@ insta_test!(opt_border_quadrant_outside, ["a", "b", "c", "ac"], &["-q", "a", "--
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn opt_select_1() -> std::io::Result<()> {
|
fn opt_select_1() -> std::io::Result<()> {
|
||||||
let res = Command::new("/bin/sh")
|
let res = Command::new("/bin/sh")
|
||||||
|
|
@ -481,6 +518,23 @@ fn opt_select_1() -> std::io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[test]
|
||||||
|
fn opt_select_1_windows() -> std::io::Result<()> {
|
||||||
|
let res = Command::new("cmd")
|
||||||
|
.arg("/C")
|
||||||
|
.arg(format!(r"(echo 1 & echo 2 & echo 3) | {SK} --select-1 -q 3"))
|
||||||
|
.env("SKIM_DEFAULT_OPTIONS", "")
|
||||||
|
.env("SKIM_DEFAULT_COMMAND", "")
|
||||||
|
.env("SKIM_OPTIONS_FILE", "")
|
||||||
|
.stdin(std::process::Stdio::null())
|
||||||
|
.output()?;
|
||||||
|
assert_eq!(res.status.code(), Some(0));
|
||||||
|
assert!(res.stdout.starts_with(b"3"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn opt_exit_0() -> std::io::Result<()> {
|
fn opt_exit_0() -> std::io::Result<()> {
|
||||||
let res = Command::new("/bin/sh")
|
let res = Command::new("/bin/sh")
|
||||||
|
|
@ -494,6 +548,22 @@ fn opt_exit_0() -> std::io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[test]
|
||||||
|
fn opt_exit_0_windows() -> std::io::Result<()> {
|
||||||
|
let res = Command::new("cmd")
|
||||||
|
.arg("/C")
|
||||||
|
.arg(format!(r"(echo 1 & echo 2 & echo 3) | {SK} --exit-0 -q 4"))
|
||||||
|
.env("SKIM_DEFAULT_OPTIONS", "")
|
||||||
|
.env("SKIM_DEFAULT_COMMAND", "")
|
||||||
|
.env("SKIM_OPTIONS_FILE", "")
|
||||||
|
.stdin(std::process::Stdio::null())
|
||||||
|
.output()?;
|
||||||
|
assert_eq!(res.status.code(), Some(1));
|
||||||
|
assert_eq!(res.stdout, &[]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
insta_test!(opt_select_1_enter, ["1", "2", "3", "11"], &["-q", "1", "--select-1"], {
|
insta_test!(opt_select_1_enter, ["1", "2", "3", "11"], &["-q", "1", "--select-1"], {
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,17 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
const PREVIEW: &str = "printf \"=%.0s\\n\" $(seq 1 1000)";
|
const PREVIEW: &str = "printf \"=%.0s\\n\" $(seq 1 1000)";
|
||||||
|
#[cfg(windows)]
|
||||||
|
const PREVIEW: &str = "for /l %i in (1,1,1000) do @echo =";
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
insta_test!(preview_preserve_quotes, ["'\"ABC\"'"], &["--preview", "echo X{}X"], {
|
insta_test!(preview_preserve_quotes, ["'\"ABC\"'"], &["--preview", "echo X{}X"], {
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
insta_test!(preview_nul_char, ["a\0b"], &["--preview", "printf \"{}\" | hexdump -C"], {
|
insta_test!(preview_nul_char, ["a\0b"], &["--preview", "printf \"{}\" | hexdump -C"], {
|
||||||
@snap;
|
@snap;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![cfg(unix)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
|
|
||||||
228
wix/main.wxs
Normal file
228
wix/main.wxs
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
<?xml version='1.0' encoding='windows-1252'?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2017 Christopher R. Field.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The "cargo wix" subcommand provides a variety of predefined variables available
|
||||||
|
for customization of this template. The values for each variable are set at
|
||||||
|
installer creation time. The following variables are available:
|
||||||
|
|
||||||
|
TargetTriple = The rustc target triple name.
|
||||||
|
TargetEnv = The rustc target environment. This is typically either
|
||||||
|
"msvc" or "gnu" depending on the toolchain downloaded and
|
||||||
|
installed.
|
||||||
|
TargetVendor = The rustc target vendor. This is typically "pc", but Rust
|
||||||
|
does support other vendors, like "uwp".
|
||||||
|
CargoTargetBinDir = The complete path to the directory containing the
|
||||||
|
binaries (exes) to include. The default would be
|
||||||
|
"target\release\". If an explicit rustc target triple is
|
||||||
|
used, i.e. cross-compiling, then the default path would
|
||||||
|
be "target\<CARGO_TARGET>\<CARGO_PROFILE>",
|
||||||
|
where "<CARGO_TARGET>" is replaced with the "CargoTarget"
|
||||||
|
variable value and "<CARGO_PROFILE>" is replaced with the
|
||||||
|
value from the "CargoProfile" variable. This can also
|
||||||
|
be overridden manually with the "target-bin-dir" flag.
|
||||||
|
CargoTargetDir = The path to the directory for the build artifacts, i.e.
|
||||||
|
"target".
|
||||||
|
CargoProfile = The cargo profile used to build the binaries
|
||||||
|
(usually "debug" or "release").
|
||||||
|
Version = The version for the installer. The default is the
|
||||||
|
"Major.Minor.Fix" semantic versioning number of the Rust
|
||||||
|
package.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please do not remove these pre-processor If-Else blocks. These are used with
|
||||||
|
the `cargo wix` subcommand to automatically determine the installation
|
||||||
|
destination for 32-bit versus 64-bit installers. Removal of these lines will
|
||||||
|
cause installation errors.
|
||||||
|
-->
|
||||||
|
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>
|
||||||
|
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||||
|
<?else ?>
|
||||||
|
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||||
|
<?endif ?>
|
||||||
|
|
||||||
|
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
|
||||||
|
|
||||||
|
<Product
|
||||||
|
Id='*'
|
||||||
|
Name='skim'
|
||||||
|
UpgradeCode='6DDAED06-EBE2-41C4-94F5-CB03F2A4B92E'
|
||||||
|
Manufacturer='Loric ANDRE; Zhang Jinzhou'
|
||||||
|
Language='1033'
|
||||||
|
Codepage='1252'
|
||||||
|
Version='$(var.Version)'>
|
||||||
|
|
||||||
|
<Package Id='*'
|
||||||
|
Keywords='Installer'
|
||||||
|
Description='Fuzzy Finder in rust!'
|
||||||
|
Manufacturer='Loric ANDRE; Zhang Jinzhou'
|
||||||
|
InstallerVersion='450'
|
||||||
|
Languages='1033'
|
||||||
|
Compressed='yes'
|
||||||
|
InstallScope='perMachine'
|
||||||
|
SummaryCodepage='1252'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MajorUpgrade
|
||||||
|
Schedule='afterInstallInitialize'
|
||||||
|
DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>
|
||||||
|
|
||||||
|
<Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1'/>
|
||||||
|
<Property Id='DiskPrompt' Value='skim Installation'/>
|
||||||
|
|
||||||
|
<Directory Id='TARGETDIR' Name='SourceDir'>
|
||||||
|
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
|
||||||
|
<Directory Id='APPLICATIONFOLDER' Name='skim'>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Enabling the license sidecar file in the installer is a four step process:
|
||||||
|
|
||||||
|
1. Uncomment the `Component` tag and its contents.
|
||||||
|
2. Change the value for the `Source` attribute in the `File` tag to a path
|
||||||
|
to the file that should be included as the license sidecar file. The path
|
||||||
|
can, and probably should be, relative to this file.
|
||||||
|
3. Change the value for the `Name` attribute in the `File` tag to the
|
||||||
|
desired name for the file when it is installed alongside the `bin` folder
|
||||||
|
in the installation directory. This can be omitted if the desired name is
|
||||||
|
the same as the file name.
|
||||||
|
4. Uncomment the `ComponentRef` tag with the Id attribute value of "License"
|
||||||
|
further down in this file.
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
<Component Id='License' Guid='*'>
|
||||||
|
<File Id='LicenseFile' Name='ChangeMe' DiskId='1' Source='C:\Path\To\File' KeyPath='yes'/>
|
||||||
|
</Component>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<Directory Id='Bin' Name='bin'>
|
||||||
|
<Component Id='Path' Guid='05D1A327-19E0-4C82-B5EC-6B28E40C691B' KeyPath='yes'>
|
||||||
|
<Environment
|
||||||
|
Id='PATH'
|
||||||
|
Name='PATH'
|
||||||
|
Value='[Bin]'
|
||||||
|
Permanent='no'
|
||||||
|
Part='last'
|
||||||
|
Action='set'
|
||||||
|
System='yes'/>
|
||||||
|
</Component>
|
||||||
|
<Component Id='binary0' Guid='*'>
|
||||||
|
<File
|
||||||
|
Id='exe0'
|
||||||
|
Name='sk.exe'
|
||||||
|
DiskId='1'
|
||||||
|
Source='$(var.CargoTargetBinDir)\sk.exe'
|
||||||
|
KeyPath='yes'/>
|
||||||
|
</Component>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
<Feature
|
||||||
|
Id='Binaries'
|
||||||
|
Title='Application'
|
||||||
|
Description='Installs all binaries and the license.'
|
||||||
|
Level='1'
|
||||||
|
ConfigurableDirectory='APPLICATIONFOLDER'
|
||||||
|
AllowAdvertise='no'
|
||||||
|
Display='expand'
|
||||||
|
Absent='disallow'>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Uncomment the following `ComponentRef` tag to add the license
|
||||||
|
sidecar file to the installer.
|
||||||
|
-->
|
||||||
|
<!--<ComponentRef Id='License'/>-->
|
||||||
|
|
||||||
|
<ComponentRef Id='binary0'/>
|
||||||
|
|
||||||
|
<Feature
|
||||||
|
Id='Environment'
|
||||||
|
Title='PATH Environment Variable'
|
||||||
|
Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.'
|
||||||
|
Level='1'
|
||||||
|
Absent='allow'>
|
||||||
|
<ComponentRef Id='Path'/>
|
||||||
|
</Feature>
|
||||||
|
</Feature>
|
||||||
|
|
||||||
|
<SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize'/>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Uncomment the following `Icon` and `Property` tags to change the product icon.
|
||||||
|
|
||||||
|
The product icon is the graphic that appears in the Add/Remove
|
||||||
|
Programs control panel for the application.
|
||||||
|
-->
|
||||||
|
<!--<Icon Id='ProductICO' SourceFile='wix\Product.ico'/>-->
|
||||||
|
<!--<Property Id='ARPPRODUCTICON' Value='ProductICO' />-->
|
||||||
|
|
||||||
|
<Property Id='ARPHELPLINK' Value='https://docs.rs/skim'/>
|
||||||
|
|
||||||
|
<UI>
|
||||||
|
<UIRef Id='WixUI_FeatureTree'/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Enabling the EULA dialog in the installer is a three step process:
|
||||||
|
|
||||||
|
1. Comment out or remove the two `Publish` tags that follow the
|
||||||
|
`WixVariable` tag.
|
||||||
|
2. Uncomment the `<WixVariable Id='WixUILicenseRtf' Value='Path\to\Eula.rft'>` tag further down
|
||||||
|
3. Replace the `Value` attribute of the `WixVariable` tag with
|
||||||
|
the path to a RTF file that will be used as the EULA and
|
||||||
|
displayed in the license agreement dialog.
|
||||||
|
-->
|
||||||
|
<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>
|
||||||
|
<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>
|
||||||
|
|
||||||
|
</UI>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Enabling the EULA dialog in the installer requires uncommenting
|
||||||
|
the following `WixUILicenseRTF` tag and changing the `Value`
|
||||||
|
attribute.
|
||||||
|
-->
|
||||||
|
<!-- <WixVariable Id='WixUILicenseRtf' Value='Relative\Path\to\Eula.rtf'/> -->
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Uncomment the next `WixVariable` tag to customize the installer's
|
||||||
|
Graphical User Interface (GUI) and add a custom banner image across
|
||||||
|
the top of each screen. See the WiX Toolset documentation for details
|
||||||
|
about customization.
|
||||||
|
|
||||||
|
The banner BMP dimensions are 493 x 58 pixels.
|
||||||
|
-->
|
||||||
|
<!--<WixVariable Id='WixUIBannerBmp' Value='wix\Banner.bmp'/>-->
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Uncomment the next `WixVariable` tag to customize the installer's
|
||||||
|
Graphical User Interface (GUI) and add a custom image to the first
|
||||||
|
dialog, or screen. See the WiX Toolset documentation for details about
|
||||||
|
customization.
|
||||||
|
|
||||||
|
The dialog BMP dimensions are 493 x 312 pixels.
|
||||||
|
-->
|
||||||
|
<!--<WixVariable Id='WixUIDialogBmp' Value='wix\Dialog.bmp'/>-->
|
||||||
|
|
||||||
|
</Product>
|
||||||
|
|
||||||
|
</Wix>
|
||||||
Loading…
Reference in a new issue