Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Test

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

on:
push:
branches:
- main
pull_request:

jobs:
test:
name: ${{ matrix.label }} (${{ matrix.features }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
label: linux-x86_64
features: default
- os: ubuntu-latest
label: linux-x86_64
features: minimal
- os: windows-latest
label: windows-x86_64
features: default
- os: windows-latest
label: windows-x86_64
features: minimal

steps:
- name: Check out repository
uses: actions/checkout@v5

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Check formatting
run: cargo fmt --all -- --check

- name: Run clippy
run: |
if ("${{ matrix.features }}" -eq "minimal") {
cargo clippy --workspace --no-default-features --all-targets --locked
} else {
cargo clippy --workspace --all-targets --locked
}
shell: pwsh

- name: Build
run: |
if ("${{ matrix.features }}" -eq "minimal") {
cargo build --workspace --no-default-features --locked
} else {
cargo build --workspace --locked
}
shell: pwsh

- name: Run tests
run: |
if ("${{ matrix.features }}" -eq "minimal") {
cargo test --workspace --no-default-features --locked
} else {
cargo test --workspace --locked
}
shell: pwsh
30 changes: 20 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@ version = "0.5.12"
edition = "2021"

[features]
default = []
default = ["syntax-highlight", "image-preview", "sftp", "terminal-panel", "auto-update"]
# Enables extraction/preview of `.tar.bz2` and `.tar.xz` archives.
# Off by default because bzip2-sys and lzma-sys are C builds that noticeably
# slow `cargo install zeta`. Without this feature those formats produce a
# clear runtime error; `.zip`, `.tar`, `.tar.gz` continue to work.
archives-extra = ["dep:bzip2", "dep:xz2"]
# Syntax highlighting for file previews (syntect + two-face). Heavy (~5-8 MB).
syntax-highlight = ["dep:syntect", "dep:two-face"]
# Image preview rendering (image + ratatui-image). Adds ~3-5 MB.
image-preview = ["dep:image", "dep:ratatui-image"]
# SSH/SFTP remote filesystem support (ssh2 + C libssh2 build).
sftp = ["dep:ssh2"]
# Embedded terminal panel (vt100 + portable-pty/conpty).
terminal-panel = ["dep:vt100", "dep:conpty", "dep:portable-pty"]
# Automatic update checks via GitHub API (ureq).
auto-update = ["dep:ureq"]

[dependencies]
anyhow = "1.0"
Expand All @@ -26,10 +36,10 @@ ratatui = "0.30"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
trash = { version = "5", default-features = false, features = ["coinit_apartmentthreaded"] }
syntect = { version = "5.3", default-features = false, features = ["default-fancy", "default-syntaxes"] }
syntect = { version = "5.3", default-features = false, features = ["default-fancy", "default-syntaxes"], optional = true }
thiserror = "2.0"
ssh2 = "0.9"
two-face = { version = "0.5", default-features = false, features = ["syntect-fancy"] }
ssh2 = { version = "0.9", optional = true }
two-face = { version = "0.5", default-features = false, features = ["syntect-fancy"], optional = true }
tui-input = "0.14"
unicode-width = "0.2"
# basic-toml is a slim TOML parser/serializer (no `toml_edit`/`winnow`).
Expand All @@ -47,24 +57,24 @@ xz2 = { version = "0.1", optional = true }
# Trim chrono: drop default `oldtime`, `wasmbind`, etc. We only need clock + std for
# formatting/now(); this also avoids pulling iana-time-zone where unnecessary.
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "gif", "bmp", "webp"] }
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "gif", "bmp", "webp"], optional = true }
tempfile = "3"
vt100 = "0.15"
vt100 = { version = "0.15", optional = true }
# Disable `image-defaults` (re-enables every image codec) and `chafa-dyn`
# (runs pkg-config at build looking for libchafa). We provide our own
# image features above; halfblocks/sixel/kitty work without them.
ratatui-image = { version = "10", default-features = false, features = ["crossterm"] }
ureq = { version = "3", default-features = false, features = ["rustls"] }
ratatui-image = { version = "10", default-features = false, features = ["crossterm"], optional = true }
ureq = { version = "3", default-features = false, features = ["rustls"], optional = true }
tui-textarea-2 = { version = "0.11", default-features = false, features = ["crossterm_0_28"] }

