Skip to content
Open
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
41 changes: 41 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
name: Bug report
about: Report something that's broken or behaves incorrectly
title: "bug: "
labels: bug
---

## Summary

<!-- One or two sentences: what happened, what did you expect? -->

## Steps to reproduce

1.
2.
3.

## Expected behavior

<!-- What should have happened? -->

## Actual behavior

<!-- What happened instead? Include any error text shown in the TUI. -->

## Environment

- `lc` version: <!-- e.g. 0.0.12, or commit hash if built from source -->
- OS: <!-- e.g. macOS 15, Ubuntu 24.04 -->
- Terminal: <!-- e.g. Alacritty 0.13, iTerm2, kitty, GNOME Terminal -->
- Rust version (if built from source): <!-- rustc --version -->

## Config & context

<!-- Anything relevant: relevant `~/.config/lc/config.toml` settings, icon theme,
whether chafa is installed for image preview, truecolor support, etc.
Remove if not applicable. -->

```
COLORTERM=?
```
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 📖 Documentation
url: https://github.com/leszek3737/LibreCommander#readme
about: Install, keybindings, config, and FAQ — check the README first.
- name: 🔍 Search existing issues
url: https://github.com/leszek3737/LibreCommander/issues?q=is%3Aissue
about: Your question may already be answered in an open or closed issue.
23 changes: 23 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
name: Feature request
about: Suggest a new feature or enhancement
title: "feat: "
labels: enhancement
---

## Problem

<!-- What are you trying to do, and what makes it hard or impossible today? -->

## Proposed solution

<!-- Describe the feature or change you'd like. If you have a keybinding or
config idea in mind, include it. -->

## Alternatives considered

<!-- Any workarounds or other approaches you've tried. -->

## Additional context

<!-- Screenshots, mockups, links to similar features in other file managers, etc. -->
34 changes: 34 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!--
Thanks for contributing to lc! Please read CONTRIBUTING.md first.
Keep the summary tight; reviewers read the diff, not the essay.
-->

## Summary

<!-- What does this PR do, and why? One short paragraph. -->

Closes #<!-- issue number, if any -->

## Type of change

- [ ] Bug fix (non-breaking)
- [ ] New feature (non-breaking)
- [ ] Refactor / cleanup
- [ ] Docs
- [ ] Breaking change

## Checklist

- [ ] Linked the issue this closes (if applicable)
- [ ] `cargo fmt`
- [ ] `cargo clippy --locked --all-targets -- -D warnings` — zero warnings
- [ ] `cargo test --locked` — green
- [ ] `cargo build --release --locked` — succeeds
- [ ] Added/updated tests for the change
- [ ] No `unsafe`, no `println!`/`eprintln!`/`dbg!` in committed code
- [ ] Commit messages follow Conventional Commits, no attribution trailers

## Notes for reviewers

<!-- Anything non-obvious: tradeoffs, why you picked this approach, areas that
need extra scrutiny. Remove if nothing to add. -->
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,10 @@
yazi
.claude/worktrees/
docs/pr-plan/
.claude-flow/
.claude/
.mcp.json
.swarm/
ruvector.db

.wayland/
208 changes: 208 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Contributing to Libre Commander

Thanks for your interest in improving `lc`! This guide covers everything you need
to build, test, and submit changes.

> **Working with an AI assistant?** [`AGENTS.md`](AGENTS.md) contains the
> machine-oriented instructions (Serena/GitNexus usage, hard rules, gotchas).
> Read that first — humans and agents follow the same rules.

---

## Quick Start

```bash
git clone https://github.com/leszek3737/LibreCommander.git
cd LibreCommander
cargo run # dev build, optimized for fast incremental rebuilds
cargo test --locked # full test suite
```

Requirements: **Rust 1.95+** (edition 2024), `cargo`.

