Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e77a66404 | ||
|
|
c84ba1f4f0 | ||
|
|
e774c98b74 | ||
|
|
1a4601ccbd | ||
|
|
87dcda60e1 | ||
|
|
47a5f45c71 | ||
|
|
f424b2d9b6 | ||
|
|
3209d597c0 | ||
|
|
60ba4e3467 | ||
|
|
41877d390a | ||
|
|
3137713245 | ||
|
|
8ddb2addfb |
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit",
|
||||
"type": "command",
|
||||
"command": "bash -c 'if [ -f Cargo.toml ]; then if command -v cargo-nextest >/dev/null 2>&1; then cargo nextest run 2>&1; else cargo test 2>&1; fi; fi'",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: ci
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: rust:latest
|
||||
commands:
|
||||
- cargo fmt --check
|
||||
- cargo clippy -- -D warnings
|
||||
- cargo test
|
||||
|
||||
- name: coverage
|
||||
image: xd009642/tarpaulin
|
||||
privileged: true
|
||||
environment:
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
commands:
|
||||
- apt-get update -qq && apt-get install -y -qq jq curl
|
||||
- cargo tarpaulin --out Json 2>/dev/null
|
||||
- |
|
||||
PCT=$(jq '(.covered / .coverable * 100) | floor' tarpaulin-report.json)
|
||||
if [ "$PCT" -ge 80 ]; then COLOR="brightgreen"
|
||||
elif [ "$PCT" -ge 60 ]; then COLOR="yellow"
|
||||
else COLOR="red"; fi
|
||||
curl -sf "https://img.shields.io/badge/coverage-${PCT}%25-${COLOR}.svg" -o coverage.svg
|
||||
- |
|
||||
curl -sf -X DELETE \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.cincoeuzebio.com/api/packages/cinco/generic/badges/latest" || true
|
||||
curl -fsSL -X PUT \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-T coverage.svg \
|
||||
"https://git.cincoeuzebio.com/api/packages/cinco/generic/badges/latest/coverage.svg"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: release
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
ref:
|
||||
- refs/tags/[0-9]*
|
||||
|
||||
steps:
|
||||
- name: build-x86_64
|
||||
image: messense/rust-musl-cross:x86_64-musl
|
||||
commands:
|
||||
- cargo build --release --target x86_64-unknown-linux-musl
|
||||
- cp target/x86_64-unknown-linux-musl/release/tmuxido tmuxido-x86_64-linux
|
||||
|
||||
- name: build-aarch64
|
||||
image: messense/rust-musl-cross:aarch64-musl
|
||||
commands:
|
||||
- cargo build --release --target aarch64-unknown-linux-musl
|
||||
- cp target/aarch64-unknown-linux-musl/release/tmuxido tmuxido-aarch64-linux
|
||||
|
||||
- name: publish
|
||||
image: alpine
|
||||
environment:
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
commands:
|
||||
- apk add --no-cache curl jq
|
||||
- |
|
||||
# Extract changelog entry for this tag (content between this and previous ## heading)
|
||||
BODY=$(awk "/^## \[${DRONE_TAG}\]/{found=1; next} found && /^## \[/{exit} found{print}" CHANGELOG.md)
|
||||
RELEASE_ID=$(curl -fsSL -X POST \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"https://git.cincoeuzebio.com/api/v1/repos/cinco/Tmuxido/releases" \
|
||||
-d "{\"tag_name\":\"$DRONE_TAG\",\"name\":\"$DRONE_TAG\",\"body\":$(echo "$BODY" | jq -Rs .)}" \
|
||||
| jq -r .id)
|
||||
for ASSET in tmuxido-x86_64-linux tmuxido-aarch64-linux; do
|
||||
curl -fsSL -X POST \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
"https://git.cincoeuzebio.com/api/v1/repos/cinco/Tmuxido/releases/$RELEASE_ID/assets" \
|
||||
-F "attachment=@$ASSET"
|
||||
done
|
||||
depends_on:
|
||||
- build-x86_64
|
||||
- build-aarch64
|
||||
@@ -1 +0,0 @@
|
||||
/target
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"rust-mcp": {
|
||||
"type": "stdio",
|
||||
"command": "rust-mcp-server",
|
||||
"args": []
|
||||
},
|
||||
"crates": {
|
||||
"type": "stdio",
|
||||
"command": "crates-mcp",
|
||||
"args": []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
[[windows]]
|
||||
name = "tmuxido"
|
||||
|
||||
[[windows]]
|
||||
name = "editor"
|
||||
layout = "main-horizontal"
|
||||
panes = [
|
||||
"code . ; claude --dangerously-skip-permissions",
|
||||
"clear",
|
||||
"clear"
|
||||
]
|
||||
|
||||
# [[windows]]
|
||||
# name = "build"
|
||||
# panes = []
|
||||
|
||||
# [[windows]]
|
||||
# name = "git"
|
||||
# panes = []
|
||||
@@ -1,255 +0,0 @@
|
||||
# ============================================================================
|
||||
# Project-specific tmux session configuration
|
||||
# ============================================================================
|
||||
# Place this file as .tmuxido.toml in your project root directory
|
||||
#
|
||||
# This configuration will be used when opening this specific project.
|
||||
# If this file doesn't exist, the global default_session from
|
||||
# ~/.config/tmuxido/tmuxido.toml will be used.
|
||||
#
|
||||
# Compatible with any tmux base-index setting (0 or 1)
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
# BASIC EXAMPLE: Single window with one pane
|
||||
# ============================================================================
|
||||
# [[windows]]
|
||||
# name = "editor"
|
||||
# panes = [] # Empty = just open a shell in the project directory
|
||||
|
||||
# ============================================================================
|
||||
# INTERMEDIATE EXAMPLE: Single window with multiple panes and layout
|
||||
# ============================================================================
|
||||
# This creates the classic layout:
|
||||
# - Main pane on top (nvim)
|
||||
# - Two smaller panes below, side by side
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "editor"
|
||||
# layout = "main-horizontal"
|
||||
# panes = [
|
||||
# "nvim .", # Pane 0: Opens nvim in project root
|
||||
# "clear", # Pane 1: Shell ready for commands
|
||||
# "clear" # Pane 2: Another shell
|
||||
# ]
|
||||
|
||||
# ============================================================================
|
||||
# ADVANCED EXAMPLE: Multiple windows for a complete workflow
|
||||
# ============================================================================
|
||||
|
||||
# Window 1: Editor with side terminal
|
||||
[[windows]]
|
||||
name = "editor"
|
||||
layout = "main-vertical"
|
||||
panes = [
|
||||
"nvim .", # Main pane: Editor
|
||||
"clear" # Side pane: Terminal for quick commands
|
||||
]
|
||||
|
||||
# Window 2: Development server
|
||||
[[windows]]
|
||||
name = "server"
|
||||
panes = [
|
||||
"npm run dev" # Auto-start dev server
|
||||
]
|
||||
|
||||
# Window 3: Git operations
|
||||
[[windows]]
|
||||
name = "git"
|
||||
panes = [
|
||||
"git status", # Show current status
|
||||
"lazygit" # Or use lazygit if installed
|
||||
]
|
||||
|
||||
# Window 4: Database/Logs
|
||||
[[windows]]
|
||||
name = "logs"
|
||||
layout = "even-horizontal"
|
||||
panes = [
|
||||
"tail -f logs/development.log",
|
||||
"docker-compose logs -f"
|
||||
]
|
||||
|
||||
# ============================================================================
|
||||
# PRACTICAL EXAMPLES BY PROJECT TYPE
|
||||
# ============================================================================
|
||||
|
||||
# --- Frontend React/Vue/Angular Project ---
|
||||
# [[windows]]
|
||||
# name = "code"
|
||||
# layout = "main-horizontal"
|
||||
# panes = ["nvim .", "clear", "clear"]
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "dev"
|
||||
# panes = ["npm run dev"]
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "test"
|
||||
# panes = ["npm run test:watch"]
|
||||
|
||||
# --- Backend API Project ---
|
||||
# [[windows]]
|
||||
# name = "editor"
|
||||
# layout = "main-vertical"
|
||||
# panes = ["nvim src/", "cargo watch -x run"] # For Rust
|
||||
# # Or: panes = ["nvim .", "nodemon server.js"] # For Node.js
|
||||
# # Or: panes = ["nvim .", "python manage.py runserver"] # For Django
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "database"
|
||||
# panes = ["psql mydb"] # Or mysql, redis-cli, etc
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "logs"
|
||||
# panes = ["tail -f logs/app.log"]
|
||||
|
||||
# --- Full Stack Project ---
|
||||
# [[windows]]
|
||||
# name = "frontend"
|
||||
# layout = "main-horizontal"
|
||||
# panes = [
|
||||
# "cd frontend && nvim .",
|
||||
# "cd frontend && npm run dev"
|
||||
# ]
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "backend"
|
||||
# layout = "main-horizontal"
|
||||
# panes = [
|
||||
# "cd backend && nvim .",
|
||||
# "cd backend && cargo run"
|
||||
# ]
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "database"
|
||||
# panes = ["docker-compose up postgres redis"]
|
||||
|
||||
# --- DevOps/Infrastructure Project ---
|
||||
# [[windows]]
|
||||
# name = "code"
|
||||
# panes = ["nvim ."]
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "terraform"
|
||||
# panes = ["terraform plan"]
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "k8s"
|
||||
# layout = "even-vertical"
|
||||
# panes = [
|
||||
# "kubectl get pods -w",
|
||||
# "stern -l app=myapp", # Log streaming
|
||||
# "k9s" # Kubernetes TUI
|
||||
# ]
|
||||
|
||||
# --- Data Science/ML Project ---
|
||||
# [[windows]]
|
||||
# name = "jupyter"
|
||||
# panes = ["jupyter lab"]
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "editor"
|
||||
# panes = ["nvim ."]
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "training"
|
||||
# layout = "even-vertical"
|
||||
# panes = [
|
||||
# "python train.py",
|
||||
# "watch -n 1 nvidia-smi" # GPU monitoring
|
||||
# ]
|
||||
|
||||
# ============================================================================
|
||||
# AVAILABLE LAYOUTS
|
||||
# ============================================================================
|
||||
# Layout determines how panes are arranged in a window:
|
||||
#
|
||||
# main-horizontal: Main pane on top, others stacked below horizontally
|
||||
# ┌─────────────────────────────┐
|
||||
# │ Main Pane │
|
||||
# ├──────────────┬──────────────┤
|
||||
# │ Pane 2 │ Pane 3 │
|
||||
# └──────────────┴──────────────┘
|
||||
#
|
||||
# main-vertical: Main pane on left, others stacked right vertically
|
||||
# ┌──────────┬──────────┐
|
||||
# │ │ Pane 2 │
|
||||
# │ Main ├──────────┤
|
||||
# │ Pane │ Pane 3 │
|
||||
# └──────────┴──────────┘
|
||||
#
|
||||
# tiled: All panes in a grid
|
||||
# ┌──────────┬──────────┐
|
||||
# │ Pane 1 │ Pane 2 │
|
||||
# ├──────────┼──────────┤
|
||||
# │ Pane 3 │ Pane 4 │
|
||||
# └──────────┴──────────┘
|
||||
#
|
||||
# even-horizontal: All panes in a row, equal width
|
||||
# ┌────┬────┬────┬────┐
|
||||
# │ P1 │ P2 │ P3 │ P4 │
|
||||
# └────┴────┴────┴────┘
|
||||
#
|
||||
# even-vertical: All panes in a column, equal height
|
||||
# ┌──────────────┐
|
||||
# │ Pane 1 │
|
||||
# ├──────────────┤
|
||||
# │ Pane 2 │
|
||||
# ├──────────────┤
|
||||
# │ Pane 3 │
|
||||
# └──────────────┘
|
||||
|
||||
# ============================================================================
|
||||
# TIPS & TRICKS
|
||||
# ============================================================================
|
||||
# 1. Commands are executed with "Enter" automatically
|
||||
# 2. Use "clear" to just open a clean shell
|
||||
# 3. Commands run in the project directory by default
|
||||
# 4. Use "cd subdir && command" to run in subdirectories
|
||||
# 5. First pane in array is pane 0 (uses the window's initial pane)
|
||||
# 6. Subsequent panes are created by splitting
|
||||
# 7. Layout is applied after all panes are created
|
||||
# 8. Empty panes array = single pane window
|
||||
# 9. You can have as many windows as you want
|
||||
# 10. Compatible with tmux base-index 0 or 1 (auto-detected)
|
||||
|
||||
# ============================================================================
|
||||
# COMMON PATTERNS
|
||||
# ============================================================================
|
||||
|
||||
# Pattern: Editor + horizontal terminal split
|
||||
# [[windows]]
|
||||
# name = "work"
|
||||
# layout = "main-horizontal"
|
||||
# panes = ["nvim .", "clear"]
|
||||
|
||||
# Pattern: Vertical split with commands side by side
|
||||
# [[windows]]
|
||||
# name = "dev"
|
||||
# layout = "even-vertical"
|
||||
# panes = ["npm run dev", "npm run test:watch"]
|
||||
|
||||
# Pattern: Monitoring dashboard
|
||||
# [[windows]]
|
||||
# name = "monitor"
|
||||
# layout = "tiled"
|
||||
# panes = [
|
||||
# "htop",
|
||||
# "watch -n 1 df -h",
|
||||
# "tail -f /var/log/syslog",
|
||||
# "docker stats"
|
||||
# ]
|
||||
|
||||
# Pattern: Simple workflow (no special layout needed)
|
||||
# [[windows]]
|
||||
# name = "code"
|
||||
# panes = []
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "run"
|
||||
# panes = []
|
||||
#
|
||||
# [[windows]]
|
||||
# name = "git"
|
||||
# panes = []
|
||||
@@ -1,41 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [0.2.4] - 2026-03-01
|
||||
|
||||
### Fixed
|
||||
- Cálculo do percentual de cobertura no CI (campo correto do JSON do tarpaulin)
|
||||
- Pipeline `release` restrito a tags com formato de versão (`[0-9]*`)
|
||||
|
||||
## [0.2.2] - 2026-02-28
|
||||
|
||||
### Added
|
||||
- Coverage badge gerado por `cargo-tarpaulin` no CI e hospedado no Gitea Generic Package Registry
|
||||
- CI status, coverage, version e Rust edition badges no README
|
||||
|
||||
## [0.2.1] - 2026-02-28
|
||||
|
||||
### Added
|
||||
- Drone CI pipeline (`ci`) that runs `cargo fmt --check`, `cargo clippy`, and `cargo test` on every push and pull request
|
||||
|
||||
## [0.2.0] - 2026-02-28
|
||||
|
||||
### Added
|
||||
- Unit tests for `cache`, `session`, and `config` modules
|
||||
- Integration tests for scan, session config, and cache lifecycle
|
||||
|
||||
### Changed
|
||||
- Refactored business logic into `lib.rs` for better testability; `main.rs` is now a thin entrypoint
|
||||
|
||||
## [0.1.1] - 2026-02-28
|
||||
|
||||
### Fixed
|
||||
- Removed personal path references from default configuration and examples
|
||||
|
||||
## [0.1.0] - 2026-02-28
|
||||
|
||||
### Added
|
||||
- Initial release of tmuxido
|
||||
@@ -1,121 +0,0 @@
|
||||
# Rust Project — Claude Instructions
|
||||
|
||||
## Mandatory Rules
|
||||
|
||||
1. **Always write tests** alongside production code — no feature ships without tests
|
||||
2. **Always verify tests pass** after every change — the PostToolUse hook runs automatically;
|
||||
if it shows failures, fix them before moving on
|
||||
3. Run `cargo clippy -- -D warnings` and resolve all warnings
|
||||
4. Run `cargo fmt` before considering any task complete
|
||||
|
||||
## Available MCP Tools
|
||||
|
||||
Install with `curl -sSf https://raw.githubusercontent.com/USUARIO/claude-rust-scaffold/main/install.sh | sh`
|
||||
|
||||
| Server | Tools | Purpose |
|
||||
|--------|-------|---------|
|
||||
| `rust-mcp` | `cargo_check`, `cargo_build`, `cargo_test`, `cargo_clippy`, `cargo_fmt`, `cargo_add` | Run cargo commands directly |
|
||||
| `crates` | search, versions, dependencies, docs | Explore crates.io and docs.rs |
|
||||
|
||||
## Test Structure
|
||||
|
||||
### Unit Tests — inside `src/`
|
||||
|
||||
Place at the bottom of each source file:
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_return_error_when_input_is_empty() {
|
||||
// arrange
|
||||
let input = "";
|
||||
// act
|
||||
let result = parse(input);
|
||||
// assert
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Name tests descriptively: `should_<outcome>_when_<condition>`
|
||||
- Cover: happy path, edge cases (empty, max values), error cases
|
||||
|
||||
### Integration Tests — `tests/` directory
|
||||
|
||||
- One file per feature or behavior
|
||||
- Use only public interfaces (`pub`)
|
||||
- Simulate real usage end-to-end
|
||||
|
||||
```rust
|
||||
// tests/parsing.rs
|
||||
use tmuxido::parse;
|
||||
|
||||
#[test]
|
||||
fn parses_valid_input_successfully() {
|
||||
let result = parse("valid input");
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
```
|
||||
|
||||
### Snapshot Testing with `insta`
|
||||
|
||||
For complex outputs or large structs:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn renders_report_correctly() {
|
||||
let report = generate_report(&data);
|
||||
insta::assert_snapshot!(report);
|
||||
}
|
||||
```
|
||||
|
||||
Review snapshots: `cargo insta review`
|
||||
|
||||
### Property Testing with `proptest`
|
||||
|
||||
For pure functions over wide input domains:
|
||||
|
||||
```rust
|
||||
use proptest::prelude::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn round_trip_encode_decode(s in ".*") {
|
||||
let encoded = encode(&s);
|
||||
prop_assert_eq!(decode(&encoded), s);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Recommended `Cargo.toml` dev-dependencies
|
||||
|
||||
```toml
|
||||
[dev-dependencies]
|
||||
proptest = "1"
|
||||
insta = { version = "1", features = ["json", "yaml"] }
|
||||
mockall = "0.13"
|
||||
|
||||
# if async:
|
||||
tokio = { version = "1", features = ["full", "test-util"] }
|
||||
```
|
||||
|
||||
## Recommended Project Structure
|
||||
|
||||
```
|
||||
tmuxido/
|
||||
├── Cargo.toml
|
||||
├── src/
|
||||
│ ├── lib.rs # core logic (unit tests at bottom)
|
||||
│ ├── main.rs # entrypoint (thin, delegates to lib)
|
||||
│ └── module/
|
||||
│ └── mod.rs # #[cfg(test)] mod tests {} at bottom
|
||||
├── tests/
|
||||
│ └── integration.rs # integration tests
|
||||
└── benches/
|
||||
└── bench.rs # benchmarks (optional)
|
||||
```
|
||||
|
||||
Prefer `lib.rs` + `main.rs` split so logic stays testable independently of the binary entrypoint.
|
||||
Generated
-944
@@ -1,944 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.4.6",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellexpand"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb"
|
||||
dependencies = [
|
||||
"dirs 6.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tmuxido"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"dirs 5.0.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"tempfile",
|
||||
"toml",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
[package]
|
||||
name = "tmuxido"
|
||||
version = "0.2.4"
|
||||
edition = "2024"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
toml = "0.8"
|
||||
dirs = "5.0"
|
||||
walkdir = "2.4"
|
||||
anyhow = "1.0"
|
||||
shellexpand = "3.1"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
@@ -1,159 +0,0 @@
|
||||
# tmuxido
|
||||
|
||||
[](https://drone.cincoeuzebio.com/cinco/Tmuxido)
|
||||
[](https://drone.cincoeuzebio.com/cinco/Tmuxido)
|
||||
[](https://git.cincoeuzebio.com/cinco/Tmuxido/releases)
|
||||

|
||||
|
||||
A Rust-based tool to quickly find and open projects in tmux using fzf. No external dependencies except tmux and fzf!
|
||||
|
||||
## Features
|
||||
|
||||
- Search for git repositories in configurable paths
|
||||
- Interactive selection using fzf
|
||||
- Native tmux session creation (no tmuxinator required!)
|
||||
- Support for project-specific `.tmuxido.toml` configs
|
||||
- Smart session switching (reuses existing sessions)
|
||||
- TOML-based configuration
|
||||
- Smart caching system for fast subsequent runs
|
||||
- Configurable cache TTL
|
||||
- Zero external dependencies (except tmux and fzf)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
curl -fsSL https://git.cincoeuzebio.com/cinco/Tmuxido/raw/branch/main/install.sh | sh
|
||||
```
|
||||
|
||||
Installs the latest release binary to `~/.local/bin/tmuxido`. On first run, the config file is created automatically at `~/.config/tmuxido/tmuxido.toml`.
|
||||
|
||||
### Build from source
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
cp target/release/tmuxido ~/.local/bin/
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
The configuration file is located at `~/.config/tmuxido/tmuxido.toml`.
|
||||
|
||||
On first run, a default configuration will be created automatically.
|
||||
|
||||
Example configuration:
|
||||
```toml
|
||||
# List of paths where to search for projects (git repositories)
|
||||
paths = [
|
||||
"~/Projects",
|
||||
# "~/work",
|
||||
]
|
||||
|
||||
# Maximum depth to search for .git directories
|
||||
max_depth = 5
|
||||
|
||||
# Enable project caching (default: true)
|
||||
cache_enabled = true
|
||||
|
||||
# Cache TTL in hours (default: 24)
|
||||
cache_ttl_hours = 24
|
||||
|
||||
# Default session configuration (used when project has no .tmuxido.toml)
|
||||
[default_session]
|
||||
|
||||
[[default_session.windows]]
|
||||
name = "editor"
|
||||
panes = []
|
||||
|
||||
[[default_session.windows]]
|
||||
name = "terminal"
|
||||
panes = []
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Run without arguments to search all configured paths and select with fzf:
|
||||
```bash
|
||||
tmuxido
|
||||
```
|
||||
|
||||
Or provide a specific directory:
|
||||
```bash
|
||||
tmuxido /path/to/project
|
||||
```
|
||||
|
||||
Force refresh the cache (useful after adding new projects):
|
||||
```bash
|
||||
tmuxido --refresh
|
||||
# or
|
||||
tmuxido -r
|
||||
```
|
||||
|
||||
Check cache status:
|
||||
```bash
|
||||
tmuxido --cache-status
|
||||
```
|
||||
|
||||
View help:
|
||||
```bash
|
||||
tmuxido --help
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- [tmux](https://github.com/tmux/tmux) - Terminal multiplexer
|
||||
- [fzf](https://github.com/junegunn/fzf) - For interactive selection
|
||||
|
||||
## How it works
|
||||
|
||||
1. Searches for git repositories (directories containing `.git`) in configured paths
|
||||
2. Caches the results for faster subsequent runs
|
||||
3. Presents them using fzf for selection
|
||||
4. Creates or switches to a tmux session for the selected project
|
||||
5. If a `.tmuxido.toml` config exists in the project, uses it to set up custom windows and panes
|
||||
6. Otherwise, creates a default session with two windows: "editor" and "terminal"
|
||||
|
||||
## Caching
|
||||
|
||||
The tool uses an incremental cache to keep subsequent runs fast:
|
||||
|
||||
- **Cache location**: `~/.cache/tmuxido/projects.json`
|
||||
- **Incremental updates**: On each run, only directories whose mtime changed are rescanned — no full rescans
|
||||
- **Manual refresh**: Use `--refresh` to force a full rescan
|
||||
- **Cache status**: Use `--cache-status` to inspect the cache
|
||||
|
||||
The cache persists indefinitely and is updated automatically when the filesystem changes.
|
||||
|
||||
## Project-specific Configuration
|
||||
|
||||
You can customize the tmux session layout for individual projects by creating a `.tmuxido.toml` file in the project root.
|
||||
|
||||
Example `.tmuxido.toml`:
|
||||
```toml
|
||||
[[windows]]
|
||||
name = "editor"
|
||||
panes = ["nvim"]
|
||||
layout = "main-horizontal"
|
||||
|
||||
[[windows]]
|
||||
name = "server"
|
||||
panes = ["npm run dev"]
|
||||
|
||||
[[windows]]
|
||||
name = "git"
|
||||
panes = []
|
||||
```
|
||||
|
||||
### Available Layouts
|
||||
|
||||
- `main-horizontal` - Main pane on top, others below
|
||||
- `main-vertical` - Main pane on left, others on right
|
||||
- `tiled` - All panes tiled
|
||||
- `even-horizontal` - All panes in horizontal row
|
||||
- `even-vertical` - All panes in vertical column
|
||||
|
||||
### Panes
|
||||
|
||||
Each window can have multiple panes with commands that run automatically:
|
||||
- First pane is the main window pane
|
||||
- Additional panes are created by splitting
|
||||
- Empty panes array = just open the window in the project directory
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="20"><rect width="76" height="20" fill="#555"/><rect x="76" width="44" height="20" fill="#e05d44"/><g fill="#fff" text-anchor="middle" font-family="sans-serif" font-size="11"><text x="38" y="14">coverage</text><text x="98" y="14">44%</text></g></svg>
|
||||
|
After Width: | Height: | Size: 309 B |
-29
@@ -1,29 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
REPO="cinco/Tmuxido"
|
||||
BASE_URL="https://git.cincoeuzebio.com"
|
||||
INSTALL_DIR="$HOME/.local/bin"
|
||||
|
||||
arch=$(uname -m)
|
||||
case "$arch" in
|
||||
x86_64) file="tmuxido-x86_64-linux" ;;
|
||||
aarch64|arm64) file="tmuxido-aarch64-linux" ;;
|
||||
*) echo "Unsupported architecture: $arch" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
tag=$(curl -fsSL "$BASE_URL/api/v1/repos/$REPO/releases?limit=1&page=1" \
|
||||
| grep -o '"tag_name":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
|
||||
[ -z "$tag" ] && { echo "Could not fetch latest release" >&2; exit 1; }
|
||||
|
||||
echo "Installing tmuxido $tag..."
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
curl -fsSL "$BASE_URL/$REPO/releases/download/$tag/$file" -o "$INSTALL_DIR/tmuxido"
|
||||
chmod +x "$INSTALL_DIR/tmuxido"
|
||||
echo "Installed: $INSTALL_DIR/tmuxido"
|
||||
|
||||
case ":$PATH:" in
|
||||
*":$INSTALL_DIR:"*) ;;
|
||||
*) echo "Note: add $INSTALL_DIR to your PATH (e.g. export PATH=\"\$HOME/.local/bin:\$PATH\")" ;;
|
||||
esac
|
||||
@@ -1,3 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
components = ["rustfmt", "clippy"]
|
||||
-268
@@ -1,268 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ProjectCache {
|
||||
pub projects: Vec<PathBuf>,
|
||||
pub last_updated: u64,
|
||||
/// mtime de cada diretório visitado durante o scan.
|
||||
/// Usado para detectar mudanças incrementais sem precisar varrer tudo.
|
||||
#[serde(default)]
|
||||
pub dir_mtimes: HashMap<PathBuf, u64>,
|
||||
}
|
||||
|
||||
fn now_secs() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
fn mtime_secs(time: SystemTime) -> u64 {
|
||||
time.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
/// Retorna o subconjunto mínimo de diretórios: aqueles que não têm nenhum
|
||||
/// ancestral também na lista. Evita rescanear a mesma subárvore duas vezes.
|
||||
pub(crate) fn minimal_roots(dirs: &[PathBuf]) -> Vec<PathBuf> {
|
||||
dirs.iter()
|
||||
.filter(|dir| {
|
||||
!dirs
|
||||
.iter()
|
||||
.any(|other| other != *dir && dir.starts_with(other))
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl ProjectCache {
|
||||
pub fn new(projects: Vec<PathBuf>, dir_mtimes: HashMap<PathBuf, u64>) -> Self {
|
||||
Self {
|
||||
projects,
|
||||
last_updated: now_secs(),
|
||||
dir_mtimes,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cache_path() -> Result<PathBuf> {
|
||||
let cache_dir = dirs::cache_dir()
|
||||
.context("Could not determine cache directory")?
|
||||
.join("tmuxido");
|
||||
|
||||
fs::create_dir_all(&cache_dir).with_context(|| {
|
||||
format!("Failed to create cache directory: {}", cache_dir.display())
|
||||
})?;
|
||||
|
||||
Ok(cache_dir.join("projects.json"))
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Option<Self>> {
|
||||
let cache_path = Self::cache_path()?;
|
||||
|
||||
if !cache_path.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&cache_path)
|
||||
.with_context(|| format!("Failed to read cache file: {}", cache_path.display()))?;
|
||||
|
||||
let cache: ProjectCache = serde_json::from_str(&content)
|
||||
.with_context(|| format!("Failed to parse cache file: {}", cache_path.display()))?;
|
||||
|
||||
Ok(Some(cache))
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<()> {
|
||||
let cache_path = Self::cache_path()?;
|
||||
|
||||
let content = serde_json::to_string_pretty(self).context("Failed to serialize cache")?;
|
||||
|
||||
fs::write(&cache_path, content)
|
||||
.with_context(|| format!("Failed to write cache file: {}", cache_path.display()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Valida e atualiza o cache de forma incremental.
|
||||
///
|
||||
/// 1. Remove projetos cujo `.git` não existe mais.
|
||||
/// 2. Detecta diretórios com mtime alterado.
|
||||
/// 3. Resscaneia apenas as subárvores mínimas que mudaram.
|
||||
///
|
||||
/// Retorna `true` se o cache foi modificado.
|
||||
/// Retorna `false` com `dir_mtimes` vazio (cache antigo) — chamador deve fazer rescan completo.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn validate_and_update(
|
||||
&mut self,
|
||||
scan_fn: &dyn Fn(&Path) -> Result<(Vec<PathBuf>, HashMap<PathBuf, u64>)>,
|
||||
) -> Result<bool> {
|
||||
let mut changed = false;
|
||||
|
||||
// Passo 1: remover projetos cujo .git não existe mais
|
||||
let before = self.projects.len();
|
||||
self.projects.retain(|p| p.join(".git").exists());
|
||||
if self.projects.len() != before {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Sem fingerprints = cache no formato antigo; sinaliza ao chamador
|
||||
if self.dir_mtimes.is_empty() {
|
||||
return Ok(changed);
|
||||
}
|
||||
|
||||
// Passo 2: encontrar diretórios com mtime diferente do armazenado
|
||||
let changed_dirs: Vec<PathBuf> = self
|
||||
.dir_mtimes
|
||||
.iter()
|
||||
.filter(|(dir, stored_mtime)| {
|
||||
fs::metadata(dir)
|
||||
.and_then(|m| m.modified())
|
||||
.map(|t| mtime_secs(t) != **stored_mtime)
|
||||
.unwrap_or(true) // diretório sumiu = tratar como mudança
|
||||
})
|
||||
.map(|(dir, _)| dir.clone())
|
||||
.collect();
|
||||
|
||||
if changed_dirs.is_empty() {
|
||||
return Ok(changed);
|
||||
}
|
||||
|
||||
// Passo 3: resscanear apenas as raízes mínimas das subárvores alteradas
|
||||
for root in minimal_roots(&changed_dirs) {
|
||||
eprintln!("Rescanning: {}", root.display());
|
||||
|
||||
// Remover entradas antigas desta subárvore
|
||||
self.projects.retain(|p| !p.starts_with(&root));
|
||||
self.dir_mtimes.retain(|d, _| !d.starts_with(&root));
|
||||
|
||||
// Resscanear e mesclar
|
||||
let (new_projects, new_fingerprints) = scan_fn(&root)?;
|
||||
self.projects.extend(new_projects);
|
||||
self.dir_mtimes.extend(new_fingerprints);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if changed {
|
||||
self.projects.sort();
|
||||
self.projects.dedup();
|
||||
self.last_updated = now_secs();
|
||||
}
|
||||
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
pub fn age_in_seconds(&self) -> u64 {
|
||||
now_secs().saturating_sub(self.last_updated)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[test]
|
||||
fn should_return_empty_when_input_is_empty() {
|
||||
let result = minimal_roots(&[]);
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_single_dir_as_root() {
|
||||
let dirs = vec![PathBuf::from("/home/user/projects")];
|
||||
let result = minimal_roots(&dirs);
|
||||
assert_eq!(result, dirs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_exclude_nested_dirs_when_parent_is_present() {
|
||||
let dirs = vec![
|
||||
PathBuf::from("/home/user"),
|
||||
PathBuf::from("/home/user/projects"),
|
||||
];
|
||||
let result = minimal_roots(&dirs);
|
||||
assert_eq!(result.len(), 1);
|
||||
assert!(result.contains(&PathBuf::from("/home/user")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_keep_sibling_dirs_that_are_not_nested() {
|
||||
let dirs = vec![
|
||||
PathBuf::from("/home/user/projects"),
|
||||
PathBuf::from("/home/user/work"),
|
||||
];
|
||||
let result = minimal_roots(&dirs);
|
||||
assert_eq!(result.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_remove_stale_projects_when_git_dir_missing() {
|
||||
let dir = tempdir().unwrap();
|
||||
let project = dir.path().join("myproject");
|
||||
fs::create_dir_all(project.join(".git")).unwrap();
|
||||
|
||||
let mut cache = ProjectCache::new(vec![project.clone()], HashMap::new());
|
||||
assert_eq!(cache.projects.len(), 1);
|
||||
|
||||
fs::remove_dir_all(project.join(".git")).unwrap();
|
||||
|
||||
let result = cache.validate_and_update(&|_| Ok((vec![], HashMap::new())));
|
||||
assert_eq!(result.unwrap(), true);
|
||||
assert!(cache.projects.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_false_when_nothing_changed() {
|
||||
let dir = tempdir().unwrap();
|
||||
let actual_mtime = fs::metadata(dir.path())
|
||||
.unwrap()
|
||||
.modified()
|
||||
.unwrap()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
let mut dir_mtimes = HashMap::new();
|
||||
dir_mtimes.insert(dir.path().to_path_buf(), actual_mtime);
|
||||
let mut cache = ProjectCache::new(vec![], dir_mtimes);
|
||||
|
||||
let result = cache.validate_and_update(&|_| Ok((vec![], HashMap::new())));
|
||||
assert_eq!(result.unwrap(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_rescan_dirs_when_mtime_changed() {
|
||||
let dir = tempdir().unwrap();
|
||||
let tracked = dir.path().to_path_buf();
|
||||
|
||||
// Store mtime 0 — guaranteed to differ from the actual mtime
|
||||
let mut dir_mtimes = HashMap::new();
|
||||
dir_mtimes.insert(tracked, 0u64);
|
||||
let mut cache = ProjectCache::new(vec![], dir_mtimes);
|
||||
|
||||
let new_project = dir.path().join("discovered");
|
||||
let scan_called = std::cell::Cell::new(false);
|
||||
let result = cache.validate_and_update(&|_root| {
|
||||
scan_called.set(true);
|
||||
Ok((vec![new_project.clone()], HashMap::new()))
|
||||
});
|
||||
|
||||
assert_eq!(result.unwrap(), true);
|
||||
assert!(scan_called.get());
|
||||
assert!(cache.projects.contains(&new_project));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_false_when_dir_mtimes_empty() {
|
||||
let mut cache = ProjectCache::new(vec![], HashMap::new());
|
||||
let result = cache.validate_and_update(&|_| Ok((vec![], HashMap::new())));
|
||||
assert_eq!(result.unwrap(), false);
|
||||
}
|
||||
}
|
||||
-156
@@ -1,156 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::session::SessionConfig;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
pub paths: Vec<String>,
|
||||
#[serde(default = "default_max_depth")]
|
||||
pub max_depth: usize,
|
||||
#[serde(default = "default_cache_enabled")]
|
||||
pub cache_enabled: bool,
|
||||
#[serde(default = "default_cache_ttl_hours")]
|
||||
pub cache_ttl_hours: u64,
|
||||
#[serde(default = "default_session_config")]
|
||||
pub default_session: SessionConfig,
|
||||
}
|
||||
|
||||
fn default_max_depth() -> usize {
|
||||
5
|
||||
}
|
||||
|
||||
fn default_cache_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_cache_ttl_hours() -> u64 {
|
||||
24
|
||||
}
|
||||
|
||||
fn default_session_config() -> SessionConfig {
|
||||
use crate::session::Window;
|
||||
|
||||
SessionConfig {
|
||||
windows: vec![
|
||||
Window {
|
||||
name: "editor".to_string(),
|
||||
panes: vec![],
|
||||
layout: None,
|
||||
},
|
||||
Window {
|
||||
name: "terminal".to_string(),
|
||||
panes: vec![],
|
||||
layout: None,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Self> {
|
||||
let config_path = Self::config_path()?;
|
||||
|
||||
if !config_path.exists() {
|
||||
return Ok(Self::default_config());
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&config_path)
|
||||
.with_context(|| format!("Failed to read config file: {}", config_path.display()))?;
|
||||
|
||||
let config: Config = toml::from_str(&content)
|
||||
.with_context(|| format!("Failed to parse config file: {}", config_path.display()))?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn config_path() -> Result<PathBuf> {
|
||||
let config_dir = dirs::config_dir()
|
||||
.context("Could not determine config directory")?
|
||||
.join("tmuxido");
|
||||
|
||||
Ok(config_dir.join("tmuxido.toml"))
|
||||
}
|
||||
|
||||
pub fn ensure_config_exists() -> Result<PathBuf> {
|
||||
let config_path = Self::config_path()?;
|
||||
|
||||
if !config_path.exists() {
|
||||
let config_dir = config_path
|
||||
.parent()
|
||||
.context("Could not get parent directory")?;
|
||||
|
||||
fs::create_dir_all(config_dir).with_context(|| {
|
||||
format!(
|
||||
"Failed to create config directory: {}",
|
||||
config_dir.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
let default_config = Self::default_config();
|
||||
let toml_string = toml::to_string_pretty(&default_config)
|
||||
.context("Failed to serialize default config")?;
|
||||
|
||||
fs::write(&config_path, toml_string).with_context(|| {
|
||||
format!("Failed to write config file: {}", config_path.display())
|
||||
})?;
|
||||
|
||||
eprintln!("Created default config at: {}", config_path.display());
|
||||
}
|
||||
|
||||
Ok(config_path)
|
||||
}
|
||||
|
||||
fn default_config() -> Self {
|
||||
Config {
|
||||
paths: vec![
|
||||
dirs::home_dir()
|
||||
.unwrap_or_default()
|
||||
.join("Projects")
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
],
|
||||
max_depth: 5,
|
||||
cache_enabled: true,
|
||||
cache_ttl_hours: 24,
|
||||
default_session: default_session_config(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_use_defaults_when_optional_fields_missing() {
|
||||
let toml_str = r#"paths = ["/home/user/projects"]"#;
|
||||
let config: Config = toml::from_str(toml_str).unwrap();
|
||||
assert_eq!(config.max_depth, 5);
|
||||
assert!(config.cache_enabled);
|
||||
assert_eq!(config.cache_ttl_hours, 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_parse_full_config_correctly() {
|
||||
let toml_str = r#"
|
||||
paths = ["/foo", "/bar"]
|
||||
max_depth = 3
|
||||
cache_enabled = false
|
||||
cache_ttl_hours = 12
|
||||
"#;
|
||||
let config: Config = toml::from_str(toml_str).unwrap();
|
||||
assert_eq!(config.paths, vec!["/foo", "/bar"]);
|
||||
assert_eq!(config.max_depth, 3);
|
||||
assert!(!config.cache_enabled);
|
||||
assert_eq!(config.cache_ttl_hours, 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_reject_invalid_toml() {
|
||||
let result: Result<Config, _> = toml::from_str("not valid toml ]][[");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
-162
@@ -1,162 +0,0 @@
|
||||
pub mod cache;
|
||||
pub mod config;
|
||||
pub mod session;
|
||||
|
||||
use anyhow::Result;
|
||||
use cache::ProjectCache;
|
||||
use config::Config;
|
||||
use session::{SessionConfig, TmuxSession};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::UNIX_EPOCH;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub fn show_cache_status(config: &Config) -> Result<()> {
|
||||
if !config.cache_enabled {
|
||||
println!("Cache is disabled in configuration");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(cache) = ProjectCache::load()? {
|
||||
let age_seconds = cache.age_in_seconds();
|
||||
let age_hours = age_seconds / 3600;
|
||||
let age_minutes = (age_seconds % 3600) / 60;
|
||||
|
||||
println!("Cache status:");
|
||||
println!(" Location: {}", ProjectCache::cache_path()?.display());
|
||||
println!(" Projects cached: {}", cache.projects.len());
|
||||
println!(" Directories tracked: {}", cache.dir_mtimes.len());
|
||||
println!(" Last updated: {}h {}m ago", age_hours, age_minutes);
|
||||
} else {
|
||||
println!("No cache found");
|
||||
println!(" Run without --cache-status to create it");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_projects(config: &Config, force_refresh: bool) -> Result<Vec<PathBuf>> {
|
||||
if !config.cache_enabled || force_refresh {
|
||||
let (projects, fingerprints) = scan_all_roots(config)?;
|
||||
let cache = ProjectCache::new(projects.clone(), fingerprints);
|
||||
cache.save()?;
|
||||
eprintln!("Cache updated with {} projects", projects.len());
|
||||
return Ok(projects);
|
||||
}
|
||||
|
||||
if let Some(mut cache) = ProjectCache::load()? {
|
||||
// Cache no formato antigo (sem dir_mtimes) → atualizar com rescan completo
|
||||
if cache.dir_mtimes.is_empty() {
|
||||
eprintln!("Upgrading cache, scanning for projects...");
|
||||
let (projects, fingerprints) = scan_all_roots(config)?;
|
||||
let new_cache = ProjectCache::new(projects.clone(), fingerprints);
|
||||
new_cache.save()?;
|
||||
eprintln!("Cache updated with {} projects", projects.len());
|
||||
return Ok(projects);
|
||||
}
|
||||
|
||||
let changed = cache.validate_and_update(&|root| scan_from_root(root, config))?;
|
||||
if changed {
|
||||
cache.save()?;
|
||||
eprintln!(
|
||||
"Cache updated incrementally ({} projects)",
|
||||
cache.projects.len()
|
||||
);
|
||||
} else {
|
||||
eprintln!("Using cached projects ({} projects)", cache.projects.len());
|
||||
}
|
||||
return Ok(cache.projects);
|
||||
}
|
||||
|
||||
// Sem cache ainda — scan completo inicial
|
||||
eprintln!("No cache found, scanning for projects...");
|
||||
let (projects, fingerprints) = scan_all_roots(config)?;
|
||||
let cache = ProjectCache::new(projects.clone(), fingerprints);
|
||||
cache.save()?;
|
||||
eprintln!("Cache updated with {} projects", projects.len());
|
||||
Ok(projects)
|
||||
}
|
||||
|
||||
pub fn scan_all_roots(config: &Config) -> Result<(Vec<PathBuf>, HashMap<PathBuf, u64>)> {
|
||||
let mut all_projects = Vec::new();
|
||||
let mut all_fingerprints = HashMap::new();
|
||||
|
||||
for path_str in &config.paths {
|
||||
let path = PathBuf::from(shellexpand::tilde(path_str).to_string());
|
||||
|
||||
if !path.exists() {
|
||||
eprintln!("Warning: Path does not exist: {}", path.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
eprintln!("Scanning: {}", path.display());
|
||||
|
||||
let (projects, fingerprints) = scan_from_root(&path, config)?;
|
||||
all_projects.extend(projects);
|
||||
all_fingerprints.extend(fingerprints);
|
||||
}
|
||||
|
||||
all_projects.sort();
|
||||
all_projects.dedup();
|
||||
|
||||
Ok((all_projects, all_fingerprints))
|
||||
}
|
||||
|
||||
pub fn scan_from_root(
|
||||
root: &Path,
|
||||
config: &Config,
|
||||
) -> Result<(Vec<PathBuf>, HashMap<PathBuf, u64>)> {
|
||||
let mut projects = Vec::new();
|
||||
let mut fingerprints = HashMap::new();
|
||||
|
||||
for entry in WalkDir::new(root)
|
||||
.max_depth(config.max_depth)
|
||||
.follow_links(false)
|
||||
.into_iter()
|
||||
.filter_entry(|e| {
|
||||
e.file_name()
|
||||
.to_str()
|
||||
.map(|s| !s.starts_with('.') || s == ".git")
|
||||
.unwrap_or(false)
|
||||
})
|
||||
{
|
||||
let entry = match entry {
|
||||
Ok(e) => e,
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if entry.file_type().is_dir() {
|
||||
if entry.file_name() == ".git" {
|
||||
// Projeto encontrado
|
||||
if let Some(parent) = entry.path().parent() {
|
||||
projects.push(parent.to_path_buf());
|
||||
}
|
||||
} else {
|
||||
// Registrar mtime para detecção de mudanças futuras
|
||||
if let Ok(metadata) = entry.metadata()
|
||||
&& let Ok(modified) = metadata.modified()
|
||||
{
|
||||
let mtime = modified
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
fingerprints.insert(entry.path().to_path_buf(), mtime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((projects, fingerprints))
|
||||
}
|
||||
|
||||
pub fn launch_tmux_session(selected: &Path, config: &Config) -> Result<()> {
|
||||
// Try to load project-specific config, fallback to global default
|
||||
let session_config = SessionConfig::load_from_project(selected)?
|
||||
.unwrap_or_else(|| config.default_session.clone());
|
||||
|
||||
// Create tmux session
|
||||
let tmux_session = TmuxSession::new(selected);
|
||||
tmux_session.create(&session_config)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command, Stdio};
|
||||
use tmuxido::config::Config;
|
||||
use tmuxido::{get_projects, launch_tmux_session, show_cache_status};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "tmuxido",
|
||||
about = "Quickly find and open projects in tmux",
|
||||
version
|
||||
)]
|
||||
struct Args {
|
||||
/// Project path to open directly (skips selection)
|
||||
project_path: Option<PathBuf>,
|
||||
|
||||
/// Force refresh the project cache
|
||||
#[arg(short, long)]
|
||||
refresh: bool,
|
||||
|
||||
/// Show cache status and exit
|
||||
#[arg(long)]
|
||||
cache_status: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
// Ensure config exists
|
||||
Config::ensure_config_exists()?;
|
||||
|
||||
// Load config
|
||||
let config = Config::load()?;
|
||||
|
||||
// Handle cache status command
|
||||
if args.cache_status {
|
||||
show_cache_status(&config)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let selected = if let Some(path) = args.project_path {
|
||||
path
|
||||
} else {
|
||||
// Get projects (from cache or scan)
|
||||
let projects = get_projects(&config, args.refresh)?;
|
||||
|
||||
if projects.is_empty() {
|
||||
eprintln!("No projects found in configured paths");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Use fzf to select a project
|
||||
select_project_with_fzf(&projects)?
|
||||
};
|
||||
|
||||
if !selected.exists() {
|
||||
eprintln!("Selected path does not exist: {}", selected.display());
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Launch tmux session
|
||||
launch_tmux_session(&selected, &config)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn select_project_with_fzf(projects: &[PathBuf]) -> Result<PathBuf> {
|
||||
let mut child = Command::new("fzf")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.context("Failed to spawn fzf. Make sure fzf is installed.")?;
|
||||
|
||||
{
|
||||
let stdin = child.stdin.as_mut().context("Failed to open stdin")?;
|
||||
for project in projects {
|
||||
writeln!(stdin, "{}", project.display())?;
|
||||
}
|
||||
}
|
||||
|
||||
let output = child.wait_with_output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let selected = String::from_utf8(output.stdout)?.trim().to_string();
|
||||
|
||||
if selected.is_empty() {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
Ok(PathBuf::from(selected))
|
||||
}
|
||||
-309
@@ -1,309 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Window {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub panes: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub layout: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct SessionConfig {
|
||||
#[serde(default)]
|
||||
pub windows: Vec<Window>,
|
||||
}
|
||||
|
||||
impl SessionConfig {
|
||||
pub fn load_from_project(project_path: &Path) -> Result<Option<Self>> {
|
||||
let config_path = project_path.join(".tmuxido.toml");
|
||||
|
||||
if !config_path.exists() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(&config_path)
|
||||
.with_context(|| format!("Failed to read session config: {}", config_path.display()))?;
|
||||
|
||||
let config: SessionConfig = toml::from_str(&content).with_context(|| {
|
||||
format!("Failed to parse session config: {}", config_path.display())
|
||||
})?;
|
||||
|
||||
Ok(Some(config))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TmuxSession {
|
||||
pub(crate) session_name: String,
|
||||
project_path: String,
|
||||
base_index: usize,
|
||||
}
|
||||
|
||||
impl TmuxSession {
|
||||
pub fn new(project_path: &Path) -> Self {
|
||||
let session_name = project_path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("project")
|
||||
.replace('.', "_")
|
||||
.replace(' ', "-");
|
||||
|
||||
let base_index = Self::get_base_index();
|
||||
|
||||
Self {
|
||||
session_name,
|
||||
project_path: project_path.display().to_string(),
|
||||
base_index,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_base_index() -> usize {
|
||||
// Try to get base-index from tmux
|
||||
let output = Command::new("tmux")
|
||||
.args(["show-options", "-gv", "base-index"])
|
||||
.output();
|
||||
|
||||
if let Ok(output) = output
|
||||
&& output.status.success()
|
||||
{
|
||||
let index_str = String::from_utf8_lossy(&output.stdout);
|
||||
if let Ok(index) = index_str.trim().parse::<usize>() {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to 0 if we can't determine
|
||||
0
|
||||
}
|
||||
|
||||
pub fn create(&self, config: &SessionConfig) -> Result<()> {
|
||||
// Check if we're already inside a tmux session
|
||||
let inside_tmux = std::env::var("TMUX").is_ok();
|
||||
|
||||
// Check if session already exists
|
||||
let session_exists = Command::new("tmux")
|
||||
.args(["has-session", "-t", &self.session_name])
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false);
|
||||
|
||||
if session_exists {
|
||||
// Session exists, just switch to it
|
||||
if inside_tmux {
|
||||
Command::new("tmux")
|
||||
.args(["switch-client", "-t", &self.session_name])
|
||||
.status()
|
||||
.context("Failed to switch to existing session")?;
|
||||
} else {
|
||||
Command::new("tmux")
|
||||
.args(["attach-session", "-t", &self.session_name])
|
||||
.status()
|
||||
.context("Failed to attach to existing session")?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create new session
|
||||
if config.windows.is_empty() {
|
||||
// Create simple session with one window
|
||||
self.create_simple_session()?;
|
||||
} else {
|
||||
// Create session with custom windows
|
||||
self.create_custom_session(config)?;
|
||||
}
|
||||
|
||||
// Attach or switch to the session
|
||||
if inside_tmux {
|
||||
Command::new("tmux")
|
||||
.args(["switch-client", "-t", &self.session_name])
|
||||
.status()
|
||||
.context("Failed to switch to new session")?;
|
||||
} else {
|
||||
Command::new("tmux")
|
||||
.args(["attach-session", "-t", &self.session_name])
|
||||
.status()
|
||||
.context("Failed to attach to new session")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_simple_session(&self) -> Result<()> {
|
||||
// Create a detached session with one window
|
||||
Command::new("tmux")
|
||||
.args([
|
||||
"new-session",
|
||||
"-d",
|
||||
"-s",
|
||||
&self.session_name,
|
||||
"-c",
|
||||
&self.project_path,
|
||||
])
|
||||
.status()
|
||||
.context("Failed to create tmux session")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_custom_session(&self, config: &SessionConfig) -> Result<()> {
|
||||
// Create session with first window
|
||||
let first_window = &config.windows[0];
|
||||
Command::new("tmux")
|
||||
.args([
|
||||
"new-session",
|
||||
"-d",
|
||||
"-s",
|
||||
&self.session_name,
|
||||
"-n",
|
||||
&first_window.name,
|
||||
"-c",
|
||||
&self.project_path,
|
||||
])
|
||||
.status()
|
||||
.context("Failed to create tmux session")?;
|
||||
|
||||
// Create panes for first window if specified
|
||||
if !first_window.panes.is_empty() {
|
||||
self.create_panes(self.base_index, &first_window.panes)?;
|
||||
}
|
||||
|
||||
// Apply layout for first window if specified
|
||||
if let Some(layout) = &first_window.layout {
|
||||
self.apply_layout(self.base_index, layout)?;
|
||||
}
|
||||
|
||||
// Create additional windows
|
||||
for (index, window) in config.windows.iter().skip(1).enumerate() {
|
||||
let window_index = self.base_index + index + 1;
|
||||
|
||||
Command::new("tmux")
|
||||
.args([
|
||||
"new-window",
|
||||
"-t",
|
||||
&format!("{}:{}", self.session_name, window_index),
|
||||
"-n",
|
||||
&window.name,
|
||||
"-c",
|
||||
&self.project_path,
|
||||
])
|
||||
.status()
|
||||
.with_context(|| format!("Failed to create window: {}", window.name))?;
|
||||
|
||||
// Create panes if specified
|
||||
if !window.panes.is_empty() {
|
||||
self.create_panes(window_index, &window.panes)?;
|
||||
}
|
||||
|
||||
// Apply layout if specified
|
||||
if let Some(layout) = &window.layout {
|
||||
self.apply_layout(window_index, layout)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Select the first window
|
||||
Command::new("tmux")
|
||||
.args([
|
||||
"select-window",
|
||||
"-t",
|
||||
&format!("{}:{}", self.session_name, self.base_index),
|
||||
])
|
||||
.status()
|
||||
.context("Failed to select first window")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_panes(&self, window_index: usize, panes: &[String]) -> Result<()> {
|
||||
for (pane_index, command) in panes.iter().enumerate() {
|
||||
let target = format!("{}:{}", self.session_name, window_index);
|
||||
|
||||
// First pane already exists (created with the window), skip split
|
||||
if pane_index > 0 {
|
||||
// Create new pane by splitting
|
||||
Command::new("tmux")
|
||||
.args(["split-window", "-t", &target, "-c", &self.project_path])
|
||||
.status()
|
||||
.context("Failed to split pane")?;
|
||||
}
|
||||
|
||||
// Send the command to the pane if it's not empty
|
||||
if !command.is_empty() {
|
||||
let pane_target = format!("{}:{}.{}", self.session_name, window_index, pane_index);
|
||||
Command::new("tmux")
|
||||
.args(["send-keys", "-t", &pane_target, command, "Enter"])
|
||||
.status()
|
||||
.context("Failed to send keys to pane")?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_layout(&self, window_index: usize, layout: &str) -> Result<()> {
|
||||
Command::new("tmux")
|
||||
.args([
|
||||
"select-layout",
|
||||
"-t",
|
||||
&format!("{}:{}", self.session_name, window_index),
|
||||
layout,
|
||||
])
|
||||
.status()
|
||||
.with_context(|| format!("Failed to apply layout: {}", layout))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn should_replace_dots_with_underscores_in_session_name() {
|
||||
let session = TmuxSession::new(Path::new("/home/user/my.project"));
|
||||
assert_eq!(session.session_name, "my_project");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_replace_spaces_with_dashes_in_session_name() {
|
||||
let session = TmuxSession::new(Path::new("/home/user/my project"));
|
||||
assert_eq!(session.session_name, "my-project");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_use_project_fallback_when_path_has_no_filename() {
|
||||
let session = TmuxSession::new(Path::new("/"));
|
||||
assert_eq!(session.session_name, "project");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_parse_window_from_toml() {
|
||||
let toml_str = r#"
|
||||
[[windows]]
|
||||
name = "editor"
|
||||
panes = ["nvim ."]
|
||||
"#;
|
||||
let config: SessionConfig = toml::from_str(toml_str).unwrap();
|
||||
assert_eq!(config.windows[0].name, "editor");
|
||||
assert_eq!(config.windows[0].panes, vec!["nvim ."]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_parse_session_config_with_layout() {
|
||||
let toml_str = r#"
|
||||
[[windows]]
|
||||
name = "main"
|
||||
layout = "tiled"
|
||||
panes = ["vim", "bash"]
|
||||
"#;
|
||||
let config: SessionConfig = toml::from_str(toml_str).unwrap();
|
||||
assert_eq!(config.windows[0].layout, Some("tiled".to_string()));
|
||||
assert_eq!(config.windows[0].panes.len(), 2);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use tmuxido::cache::ProjectCache;
|
||||
|
||||
#[test]
|
||||
fn should_save_and_reload_cache() {
|
||||
let projects = vec![PathBuf::from("/tmp/test_tmuxido_project")];
|
||||
let cache = ProjectCache::new(projects.clone(), HashMap::new());
|
||||
cache.save().unwrap();
|
||||
|
||||
let loaded = ProjectCache::load().unwrap().unwrap();
|
||||
assert_eq!(loaded.projects, projects);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
use tmuxido::config::Config;
|
||||
use tmuxido::scan_from_root;
|
||||
use tmuxido::session::SessionConfig;
|
||||
|
||||
fn make_config(max_depth: usize) -> Config {
|
||||
Config {
|
||||
paths: vec![],
|
||||
max_depth,
|
||||
cache_enabled: true,
|
||||
cache_ttl_hours: 24,
|
||||
default_session: SessionConfig { windows: vec![] },
|
||||
}
|
||||
}
|
||||
|
||||
/// `tempfile::tempdir()` creates hidden dirs (e.g. `/tmp/.tmpXXXXXX`) on this
|
||||
/// system, which `scan_from_root`'s `filter_entry` would skip. Create a
|
||||
/// visible subdirectory to use as the actual scan root.
|
||||
fn make_scan_root() -> (tempfile::TempDir, std::path::PathBuf) {
|
||||
let dir = tempdir().unwrap();
|
||||
let root = dir.path().join("scan_root");
|
||||
fs::create_dir_all(&root).unwrap();
|
||||
(dir, root)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_find_git_repos_in_temp_dir() {
|
||||
let (_dir, root) = make_scan_root();
|
||||
fs::create_dir_all(root.join("foo/.git")).unwrap();
|
||||
fs::create_dir_all(root.join("bar/.git")).unwrap();
|
||||
|
||||
let config = make_config(5);
|
||||
let (projects, _) = scan_from_root(&root, &config).unwrap();
|
||||
|
||||
assert_eq!(projects.len(), 2);
|
||||
assert!(projects.iter().any(|p| p.ends_with("foo")));
|
||||
assert!(projects.iter().any(|p| p.ends_with("bar")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_descend_into_hidden_dirs() {
|
||||
let (_dir, root) = make_scan_root();
|
||||
fs::create_dir_all(root.join(".hidden/repo/.git")).unwrap();
|
||||
|
||||
let config = make_config(5);
|
||||
let (projects, _) = scan_from_root(&root, &config).unwrap();
|
||||
|
||||
assert!(projects.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_respect_max_depth() {
|
||||
let (_dir, root) = make_scan_root();
|
||||
// Shallow: project/.git at depth 2 from root — found with max_depth=2
|
||||
fs::create_dir_all(root.join("project/.git")).unwrap();
|
||||
// Deep: nested/deep/project/.git at depth 4 — excluded with max_depth=2
|
||||
fs::create_dir_all(root.join("nested/deep/project/.git")).unwrap();
|
||||
|
||||
let config = make_config(2);
|
||||
let (projects, _) = scan_from_root(&root, &config).unwrap();
|
||||
|
||||
assert_eq!(projects.len(), 1);
|
||||
assert!(projects[0].ends_with("project"));
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
use tmuxido::session::SessionConfig;
|
||||
|
||||
#[test]
|
||||
fn should_load_project_session_config() {
|
||||
let dir = tempdir().unwrap();
|
||||
let config_content = r#"
|
||||
[[windows]]
|
||||
name = "editor"
|
||||
panes = ["nvim ."]
|
||||
"#;
|
||||
fs::write(dir.path().join(".tmuxido.toml"), config_content).unwrap();
|
||||
|
||||
let result = SessionConfig::load_from_project(dir.path()).unwrap();
|
||||
assert!(result.is_some());
|
||||
let config = result.unwrap();
|
||||
assert_eq!(config.windows[0].name, "editor");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_none_when_no_project_config() {
|
||||
let dir = tempdir().unwrap();
|
||||
let result = SessionConfig::load_from_project(dir.path()).unwrap();
|
||||
assert!(result.is_none());
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
# ============================================================================
|
||||
# tmuxido - Global Configuration
|
||||
# ============================================================================
|
||||
# Location: ~/.config/tmuxido/tmuxido.toml
|
||||
#
|
||||
# This is the main configuration file that controls:
|
||||
# 1. Where to search for projects
|
||||
# 2. Caching behavior
|
||||
# 3. Default session layout (used when projects don't have .tmuxido.toml)
|
||||
#
|
||||
# Compatible with any tmux base-index setting (0 or 1, auto-detected)
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
# PROJECT DISCOVERY
|
||||
# ============================================================================
|
||||
# Paths where tmuxido will search for git repositories
|
||||
# Supports ~ for home directory expansion
|
||||
|
||||
paths = [
|
||||
"~/Projects",
|
||||
# "~/work",
|
||||
# "~/opensource",
|
||||
# "~/clients/company-name",
|
||||
]
|
||||
|
||||
# Maximum directory depth to search for .git folders
|
||||
# Higher values = slower scan, but finds deeply nested projects
|
||||
# Lower values = faster scan, but might miss some projects
|
||||
# Default: 5
|
||||
max_depth = 5
|
||||
|
||||
# ============================================================================
|
||||
# CACHING CONFIGURATION
|
||||
# ============================================================================
|
||||
# Caching dramatically speeds up subsequent runs by storing discovered projects
|
||||
|
||||
# Enable/disable project caching
|
||||
# Default: true
|
||||
cache_enabled = true
|
||||
|
||||
# How long (in hours) before cache is considered stale and refreshed
|
||||
# Set lower if you frequently add new projects
|
||||
# Set higher if your projects are stable
|
||||
# Default: 24
|
||||
cache_ttl_hours = 24
|
||||
|
||||
# Cache location: ~/.cache/tmuxido/projects.json
|
||||
# Use --refresh flag to force cache update
|
||||
# Use --cache-status to see cache information
|
||||
|
||||
# ============================================================================
|
||||
# DEFAULT SESSION CONFIGURATION
|
||||
# ============================================================================
|
||||
# This configuration is used for projects that don't have their own
|
||||
# .tmuxido.toml file in the project root.
|
||||
#
|
||||
# You can customize this to match your preferred workflow!
|
||||
# ============================================================================
|
||||
|
||||
[default_session]
|
||||
|
||||
# --- OPTION 1: Simple two-window setup (CURRENT DEFAULT) ---
|
||||
[[default_session.windows]]
|
||||
name = "editor"
|
||||
panes = []
|
||||
|
||||
[[default_session.windows]]
|
||||
name = "terminal"
|
||||
panes = []
|
||||
|
||||
# --- OPTION 2: Single window with nvim and terminal split ---
|
||||
# Uncomment this and comment out Option 1 above
|
||||
# [[default_session.windows]]
|
||||
# name = "work"
|
||||
# layout = "main-horizontal"
|
||||
# panes = [
|
||||
# "nvim .", # Main pane: Editor
|
||||
# "clear", # Bottom left: Terminal
|
||||
# "clear" # Bottom right: Terminal
|
||||
# ]
|
||||
|
||||
# --- OPTION 3: Three-window workflow (code, run, git) ---
|
||||
# Uncomment this and comment out Option 1 above
|
||||
# [[default_session.windows]]
|
||||
# name = "code"
|
||||
# layout = "main-vertical"
|
||||
# panes = ["nvim .", "clear"]
|
||||
#
|
||||
# [[default_session.windows]]
|
||||
# name = "run"
|
||||
# panes = []
|
||||
#
|
||||
# [[default_session.windows]]
|
||||
# name = "git"
|
||||
# panes = []
|
||||
|
||||
# --- OPTION 4: Full development setup ---
|
||||
# Uncomment this and comment out Option 1 above
|
||||
# [[default_session.windows]]
|
||||
# name = "editor"
|
||||
# layout = "main-horizontal"
|
||||
# panes = ["nvim .", "clear", "clear"]
|
||||
#
|
||||
# [[default_session.windows]]
|
||||
# name = "server"
|
||||
# panes = []
|
||||
#
|
||||
# [[default_session.windows]]
|
||||
# name = "logs"
|
||||
# panes = []
|
||||
#
|
||||
# [[default_session.windows]]
|
||||
# name = "git"
|
||||
# panes = ["git status"]
|
||||
|
||||
# ============================================================================
|
||||
# AVAILABLE LAYOUTS
|
||||
# ============================================================================
|
||||
# Use these layout values in your windows:
|
||||
#
|
||||
# main-horizontal: Main pane on top, others below
|
||||
# ┌─────────────────────────────┐
|
||||
# │ Main Pane │
|
||||
# ├──────────────┬──────────────┤
|
||||
# │ Pane 2 │ Pane 3 │
|
||||
# └──────────────┴──────────────┘
|
||||
#
|
||||
# main-vertical: Main pane on left, others on right
|
||||
# ┌──────────┬──────────┐
|
||||
# │ │ Pane 2 │
|
||||
# │ Main ├──────────┤
|
||||
# │ Pane │ Pane 3 │
|
||||
# └──────────┴──────────┘
|
||||
#
|
||||
# tiled: All panes in a grid
|
||||
# ┌──────────┬──────────┐
|
||||
# │ Pane 1 │ Pane 2 │
|
||||
# ├──────────┼──────────┤
|
||||
# │ Pane 3 │ Pane 4 │
|
||||
# └──────────┴──────────┘
|
||||
#
|
||||
# even-horizontal: All panes in equal-width columns
|
||||
# ┌────┬────┬────┬────┐
|
||||
# │ P1 │ P2 │ P3 │ P4 │
|
||||
# └────┴────┴────┴────┘
|
||||
#
|
||||
# even-vertical: All panes in equal-height rows
|
||||
# ┌──────────────┐
|
||||
# │ Pane 1 │
|
||||
# ├──────────────┤
|
||||
# │ Pane 2 │
|
||||
# ├──────────────┤
|
||||
# │ Pane 3 │
|
||||
# └──────────────┘
|
||||
|
||||
# ============================================================================
|
||||
# USAGE EXAMPLES
|
||||
# ============================================================================
|
||||
# Run without arguments (uses fzf to select project):
|
||||
# $ tmuxido
|
||||
#
|
||||
# Open specific project directly:
|
||||
# $ tmuxido /path/to/project
|
||||
#
|
||||
# Force refresh cache (after adding new projects):
|
||||
# $ tmuxido --refresh
|
||||
# $ tmuxido -r
|
||||
#
|
||||
# Check cache status:
|
||||
# $ tmuxido --cache-status
|
||||
#
|
||||
# Show help:
|
||||
# $ tmuxido --help
|
||||
|
||||
# ============================================================================
|
||||
# PROJECT-SPECIFIC OVERRIDES
|
||||
# ============================================================================
|
||||
# To customize a specific project, create .tmuxido.toml in that
|
||||
# project's root directory. See .tmuxido.toml.example for details.
|
||||
#
|
||||
# Hierarchy:
|
||||
# 1. Project's .tmuxido.toml (highest priority)
|
||||
# 2. Global [default_session] from this file (fallback)
|
||||
|
||||
# ============================================================================
|
||||
# TIPS & BEST PRACTICES
|
||||
# ============================================================================
|
||||
# 1. Start with the simple Option 1 default, customize as you learn
|
||||
# 2. Use project-specific configs for special projects (web apps, etc)
|
||||
# 3. Set cache_ttl_hours lower (6-12) if you frequently add projects
|
||||
# 4. Add multiple paths to organize personal vs work vs open-source
|
||||
# 5. Use max_depth wisely - higher isn't always better (slower scans)
|
||||
# 6. Run --cache-status to verify your settings are working
|
||||
# 7. The tool auto-detects your tmux base-index (0 or 1), no config needed
|
||||
# 8. Empty panes = shell in project directory (fastest to open)
|
||||
# 9. Commands in panes run automatically when session is created
|
||||
# 10. Use "clear" in panes for clean shells without running commands
|
||||
|
||||
# ============================================================================
|
||||
# TROUBLESHOOTING
|
||||
# ============================================================================
|
||||
# Projects not showing up?
|
||||
# - Check that paths exist and contain .git directories
|
||||
# - Increase max_depth if projects are nested deeper
|
||||
# - Run with --refresh to force cache update
|
||||
#
|
||||
# Cache seems stale?
|
||||
# - Run tmuxido --refresh
|
||||
# - Lower cache_ttl_hours value
|
||||
#
|
||||
# Windows/panes not created correctly?
|
||||
# - Tool auto-detects base-index, but verify with: tmux show-options -g base-index
|
||||
# - Check TOML syntax in default_session or project config
|
||||
#
|
||||
# Want to reset to defaults?
|
||||
# - Delete this file, it will be recreated on next run
|
||||
# - Or copy from: /path/to/repo/tmuxido.toml.example
|
||||
Reference in New Issue
Block a user