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
4 changes: 3 additions & 1 deletion .github/agents/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
````markdown
# RCDir Development Guidelines

Auto-generated from all feature plans. Last updated: 2026-04-19
Auto-generated from all feature plans. Last updated: 2026-04-20

## Active Technologies
- Rust stable (edition 2024, toolchain 1.85+) + `windows` crate 0.62 (Win32 API), `widestring` 1 (UTF-16) (003-file-icons)
Expand All @@ -12,6 +12,7 @@ Auto-generated from all feature plans. Last updated: 2026-04-19
- Single flat file (`%USERPROFILE%\.rcdirconfig`), UTF-8 with optional BOM (006-config-file-support)
- Rust stable (latest stable release, per rust-toolchain.toml) + `windows` crate (Win32 API: `CreateFileW`, `DeviceIoControl`, `FSCTL_GET_REPARSE_POINT`) (007-symlink-junction-targets)
- N/A (filesystem reads only, no persistent state) (007-symlink-junction-targets)
- Rust stable (latest) + `windows` crate (Win32 API), `widestring` (UTF-16) (008-ellipsize-targets)

- Rust stable (1.93.0), Edition 2024 + `windows` crate (Win32 APIs), `widestring` (UTF-16 interop) (master)

Expand All @@ -31,6 +32,7 @@ cargo test; cargo clippy
Rust stable (1.93.0), Edition 2024: Follow standard conventions

## Recent Changes
- 008-ellipsize-targets: Added Rust stable (latest) + `windows` crate (Win32 API), `widestring` (UTF-16)
- 007-symlink-junction-targets: Added Rust stable (latest stable release, per rust-toolchain.toml) + `windows` crate (Win32 API: `CreateFileW`, `DeviceIoControl`, `FSCTL_GET_REPARSE_POINT`)
- 006-config-file-support: Added Rust stable (latest stable release) + `windows` crate for Win32 console API; standard library for file I/O (`std::fs::read`)
Fix- 004-tree-view: Added Rust stable (edition 2024) + `windows` crate (Win32 API), `widestring` crate, Rust std library only (no third-party libraries)
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to RCDir are documented in this file.

## [5.5] - 2026-04-20

### Added

- Ellipsize long link target paths: middle-truncate long reparse target paths with `…` (U+2026) to prevent line wrapping
- Priority-based truncation: preserves first two dirs + leaf dir + filename, degrades gracefully
- Works in both normal and tree modes (tree mode accounts for connector prefix width)
- Ellipsis character rendered in Default color for visual distinction from path text
- `--Ellipsize` switch (on by default); `--Ellipsize-` to disable and show full paths
- Configurable via `.rcdirconfig` and `RCDIR` environment variable
- 23 new unit tests for path truncation algorithm
- 1 new output parity test for `--Ellipsize-` disabled mode

## [5.4] - 2026-04-20

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ It's designed as a practical `dir`-style command with useful defaults (color by

| Version | Highlights |
|---------|------------|
| **5.5** | Ellipsize long link target paths — middle-truncate with `…` to prevent line wrapping |
| **5.4** | Symlink, junction, and AppExecLink target display (`→ target`) |
| **5.3** | Config file support (`.rcdirconfig`), `--config` diagnostics, `--settings` merged view |
| **5.2** | Interactive PowerShell alias configuration (`--set-aliases`, `--get-aliases`, `--remove-aliases`) |
Expand Down
2 changes: 1 addition & 1 deletion Version.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# Major and minor are updated manually.
major = 5
minor = 4
build = 1404
build = 1406
35 changes: 35 additions & 0 deletions specs/008-ellipsize-targets/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Specification Quality Checklist: Ellipsize Long Link Target Paths

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-04-20
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- Spec ported 1:1 from TCDir spec 008 — all clarifications already resolved in TCDir session 2026-04-19
- All items pass validation — spec is ready for `/speckit.clarify` or `/speckit.plan`
64 changes: 64 additions & 0 deletions specs/008-ellipsize-targets/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Data Model: Ellipsize Long Link Target Paths

**Date**: 2026-04-20
**Feature**: 008-ellipsize-targets

## Entities

### EllipsizedPath (new — in `src/path_ellipsis.rs`)