Before proposing a feature, please [open an issue](https://github.com/leszek3737/LibreCommander/issues)
to discuss scope — especially for anything beyond a bug fix.

---

## The Quality Gate

CI runs these on every push and PR (`.github/workflows/rust.yml`, Linux + macOS
matrix). They must be green before merge — run them locally first:

```bash
cargo fmt
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked
cargo build --release --locked
```

| Command | Purpose |
|---------|---------|
| `cargo fmt` | Format the code |
| `cargo clippy --locked --all-targets -- -D warnings` | Zero lint warnings |
| `cargo test --locked` | All tests pass |
| `cargo build --release --locked` | Release build succeeds |

> `--locked` is intentional: contributions must build against the committed
> `Cargo.lock`, not a freshly resolved dependency set.

---

## Hard Rules

These are non-negotiable — they protect the TUI and your data:

- **`unsafe_code = "forbid"`** — no `unsafe`, anywhere. No exceptions.
- **No `println!` / `eprintln!` / `dbg!`** in committed code — they corrupt the
TUI display and are denied by clippy. Use the `app::debug_log!` macro instead.
- **Never mutate state from `ui::*` draw code** — rendering must be a pure
function of `AppState`. Only `input::*` handlers mutate state.
- **Never block the event thread** — any work over ~50 ms goes to `rayon` or the
`app::job_runner`, never inline.
- **Never introduce `tokio`/async** — the project is intentionally synchronous.
Use `rayon` for parallelism, `mpsc` channels for progress.
- **Destructive ops need explicit confirmation** — delete/move/overwrite must
confirm unless already confirmed in the current flow.
- **Symlinks are data** — don't follow them during chmod/copy/delete unless the
operation explicitly requires it.
- **Cross-device moves** use copy+delete fallback with cancellation and no-clobber.
- **Archive extraction** validates paths (zip-slip), sets size limits, handles
symlinks safely.
- **No network calls** — `lc` is offline by design.

---

## Architecture (at a glance)

```
┌─────────────┐
crossterm ──► │ event loop │ (main.rs) blocking read + poll(33ms)
└──────┬──────┘
│ events
┌─────────────┐ ┌──────────────┐
│ input/* │ ──mut──► │ AppState │ single source of truth (~36 fields)
│ handlers │ └──────┬───────┘
└─────────────┘ │ pure read
▲ ▼
┌─────────────┐ ┌──────────────┐
│ ops/* │ ◄─jobs─ │ ui/* │ pure render, never mutates
│ (rayon) │ │ ratatui │
└─────────────┘ └──────────────┘
```

- **Sync event loop + rayon offloading** — no async runtime.
- **One `AppState` struct** holds all UI-relevant data → enables pure rendering.
- **Ratatui** immediate-mode + crossterm backend + double-buffered diff.

### Repository map

| Directory | Responsibility | Notes |
|-----------|---------------|-------|
| `src/main.rs` | Event loop, dispatch, `TerminalGuard` | RAII terminal cleanup on panic |
| `src/render.rs` | Render orchestration | |
| `src/render_dialog_map.rs` | Dialog render dispatch | by `DialogKind` |
| `src/input/` | Key/mouse handling — **mutates state** | `normal.rs`, `dialogs.rs`, `mode_dispatch.rs` |
| `src/app/` | State types, config, keymaps, job runner, watcher sync | `types/app_state.rs` |
| `src/ops/` | File operations — copy, move, delete, search, archive, sort | MUST be cancellable |
| `src/ui/` | Pure rendering — **never mutates state** | `panels/`, `dialogs/`, `viewer/` |
| `src/fs/` | Directory reads (rayon), `notify` watcher, path helpers | |
| `src/tests/` | Integration tests | `AppState` harness |
| `src/menu.rs` | `F9` menu bar definitions | |

For deeper detail (event-loop internals, ADRs), read `src/main.rs` and
`AGENTS.md`.

---

## Code Conventions

We follow standard Rust style; only the **non-default** rules are listed:

- **Functions over ~100 lines** trip `too_many_lines`. Split along natural seams;
propose the split in your PR first.
- Prefer `?` and `let ... else` over `.unwrap()`/`.expect()`.
`#[allow(...)]` only on `mod tests` blocks.
- Use `unicode-width` for column math — `len() != display width` for CJK/emoji
filenames.
- Prefer `std::io::Result`; avoid `anyhow`.
- `#[allow(...)]` to suppress lints is allowed **only** for:
`unwrap_used`/`expect_used`/`panic` on tests, `print_stdout` when the TUI is
suspended, `non_snake_case` for external tokens.
- ANSI escape sequences in strings are **not** rendered — use the `ansi-to-tui` crate.
- The `notify` watcher backend differs on macOS (`cfg(target_os)`) — test
path/permission logic for both platforms.

### File-size policy

800 lines is a **checkpoint**, not a hard limit. Evaluate whether a split along
natural seams exists; propose it before splitting. Cohesive files (single state
machine, one impl block) stay even if large. Never split by line count alone.

---

## Testing

- **Unit tests:** inline `#[cfg(test)] mod tests` in the same file; directory
modules have `tests.rs` siblings.
- **Integration tests:** `src/tests/` — use the `AppState` harness.
- **File ops:** always use `tempfile::TempDir`; cover symlinks, cross-device,
and Unicode filenames.
- **UI rendering:** `Terminal::new(TestBackend::new(w, h))` + assert on the
buffer. See `ui/viewer/tests.rs`.
- **Async/watcher patterns:** `EventHarness` from `fs/watcher/tests.rs`.

```bash
cargo test --locked # everything
cargo test <name> -- --nocapture # single test with output
```

---

## Gotchas

- The event loop uses blocking `crossterm::event::read()` + `event::poll(33ms)`
(`EVENT_POLL_TIMEOUT_MS` in `main.rs`). Don't change the timeout without
understanding the spinner tick (200 ms).
- `TerminalGuard` provides RAII cleanup on panic — don't bypass it in error paths.
- When spawning an external process: you **must** `LeaveAlternateScreen` +
`disable_raw_mode` first, and reclaim after. (Vim queries terminal
capabilities via ANSI that crossterm reads as keyboard events.)
- Adding a new dialog touches **4 places**: `DialogKind` variant (`modes.rs`),
detail struct (`types/dialogs.rs`), input handler (`input/dialogs.rs`), and
render (`render_dialog_map.rs` + `ui/dialogs/`).
- The main loop calls `sync_watcher_job_state` before `sync_watcher_paths`, and
`pre_draw()` before `terminal.draw()` — check this before modifying the loop.
- Config migration requires user approval — users hand-edit `config.toml`.

---

## Commits

We use [Conventional Commits](https://www.conventionalcommits.org/):

```
feat(archive): add zstd write support
fix(viewer): correct horizontal scroll on wide lines
refactor(input): split mode dispatch
docs(readme): add screenshot
```

- Don't bump the `Cargo.toml` version unless asked.
- Keep commit messages clean and self-contained — no co-author trailers,
attribution lines, or "generated with …" notes.
- Each logical change is its own commit; never amend a commit that has been
pushed.

---

## Filing issues & pull requests

- **Bugs:** use the bug-report template; include OS, terminal, Rust version, and
minimal reproduction steps.
- **Features:** open an issue to discuss before implementing large changes.
- **Pull requests:** use the PR template, ensure the quality gate is green, and
link the issue you're closing (`Closes #123`).

Happy hacking! 🦀
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ rust-version = "1.95"
description = "A fast Rust terminal file manager inspired by Midnight Commander"
license = "MIT"
readme = "README.md"
repository = "https://github.com/leszek3737/lc---Libre-Commander"
repository = "https://github.com/leszek3737/LibreCommander"

[dependencies]
bitflags = "2"
Expand Down
Loading
Loading