[target.'cfg(windows)'.dependencies]
conpty = "0.7"
conpty = { version = "0.7", optional = true }

# portable-pty is only used by the Unix branch of src/pty.rs; on Windows we use
# conpty directly. Scoping it to cfg(unix) avoids compiling portable-pty + nix +
# serial2 + filedescriptor on Windows builds.
[target.'cfg(unix)'.dependencies]
portable-pty = "0.9"
portable-pty = { version = "0.9", optional = true }

[profile.release]
opt-level = "z" # optimise for binary size
Expand Down
151 changes: 151 additions & 0 deletions docs/implementation-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Zeta Implementation Plan

> Created: 2026-05-12
> Branch: `feat/feature-gating`
> Based on: `docs/optimization-and-architecture-analysis.md`

---

## Phase 1: Feature Gating (v0.6.0) — IN PROGRESS

**Goal:** Make heavy dependencies optional via Cargo features to reduce binary size and startup overhead. A minimal build (`--no-default-features`) should compile and run core file manager functionality.

**Branch:** `feat/feature-gating`

### 1.1 Cargo.toml Features

| Feature | Dependencies | Default? | Files Impacted |
|---------|-------------|----------|----------------|
| `syntax-highlight` | `syntect`, `two-face` | ✅ Yes | `src/highlight.rs`, `src/jobs.rs`, `src/preview.rs`, `src/ui/mod.rs`, `src/ui/pane.rs`, `src/config.rs` |
| `image-preview` | `image`, `ratatui-image` | ✅ Yes | `src/app.rs`, `src/jobs.rs`, `src/preview.rs`, `src/state/mod.rs`, `src/ui/preview.rs` |
| `sftp` | `ssh2` | ✅ Yes | `src/jobs.rs`, `src/fs/sftp.rs`, `src/state/ssh.rs`, `src/ui/ssh.rs` |
| `terminal-panel` | `vt100`, `portable-pty`, `conpty` | ✅ Yes | `src/pty.rs`, `src/state/terminal.rs`, `src/ui/terminal.rs`, `src/jobs.rs` |
| `auto-update` | `ureq` | ✅ Yes | `src/update.rs`, `src/app.rs` |
| `archives-extra` | `bzip2`, `xz2` | ❌ No | `src/jobs.rs` (already gated) |

### 1.2 Checklist

- [x] Create branch `feat/feature-gating`
- [ ] Gate `syntax-highlight`
- [ ] Make `syntect`, `two-face` optional in Cargo.toml
- [ ] `#[cfg(feature = "syntax-highlight")]` on `src/highlight.rs` module
- [ ] Stub `highlight_text()` returning `None` when feature disabled
- [ ] Remove `syntect_theme` from `PreviewRequest` / `AppConfig` when disabled
- [ ] Fix `ui/mod.rs` markdown preview path
- [ ] Fix `ui/pane.rs` test code
- [ ] Gate `image-preview`
- [ ] Make `image`, `ratatui-image` optional in Cargo.toml
- [ ] `#[cfg(feature = "image-preview")]` on image preview code in `src/preview.rs`, `src/ui/preview.rs`
- [ ] Stub image preview returning "image preview disabled" when feature off
- [ ] Handle `Picker` type in `AppState` — use `()` or feature-gate field
- [ ] Gate `sftp`
- [ ] Make `ssh2` optional in Cargo.toml
- [ ] `#[cfg(feature = "sftp")]` on `src/fs/sftp.rs`
- [ ] Feature-gate SFTP commands/worker in `src/jobs.rs`
- [ ] Feature-gate SSH state/UI modules
- [ ] Gate `terminal-panel`
- [ ] Make `vt100` optional; `portable-pty` and `conpty` already target-scoped
- [ ] `#[cfg(feature = "terminal-panel")]` on `src/pty.rs`, `src/state/terminal.rs`, `src/ui/terminal.rs`
- [ ] Feature-gate terminal worker/commands in `src/jobs.rs`
- [ ] Handle `TerminalState` in `WorkspaceState` — use `Option` or feature-gate field
- [ ] Gate `auto-update`
- [ ] Make `ureq` optional in Cargo.toml
- [ ] `#[cfg(feature = "auto-update")]` on `src/update.rs`
- [ ] Stub `UpdateChecker` / `UpdateState` when disabled
- [ ] Feature-gate update check at startup in `src/app.rs`
- [ ] Testing
- [ ] `cargo test --workspace --features default` passes
- [ ] `cargo test --workspace --no-default-features` passes (minimal build)
- [ ] Add CI workflow matrix for both feature sets
- [ ] Validation
- [ ] `cargo fmt --all -- --check`
- [ ] `cargo clippy --workspace --all-targets --all-features -- -D warnings`
- [ ] `cargo test --workspace`
- [ ] `cargo build --release --no-default-features` (minimal binary)
- [ ] `cargo build --release` (full binary)