Return type from `ellipsize_path()`. Enables the displayer to render prefix and suffix in the source file's color with the `…` in Default color.

| Field | Type | Description |
|-------|------|-------------|
| `prefix` | `String` | Path text before the ellipsis (e.g., `C:\Program Files\`). Full path if not truncated. |
| `suffix` | `String` | Path text after the ellipsis (e.g., `\python3.12.exe`). Empty if not truncated. |
| `truncated` | `bool` | `true` if the path was middle-truncated, `false` if shown in full. |

**Rules:**
- When `truncated` is false: `prefix` contains the full path, `suffix` is empty
- When `truncated` is true: display is `prefix` + `…` + `suffix`
- Total display width when truncated: `prefix.len() + 1 + suffix.len()` (the `…` is 1 char wide)

### Config (modified — in `src/config/mod.rs`)

| Field | Type | Default | New? |
|-------|------|---------|------|
| `ellipsize` | `Option<bool>` | `None` (treated as true) | Yes |

### CommandLine (modified — in `src/command_line.rs`)

| Field | Type | Default | New? |
|-------|------|---------|------|
| `ellipsize` | `Option<bool>` | `None` (treated as true) | Yes |

## State Transitions

None. `EllipsizedPath` is computed per-line during display, not stored.

## Relationships

```
NormalDisplayer / TreeDisplayer
│ Computes available_width from metadata column widths + console.width()
ellipsize_path(&reparse_target, available_width)
EllipsizedPath { prefix, suffix, truncated }
if truncated:
writef(text_attr, prefix) + printf(Default, "…") + writef_line(text_attr, suffix)
else:
writef_line(text_attr, prefix)
```

## Validation Rules

- `available_width` is computed from `console.width()` minus all metadata columns — never negative (clamped to 0)
- Truncation only applies when `cmd.ellipsize.unwrap_or(true)` is true
- Paths with fewer than 3 components are never truncated (nothing to elide)
- The `…` character must save space: if the truncated form isn't shorter than the original, return the original
72 changes: 72 additions & 0 deletions specs/008-ellipsize-targets/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Implementation Plan: Ellipsize Long Link Target Paths

**Branch**: `008-ellipsize-targets` | **Date**: 2026-04-20 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `specs/008-ellipsize-targets/spec.md`

## Summary

Middle-truncate long link target paths using `…` (U+2026) to prevent line wrapping in normal and tree modes. Truncation preserves first two directory components and leaf filename where possible, falling back gracefully. New `--Ellipsize` switch (default on) with `--Ellipsize-` to disable. Ellipsis rendered in `Default` color attribute to be visually distinct from path text.

## Technical Context

**Language/Version**: Rust stable (latest)
**Primary Dependencies**: `windows` crate (Win32 API), `widestring` (UTF-16)
**Storage**: N/A
**Testing**: `cargo test` (Rust built-in `#[test]` + `#[cfg(test)]`)
**Target Platform**: Windows 10/11, x64 and ARM64
**Project Type**: CLI tool (desktop)
**Performance Goals**: Pure string operations — no I/O, no measurable cost
**Constraints**: Truncation logic must be a pure function testable with synthetic data
**Scale/Scope**: One new module (`path_ellipsis.rs`), switch plumbing in 3 files, display changes in 2 displayers

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

| Principle | Status | Notes |
|-----------|--------|-------|
| I. Code Quality | PASS | Pure function for truncation logic; `Result` not needed (infallible string operations); follows existing module patterns |
| II. Testing Discipline | PASS | Pure truncation function testable with synthetic data; real WindowsApps paths as test inputs; no system state accessed; output parity tests added |
| III. UX Consistency | PASS | New `--Ellipsize` switch follows `--Icons`/`--Tree` pattern; documented in `-?` help and `--Settings`; `…` in Default color for visual distinction |
| IV. Performance | PASS | String operations only, called 0–N times per directory for reparse entries — negligible cost |
| V. Simplicity | PASS | One pure function, minimal display changes, follows existing switch infrastructure exactly |

**Gate result: PASS** — No violations.

## Project Structure

### Documentation (this feature)

```text
specs/008-ellipsize-targets/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
└── tasks.md # Phase 2 output (/speckit.tasks)
```

### Source Code (files affected)

```text
src/
├── path_ellipsis.rs # New — EllipsizedPath struct, ellipsize_path() pure function, unit tests
├── lib.rs # Add `pub mod path_ellipsis;`
├── command_line.rs # Add `ellipsize: Option<bool>` field, parse --Ellipsize/--Ellipsize- switch
├── config/
│ ├── mod.rs # Add `ellipsize: Option<bool>` field, bump SWITCH_COUNT 9→10, add to SWITCH_MEMBER_ORDER
│ └── env_overrides.rs # Add ellipsize/ellipsize- to SWITCH_MAPPINGS, switch_name_to_source_index
├── results_displayer/
│ ├── normal.rs # Compute available width, call ellipsize_path(), render with split colors
│ └── tree.rs # Same as normal + account for tree prefix width
├── usage.rs # Add --Ellipsize to help output, SwitchInfo, and --Settings display

tests/
└── output_parity.rs # Add parity test cases for ellipsize in normal and tree modes
```

**Structure Decision**: New `path_ellipsis.rs` module encapsulates the truncation logic as a pure function — keeps displayers clean and makes the algorithm independently testable without mocks. Follows the same pattern as other single-purpose modules (`reparse_resolver.rs`, `cloud_status.rs`).

## Complexity Tracking

No constitution violations — this section is empty.
54 changes: 54 additions & 0 deletions specs/008-ellipsize-targets/quickstart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Quickstart: Ellipsize Long Link Target Paths

**Date**: 2026-04-20
**Feature**: 008-ellipsize-targets

## What's Changing

Long link target paths are middle-truncated with `…` to prevent line wrapping:

```
Before (wraps at 120 chars):
python.exe → C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.29.30.0_arm64__8wekyb3d8bbwe\AppInstallerPythonRedirector.exe

After (fits in 120 chars):
python.exe → C:\Program Files\…\AppInstallerPythonRedirector.exe
```

New switch: `--Ellipsize` (default on), `--Ellipsize-` to disable.

## Files to Modify

| File | Change |
|------|--------|
| `src/path_ellipsis.rs` | **New** — `EllipsizedPath` struct, `ellipsize_path()` pure function, `#[cfg(test)]` unit tests |
| `src/lib.rs` | Add `pub mod path_ellipsis;` |
| `src/command_line.rs` | Add `ellipsize: Option<bool>` field, parse `--Ellipsize` / `--Ellipsize-`, add to recognized switches, apply config defaults |
| `src/config/mod.rs` | Add `ellipsize: Option<bool>` field, bump `SWITCH_COUNT` 9→10, add to `SWITCH_MEMBER_ORDER` |
| `src/config/env_overrides.rs` | Add `ellipsize`/`ellipsize-` to `SWITCH_MAPPINGS`, `switch_name_to_source_index` |
| `src/results_displayer/normal.rs` | Compute available width, call `ellipsize_path()`, render with split colors |
| `src/results_displayer/tree.rs` | Same as normal + account for tree prefix width |
| `src/usage.rs` | Add `--Ellipsize` to help output, `SWITCH_INFOS`, and `--Settings` display |
| `tests/output_parity.rs` | Add parity test cases for ellipsize in normal and tree modes |

## Build & Test

```powershell
# Build (uses VS Code task or Build.ps1 — never raw cargo build)
# Use VS Code task: "Build Debug (current arch)"

# Test
cargo test

# Clippy
cargo clippy -- -D warnings
```

## Key Design Decisions

1. **Pure function** — `ellipsize_path()` takes a path string and available width, returns a struct with prefix/suffix split
2. **Arithmetic width calculation** — no character counter needed; compute from known column widths + `console.width()`
3. **Priority-based truncation** — first two dirs + leaf dir + filename > first two dirs + filename > first dir + filename > leaf only
4. **Ellipsis color** — `Attribute::Default`, not file color, so it's visually distinct
5. **Default on** — most users benefit from truncation; `--Ellipsize-` opts out
6. **Same pattern as --Icons/--Tree** — `Option<bool>` with conditional merge in `apply_config_defaults`
Loading
Loading