---

## Phase 2: Rendering & Cache Optimizations (v0.6.x)

**Branch:** TBD (`feat/render-optimizations`)

### 2.1 Pane Cache Rebuild
- Cache `lower_name: String` in `EntryInfo` at scan time
- Reuse `filtered_indices` Vec with `clear()` + `extend()`
- Pre-allocate `Vec` capacity in `rebuild_cache()`

### 2.2 Rendering Hot Path
- Pre-allocate `Vec<Span>` capacity in `render_status_bar()`, `render_key_hints()`, `render_pane()`
- Cache breadcrumb strings in `PaneState`
- Reuse `Vec<ListItem>` buffer across frames
- Replace `"─".repeat()` with static lookup
- Move markdown parsing to preview worker thread

### 2.3 String/Path Optimizations
- `selected_path()` → return `Option<&Path>`
- Cache `display_name: String` in `EntryInfo`
- Gate debug string formatting behind `debug_visible`

---

## Phase 3: TUI Evolution — Composable Layout (v0.6.x)

**Branch:** TBD (`feat/composable-layout`)

### 3.1 Panel Trait Abstraction
- Extract `Panel` trait from pane concepts
- Implementors: `FilePanel`, `PreviewPanel`, `TerminalPanel`, `EditorPanel`

### 3.2 Docked Panels
- Add bottom/side dock areas to workspace layout
- Migrate search, bookmarks, git status from modals to docks

### 3.3 Configurable Layouts
- Per-workspace layout config (2×2, 3×1, classic dual-pane)
- Session persistence for layout state

---

## Phase 4: Extensibility — Rich Hooks (v0.6.x / v0.7.x)

**Branch:** TBD (`feat/rich-hooks`)

### 4.1 JSON-RPC Hook Protocol
- New `mode = "jsonrpc"` for hooks
- stdin/stdout protocol for two-way communication
- Hook can emit `PluginCommand` responses

### 4.2 New Hook Events
- `on_preview`, `on_mark`, `on_pre_command`, `on_post_command`, `on_key`

### 4.3 Plugin Context
- Serialize `PluginContext` (selection, marks, workspace, pane) to JSON
- Allow hooks to return `Vec<PluginCommand>` to influence Zeta state

---

## Phase 5: Native Dynamic Plugins (v0.7.x)

**Branch:** TBD (`feat/native-plugins`)

### 5.1 `ZetaPlugin` Trait
- Define stable(ish) plugin trait
- Load `.so`/`.dll` from `~/.config/zeta/plugins/` via `libloading`

### 5.2 Plugin Points
- Custom preview generators
- Custom filesystem backends
- Custom panels
- Custom actions

---

## Progress Log

| Date | Phase | Action | Status |
|------|-------|--------|--------|
| 2026-05-12 | 1 | Created branch, analyzed dependency usage | ✅ Done |
| 2026-05-12 | 1 | Implemented all feature gates, fixed compilation | ✅ Done |
| 2026-05-12 | 1 | cargo fmt, cargo clippy --all-features, cargo test --workspace | ✅ Done |
| 2026-05-12 | 1 | Committed to `feat/feature-gating` | ✅ Done |
Loading
Loading