Full Code of mufeedvh/code2prompt for AI

main e73c34d17ee6 cached
255 files
943.9 KB
245.8k tokens
549 symbols
1 requests
Download .txt
Showing preview only (1,019K chars total). Download the full file or copy to clipboard to get everything.
Repository: mufeedvh/code2prompt
Branch: main
Commit: e73c34d17ee6
Files: 255
Total size: 943.9 KB

Directory structure:
gitextract_2efzl1fk/

├── .assets/
│   └── flow_diagram.md
├── .c2pconfig
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yml
│       ├── release.yml
│       └── website.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── README_ES.md
├── crates/
│   ├── code2prompt/
│   │   ├── Cargo.toml
│   │   ├── src/
│   │   │   ├── args.rs
│   │   │   ├── clipboard.rs
│   │   │   ├── config.rs
│   │   │   ├── config_loader.rs
│   │   │   ├── main.rs
│   │   │   ├── model/
│   │   │   │   ├── commands.rs
│   │   │   │   ├── mod.rs
│   │   │   │   ├── prompt_output.rs
│   │   │   │   ├── settings.rs
│   │   │   │   ├── statistics/
│   │   │   │   │   ├── mod.rs
│   │   │   │   │   └── types.rs
│   │   │   │   └── template/
│   │   │   │       ├── editor.rs
│   │   │   │       ├── mod.rs
│   │   │   │       ├── picker.rs
│   │   │   │       └── variable.rs
│   │   │   ├── token_map.rs
│   │   │   ├── tui.rs
│   │   │   ├── utils.rs
│   │   │   ├── view/
│   │   │   │   ├── formatters.rs
│   │   │   │   └── mod.rs
│   │   │   └── widgets/
│   │   │       ├── file_selection.rs
│   │   │       ├── mod.rs
│   │   │       ├── output.rs
│   │   │       ├── settings.rs
│   │   │       ├── statistics_by_extension.rs
│   │   │       ├── statistics_overview.rs
│   │   │       ├── statistics_token_map.rs
│   │   │       └── template/
│   │   │           ├── editor.rs
│   │   │           ├── mod.rs
│   │   │           ├── picker.rs
│   │   │           └── variable.rs
│   │   └── tests/
│   │       ├── common/
│   │       │   ├── fixtures.rs
│   │       │   ├── mod.rs
│   │       │   └── test_env.rs
│   │       ├── config_test.rs
│   │       ├── git_integration_test.rs
│   │       ├── integration_test.rs
│   │       ├── std_output_test.rs
│   │       └── template_integration_test.rs
│   ├── code2prompt-core/
│   │   ├── Cargo.toml
│   │   ├── src/
│   │   │   ├── builtin_templates.rs
│   │   │   ├── configuration.rs
│   │   │   ├── default_template_md.hbs
│   │   │   ├── default_template_xml.hbs
│   │   │   ├── file_processor/
│   │   │   │   ├── csv.rs
│   │   │   │   ├── default.rs
│   │   │   │   ├── ipynb.rs
│   │   │   │   ├── jsonl.rs
│   │   │   │   ├── mod.rs
│   │   │   │   └── tsv.rs
│   │   │   ├── filter.rs
│   │   │   ├── git.rs
│   │   │   ├── lib.rs
│   │   │   ├── path.rs
│   │   │   ├── selection.rs
│   │   │   ├── session.rs
│   │   │   ├── sort.rs
│   │   │   ├── template.rs
│   │   │   ├── tokenizer.rs
│   │   │   └── util.rs
│   │   ├── templates/
│   │   │   ├── binary-exploitation-ctf-solver.hbs
│   │   │   ├── clean-up-code.hbs
│   │   │   ├── cryptography-ctf-solver.hbs
│   │   │   ├── document-the-code.hbs
│   │   │   ├── find-security-vulnerabilities.hbs
│   │   │   ├── fix-bugs.hbs
│   │   │   ├── improve-performance.hbs
│   │   │   ├── refactor.hbs
│   │   │   ├── reverse-engineering-ctf-solver.hbs
│   │   │   ├── web-ctf-solver.hbs
│   │   │   ├── write-git-commit.hbs
│   │   │   ├── write-github-pull-request.hbs
│   │   │   └── write-github-readme.hbs
│   │   └── tests/
│   │       ├── binary_detection_test.rs
│   │       ├── file_processor_test.rs
│   │       ├── filter_test.rs
│   │       ├── git_test.rs
│   │       ├── path_test.rs
│   │       ├── session_integration_test.rs
│   │       ├── sort_test.rs
│   │       ├── template_test.rs
│   │       └── util_test.rs
│   └── code2prompt-python/
│       ├── .python-version
│       ├── Cargo.toml
│       ├── pyproject.toml
│       ├── python-sdk/
│       │   ├── .gitignore
│       │   ├── README.md
│       │   ├── __init__.py
│       │   ├── code2prompt_rs/
│       │   │   ├── __init__.py
│       │   │   └── code2prompt.py
│       │   └── examples/
│       │       └── basic_usage.py
│       ├── src/
│       │   ├── lib.rs
│       │   ├── python.rs
│       │   └── python.rs.bak
│       └── tests/
│           ├── __init__.py
│           ├── conftest.py
│           ├── test_config.py
│           ├── test_generation.py
│           └── test_special_feature.py
├── llms-install.md
└── website/
    ├── .gitignore
    ├── .vscode/
    │   ├── extensions.json
    │   └── launch.json
    ├── README.md
    ├── astro.config.mjs
    ├── package.json
    ├── pnpm-workspace.yaml
    ├── public/
    │   ├── CNAME
    │   ├── assets/
    │   │   ├── css/
    │   │   │   └── marquee.css
    │   │   └── js/
    │   │       └── main.js
    │   └── prism-theme.css
    ├── src/
    │   ├── assets/
    │   │   └── examples/
    │   │       ├── history_notes/
    │   │       │   ├── history_notes/
    │   │       │   │   ├── history/
    │   │       │   │   │   ├── medieval.txt
    │   │       │   │   │   ├── renaissance.txt
    │   │       │   │   │   └── ww2.txt
    │   │       │   │   └── meta/
    │   │       │   │       └── my_revision_goals.txt
    │   │       │   ├── prompt.md
    │   │       │   └── question.txt
    │   │       ├── my_recipes/
    │   │       │   ├── my_recipes/
    │   │       │   │   ├── pantry/
    │   │       │   │   │   └── my_ingredients.txt
    │   │       │   │   └── recipes/
    │   │       │   │       ├── pasta.txt
    │   │       │   │       ├── pizza.txt
    │   │       │   │       ├── salad.txt
    │   │       │   │       └── soup.txt
    │   │       │   ├── prompt.md
    │   │       │   └── question.txt
    │   │       └── node_app/
    │   │           ├── node_app/
    │   │           │   ├── README.md
    │   │           │   ├── data/
    │   │           │   │   └── sample.json
    │   │           │   └── src/
    │   │           │       ├── index.js
    │   │           │       └── utils.js
    │   │           ├── prompt.md
    │   │           └── question.txt
    │   ├── components/
    │   │   ├── Footer.astro
    │   │   ├── Header.astro
    │   │   ├── Section0.astro
    │   │   ├── Section1.astro
    │   │   ├── Section2.astro
    │   │   ├── Section3.astro
    │   │   └── Section4.astro
    │   ├── content/
    │   │   └── docs/
    │   │       ├── blog/
    │   │       │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       ├── de/
    │   │       │   ├── blog/
    │   │       │   │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       │   └── docs/
    │   │       │       ├── explanations/
    │   │       │       │   ├── glob_pattern_filter.mdx
    │   │       │       │   ├── glob_patterns.md
    │   │       │       │   └── tokenizers.md
    │   │       │       ├── how_to/
    │   │       │       │   ├── filter_files.md
    │   │       │       │   ├── install.mdx
    │   │       │       │   └── ssh.md
    │   │       │       ├── references/
    │   │       │       │   ├── command_line_options.md
    │   │       │       │   └── default_template.md
    │   │       │       ├── tutorials/
    │   │       │       │   ├── getting_started.mdx
    │   │       │       │   ├── learn_filters.mdx
    │   │       │       │   └── learn_templates.mdx
    │   │       │       ├── vision.mdx
    │   │       │       └── welcome.mdx
    │   │       ├── docs/
    │   │       │   ├── explanations/
    │   │       │   │   ├── glob_pattern_filter.mdx
    │   │       │   │   ├── glob_patterns.md
    │   │       │   │   └── tokenizers.md
    │   │       │   ├── how_to/
    │   │       │   │   ├── filter_files.md
    │   │       │   │   ├── install.mdx
    │   │       │   │   └── ssh.md
    │   │       │   ├── references/
    │   │       │   │   ├── command_line_options.md
    │   │       │   │   └── default_template.md
    │   │       │   ├── tutorials/
    │   │       │   │   ├── configuration.mdx
    │   │       │   │   ├── getting_started.mdx
    │   │       │   │   ├── learn_filters.mdx
    │   │       │   │   └── learn_templates.mdx
    │   │       │   ├── vision.mdx
    │   │       │   └── welcome.mdx
    │   │       ├── es/
    │   │       │   ├── blog/
    │   │       │   │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       │   └── docs/
    │   │       │       ├── explanations/
    │   │       │       │   ├── glob_pattern_filter.mdx
    │   │       │       │   ├── glob_patterns.md
    │   │       │       │   └── tokenizers.md
    │   │       │       ├── how_to/
    │   │       │       │   ├── filter_files.md
    │   │       │       │   ├── install.mdx
    │   │       │       │   └── ssh.md
    │   │       │       ├── references/
    │   │       │       │   ├── command_line_options.md
    │   │       │       │   └── default_template.md
    │   │       │       ├── tutorials/
    │   │       │       │   ├── getting_started.mdx
    │   │       │       │   ├── learn_filters.mdx
    │   │       │       │   └── learn_templates.mdx
    │   │       │       ├── vision.mdx
    │   │       │       └── welcome.mdx
    │   │       ├── fr/
    │   │       │   ├── blog/
    │   │       │   │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       │   └── docs/
    │   │       │       ├── explanations/
    │   │       │       │   ├── glob_pattern_filter.mdx
    │   │       │       │   ├── glob_patterns.md
    │   │       │       │   └── tokenizers.md
    │   │       │       ├── how_to/
    │   │       │       │   ├── filter_files.md
    │   │       │       │   ├── install.mdx
    │   │       │       │   └── ssh.md
    │   │       │       ├── references/
    │   │       │       │   ├── command_line_options.md
    │   │       │       │   └── default_template.md
    │   │       │       ├── tutorials/
    │   │       │       │   ├── getting_started.mdx
    │   │       │       │   ├── learn_filters.mdx
    │   │       │       │   └── learn_templates.mdx
    │   │       │       ├── vision.mdx
    │   │       │       └── welcome.mdx
    │   │       ├── ja/
    │   │       │   ├── blog/
    │   │       │   │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       │   └── docs/
    │   │       │       ├── explanations/
    │   │       │       │   ├── glob_pattern_filter.mdx
    │   │       │       │   ├── glob_patterns.md
    │   │       │       │   └── tokenizers.md
    │   │       │       ├── how_to/
    │   │       │       │   ├── filter_files.md
    │   │       │       │   ├── install.mdx
    │   │       │       │   └── ssh.md
    │   │       │       ├── references/
    │   │       │       │   ├── command_line_options.md
    │   │       │       │   └── default_template.md
    │   │       │       ├── tutorials/
    │   │       │       │   ├── getting_started.mdx
    │   │       │       │   ├── learn_filters.mdx
    │   │       │       │   └── learn_templates.mdx
    │   │       │       ├── vision.mdx
    │   │       │       └── welcome.mdx
    │   │       ├── ru/
    │   │       │   ├── blog/
    │   │       │   │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       │   └── docs/
    │   │       │       ├── explanations/
    │   │       │       │   ├── glob_pattern_filter.mdx
    │   │       │       │   ├── glob_patterns.md
    │   │       │       │   └── tokenizers.md
    │   │       │       ├── how_to/
    │   │       │       │   ├── filter_files.md
    │   │       │       │   ├── install.mdx
    │   │       │       │   └── ssh.md
    │   │       │       ├── references/
    │   │       │       │   ├── command_line_options.md
    │   │       │       │   └── default_template.md
    │   │       │       ├── tutorials/
    │   │       │       │   ├── getting_started.mdx
    │   │       │       │   ├── learn_filters.mdx
    │   │       │       │   └── learn_templates.mdx
    │   │       │       ├── vision.mdx
    │   │       │       └── welcome.mdx
    │   │       └── zh/
    │   │           ├── blog/
    │   │           │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │           └── docs/
    │   │               ├── explanations/
    │   │               │   ├── glob_pattern_filter.mdx
    │   │               │   ├── glob_patterns.md
    │   │               │   └── tokenizers.md
    │   │               ├── how_to/
    │   │               │   ├── filter_files.md
    │   │               │   ├── install.mdx
    │   │               │   └── ssh.md
    │   │               ├── references/
    │   │               │   ├── command_line_options.md
    │   │               │   └── default_template.md
    │   │               ├── tutorials/
    │   │               │   ├── getting_started.mdx
    │   │               │   ├── learn_filters.mdx
    │   │               │   └── learn_templates.mdx
    │   │               ├── vision.mdx
    │   │               └── welcome.mdx
    │   ├── content.config.ts
    │   ├── layouts/
    │   │   ├── BaseLayout.astro
    │   │   └── BlogPostLayout.astro
    │   ├── pages/
    │   │   ├── index.astro
    │   │   └── robots.txt.ts
    │   └── styles/
    │       └── global.css
    └── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .assets/flow_diagram.md
================================================
---
config:
  flowchart:
    nodeSpacing: 15
    rankSpacing: 50
    curve: monotoneX
  layout: fixed
---
flowchart LR
 subgraph S1["1. Input Source"]
        A[("📂 Codebase & Git")]
  end
 subgraph S2["2. Code2Prompt Core"]
    direction LR
        B{"🔍 Filtrage & Config"}
        C["🧠 Smart Processing
(Parse CSV, Notebooks, JSONL)"]
        D["🎨 Templating Layer
(Handlebars + Token Count)"]
  end
 subgraph S3["3. Delivery Interfaces"]
    direction TB
        E["💻 CLI / TUI"]
        F["🐍 Python SDK"]
        G["🔌 MCP Server"]
  end
    A --> B
    B --> C
    C --> D
    D --> E & F & G
    E --> H("🤖 LLM / AI Model")
    F --> H
    G --> H
    H -. 📝 Generate &amp; <br> Integrate Code .-> A

     A:::input
     B:::core
     C:::core
     D:::core
     E:::delivery
     F:::delivery
     G:::delivery
     H:::ai
    classDef input fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    classDef core fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    classDef delivery fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
    classDef ai fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    classDef loop fill:#ffffff,stroke:#333,stroke-width:1px,stroke-dasharray: 5 5

================================================
FILE: .c2pconfig
================================================
default_output = "clipboard"
include_patterns = ["*.rs"]
exclude_patterns = ["**/test*"]
line_numbers = false
absolute_path = true

[user_variables]
project = "code2prompt"


================================================
FILE: .github/dependabot.yml
================================================
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#package-ecosystem-

version: 2
updates:
  - package-ecosystem: "github-actions" # .github/workflows/*.yml
    target-branch: "main"
    directory: "/"
    schedule:
      interval: "weekly"
  - package-ecosystem: "cargo" # Cargo.lock
    target-branch: "main"
    directory: "/"
    schedule:
      interval: "weekly"
  - package-ecosystem: "cargo" # Cargo.lock
    target-branch: "main"
    directory: "/crates/code2prompt-core"
    schedule:
      interval: "weekly"
  - package-ecosystem: "pip" # pyproject.toml
    target-branch: "main"
    directory: "/crates/code2prompt-python"
    schedule:
      interval: "weekly"
  - package-ecosystem: "uv" # requirements.lock
    target-branch: "main"
    directory: "/crates/code2prompt-python"
    schedule:
      interval: "weekly"
  - package-ecosystem: "npm" # package.json and yarn.lock
    target-branch: "main"
    directory: "/website"
    schedule:
      interval: "weekly"


================================================
FILE: .github/workflows/ci.yml
================================================
# Run tests for code2prompt
name: Code2prompt Continuous Integration

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

env:
  CARGO_TERM_COLOR: always

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v6
    - name: Run tests
      run: cargo test --verbose


================================================
FILE: .github/workflows/release.yml
================================================
# Build and publish release on tags push

name: Code2prompt Release

on:
  push:
    tags:
      - 'v[0-9]*.[0-9]*.[0-9]*'

jobs:
  build:
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
          - os: macos-latest
            target: x86_64-apple-darwin
          - os: macos-latest
            target: aarch64-apple-darwin
          - os: windows-latest
            target: x86_64-pc-windows-msvc
    runs-on: ${{ matrix.os }}
    outputs:
      asset-path: ${{ steps.set_asset.outputs.asset-path }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Set up Rust toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          target: ${{ matrix.target }}
          override: true

      - name: Cache Rust dependencies
        uses: actions/cache@v5
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            target/
          key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-${{ matrix.target }}-cargo-

      - name: Install extra dependencies on Ubuntu
        if: runner.os == 'Linux'
        run: |
          if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then
            sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu
          fi

      - name: Cache LLVM on Windows
        if: runner.os == 'Windows'
        id: cache-llvm
        uses: actions/cache@v5
        with:
          path: C:\Program Files\LLVM
          key: windows-llvm-latest
          
      - name: Install LLVM on Windows
        if: runner.os == 'Windows' && steps.cache-llvm.outputs.cache-hit != 'true'
        run: |
          choco install llvm

      - name: Build
        run: cargo build --release --target ${{ matrix.target }}

      # Packaging for Windows (PowerShell)
      - name: Package Binary (Windows)
        if: runner.os == 'Windows'
        id: package_windows
        shell: pwsh
        run: |
          $BIN_DIR = "target/${{ matrix.target }}/release"
          $BIN_NAME = "code2prompt"
          New-Item -ItemType Directory -Force -Path release | Out-Null
          Copy-Item "$BIN_DIR\$BIN_NAME.exe" "release/${BIN_NAME}-${{ matrix.target }}.exe"
          # Enregistrer le chemin de l'artefact dans un fichier
          Set-Content -Path asset_windows.txt -Value "release/${BIN_NAME}-${{ matrix.target }}.exe"

      # Packaging for Linux/macOS (bash)
      - name: Package Binary (Unix)
        if: runner.os != 'Windows'
        id: package_unix
        shell: bash
        run: |
          BIN_DIR=target/${{ matrix.target }}/release
          BIN_NAME=code2prompt
          mkdir -p release
          cp "$BIN_DIR/$BIN_NAME" "release/${BIN_NAME}-${{ matrix.target }}"
          echo "release/${BIN_NAME}-${{ matrix.target }}" > asset_unix.txt

      # Get Artifact's path according to OS and defines it as output
      - name: Set asset output
        id: set_asset
        shell: bash
        run: |
          if [ -f asset_windows.txt ]; then
            ASSET_PATH=$(cat asset_windows.txt)
          else
            ASSET_PATH=$(cat asset_unix.txt)
          fi
          echo "Asset path: $ASSET_PATH"
          echo "::set-output name=asset-path::$ASSET_PATH"

      - name: Upload Artifact
        uses: actions/upload-artifact@v6
        with:
          name: asset-${{ matrix.target }}
          path: ${{ steps.set_asset.outputs.asset-path }}

  release:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v7
        with:
          path: artifacts

      - name: Create GitHub Release and upload assets
        if: startsWith(github.ref, 'refs/tags/')
        uses: softprops/action-gh-release@v2
        with:
          tag_name: ${{ github.ref }}
          name: Release ${{ github.ref }}
          body: "Automatically generated release for ${{ github.ref }}"
          files: |
            artifacts/**
        env:
          GITHUB_TOKEN: ${{ secrets.C2P_RELEASE_TOKEN }}


================================================
FILE: .github/workflows/website.yml
================================================
name: Code2prompt Website

on:
  # Trigger the workflow every time you push to the `main` branch
  # Using a different branch name? Replace `main` with your branch’s name
  push:
    branches: [main]
  # Allows you to run this workflow manually from the Actions tab on GitHub.
  workflow_dispatch:

# Allow this job to clone the repo and create a page deployment
permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout your repository using git
        uses: actions/checkout@v6
      - name: Install, build, and upload your site
        uses: withastro/action@v5
        with:
          path: website # The root location of your Astro project inside the repository. (optional)
          # node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 20. (optional)
          package-manager: pnpm # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4


================================================
FILE: .gitignore
================================================
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
# Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

### Linux ###
*~

# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

# KDE directory preferences
.directory

# Linux trash folder which might appear on any partition or disk
.Trash-*

# .nfs files are created when an open file is removed but is still being accessed
.nfs*

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### macOS Patch ###
# iCloud generated files
*.icloud

### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db

# Dump file
*.stackdump

# Folder config file
[Dd]esktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Windows shortcuts
*.lnk

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# UV
#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#uv.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# PyPI configuration file
.pypirc

.claude
CLAUDE


================================================
FILE: Cargo.toml
================================================
[workspace]
resolver = "2"
members = [
    "crates/code2prompt-core",
    "crates/code2prompt",
    "crates/code2prompt-python",
]
default-members = ["crates/code2prompt-core", "crates/code2prompt"]

[profile.release]
lto = "thin"
panic = 'abort'
codegen-units = 1

[workspace.dependencies]
anyhow = "1.0.98"
ansi_term = "0.12.1"
arboard = { version = "3.6.0" }
bracoxide = "0.1.8"
colored = "3.0.0"
csv = "1.4.0"
chrono = { version = "0.4", features = ["serde"] }
chardetng = { version = "0.1.17" }
clap = { version = "4.5", features = ["derive"] }
content_inspector = "0.2.4"
crossterm = "0.29.0"
dirs = "6.0.0"
derive_builder = { version = "0.20.2" }
env_logger = { version = "0.11.3" }
encoding_rs = { version = "0.8.35" }
indicatif = "0.18.0"
inquire = "0.9.1"
log = "0.4"
lscolors = { version = "0.21.0", features = ["ansi_term"] }
ignore = "0.4.25"
git2 = { version = "0.20.2", default-features = false, features = [
    "https",
    "vendored-libgit2",
    "vendored-openssl",
] }
globset = "0.4.15"
handlebars = "6.4.0"
once_cell = "1.19.0"
pyo3 = { version = "0.27", features = ["extension-module", "abi3-py312"] }
ratatui = "0.29.0"
regex = "1.10.3"
rayon = "1.11.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.148"
termtree = "0.5"
tiktoken-rs = "0.9.1"
terminal_size = "0.4.3"
tokio = { version = "1.49.0", features = ["full"] }
toml = "0.9.10"
tui-tree-widget = "0.23.0"
tui-textarea = "0.7"
unicode-width = "0.2.0"
walkdir = "2.4.0"
winapi = { version = "0.3.9", features = ["errhandlingapi"] }


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 Mufeed VH

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<div align="center">
  <a href="https://code2prompt.dev">
    <img align="center" width="550px" src="https://github.com/mufeedvh/code2prompt/blob/main/.assets/logo_dark_v0.0.2.svg?raw=true" alt="Code2prompt"/>
  </a>
  <br>
  <h3>Convert your codebase into a single LLM prompt.</h3>
</div>

<p align="center">
  <a href="https://code2prompt.dev"><b>Website</b></a> •
  <a href="https://code2prompt.dev/docs/welcome/"><b>Documentation</b></a> •
  <a href="https://discord.com/invite/ZZyBbsHTwH"><b>Discord</b></a>
</p>

<div align="center">

[![License](https://img.shields.io/github/license/mufeedvh/code2prompt.svg?style=flat-square)](https://github.com/mufeedvh/code2prompt/blob/master/LICENSE)
[![Crates.io](https://img.shields.io/crates/v/code2prompt.svg?style=flat-square)](https://crates.io/crates/code2prompt)
[![PyPI](https://img.shields.io/pypi/v/code2prompt-rs?style=flat-square&logo=pypi&logoColor=white)](https://pypi.org/project/code2prompt-rs/)
[![CI](https://github.com/mufeedvh/code2prompt/actions/workflows/ci.yml/badge.svg?style=flat-square)](https://github.com/mufeedvh/code2prompt/actions)
[![Discord](https://img.shields.io/discord/1342336677905039451?style=flat-square&logo=discord&logoColor=white)](https://discord.com/invite/ZZyBbsHTwH)
[![Docs.rs](https://docs.rs/code2prompt-core/badge.svg?style=flat-square)](https://docs.rs/code2prompt-core)
[![Crates.io Downloads](https://img.shields.io/crates/d/code2prompt.svg?style=flat-square)](https://crates.io/crates/code2prompt)
[![GitHub Stars](https://img.shields.io/github/stars/mufeedvh/code2prompt?style=social)](https://github.com/mufeedvh/code2prompt)

</div>

---

<h1 align="center">
  <a href="https://code2prompt.dev"><img src="https://github.com/mufeedvh/code2prompt/blob/main/.assets/demo.gif?raw=true" alt="code2prompt demo"></a>
</h1>

![Flow Diagram](https://github.com/mufeedvh/code2prompt/blob/main/.assets/flow_diagram.png?raw=true)

**Code2Prompt** is a powerful context engineering tool designed to ingest codebases and format them for Large Language Models. Whether you are manually copying context for ChatGPT, building AI agents via Python, or running a MCP server, Code2Prompt streamlines the context preparation process.

## ⚡ Quick Install

### Cargo

```bash
cargo install code2prompt 
```

To enable optional Wayland support (e.g., for clipboard integration on Wayland-based systems), use the `wayland` feature flag:

```bash
cargo install --features wayland code2prompt
```

### Homebrew

```bash
brew install code2prompt
```

### SDK with pip 🐍

```bash
pip install code2prompt-rs
```

## 🚀 Quick Start

Once installed, generating a prompt from your codebase is as simple as pointing the tool to your directory.

**Basic Usage**: Generate a prompt from the current directory and copy it to the clipboard.

```sh
code2prompt .
```

**Save to file**:

```sh
code2prompt path/to/project --output-file prompt.txt
```

## 🌐 Ecosystem

Code2Prompt is more than just a CLI tool. It is a complete ecosystem for codebase context.

| 🧱 Core Library <br><img src="https://img.shields.io/badge/Rust-FF6700?style=for-the-badge&logo=rust&logoColor=white" alt="Rust Core Badge"/>| 💻 CLI Tool <br><img src="https://img.shields.io/badge/Terminal-2C3E50?style=for-the-badge&logo=gnu-bash&logoColor=white" alt="CLI Badge"/> | 🐍 Python SDK <br><img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python SDK Badge"/> | 🤖 MCP Server <img src="https://img.shields.io/badge/Agentic%20Flow-7E57C2?style=for-the-badge&logo=server&logoColor=white" alt="MCP Server Badge"/> |
| :---: | :---: | :---: | :---: |
| The internal, high-speed library responsible for secure file traversal, respecting `.gitignore` rules, and structuring Git metadata. | Designed for humans, featuring both a minimal CLI and an interactive TUI. Generate formatted prompts, track token usage, and outputs the result to your clipboard or stdout. | Provides fast Python bindings to the Rust Core. Ideal for AI Agents, automation scripts, or deep integration into RAG pipelines. Available on PyPI. | Run Code2Prompt as a local service, enabling agentic applications to read your local codebase efficiently without bloating your context window. |

## 📚 Documentation

Check our online [documentation](https://code2prompt.dev/docs/welcome/) for detailed instructions

## ✨ Features

Code2Prompt transforms your entire codebase into a well-structured prompt for large language models. Key features include:

- **Terminal User Interface (TUI)**: Interactive terminal interface for configuring and generating prompts
- **Smart Filtering**: Include/exclude files using glob patterns and respect `.gitignore` rules
- **Flexible Templating**: Customize prompts with Handlebars templates for different use cases
- **Automatic Code Processing**: Convert codebases of any size into readable, formatted prompts
- **Token Tracking**: Track token usage to stay within LLM context limits
- **Smart File Reading**: Simplify reading various file formats for LLMs (CSV, Notebooks, JSONL, etc.)
- **Git Integration**: Include diffs, logs, and branch comparisons in your prompts
- **Blazing Fast**: Built in Rust for high performance and low resource usage

Stop manually copying files and formatting code for LLMs. Code2Prompt handles the tedious work so you can focus on getting insights and solutions from AI models.

## Alternative Installation

Refer to the [documentation](https://code2prompt.dev/docs/how_to/install/) for detailed installation instructions.

### Binary releases

Download the latest binary for your OS from [Releases](https://github.com/mufeedvh/code2prompt/releases).

### Source build

Requires:

- [Git](https://git-scm.org/downloads), [Rust](https://rust-lang.org/tools/install) and `Cargo`.

```sh
git clone https://github.com/mufeedvh/code2prompt.git
cd code2prompt/
cargo install --path crates/code2prompt
```

## ⭐ Star Gazing

[![Star History Chart](https://api.star-history.com/svg?repos=mufeedvh/code2prompt&type=Date)](https://star-history.com/#mufeedvh/code2prompt&Date)

## 📜 License

Licensed under the MIT License, see <a href="https://github.com/mufeedvh/code2prompt/blob/master/LICENSE">LICENSE</a> for more information.

## Liked the project?

If you liked the project and found it useful, please give it a :star: !

## 👥 Contribution

Ways to contribute:

- Suggest a feature
- Report a bug
- Fix something and open a pull request
- Help me document the code
- Spread the word


================================================
FILE: README_ES.md
================================================
# code2prompt

[![crates.io](https://img.shields.io/crates/v/code2prompt.svg)](https://crates.io/crates/code2prompt)
[![LICENSE](https://img.shields.io/github/license/mufeedvh/code2prompt.svg#cache1)](https://github.com/mufeedvh/code2prompt/blob/master/LICENSE)

<h1 align="center">
  <a href="https://github.com/mufeedvh/code2prompt"><img src=".assets/code2prompt-screenshot.png" alt="code2prompt"></a>
</h1>

`code2prompt` es una herramienta de línea de comandos (CLI) que convierte tu base de código en un único prompt para LLM, incluyendo un árbol de archivos fuente, plantillas de prompts y conteo de tokens.

## Tabla de Contenidos

- [Características](#features)
- [Instalación](#installation)
- [Uso](#usage)
- [Plantillas](#templates)
- [Variables Definidas por el Usuario](#user-defined-variables)
- [Tokenizadores](#tokenizers)
- [Contribución](#contribution)
- [Licencia](#license)
- [Apoya al Autor](#support-the-author)

## Características

Puedes ejecutar esta herramienta en un directorio completo, y generará un prompt bien formateado en Markdown que detalla la estructura del árbol de archivos fuente y todo el código. Luego puedes cargar este documento en modelos como GPT o Claude con ventanas de contexto amplias y pedirles que:

- Generen prompts para LLM rápidamente a partir de bases de código de cualquier tamaño.
- Personalicen la generación de prompts usando plantillas de Handlebars (ver la [plantilla predeterminada](src/default_template.hbs))
- Respete los archivos `.gitignore`.
- Filtren y excluyan archivos utilizando patrones glob.
- Muestren el conteo de tokens del prompt generado (Ver [Tokenizadores](#tokenizers) para más detalles).
- Incluyan opcionalmente salidas de `git diff` (archivos en estado staged) en el prompt generado.
- Copien automáticamente el prompt generado al portapapeles.
- Guarden el prompt generado en un archivo de salida.
- Excluyan archivos y carpetas por nombre o ruta.
- Añadan números de línea a los bloques de código fuente.

Puedes personalizar las plantillas de prompts para lograr cualquier caso de uso deseado. Básicamente, recorre una base de código y crea un prompt con todos los archivos fuente combinados. En resumen, automatiza la tarea de copiar y formatear múltiples archivos fuente en un único prompt y te informa cuántos tokens consume.

## Instalación

### Lanzamiento de binarios
Descarga el binario más reciente para tu sistema operativo desde [Releases](https://github.com/mufeedvh/code2prompt/releases).

### Construcción desde código fuente
Requisitos:

- [Git](https://git-scm.org/downloads), [Rust](https://rust-lang.org/tools/install) y Cargo.

```sh
git clone https://github.com/mufeedvh/code2prompt.git
cd code2prompt/
cargo build --release
```

## cargo

```bash
# Cargo
$ cargo install code2prompt

# Homebrew
$ brew install code2prompt
```

Para versiones no publicadas:

```sh
cargo install --git https://github.com/mufeedvh/code2prompt
```

### AUR
`code2prompt` está disponible en [`AUR`](https://aur.archlinux.org/packages?O=0&K=code2prompt).  Instálalo usando cualquier gestor AUR.

```sh
paru/yay -S code2prompt
```

### Nix
Si utilizas Nix, puedes instalarlo con `nix-env` o `profile`:

```sh
# Sin flakes:
nix-env -iA nixpkgs.code2prompt
# Con flakes:
nix profile install nixpkgs#code2prompt
```

## Uso

Genera un prompt desde un directorio de código:

```sh
code2prompt path/to/codebase
```

Usa un archivo de plantilla Handlebars personalizado:

```sh
code2prompt path/to/codebase -t path/to/template.hbs
```

Filtrar archivos usando patrones glob:

```sh
code2prompt path/to/codebase --include="*.rs,*.toml"
```

Excluir archivos usando patrones glob:

```sh
code2prompt path/to/codebase --exclude="*.txt,*.md"
```

Excluir archivos/carpetas del árbol de origen basándose en patrones de exclusión:

```sh
code2prompt path/to/codebase --exclude="*.npy,*.wav" --exclude-from-tree
```

Mostrar el conteo de tokens del prompt generado:

```sh
code2prompt path/to/codebase --tokens
```

Especificar un tokenizador para el conteo de tokens:

```sh
code2prompt path/to/codebase --tokens --encoding=p50k
```

Tokenizadores soportados: `cl100k`, `p50k`, `p50k_edit`, `r50k_bas`.
> [!NOTE]  
> Ver [Tokenizadores](#tokenizers) para más detalles.

Guardar el prompt generado en un archivo de salida:

```sh
code2prompt path/to/codebase --output=output.txt
```

Imprimir salida como JSON:

```sh
code2prompt path/to/codebase --json
```

La salida JSON tendrá la siguiente estructura:

```json
{
  "prompt": "<Generated Prompt>", 
  "directory_name": "codebase",
  "token_count": 1234,
  "model_info": "Modelos de ChatGPT, text-embedding-ada-002",
  "files": []
}
```

Generar un mensaje de commit de Git (para archivos en estado staged):

```sh
code2prompt path/to/codebase --diff -t templates/write-git-commit.hbs
```

Generar una Pull Request comparando ramas (para archivos en estado staged):

```sh
code2prompt path/to/codebase --git-diff-branch 'main, development' --git-log-branch 'main, development' -t templates/write-github-pull-request.hbs
```

Añadir números de línea a los bloques de código fuente:

```sh
code2prompt path/to/codebase --line-number
```

Desactivar el envoltorio de código dentro de bloques de código markdown:

```sh
code2prompt path/to/codebase --no-codeblock
```

- Reescribir el código a otro idioma.
- Encontrar errores/vulnerabilidades de seguridad.
- Documentar el código.
- Implementar nuevas características.

> Inicialmente escribí esto para uso personal para utilizar la ventana de contexto de 200K de Claude 3.0 y ha resultado ser bastante útil, ¡así que decidí hacerlo de código abierto!

## Plantillas

`code2prompt` viene con un conjunto de plantillas integradas para casos de uso comunes. Puedes encontrarlas en el directorio [`templates`](templates).

### [`document-the-code.hbs`](templates/document-the-code.hbs)

Usa esta plantilla para generar prompts para documentar el código. Añadirá comentarios de documentación a todas las funciones, métodos, clases y módulos públicos en la base de código.

### [`find-security-vulnerabilities.hbs`](templates/find-security-vulnerabilities.hbs)

Usa esta plantilla para generar prompts para encontrar posibles vulnerabilidades de seguridad en la base de código. Buscará problemas de seguridad comunes y proporcionará recomendaciones sobre cómo solucionarlos o mitigarlos.

### [`clean-up-code.hbs`](templates/clean-up-code.hbs)

Usa esta plantilla para generar prompts para limpiar y mejorar la calidad del código. Buscará oportunidades para mejorar la legibilidad, adherencia a las mejores prácticas, eficiencia, manejo de errores, y más.

### [`fix-bugs.hbs`](templates/fix-bugs.hbs)

Usa esta plantilla para generar prompts para corregir errores en la base de código. Ayudará a diagnosticar problemas, proporcionar sugerencias de corrección y actualizar el código con las correcciones propuestas.

### [`write-github-pull-request.hbs`](templates/write-github-pull-request.hbs)

Usa esta plantilla para crear una descripción de Pull Request de GitHub en markdown comparando el git diff y el git log de dos ramas.

### [`write-github-readme.hbs`](templates/write-github-readme.hbs)

Usa esta plantilla para generar un archivo README de alta calidad para el proyecto, adecuado para alojar en GitHub. Analizará la base de código para entender su propósito y funcionalidad, y generará el contenido del README en formato Markdown.

### [`write-git-commit.hbs`](templates/write-git-commit.hbs)

Usa esta plantilla para generar commits de git a partir de los archivos en estado staged en tu directorio git. Analizará la base de código para entender su propósito y funcionalidad, y generará el contenido del mensaje de commit de git en formato Markdown.

### [`improve-performance.hbs`](templates/improve-performance.hbs)

Usa esta plantilla para generar prompts para mejorar el rendimiento de la base de código. Buscará oportunidades de optimización, proporcionará sugerencias específicas y actualizará el código con los cambios.

Puedes usar estas plantillas pasando el flag `-t` seguido de la ruta al archivo de plantilla. Por ejemplo:

```sh
code2prompt path/to/codebase -t templates/document-the-code.hbs
```

## Variables Definidas por el Usuario

`code2prompt` soporta el uso de variables definidas por el usuario en las plantillas de Handlebars. Cualquier variable en la plantilla que no sea parte del contexto predeterminado (`absolute_code_path`, `source_tree`, `files`) será tratada como una variable definida por el usuario.

Durante la generación del prompt, `code2prompt` solicitará al usuario que ingrese valores para estas variables definidas por el usuario. Esto permite una mayor personalización de los prompts generados basados en la entrada del usuario.

Por ejemplo, si tu plantilla incluye `{{challenge_name}}` y `{{challenge_description}}`, se te pedirá que ingreses valores para estas variables al ejecutar `code2prompt`.

Esta característica permite crear plantillas reutilizables que pueden adaptarse a diferentes escenarios basados en la información proporcionada por el usuario.

## Tokenizadores

La tokenización se implementa usando [`tiktoken-rs`](https://github.com/zurawiki/tiktoken-rs). `tiktoken` soporta estas codificaciones utilizadas por los modelos de OpenAI:

| Nombre de codificación  | Modelos de OpenAI                                                          |
| ----------------------- | ------------------------------------------------------------------------- |
| `cl100k_base`           | Modelos de ChatGPT, `text-embedding-ada-002`                              |
| `p50k_base`             | Modelos de código, `text-davinci-002`, `text-davinci-003`                 |
| `p50k_edit`             | Usar para modelos de edición como `text-davinci-edit-001`, `code-davinci-edit-001` |
| `r50k_base` (o `gpt2`)  | Modelos GPT-3 como `davinci`                                              |
| `o200k_base`            | Modelos GPT-4o                                                            |

Para más contexto sobre los diferentes tokenizadores, ver el [OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/66b988407d8d13cad5060a881dc8c892141f2d5c/examples/How_to_count_tokens_with_tiktoken.ipynb)

## ¿Cómo es útil?

`code2prompt` facilita la generación de prompts para LLMs desde tu base de código. Recorre el directorio, construye una estructura de árbol y recopila información sobre cada archivo. Puedes personalizar la generación de prompts usando plantillas de Handlebars. El prompt generado se copia automáticamente en tu portapapeles y también se puede guardar en un archivo de salida. `code2prompt` ayuda a agilizar el proceso de creación de prompts para análisis de código, generación y otras tareas.

## Contribución

Formas de contribuir:

- Sugerir una característica
- Reportar un error  
- Arreglar algo y abrir un pull request
- Ayudarme a documentar el código
- Difundir la palabra

## Licencia

Licenciado bajo la Licencia MIT, ver <a href="https://github.com/mufeedvh/code2prompt/blob/master/LICENSE">LICENSE</a> para más información.

## ¿Te gustó el proyecto?

Si te gustó el proyecto y lo encontraste útil, por favor dale una :star: y considera apoyar a los autores!


================================================
FILE: crates/code2prompt/Cargo.toml
================================================
[package]
name = "code2prompt"
version = "4.2.0"
edition = "2024"
description = "Command-line interface for code2prompt"
license = "MIT"
repository = "https://github.com/mufeedvh/code2prompt"
readme = "../../README.md"

[features]
wayland = ["arboard/wayland-data-control"]

[dependencies]
code2prompt_core = { path = "../code2prompt-core", version = "4.2.0" }
clap = { workspace = true }
env_logger = { workspace = true }
arboard = { workspace = true }
anyhow = { workspace = true }
colored = { workspace = true }
indicatif = { workspace = true }
log = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
toml = { workspace = true }
inquire = { workspace = true }
terminal_size = { workspace = true }
lscolors = { workspace = true }
ansi_term = { workspace = true }
ratatui = { workspace = true }
crossterm = { workspace = true }
tokio = { workspace = true }
tui-tree-widget = { workspace = true }
tui-textarea = { workspace = true }
walkdir = { workspace = true }
unicode-width = { workspace = true }
bracoxide = { workspace = true }
git2 = { workspace = true }
chrono = { workspace = true }
dirs = { workspace = true }
regex = { workspace = true }
handlebars = { workspace = true }
ignore = { workspace = true }
tiktoken-rs = { workspace = true }

[target.'cfg(windows)'.dependencies]
winapi = { workspace = true }

[[bin]]
name = "code2prompt"
path = "src/main.rs"

[dev-dependencies]
tempfile = "3.24"
assert_cmd = "2.1.1"
predicates = "3.1"
env_logger = "0.11.3"
rstest = "0.26"


================================================
FILE: crates/code2prompt/src/args.rs
================================================
//! Command-line argument parsing and validation.
//!
//! This module defines the CLI structure using clap for parsing command-line arguments
//! and options for the code2prompt tool. It supports both TUI and CLI modes with
//! comprehensive configuration options for file selection, output formatting,
//! tokenization, and git integration.
use anyhow::{Result, anyhow};
use clap::{Parser, builder::ValueParser};
use code2prompt_core::{
    sort::FileSortMethod, template::OutputFormat, tokenizer::TokenFormat, tokenizer::TokenizerType,
};
use serde::de::DeserializeOwned;
use std::path::PathBuf;

// ~~~ CLI Arguments ~~~
#[derive(Parser, Debug)]
#[clap(
    name = env!("CARGO_PKG_NAME"),
    version = env!("CARGO_PKG_VERSION"),
    author = env!("CARGO_PKG_AUTHORS")
)]
#[command(arg_required_else_help = true)]
pub struct Cli {
    /// Path to the codebase directory
    #[arg(value_name = "PATH_TO_ANALYZE", default_value = ".")]
    pub path: PathBuf,

    /// Optional output file (use "-" for stdout)
    #[arg(short = 'O', long = "output-file", value_name = "FILE")]
    pub output_file: Option<String>,

    /// Launch the Terminal User Interface
    #[clap(long)]
    pub tui: bool,

    /// Patterns to include
    #[clap(short = 'i', long = "include")]
    pub include: Vec<String>,

    /// Patterns to exclude
    #[clap(short = 'e', long = "exclude")]
    pub exclude: Vec<String>,

    /// Output format
    #[clap(
        short = 'F',
        long = "output-format",
        value_name = "markdown, json, xml",
        value_parser = ValueParser::new(parse_serde::<OutputFormat>)
    )]
    pub output_format: Option<OutputFormat>,

    /// Optional Path to a custom Handlebars template
    #[clap(short, long, value_name = "TEMPLATE")]
    pub template: Option<PathBuf>,

    /// List the full directory tree
    #[clap(long)]
    pub full_directory_tree: bool,

    /// Token encoding to use for token count
    #[clap(
        long,
        value_name = "cl100k, p50k, p50k_edit, r50k",
        value_parser = ValueParser::new(parse_serde::<TokenizerType>),
    )]
    pub encoding: Option<TokenizerType>,

    /// Display the token count of the generated prompt. Accepts a format: "raw" (machine parsable) or "format" (human readable)
    #[clap(
        long,
        value_name = "raw,format",
        value_parser = ValueParser::new(parse_serde::<TokenFormat>),
    )]
    pub token_format: Option<TokenFormat>,

    /// Include git diff
    #[clap(short, long)]
    pub diff: bool,

    /// Generate git diff between two branches
    #[clap(long, value_name = "BRANCHES", num_args = 2, value_delimiter = ',')]
    pub git_diff_branch: Option<Vec<String>>,

    /// Retrieve git log between two branches
    #[clap(long, value_name = "BRANCHES", num_args = 2, value_delimiter = ',')]
    pub git_log_branch: Option<Vec<String>>,

    /// Add line numbers to the source code
    #[clap(short, long)]
    pub line_numbers: bool,

    /// If true, paths in the output will be absolute instead of relative.
    #[clap(long)]
    pub absolute_paths: bool,

    /// Follow symlinks
    #[clap(short = 'L', long)]
    pub follow_symlinks: bool,

    /// Include hidden directories and files
    #[clap(long)]
    pub hidden: bool,

    /// Disable wrapping code inside markdown code blocks
    #[clap(long)]
    pub no_codeblock: bool,

    /// Copy output to clipboard
    #[clap(short = 'c', long)]
    pub clipboard: bool,

    /// Optional Disable copying to clipboard (deprecated, use default behavior)
    #[clap(long, hide = true)]
    pub no_clipboard: bool,

    /// Skip .gitignore rules
    #[clap(long)]
    pub no_ignore: bool,

    /// Sort order for files
    #[clap(
        long,
        value_name = "name_asc, name_desc, date_asc, date_desc",
        value_parser = ValueParser::new(parse_serde::<FileSortMethod>),
    )]
    pub sort: Option<FileSortMethod>,

    /// Suppress progress and success messages
    #[clap(short = 'q', long)]
    pub quiet: bool,

    /// Display a visual token map of files (similar to disk usage tools)
    #[clap(long)]
    pub token_map: bool,

    /// Maximum number of lines to display in token map (default: terminal height - 10)
    #[clap(long, value_name = "NUMBER")]
    pub token_map_lines: Option<usize>,

    /// Minimum percentage of tokens to display in token map (default: 0.1%)
    #[clap(long, value_name = "PERCENT")]
    pub token_map_min_percent: Option<f64>,

    /// Start with all files deselected
    #[clap(long)]
    pub deselected: bool,

    #[arg(long, hide = true)]
    pub clipboard_daemon: bool,
}

/// Helper function to parse serde deserializable enum from string inputs.
fn parse_serde<T: DeserializeOwned>(s: &str) -> Result<T> {
    serde_json::from_value(serde_json::Value::String(s.to_string()))
        .map_err(|e| anyhow!("Failed to parse value: {}", e))
}


================================================
FILE: crates/code2prompt/src/clipboard.rs
================================================
use anyhow::{Context, Result};

#[cfg(not(target_os = "linux"))]
/// Copies the provided text to the system clipboard.
///
/// This is a simple, one-shot copy operation suitable for non-Linux platforms
/// or scenarios where maintaining the clipboard content is not required.
///
/// # Arguments
///
/// * `text` - The text content to be copied.
///
/// # Returns
///
/// * `Result<()>` - Returns Ok on success, or an error if the clipboard could not be accessed.
pub fn copy_text_to_clipboard(text: &str) -> Result<()> {
    use arboard::Clipboard;
    match Clipboard::new() {
        Ok(mut clipboard) => {
            clipboard
                .set_text(text.to_string())
                .context("Failed to copy to clipboard")?;
            Ok(())
        }
        Err(e) => Err(anyhow::anyhow!("Failed to initialize clipboard: {}", e)),
    }
}

#[cfg(target_os = "linux")]
/// Entry point for the clipboard daemon process on Linux.
///
/// This function reads clipboard content from its standard input, sets it as the system clipboard,
/// and then waits to serve clipboard requests. This ensures that the clipboard content remains available
/// even after the main application exits. The daemon will exit automatically once the clipboard is overwritten.
///
/// # Returns
///
/// * `Result<()>` - Returns Ok on success or an error if clipboard operations fail.
pub fn serve_clipboard_daemon() -> Result<()> {
    use arboard::{Clipboard, LinuxClipboardKind, SetExtLinux};
    use std::io::Read;

    // Read content from stdin
    let mut content_from_stdin = String::new();
    std::io::stdin()
        .read_to_string(&mut content_from_stdin)
        .context("Failed to read from stdin")?;

    // Initialize the clipboard
    let mut clipboard = Clipboard::new().context("Failed to initialize clipboard")?;

    // Explicitly set the clipboard selection to Clipboard (not Primary)
    clipboard
        .set()
        .clipboard(LinuxClipboardKind::Clipboard)
        .wait()
        .text(content_from_stdin)
        .context("Failed to set clipboard content")?;
    Ok(())
}

#[cfg(target_os = "linux")]
/// Spawns a daemon process to maintain clipboard content on Linux.
///
/// On Linux (Wayland/X11), the clipboard content is owned by the process that defined it.
/// If the main application exits, the clipboard would be cleared.
/// To avoid this, this function spawns a new process that will run in the background
/// (daemon) and maintain the clipboard content until it is overwritten by a new copy.
///
/// # Arguments
///
/// * `text` - The text to be served by the daemon process.
///
/// # Returns
///
/// * `Result<()>` - Returns Ok if the daemon process was spawned and the content was sent successfully,
///   or an error if the process could not be launched or written to.
pub fn spawn_clipboard_daemon(content: &str) -> Result<()> {
    use std::process::{Command, Stdio};
    use log::info;

    // ~~~ Setting up the command to run the daemon ~~~
    let current_exe: std::path::PathBuf =
        std::env::current_exe().context("Failed to get current executable path")?;
    let mut args: Vec<String> = std::env::args().collect();
    args.push("--clipboard-daemon".to_string());

    // ~~~ Spawn the clipboard daemon process ~~~
    let mut child = Command::new(current_exe)
        .args(&args[1..])
        .stdin(Stdio::piped())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn()
        .context("Failed to launch clipboard daemon process")?;

    // ~~~ Write the content to the daemon's standard input ~~~
    use std::io::Write;
    let mut stdin = child
        .stdin
        .take()
        .context("Failed to acquire stdin pipe for clipboard daemon process")?;
    stdin
        .write_all(content.as_bytes())
        .context("Failed to write content to clipboard daemon process")?;
    info!("Clipboard daemon launched successfully");

    Ok(())
}

/// Copy text to clipboard
pub fn copy_to_clipboard(text: &str) -> Result<()> {
    #[cfg(target_os = "linux")]
    {
        spawn_clipboard_daemon(text)
    }
    #[cfg(not(target_os = "linux"))]
    {
        copy_text_to_clipboard(text)
    }
}


================================================
FILE: crates/code2prompt/src/config.rs
================================================
//! Configuration parsing and session creation utilities.
//!
//! This module handles the conversion of command-line arguments into
//! Code2PromptSession instances, consolidating all configuration parsing
//! logic in one place for better maintainability and separation of concerns.

use anyhow::{Context, Result};
use code2prompt_core::{
    configuration::Code2PromptConfig,
    session::Code2PromptSession,
    sort::FileSortMethod,
    template::{OutputFormat, extract_undefined_variables},
    tokenizer::TokenizerType,
};
use inquire::Text;
use log::error;
use std::path::PathBuf;

use crate::{args::Cli, config_loader::ConfigSource};

/// Unified session builder that merges configuration layering in one place
/// - base: Some(&ConfigSource) to use loaded config as defaults; None to use CLI defaults
/// - args: CLI arguments
/// - tui_mode: whether running in TUI mode (enables token map by default)
pub fn build_session(
    base: Option<&ConfigSource>,
    args: &Cli,
    tui_mode: bool,
) -> Result<Code2PromptSession> {
    let mut configuration = Code2PromptConfig::builder();

    let cfg = base.map(|b| &b.config);

    // Path: config path takes precedence if provided, otherwise CLI path
    if let Some(c) = cfg {
        if let Some(path) = &c.path {
            configuration.path(PathBuf::from(path));
        } else {
            configuration.path(args.path.clone());
        }
    } else {
        configuration.path(args.path.clone());
    }

    // Include/Exclude patterns:
    // If CLI provides any patterns, they override config patterns completely (to avoid conflicts)
    let use_cli_patterns = !args.include.is_empty() || !args.exclude.is_empty();
    let (include_patterns, exclude_patterns) = if use_cli_patterns {
        (
            expand_comma_separated_patterns(&args.include),
            expand_comma_separated_patterns(&args.exclude),
        )
    } else if let Some(c) = cfg {
        (c.include_patterns.clone(), c.exclude_patterns.clone())
    } else {
        (
            expand_comma_separated_patterns(&args.include),
            expand_comma_separated_patterns(&args.exclude),
        )
    };
    configuration
        .include_patterns(include_patterns)
        .exclude_patterns(exclude_patterns);

    // Display options: CLI overrides config (logical-or semantics for booleans)
    let cfg_line_numbers = cfg.map(|c| c.line_numbers).unwrap_or(false);
    let cfg_absolute = cfg.map(|c| c.absolute_path).unwrap_or(false);
    let cfg_full_tree = cfg.map(|c| c.full_directory_tree).unwrap_or(false);
    configuration
        .line_numbers(args.line_numbers || cfg_line_numbers)
        .absolute_path(args.absolute_paths || cfg_absolute)
        .full_directory_tree(args.full_directory_tree || cfg_full_tree);

    // Output format: CLI overrides config
    let output_format = if let Some(output_format_str) = args.output_format {
        output_format_str
    } else if let Some(c) = cfg {
        c.output_format.unwrap_or(OutputFormat::Markdown)
    } else {
        OutputFormat::Markdown
    };
    configuration.output_format(output_format);

    // Sort method: CLI overrides config
    let sort_method = if let Some(sort_str) = args.sort {
        sort_str
    } else if let Some(c) = cfg {
        c.sort_method.unwrap_or(FileSortMethod::NameAsc)
    } else {
        FileSortMethod::NameAsc
    };
    configuration.sort_method(sort_method);

    // Tokenizer settings: CLI overrides config
    let tokenizer_type = if let Some(encoding) = args.encoding {
        encoding
    } else if let Some(c) = cfg {
        c.encoding.unwrap_or(TokenizerType::Cl100kBase)
    } else {
        TokenizerType::Cl100kBase
    };

    // Token format: CLI overrides config
    let token_format = if let Some(format) = args.token_format {
        format
    } else if let Some(c) = cfg {
        c.token_format
            .unwrap_or(code2prompt_core::tokenizer::TokenFormat::Format)
    } else {
        code2prompt_core::tokenizer::TokenFormat::Format
    };

    configuration
        .encoding(tokenizer_type)
        .token_format(token_format);

    // Template: CLI overrides config
    let (template_str, template_name) = if args.template.is_some() {
        parse_template(&args.template).map_err(|e| {
            error!("Failed to parse template: {}", e);
            e
        })?
    } else if let Some(c) = cfg {
        (
            c.template_str.clone().unwrap_or_default(),
            c.template_name
                .clone()
                .unwrap_or_else(|| "default".to_string()),
        )
    } else {
        ("".to_string(), "default".to_string())
    };

    configuration
        .template_str(template_str)
        .template_name(template_name);

    // Git options: CLI overrides config
    let diff_branches = parse_branch_argument(&args.git_diff_branch).or_else(|| {
        cfg.and_then(|c| {
            c.diff_branches.as_ref().and_then(|branches| {
                if branches.len() == 2 {
                    Some((branches[0].clone(), branches[1].clone()))
                } else {
                    None
                }
            })
        })
    });

    let log_branches = parse_branch_argument(&args.git_log_branch).or_else(|| {
        cfg.and_then(|c| {
            c.log_branches.as_ref().and_then(|branches| {
                if branches.len() == 2 {
                    Some((branches[0].clone(), branches[1].clone()))
                } else {
                    None
                }
            })
        })
    });

    let cfg_diff_enabled = cfg.map(|c| c.diff_enabled).unwrap_or(false);
    let cfg_token_map_enabled = cfg.map(|c| c.token_map_enabled).unwrap_or(false);
    let cfg_deselected = cfg.map(|c| c.deselected).unwrap_or(false);

    configuration
        .diff_enabled(args.diff || cfg_diff_enabled)
        .diff_branches(diff_branches)
        .log_branches(log_branches)
        .no_ignore(args.no_ignore)
        .hidden(args.hidden)
        .no_codeblock(args.no_codeblock)
        .follow_symlinks(args.follow_symlinks)
        .token_map_enabled(args.token_map || cfg_token_map_enabled || tui_mode)
        .deselected(args.deselected || cfg_deselected);

    // User variables from config (if available)
    if let Some(c) = cfg {
        configuration.user_variables(c.user_variables.clone());
    }

    let session = Code2PromptSession::new(configuration.build()?);
    Ok(session)
}

/// Parses the branch argument from command line options.
///
/// Takes an optional vector of strings and converts it to a tuple of two branch names
/// if exactly two branches are provided.
///
/// # Arguments
///
/// * `branch_arg` - An optional vector containing branch names
///
/// # Returns
///
/// * `Option<(String, String)>` - A tuple of (from_branch, to_branch) if two branches were provided, None otherwise
pub fn parse_branch_argument(branch_arg: &Option<Vec<String>>) -> Option<(String, String)> {
    match branch_arg {
        Some(branches) if branches.len() == 2 => Some((branches[0].clone(), branches[1].clone())),
        _ => None,
    }
}

/// Loads a template from a file path or returns default values.
///
/// # Arguments
///
/// * `template_arg` - An optional path to a template file
///
/// # Returns
///
/// * `Result<(String, String)>` - A tuple containing (template_content, template_name)
///   where template_name is "custom" for user-provided templates or "default" otherwise
pub fn parse_template(template_arg: &Option<PathBuf>) -> Result<(String, String)> {
    match template_arg {
        Some(path) => {
            let template_str =
                std::fs::read_to_string(path).context("Failed to load custom template file")?;
            Ok((template_str, "custom".to_string()))
        }
        None => Ok(("".to_string(), "default".to_string())),
    }
}

/// Handles user-defined variables in the template and adds them to the session.
///
/// This function extracts undefined variables from the template and prompts
/// the user to provide values for them through interactive input.
///
/// # Arguments
///
/// * `session` - The Code2PromptSession to modify
/// * `template_content` - The template content string to analyze
///
/// # Returns
///
/// * `Result<()>` - An empty result indicating success or an error
pub fn handle_undefined_variables(
    session: &mut Code2PromptSession,
    template_content: &str,
) -> Result<()> {
    let undefined_variables = extract_undefined_variables(template_content);

    for var in undefined_variables.iter() {
        // Check if variable is already defined in user_variables
        if !session.config.user_variables.contains_key(var) {
            let prompt = format!("Enter value for '{}': ", var);
            let answer = Text::new(&prompt)
                .with_help_message("Fill user defined variable in template")
                .prompt()
                .unwrap_or_default();
            session.config.user_variables.insert(var.clone(), answer);
        }
    }

    Ok(())
}

/// Expands comma-separated patterns while preserving brace expansion patterns
///
/// This function handles the expansion of comma-separated include/exclude patterns
/// while being careful not to split patterns that contain brace expansion syntax.
///
/// # Arguments
///
/// * `patterns` - A vector of pattern strings that may contain comma-separated values
///
/// # Returns
///
/// * `Vec<String>` - A vector of individual patterns
fn expand_comma_separated_patterns(patterns: &[String]) -> Vec<String> {
    let mut expanded = Vec::new();

    for pattern in patterns {
        // If the pattern contains braces, don't split on commas (preserve brace expansion)
        if pattern.contains('{') && pattern.contains('}') {
            expanded.push(pattern.clone());
        } else {
            // Split on commas for regular patterns
            for part in pattern.split(',') {
                let trimmed = part.trim();
                if !trimmed.is_empty() {
                    expanded.push(trimmed.to_string());
                }
            }
        }
    }

    expanded
}


================================================
FILE: crates/code2prompt/src/config_loader.rs
================================================
//! Configuration file loading and management.
//!
//! This module handles loading TOML configuration files from multiple locations
//! with proper priority handling and informational messages.

use anyhow::{Context, Result};
use code2prompt_core::configuration::{OutputDestination, TomlConfig};
use colored::*;
use log::{debug, info};
use std::path::Path;

/// Configuration source information
#[derive(Debug, Clone)]
pub struct ConfigSource {
    pub config: TomlConfig,
}

/// Load configuration with proper priority handling
pub fn load_config(quiet: bool) -> Result<ConfigSource> {
    // Check for local config first (.c2pconfig in current directory)
    let local_config_path = std::env::current_dir()?.join(".c2pconfig");
    if local_config_path.exists() {
        match load_config_from_file(&local_config_path) {
            Ok(config) => {
                if !quiet {
                    eprintln!(
                        "{}{}{} Using config from: {}",
                        "[".bold().white(),
                        "i".bold().blue(),
                        "]".bold().white(),
                        local_config_path.display()
                    );
                }
                info!("Loaded local config from: {}", local_config_path.display());
                return Ok(ConfigSource { config });
            }
            Err(e) => {
                debug!("Failed to load local config: {}", e);
            }
        }
    }

    // Check for global config (~/.config/code2prompt/.c2pconfig)
    if let Some(config_dir) = dirs::config_dir() {
        let global_config_path = config_dir.join("code2prompt").join(".c2pconfig");
        if global_config_path.exists() {
            match load_config_from_file(&global_config_path) {
                Ok(config) => {
                    if !quiet {
                        eprintln!(
                            "{}{}{} Using config from: {}",
                            "[".bold().white(),
                            "i".bold().blue(),
                            "]".bold().white(),
                            global_config_path.display()
                        );
                    }
                    info!(
                        "Loaded global config from: {}",
                        global_config_path.display()
                    );
                    return Ok(ConfigSource { config });
                }
                Err(e) => {
                    debug!("Failed to load global config: {}", e);
                }
            }
        }
    }

    // Use default configuration
    if !quiet {
        eprintln!(
            "{}{}{} Using default configuration",
            "[".bold().white(),
            "i".bold().blue(),
            "]".bold().white(),
        );
    }
    info!("Using default configuration");

    Ok(ConfigSource {
        config: TomlConfig::default(),
    })
}

/// Load TOML configuration from a file
fn load_config_from_file(path: &Path) -> Result<TomlConfig> {
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("Failed to read config file: {}", path.display()))?;

    TomlConfig::from_toml_str(&content)
        .with_context(|| format!("Failed to parse TOML config file: {}", path.display()))
}

/// Get the default output destination from config
pub fn get_default_output_destination(config_source: &ConfigSource) -> OutputDestination {
    config_source.config.default_output.clone()
}


================================================
FILE: crates/code2prompt/src/main.rs
================================================
//! code2prompt is a command-line tool to generate an LLM prompt from a codebase directory.
//!
//! Authors: Olivier D'Ancona (@ODAncona), Mufeed VH (@mufeedvh)
mod args;
mod clipboard;
mod config;
mod config_loader;
mod model;
mod token_map;
mod tui;
mod utils;
mod view;
mod widgets;

use crate::utils::format_number;
use anyhow::{Context, Result};
use args::Cli;
use clap::Parser;
use code2prompt_core::template::write_to_file;
use colored::*;
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, error, info};
use std::io::Write;
use tui::run_tui;

#[tokio::main]
async fn main() -> Result<()> {
    env_logger::init();
    info! {"Args: {:?}", std::env::args().collect::<Vec<_>>()};

    let args: Cli = Cli::parse();

    // ~~~ Clipboard Daemon ~~~
    #[cfg(target_os = "linux")]
    {
        use clipboard::serve_clipboard_daemon;
        if args.clipboard_daemon {
            info! {"Serving clipboard daemon..."};
            serve_clipboard_daemon()?;
            info! {"Shutting down gracefully..."};
            return Ok(());
        }
    }

    // ~~~ TUI or CLI Mode ~~~
    if args.tui {
        // ~~~ Build Session for TUI ~~~
        let session = config::build_session(None, &args, args.tui).unwrap_or_else(|e| {
            error!("Failed to create session: {}", e);
            std::process::exit(1);
        });
        run_tui(session).await
    } else {
        run_cli_mode_with_args(args).await
    }
}

/// Run the CLI mode with parsed arguments
async fn run_cli_mode_with_args(args: Cli) -> Result<()> {
    use code2prompt_core::configuration::OutputDestination;
    use config_loader::{get_default_output_destination, load_config};

    let quiet_mode = args.quiet;

    // ~~~ Load Configuration ~~~
    let config_source = load_config(quiet_mode)?; // load config files first (local > global), then apply CLI args on top

    // ~~~ Build Session with config + CLI args ~~~
    let mut session = config::build_session(Some(&config_source), &args, false)?;

    // ~~~ Determine Output Behavior ~~~
    let default_output = get_default_output_destination(&config_source);

    // Determine final output destinations (Solution B: Unix-style behavior)
    let output_to_clipboard = if args.clipboard {
        // Explicit clipboard flag - ONLY clipboard, no stdout
        true
    } else if args.output_file.is_some() {
        // Output file specified, don't use clipboard unless explicitly requested
        false
    } else {
        // Use config default
        matches!(default_output, OutputDestination::Clipboard)
    };

    let output_to_stdout = if args.clipboard {
        false
    } else if let Some(ref output_file) = args.output_file {
        output_file == "-"
    } else {
        match default_output {
            OutputDestination::Stdout => true,
            OutputDestination::Clipboard => false,
            OutputDestination::File => false,
        }
    };

    // ~~~ Create Session ~~~
    let spinner = if !quiet_mode {
        Some(setup_spinner("Traversing directory and building tree..."))
    } else {
        None
    };

    // ~~~ Gather Repository Data ~~~
    session.load_codebase().map_err(|e| {
        if let Some(s) = spinner.as_ref() {
            s.finish_with_message("Failed!".red().to_string())
        }
        error!("Failed to build directory tree: \n{}", e);
        anyhow::anyhow!("Failed to build directory tree: {}", e)
    })?;
    if let Some(s) = spinner.as_ref() {
        s.set_message("Proceeding…")
    }

    // ~~~ Git Related ~~~
    // Git Diff
    if session.config.diff_enabled {
        if let Some(s) = spinner.as_ref() {
            s.set_message("Generating git diff...")
        }
        session.load_git_diff().unwrap_or_else(|e| {
            if let Some(s) = spinner.as_ref() {
                s.finish_with_message("Failed!".red().to_string())
            }
            error!("Failed to generate git diff: {}", e);
            std::process::exit(1);
        });
    }

    // Load Git diff between branches if provided
    if session.config.diff_branches.is_some() {
        if let Some(s) = spinner.as_ref() {
            s.set_message("Generating git diff between two branches...")
        }
        session
            .load_git_diff_between_branches()
            .unwrap_or_else(|e| {
                if let Some(s) = spinner.as_ref() {
                    s.finish_with_message("Failed!".red().to_string())
                }
                error!("Failed to generate git diff: {}", e);
                std::process::exit(1);
            });
    }

    // Load Git log between branches if provided
    if session.config.log_branches.is_some() {
        if let Some(ref s) = spinner {
            s.set_message("Generating git log between two branches...");
        }
        session.load_git_log_between_branches().unwrap_or_else(|e| {
            if let Some(ref s) = spinner {
                s.finish_with_message("Failed!".red().to_string());
            }
            error!("Failed to generate git log: {}", e);
            std::process::exit(1);
        });
    }

    // ~~~ Template ~~~

    // Handle undefined variables (modifies session.config.user_variables)
    let template_str_clone = session.config.template_str.clone();
    config::handle_undefined_variables(&mut session, &template_str_clone)?;

    // Data - now build after handling undefined variables
    let data = session.build_template_data();
    debug!(
        "Template Context: absolute_code_path={}, files_count={}, has_user_vars={}",
        data.absolute_code_path,
        data.files.map(|f| f.len()).unwrap_or(0),
        !session.config.user_variables.is_empty()
    );

    // Render
    let rendered = session.render_prompt(&data).unwrap_or_else(|e| {
        error!("Failed to render prompt: {}", e);
        std::process::exit(1);
    });

    if let Some(ref s) = spinner {
        s.finish_with_message("Codebase Traversal Done!".green().to_string());
    }

    // ~~~ Token Count ~~~
    let token_count = rendered.token_count;
    let formatted_token_count = format_number(token_count, &session.config.token_format);
    let model_info = rendered.model_info;

    if !quiet_mode {
        eprintln!(
            "{}{}{} Token count: {}, Model info: {}",
            "[".bold().white(),
            "i".bold().blue(),
            "]".bold().white(),
            formatted_token_count,
            model_info
        );
    }

    // ~~~ Token Map Display ~~~
    if args.token_map {
        use crate::token_map::{display_token_map, generate_token_map_with_limit};

        if let Some(files) = session.data.files.as_ref() {
            // Calculate total tokens from individual file counts
            let total_from_files: usize = files.iter().map(|f| f.token_count).sum();

            // Get max lines from command line or calculate from terminal height
            let max_lines = args.token_map_lines.unwrap_or_else(|| {
                terminal_size::terminal_size()
                    .map(|(_, terminal_size::Height(h))| {
                        let height = h as usize;
                        // Ensure minimum of 10 lines, subtract 10 for other output
                        if height > 20 { height - 10 } else { 10 }
                    })
                    .unwrap_or(20) // Default to 20 lines if terminal size detection fails
            });

            // Use the sum of individual file tokens for the map with line limit
            let entries = generate_token_map_with_limit(
                files,
                total_from_files,
                Some(max_lines),
                args.token_map_min_percent,
            );
            display_token_map(&entries, total_from_files);
        }
    }

    // ~~~ Output to Stdout ~~~
    if output_to_stdout {
        print!("{}", &rendered.prompt);
        std::io::stdout()
            .flush()
            .context("Failed to flush stdout")?;
    }

    // ~~~ Copy to Clipboard ~~~
    if output_to_clipboard {
        use crate::clipboard::copy_to_clipboard;
        match copy_to_clipboard(&rendered.prompt) {
            Ok(_) => {
                if !quiet_mode {
                    eprintln!(
                        "{}{}{} {}",
                        "[".bold().white(),
                        "✓".bold().green(),
                        "]".bold().white(),
                        "Copied to clipboard successfully.".green()
                    );
                }
            }
            Err(e) => {
                if !quiet_mode {
                    eprintln!(
                        "{}{}{} {}",
                        "[".bold().white(),
                        "!".bold().red(),
                        "]".bold().white(),
                        format!("Failed to copy to clipboard: {}", e).red()
                    );
                }
            }
        }
    }

    // ~~~ Output File ~~~
    if let Some(ref output_file) = args.output_file
        && output_file != "-"
    {
        output_prompt(
            Some(std::path::Path::new(output_file)),
            &rendered.prompt,
            quiet_mode,
        )?;
    }

    Ok(())
}

/// Sets up a progress spinner with a given message
///
/// # Arguments
///
/// * `message` - A message to display with the spinner
///
/// # Returns
///
/// * `ProgressBar` - The configured progress spinner
fn setup_spinner(message: &str) -> ProgressBar {
    let spinner = ProgressBar::new_spinner();
    spinner.enable_steady_tick(std::time::Duration::from_millis(220));
    let done_symbol = format!(
        "{}{}{}",
        "[".bold().white(),
        "✓".bold().green(),
        "]".bold().white()
    );
    spinner.set_style(
        ProgressStyle::default_spinner()
            .tick_strings(&[
                "▹▹▹▹▹",
                "▸▹▹▹▹",
                "▹▸▹▹▹",
                "▹▹▸▹▹",
                "▹▹▹▸▹",
                "▹▹▹▹▸",
                &done_symbol,
            ])
            .template("{spinner:.blue} {msg}")
            .unwrap(),
    );
    spinner.set_message(message.to_string());
    spinner
}

// ~~~ Output to file or stdout ~~~
fn output_prompt(
    effective_output: Option<&std::path::Path>,
    rendered: &str,
    quiet: bool,
) -> Result<()> {
    let output_path = match effective_output {
        Some(path) => path,
        None => return Ok(()), // nothing to do
    };

    let path_str = output_path.to_string_lossy();
    if path_str == "-" {
        // stdout
        print!("{}", rendered);
        std::io::stdout()
            .flush()
            .context("Failed to flush stdout")?;
    } else {
        // file
        write_to_file(&path_str, rendered)
            .context(format!("Failed to write to file: {}", path_str))?;

        if !quiet {
            eprintln!(
                "{}{}{} {}",
                "[".bold().white(),
                "✓".bold().green(),
                "]".bold().white(),
                format!("Prompt written to file: {}", path_str).green()
            );
        }
    }

    Ok(())
}


================================================
FILE: crates/code2prompt/src/model/commands.rs
================================================
//! Command system for handling side effects in the Model-View-Update architecture.
//!
//! This module implements the Cmd pattern from Elm/Redux, allowing the Model::update()
//! function to remain pure while still triggering side effects like async operations,
//! file I/O, and clipboard operations.

use std::collections::HashMap;

/// Commands represent side effects that should be executed after model updates.
/// This allows Model::update() to remain pure while still triggering necessary
/// side effects like async operations, file I/O, etc.
#[derive(Debug, Clone)]
pub enum Cmd {
    /// No command - pure state update only
    None,

    /// Run analysis in background
    RunAnalysis {
        template_content: String,
        user_variables: HashMap<String, String>,
    },

    /// Copy text to clipboard
    CopyToClipboard(String),

    /// Save text to file
    SaveToFile { filename: String, content: String },

    /// Save template to custom directory
    SaveTemplate { filename: String, content: String },

    /// Refresh file tree from session
    RefreshFileTree,
}


================================================
FILE: crates/code2prompt/src/model/mod.rs
================================================
//! Data structures and application state management for the TUI.
//!
//! This module contains the core data structures that represent the application state,
//! including the main Model struct, tab definitions, message types for event handling,
//! and all state management submodules. It serves as the central state container
//! for the terminal user interface.

pub mod commands;
pub mod prompt_output;
pub mod settings;
pub mod statistics;
pub mod template;

pub use commands::*;
pub use prompt_output::*;
pub use settings::*;
pub use statistics::*;
pub use template::*;

use crate::utils::directory_contains_selected_files;
use code2prompt_core::session::Code2PromptSession;

/// The five main tabs of the TUI
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tab {
    FileTree,
    Settings,
    Statistics,
    Template,
    PromptOutput,
}

/// Input mode for the FileTree tab
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileTreeInputMode {
    Normal,
    Search,
}

/// Hierarchical file node for TUI display with proper parent-child relationships
#[derive(Debug, Clone)]
pub struct DisplayFileNode {
    pub path: std::path::PathBuf,
    pub name: String,
    pub is_directory: bool,
    pub is_expanded: bool,
    pub level: usize,
    pub children_loaded: bool,
    pub children: Vec<DisplayFileNode>,
}

impl DisplayFileNode {
    pub fn new(path: std::path::PathBuf, level: usize) -> Self {
        let name = path
            .file_name()
            .map(|n| n.to_string_lossy().to_string())
            .unwrap_or_else(|| path.to_string_lossy().to_string());

        let is_directory = path.is_dir();

        Self {
            path,
            name,
            is_directory,
            is_expanded: false,
            level,
            children_loaded: false,
            children: Vec::new(),
        }
    }

    /// Find a node by path in the tree (recursive)
    pub fn find_node_mut(&mut self, target_path: &std::path::Path) -> Option<&mut DisplayFileNode> {
        if self.path == target_path {
            return Some(self);
        }

        for child in &mut self.children {
            if let Some(found) = child.find_node_mut(target_path) {
                return Some(found);
            }
        }

        None
    }

    /// Load children for this directory node
    pub fn load_children(
        &mut self,
        session: &mut code2prompt_core::session::Code2PromptSession,
    ) -> Result<(), Box<dyn std::error::Error>> {
        if !self.is_directory || self.children_loaded {
            return Ok(());
        }

        self.children.clear();

        // Use ignore crate to respect gitignore
        use ignore::WalkBuilder;
        let walker = WalkBuilder::new(&self.path).max_depth(Some(1)).build();

        for entry in walker {
            let entry = entry?;
            let path = entry.path();

            if path == self.path {
                continue; // Skip self
            }

            let mut child = DisplayFileNode::new(path.to_path_buf(), self.level + 1);

            // Auto-expand if contains selected files
            if child.is_directory && directory_contains_selected_files(&child.path, session) {
                child.is_expanded = true;
            }

            self.children.push(child);
        }

        // Sort children: directories first, then alphabetically
        self.children
            .sort_by(|a, b| match (a.is_directory, b.is_directory) {
                (true, false) => std::cmp::Ordering::Less,
                (false, true) => std::cmp::Ordering::Greater,
                _ => a.name.cmp(&b.name),
            });

        self.children_loaded = true;
        Ok(())
    }
}

/// Messages for updating the model
#[derive(Debug, Clone)]
pub enum Message {
    SwitchTab(Tab),
    Quit,

    UpdateSearchQuery(String),
    ToggleFileSelection(usize),
    ExpandDirectory(usize),
    CollapseDirectory(usize),
    MoveTreeCursor(i32),
    RefreshFileTree,

    EnterSearchMode,
    ExitSearchMode,

    MoveSettingsCursor(i32),
    ToggleSetting(usize),
    CycleSetting(usize),

    RunAnalysis,
    AnalysisComplete(AnalysisResults),
    AnalysisError(String),

    CopyToClipboard,
    SaveToFile(String),
    ScrollOutput(i16),

    CycleStatisticsView(i8),
    ScrollStatistics(i16),

    SaveTemplate(String),
    ReloadTemplate,
    LoadTemplate,
    RefreshTemplates,

    SetTemplateFocus(TemplateFocus, FocusMode),
    SetTemplateFocusMode(FocusMode),
    TemplateEditorInput(ratatui::crossterm::event::KeyEvent),
    TemplatePickerMove(i32),

    VariableStartEditing(String),
    VariableInputChar(char),
    VariableInputBackspace,
    VariableInputEnter,
    VariableInputCancel,
    VariableNavigateUp,
    VariableNavigateDown,
}

/// Represents the overall state of the TUI application.
#[derive(Debug, Clone)]
pub struct Model {
    pub session: Code2PromptSession,
    pub current_tab: Tab,
    pub should_quit: bool,
    pub file_tree_input_mode: FileTreeInputMode,
    pub file_tree_nodes: Vec<DisplayFileNode>,
    pub search_query: String,
    pub tree_cursor: usize,
    pub file_tree_scroll: u16,
    pub settings: SettingsState,
    pub statistics: StatisticsState,
    pub template: TemplateState,
    pub prompt_output: PromptOutputState,
    pub status_message: String,
}

impl Default for Model {
    fn default() -> Self {
        let config = code2prompt_core::configuration::Code2PromptConfig::default();
        let session = Code2PromptSession::new(config);

        Model {
            session,
            current_tab: Tab::FileTree,
            should_quit: false,
            file_tree_input_mode: FileTreeInputMode::Normal,
            file_tree_nodes: Vec::new(),
            search_query: String::new(),
            tree_cursor: 0,
            file_tree_scroll: 0,
            settings: SettingsState::default(),
            statistics: StatisticsState::default(),
            template: TemplateState::default(),
            prompt_output: PromptOutputState::default(),
            status_message: String::new(),
        }
    }
}

impl Model {
    pub fn new(session: Code2PromptSession) -> Self {
        Model {
            session,
            current_tab: Tab::FileTree,
            should_quit: false,
            file_tree_input_mode: FileTreeInputMode::Normal,
            file_tree_nodes: Vec::new(),
            search_query: String::new(),
            tree_cursor: 0,
            file_tree_scroll: 0,
            settings: SettingsState::default(),
            statistics: StatisticsState::default(),
            template: TemplateState::default(),
            prompt_output: PromptOutputState::default(),
            status_message: String::new(),
        }
    }

    /// Get grouped settings for display
    pub fn get_settings_groups(&self) -> Vec<SettingsGroup> {
        crate::view::format_settings_groups(&self.session)
    }

    pub fn update(&self, message: Message) -> (Self, Cmd) {
        let mut new_model = self.clone();

        match message {
            Message::Quit => {
                new_model.should_quit = true;
                new_model.status_message = "Goodbye!".to_string();
                (new_model, Cmd::None)
            }

            Message::SwitchTab(tab) => {
                new_model.current_tab = tab;
                new_model.status_message = format!("Switched to {:?} tab", tab);
                (new_model, Cmd::None)
            }

            Message::RefreshFileTree => {
                new_model.status_message = "Refreshing file tree...".to_string();
                (new_model, Cmd::RefreshFileTree)
            }

            Message::UpdateSearchQuery(query) => {
                new_model.search_query = query;
                new_model.tree_cursor = 0; // Reset cursor when search changes
                new_model.file_tree_scroll = 0; // Reset scroll when search changes
                (new_model, Cmd::None)
            }

            Message::EnterSearchMode => {
                new_model.file_tree_input_mode = FileTreeInputMode::Search;
                new_model.status_message = "Search mode - Type to search, Esc to exit".to_string();
                (new_model, Cmd::None)
            }

            Message::ExitSearchMode => {
                new_model.file_tree_input_mode = FileTreeInputMode::Normal;
                new_model.status_message = "Exited search mode".to_string();
                (new_model, Cmd::None)
            }

            Message::MoveTreeCursor(delta) => {
                let visible_nodes = crate::utils::get_visible_nodes(
                    &new_model.file_tree_nodes,
                    &new_model.search_query,
                    &mut new_model.session,
                );
                let visible_count = visible_nodes.len();

                if visible_count > 0 {
                    let new_cursor = if delta > 0 {
                        (new_model.tree_cursor + delta as usize).min(visible_count - 1)
                    } else {
                        new_model.tree_cursor.saturating_sub((-delta) as usize)
                    };
                    new_model.tree_cursor = new_cursor;
                }
                (new_model, Cmd::None)
            }

            Message::MoveSettingsCursor(delta) => {
                let settings_count = new_model
                    .settings
                    .get_settings_items(&new_model.session)
                    .len();
                if settings_count > 0 {
                    let new_cursor = if delta > 0 {
                        (new_model.settings.settings_cursor + delta as usize)
                            .min(settings_count - 1)
                    } else {
                        new_model
                            .settings
                            .settings_cursor
                            .saturating_sub((-delta) as usize)
                    };
                    new_model.settings.settings_cursor = new_cursor;
                }
                (new_model, Cmd::None)
            }

            Message::ToggleFileSelection(index) => {
                let visible_nodes = crate::utils::get_visible_nodes(
                    &new_model.file_tree_nodes,
                    &new_model.search_query,
                    &mut new_model.session,
                );

                if let Some(display_node) = visible_nodes.get(index) {
                    let node_path = display_node.node.path.clone();
                    let name = display_node.node.name.clone();
                    let is_directory = display_node.node.is_directory;
                    let current = display_node.is_selected;

                    // Convert to relative path for session
                    let relative_path =
                        if let Ok(rel) = node_path.strip_prefix(&new_model.session.config.path) {
                            rel.to_path_buf()
                        } else {
                            node_path.clone()
                        };

                    // Update session selection state (single source of truth)
                    new_model.session.toggle_file_selection(relative_path);

                    let action = if current { "Deselected" } else { "Selected" };
                    let extra = if is_directory { " (and contents)" } else { "" };
                    new_model.status_message = format!("{} {}{}", action, name, extra);
                }
                (new_model, Cmd::None)
            }

            Message::ExpandDirectory(index) => {
                let visible_nodes = crate::utils::get_visible_nodes(
                    &new_model.file_tree_nodes,
                    &new_model.search_query,
                    &mut new_model.session,
                );

                if let Some(display_node) = visible_nodes.get(index)
                    && display_node.node.is_directory
                {
                    let node_path = display_node.node.path.clone();
                    let name = display_node.node.name.clone();

                    // Ensure the path exists in the tree first
                    if let Err(e) = crate::utils::ensure_path_exists_in_tree(
                        &mut new_model.file_tree_nodes,
                        &node_path,
                        &mut new_model.session,
                    ) {
                        new_model.status_message =
                            format!("Failed to ensure path exists for {}: {}", name, e);
                        return (new_model, Cmd::None);
                    }

                    // Find and expand the node in the tree
                    let mut found = false;
                    for root_node in &mut new_model.file_tree_nodes {
                        if let Some(node) = root_node.find_node_mut(&node_path) {
                            if !node.is_expanded {
                                node.is_expanded = true;
                                // Load children if not already loaded
                                if !node.children_loaded
                                    && let Err(e) = node.load_children(&mut new_model.session)
                                {
                                    new_model.status_message =
                                        format!("Failed to load children for {}: {}", name, e);
                                    return (new_model, Cmd::None);
                                }
                                new_model.status_message = format!("Expanded {}", name);
                            } else {
                                new_model.status_message = format!("{} is already expanded", name);
                            }
                            found = true;
                            break;
                        }
                    }

                    if !found {
                        new_model.status_message = format!("Could not find directory {}", name);
                    }
                }
                (new_model, Cmd::None)
            }

            Message::CollapseDirectory(index) => {
                let visible_nodes = crate::utils::get_visible_nodes(
                    &new_model.file_tree_nodes,
                    &new_model.search_query,
                    &mut new_model.session,
                );

                if let Some(display_node) = visible_nodes.get(index)
                    && display_node.node.is_directory
                {
                    let node_path = display_node.node.path.clone();
                    let name = display_node.node.name.clone();

                    // Find and collapse the node in the tree
                    let mut found = false;
                    for root_node in &mut new_model.file_tree_nodes {
                        if let Some(node) = root_node.find_node_mut(&node_path)
                            && node.is_expanded
                        {
                            node.is_expanded = false;
                            new_model.status_message = format!("Collapsed {}", name);
                            found = true;
                            break;
                        }
                    }

                    if !found {
                        new_model.status_message = format!("Could not find directory {}", name);
                    }
                }
                (new_model, Cmd::None)
            }

            Message::ToggleSetting(index) => {
                let items = new_model.settings.get_settings_items(&new_model.session);
                if let Some(item) = items.get(index) {
                    let setting_name = new_model.settings.update_setting_by_key(
                        &mut new_model.session,
                        item.key,
                        SettingAction::Toggle,
                    );
                    new_model.status_message = format!("Toggled {}", setting_name);
                } else {
                    new_model.status_message = format!("Invalid setting index: {}", index);
                }
                (new_model, Cmd::None)
            }

            Message::CycleSetting(index) => {
                let items = new_model.settings.get_settings_items(&new_model.session);
                if let Some(item) = items.get(index) {
                    let setting_name = new_model.settings.update_setting_by_key(
                        &mut new_model.session,
                        item.key,
                        SettingAction::Cycle,
                    );
                    new_model.status_message = format!("Cycled {}", setting_name);
                } else {
                    new_model.status_message = format!("Invalid setting index: {}", index);
                }
                (new_model, Cmd::None)
            }

            Message::RunAnalysis => {
                if !new_model.prompt_output.analysis_in_progress {
                    new_model.prompt_output.analysis_in_progress = true;
                    new_model.prompt_output.analysis_error = None;
                    new_model.status_message = "Running analysis...".to_string();
                    new_model.current_tab = Tab::PromptOutput; // Switch to output tab

                    let cmd = Cmd::RunAnalysis {
                        template_content: new_model.template.get_template_content().to_string(),
                        user_variables: new_model.template.variables.user_variables.clone(),
                    };
                    (new_model, cmd)
                } else {
                    new_model.status_message = "Analysis already in progress...".to_string();
                    (new_model, Cmd::None)
                }
            }

            Message::AnalysisComplete(results) => {
                new_model.prompt_output.analysis_in_progress = false;
                new_model.prompt_output.generated_prompt = Some(results.generated_prompt);
                new_model.prompt_output.token_count = results.token_count;
                new_model.prompt_output.file_count = results.file_count;
                // Reset output scroll so the new content starts at the top.
                new_model.prompt_output.output_scroll = 0;
                new_model.statistics.token_map_entries = results.token_map_entries;
                let tokens = results.token_count.unwrap_or(0);
                new_model.status_message = format!(
                    "Analysis complete! {} tokens, {} files",
                    tokens, results.file_count
                );
                (new_model, Cmd::None)
            }

            Message::AnalysisError(error) => {
                new_model.prompt_output.analysis_in_progress = false;
                new_model.prompt_output.analysis_error = Some(error.clone());
                new_model.status_message = format!("Analysis failed: {}", error);
                (new_model, Cmd::None)
            }

            Message::CopyToClipboard => {
                if let Some(prompt) = &new_model.prompt_output.generated_prompt {
                    let cmd = Cmd::CopyToClipboard(prompt.clone());
                    (new_model, cmd)
                } else {
                    new_model.status_message = "No prompt to copy".to_string();
                    (new_model, Cmd::None)
                }
            }

            Message::SaveToFile(filename) => {
                if let Some(prompt) = &new_model.prompt_output.generated_prompt {
                    let cmd = Cmd::SaveToFile {
                        filename,
                        content: prompt.clone(),
                    };
                    (new_model, cmd)
                } else {
                    new_model.status_message = "No prompt to save".to_string();
                    (new_model, Cmd::None)
                }
            }

            Message::ScrollOutput(delta) => {
                // Apply delta only; widgets will clamp based on actual viewport.
                let new_scroll = if delta < 0 {
                    new_model
                        .prompt_output
                        .output_scroll
                        .saturating_sub((-delta) as u16)
                } else {
                    new_model
                        .prompt_output
                        .output_scroll
                        .saturating_add(delta as u16)
                };
                new_model.prompt_output.output_scroll = new_scroll;
                (new_model, Cmd::None)
            }

            Message::CycleStatisticsView(direction) => {
                new_model.statistics.view = if direction > 0 {
                    new_model.statistics.view.next()
                } else {
                    new_model.statistics.view.prev()
                };
                new_model.statistics.scroll = 0;
                new_model.status_message =
                    format!("Switched to {} view", new_model.statistics.view.as_str());
                (new_model, Cmd::None)
            }

            Message::ScrollStatistics(delta) => {
                let new_scroll = if delta < 0 {
                    new_model.statistics.scroll.saturating_sub((-delta) as u16)
                } else {
                    new_model.statistics.scroll.saturating_add(delta as u16)
                };
                new_model.statistics.scroll = new_scroll;
                (new_model, Cmd::None)
            }

            Message::SaveTemplate(filename) => {
                let content = new_model.template.get_template_content().to_string();
                let cmd = Cmd::SaveTemplate {
                    filename: filename.clone(),
                    content,
                };
                new_model.status_message = "Saving template...".to_string();
                (new_model, cmd)
            }

            Message::ReloadTemplate => {
                new_model.template.editor = crate::model::template::EditorState::default();
                new_model.template.sync_variables_with_template();
                new_model.status_message = "Reloaded template".to_string();
                (new_model, Cmd::None)
            }

            Message::LoadTemplate => {
                let result = new_model.template.load_selected_template();
                match result {
                    Ok(template_name) => {
                        new_model.template.sync_variables_with_template();
                        new_model.status_message = format!("Loaded template: {}", template_name);
                    }
                    Err(e) => {
                        new_model.status_message = format!("Failed to load template: {}", e);
                    }
                }
                (new_model, Cmd::None)
            }

            Message::RefreshTemplates => {
                new_model.template.picker.refresh();
                new_model.status_message = "Templates refreshed".to_string();
                (new_model, Cmd::None)
            }

            Message::SetTemplateFocus(focus, mode) => {
                new_model.template.set_focus(focus);
                new_model.template.set_focus_mode(mode);
                if mode == crate::model::template::FocusMode::EditingVariable {
                    new_model
                        .template
                        .variables
                        .move_to_first_missing_variable();
                }
                new_model.status_message = format!("Template focus: {:?} ({:?})", focus, mode);
                (new_model, Cmd::None)
            }

            Message::SetTemplateFocusMode(mode) => {
                new_model.template.set_focus_mode(mode);
                new_model.status_message = format!("Template mode: {:?}", mode);
                (new_model, Cmd::None)
            }

            Message::TemplateEditorInput(key) => {
                new_model.template.editor.editor.input(key);
                new_model.template.editor.sync_content_from_textarea();
                new_model.template.editor.validate_template();
                new_model.template.sync_variables_with_template();
                (new_model, Cmd::None)
            }

            Message::TemplatePickerMove(delta) => {
                if delta > 0 {
                    new_model.template.picker.move_cursor_down();
                } else {
                    new_model.template.picker.move_cursor_up();
                }
                (new_model, Cmd::None)
            }

            Message::VariableStartEditing(var_name) => {
                new_model.template.variables.editing_variable = Some(var_name.clone());
                new_model.template.variables.show_variable_input = true;
                new_model.template.variables.variable_input_content.clear();
                new_model.status_message = format!("Editing variable: {}", var_name);
                (new_model, Cmd::None)
            }

            Message::VariableInputChar(c) => {
                new_model.template.variables.add_char_to_input(c);
                (new_model, Cmd::None)
            }

            Message::VariableInputBackspace => {
                new_model.template.variables.remove_char_from_input();
                (new_model, Cmd::None)
            }

            Message::VariableInputEnter => {
                if let Some((var_name, value)) = new_model.template.variables.finish_editing() {
                    new_model.status_message = format!("Set {} = {}", var_name, value);
                    new_model.template.sync_variables_with_template();
                }
                (new_model, Cmd::None)
            }

            Message::VariableInputCancel => {
                new_model.template.variables.cancel_editing();
                new_model.status_message = "Cancelled variable editing".to_string();
                (new_model, Cmd::None)
            }

            Message::VariableNavigateUp => {
                if new_model.template.variables.cursor > 0 {
                    new_model.template.variables.cursor -= 1;
                }
                (new_model, Cmd::None)
            }

            Message::VariableNavigateDown => {
                let variables = new_model.template.get_organized_variables();
                if new_model.template.variables.cursor < variables.len().saturating_sub(1) {
                    new_model.template.variables.cursor += 1;
                }
                (new_model, Cmd::None)
            }
        }
    }
}


================================================
FILE: crates/code2prompt/src/model/prompt_output.rs
================================================
//! Prompt output state management for the TUI application.
//!
//! This module contains the prompt output state and related functionality
//! for managing generated prompts and analysis results in the TUI.

/// Prompt output state containing all prompt output related data
#[derive(Debug, Default, Clone)]
pub struct PromptOutputState {
    pub generated_prompt: Option<String>,
    pub token_count: Option<usize>,
    pub file_count: usize,
    pub analysis_in_progress: bool,
    pub analysis_error: Option<String>,
    pub output_scroll: u16,
}

/// Results from code2prompt analysis
#[derive(Debug, Clone)]
pub struct AnalysisResults {
    pub file_count: usize,
    pub token_count: Option<usize>,
    pub generated_prompt: String,
    pub token_map_entries: Vec<crate::token_map::TokenMapEntry>,
}


================================================
FILE: crates/code2prompt/src/model/settings.rs
================================================
//! Settings state management for the TUI application.
//!
//! This module contains the settings state, settings groups, and related
//! functionality for managing configuration options in the TUI.

use code2prompt_core::session::Code2PromptSession;
use code2prompt_core::template::OutputFormat;
use code2prompt_core::tokenizer::TokenFormat;

/// Settings state containing cursor position and related data
#[derive(Default, Debug, Clone)]
pub struct SettingsState {
    pub settings_cursor: usize,
}

/// Settings group for organizing settings
#[derive(Debug, Clone)]
pub struct SettingsGroup {
    pub name: String,
    pub items: Vec<SettingsItem>,
}

/// Settings item for display and interaction
#[derive(Debug, Clone)]
pub struct SettingsItem {
    pub key: SettingKey,
    pub name: String,
    pub description: String,
    pub setting_type: SettingType,
}

#[derive(Debug, Clone)]
pub enum SettingType {
    Boolean(bool),
    Choice {
        options: Vec<String>,
        selected: usize,
    },
}

#[derive(Debug, Clone)]
pub enum SettingAction {
    Toggle,
    Cycle,
}

/// Unique identifier for each setting
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SettingKey {
    LineNumbers,
    AbsolutePaths,
    NoCodeblock,
    OutputFormat,
    TokenFormat,
    FullDirectoryTree,
    SortMethod,
    TokenizerType,
    GitDiff,
    FollowSymlinks,
    HiddenFiles,
    NoIgnore,
    Deselected,
}

impl SettingsState {
    /// Get flattened list of settings for display (uses format_settings_groups)
    pub fn get_settings_items(&self, session: &Code2PromptSession) -> Vec<SettingsItem> {
        crate::view::format_settings_groups(session)
            .into_iter()
            .flat_map(|group| group.items)
            .collect()
    }

    /// Update setting based on SettingKey and action
    pub fn update_setting_by_key(
        &self,
        session: &mut Code2PromptSession,
        key: SettingKey,
        action: SettingAction,
    ) -> &'static str {
        match (key, action) {
            (SettingKey::LineNumbers, SettingAction::Toggle | SettingAction::Cycle) => {
                session.config.line_numbers = !session.config.line_numbers;
                "Line Numbers"
            }
            (SettingKey::AbsolutePaths, SettingAction::Toggle | SettingAction::Cycle) => {
                session.config.absolute_path = !session.config.absolute_path;
                "Absolute Paths"
            }
            (SettingKey::NoCodeblock, SettingAction::Toggle | SettingAction::Cycle) => {
                session.config.no_codeblock = !session.config.no_codeblock;
                "No Codeblock"
            }
            (SettingKey::OutputFormat, SettingAction::Cycle) => {
                session.config.output_format = match session.config.output_format {
                    OutputFormat::Markdown => OutputFormat::Json,
                    OutputFormat::Json => OutputFormat::Xml,
                    OutputFormat::Xml => OutputFormat::Markdown,
                };
                "Output Format"
            }
            (SettingKey::TokenFormat, SettingAction::Cycle) => {
                session.config.token_format = match session.config.token_format {
                    TokenFormat::Raw => TokenFormat::Format,
                    TokenFormat::Format => TokenFormat::Raw,
                };
                "Token Format"
            }
            (SettingKey::FullDirectoryTree, SettingAction::Toggle | SettingAction::Cycle) => {
                session.config.full_directory_tree = !session.config.full_directory_tree;
                "Full Directory Tree"
            }
            (SettingKey::SortMethod, SettingAction::Cycle) => {
                session.config.sort_method = Some(match session.config.sort_method {
                    Some(code2prompt_core::sort::FileSortMethod::NameAsc) => {
                        code2prompt_core::sort::FileSortMethod::NameDesc
                    }
                    Some(code2prompt_core::sort::FileSortMethod::NameDesc) => {
                        code2prompt_core::sort::FileSortMethod::DateAsc
                    }
                    Some(code2prompt_core::sort::FileSortMethod::DateAsc) => {
                        code2prompt_core::sort::FileSortMethod::DateDesc
                    }
                    Some(code2prompt_core::sort::FileSortMethod::DateDesc) | None => {
                        code2prompt_core::sort::FileSortMethod::NameAsc
                    }
                });
                "Sort Method"
            }
            (SettingKey::TokenizerType, SettingAction::Cycle) => {
                session.config.encoding = match session.config.encoding {
                    code2prompt_core::tokenizer::TokenizerType::Cl100kBase => {
                        code2prompt_core::tokenizer::TokenizerType::O200kBase
                    }
                    code2prompt_core::tokenizer::TokenizerType::O200kBase => {
                        code2prompt_core::tokenizer::TokenizerType::P50kBase
                    }
                    code2prompt_core::tokenizer::TokenizerType::P50kBase => {
                        code2prompt_core::tokenizer::TokenizerType::P50kEdit
                    }
                    code2prompt_core::tokenizer::TokenizerType::P50kEdit => {
                        code2prompt_core::tokenizer::TokenizerType::R50kBase
                    }
                    code2prompt_core::tokenizer::TokenizerType::R50kBase => {
                        code2prompt_core::tokenizer::TokenizerType::Cl100kBase
                    }
                };
                "Tokenizer Type"
            }
            (SettingKey::GitDiff, SettingAction::Toggle | SettingAction::Cycle) => {
                session.config.diff_enabled = !session.config.diff_enabled;
                "Git Diff"
            }
            (SettingKey::FollowSymlinks, SettingAction::Toggle | SettingAction::Cycle) => {
                session.config.follow_symlinks = !session.config.follow_symlinks;
                "Follow Symlinks"
            }
            (SettingKey::HiddenFiles, SettingAction::Toggle | SettingAction::Cycle) => {
                session.config.hidden = !session.config.hidden;
                "Hidden Files"
            }
            (SettingKey::NoIgnore, SettingAction::Toggle | SettingAction::Cycle) => {
                session.config.no_ignore = !session.config.no_ignore;
                "No Ignore"
            }
            (SettingKey::Deselected, SettingAction::Toggle | SettingAction::Cycle) => {
                session.set_deselected(!session.config.deselected);
                "Deselected by Default"
            }
            _ => "Unknown Setting",
        }
    }
}


================================================
FILE: crates/code2prompt/src/model/statistics/mod.rs
================================================
//! Statistics state management for the TUI application.
//!
//! This module contains the statistics state and related functionality,
//! including different statistics views and their management.

pub mod types;

use crate::model::DisplayFileNode;
use crate::utils::format_number;
pub use types::*;

/// Statistics state containing all statistics-related data
#[derive(Debug, Clone)]
pub struct StatisticsState {
    pub view: StatisticsView,
    pub scroll: u16,
    pub token_map_entries: Vec<crate::token_map::TokenMapEntry>,
}

impl Default for StatisticsState {
    fn default() -> Self {
        StatisticsState {
            view: StatisticsView::Overview,
            scroll: 0,
            token_map_entries: Vec::new(),
        }
    }
}

impl StatisticsState {
    /// Count selected files using session-based approach
    pub fn count_selected_files(
        session: &mut code2prompt_core::session::Code2PromptSession,
    ) -> usize {
        session.get_selected_files().unwrap_or_default().len()
    }

    /// Count total files in the tree nodes
    pub fn count_total_files(nodes: &[DisplayFileNode]) -> usize {
        fn rec(n: &DisplayFileNode) -> usize {
            if !n.is_directory {
                1
            } else {
                n.children.iter().map(rec).sum()
            }
        }
        nodes.iter().map(rec).sum()
    }

    /// Format number according to token format setting (moved from widget)
    pub fn format_number(
        num: usize,
        token_format: &code2prompt_core::tokenizer::TokenFormat,
    ) -> String {
        format_number(num, token_format)
    }

    /// Aggregate tokens by file extension (moved from widget - business logic belongs in Model)
    pub fn aggregate_by_extension(&self) -> Vec<(String, usize, usize)> {
        let mut extension_stats: std::collections::HashMap<String, (usize, usize)> =
            std::collections::HashMap::new();

        for entry in &self.token_map_entries {
            if !entry.metadata.is_dir {
                let extension = entry
                    .name
                    .split('.')
                    .next_back()
                    .map(|ext| format!(".{}", ext))
                    .unwrap_or_else(|| "(no extension)".to_string());

                let (tokens, count) = extension_stats.entry(extension).or_insert((0, 0));
                *tokens += entry.tokens;
                *count += 1;
            }
        }

        // Convert to sorted vec (by tokens desc)
        let mut ext_vec: Vec<(String, usize, usize)> = extension_stats
            .into_iter()
            .map(|(ext, (tokens, count))| (ext, tokens, count))
            .collect();
        ext_vec.sort_by(|a, b| b.1.cmp(&a.1));
        ext_vec
    }
}


================================================
FILE: crates/code2prompt/src/model/statistics/types.rs
================================================
//! Statistics view types and enums.
//!
//! This module contains the StatisticsView enum and related types
//! for managing different statistics views in the TUI.

/// Different views available in the Statistics tab
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StatisticsView {
    Overview,   // General statistics and summary
    TokenMap,   // Token distribution by directory/file
    Extensions, // Token distribution by file extension
}

impl StatisticsView {
    pub fn next(&self) -> Self {
        match self {
            StatisticsView::Overview => StatisticsView::TokenMap,
            StatisticsView::TokenMap => StatisticsView::Extensions,
            StatisticsView::Extensions => StatisticsView::Overview,
        }
    }

    pub fn prev(&self) -> Self {
        match self {
            StatisticsView::Overview => StatisticsView::Extensions,
            StatisticsView::TokenMap => StatisticsView::Overview,
            StatisticsView::Extensions => StatisticsView::TokenMap,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            StatisticsView::Overview => "Overview",
            StatisticsView::TokenMap => "Token Map",
            StatisticsView::Extensions => "Extensions",
        }
    }
}


================================================
FILE: crates/code2prompt/src/model/template/editor.rs
================================================
//! Template editor state management.
//!
//! This module contains the state and logic for the template editor component,
//! including TextArea management, validation, and content synchronization.

use regex::Regex;
use std::collections::HashSet;
use tui_textarea::TextArea;

/// State for the template editor component
#[derive(Debug)]
pub struct EditorState {
    pub content: String,
    pub editor: TextArea<'static>,
    pub current_template_name: String,
    pub is_valid: bool,
    pub validation_message: String,
    pub template_variables: Vec<String>, // Variables found in template
}

impl Clone for EditorState {
    fn clone(&self) -> Self {
        let mut new_editor = TextArea::from(self.editor.lines().iter().map(|s| s.as_str()));
        new_editor.move_cursor(tui_textarea::CursorMove::Jump(
            self.editor.cursor().0.try_into().unwrap_or(0),
            self.editor.cursor().1.try_into().unwrap_or(0),
        ));

        Self {
            content: self.content.clone(),
            editor: new_editor,
            current_template_name: self.current_template_name.clone(),
            is_valid: self.is_valid,
            validation_message: self.validation_message.clone(),
            template_variables: self.template_variables.clone(),
        }
    }
}

impl Default for EditorState {
    fn default() -> Self {
        // Load default markdown template from API
        let content = if let Some(builtin_template) =
            code2prompt_core::builtin_templates::BuiltinTemplates::get_template("default-markdown")
        {
            builtin_template.content
        } else {
            "# {{project_name}}\n\n{{#if files}}\n{{#each files}}\n## {{path}}\n\n```{{extension}}\n{{content}}\n```\n\n{{/each}}\n{{/if}}"
        };

        let editor = TextArea::from(content.lines());

        let mut state = Self {
            content: content.to_string(),
            editor,
            current_template_name: "Default (Markdown)".to_string(),
            is_valid: true,
            validation_message: String::new(),
            template_variables: Vec::new(),
        };

        state.analyze_template_variables();
        state
    }
}

impl EditorState {
    /// Update content from TextArea and re-analyze variables
    pub fn sync_content_from_textarea(&mut self) {
        self.content = self.editor.lines().join("\n");
        self.analyze_template_variables();
    }

    /// Parse template content to extract all {{variable}} references
    pub fn analyze_template_variables(&mut self) {
        let re = Regex::new(r"\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}").unwrap();
        let mut found_vars = HashSet::new();

        for cap in re.captures_iter(&self.content) {
            if let Some(var_name) = cap.get(1) {
                found_vars.insert(var_name.as_str().to_string());
            }
        }

        self.template_variables = found_vars.into_iter().collect();
        self.template_variables.sort();
    }

    /// Get all variables found in the template
    pub fn get_template_variables(&self) -> &[String] {
        &self.template_variables
    }

    /// Validate template syntax with enhanced Handlebars checking
    pub fn validate_template(&mut self) {
        // First check for balanced braces
        let open_count = self.content.matches("{{").count();
        let close_count = self.content.matches("}}").count();

        if open_count != close_count {
            self.is_valid = false;
            self.validation_message = format!(
                "Unbalanced braces: {} opening, {} closing",
                open_count, close_count
            );
            return;
        }

        // Try to compile the template with Handlebars
        match self.compile_template() {
            Ok(_) => {
                self.is_valid = true;
                self.validation_message = String::new();
            }
            Err(e) => {
                self.is_valid = false;
                self.validation_message = format!("Template syntax error: {}", e);
            }
        }
    }

    /// Attempt to compile the template to check for syntax errors
    fn compile_template(&self) -> Result<(), String> {
        let mut handlebars = handlebars::Handlebars::new();

        // Set strict mode to catch undefined variables
        handlebars.set_strict_mode(false); // Allow undefined variables for now

        match handlebars.register_template_string("test", &self.content) {
            Ok(_) => Ok(()),
            Err(e) => Err(format!("{}", e)),
        }
    }

    /// Get current template content
    pub fn get_content(&self) -> &str {
        &self.content
    }
}


================================================
FILE: crates/code2prompt/src/model/template/mod.rs
================================================
//! Template state management module.
//!
//! This module coordinates the three template sub-components:
//! - Editor: Template content editing and validation
//! - Variable: Variable management and validation
//! - Picker: Template selection and loading

pub mod editor;
pub mod picker;
pub mod variable;

pub use editor::EditorState;
pub use picker::{ActiveList, PickerState};
pub use variable::{VariableCategory, VariableInfo, VariableState};

/// Which component is currently focused
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TemplateFocus {
    Editor,
    Variables,
    Picker,
}

/// Focus mode determines interaction behavior
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FocusMode {
    Normal,          // Can switch between panels with e/v/p
    EditingTemplate, // Locked to editor, ESC to exit
    EditingVariable, // Locked to variables, ESC to exit
}

/// Coordinated template state containing all sub-components
#[derive(Debug, Clone)]
pub struct TemplateState {
    pub editor: EditorState,
    pub variables: VariableState,
    pub picker: PickerState,
    pub focus: TemplateFocus,
    pub focus_mode: FocusMode,
    pub status_message: String,
}

impl Default for TemplateState {
    fn default() -> Self {
        let mut state = Self {
            editor: EditorState::default(),
            variables: VariableState::default(),
            picker: PickerState::default(),
            focus: TemplateFocus::Editor,
            focus_mode: FocusMode::Normal,
            status_message: String::new(),
        };

        // Initialize variable state with template variables
        state.sync_variables_with_template();
        state
    }
}

impl TemplateState {
    /// Create template state from model (for TUI integration)
    pub fn from_model(model: &crate::model::Model) -> Self {
        // Create a new state based on the model's template state
        model.template.clone()
    }

    /// Synchronize variables with current template content
    pub fn sync_variables_with_template(&mut self) {
        let template_vars = self.editor.get_template_variables();
        self.variables.update_missing_variables(template_vars);
    }

    /// Set focus to a specific component
    pub fn set_focus(&mut self, focus: TemplateFocus) {
        self.focus = focus;
    }

    /// Get current focus
    pub fn get_focus(&self) -> TemplateFocus {
        self.focus
    }

    /// Set focus mode
    pub fn set_focus_mode(&mut self, mode: FocusMode) {
        self.focus_mode = mode;
    }

    /// Get current focus mode
    pub fn get_focus_mode(&self) -> FocusMode {
        self.focus_mode
    }

    /// Check if currently in an editing mode
    pub fn is_in_editing_mode(&self) -> bool {
        matches!(
            self.focus_mode,
            FocusMode::EditingTemplate | FocusMode::EditingVariable
        )
    }

    /// Get organized variables for display
    pub fn get_organized_variables(&self) -> Vec<VariableInfo> {
        self.variables
            .get_organized_variables(self.editor.get_template_variables())
    }

    /// Get current template content for analysis
    pub fn get_template_content(&self) -> &str {
        self.editor.get_content()
    }

    /// Get status message
    pub fn get_status(&self) -> &str {
        &self.status_message
    }

    /// Load the currently selected template from the picker
    pub fn load_selected_template(&mut self) -> Result<String, String> {
        let selected_template = self.get_selected_template()?;

        // Load template content based on type
        let (content, template_name) = if selected_template
            .path
            .to_string_lossy()
            .starts_with("builtin://")
        {
            // Load built-in template from embedded resources
            let path_str = selected_template.path.to_string_lossy();
            let template_key = path_str.strip_prefix("builtin://").unwrap_or("");

            if let Some(builtin_template) =
                code2prompt_core::builtin_templates::BuiltinTemplates::get_template(template_key)
            {
                (
                    builtin_template.content.to_string(),
                    builtin_template.name.to_string(),
                )
            } else {
                return Err(format!("Built-in template '{}' not found", template_key));
            }
        } else {
            // Load template from file
            let content = std::fs::read_to_string(&selected_template.path)
                .map_err(|e| format!("Failed to read template file: {}", e))?;
            (content, selected_template.name.clone())
        };

        // Update editor with new content
        self.editor.content = content.clone();
        self.editor.current_template_name = template_name.clone();

        // Create new TextArea with the content
        self.editor.editor = tui_textarea::TextArea::from(content.lines());

        // Sync and validate
        self.editor.sync_content_from_textarea();
        self.editor.validate_template();

        Ok(template_name)
    }

    /// Get the currently selected template from the picker
    fn get_selected_template(&self) -> Result<&picker::TemplateFile, String> {
        match self.picker.active_list {
            ActiveList::Default => self
                .picker
                .default_templates
                .get(self.picker.default_cursor)
                .ok_or_else(|| "No default template selected".to_string()),
            ActiveList::Custom => self
                .picker
                .custom_templates
                .get(self.picker.custom_cursor)
                .ok_or_else(|| "No custom template selected".to_string()),
        }
    }
}


================================================
FILE: crates/code2prompt/src/model/template/picker.rs
================================================
//! Template picker state management.
//!
//! This module contains the state and logic for the template picker component,
//! including loading templates from default and custom directories.

use std::path::PathBuf;

/// Represents a template file
#[derive(Debug, Clone)]
pub struct TemplateFile {
    pub name: String,
    pub path: PathBuf,
}

/// Which list is currently active in the picker
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ActiveList {
    Default,
    Custom,
}

/// State for the template picker component
#[derive(Debug, Clone)]
pub struct PickerState {
    pub default_templates: Vec<TemplateFile>,
    pub custom_templates: Vec<TemplateFile>,
    pub active_list: ActiveList,
    pub default_cursor: usize,
    pub custom_cursor: usize,
}

impl Default for PickerState {
    fn default() -> Self {
        let mut state = Self {
            default_templates: Vec::new(),
            custom_templates: Vec::new(),
            active_list: ActiveList::Default,
            default_cursor: 0,
            custom_cursor: 0,
        };

        state.load_all_templates();
        state
    }
}

impl PickerState {
    /// Load all templates from default and custom directories
    pub fn load_all_templates(&mut self) {
        self.load_default_templates();
        self.load_custom_templates();
    }

    /// Load built-in default templates
    fn load_default_templates(&mut self) {
        self.default_templates.clear();

        // Load all built-in templates from the core
        let builtin_templates = code2prompt_core::builtin_templates::BuiltinTemplates::get_all();

        // Sort templates by name for consistent ordering
        let mut template_entries: Vec<_> = builtin_templates.iter().collect();
        template_entries.sort_by(|a, b| a.1.name.cmp(b.1.name));

        for (key, template) in template_entries {
            self.default_templates.push(TemplateFile {
                name: template.name.to_string(),
                path: PathBuf::from(format!("builtin://{}", key)),
            });
        }
    }

    /// Load custom templates from user directory
    fn load_custom_templates(&mut self) {
        self.custom_templates.clear();

        // Load templates from custom directory using utility function
        if let Ok(all_templates) = crate::utils::load_all_templates() {
            for (name, path) in all_templates {
                // All templates from load_all_templates are custom
                self.custom_templates.push(TemplateFile {
                    name,
                    path: PathBuf::from(path),
                });
            }
        }
    }

    /// Move cursor up in unified list
    pub fn move_cursor_up(&mut self) {
        let total_items = self.get_total_selectable_items();
        if total_items == 0 {
            return;
        }

        let current_global = self.get_global_template_index();
        let new_global = if current_global == 0 {
            total_items - 1 // Wrap to bottom
        } else {
            current_global - 1
        };

        self.set_cursor_from_global_position(new_global);
    }

    /// Move cursor down in unified list
    pub fn move_cursor_down(&mut self) {
        let total_items = self.get_total_selectable_items();
        if total_items == 0 {
            return;
        }

        let current_global = self.get_global_template_index();
        let new_global = (current_global + 1) % total_items;

        self.set_cursor_from_global_position(new_global);
    }

    /// Refresh templates by reloading from directories
    pub fn refresh(&mut self) {
        self.load_all_templates();

        // Reset cursors if they're out of bounds
        if self.default_cursor >= self.default_templates.len() {
            self.default_cursor = self.default_templates.len().saturating_sub(1);
        }
        if self.custom_cursor >= self.custom_templates.len() {
            self.custom_cursor = self.custom_templates.len().saturating_sub(1);
        }
    }

    /// Get global cursor position for unified list display (for rendering)
    pub fn get_global_cursor_position(&self) -> usize {
        let mut position = 0;

        // Count default templates section
        if !self.default_templates.is_empty() {
            position += 1; // Section header
            if self.active_list == ActiveList::Default {
                position += self.default_cursor;
                return position;
            }
            position += self.default_templates.len();
        }

        // Count custom templates section
        if !self.custom_templates.is_empty() {
            if !self.default_templates.is_empty() {
                position += 1; // Separator
            }
            position += 1; // Section header
            if self.active_list == ActiveList::Custom {
                position += self.custom_cursor;
                return position;
            }
        }

        position
    }

    /// Get global template index (for navigation logic)
    fn get_global_template_index(&self) -> usize {
        match self.active_list {
            ActiveList::Default => self.default_cursor,
            ActiveList::Custom => self.default_templates.len() + self.custom_cursor,
        }
    }

    /// Get total number of selectable items (templates only, not headers)
    fn get_total_selectable_items(&self) -> usize {
        self.default_templates.len() + self.custom_templates.len()
    }

    /// Set cursor position from global position in unified list
    fn set_cursor_from_global_position(&mut self, global_pos: usize) {
        let mut template_index = 0;

        // Check if position is in default templates
        if global_pos < self.default_templates.len() {
            self.active_list = ActiveList::Default;
            self.default_cursor = global_pos;
            return;
        }
        template_index += self.default_templates.len();

        // Check if position is in custom templates
        if global_pos < template_index + self.custom_templates.len() {
            self.active_list = ActiveList::Custom;
            self.custom_cursor = global_pos - template_index;
        }
    }
}


================================================
FILE: crates/code2prompt/src/model/template/variable.rs
================================================
//! Template variable state management.
//!
//! This module contains the state and logic for managing template variables,
//! including system variables, user-defined variables, and missing variables.

use std::collections::HashMap;

/// Variable categories for display and management
#[derive(Debug, Clone, PartialEq)]
pub enum VariableCategory {
    System,  // From build_template_data
    User,    // User-defined
    Missing, // In template but not defined
}

/// Information about a template variable
#[derive(Debug, Clone)]
pub struct VariableInfo {
    pub name: String,
    pub value: Option<String>,
    pub category: VariableCategory,
    pub description: Option<String>,
}

/// State for the template variable component
#[derive(Debug, Clone)]
pub struct VariableState {
    pub system_variables: HashMap<String, String>, // System variables with descriptions
    pub user_variables: HashMap<String, String>,   // User-defined variables
    pub missing_variables: Vec<String>,            // Variables in template but not defined
    pub cursor: usize,                             // Current cursor position in variable list
    pub editing_variable: Option<String>,          // Currently editing variable name
    pub variable_input_content: String,            // Content being typed for variable
    pub show_variable_input: bool,                 // Show variable input dialog
}

impl Default for VariableState {
    fn default() -> Self {
        Self {
            system_variables: Self::get_default_system_variables(),
            user_variables: HashMap::new(),
            missing_variables: Vec::new(),
            cursor: 0,
            editing_variable: None,
            variable_input_content: String::new(),
            show_variable_input: false,
        }
    }
}

impl VariableState {
    /// Get default system variables that are available from build_template_data
    fn get_default_system_variables() -> HashMap<String, String> {
        let mut vars = HashMap::new();

        // Main template variables from build_template_data()
        vars.insert(
            "absolute_code_path".to_string(),
            "Path to the codebase directory".to_string(),
        );
        vars.insert(
            "source_tree".to_string(),
            "Directory tree structure".to_string(),
        );
        vars.insert(
            "files".to_string(),
            "Array of file objects with content".to_string(),
        );
        vars.insert(
            "git_diff".to_string(),
            "Git diff output (if enabled)".to_string(),
        );
        vars.insert(
            "git_diff_branch".to_string(),
            "Git diff between branches".to_string(),
        );
        vars.insert(
            "git_log_branch".to_string(),
            "Git log between branches".to_string(),
        );

        // File object properties (used within {{#each files}} loops)
        vars.insert(
            "path".to_string(),
            "File path (available in {{#each files}} context)".to_string(),
        );
        vars.insert(
            "code".to_string(),
            "File content (available in {{#each files}} context)".to_string(),
        );
        vars.insert(
            "extension".to_string(),
            "File extension (available in {{#each files}} context)".to_string(),
        );
        vars.insert(
            "token_count".to_string(),
            "Token count for file (available in {{#each files}} context)".to_string(),
        );
        vars.insert(
            "metadata".to_string(),
            "File metadata (available in {{#each files}} context)".to_string(),
        );
        vars.insert(
            "mod_time".to_string(),
            "File modification time (available in {{#each files}} context)".to_string(),
        );

        vars
    }

    /// Update missing variables based on template variables
    pub fn update_missing_variables(&mut self, template_variables: &[String]) {
        self.missing_variables.clear();

        for var in template_variables {
            if !self.system_variables.contains_key(var) && !self.user_variables.contains_key(var) {
                self.missing_variables.push(var.clone());
            }
        }

        self.missing_variables.sort();
    }

    /// Get all variables organized by category for display
    pub fn get_organized_variables(&self, template_variables: &[String]) -> Vec<VariableInfo> {
        let mut variables = Vec::new();

        // System variables (only those used in template)
        for var in template_variables {
            if let Some(desc) = self.system_variables.get(var) {
                variables.push(VariableInfo {
                    name: var.clone(),
                    value: Some("(system)".to_string()),
                    category: VariableCategory::System,
                    description: Some(desc.clone()),
                });
            }
        }

        // User variables (only those used in template)
        for var in template_variables {
            if let Some(value) = self.user_variables.get(var) {
                variables.push(VariableInfo {
                    name: var.clone(),
                    value: Some(value.clone()),
                    category: VariableCategory::User,
                    description: None,
                });
            }
        }

        // Missing variables
        for var in &self.missing_variables {
            variables.push(VariableInfo {
                name: var.clone(),
                value: None,
                category: VariableCategory::Missing,
                description: Some("⚠️ Not defined".to_string()),
            });
        }

        variables
    }

    /// Set a user variable
    pub fn set_user_variable(&mut self, key: String, value: String) {
        self.user_variables.insert(key, value);
    }

    /// Check if there are missing variables
    pub fn has_missing_variables(&self) -> bool {
        !self.missing_variables.is_empty()
    }

    /// Cancel variable editing
    pub fn cancel_editing(&mut self) {
        self.editing_variable = None;
        self.variable_input_content.clear();
        self.show_variable_input = false;
    }

    /// Finish editing variable and save
    pub fn finish_editing(&mut self) -> Option<(String, String)> {
        if let Some(var_name) = self.editing_variable.take() {
            let value = self.variable_input_content.clone();
            self.set_user_variable(var_name.clone(), value.clone());
            self.variable_input_content.clear();
            self.show_variable_input = false;
            Some((var_name, value))
        } else {
            None
        }
    }

    /// Add character to variable input
    pub fn add_char_to_input(&mut self, c: char) {
        self.variable_input_content.push(c);
    }

    /// Remove character from variable input
    pub fn remove_char_from_input(&mut self) {
        self.variable_input_content.pop();
    }

    /// Get current variable input content
    pub fn get_input_content(&self) -> &str {
        &self.variable_input_content
    }

    /// Check if currently editing a variable
    pub fn is_editing(&self) -> bool {
        self.show_variable_input
    }

    /// Get currently editing variable name
    pub fn get_editing_variable(&self) -> Option<&String> {
        self.editing_variable.as_ref()
    }

    /// Move cursor to first missing/user-defined variable
    pub fn move_to_first_missing_variable(&mut self) {
        // This will be called when entering variable editing mode
        // For now, just reset cursor to 0, but we could enhance this
        // to find the first missing variable in the organized list
        self.cursor = 0;
    }
}


================================================
FILE: crates/code2prompt/src/token_map.rs
================================================
//! Token map visualization and analysis.
//!
//! This module provides functionality for generating and displaying visual token maps
//! that show how tokens are distributed across files in a codebase. It creates
//! hierarchical tree structures with visual bars and colors, similar to disk usage
//! analyzers but for token consumption.
use code2prompt_core::path::FileEntry;
use lscolors::{Indicator, LsColors};
use serde::Deserialize;
use std::cmp::Ordering;
use std::collections::{BTreeMap, BinaryHeap, HashMap};
use std::path::Path;
use unicode_width::UnicodeWidthStr;

/// Color information for TUI rendering
#[derive(Debug, Clone)]
pub enum TuiColor {
    White,
    Gray,
    Red,
    Green,
    Blue,
    Yellow,
    Cyan,
    Magenta,
    LightRed,
    LightGreen,
    LightBlue,
    LightYellow,
    LightCyan,
    LightMagenta,
}

/// Formatted line for TUI token map display with separate components
#[derive(Debug, Clone)]
pub struct TuiTokenMapLine {
    pub tokens_part: String,
    pub prefix_part: String,
    pub name_part: String,
    pub name_color: TuiColor,
    pub bar_part: String,
    pub percentage_part: String,
}

#[derive(Debug, Clone, Copy, Deserialize)]
pub struct EntryMetadata {
    pub is_dir: bool,
}

#[derive(Debug, Clone)]
struct TreeNode {
    tokens: usize,
    children: BTreeMap<String, TreeNode>,
    path: String,
    metadata: Option<EntryMetadata>,
}

impl TreeNode {
    fn with_path(path: String) -> Self {
        TreeNode {
            tokens: 0,
            children: BTreeMap::new(),
            path,
            metadata: None,
        }
    }
}

// For priority queue ordering
#[derive(Debug, Clone, Eq, PartialEq)]
struct NodePriority {
    tokens: usize,
    path: String,
    depth: usize,
}

impl Ord for NodePriority {
    fn cmp(&self, other: &Self) -> Ordering {
        // Order by tokens (descending), then by depth (ascending), then by path
        self.tokens
            .cmp(&other.tokens)
            .then_with(|| other.depth.cmp(&self.depth))
            .then_with(|| self.path.cmp(&other.path))
    }
}

impl PartialOrd for NodePriority {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

/// Generate a hierarchical token map with optional display limits.
///
/// Creates a tree structure showing token distribution across files and directories,
/// with optional limits on the number of entries and minimum percentage thresholds
/// for inclusion in the output.
///
/// # Arguments
///
/// * `files` - Array of file metadata from the code2prompt session
/// * `total_tokens` - Total token count for percentage calculations
/// * `max_lines` - Maximum number of entries to return (None for unlimited)
/// * `min_percent` - Minimum percentage threshold for inclusion (None for no limit)
///
/// # Returns
///
/// * `Vec<TokenMapEntry>` - Hierarchical list of token map entries ready for display
pub fn generate_token_map_with_limit(
    files: &[FileEntry],
    total_tokens: usize,
    max_lines: Option<usize>,
    min_percent: Option<f64>,
) -> Vec<TokenMapEntry> {
    let max_lines = max_lines.unwrap_or(20);
    let min_percent = min_percent.unwrap_or(0.1);

    let mut root = TreeNode::with_path(String::new());
    root.tokens = total_tokens;

    // Insert all files into the tree
    for file in files {
        let path_str = &file.path;
        let tokens = file.token_count;
        let metadata = EntryMetadata {
            is_dir: file.metadata.is_dir,
        };

        let path = Path::new(path_str);

        // Skip the root component if it exists
        let components: Vec<_> = path
            .components()
            .filter_map(|c| c.as_os_str().to_str())
            .collect();

        insert_path(&mut root, &components, tokens, String::new(), metadata);
    }

    // Use priority queue to select most significant entries
    let allowed_nodes = select_nodes_to_display(&root, total_tokens, max_lines, min_percent);

    // Convert tree to sorted entries for display
    let mut entries = Vec::new();
    rebuild_filtered_tree(
        &root,
        String::new(),
        &allowed_nodes,
        &mut entries,
        0,
        total_tokens,
        true,
    );

    // Add summary for hidden files if needed
    let displayed_tokens: usize = entries
        .iter()
        .map(|e| {
            if !e.metadata.is_dir {
                e.tokens
            } else {
                // For directories, only count their direct file children to avoid double counting
                0
            }
        })
        .sum();

    let hidden_tokens = calculate_file_tokens(&root) - displayed_tokens;
    if hidden_tokens > 0 {
        entries.push(TokenMapEntry {
            path: "(other files)".to_string(),
            name: "(other files)".to_string(),
            tokens: hidden_tokens,
            percentage: (hidden_tokens as f64 / total_tokens as f64) * 100.0,
            depth: 0,
            is_last: true,
            metadata: EntryMetadata { is_dir: false },
        });
    }

    entries
}

fn calculate_file_tokens(node: &TreeNode) -> usize {
    if node.metadata.is_some_and(|m| !m.is_dir) {
        node.tokens
    } else {
        node.children.values().map(calculate_file_tokens).sum()
    }
}

fn insert_path(
    node: &mut TreeNode,
    components: &[&str],
    tokens: usize,
    parent_path: String,
    file_metadata: EntryMetadata,
) {
    if components.is_empty() {
        return;
    }

    if components.len() == 1 {
        // This is a file
        let file_name = components[0].to_string();
        let file_path = if parent_path.is_empty() {
            file_name.clone()
        } else {
            format!("{}/{}", parent_path, file_name)
        };
        let child = node
            .children
            .entry(file_name)
            .or_insert_with(|| TreeNode::with_path(file_path));
        child.tokens = tokens;
        child.metadata = Some(file_metadata);
    } else {
        // This is a directory
        let dir_name = components[0].to_string();
        let dir_path = if parent_path.is_empty() {
            dir_name.clone()
        } else {
            format!("{}/{}", parent_path, dir_name)
        };
        let child = node
            .children
            .entry(dir_name)
            .or_insert_with(|| TreeNode::with_path(dir_path.clone()));
        child.tokens += tokens;
        child.metadata = Some(EntryMetadata { is_dir: true });
        insert_path(child, &components[1..], tokens, dir_path, file_metadata);
    }
}

#[derive(Debug, Clone)]
pub struct TokenMapEntry {
    pub path: String,
    pub name: String,
    pub tokens: usize,
    pub percentage: f64,
    pub depth: usize,
    pub is_last: bool,
    pub metadata: EntryMetadata,
}

/// Select nodes to display using priority queue
fn select_nodes_to_display(
    root: &TreeNode,
    total_tokens: usize,
    max_lines: usize,
    min_percent: f64,
) -> HashMap<String, usize> {
    let mut heap = BinaryHeap::new();
    let mut allowed_nodes = HashMap::new();
    let min_tokens = (total_tokens as f64 * min_percent / 100.0) as usize;

    // Start with root children
    for child in root.children.values() {
        if child.tokens >= min_tokens {
            heap.push(NodePriority {
                tokens: child.tokens,
                path: child.path.clone(),
                depth: 0,
            });
        }
    }

    // Process nodes by priority
    while allowed_nodes.len() < max_lines.saturating_sub(1) && !heap.is_empty() {
        if let Some(node_priority) = heap.pop() {
            allowed_nodes.insert(node_priority.path.clone(), node_priority.depth);

            // Find the node in the tree and add its children
            if let Some(node) = find_node_by_path(root, &node_priority.path) {
                for child in node.children.values() {
                    if child.tokens >= min_tokens && !allowed_nodes.contains_key(&child.path) {
                        heap.push(NodePriority {
                            tokens: child.tokens,
                            path: child.path.clone(),
                            depth: node_priority.depth + 1,
                        });
                    }
                }
            }
        }
    }

    allowed_nodes
}

/// Find a node by its path
fn find_node_by_path<'a>(root: &'a TreeNode, path: &str) -> Option<&'a TreeNode> {
    if path.is_empty() {
        return Some(root);
    }

    let components: Vec<&str> = path.split('/').collect();
    let mut current = root;

    for component in components {
        match current.children.get(component) {
            Some(child) => current = child,
            None => return None,
        }
    }

    Some(current)
}

/// Rebuild tree with only allowed nodes
fn rebuild_filtered_tree(
    node: &TreeNode,
    path: String,
    allowed_nodes: &HashMap<String, usize>,
    entries: &mut Vec<TokenMapEntry>,
    depth: usize,
    total_tokens: usize,
    is_last: bool,
) {
    // Check if this node should be included
    if !path.is_empty() && allowed_nodes.contains_key(&path) {
        let percentage = (node.tokens as f64 / total_tokens as f64) * 100.0;
        let name = path.split('/').next_back().unwrap_or(&path).to_string();

        let metadata = node.metadata.unwrap_or(EntryMetadata { is_dir: true });

        entries.push(TokenMapEntry {
            path: path.clone(),
            name,
            tokens: node.tokens,
            percentage,
            depth,
            is_last,
            metadata,
        });
    }

    // Process children that are in allowed_nodes
    let mut filtered_children: Vec<_> = node
        .children
        .iter()
        .filter(|(_, child)| allowed_nodes.contains_key(&child.path))
        .collect();

    // Sort by tokens descending
    filtered_children.sort_by(|a, b| b.1.tokens.cmp(&a.1.tokens));

    let child_count = filtered_children.len();
    for (i, (name, child)) in filtered_children.into_iter().enumerate() {
        let child_path = if path.is_empty() {
            name.clone()
        } else {
            format!("{}/{}", path, name)
        };

        let is_last_child = i == child_count - 1;
        rebuild_filtered_tree(
            child,
            child_path,
            allowed_nodes,
            entries,
            depth + 1,
            total_tokens,
            is_last_child,
        );
    }
}

fn should_enable_colors() -> bool {
    // Check NO_COLOR environment variable (https://no-color.org/)
    if std::env::var_os("NO_COLOR").is_some() {
        return false;
    }

    // Check if we're in a terminal
    if terminal_size::terminal_size().is_none() {
        return false;
    }

    // On Windows, enable ANSI support
    #[cfg(windows)]
    {
        use log::error;
        match ansi_term::enable_ansi_support() {
            Ok(_) => true,
            Err(_) => {
                error!("This version of Windows does not support ANSI colors");
                false
            }
        }
    }

    #[cfg(not(windows))]
    {
        true
    }
}

/// Display a visual token map with colors and hierarchical tree structure.
///
/// Renders the token map entries as a formatted tree with visual progress bars,
/// colors based on file types, and proper Unicode tree drawing characters.
/// Automatically adapts to terminal width and applies appropriate colors.
///
/// # Arguments
///
/// * `entries` - The token map entries to display
/// * `total_tokens` - Total token count for percentage calculations
pub fn display_token_map(entries: &[TokenMapEntry], total_tokens: usize) {
    if entries.is_empty() {
        return;
    }

    // Initialize LsColors from environment
    let ls_colors = LsColors::from_env().unwrap_or_default();
    let colors_enabled = should_enable_colors();

    // Terminal width detection
    let terminal_width = terminal_size::terminal_size()
        .map(|(terminal_size::Width(w), _)| w as usize)
        .unwrap_or(80);

    // Calculate max token width for alignment
    let max_token_width = entries
        .iter()
        .map(|e| format_tokens(e.tokens).len())
        .max()
        .unwrap_or(3)
        .max(format_tokens(total_tokens).len())
        .max(4);

    // Calculate max name length including tree prefix
    let max_name_length = entries
        .iter()
        .map(|e| {
            let prefix_width = if e.depth == 0 { 3 } else { (e.depth * 2) + 3 };
            prefix_width + UnicodeWidthStr::width(e.name.as_str())
        })
        .max()
        .unwrap_or(20)
        .min(terminal_width / 2);

    // Calculate bar width
    let bar_width = terminal_width
        .saturating_sub(max_token_width + 3 + max_name_length + 2 + 2 + 5)
        .max(20);

    // Initialize parent bars array
    let mut parent_bars: Vec<String> = vec![String::new(); 10];
    parent_bars[0] = "█".repeat(bar_width);

    for (i, entry) in entries.iter().enumerate() {
        // Build tree prefix using shared logic
        let prefix = build_tree_prefix(entry, entries, i);

        // Format tokens
        let tokens_str = format_tokens(entry.tokens);

        // Generate hierarchical bar
        let parent_bar = if entry.depth > 0 {
            &parent_bars[entry.depth - 1]
        } else {
            &parent_bars[0]
        };

        let bar = generate_hierarchical_bar(bar_width, parent_bar, entry.percentage, entry.depth);

        // Update parent bars
        if entry.depth < parent_bars.len() {
            parent_bars[entry.depth] = bar.clone();
        }

        // Format percentage
        let percentage_str = format!("{:>4.0}%", entry.percentage);

        // Calculate padding for name
        let prefix_display_width = prefix.chars().count();
        let name_padding = max_name_length
            .saturating_sub(prefix_display_width + UnicodeWidthStr::width(entry.name.as_str()));

        // Create name with padding FIRST
        let name_with_padding = format!("{}{}", entry.name, " ".repeat(name_padding));

        // THEN apply colors to the name+padding combination
        let colored_name_with_padding = if colors_enabled && entry.name != "(other files)" {
            // Use our cached metadata to choose the coloring strategy
            let ansi_style = if entry.metadata.is_dir {
                // For directories, we know the type. No need to hit the filesystem.
                ls_colors
                    .style_for_indicator(Indicator::Directory)
                    .map(|s| s.to_ansi_term_style())
                    .unwrap_or_default()
            } else {
                // For files, rely on extension-based styling (no filesystem stat).
                ls_colors
                    .style_for_path(std::path::Path::new(&entry.path))
                    .map(lscolors::Style::to_ansi_term_style)
                    .unwrap_or_default()
            };

            // Apply style to name WITH padding
            format!("{}", ansi_style.paint(name_with_padding))
        } else {
            name_with_padding
        };

        eprintln!(
            "{:>width$}   {}{} │{}│ {}",
            tokens_str,
            prefix,
            colored_name_with_padding,
            bar,
            percentage_str,
            width = max_token_width
        );
    }
}

/// Build tree prefix for an entry (shared logic for CLI and TUI)
fn build_tree_prefix(entry: &TokenMapEntry, entries: &[TokenMapEntry], index: usize) -> String {
    let mut prefix = String::new();

    // Add vertical lines for parent levels
    for d in 0..entry.depth {
        if d < entry.depth - 1 {
            // Check if we need a vertical line at this depth
            let needs_line = entries
                .iter()
                .skip(index + 1)
                .take_while(|entry| entry.depth > d)
                .any(|entry| entry.depth == d + 1);
            if needs_line {
                prefix.push_str("│ ");
            } else {
                prefix.push_str("  ");
            }
        } else if entry.is_last {
            prefix.push_str("└─");
        } else {
            prefix.push_str("├─");
        }
    }

    // Special handling for root
    if entry.depth == 0 && index == 0 && entry.name != "(other files)" {
        prefix = "┌─".to_string();
    }

    // Check if has children
    let has_children = entries
        .get(index + 1)
        .map(|next| next.depth > entry.depth)
        .unwrap_or(false);

    // Add the connecting character
    if entry.depth > 0 || entry.name == "(other files)" {
        if has_children {
            prefix.push('┬');
        } else {
            prefix.push('─');
        }
    } else if index == 0 {
        prefix.push('┴');
    }

    prefix.push(' ');
    prefix
}

/// Determine TUI color for an entry based on file type and extension
fn determine_tui_color(entry: &TokenMapEntry) -> TuiColor {
    if entry.metadata.is_dir {
        TuiColor::Cyan
    } else {
        match entry.name.split('.').next_back().unwrap_or("") {
            // Systems / compiled langs
            "rs" => TuiColor::Yellow,
            "c" | "h" | "cpp" | "cxx" | "hpp" => TuiColor::Blue,
            "go" => TuiColor::LightBlue,
            "java" | "kt" | "kts" => TuiColor::Red,
            "swift" => TuiColor::LightRed,
            "zig" => TuiColor::LightYellow,

            // Web
            "js" | "mjs" | "cjs" => TuiColor::LightGreen,
            "ts" | "tsx" | "jsx" => TuiColor::LightCyan,
            "html" | "htm" => TuiColor::Magenta,
            "css" | "scss" | "less" => TuiColor::LightMagenta,

            // Scripting / automation
            "py" => TuiColor::LightYellow,
            "sh" | "bash" | "zsh" => TuiColor::Gray,
            "rb" => TuiColor::LightRed,
            "pl" => TuiColor::LightCyan,
            "php" => TuiColor::LightMagenta,
            "lua" => TuiColor::LightBlue,

            // Data / config / markup
            "json" | "toml" | "yaml" | "yml" => TuiColor::Magenta,
            "xml" => TuiColor::LightGreen,
            "csv" => TuiColor::Green,
            "ini" => TuiColor::Gray,

            // Docs
            "md" | "txt" | "rst" | "adoc" => TuiColor::Green,
            "pdf" => TuiColor::Red,

            // Default
            _ => TuiColor::White,
        }
    }
}

/// Format token map entries for TUI display with adaptive layout.
///
/// Creates formatted lines with tree structure and color information suitable
/// for rendering in a TUI interface using ratatui. This function uses the same
/// adaptive layout logic as the CLI version but returns structured data components
/// instead of printing directly.
///
/// # Arguments
///
/// * `entries` - The token map entries to format
/// * `total_tokens` - Total token count for percentage calculations
/// * `terminal_width` - Width of the terminal/TUI area for adaptive layout
///
/// # Returns
///
/// * `Vec<TuiTokenMapLine>` - Formatted lines ready for TUI rendering
pub fn format_token_map_for_tui(
    entries: &[TokenMapEntry],
    total_tokens: usize,
    terminal_width: usize,
) -> Vec<TuiTokenMapLine> {
    if entries.is_empty() {
        return Vec::new();
    }

    // Use the same adaptive layout logic as CLI
    let terminal_width = terminal_width.max(80); // Minimum width

    // Calculate max token width for alignment (same as CLI)
    let max_token_width = entries
        .iter()
        .map(|e| format_tokens(e.tokens).len())
        .max()
        .unwrap_or(3)
        .max(format_tokens(total_tokens).len())
        .max(4);

    // Calculate max name length including tree prefix (same as CLI)
    let max_name_length = entries
        .iter()
        .map(|e| {
            let prefix_width = if e.depth == 0 { 3 } else { (e.depth * 2) + 3 };
            prefix_width + UnicodeWidthStr::width(e.name.as_str())
        })
        .max()
        .unwrap_or(20)
        .min(terminal_width / 2);

    // Calculate bar width (adjusted for TUI to prevent overflow)
    // TUI needs a bit more space than CLI to prevent the percentage column from overflowing
    let bar_width = terminal_width
        .saturating_sub(max_token_width + 3 + max_name_length + 2 + 2 + 7) // +2 more chars for TUI
        .max(15); // Minimum bar width reduced slightly for TUI

    // Initialize parent bars array (same as CLI)
    let mut parent_bars: Vec<String> = vec![String::new(); 10];
    parent_bars[0] = "█".repeat(bar_width);

    let mut lines = Vec::new();

    for (i, entry) in entries.iter().enumerate() {
        // Build tree prefix using shared logic
        let prefix = build_tree_prefix(entry, entries, i);

        // Format tokens
        let tokens_str = format_tokens(entry.tokens);

        // Generate hierarchical bar (same as CLI)
        let parent_bar = if entry.depth > 0 {
            &parent_bars[entry.depth - 1]
        } else {
            &parent_bars[0]
        };

        let bar = generate_hierarchical_bar(bar_width, parent_bar, entry.percentage, entry.depth);

        // Update parent bars (same as CLI)
        if entry.depth < parent_bars.len() {
            parent_bars[entry.depth] = bar.clone();
        }

        // Format percentage
        let percentage_str = format!("{:>4.0}%", entry.percentage);

        // Calculate padding for name (same as CLI)
        let prefix_display_width = prefix.chars().count();
        let name_padding = max_name_length
            .saturating_sub(prefix_display_width + UnicodeWidthStr::width(entry.name.as_str()));

        // Create name with padding
        let name_with_padding = format!("{}{}", entry.name, " ".repeat(name_padding));

        // Determine color based on entry type and extension
        let name_color = determine_tui_color(entry);

        // Create structured components for TUI rendering
        lines.push(TuiTokenMapLine {
            tokens_part: format!("{:>width$}", tokens_str, width = max_token_width),
            prefix_part: prefix,
            name_part: name_with_padding,
            name_color,
            bar_part: format!("│{}│", bar),
            percentage_part: percentage_str,
        });
    }

    lines
}

// Format token counts with K/M suffixes (dust-style)
fn format_tokens(tokens: usize) -> String {
    if tokens >= 1_000_000 {
        let millions = (tokens + 500_000) / 1_000_000;
        format!("{}M", millions)
    } else if tokens >= 1_000 {
        let thousands = (tokens + 500) / 1_000;
        format!("{}K", thousands)
    } else {
        format!("{}", tokens)
    }
}

// Generate bar with dust-style depth shading
fn generate_hierarchical_bar(
    bar_width: usize,
    parent_bar: &str,
    percentage: f64,
    depth: usize,
) -> String {
    // Calculate how many characters should be filled for this entry
    let filled_chars = ((percentage / 100.0) * bar_width as f64).round() as usize;
    let mut result = String::new();

    // Depth determines which shade to use for parent's solid blocks
    let shade_char = match depth.max(1) {
        1 => ' ', // Level 1: parent blocks become spaces
        2 => '░', // Level 2: light shade
        3 => '▒', // Level 3: medium shade
        _ => '▓', // Level 4+: dark shade
    };

    // Process each character position
    let parent_chars: Vec<char> = parent_bar.chars().collect();
    for i in 0..bar_width {
        if i < filled_chars {
            // This is our filled portion - always solid
            result.push('█');
        } else if i < parent_chars.len() {
            // This is parent's portion
            let parent_char = parent_chars[i];
            if parent_char == '█' {
                // Replace parent's solid blocks with our shade
                result.push(shade_char);
            } else {
                // Keep parent's existing shading
                result.push(parent_char);
            }
        } else {
            // Beyond parent's bar - empty
            result.push(' ');
        }
    }

    result
}


================================================
FILE: crates/code2prompt/src/tui.rs
================================================
//! Terminal User Interface implementation.
//!
//! This module implements the complete TUI for code2prompt using ratatui and crossterm.
//! It provides a tabbed interface with file selection, settings configuration,
//! statistics viewing, and prompt output. The interface supports keyboard navigation,
//! file tree browsing, real-time analysis, and clipboard integration.

use anyhow::Result;
use code2prompt_core::session::Code2PromptSession;
use crossterm::{
    execute,
    terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{
    crossterm::event::{KeyCode, KeyEvent, KeyModifiers},
    prelude::*,
    widgets::*,
};
use std::io::{Stdout, stdout};
use tokio::sync::mpsc;

use crate::clipboard::copy_to_clipboard;
use crate::model::{
    AnalysisResults, Cmd, FileTreeInputMode, Message, Model, StatisticsView, Tab, TemplateState,
    template::{FocusMode, TemplateFocus, VariableCategory},
};
use crate::token_map::generate_token_map_with_limit;
use crate::utils::{save_template_to_custom_dir, save_to_file};
use crate::widgets::{
    FileSelectionWidget, OutputWidget, SettingsWidget, StatisticsByExtensionWidget,
    StatisticsOverviewWidget, StatisticsTokenMapWidget, TemplateWidget,
};

use crate::utils::build_file_tree_from_session;

pub struct TuiApp {
    model: Model,
    terminal: Terminal<CrosstermBackend<Stdout>>,
    message_tx: mpsc::UnboundedSender<Message>,
    message_rx: mpsc::UnboundedReceiver<Message>,
}

impl TuiApp {
    /// Create a new TUI application.
    ///
    /// Initializes the terminal and sets up the application state from the provided session.
    /// The initial file tree is requested via a `RefreshFileTree` message in `run()`.
    ///
    /// Returns an error if the terminal cannot be initialized.
    pub fn new(session: Code2PromptSession) -> Result<Self> {
        let terminal = init_terminal()?;
        let (message_tx, message_rx) = mpsc::unbounded_channel();
        let model = Model::new(session);

        Ok(Self {
            model,
            terminal,
            message_tx,
            message_rx,
        })
    }

    // ~~~ Optimized Main Loop ~~~
    pub async fn run(&mut self) -> Result<()> {
        // Initialize file tree
        self.handle_message(Message::RefreshFileTree)?;

        loop {
            // Process all available events with coalescing
            let mut messages = Vec::new();

            // Drain all available keyboard events
            while crossterm::event::poll(std::time::Duration::from_millis(0))? {
                if let crossterm::event::Event::Key(key) = crossterm::event::read()?
                    && key.kind == crossterm::event::KeyEventKind::Press
                {
                    // Convert to ratatui KeyEvent
                    let ratatui_key = self.convert_crossterm_key(key);

                    // Handle the key event
                    if let Some(message) = self.handle_key_event(ratatui_key) {
                        if let Some(last_message) = messages.last_mut()
                            && self.try_coalesce_messages(last_message, &message)
                        {
                            continue; // Message was coalesced
                        }
                        messages.push(message);
                    }
                }
            }

            // Handle all messages
            for message in messages {
                self.handle_message(message)?;
            }

            // Handle internal messages (non-blocking)
            while let Ok(message) = self.message_rx.try_recv() {
                self.handle_message(message)?;
            }

            // Render the UI
            let model = self.model.clone();
            self.terminal.draw(|frame| {
                TuiApp::render_with_model(&model, frame);
            })?;

            if self.model.should_quit {
                break;
            }

            // Small sleep to prevent busy waiting
            tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
        }

        Ok(())
    }

    /// Render the TUI using the provided model and frame.
    ///
    /// This function handles the layout and rendering of all components based on the current state.
    /// It divides the terminal into sections for the tab bar, content area, and status bar,
    /// and renders the appropriate widgets for the active tab.
    ///
    /// # Arguments
    ///
    /// * `model` - The current application state model
    /// * `frame` - The frame to render the UI components onto
    ///
    fn render_with_model(model: &Model, frame: &mut Frame) {
        let area = frame.area();

        // ~~~ Main layout ~~~
        let main_layout = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Length(3), // Tab bar
                Constraint::Min(0),    // Content
                Constraint::Length(3), // Status bar
            ])
            .split(area);

        // Tab bar
        Self::render_tab_bar_static(model, frame, main_layout[0]);

        // Current tab content
        match model.current_tab {
            Tab::FileTree => {
                let widget = FileSelectionWidget::new(model);
                let mut state = ();
                frame.render_stateful_widget(widget, main_layout[1], &mut state);
            }
            Tab::Settings => {
                let widget = SettingsWidget::new(model);
                let mut state = ();
                frame.render_stateful_widget(widget, main_layout[1], &mut state);
            }
            Tab::Statistics => match model.statistics.view {
                StatisticsView::Overview => {
                    let widget = StatisticsOverviewWidget::new(model);
                    frame.render_widget(widget, main_layout[1]);
                }
                StatisticsView::TokenMap => {
                    let widget = StatisticsTokenMapWidget::new(model);
                    let mut state = ();
                    frame.render_stateful_widget(widget, main_layout[1], &mut state);
                }
                StatisticsView::Extensions => {
                    let widget = StatisticsByExtensionWidget::new(model);
                    let mut state = ();
                    frame.render_stateful_widget(widget, main_layout[1], &mut state);
                }
            },
            Tab::Template => {
                let widget = TemplateWidget::new(model);
                let mut state = TemplateState::from_model(model);
                frame.render_stateful_widget(widget, main_layout[1], &mut state);
            }
            Tab::PromptOutput => {
                let widget = OutputWidget::new(model);
                let mut state = ();
                frame.render_stateful_widget(widget, main_layout[1], &mut state);
            }
        }

        // Status bar
        Self::render_status_bar_static(model, frame, main_layout[2]);
    }

    /// Handle a key event and return an optional message.
    ///
    /// This function processes keyboard input, prioritizing search mode
    /// when active. It handles global shortcuts for tab switching and quitting,
    /// as well as delegating tab-specific key events to the appropriate handlers.
    /// # Arguments
    ///
    /// * `key` - The key event to handle.
    ///
    /// # Returns
    ///
    /// * `Option<Message>` - An optional message to be processed by the main loop.
    ///   
    fn handle_key_event(&self, key: KeyEvent) -> Option<Message> {
        // Check if we're in search mode first - this takes priority over global shortcuts
        if self.model.file_tree_input_mode == FileTreeInputMode::Search
            && self.model.current_tab == Tab::FileTree
        {
            return self.handle_file_tree_keys(key);
        }

        // Check if we're in template editing mode - ESC should exit editing mode, not quit app
        if self.model.current_tab == Tab::Template && self.model.template.is_in_editing_mode() {
            if key.code == KeyCode::Esc {
                return Some(Message::SetTemplateFocusMode(FocusMode::Normal));
            }
            // In editing modes, delegate to template handler
            return self.handle_template_keys(key);
        }

        // Global shortcuts (only when not in search mode or template editing mode)
        match key.code {
            KeyCode::Char('q') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                return Some(Message::Quit);
            }
            KeyCode::Esc => return Some(Message::Quit),
            KeyCode::Char('1') => return Some(Message::SwitchTab(Tab::FileTree)),
            KeyCode::Char('2') => return Some(Message::SwitchTab(Tab::Settings)),
            KeyCode::Char('3') => return Some(Message::SwitchTab(Tab::Statistics)),
            KeyCode::Char('4') => return Some(Message::SwitchTab(Tab::Template)),
            KeyCode::Char('5') => return Some(Message::SwitchTab(Tab::PromptOutput)),
            KeyCode::Tab if !key.modifiers.contains(KeyModifiers::SHIFT) => {
                // Cycle through tabs: Selection -> Settings -> Statistics -> Template -> Output -> Selection
                let next_tab = match self.model.current_tab {
                    Tab::FileTree => Tab::Settings,
                    Tab::Settings => Tab::Statistics,
                    Tab::Statistics => Tab::Template,
                    Tab::Template => Tab::PromptOutput,
                    Tab::PromptOutput => Tab::FileTree,
                };
                return Some(Message::SwitchTab(next_tab));
            }
            KeyCode::BackTab | KeyCode::Tab if key.modifiers.contains(KeyModifiers::SHIFT) => {
                // Cycle through tabs in reverse: Selection <- Settings <- Statistics <- Template <- Output <- Selection
                let prev_tab = match self.model.current_tab {
                    Tab::FileTree => Tab::PromptOutput,
                    Tab::Settings => Tab::FileTree,
                    Tab::Statistics => Tab::Settings,
                    Tab::Template => Tab::Statistics,
                    Tab::PromptOutput => Tab::Template,
                };
                return Some(Message::SwitchTab(prev_tab));
            }
            _ => {}
        }

        // Tab-specific shortcuts
        match self.model.current_tab {
            Tab::FileTree => self.handle_file_tree_keys(key),
            Tab::Settings => self.handle_settings_keys(key),
            Tab::Statistics => self.handle_statistics_keys(key),
            Tab::Template => self.handle_template_keys(key),
            Tab::PromptOutput => self.handle_prompt_output_keys(key),
        }
    }

    fn handle_file_tree_keys(&self, key: KeyEvent) -> Option<Message> {
        // Pure logic in TUI - no direct widget calls (Elm/Redux pattern)
        if self.model.file_tree_input_mode == FileTreeInputMode::Search {
            match key.code {
                KeyCode::Esc => Some(Message::ExitSearchMode),
                KeyCode::Enter => {
                    // Apply search and exit search mode
                    Some(Message::ExitSearchMode)
                }
                KeyCode::Backspace => {
                    let mut query = self.model.search_query.clone();
                    query.pop();
                    Some(Message::UpdateSearchQuery(query))
                }
                KeyCode::Char(c) => {
                    let mut query = self.model.search_query.clone();
                    query.push(c);
                    Some(Message::UpdateSearchQuery(query))
                }
                _ => None,
            }
        } else {
            // Normal navigation mode
            match key.code {
                KeyCode::Up => Some(Message::MoveTreeCursor(-1)),
                KeyCode::Down => Some(Message::MoveTreeCursor(1)),
                KeyCode::PageUp => Some(Message::MoveTreeCursor(-10)),
                KeyCode::PageDown => Some(Message::MoveTreeCursor(10)),
                KeyCode::Home => Some(Message::MoveTreeCursor(-9999)),
                KeyCode::End => Some(Message::MoveTreeCursor(9999)),
                KeyCode::Char(' ') => Some(Message::ToggleFileSelection(self.model.tree_cursor)),
                KeyCode::Enter => Some(Message::RunAnalysis),
                KeyCode::Right => Some(Message::ExpandDirectory(self.model.tree_cursor)),
                KeyCode::Left => Some(Message::CollapseDirectory(self.model.tree_cursor)),
                KeyCode::Char('/') => Some(Message::EnterSearchMode),
                KeyCode::Char('s') | KeyCode::Char('S') => Some(Message::EnterSearchMode),
                KeyCode::Char('r') | KeyCode::Char('R') => Some(Message::RefreshFileTree),
                _ => None,
            }
        }
    }

    fn handle_settings_keys(&self, key: KeyEvent) -> Option<Message> {
        match key.code {
            KeyCode::Up => Some(Message::MoveSettingsCursor(-1)),
            KeyCode::Down => Some(Message::MoveSettingsCursor(1)),
            KeyCode::Char(' ') => Some(Message::ToggleSetting(self.model.settings.settings_cursor)),
            KeyCode::Left | KeyCode::Right => {
                Some(Message::CycleSetting(self.model.settings.settings_cursor))
            }
            KeyCode::Enter => Some(Message::RunAnalysis),
            _ => None,
        }
    }

    fn handle_statistics_keys(&self, key: KeyEvent) -> Option<Message> {
        match key.code {
            KeyCode::Enter => Some(Message::RunAnalysis),
            KeyCode::Left => Some(Message::CycleStatisticsView(-1)), // Previous view
            KeyCode::Right => Some(Message::CycleStatisticsView(1)), // Next view
            KeyCode::Up => Some(Message::ScrollStatistics(-1)),
            KeyCode::Down => Some(Message::ScrollStatistics(1)),
            KeyCode::PageUp => Some(Message::ScrollStatistics(-5)),
            KeyCode::PageDown => Some(Message::ScrollStatistics(5)),
            KeyCode::Home => Some(Message::ScrollStatistics(-9999)),
            KeyCode::End => Some(Message::ScrollStatistics(9999)),
            _ => None,
        }
    }

    fn handle_template_keys(&self, key: KeyEvent) -> Option<Message> {
        let is_in_editing_mode = self.model.template.is_in_editing_mode();
        let current_focus = self.model.template.get_focus();

        // Handle ESC key to exit editing modes
        if key.code == KeyCode::Esc && is_in_editing_mode {
            return Some(Message::SetTemplateFocusMode(FocusMode::Normal));
        }

        if is_in_editing_mode {
            match current_focus {
                TemplateFocus::Editor => {
                    return Some(Message::TemplateEditorInput(key));
                }
                TemplateFocus::Variables => {
                    if self.model.template.variables.is_editing() {
                        // Currently editing a variable value
                        match key.code {
                            KeyCode::Char(c) => return Some(Message::VariableInputChar(c)),
                            KeyCode::Backspace => return Some(Message::VariableInputBackspace),
                            KeyCode::Enter => return Some(Message::VariableInputEnter),
                            KeyCode::Esc => return Some(Message::VariableInputCancel),
                            _ => return None,
                        }
                    } else {
                        // Navigating variables list
                        match key.code {
                            KeyCode::Up => return Some(Message::VariableNavigateUp),
                            KeyCode::Down => return Some(Message::VariableNavigateDown),
                            KeyCode::Enter | KeyCode::Char(' ') => {
                                // Start editing the current variable
                                let variables = self.model.template.get_organized_variables();
                                if let Some(var) =
                                    variables.get(self.model.template.variables.cursor)
                                    && var.category == VariableCategory::Missing
                                {
                                    return Some(Message::VariableStartEditing(var.name.clone()));
                                }
                                return None;
                            }
                            _ => return None,
                        }
                    }
                }
                _ => {}
            }
        }

        // Normal mode: Handle global shortcuts and focus switching
        match key.code {
            KeyCode::Char('e') | KeyCode::Char('E') => {
                return Some(Message::SetTemplateFocus(
                    TemplateFocus::Editor,
                    FocusMode::EditingTemplate,
                ));
            }
            KeyCode::Char('v') | KeyCode::Char('V') => {
                return Some(Message::SetTemplateFocus(
                    TemplateFocus::Variables,
                    FocusMode::EditingVariable,
                ));
            }
            KeyCode::Char('p') | KeyCode::Char('P') => {
                return Some(Message::SetTemplateFocus(
                    TemplateFocus::Picker,
                    FocusMode::Normal,
                ));
            }
            KeyCode::Char('s') | KeyCode::Char('S') => {
                // Save template with timestamp
                let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
                let filename = format!("custom_template_{}", timestamp);
                return Some(Message::SaveTemplate(filename));
            }
            KeyCode::Char('r') | KeyCode::Char('R') => {
                // Reload default template
                return Some(Message::ReloadTemplate);
            }
            KeyCode::Enter => {
                // Run analysis
                return Some(Message::RunAnalysis);
            }
            _ => {}
        }

        // Handle input for focused component in normal mode
        if current_focus == TemplateFocus::Picker {
            match key.code {
                KeyCode::Up => return Some(Message::TemplatePickerMove(-1)),
                KeyCode::Down => return Some(Message::TemplatePickerMove(1)),
                KeyCode::Enter | KeyCode::Char('l') | KeyCode::Char('L') | KeyCode::Char(' ') => {
                    return Some(Message::LoadTemplate);
                }
                KeyCode::Char('r') | KeyCode::Char('R') => {
                    return Some(Message::RefreshTemplates);
                }
                _ => {}
            }
        }

        None
    }

    fn handle_prompt_output_keys(&self, key: KeyEvent) -> Option<Message> {
        match key.code {
            KeyCode::Up => Some(Message::ScrollOutput(-1)),
            KeyCode::Down => Some(Message::ScrollOutput(1)),
            KeyCode::PageUp => Some(Message::ScrollOutput(-10)),
            KeyCode::PageDown => Some(Message::ScrollOutput(10)),
            KeyCode::Home => Some(Message::ScrollOutput(-9999)),
            KeyCode::End => Some(Message::ScrollOutput(9999)),
            KeyCode::Char('c') | KeyCode::Char('C') => Some(Message::CopyToClipboard),
            KeyCode::Char('s') | KeyCode::Char('S') => {
                let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
                let filename = format!("prompt_{}.md", timestamp);
                Some(Message::SaveToFile(filename))
            }
            KeyCode::Enter => Some(Message::RunAnalysis),
            _ => None,
        }
    }

    /// Handle a message using the Elm/Redux pattern.
    /// This uses the pure Model::update() function and executes any side effects.
    fn handle_message(&mut self, message: Message) -> Result<()> {
        let (new_model, cmd) = self.model.update(message);
        self.model = new_model;

        // Execute any side effects
        self.execute_cmd(cmd)?;

        Ok(())
    }

    /// Execute a command (side effect) from the Model::update() function.
    /// This is where all the impure operations happen.
    fn execute_cmd(&mut self, cmd: Cmd) -> Result<()> {
        match cmd {
            Cmd::None => {
                // No side effect
            }

            Cmd::RefreshFileTree => {
                // Always use session-based tree building for proper pattern initialization
                match build_file_tree_from_session(&mut self.model.session) {
                    Ok(tree) => {
                        self.model.file_tree_nodes = tree;
                        self.model.status_message =
                            "File tree loaded with patterns applied and files auto-expanded"
                                .to_string();
                    }
                    Err(e) => {
                        self.model.status_message = format!("Error loading files: {}", e);
                    }
                }
            }

            Cmd::RunAnalysis {
                template_content,
                user_variables,
            } => {
                // Use the current session state (with all user selections)
                let mut session = self.model.session.clone();
                let tx = self.message_tx.clone();

                tokio::spawn(async move {
                    // Set custom template content
                    session.config.template_str = template_content;
                    session.config.template_name = "Custom Template".to_string();

                    // Transfer user variables from TUI to session config
                    session.config.user_variables = user_variables;

                    match session.generate_prompt() {
                        Ok(rendered) => {
                            // Convert to AnalysisResults format expected by TUI
                            let token_map_entries = if rendered.token_count > 0 {
                                if let Some(files) = session.data.files.as_ref() {
                                    generate_token_map_with_limit(
                                        files,
                                        rendered.token_count,
                                        Some(50),
                                        Some(0.5),
                                    )
                                } else {
                                    Vec::new()
                                }
                            } else {
                                Vec::new()
                            };

                            let result = AnalysisResults {
                                file_count: rendered.files.len(),
                                token_count: Some(rendered.token_count),
                                generated_prompt: rendered.prompt,
                                token_map_entries,
                            };
                            let _ = tx.send(Message::AnalysisComplete(result));
                        }
                        Err(e) => {
                            let _ = tx.send(Message::AnalysisError(e.to_string()));
                        }
                    }
                });
            }

            Cmd::CopyToClipboard(content) => match copy_to_clipboard(&content) {
                Ok(_) => {
                    self.model.status_message = "Copied to clipboard!".to_string();
                }
                Err(e) => {
                    self.model.status_message = format!("Copy failed: {}", e);
                }
            },

            Cmd::SaveToFile { filename, content } => {
                match save_to_file(std::path::Path::new(&filename), &content) {
                    Ok(_) => {
                        self.model.status_message = format!("Saved to {}", filename);
                    }
                    Err(e) => {
                        self.model.status_message = format!("Save failed: {}", e);
                    }
                }
            }

            Cmd::SaveTemplate { filename, content } => {
                match save_template_to_custom_dir(std::path::Path::new(&filename), &content) {
                    Ok(_) => {
                        self.model.status_message = format!("Template saved as {}", filename);
                        // Refresh templates to show the new one
                        self.model.template.picker.refresh();
                    }
                    Err(e) => {
                        self.model.status_message = format!("Template save failed: {}", e);
                    }
                }
            }
        }

        Ok(())
    }

    fn render_tab_bar_static(model: &Model, frame: &mut Frame, area: Rect) {
        let tabs = vec![
            "1. Selection",
            "2. Settings",
            "3. Statistics",
            "4. Template",
            "5. Output",
        ];
        let selected = match model.current_tab {
            Tab::FileTree => 0,
            Tab::Settings => 1,
            Tab::Statistics => 2,
            Tab::Template => 3,
            Tab::PromptOutput => 4,
        };

        let tabs_widget = Tabs::new(tabs)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .title("Code2Prompt TUI"),
            )
            .select(selected)
            .style(Style::default().fg(Color::White))
            .highlight_style(
                Style::default()
                    .fg(Color::Yellow)
                    .add_modifier(Modifier::BOLD),
            );

        frame.render_widget(tabs_widget, area);
    }

    fn render_status_bar_static(model: &Model, frame: &mut Frame, area: Rect) {
        let status_text = if !model.status_message.is_empty() {
            model.status_message.clone()
        } else {
            "Tab/Shift+Tab: Switch tabs | 1/2/3/4: Direct tab | Enter: Run Analysis | Esc/Ctrl+Q: Quit".to_string()
        };

        let status_widget = Paragraph::new(status_text)
            .block(Block::default().borders(Borders::ALL))
            .style(Style::default().fg(Color::Cyan));
        frame.render_widget(status_widget, area);
    }

    /// Convert crossterm KeyEvent to ratatui KeyEvent
    fn convert_crossterm_key(&self, key: crossterm::event::KeyEvent) -> KeyEvent {
        use ratatui::crossterm::event::{KeyCode, KeyEventKind, KeyEventState, KeyModifiers};

        KeyEvent {
            code: match key.code {
                crossterm::event::KeyCode::Backspace => KeyCode::Backspace,
                crossterm::event::KeyCode::Enter => KeyCode::Enter,
                crossterm::event::KeyCode::Left => KeyCode::Left,
                crossterm::event::KeyCode::Right => KeyCode::Right,
                crossterm::event::KeyCode::Up => KeyCode::Up,
                crossterm::event::KeyCode::Down => KeyCode::Down,
                crossterm::event::KeyCode::Home => KeyCode::Home,
                crossterm::event::KeyCode::End => KeyCode::End,
                crossterm::event::KeyCode::PageUp => KeyCode::PageUp,
                crossterm::event::KeyCode::PageDown => KeyCode::PageDown,
                crossterm::event::KeyCode::Tab => KeyCode::Tab,
                crossterm::event::KeyCode::BackTab => KeyCode::BackTab,
                crossterm::event::KeyCode::Delete => KeyCode::Delete,
                crossterm::event::KeyCode::Insert => KeyCode::Insert,
                crossterm::event::KeyCode::F(n) => KeyCode::F(n),
                crossterm::event::KeyCode::Char(c) => KeyCode::Char(c),
                crossterm::event::KeyCode::Null => KeyCode::Null,
                crossterm::event::KeyCode::Esc => KeyCode::Esc,
                _ => KeyCode::Null, // Simplified for other key codes
            },
            modifiers: KeyModifiers::from_bits_truncate(key.modifiers.bits()),
            kind: match key.kind {
                crossterm::event::KeyEventKind::Press => KeyEventKind::Press,
                crossterm::event::KeyEventKind::Repeat => KeyEventKind::Repeat,
                crossterm::event::KeyEventKind::Release => KeyEventKind::Release,
            },
            state: KeyEventState::from_bits_truncate(key.state.bits()),
        }
    }

    /// Try to coalesce two messages if they are similar (e.g., scroll events)
    fn try_coalesce_messages(&self, last_message: &mut Message, new_message: &Message) -> bool {
        match (last_message, new_message) {
            (Message::MoveTreeCursor(delta1), Message::MoveTreeCursor(delta2)) => {
                *delta1 += delta2;
                true
            }
            (Message::MoveSettingsCursor(delta1), Message::MoveSettingsCursor(delta2)) => {
                *delta1 += delta2;
                true
            }
            (Message::ScrollStatistics(delta1), Message::ScrollStatistics(delta2)) => {
                *delta1 += delta2;
                true
            }
            (Message::ScrollOutput(delta1), Message::ScrollOutput(delta2)) => {
                *delta1 += delta2;
                true
            }
            (Message::TemplatePickerMove(delta1), Message::TemplatePickerMove(delta2)) => {
                *delta1 += delta2;
                true
            }
            _ => false, // Cannot coalesce these messages
        }
    }
}

/// Run the Terminal User Interface.
///
/// This is the main entry point for the TUI mode. It parses command-line arguments,
/// initializes the TUI application, and runs the main event loop until the user exits.
///
/// # Returns
///
/// * `Result<()>` - Ok on successful exit, Err if initialization or runtime errors occur
///
/// # Errors
///
/// Returns an error if the TUI cannot be initialized or if runtime errors occur during execution.
pub async fn run_tui(session: Code2PromptSession) -> Result<()> {
    let mut app = TuiApp::new(session)?;

    let result = app.run().await;

    // Clean up terminal
    restore_terminal()?;

    result
}

fn init_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
    enable_raw_mode()?;
    let mut stdout = stdout();
    execute!(stdout, EnterAlternateScreen)?;
    let backend = CrosstermBackend::new(stdout);
    Terminal::new(backend).map_err(Into::into)
}

fn restore_terminal() -> Result<()> {
    disable_raw_mode()?;
    execute!(stdout(), LeaveAlternateScreen)?;
    Ok(())
}


================================================
FILE: crates/code2prompt/src/utils.rs
================================================
//! Utility functions for the TUI application.
//!
//! This module contains helper functions for building file trees,
//! managing file operations, and other utility functions used throughout the TUI.

use crate::model::DisplayFileNode;
use anyhow::Result;
use code2prompt_core::session::Code2PromptSession;
use regex::Regex;
use std::path::Path;

/// Build hierarchical file tree from session using traverse_directory with SelectionEngine
pub fn build_file_tree_from_session(
    session: &mut Code2PromptSession,
) -> Result<Vec<DisplayFileNode>> {
    let mut root_nodes = Vec::new();

    // Build root level nodes using ignore crate to respect gitignore
    use ignore::WalkBuilder;
    let walker = WalkBuilder::new(&session.config.path)
        .max_depth(Some(1))
        .git_ignore(!session.config.no_ignore) // Respect the no_ignore flag
        .hidden(!session.config.hidden) // Also respect the hidden flag for consistency
        .build();

    for entry in walker {
        let entry = entry?;
        let path = entry.path();

        if path == session.config.path {
            continue; // Skip root directory itself
        }

        let mut node = DisplayFileNode::new(path.to_path_buf(), 0);

        // Auto-expand recursively if directory contains selected files
        if node.is_directory {
            auto_expand_recursively(&mut node, session);
        }

        root_nodes.push(node);
    }

    // Sort root nodes: directories first, then alphabetically
    root_nodes.sort_by(|a, b| match (a.is_directory, b.is_directory) {
        (true, false) => std::cmp::Ordering::Less,
        (false, true) => std::cmp::Ordering::Greater,
        _ => a.name.cmp(&b.name),
    });

    Ok(root_nodes)
}

/// Recursively auto-expand directories that contain selected files
fn auto_expand_recursively(node: &mut DisplayFileNode, session: &mut Code2PromptSession) {
    if !node.is_directory {
        return;
    }

    if directory_contains_selected_files(&node.path, session) {
        node.is_expanded = true;
        // Load children
        if let Err(e) = node.load_children(session) {
            eprintln!("Warning: Failed to load children for {}: {}", node.name, e);
            return;
        }

        // Recursively auto-expand children
        for child in &mut node.children {
            if child.is_directory {
                auto_expand_recursively(child, session);
            }
        }
    }
}

/// Check if a directory contains any selected files (helper function)
pub(crate) fn directory_contains_selected_files(
    dir_path: &Path,
    session: &mut Code2PromptSession,
) -> bool {
    if let Ok(entries) = std::fs::read_dir(dir_path) {
        for entry in entries.flatten() {
            let path = entry.path();
            let relative_path = if let Ok(rel) = path.strip_prefix(&session.config.path) {
                rel
            } else {
                continue;
            };

            if session.is_file_selected(relative_path) {
                return true;
            }

            // Recursively check subdirectories
            if path.is_dir() && directory_contains_selected_files(&path, session) {
                return true;
            }
        }
    }
    false
}

/// Get visible nodes for display (flattened tree with search filtering)
pub fn get_visible_nodes(
    nodes: &[DisplayFileNode],
    search_query: &str,
    session: &mut Code2PromptSession,
) -> Vec<DisplayNodeWithSelection> {
    let mut visible = Vec::new();
    let search_active = !search_query.is_empty();
    let matcher = build_query_matcher(search_query);
    collect_visible_nodes_recursive(nodes, &matcher, session, &mut visible, search_active);
    visible
}

/// Simple matcher that supports case-insensitive substring and '*'/'?' wildcards.
enum QueryMatcher {
    Substr(String),
    Regex(Regex),
}

fn build_query_matcher(raw: &str) -> QueryMatcher {
    // Trim incidental whitespace for more predictable matches.
    let raw = raw.trim();
    let has_wildcards = raw.contains('*') || raw.contains('?');
    if has_wildcards {
        // Escape regex meta, then re-introduce wildcards
        let mut pat = regex::escape(raw);
        pat = pat.replace(r"\*", ".*").replace(r"\?", ".");
        let anchored = format!("(?i)^{}$", pat); // (?i) = case-insensitive
        QueryMatcher::Regex(Regex::new(&anchored).unwrap_or_else(|_| Regex::new(".*").unwrap()))
    } else {
        QueryMatcher::Substr(raw.to_lowercase())
    }
}

fn matches(m: &QueryMatcher, text: &str) -> bool {
    match m {
        QueryMatcher::Substr(needle) => text.to_lowercase().contains(needle),
        QueryMatcher::Regex(re) => re.is_match(text),
    }
}

/// Node with selection state for display
#[derive(Debug, Clone)]
pub struct DisplayNodeWithSelection {
    pub node: DisplayFileNode,
    pub is_selected: bool,
}

/// Recursively collect visible nodes
fn collect_visible_nodes_recursive(
    nodes: &[DisplayFileNode],
    matcher: &QueryMatcher,
    session: &mut Code2PromptSession,
    visible: &mut Vec<DisplayNodeWithSelection>,
    search_active: bool,
) {
    for node in nodes {
        // Case-insensitive match on name or full path (with optional wildcards)
        let matches_current = if matches!(matcher, QueryMatcher::Substr(s) if s.is_empty()) {
            true
        } else {
            matches(matcher, &node.name) || matches(matcher, &node.path.to_string_lossy())
        };

        if search_active {
            // In search mode, traverse into directories regardless of expansion
            let mut child_results: Vec<DisplayNodeWithSelection> = Vec::new();
            if node.is_directory {
                let children = get_children_for_search(node, session);
                collect_visible_nodes_recursive(
                    &children,
                    matcher,
                    session,
                    &mut child_results,
                    true,
                );
            }

            let include_self = matches_current || !child_results.is_empty();

            if include_self {
                let relative_path = if let Ok(rel) = node.path.strip_prefix(&session.config.path) {
                    rel
                } else {
                    &node.path
                };
                let is_selected = session.is_file_selected(relative_path);

                // Show directories as expanded in search results for better context
                let mut node_clone = node.clone();
                if node_clone.is_directory {
                    node_clone.is_expanded = true;
                }

                visible.push(DisplayNodeWithSelection {
                    node: node_clone,
                    is_selected,
                });

                visible.extend(child_results);
            }
        } else {
            // Normal mode: only include node if it matches (empty query matches all)
            if matches_current {
                let relative_path = if let Ok(rel) = node.path.strip_prefix(&session.config.path) {
                    rel
                } else {
                    &node.path
                };
                let is_selected = session.is_file_selected(relative_path);

                visible.push(DisplayNodeWithSelection {
                    node: node.clone(),
                    is_selected,
                });

                // Only descend if the directory is expanded
                if node.is_directory && node.is_expanded {
                    collect_visible_nodes_recursive(
                        &node.children,
                        matcher,
                        session,
                        visible,
                        false,
                    );
                }
            }
        }
    }
}

/// Save content to a file
pub fn save_to_file(path: &Path, content: &str) -> Result<()> {
    std::fs::write(path, content)?;
    Ok(())
}

/// Format a number with thousand separators according to TokenFormat
///
/// - TokenFormat::Raw: returns the number as-is (e.g., "1234567")
/// - TokenFormat::Format: adds separators every 3 digits (e.g., "1,234,567")
///
/// # Arguments
/// * `num` - The number to format
/// * `format` - The token format setting
///
/// # Returns
/// Formatted string representation of the number
pub fn format_number(num: usize, format: &code2prompt_core::tokenizer::TokenFormat) -> String {
    use code2prompt_core::tokenizer::TokenFormat;

    match format {
        TokenFormat::Raw => num.to_string(),
        TokenFormat::Format => {
            let s = num.to_string();
            let chars: Vec<char> = s.chars().collect();
            let mut result = String::new();

            for (i, c) in chars.iter().enumerate() {
                if i > 0 && (chars.len() - i).is_multiple_of(3) {
                    result.push(',');
                }
                result.push(*c);
            }
            result
        }
    }
}

/// Load children for search mode without mutating the original tree
fn get_children_for_search(
    node: &DisplayFileNode,
    session: &mut Code2PromptSession,
) -> Vec<DisplayFileNode> {
    if !node.is_directory {
        return Vec::new();
    }

    if node.children_loaded {
        return node.children.clone()
Download .txt
gitextract_2efzl1fk/

├── .assets/
│   └── flow_diagram.md
├── .c2pconfig
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yml
│       ├── release.yml
│       └── website.yml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── README.md
├── README_ES.md
├── crates/
│   ├── code2prompt/
│   │   ├── Cargo.toml
│   │   ├── src/
│   │   │   ├── args.rs
│   │   │   ├── clipboard.rs
│   │   │   ├── config.rs
│   │   │   ├── config_loader.rs
│   │   │   ├── main.rs
│   │   │   ├── model/
│   │   │   │   ├── commands.rs
│   │   │   │   ├── mod.rs
│   │   │   │   ├── prompt_output.rs
│   │   │   │   ├── settings.rs
│   │   │   │   ├── statistics/
│   │   │   │   │   ├── mod.rs
│   │   │   │   │   └── types.rs
│   │   │   │   └── template/
│   │   │   │       ├── editor.rs
│   │   │   │       ├── mod.rs
│   │   │   │       ├── picker.rs
│   │   │   │       └── variable.rs
│   │   │   ├── token_map.rs
│   │   │   ├── tui.rs
│   │   │   ├── utils.rs
│   │   │   ├── view/
│   │   │   │   ├── formatters.rs
│   │   │   │   └── mod.rs
│   │   │   └── widgets/
│   │   │       ├── file_selection.rs
│   │   │       ├── mod.rs
│   │   │       ├── output.rs
│   │   │       ├── settings.rs
│   │   │       ├── statistics_by_extension.rs
│   │   │       ├── statistics_overview.rs
│   │   │       ├── statistics_token_map.rs
│   │   │       └── template/
│   │   │           ├── editor.rs
│   │   │           ├── mod.rs
│   │   │           ├── picker.rs
│   │   │           └── variable.rs
│   │   └── tests/
│   │       ├── common/
│   │       │   ├── fixtures.rs
│   │       │   ├── mod.rs
│   │       │   └── test_env.rs
│   │       ├── config_test.rs
│   │       ├── git_integration_test.rs
│   │       ├── integration_test.rs
│   │       ├── std_output_test.rs
│   │       └── template_integration_test.rs
│   ├── code2prompt-core/
│   │   ├── Cargo.toml
│   │   ├── src/
│   │   │   ├── builtin_templates.rs
│   │   │   ├── configuration.rs
│   │   │   ├── default_template_md.hbs
│   │   │   ├── default_template_xml.hbs
│   │   │   ├── file_processor/
│   │   │   │   ├── csv.rs
│   │   │   │   ├── default.rs
│   │   │   │   ├── ipynb.rs
│   │   │   │   ├── jsonl.rs
│   │   │   │   ├── mod.rs
│   │   │   │   └── tsv.rs
│   │   │   ├── filter.rs
│   │   │   ├── git.rs
│   │   │   ├── lib.rs
│   │   │   ├── path.rs
│   │   │   ├── selection.rs
│   │   │   ├── session.rs
│   │   │   ├── sort.rs
│   │   │   ├── template.rs
│   │   │   ├── tokenizer.rs
│   │   │   └── util.rs
│   │   ├── templates/
│   │   │   ├── binary-exploitation-ctf-solver.hbs
│   │   │   ├── clean-up-code.hbs
│   │   │   ├── cryptography-ctf-solver.hbs
│   │   │   ├── document-the-code.hbs
│   │   │   ├── find-security-vulnerabilities.hbs
│   │   │   ├── fix-bugs.hbs
│   │   │   ├── improve-performance.hbs
│   │   │   ├── refactor.hbs
│   │   │   ├── reverse-engineering-ctf-solver.hbs
│   │   │   ├── web-ctf-solver.hbs
│   │   │   ├── write-git-commit.hbs
│   │   │   ├── write-github-pull-request.hbs
│   │   │   └── write-github-readme.hbs
│   │   └── tests/
│   │       ├── binary_detection_test.rs
│   │       ├── file_processor_test.rs
│   │       ├── filter_test.rs
│   │       ├── git_test.rs
│   │       ├── path_test.rs
│   │       ├── session_integration_test.rs
│   │       ├── sort_test.rs
│   │       ├── template_test.rs
│   │       └── util_test.rs
│   └── code2prompt-python/
│       ├── .python-version
│       ├── Cargo.toml
│       ├── pyproject.toml
│       ├── python-sdk/
│       │   ├── .gitignore
│       │   ├── README.md
│       │   ├── __init__.py
│       │   ├── code2prompt_rs/
│       │   │   ├── __init__.py
│       │   │   └── code2prompt.py
│       │   └── examples/
│       │       └── basic_usage.py
│       ├── src/
│       │   ├── lib.rs
│       │   ├── python.rs
│       │   └── python.rs.bak
│       └── tests/
│           ├── __init__.py
│           ├── conftest.py
│           ├── test_config.py
│           ├── test_generation.py
│           └── test_special_feature.py
├── llms-install.md
└── website/
    ├── .gitignore
    ├── .vscode/
    │   ├── extensions.json
    │   └── launch.json
    ├── README.md
    ├── astro.config.mjs
    ├── package.json
    ├── pnpm-workspace.yaml
    ├── public/
    │   ├── CNAME
    │   ├── assets/
    │   │   ├── css/
    │   │   │   └── marquee.css
    │   │   └── js/
    │   │       └── main.js
    │   └── prism-theme.css
    ├── src/
    │   ├── assets/
    │   │   └── examples/
    │   │       ├── history_notes/
    │   │       │   ├── history_notes/
    │   │       │   │   ├── history/
    │   │       │   │   │   ├── medieval.txt
    │   │       │   │   │   ├── renaissance.txt
    │   │       │   │   │   └── ww2.txt
    │   │       │   │   └── meta/
    │   │       │   │       └── my_revision_goals.txt
    │   │       │   ├── prompt.md
    │   │       │   └── question.txt
    │   │       ├── my_recipes/
    │   │       │   ├── my_recipes/
    │   │       │   │   ├── pantry/
    │   │       │   │   │   └── my_ingredients.txt
    │   │       │   │   └── recipes/
    │   │       │   │       ├── pasta.txt
    │   │       │   │       ├── pizza.txt
    │   │       │   │       ├── salad.txt
    │   │       │   │       └── soup.txt
    │   │       │   ├── prompt.md
    │   │       │   └── question.txt
    │   │       └── node_app/
    │   │           ├── node_app/
    │   │           │   ├── README.md
    │   │           │   ├── data/
    │   │           │   │   └── sample.json
    │   │           │   └── src/
    │   │           │       ├── index.js
    │   │           │       └── utils.js
    │   │           ├── prompt.md
    │   │           └── question.txt
    │   ├── components/
    │   │   ├── Footer.astro
    │   │   ├── Header.astro
    │   │   ├── Section0.astro
    │   │   ├── Section1.astro
    │   │   ├── Section2.astro
    │   │   ├── Section3.astro
    │   │   └── Section4.astro
    │   ├── content/
    │   │   └── docs/
    │   │       ├── blog/
    │   │       │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       ├── de/
    │   │       │   ├── blog/
    │   │       │   │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       │   └── docs/
    │   │       │       ├── explanations/
    │   │       │       │   ├── glob_pattern_filter.mdx
    │   │       │       │   ├── glob_patterns.md
    │   │       │       │   └── tokenizers.md
    │   │       │       ├── how_to/
    │   │       │       │   ├── filter_files.md
    │   │       │       │   ├── install.mdx
    │   │       │       │   └── ssh.md
    │   │       │       ├── references/
    │   │       │       │   ├── command_line_options.md
    │   │       │       │   └── default_template.md
    │   │       │       ├── tutorials/
    │   │       │       │   ├── getting_started.mdx
    │   │       │       │   ├── learn_filters.mdx
    │   │       │       │   └── learn_templates.mdx
    │   │       │       ├── vision.mdx
    │   │       │       └── welcome.mdx
    │   │       ├── docs/
    │   │       │   ├── explanations/
    │   │       │   │   ├── glob_pattern_filter.mdx
    │   │       │   │   ├── glob_patterns.md
    │   │       │   │   └── tokenizers.md
    │   │       │   ├── how_to/
    │   │       │   │   ├── filter_files.md
    │   │       │   │   ├── install.mdx
    │   │       │   │   └── ssh.md
    │   │       │   ├── references/
    │   │       │   │   ├── command_line_options.md
    │   │       │   │   └── default_template.md
    │   │       │   ├── tutorials/
    │   │       │   │   ├── configuration.mdx
    │   │       │   │   ├── getting_started.mdx
    │   │       │   │   ├── learn_filters.mdx
    │   │       │   │   └── learn_templates.mdx
    │   │       │   ├── vision.mdx
    │   │       │   └── welcome.mdx
    │   │       ├── es/
    │   │       │   ├── blog/
    │   │       │   │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       │   └── docs/
    │   │       │       ├── explanations/
    │   │       │       │   ├── glob_pattern_filter.mdx
    │   │       │       │   ├── glob_patterns.md
    │   │       │       │   └── tokenizers.md
    │   │       │       ├── how_to/
    │   │       │       │   ├── filter_files.md
    │   │       │       │   ├── install.mdx
    │   │       │       │   └── ssh.md
    │   │       │       ├── references/
    │   │       │       │   ├── command_line_options.md
    │   │       │       │   └── default_template.md
    │   │       │       ├── tutorials/
    │   │       │       │   ├── getting_started.mdx
    │   │       │       │   ├── learn_filters.mdx
    │   │       │       │   └── learn_templates.mdx
    │   │       │       ├── vision.mdx
    │   │       │       └── welcome.mdx
    │   │       ├── fr/
    │   │       │   ├── blog/
    │   │       │   │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       │   └── docs/
    │   │       │       ├── explanations/
    │   │       │       │   ├── glob_pattern_filter.mdx
    │   │       │       │   ├── glob_patterns.md
    │   │       │       │   └── tokenizers.md
    │   │       │       ├── how_to/
    │   │       │       │   ├── filter_files.md
    │   │       │       │   ├── install.mdx
    │   │       │       │   └── ssh.md
    │   │       │       ├── references/
    │   │       │       │   ├── command_line_options.md
    │   │       │       │   └── default_template.md
    │   │       │       ├── tutorials/
    │   │       │       │   ├── getting_started.mdx
    │   │       │       │   ├── learn_filters.mdx
    │   │       │       │   └── learn_templates.mdx
    │   │       │       ├── vision.mdx
    │   │       │       └── welcome.mdx
    │   │       ├── ja/
    │   │       │   ├── blog/
    │   │       │   │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       │   └── docs/
    │   │       │       ├── explanations/
    │   │       │       │   ├── glob_pattern_filter.mdx
    │   │       │       │   ├── glob_patterns.md
    │   │       │       │   └── tokenizers.md
    │   │       │       ├── how_to/
    │   │       │       │   ├── filter_files.md
    │   │       │       │   ├── install.mdx
    │   │       │       │   └── ssh.md
    │   │       │       ├── references/
    │   │       │       │   ├── command_line_options.md
    │   │       │       │   └── default_template.md
    │   │       │       ├── tutorials/
    │   │       │       │   ├── getting_started.mdx
    │   │       │       │   ├── learn_filters.mdx
    │   │       │       │   └── learn_templates.mdx
    │   │       │       ├── vision.mdx
    │   │       │       └── welcome.mdx
    │   │       ├── ru/
    │   │       │   ├── blog/
    │   │       │   │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │       │   └── docs/
    │   │       │       ├── explanations/
    │   │       │       │   ├── glob_pattern_filter.mdx
    │   │       │       │   ├── glob_patterns.md
    │   │       │       │   └── tokenizers.md
    │   │       │       ├── how_to/
    │   │       │       │   ├── filter_files.md
    │   │       │       │   ├── install.mdx
    │   │       │       │   └── ssh.md
    │   │       │       ├── references/
    │   │       │       │   ├── command_line_options.md
    │   │       │       │   └── default_template.md
    │   │       │       ├── tutorials/
    │   │       │       │   ├── getting_started.mdx
    │   │       │       │   ├── learn_filters.mdx
    │   │       │       │   └── learn_templates.mdx
    │   │       │       ├── vision.mdx
    │   │       │       └── welcome.mdx
    │   │       └── zh/
    │   │           ├── blog/
    │   │           │   └── 2025.04.11_why_I_wrote_code2prompt.mdx
    │   │           └── docs/
    │   │               ├── explanations/
    │   │               │   ├── glob_pattern_filter.mdx
    │   │               │   ├── glob_patterns.md
    │   │               │   └── tokenizers.md
    │   │               ├── how_to/
    │   │               │   ├── filter_files.md
    │   │               │   ├── install.mdx
    │   │               │   └── ssh.md
    │   │               ├── references/
    │   │               │   ├── command_line_options.md
    │   │               │   └── default_template.md
    │   │               ├── tutorials/
    │   │               │   ├── getting_started.mdx
    │   │               │   ├── learn_filters.mdx
    │   │               │   └── learn_templates.mdx
    │   │               ├── vision.mdx
    │   │               └── welcome.mdx
    │   ├── content.config.ts
    │   ├── layouts/
    │   │   ├── BaseLayout.astro
    │   │   └── BlogPostLayout.astro
    │   ├── pages/
    │   │   ├── index.astro
    │   │   └── robots.txt.ts
    │   └── styles/
    │       └── global.css
    └── tsconfig.json
Download .txt
SYMBOL INDEX (549 symbols across 73 files)

FILE: crates/code2prompt-core/src/builtin_templates.rs
  type BuiltinTemplate (line 11) | pub struct BuiltinTemplate {
  type BuiltinTemplates (line 18) | pub struct BuiltinTemplates;
    method get_all (line 24) | pub fn get_all() -> &'static HashMap<&'static str, BuiltinTemplate> {
    method get_template (line 152) | pub fn get_template(key: &str) -> Option<BuiltinTemplate> {
    method get_template_keys (line 157) | pub fn get_template_keys() -> Vec<&'static str> {
    method has_template (line 162) | pub fn has_template(key: &str) -> bool {

FILE: crates/code2prompt-core/src/configuration.rs
  type Code2PromptConfig (line 18) | pub struct Code2PromptConfig {
    method builder (line 94) | pub fn builder() -> Code2PromptConfigBuilder {
  type OutputDestination (line 102) | pub enum OutputDestination {
  type TomlConfig (line 112) | pub struct TomlConfig {
    method from_toml_str (line 161) | pub fn from_toml_str(content: &str) -> Result<Self, toml::de::Error> {
    method to_string (line 166) | pub fn to_string(&self) -> Result<String, toml::ser::Error> {
    method to_code2prompt_config (line 171) | pub fn to_code2prompt_config(&self) -> Code2PromptConfig {
  function export_config_to_toml (line 225) | pub fn export_config_to_toml(config: &Code2PromptConfig) -> Result<Strin...

FILE: crates/code2prompt-core/src/file_processor/csv.rs
  type CsvProcessor (line 18) | pub struct CsvProcessor;
    method process_with_delimiter (line 28) | pub(crate) fn process_with_delimiter(
  method process (line 80) | fn process(&self, content: &[u8], path: &Path) -> Result<String> {

FILE: crates/code2prompt-core/src/file_processor/default.rs
  type DefaultTextProcessor (line 17) | pub struct DefaultTextProcessor;
  method process (line 20) | fn process(&self, content: &[u8], _path: &Path) -> Result<String> {

FILE: crates/code2prompt-core/src/file_processor/ipynb.rs
  type JupyterNotebookProcessor (line 16) | pub struct JupyterNotebookProcessor;
    method process_with_fallback (line 109) | pub fn process_with_fallback(&self, content: &[u8], path: &Path) -> Re...
  method process (line 19) | fn process(&self, content: &[u8], _path: &Path) -> Result<String> {

FILE: crates/code2prompt-core/src/file_processor/jsonl.rs
  type JsonLinesProcessor (line 15) | pub struct JsonLinesProcessor;
    method process_with_fallback (line 64) | pub fn process_with_fallback(&self, content: &[u8], path: &Path) -> Re...
  method process (line 18) | fn process(&self, content: &[u8], _path: &Path) -> Result<String> {

FILE: crates/code2prompt-core/src/file_processor/mod.rs
  type FileProcessor (line 27) | pub trait FileProcessor: Send + Sync {
    method process (line 38) | fn process(&self, content: &[u8], path: &Path) -> Result<String>;
  function get_processor_for_extension (line 57) | pub fn get_processor_for_extension(extension: &str) -> Box<dyn FileProce...

FILE: crates/code2prompt-core/src/file_processor/tsv.rs
  type TsvProcessor (line 11) | pub struct TsvProcessor;
  method process (line 14) | fn process(&self, content: &[u8], path: &Path) -> Result<String> {

FILE: crates/code2prompt-core/src/filter.rs
  type FilterEngine (line 14) | pub struct FilterEngine {
    method new (line 21) | pub fn new(include_patterns: &[String], exclude_patterns: &[String]) -...
    method matches_patterns (line 29) | pub fn matches_patterns(&self, path: &Path) -> bool {
    method include_globset (line 34) | pub fn include_globset(&self) -> &GlobSet {
    method exclude_globset (line 39) | pub fn exclude_globset(&self) -> &GlobSet {
    method has_include_patterns (line 44) | pub fn has_include_patterns(&self) -> bool {
    method is_excluded (line 49) | pub fn is_excluded(&self, path: &Path) -> bool {
  function build_globset (line 67) | pub fn build_globset(patterns: &[String]) -> GlobSet {
  function should_include_file (line 131) | pub fn should_include_file(

FILE: crates/code2prompt-core/src/git.rs
  function get_git_diff (line 26) | pub fn get_git_diff(repo_path: &Path) -> Result<String> {
  function get_git_diff_between_branches (line 92) | pub fn get_git_diff_between_branches(
  function get_git_log (line 142) | pub fn get_git_log(repo_path: &Path, branch1: &str, branch2: &str) -> Re...
  function branch_exists (line 195) | fn branch_exists(repo: &Repository, branch_name: &str) -> bool {

FILE: crates/code2prompt-core/src/path.rs
  type EntryMetadata (line 20) | pub struct EntryMetadata {
    method from (line 26) | fn from(meta: &std::fs::Metadata) -> Self {
  type FileEntry (line 36) | pub struct FileEntry {
  type FileToProcess (line 48) | struct FileToProcess {
  function traverse_directory (line 71) | pub fn traverse_directory(
  function discover_files (line 91) | fn discover_files(
  function process_files_parallel (line 168) | fn process_files_parallel(
  function read_file_with_binary_check (line 185) | fn read_file_with_binary_check(path: &Path, file_size: u64) -> std::io::...
  function process_single_file (line 213) | fn process_single_file(file_info: &FileToProcess, config: &Code2PromptCo...
  function assemble_results (line 297) | fn assemble_results(
  function display_name (line 318) | pub fn display_name<P: AsRef<Path>>(p: P) -> String {
  function wrap_code_block (line 346) | pub fn wrap_code_block(

FILE: crates/code2prompt-core/src/selection.rs
  type SelectionAction (line 14) | pub struct SelectionAction {
  type ActionType (line 23) | pub enum ActionType {
  type SelectionEngine (line 31) | pub struct SelectionEngine {
    method new (line 47) | pub fn new(
    method is_selected (line 62) | pub fn is_selected(&mut self, path: &Path) -> bool {
    method compute_selection (line 74) | fn compute_selection(&self, path: &Path) -> bool {
    method find_applicable_user_action (line 95) | fn find_applicable_user_action(&self, path: &Path) -> Option<&Selectio...
    method action_applies_to_path (line 120) | fn action_applies_to_path(&self, action: &SelectionAction, path: &Path...
    method calculate_specificity (line 135) | fn calculate_specificity(&self, path: &Path) -> u32 {
    method include_file (line 140) | pub fn include_file(&mut self, path: PathBuf) {
    method exclude_file (line 145) | pub fn exclude_file(&mut self, path: PathBuf) {
    method toggle_file (line 150) | pub fn toggle_file(&mut self, path: PathBuf) {
    method add_user_action (line 161) | fn add_user_action(&mut self, path: PathBuf, action: ActionType) {
    method get_selected_files (line 175) | pub fn get_selected_files(&mut self, root_path: &Path) -> Result<Vec<P...
    method collect_selected_files_recursive (line 206) | fn collect_selected_files_recursive(
    method clear_user_actions (line 236) | pub fn clear_user_actions(&mut self) {
    method user_action_count (line 242) | pub fn user_action_count(&self) -> usize {
    method has_user_actions (line 247) | pub fn has_user_actions(&self) -> bool {
    method filter_engine (line 252) | pub fn filter_engine(&self) -> &FilterEngine {
    method set_deselected_by_default (line 257) | pub fn set_deselected_by_default(&mut self, value: bool) {
    method fmt (line 264) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  function test_specificity_calculation (line 279) | fn test_specificity_calculation() {
  function test_precedence_rules (line 291) | fn test_precedence_rules() {
  function test_recent_wins_over_old (line 306) | fn test_recent_wins_over_old() {
  function test_deselected_by_default (line 319) | fn test_deselected_by_default() {

FILE: crates/code2prompt-core/src/session.rs
  type Code2PromptSession (line 19) | pub struct Code2PromptSession {
    method new (line 75) | pub fn new(config: Code2PromptConfig) -> Self {
    method add_include_pattern (line 90) | pub fn add_include_pattern(&mut self, pattern: String) -> &mut Self {
    method add_exclude_pattern (line 101) | pub fn add_exclude_pattern(&mut self, pattern: String) -> &mut Self {
    method select_file (line 113) | pub fn select_file(&mut self, path: PathBuf) -> &mut Self {
    method deselect_file (line 127) | pub fn deselect_file(&mut self, path: PathBuf) -> &mut Self {
    method toggle_file_selection (line 141) | pub fn toggle_file_selection(&mut self, path: PathBuf) -> &mut Self {
    method is_file_selected (line 155) | pub fn is_file_selected(&mut self, path: &std::path::Path) -> bool {
    method get_selected_files (line 166) | pub fn get_selected_files(&mut self) -> Result<Vec<PathBuf>> {
    method clear_user_actions (line 173) | pub fn clear_user_actions(&mut self) -> &mut Self {
    method has_user_actions (line 179) | pub fn has_user_actions(&self) -> bool {
    method set_deselected (line 184) | pub fn set_deselected(&mut self, value: bool) -> &mut Self {
    method load_codebase (line 191) | pub fn load_codebase(&mut self) -> Result<()> {
    method load_git_diff (line 204) | pub fn load_git_diff(&mut self) -> Result<()> {
    method load_git_diff_between_branches (line 211) | pub fn load_git_diff_between_branches(&mut self) -> Result<()> {
    method load_git_log_between_branches (line 220) | pub fn load_git_log_between_branches(&mut self) -> Result<()> {
    method build_template_data (line 229) | pub fn build_template_data(&self) -> TemplateContext<'_> {
    method render_prompt (line 243) | pub fn render_prompt(&self, template_context: &TemplateContext) -> Res...
    method calculate_token_count_from_cache (line 319) | fn calculate_token_count_from_cache(&self, tokenizer_type: &TokenizerT...
    method calculate_structural_tokens (line 347) | fn calculate_structural_tokens(&self, tokenizer_type: &TokenizerType) ...
    method fallback_structural_estimate (line 434) | fn fallback_structural_estimate(&self, tokenizer_type: &TokenizerType)...
    method generate_prompt (line 468) | pub fn generate_prompt(&mut self) -> Result<RenderedPrompt> {
  type SessionData (line 28) | pub struct SessionData {
  type TemplateContext (line 41) | pub struct TemplateContext<'a> {
  type RenderedPrompt (line 65) | pub struct RenderedPrompt {

FILE: crates/code2prompt-core/src/sort.rs
  type FileSortMethod (line 11) | pub enum FileSortMethod {
    method fmt (line 23) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  function sort_files (line 41) | pub fn sort_files(files: &mut [FileEntry], sort_method: Option<FileSortM...
  function sort_tree (line 70) | pub fn sort_tree<D: Ord + std::fmt::Display>(
  function sort_tree_impl (line 85) | fn sort_tree_impl<D: Ord + std::fmt::Display>(tree: &mut Tree<D>, ascend...

FILE: crates/code2prompt-core/src/template.rs
  function handlebars_setup (line 19) | pub fn handlebars_setup(template_str: &str, template_name: &str) -> Resu...
  function extract_undefined_variables (line 39) | pub fn extract_undefined_variables(template: &str) -> Vec<String> {
  function render_template (line 68) | pub fn render_template<T: Serialize>(
  function write_to_file (line 89) | pub fn write_to_file(output_path: &str, rendered: &str) -> Result<()> {
  type OutputFormat (line 99) | pub enum OutputFormat {
    method fmt (line 107) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

FILE: crates/code2prompt-core/src/tokenizer.rs
  type TokenFormat (line 10) | pub enum TokenFormat {
    method fmt (line 17) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  type TokenizerType (line 27) | pub enum TokenizerType {
    method fmt (line 42) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    method description (line 55) | pub fn description(&self) -> &'static str {
  function count_tokens (line 85) | pub fn count_tokens(rendered: &str, tokenizer_type: &TokenizerType) -> u...

FILE: crates/code2prompt-core/src/util.rs
  function strip_utf8_bom (line 8) | pub fn strip_utf8_bom(data: &[u8]) -> &[u8] {

FILE: crates/code2prompt-core/tests/binary_detection_test.rs
  function create_test_directory_with_binary (line 9) | fn create_test_directory_with_binary() -> TempDir {
  function test_binary_files_are_skipped (line 57) | fn test_binary_files_are_skipped() {
  function test_empty_file_handling (line 86) | fn test_empty_file_handling() {
  function test_small_binary_file (line 105) | fn test_small_binary_file() {
  function test_text_file_with_unicode (line 125) | fn test_text_file_with_unicode() {
  function test_mixed_directory_structure (line 150) | fn test_mixed_directory_structure() {
  function test_large_text_file (line 184) | fn test_large_text_file() {
  function test_pdf_detection (line 208) | fn test_pdf_detection() {
  function test_various_text_formats (line 228) | fn test_various_text_formats() {

FILE: crates/code2prompt-core/tests/file_processor_test.rs
  function test_csv_with_headers_and_data (line 17) | fn test_csv_with_headers_and_data() {
  function test_csv_with_quoted_fields (line 30) | fn test_csv_with_quoted_fields() {
  function test_csv_empty (line 43) | fn test_csv_empty() {
  function test_csv_malformed_fallback (line 55) | fn test_csv_malformed_fallback() {
  function test_tsv_with_headers_and_data (line 75) | fn test_tsv_with_headers_and_data() {
  function test_tsv_with_spaces (line 89) | fn test_tsv_with_spaces() {
  function test_tsv_empty (line 102) | fn test_tsv_empty() {
  function test_jsonl_with_multiple_lines (line 122) | fn test_jsonl_with_multiple_lines() {
  function test_jsonl_single_line (line 140) | fn test_jsonl_single_line() {
  function test_jsonl_with_nested_objects (line 154) | fn test_jsonl_with_nested_objects() {
  function test_jsonl_empty_file (line 166) | fn test_jsonl_empty_file() {
  function test_jsonl_invalid_json (line 175) | fn test_jsonl_invalid_json() {
  function test_jsonl_with_fallback (line 184) | fn test_jsonl_with_fallback() {
  function test_ipynb_with_code_cells (line 204) | fn test_ipynb_with_code_cells() {
  function test_ipynb_with_many_code_cells (line 236) | fn test_ipynb_with_many_code_cells() {
  function test_ipynb_no_code_cells (line 261) | fn test_ipynb_no_code_cells() {
  function test_ipynb_invalid_json (line 279) | fn test_ipynb_invalid_json() {
  function test_ipynb_with_fallback (line 288) | fn test_ipynb_with_fallback() {
  function test_valid_utf8 (line 309) | fn test_valid_utf8() {
  function test_invalid_utf8 (line 319) | fn test_invalid_utf8() {
  function test_gb2312_encoding_detection (line 330) | fn test_gb2312_encoding_detection() {

FILE: crates/code2prompt-core/tests/filter_test.rs
  function test_dir (line 10) | fn test_dir() -> TempDir {
  function base_path (line 43) | fn base_path(test_dir: &TempDir) -> &Path {
  function test_files_inclusion (line 53) | fn test_files_inclusion(
  function test_no_include_no_exclude_path (line 86) | fn test_no_include_no_exclude_path() {
  function test_no_include_no_exclude_empty (line 98) | fn test_no_include_no_exclude_empty(test_dir: TempDir) {
  function test_no_include_exclude_path (line 130) | fn test_no_include_exclude_path() {
  function test_no_include_exclude_by_filename (line 142) | fn test_no_include_exclude_by_filename(test_dir: TempDir) {
  function test_no_include_exclude_path_patterns (line 157) | fn test_no_include_exclude_path_patterns(test_dir: TempDir) {
  function test_no_include_exclude_folders (line 191) | fn test_no_include_exclude_folders(test_dir: TempDir) {
  function test_no_include_exclude_files (line 226) | fn test_no_include_exclude_files(test_dir: TempDir) {
  function test_no_include_exclude_patterns (line 258) | fn test_no_include_exclude_patterns(test_dir: TempDir) {
  function test_include_no_exclude_patterns (line 294) | fn test_include_no_exclude_patterns(test_dir: TempDir) {
  function test_include_no_exclude_files (line 328) | fn test_include_no_exclude_files(test_dir: TempDir) {
  function test_include_no_exclude_folders (line 360) | fn test_include_no_exclude_folders(test_dir: TempDir) {
  function test_include_no_exclude_by_path_pattern (line 395) | fn test_include_no_exclude_by_path_pattern(test_dir: TempDir) {
  function test_include_no_exclude_by_filename (line 430) | fn test_include_no_exclude_by_filename(test_dir: TempDir) {
  function test_include_exclude_conflict_file (line 451) | fn test_include_exclude_conflict_file(test_dir: TempDir) {
  function test_include_exclude_exclude_takes_precedence (line 483) | fn test_include_exclude_exclude_takes_precedence(test_dir: TempDir) {
  function test_include_exclude_conflict_folder (line 508) | fn test_include_exclude_conflict_folder(test_dir: TempDir) {
  function test_include_exclude_conflict_extension (line 542) | fn test_include_exclude_conflict_extension(test_dir: TempDir) {
  function test_brace_expansion_first_item (line 577) | fn test_brace_expansion_first_item(test_dir: TempDir) {
  function test_brace_expansion_multiple_patterns (line 606) | fn test_brace_expansion_multiple_patterns(test_dir: TempDir) {

FILE: crates/code2prompt-core/tests/git_test.rs
  function test_get_git_diff (line 11) | fn test_get_git_diff() {
  function test_get_git_diff_between_branches (line 66) | fn test_get_git_diff_between_branches() {
  function test_get_git_log (line 155) | fn test_get_git_log() {
  function test_git_diff_with_commit_hashes_and_tags (line 269) | fn test_git_diff_with_commit_hashes_and_tags() {

FILE: crates/code2prompt-core/tests/path_test.rs
  function git_repo_with_files (line 22) | fn git_repo_with_files() -> TempDir {
  function simple_dir_structure (line 46) | fn simple_dir_structure() -> TempDir {
  function base_config (line 67) | fn base_config(path: &Path) -> Code2PromptConfig {
  function file_exists (line 77) | fn file_exists(files: &[FileEntry], path: &str) -> bool {
  function get_metadata (line 82) | fn get_metadata(files: &[FileEntry], path: &str) -> Option<EntryMetadata> {
  function test_basic_traversal (line 98) | fn test_basic_traversal(simple_dir_structure: TempDir) {
  function test_respects_gitignore (line 117) | fn test_respects_gitignore(git_repo_with_files: TempDir) {
  function test_ignores_gitignore_when_disabled (line 135) | fn test_ignores_gitignore_when_disabled(git_repo_with_files: TempDir) {
  function test_excludes_hidden_files_by_default (line 151) | fn test_excludes_hidden_files_by_default(simple_dir_structure: TempDir) {
  function test_includes_hidden_files_when_enabled (line 164) | fn test_includes_hidden_files_when_enabled(simple_dir_structure: TempDir) {
  function test_file_content_processing (line 183) | fn test_file_content_processing(simple_dir_structure: TempDir) {
  function test_file_metadata (line 205) | fn test_file_metadata(simple_dir_structure: TempDir) {
  function test_relative_paths_by_default (line 221) | fn test_relative_paths_by_default(simple_dir_structure: TempDir) {
  function test_absolute_paths_when_enabled (line 230) | fn test_absolute_paths_when_enabled(simple_dir_structure: TempDir) {
  function test_symlink_following_when_enabled (line 274) | fn test_symlink_following_when_enabled(simple_dir_structure: TempDir) {

FILE: crates/code2prompt-core/tests/session_integration_test.rs
  function create_test_project (line 12) | fn create_test_project() -> TempDir {
  function test_session_select_deselect_file (line 31) | fn test_session_select_deselect_file() {
  function test_session_multiple_files (line 58) | fn test_session_multiple_files() {
  function test_session_multiple_file_selection (line 89) | fn test_session_multiple_file_selection() {
  function test_session_clear_user_actions (line 110) | fn test_session_clear_user_actions() {
  function test_session_add_patterns (line 134) | fn test_session_add_patterns() {
  function test_session_relative_path_handling (line 158) | fn test_session_relative_path_handling() {

FILE: crates/code2prompt-core/tests/sort_test.rs
  function test_sort_files_name_asc (line 10) | fn test_sort_files_name_asc() {
  function test_sort_files_name_desc (line 58) | fn test_sort_files_name_desc() {
  function test_sort_files_date_asc (line 106) | fn test_sort_files_date_asc() {
  function test_sort_files_date_desc (line 154) | fn test_sort_files_date_desc() {
  function test_sort_files_none (line 202) | fn test_sort_files_none() {
  function test_sort_tree_name_asc (line 228) | fn test_sort_tree_name_asc() {
  function test_sort_tree_name_desc (line 245) | fn test_sort_tree_name_desc() {
  function test_sort_tree_date_asc_falls_back_to_name (line 260) | fn test_sort_tree_date_asc_falls_back_to_name() {
  function test_sort_tree_none (line 279) | fn test_sort_tree_none() {

FILE: crates/code2prompt-core/tests/template_test.rs
  function test_handlebars_setup (line 9) | fn test_handlebars_setup() {
  function test_extract_undefined_variables (line 33) | fn test_extract_undefined_variables() {
  function test_render_template (line 40) | fn test_render_template() {

FILE: crates/code2prompt-core/tests/util_test.rs
  function test_strip_utf8_bom_when_present (line 8) | fn test_strip_utf8_bom_when_present() {
  function test_strip_utf8_bom_when_not_present (line 19) | fn test_strip_utf8_bom_when_not_present() {
  function test_strip_utf8_bom_empty_input (line 29) | fn test_strip_utf8_bom_empty_input() {
  function test_strip_utf8_bom_only_bom (line 39) | fn test_strip_utf8_bom_only_bom() {

FILE: crates/code2prompt-python/python-sdk/code2prompt_rs/code2prompt.py
  class RenderedPrompt (line 5) | class RenderedPrompt:
    method __init__ (line 6) | def __init__(self, prompt, token_count, directory, model_info):
  class Code2Prompt (line 12) | class Code2Prompt:
    method __init__ (line 13) | def __init__(self, path, include_patterns=None, exclude_patterns=None,
    method session (line 46) | def session(self) -> rust_sdk.PyCode2PromptSession:
    method generate (line 69) | def generate(self, template=None, encoding=None) -> RenderedPrompt:
    method token_count (line 106) | def token_count(self, encoding=None):
    method info (line 113) | def info(self):

FILE: crates/code2prompt-python/python-sdk/examples/basic_usage.py
  function main (line 5) | def main():

FILE: crates/code2prompt-python/src/python.rs
  type PyCode2PromptSession (line 13) | struct PyCode2PromptSession {
    method new (line 20) | fn new(path: &str) -> PyResult<Self> {
    method include (line 37) | fn include(&mut self, patterns: Vec<String>) -> PyResult<Py<Self>> {
    method exclude (line 52) | fn exclude(&mut self, patterns: Vec<String>) -> PyResult<Py<Self>> {
    method with_line_numbers (line 67) | fn with_line_numbers(&mut self, value: bool) -> PyResult<Py<Self>> {
    method with_absolute_paths (line 82) | fn with_absolute_paths(&mut self, value: bool) -> PyResult<Py<Self>> {
    method with_full_directory_tree (line 97) | fn with_full_directory_tree(&mut self, value: bool) -> PyResult<Py<Sel...
    method with_code_blocks (line 112) | fn with_code_blocks(&mut self, value: bool) -> PyResult<Py<Self>> {
    method follow_symlinks (line 127) | fn follow_symlinks(&mut self, value: bool) -> PyResult<Py<Self>> {
    method include_hidden (line 142) | fn include_hidden(&mut self, value: bool) -> PyResult<Py<Self>> {
    method no_ignore (line 157) | fn no_ignore(&mut self, value: bool) -> PyResult<Py<Self>> {
    method sort_by (line 172) | fn sort_by(&mut self, method: &str) -> PyResult<Py<Self>> {
    method output_format (line 198) | fn output_format(&mut self, format: &str) -> PyResult<Py<Self>> {
    method with_token_encoding (line 224) | fn with_token_encoding(&mut self, encoding: &str) -> PyResult<Py<Self>> {
    method with_token_format (line 251) | fn with_token_format(&mut self, format: &str) -> PyResult<Py<Self>> {
    method with_template (line 276) | fn with_template(&mut self, template: String, name: Option<String>) ->...
    method with_variable (line 297) | fn with_variable(&mut self, key: String, value: String) -> PyResult<Py...
    method generate (line 312) | fn generate(&mut self) -> PyResult<String> {
    method info (line 322) | fn info(&self) -> PyResult<HashMap<String, String>> {
    method token_count (line 341) | fn token_count(&self) -> PyResult<usize> {
  function code2prompt_rs (line 355) | fn code2prompt_rs(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<...

FILE: crates/code2prompt-python/tests/conftest.py
  function test_hierarchy (line 9) | def test_hierarchy():
  function test_dir (line 55) | def test_dir(test_hierarchy):

FILE: crates/code2prompt-python/tests/test_config.py
  function test_basic_initialization (line 6) | def test_basic_initialization(test_dir):
  function test_initialization_with_options (line 14) | def test_initialization_with_options(test_dir):
  function test_session_creation (line 37) | def test_session_creation(test_dir):
  function test_configuration_chain (line 48) | def test_configuration_chain(test_dir):

FILE: crates/code2prompt-python/tests/test_generation.py
  function test_generate_basic (line 5) | def test_generate_basic(test_dir):
  function test_generate_with_include_patterns (line 16) | def test_generate_with_include_patterns(test_dir):
  function test_generate_with_exclude_patterns (line 32) | def test_generate_with_exclude_patterns(test_dir):
  function test_generate_with_line_numbers (line 47) | def test_generate_with_line_numbers(test_dir):
  function test_generate_with_relative_and_absolute_paths (line 59) | def test_generate_with_relative_and_absolute_paths(test_dir):
  function test_generate_with_custom_template (line 87) | def test_generate_with_custom_template(test_dir):
  function test_token_count (line 107) | def test_token_count(test_dir):
  function test_multiple_encoding_options (line 120) | def test_multiple_encoding_options(test_dir):

FILE: crates/code2prompt-python/tests/test_special_feature.py
  function test_hidden_files (line 9) | def test_hidden_files(test_dir):
  function test_directory_tree (line 28) | def test_directory_tree(test_dir):
  function test_no_code_blocks (line 40) | def test_no_code_blocks(test_dir):
  function test_sort_files (line 61) | def test_sort_files(test_dir):

FILE: crates/code2prompt/src/args.rs
  type Cli (line 23) | pub struct Cli {
  function parse_serde (line 154) | fn parse_serde<T: DeserializeOwned>(s: &str) -> Result<T> {

FILE: crates/code2prompt/src/clipboard.rs
  function copy_text_to_clipboard (line 16) | pub fn copy_text_to_clipboard(text: &str) -> Result<()> {
  function serve_clipboard_daemon (line 39) | pub fn serve_clipboard_daemon() -> Result<()> {
  function spawn_clipboard_daemon (line 78) | pub fn spawn_clipboard_daemon(content: &str) -> Result<()> {
  function copy_to_clipboard (line 112) | pub fn copy_to_clipboard(text: &str) -> Result<()> {

FILE: crates/code2prompt/src/config.rs
  function build_session (line 25) | pub fn build_session(
  function parse_branch_argument (line 199) | pub fn parse_branch_argument(branch_arg: &Option<Vec<String>>) -> Option...
  function parse_template (line 216) | pub fn parse_template(template_arg: &Option<PathBuf>) -> Result<(String,...
  function handle_undefined_variables (line 240) | pub fn handle_undefined_variables(
  function expand_comma_separated_patterns (line 273) | fn expand_comma_separated_patterns(patterns: &[String]) -> Vec<String> {

FILE: crates/code2prompt/src/config_loader.rs
  type ConfigSource (line 14) | pub struct ConfigSource {
  function load_config (line 19) | pub fn load_config(quiet: bool) -> Result<ConfigSource> {
  function load_config_from_file (line 88) | fn load_config_from_file(path: &Path) -> Result<TomlConfig> {
  function get_default_output_destination (line 97) | pub fn get_default_output_destination(config_source: &ConfigSource) -> O...

FILE: crates/code2prompt/src/main.rs
  function main (line 27) | async fn main() -> Result<()> {
  function run_cli_mode_with_args (line 59) | async fn run_cli_mode_with_args(args: Cli) -> Result<()> {
  function setup_spinner (line 293) | fn setup_spinner(message: &str) -> ProgressBar {
  function output_prompt (line 321) | fn output_prompt(

FILE: crates/code2prompt/src/model/commands.rs
  type Cmd (line 13) | pub enum Cmd {

FILE: crates/code2prompt/src/model/mod.rs
  type Tab (line 25) | pub enum Tab {
  type FileTreeInputMode (line 35) | pub enum FileTreeInputMode {
  type DisplayFileNode (line 42) | pub struct DisplayFileNode {
    method new (line 53) | pub fn new(path: std::path::PathBuf, level: usize) -> Self {
    method find_node_mut (line 73) | pub fn find_node_mut(&mut self, target_path: &std::path::Path) -> Opti...
    method load_children (line 88) | pub fn load_children(
  type Message (line 135) | pub enum Message {
  type Model (line 185) | pub struct Model {
    method new (line 225) | pub fn new(session: Code2PromptSession) -> Self {
    method get_settings_groups (line 244) | pub fn get_settings_groups(&self) -> Vec<SettingsGroup> {
    method update (line 248) | pub fn update(&self, message: Message) -> (Self, Cmd) {
  method default (line 202) | fn default() -> Self {

FILE: crates/code2prompt/src/model/prompt_output.rs
  type PromptOutputState (line 8) | pub struct PromptOutputState {
  type AnalysisResults (line 19) | pub struct AnalysisResults {

FILE: crates/code2prompt/src/model/settings.rs
  type SettingsState (line 12) | pub struct SettingsState {
    method get_settings_items (line 67) | pub fn get_settings_items(&self, session: &Code2PromptSession) -> Vec<...
    method update_setting_by_key (line 75) | pub fn update_setting_by_key(
  type SettingsGroup (line 18) | pub struct SettingsGroup {
  type SettingsItem (line 25) | pub struct SettingsItem {
  type SettingType (line 33) | pub enum SettingType {
  type SettingAction (line 42) | pub enum SettingAction {
  type SettingKey (line 49) | pub enum SettingKey {

FILE: crates/code2prompt/src/model/statistics/mod.rs
  type StatisticsState (line 14) | pub struct StatisticsState {
    method count_selected_files (line 32) | pub fn count_selected_files(
    method count_total_files (line 39) | pub fn count_total_files(nodes: &[DisplayFileNode]) -> usize {
    method format_number (line 51) | pub fn format_number(
    method aggregate_by_extension (line 59) | pub fn aggregate_by_extension(&self) -> Vec<(String, usize, usize)> {
  method default (line 21) | fn default() -> Self {

FILE: crates/code2prompt/src/model/statistics/types.rs
  type StatisticsView (line 8) | pub enum StatisticsView {
    method next (line 15) | pub fn next(&self) -> Self {
    method prev (line 23) | pub fn prev(&self) -> Self {
    method as_str (line 31) | pub fn as_str(&self) -> &'static str {

FILE: crates/code2prompt/src/model/template/editor.rs
  type EditorState (line 12) | pub struct EditorState {
    method sync_content_from_textarea (line 69) | pub fn sync_content_from_textarea(&mut self) {
    method analyze_template_variables (line 75) | pub fn analyze_template_variables(&mut self) {
    method get_template_variables (line 90) | pub fn get_template_variables(&self) -> &[String] {
    method validate_template (line 95) | pub fn validate_template(&mut self) {
    method compile_template (line 123) | fn compile_template(&self) -> Result<(), String> {
    method get_content (line 136) | pub fn get_content(&self) -> &str {
  method clone (line 22) | fn clone(&self) -> Self {
  method default (line 41) | fn default() -> Self {

FILE: crates/code2prompt/src/model/template/mod.rs
  type TemplateFocus (line 18) | pub enum TemplateFocus {
  type FocusMode (line 26) | pub enum FocusMode {
  type TemplateState (line 34) | pub struct TemplateState {
    method from_model (line 62) | pub fn from_model(model: &crate::model::Model) -> Self {
    method sync_variables_with_template (line 68) | pub fn sync_variables_with_template(&mut self) {
    method set_focus (line 74) | pub fn set_focus(&mut self, focus: TemplateFocus) {
    method get_focus (line 79) | pub fn get_focus(&self) -> TemplateFocus {
    method set_focus_mode (line 84) | pub fn set_focus_mode(&mut self, mode: FocusMode) {
    method get_focus_mode (line 89) | pub fn get_focus_mode(&self) -> FocusMode {
    method is_in_editing_mode (line 94) | pub fn is_in_editing_mode(&self) -> bool {
    method get_organized_variables (line 102) | pub fn get_organized_variables(&self) -> Vec<VariableInfo> {
    method get_template_content (line 108) | pub fn get_template_content(&self) -> &str {
    method get_status (line 113) | pub fn get_status(&self) -> &str {
    method load_selected_template (line 118) | pub fn load_selected_template(&mut self) -> Result<String, String> {
    method get_selected_template (line 163) | fn get_selected_template(&self) -> Result<&picker::TemplateFile, Strin...
  method default (line 44) | fn default() -> Self {

FILE: crates/code2prompt/src/model/template/picker.rs
  type TemplateFile (line 10) | pub struct TemplateFile {
  type ActiveList (line 17) | pub enum ActiveList {
  type PickerState (line 24) | pub struct PickerState {
    method load_all_templates (line 49) | pub fn load_all_templates(&mut self) {
    method load_default_templates (line 55) | fn load_default_templates(&mut self) {
    method load_custom_templates (line 74) | fn load_custom_templates(&mut self) {
    method move_cursor_up (line 90) | pub fn move_cursor_up(&mut self) {
    method move_cursor_down (line 107) | pub fn move_cursor_down(&mut self) {
    method refresh (line 120) | pub fn refresh(&mut self) {
    method get_global_cursor_position (line 133) | pub fn get_global_cursor_position(&self) -> usize {
    method get_global_template_index (line 162) | fn get_global_template_index(&self) -> usize {
    method get_total_selectable_items (line 170) | fn get_total_selectable_items(&self) -> usize {
    method set_cursor_from_global_position (line 175) | fn set_cursor_from_global_position(&mut self, global_pos: usize) {
  method default (line 33) | fn default() -> Self {

FILE: crates/code2prompt/src/model/template/variable.rs
  type VariableCategory (line 10) | pub enum VariableCategory {
  type VariableInfo (line 18) | pub struct VariableInfo {
  type VariableState (line 27) | pub struct VariableState {
    method get_default_system_variables (line 53) | fn get_default_system_variables() -> HashMap<String, String> {
    method update_missing_variables (line 112) | pub fn update_missing_variables(&mut self, template_variables: &[Strin...
    method get_organized_variables (line 125) | pub fn get_organized_variables(&self, template_variables: &[String]) -...
    method set_user_variable (line 166) | pub fn set_user_variable(&mut self, key: String, value: String) {
    method has_missing_variables (line 171) | pub fn has_missing_variables(&self) -> bool {
    method cancel_editing (line 176) | pub fn cancel_editing(&mut self) {
    method finish_editing (line 183) | pub fn finish_editing(&mut self) -> Option<(String, String)> {
    method add_char_to_input (line 196) | pub fn add_char_to_input(&mut self, c: char) {
    method remove_char_from_input (line 201) | pub fn remove_char_from_input(&mut self) {
    method get_input_content (line 206) | pub fn get_input_content(&self) -> &str {
    method is_editing (line 211) | pub fn is_editing(&self) -> bool {
    method get_editing_variable (line 216) | pub fn get_editing_variable(&self) -> Option<&String> {
    method move_to_first_missing_variable (line 221) | pub fn move_to_first_missing_variable(&mut self) {
  method default (line 38) | fn default() -> Self {

FILE: crates/code2prompt/src/token_map.rs
  type TuiColor (line 17) | pub enum TuiColor {
  type TuiTokenMapLine (line 36) | pub struct TuiTokenMapLine {
  type EntryMetadata (line 46) | pub struct EntryMetadata {
  type TreeNode (line 51) | struct TreeNode {
    method with_path (line 59) | fn with_path(path: String) -> Self {
  type NodePriority (line 71) | struct NodePriority {
  method cmp (line 78) | fn cmp(&self, other: &Self) -> Ordering {
  method partial_cmp (line 88) | fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
  function generate_token_map_with_limit (line 109) | pub fn generate_token_map_with_limit(
  function calculate_file_tokens (line 184) | fn calculate_file_tokens(node: &TreeNode) -> usize {
  function insert_path (line 192) | fn insert_path(
  type TokenMapEntry (line 236) | pub struct TokenMapEntry {
  function select_nodes_to_display (line 247) | fn select_nodes_to_display(
  function find_node_by_path (line 292) | fn find_node_by_path<'a>(root: &'a TreeNode, path: &str) -> Option<&'a T...
  function rebuild_filtered_tree (line 311) | fn rebuild_filtered_tree(
  function should_enable_colors (line 369) | fn should_enable_colors() -> bool {
  function display_token_map (line 409) | pub fn display_token_map(entries: &[TokenMapEntry], total_tokens: usize) {
  function build_tree_prefix (line 520) | fn build_tree_prefix(entry: &TokenMapEntry, entries: &[TokenMapEntry], i...
  function determine_tui_color (line 571) | fn determine_tui_color(entry: &TokenMapEntry) -> TuiColor {
  function format_token_map_for_tui (line 630) | pub fn format_token_map_for_tui(
  function format_tokens (line 724) | fn format_tokens(tokens: usize) -> String {
  function generate_hierarchical_bar (line 737) | fn generate_hierarchical_bar(

FILE: crates/code2prompt/src/tui.rs
  type TuiApp (line 36) | pub struct TuiApp {
    method new (line 50) | pub fn new(session: Code2PromptSession) -> Result<Self> {
    method run (line 64) | pub async fn run(&mut self) -> Result<()> {
    method render_with_model (line 130) | fn render_with_model(model: &Model, frame: &mut Frame) {
    method handle_key_event (line 203) | fn handle_key_event(&self, key: KeyEvent) -> Option<Message> {
    method handle_file_tree_keys (line 266) | fn handle_file_tree_keys(&self, key: KeyEvent) -> Option<Message> {
    method handle_settings_keys (line 308) | fn handle_settings_keys(&self, key: KeyEvent) -> Option<Message> {
    method handle_statistics_keys (line 321) | fn handle_statistics_keys(&self, key: KeyEvent) -> Option<Message> {
    method handle_template_keys (line 336) | fn handle_template_keys(&self, key: KeyEvent) -> Option<Message> {
    method handle_prompt_output_keys (line 439) | fn handle_prompt_output_keys(&self, key: KeyEvent) -> Option<Message> {
    method handle_message (line 460) | fn handle_message(&mut self, message: Message) -> Result<()> {
    method execute_cmd (line 472) | fn execute_cmd(&mut self, cmd: Cmd) -> Result<()> {
    method render_tab_bar_static (line 579) | fn render_tab_bar_static(model: &Model, frame: &mut Frame, area: Rect) {
    method render_status_bar_static (line 612) | fn render_status_bar_static(model: &Model, frame: &mut Frame, area: Re...
    method convert_crossterm_key (line 626) | fn convert_crossterm_key(&self, key: crossterm::event::KeyEvent) -> Ke...
    method try_coalesce_messages (line 662) | fn try_coalesce_messages(&self, last_message: &mut Message, new_messag...
  function run_tui (line 701) | pub async fn run_tui(session: Code2PromptSession) -> Result<()> {
  function init_terminal (line 712) | fn init_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
  function restore_terminal (line 720) | fn restore_terminal() -> Result<()> {

FILE: crates/code2prompt/src/utils.rs
  function build_file_tree_from_session (line 13) | pub fn build_file_tree_from_session(
  function auto_expand_recursively (line 55) | fn auto_expand_recursively(node: &mut DisplayFileNode, session: &mut Cod...
  function directory_contains_selected_files (line 78) | pub(crate) fn directory_contains_selected_files(
  function get_visible_nodes (line 105) | pub fn get_visible_nodes(
  type QueryMatcher (line 118) | enum QueryMatcher {
  function build_query_matcher (line 123) | fn build_query_matcher(raw: &str) -> QueryMatcher {
  function matches (line 138) | fn matches(m: &QueryMatcher, text: &str) -> bool {
  type DisplayNodeWithSelection (line 147) | pub struct DisplayNodeWithSelection {
  function collect_visible_nodes_recursive (line 153) | fn collect_visible_nodes_recursive(
  function save_to_file (line 236) | pub fn save_to_file(path: &Path, content: &str) -> Result<()> {
  function format_number (line 252) | pub fn format_number(num: usize, format: &code2prompt_core::tokenizer::T...
  function get_children_for_search (line 274) | fn get_children_for_search(
  function save_template_to_custom_dir (line 324) | pub fn save_template_to_custom_dir(filename: &Path, content: &str) -> Re...
  function load_all_templates (line 339) | pub fn load_all_templates() -> Result<Vec<(String, String)>> {
  function ensure_path_exists_in_tree (line 390) | pub fn ensure_path_exists_in_tree(

FILE: crates/code2prompt/src/view/formatters.rs
  function format_settings_groups (line 14) | pub fn format_settings_groups(session: &Code2PromptSession) -> Vec<Setti...

FILE: crates/code2prompt/src/widgets/file_selection.rs
  type FileSelectionState (line 10) | pub type FileSelectionState = ();
  type FileSelectionWidget (line 13) | pub struct FileSelectionWidget<'a> {
  function new (line 18) | pub fn new(model: &'a Model) -> Self {
  type State (line 24) | type State = FileSelectionState;
  method render (line 26) | fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {

FILE: crates/code2prompt/src/widgets/output.rs
  type OutputState (line 10) | pub type OutputState = ();
  type OutputWidget (line 13) | pub struct OutputWidget<'a> {
  function new (line 18) | pub fn new(model: &'a Model) -> Self {
  type State (line 24) | type State = OutputState;
  method render (line 26) | fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {

FILE: crates/code2prompt/src/widgets/settings.rs
  type SettingsState (line 10) | pub type SettingsState = ();
  type SettingsWidget (line 13) | pub struct SettingsWidget<'a> {
  function new (line 18) | pub fn new(model: &'a Model) -> Self {
  type State (line 24) | type State = SettingsState;
  method render (line 26) | fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {

FILE: crates/code2prompt/src/widgets/statistics_by_extension.rs
  type ExtensionState (line 10) | pub type ExtensionState = ();
  type StatisticsByExtensionWidget (line 13) | pub struct StatisticsByExtensionWidget<'a> {
  function new (line 18) | pub fn new(model: &'a Model) -> Self {
  type State (line 24) | type State = ExtensionState;
  method render (line 26) | fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {

FILE: crates/code2prompt/src/widgets/statistics_overview.rs
  type StatisticsOverviewWidget (line 9) | pub struct StatisticsOverviewWidget<'a> {
  function new (line 14) | pub fn new(model: &'a Model) -> Self {
  method render (line 20) | fn render(self, area: Rect, buf: &mut Buffer) {

FILE: crates/code2prompt/src/widgets/statistics_token_map.rs
  type TokenMapState (line 11) | pub type TokenMapState = ();
  type StatisticsTokenMapWidget (line 14) | pub struct StatisticsTokenMapWidget<'a> {
  function new (line 19) | pub fn new(model: &'a Model) -> Self {
  type State (line 25) | type State = TokenMapState;
  method render (line 27) | fn render(self, area: Rect, buf: &mut Buffer, _state: &mut Self::State) {

FILE: crates/code2prompt/src/widgets/template/editor.rs
  type TemplateEditorWidget (line 12) | pub struct TemplateEditorWidget;
    method new (line 15) | pub fn new() -> Self {
    method render (line 20) | pub fn render(
  method default (line 99) | fn default() -> Self {

FILE: crates/code2prompt/src/widgets/template/mod.rs
  type TemplateWidget (line 24) | pub struct TemplateWidget {
    method new (line 31) | pub fn new(_model: &Model) -> Self {
    method render (line 40) | pub fn render(&self, area: Rect, buf: &mut Buffer, state: &mut Templat...
    method render_content (line 58) | fn render_content(&self, area: Rect, buf: &mut Buffer, state: &mut Tem...
    method render_footer (line 143) | fn render_footer(&self, area: Rect, buf: &mut Buffer, state: &Template...
  type State (line 220) | type State = TemplateState;
  method render (line 222) | fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {

FILE: crates/code2prompt/src/widgets/template/picker.rs
  type TemplatePickerWidget (line 12) | pub struct TemplatePickerWidget;
    method new (line 15) | pub fn new() -> Self {
    method render (line 20) | pub fn render(&self, area: Rect, buf: &mut Buffer, state: &PickerState...
    method get_help_text (line 129) | pub fn get_help_text(is_focused: bool, _active_list: ActiveList) -> &'...
  method default (line 139) | fn default() -> Self {

FILE: crates/code2prompt/src/widgets/template/variable.rs
  type TemplateVariableWidget (line 12) | pub struct TemplateVariableWidget;
    method new (line 15) | pub fn new() -> Self {
    method render (line 20) | pub fn render(
    method render_variable_input (line 147) | fn render_variable_input(&self, area: Rect, buf: &mut Buffer, state: &...
    method centered_rect (line 168) | fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
  method default (line 190) | fn default() -> Self {

FILE: crates/code2prompt/tests/common/fixtures.rs
  function basic_test_env (line 11) | pub fn basic_test_env() -> BasicTestEnv {
  function git_test_env (line 19) | pub fn git_test_env() -> GitTestEnv {
  function stdout_test_env (line 27) | pub fn stdout_test_env() -> StdoutTestEnv {
  function template_test_env (line 35) | pub fn template_test_env() -> TemplateTestEnv {
  function create_standard_hierarchy (line 42) | pub fn create_standard_hierarchy(base_path: &std::path::Path) {
  function create_git_hierarchy (line 78) | pub fn create_git_hierarchy(base_path: &std::path::Path) {
  function create_simple_test_files (line 108) | pub fn create_simple_test_files(base_path: &std::path::Path) {
  function create_test_codebase (line 129) | pub fn create_test_codebase(base_path: &std::path::Path) {

FILE: crates/code2prompt/tests/common/mod.rs
  function init_logger (line 16) | pub fn init_logger() {

FILE: crates/code2prompt/tests/common/test_env.rs
  type BasicTestEnv (line 12) | pub struct BasicTestEnv {
    method new (line 18) | pub fn new() -> Self {
    method command (line 25) | pub fn command(&self) -> Command {
    method read_output (line 34) | pub fn read_output(&self) -> String {
  type GitTestEnv (line 42) | pub struct GitTestEnv {
    method new (line 48) | pub fn new() -> Self {
    method command (line 56) | pub fn command(&self) -> Command {
    method read_output (line 65) | pub fn read_output(&self) -> String {
  type StdoutTestEnv (line 73) | pub struct StdoutTestEnv {
    method new (line 78) | pub fn new() -> Self {
    method path (line 84) | pub fn path(&self) -> &str {
  type TemplateTestEnv (line 90) | pub struct TemplateTestEnv {
    method new (line 96) | pub fn new() -> Self {
    method command (line 103) | pub fn command(&self) -> Command {
    method read_output (line 112) | pub fn read_output(&self) -> String {
    method output_file_exists (line 117) | pub fn output_file_exists(&self) -> bool {
  function create_temp_file (line 123) | pub fn create_temp_file(dir: &Path, name: &str, content: &str) -> std::p...

FILE: crates/code2prompt/tests/config_test.rs
  function test_toml_config_parsing (line 18) | fn test_toml_config_parsing() {
  function test_toml_config_export (line 89) | fn test_toml_config_export() {
  function test_local_config_file_loading (line 112) | fn test_local_config_file_loading() {
  function test_unix_style_default_stdout (line 138) | fn test_unix_style_default_stdout() {
  function test_clipboard_flag (line 159) | fn test_clipboard_flag() {
  function test_cli_args_override_config (line 173) | fn test_cli_args_override_config() {
  function test_config_info_messages (line 202) | fn test_config_info_messages() {
  function test_default_config_message (line 224) | fn test_default_config_message() {
  function test_cli_args_message (line 240) | fn test_cli_args_message() {

FILE: crates/code2prompt/tests/git_integration_test.rs
  function test_gitignore (line 17) | fn test_gitignore(git_test_env: GitTestEnv) {
  function test_gitignore_no_ignore (line 35) | fn test_gitignore_no_ignore(git_test_env: GitTestEnv) {
  function test_git_repo_initialization (line 51) | fn test_git_repo_initialization(git_test_env: GitTestEnv) {
  function test_gitignore_patterns (line 63) | fn test_gitignore_patterns(

FILE: crates/code2prompt/tests/integration_test.rs
  function test_file_filtering (line 17) | fn test_file_filtering(
  function test_include_exclude_with_exclude_priority (line 63) | fn test_include_exclude_with_exclude_priority(basic_test_env: BasicTestE...
  function test_no_filters (line 86) | fn test_no_filters(basic_test_env: BasicTestEnv) {
  function test_full_directory_tree (line 116) | fn test_full_directory_tree(basic_test_env: BasicTestEnv) {
  function test_brace_expansion (line 145) | fn test_brace_expansion(basic_test_env: BasicTestEnv) {
  function test_command_helper (line 173) | fn test_command_helper(basic_test_env: BasicTestEnv) {

FILE: crates/code2prompt/tests/std_output_test.rs
  function test_output_default (line 17) | fn test_output_default(stdout_test_env: StdoutTestEnv) {
  function test_stdout_configurations (line 39) | fn test_stdout_configurations(
  function test_file_output_configurations (line 84) | fn test_file_output_configurations(
  function test_conflicting_output_options_should_fail (line 144) | fn test_conflicting_output_options_should_fail(stdout_test_env: StdoutTe...
  function test_output_file_vs_stdout_conflict (line 161) | fn test_output_file_vs_stdout_conflict(stdout_test_env: StdoutTestEnv) {
  function test_stdout_with_different_formats (line 187) | fn test_stdout_with_different_formats(
  function test_stderr_messages_normal_mode (line 212) | fn test_stderr_messages_normal_mode(stdout_test_env: StdoutTestEnv) {
  function test_stderr_messages_quiet_mode (line 231) | fn test_stderr_messages_quiet_mode(stdout_test_env: StdoutTestEnv) {
  function test_stderr_messages_with_clipboard (line 253) | fn test_stderr_messages_with_clipboard(stdout_test_env: StdoutTestEnv) {
  function test_stderr_with_output_formats (line 269) | fn test_stderr_with_output_formats(stdout_test_env: StdoutTestEnv, #[cas...
  function test_stdout_stderr_separation (line 289) | fn test_stdout_stderr_separation(stdout_test_env: StdoutTestEnv) {
  function test_stdout_fixture_setup (line 310) | fn test_stdout_fixture_setup(stdout_test_env: StdoutTestEnv) {

FILE: crates/code2prompt/tests/template_integration_test.rs
  function test_output_format_templates (line 19) | fn test_output_format_templates(
  function test_json_output_format (line 45) | fn test_json_output_format(template_test_env: TemplateTestEnv) {
  function test_template_fixture_setup (line 61) | fn test_template_fixture_setup(template_test_env: TemplateTestEnv) {
  function test_basic_template_rendering (line 92) | fn test_basic_template_rendering(template_test_env: TemplateTestEnv) {
  function test_template_with_file_extensions (line 109) | fn test_template_with_file_extensions(template_test_env: TemplateTestEnv) {
  function test_template_output_structure (line 124) | fn test_template_output_structure(template_test_env: TemplateTestEnv) {
  function test_template_with_filters (line 151) | fn test_template_with_filters(
  function test_template_command_creation (line 175) | fn test_template_command_creation(template_test_env: TemplateTestEnv) {

FILE: website/public/assets/js/main.js
  function copyToClipboard (line 1) | function copyToClipboard() {
  function addAnimation (line 17) | function addAnimation() {

FILE: website/src/assets/examples/node_app/node_app/src/index.js
  constant PORT (line 4) | const PORT = process.env.PORT || 3000;

FILE: website/src/assets/examples/node_app/node_app/src/utils.js
  function processData (line 1) | function processData() {
Condensed preview — 255 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,023K chars).
[
  {
    "path": ".assets/flow_diagram.md",
    "chars": 1165,
    "preview": "---\nconfig:\n  flowchart:\n    nodeSpacing: 15\n    rankSpacing: 50\n    curve: monotoneX\n  layout: fixed\n---\nflowchart LR\n "
  },
  {
    "path": ".c2pconfig",
    "chars": 173,
    "preview": "default_output = \"clipboard\"\ninclude_patterns = [\"*.rs\"]\nexclude_patterns = [\"**/test*\"]\nline_numbers = false\nabsolute_p"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 1037,
    "preview": "# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#package-ecosy"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 323,
    "preview": "# Run tests for code2prompt\nname: Code2prompt Continuous Integration\n\non:\n  push:\n    branches: [ \"main\" ]\n  pull_reques"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 4263,
    "preview": "# Build and publish release on tags push\n\nname: Code2prompt Release\n\non:\n  push:\n    tags:\n      - 'v[0-9]*.[0-9]*.[0-9]"
  },
  {
    "path": ".github/workflows/website.yml",
    "chars": 1329,
    "preview": "name: Code2prompt Website\n\non:\n  # Trigger the workflow every time you push to the `main` branch\n  # Using a different b"
  },
  {
    "path": ".gitignore",
    "chars": 4957,
    "preview": "# Generated by Cargo\n# will have compiled files and executables\ndebug/\ntarget/\n\n# Remove Cargo.lock from gitignore if cr"
  },
  {
    "path": "Cargo.toml",
    "chars": 1535,
    "preview": "[workspace]\nresolver = \"2\"\nmembers = [\n    \"crates/code2prompt-core\",\n    \"crates/code2prompt\",\n    \"crates/code2prompt-"
  },
  {
    "path": "LICENSE",
    "chars": 1066,
    "preview": "MIT License\n\nCopyright (c) 2024 Mufeed VH\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
  },
  {
    "path": "README.md",
    "chars": 6502,
    "preview": "<div align=\"center\">\n  <a href=\"https://code2prompt.dev\">\n    <img align=\"center\" width=\"550px\" src=\"https://github.com/"
  },
  {
    "path": "README_ES.md",
    "chars": 11223,
    "preview": "# code2prompt\n\n[![crates.io](https://img.shields.io/crates/v/code2prompt.svg)](https://crates.io/crates/code2prompt)\n[!["
  },
  {
    "path": "crates/code2prompt/Cargo.toml",
    "chars": 1517,
    "preview": "[package]\nname = \"code2prompt\"\nversion = \"4.2.0\"\nedition = \"2024\"\ndescription = \"Command-line interface for code2prompt\""
  },
  {
    "path": "crates/code2prompt/src/args.rs",
    "chars": 4871,
    "preview": "//! Command-line argument parsing and validation.\n//!\n//! This module defines the CLI structure using clap for parsing c"
  },
  {
    "path": "crates/code2prompt/src/clipboard.rs",
    "chars": 4177,
    "preview": "use anyhow::{Context, Result};\n\n#[cfg(not(target_os = \"linux\"))]\n/// Copies the provided text to the system clipboard.\n/"
  },
  {
    "path": "crates/code2prompt/src/config.rs",
    "chars": 10103,
    "preview": "//! Configuration parsing and session creation utilities.\n//!\n//! This module handles the conversion of command-line arg"
  },
  {
    "path": "crates/code2prompt/src/config_loader.rs",
    "chars": 3455,
    "preview": "//! Configuration file loading and management.\n//!\n//! This module handles loading TOML configuration files from multipl"
  },
  {
    "path": "crates/code2prompt/src/main.rs",
    "chars": 11071,
    "preview": "//! code2prompt is a command-line tool to generate an LLM prompt from a codebase directory.\n//!\n//! Authors: Olivier D'A"
  },
  {
    "path": "crates/code2prompt/src/model/commands.rs",
    "chars": 1093,
    "preview": "//! Command system for handling side effects in the Model-View-Update architecture.\n//!\n//! This module implements the C"
  },
  {
    "path": "crates/code2prompt/src/model/mod.rs",
    "chars": 26498,
    "preview": "//! Data structures and application state management for the TUI.\n//!\n//! This module contains the core data structures "
  },
  {
    "path": "crates/code2prompt/src/model/prompt_output.rs",
    "chars": 805,
    "preview": "//! Prompt output state management for the TUI application.\n//!\n//! This module contains the prompt output state and rel"
  },
  {
    "path": "crates/code2prompt/src/model/settings.rs",
    "chars": 6733,
    "preview": "//! Settings state management for the TUI application.\n//!\n//! This module contains the settings state, settings groups,"
  },
  {
    "path": "crates/code2prompt/src/model/statistics/mod.rs",
    "chars": 2751,
    "preview": "//! Statistics state management for the TUI application.\n//!\n//! This module contains the statistics state and related f"
  },
  {
    "path": "crates/code2prompt/src/model/statistics/types.rs",
    "chars": 1264,
    "preview": "//! Statistics view types and enums.\n//!\n//! This module contains the StatisticsView enum and related types\n//! for mana"
  },
  {
    "path": "crates/code2prompt/src/model/template/editor.rs",
    "chars": 4664,
    "preview": "//! Template editor state management.\n//!\n//! This module contains the state and logic for the template editor component"
  },
  {
    "path": "crates/code2prompt/src/model/template/mod.rs",
    "chars": 5715,
    "preview": "//! Template state management module.\n//!\n//! This module coordinates the three template sub-components:\n//! - Editor: T"
  },
  {
    "path": "crates/code2prompt/src/model/template/picker.rs",
    "chars": 6170,
    "preview": "//! Template picker state management.\n//!\n//! This module contains the state and logic for the template picker component"
  },
  {
    "path": "crates/code2prompt/src/model/template/variable.rs",
    "chars": 7732,
    "preview": "//! Template variable state management.\n//!\n//! This module contains the state and logic for managing template variables"
  },
  {
    "path": "crates/code2prompt/src/token_map.rs",
    "chars": 23984,
    "preview": "//! Token map visualization and analysis.\n//!\n//! This module provides functionality for generating and displaying visua"
  },
  {
    "path": "crates/code2prompt/src/tui.rs",
    "chars": 30410,
    "preview": "//! Terminal User Interface implementation.\n//!\n//! This module implements the complete TUI for code2prompt using ratatu"
  },
  {
    "path": "crates/code2prompt/src/utils.rs",
    "chars": 14986,
    "preview": "//! Utility functions for the TUI application.\n//!\n//! This module contains helper functions for building file trees,\n//"
  },
  {
    "path": "crates/code2prompt/src/view/formatters.rs",
    "chars": 7479,
    "preview": "//! Formatting functions for display purposes.\n//!\n//! This module contains pure functions that format data for display "
  },
  {
    "path": "crates/code2prompt/src/view/mod.rs",
    "chars": 307,
    "preview": "//! View layer for the TUI application.\n//!\n//! This module contains all the formatting and display logic that was previ"
  },
  {
    "path": "crates/code2prompt/src/widgets/file_selection.rs",
    "chars": 6570,
    "preview": "//! File selection widget for directory tree navigation and file selection.\n\nuse crate::model::Model;\nuse ratatui::{\n   "
  },
  {
    "path": "crates/code2prompt/src/widgets/mod.rs",
    "chars": 728,
    "preview": "//! Widget components for the TUI interface.\n//!\n//! This module contains all the widget implementations using Ratatui's"
  },
  {
    "path": "crates/code2prompt/src/widgets/output.rs",
    "chars": 4259,
    "preview": "//! Output widget for displaying generated prompt with scrolling capability.\n\nuse crate::model::Model;\nuse ratatui::{\n  "
  },
  {
    "path": "crates/code2prompt/src/widgets/settings.rs",
    "chars": 4159,
    "preview": "//! Settings widget for configuration management.\n\nuse crate::model::Model;\nuse ratatui::{\n    prelude::*,\n    widgets::"
  },
  {
    "path": "crates/code2prompt/src/widgets/statistics_by_extension.rs",
    "chars": 8485,
    "preview": "//! Statistics by extension widget for displaying extension-based histogram.\n\nuse crate::model::{Model, StatisticsState}"
  },
  {
    "path": "crates/code2prompt/src/widgets/statistics_overview.rs",
    "chars": 7516,
    "preview": "//! Statistics overview widget for displaying analysis summary.\nuse crate::model::{Model, StatisticsState};\nuse ratatui:"
  },
  {
    "path": "crates/code2prompt/src/widgets/statistics_token_map.rs",
    "chars": 6118,
    "preview": "//! Statistics token map widget for displaying token distribution.\n\nuse crate::model::Model;\nuse crate::token_map::{TuiC"
  },
  {
    "path": "crates/code2prompt/src/widgets/template/editor.rs",
    "chars": 3441,
    "preview": "//! Template Editor sub-widget.\n//!\n//! This widget provides an editable text area for template content with validation."
  },
  {
    "path": "crates/code2prompt/src/widgets/template/mod.rs",
    "chars": 8567,
    "preview": "//! Template widget module.\n//!\n//! This module coordinates the three template sub-widgets:\n//! - Editor: Template conte"
  },
  {
    "path": "crates/code2prompt/src/widgets/template/picker.rs",
    "chars": 4855,
    "preview": "//! Template Picker sub-widget.\n//!\n//! This widget provides template selection with separate default and custom lists.\n"
  },
  {
    "path": "crates/code2prompt/src/widgets/template/variable.rs",
    "chars": 6327,
    "preview": "//! Template Variable sub-widget.\n//!\n//! This widget provides a 2-column display for template variables with direct edi"
  },
  {
    "path": "crates/code2prompt/tests/common/fixtures.rs",
    "chars": 4510,
    "preview": "//! rstest fixtures for code2prompt integration tests\n\nuse super::test_env::*;\nuse colored::*;\nuse log::info;\nuse rstest"
  },
  {
    "path": "crates/code2prompt/tests/common/mod.rs",
    "chars": 607,
    "preview": "//! Common test utilities and fixtures for code2prompt integration tests\n//!\n//! This module provides reusable fixtures "
  },
  {
    "path": "crates/code2prompt/tests/common/test_env.rs",
    "chars": 3975,
    "preview": "//! Test environment types and utilities\n\n#![allow(dead_code)]\n\nuse assert_cmd::Command;\nuse std::fs::{self, File};\nuse "
  },
  {
    "path": "crates/code2prompt/tests/config_test.rs",
    "chars": 8129,
    "preview": "//! Tests for TOML configuration functionality\n//!\n//! This module tests the TOML configuration loading, parsing, and in"
  },
  {
    "path": "crates/code2prompt/tests/git_integration_test.rs",
    "chars": 3220,
    "preview": "//! Git integration tests for code2prompt\n//!\n//! This module tests git-related functionality including gitignore handli"
  },
  {
    "path": "crates/code2prompt/tests/integration_test.rs",
    "chars": 6394,
    "preview": "//! Integration tests for code2prompt file filtering functionality\n//!\n//! This module tests the include/exclude pattern"
  },
  {
    "path": "crates/code2prompt/tests/std_output_test.rs",
    "chars": 11609,
    "preview": "//! Standard output tests for code2prompt\n//!\n//! This module tests stdout functionality, output redirection,\n//! and va"
  },
  {
    "path": "crates/code2prompt/tests/template_integration_test.rs",
    "chars": 6215,
    "preview": "//! Template integration tests for code2prompt\n//!\n//! This module tests template functionality, output formats,\n//! and"
  },
  {
    "path": "crates/code2prompt-core/Cargo.toml",
    "chars": 1674,
    "preview": "[package]\nname = \"code2prompt_core\"\nversion = \"4.2.0\"\nauthors = [\n    \"Mufeed VH <mufeed@lyminal.space>\",\n    \"Olivier D"
  },
  {
    "path": "crates/code2prompt-core/src/builtin_templates.rs",
    "chars": 6817,
    "preview": "//! Built-in templates embedded as static resources.\n//!\n//! This module provides access to all built-in templates that "
  },
  {
    "path": "crates/code2prompt-core/src/configuration.rs",
    "chars": 8529,
    "preview": "//! This module defines the `Code2PromptConfig` struct and its Builder for configuring the behavior\n//! of code2prompt i"
  },
  {
    "path": "crates/code2prompt-core/src/default_template_md.hbs",
    "chars": 204,
    "preview": "Project Path: {{ absolute_code_path }}\n\nSource Tree:\n\n```txt\n{{ source_tree }}\n```\n\n{{#each files}}\n{{#if code}}\n`{{path"
  },
  {
    "path": "crates/code2prompt-core/src/default_template_xml.hbs",
    "chars": 300,
    "preview": "<directory>{{absolute_code_path}}</directory>\n\n<source-tree>\n  {{source_tree}}\n</source-tree>\n\n<files>\n  {{#each files}}"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/csv.rs",
    "chars": 3066,
    "preview": "//! CSV file processor with schema extraction.\n//!\n//! This processor uses the `csv` crate to robustly parse CSV files a"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/default.rs",
    "chars": 1270,
    "preview": "//! Default text processor for standard file types.\n//!\n//! This processor handles all file types that don't require spe"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/ipynb.rs",
    "chars": 4011,
    "preview": "//! Jupyter Notebook (.ipynb) file processor.\n//!\n//! This processor parses Jupyter notebook JSON and extracts:\n//! - To"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/jsonl.rs",
    "chars": 2617,
    "preview": "//! JSON Lines (JSONL) file processor with schema extraction.\n//!\n//! This processor parses JSONL/NDJSON files and extra"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/mod.rs",
    "chars": 2291,
    "preview": "//! File processor module for handling different file types intelligently.\n//!\n//! This module provides a strategy patte"
  },
  {
    "path": "crates/code2prompt-core/src/file_processor/tsv.rs",
    "chars": 1169,
    "preview": "//! TSV (Tab-Separated Values) file processor.\n//!\n//! This processor is a thin wrapper around the CSV processor with ta"
  },
  {
    "path": "crates/code2prompt-core/src/filter.rs",
    "chars": 5245,
    "preview": "//! This module contains pure filtering logic for files based on glob patterns.\n//!\n//! This module provides reusable, s"
  },
  {
    "path": "crates/code2prompt-core/src/git.rs",
    "chars": 7085,
    "preview": "//! This module handles git operations.\n\nuse anyhow::{Context, Result};\nuse git2::{DiffOptions, Repository};\nuse log::in"
  },
  {
    "path": "crates/code2prompt-core/src/lib.rs",
    "chars": 252,
    "preview": "//! Core library for code2prompt.\npub mod builtin_templates;\npub mod configuration;\npub mod file_processor;\npub mod filt"
  },
  {
    "path": "crates/code2prompt-core/src/path.rs",
    "chars": 12433,
    "preview": "//! This module contains the functions for traversing the directory and processing the files.\nuse crate::configuration::"
  },
  {
    "path": "crates/code2prompt-core/src/selection.rs",
    "chars": 11050,
    "preview": "//! This module contains the SelectionEngine that handles user file selection with precedence rules.\n//!\n//! The Selecti"
  },
  {
    "path": "crates/code2prompt-core/src/session.rs",
    "chars": 18709,
    "preview": "//! This module defines a Code2promptSession struct that provide a stateful interface to code2prompt-core.\n//! It allows"
  },
  {
    "path": "crates/code2prompt-core/src/sort.rs",
    "chars": 3452,
    "preview": "//! This module provides sorting methods for files and directory trees.\n\nuse crate::path::FileEntry;\nuse serde::{self, D"
  },
  {
    "path": "crates/code2prompt-core/src/template.rs",
    "chars": 3500,
    "preview": "//! This module contains the functions to set up the Handlebars template engine and render the template with the provide"
  },
  {
    "path": "crates/code2prompt-core/src/tokenizer.rs",
    "chars": 3665,
    "preview": "//! This module encapsulates the logic for counting the tokens in the rendered text.\nuse log::debug;\nuse serde::{Deseria"
  },
  {
    "path": "crates/code2prompt-core/src/util.rs",
    "chars": 544,
    "preview": "//! This module contains util functions\n\n/// Removes a UTF‑8 Byte Order Mark (BOM) from the beginning of a byte slice if"
  },
  {
    "path": "crates/code2prompt-core/templates/binary-exploitation-ctf-solver.hbs",
    "chars": 1759,
    "preview": "Challenge Name: {{challenge_name}}\nCategory: Binary Exploitation\n\nDescription: {{challenge_description}}\n\nProvided Files"
  },
  {
    "path": "crates/code2prompt-core/templates/clean-up-code.hbs",
    "chars": 1272,
    "preview": "Project Path: {{ absolute_code_path }}\n\nI'd like your help cleaning up and improving the code quality in this project. P"
  },
  {
    "path": "crates/code2prompt-core/templates/cryptography-ctf-solver.hbs",
    "chars": 1465,
    "preview": "Challenge Name: {{challenge_name}}\nCategory: Cryptography\n\nDescription: {{challenge_description}}\n\nProvided Files:\n{{#ea"
  },
  {
    "path": "crates/code2prompt-core/templates/document-the-code.hbs",
    "chars": 1008,
    "preview": "Project Path: {{ absolute_code_path }}\n\nSource Tree: \n```\n{{ source_tree }}\n```\n\n{{#each files}}\n{{#if code}}\n`{{path}}`"
  },
  {
    "path": "crates/code2prompt-core/templates/find-security-vulnerabilities.hbs",
    "chars": 1855,
    "preview": "Project Path: {{ absolute_code_path }}\n\nI want you to carefully review the code in this project and identify any potenti"
  },
  {
    "path": "crates/code2prompt-core/templates/fix-bugs.hbs",
    "chars": 1310,
    "preview": "Project Path: {{ absolute_code_path }}\n\nI need your help tracking down and fixing some bugs that have been reported in t"
  },
  {
    "path": "crates/code2prompt-core/templates/improve-performance.hbs",
    "chars": 1556,
    "preview": "Project Path: {{ absolute_code_path }}\n\nI'd like your help improving the performance of this codebase. It works correctl"
  },
  {
    "path": "crates/code2prompt-core/templates/refactor.hbs",
    "chars": 1713,
    "preview": "Project Path: {{ absolute_code_path }}\n\nI need your help refactoring this codebase to improve its design, maintainabilit"
  },
  {
    "path": "crates/code2prompt-core/templates/reverse-engineering-ctf-solver.hbs",
    "chars": 1606,
    "preview": "Challenge Name: {{challenge_name}}\nCategory: Reverse Engineering \n\nDescription: {{challenge_description}}\n\nProvided File"
  },
  {
    "path": "crates/code2prompt-core/templates/web-ctf-solver.hbs",
    "chars": 1452,
    "preview": "Challenge Name: {{challenge_name}}  \nCategory: Web Exploitation\n\nDescription: {{challenge_description}}\n\nTarget URL: {{t"
  },
  {
    "path": "crates/code2prompt-core/templates/write-git-commit.hbs",
    "chars": 1140,
    "preview": "Project Path: {{ absolute_code_path }}\n\nI'd like you to generate a high-quality git commit message for the provided `git"
  },
  {
    "path": "crates/code2prompt-core/templates/write-github-pull-request.hbs",
    "chars": 1485,
    "preview": "Project Path: {{ absolute_code_path }}\n\nI want you to generate a high-quality well-crafted Github pull request descripti"
  },
  {
    "path": "crates/code2prompt-core/templates/write-github-readme.hbs",
    "chars": 1036,
    "preview": "Project Path: {{ absolute_code_path }}\n\nI'd like you to generate a high-quality README file for this project, suitable f"
  },
  {
    "path": "crates/code2prompt-core/tests/binary_detection_test.rs",
    "chars": 8335,
    "preview": "//! Tests for binary file detection using content_inspector\n\nuse code2prompt_core::configuration::Code2PromptConfig;\nuse"
  },
  {
    "path": "crates/code2prompt-core/tests/file_processor_test.rs",
    "chars": 11946,
    "preview": "//! Tests for file processor module\n//!\n//! This file contains all tests for the file processor implementations,\n//! org"
  },
  {
    "path": "crates/code2prompt-core/tests/filter_test.rs",
    "chars": 19396,
    "preview": "/// This file tests the filter logic\n/// Code2prompt uses the file globbing and globpattern to match files\nuse code2prom"
  },
  {
    "path": "crates/code2prompt-core/tests/git_test.rs",
    "chars": 14936,
    "preview": "use code2prompt_core::git::{get_git_diff, get_git_diff_between_branches, get_git_log};\n\n#[cfg(test)]\nmod tests {\n    use"
  },
  {
    "path": "crates/code2prompt-core/tests/path_test.rs",
    "chars": 9670,
    "preview": "//! # Path Module Tests\n//!\n//! Tests for path traversal, directory structure handling, and file processing.\n//! Uses rs"
  },
  {
    "path": "crates/code2prompt-core/tests/session_integration_test.rs",
    "chars": 7231,
    "preview": "//! Integration tests for the session with simplified file selection\n\nuse code2prompt_core::configuration::Code2PromptCo"
  },
  {
    "path": "crates/code2prompt-core/tests/sort_test.rs",
    "chars": 10425,
    "preview": "use code2prompt_core::path::{EntryMetadata, FileEntry};\nuse code2prompt_core::sort::{FileSortMethod, sort_files, sort_tr"
  },
  {
    "path": "crates/code2prompt-core/tests/template_test.rs",
    "chars": 1712,
    "preview": "use code2prompt_core::template::{extract_undefined_variables, handlebars_setup, render_template};\n\n#[cfg(test)]\nmod test"
  },
  {
    "path": "crates/code2prompt-core/tests/util_test.rs",
    "chars": 1232,
    "preview": "use code2prompt_core::util::strip_utf8_bom;\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_strip_u"
  },
  {
    "path": "crates/code2prompt-python/.python-version",
    "chars": 7,
    "preview": "3.13.1\n"
  },
  {
    "path": "crates/code2prompt-python/Cargo.toml",
    "chars": 258,
    "preview": "[package]\nname = \"code2prompt-python\"\nversion = \"3.2.0\"\nedition = \"2024\"\n\n[lib]\nname = \"code2prompt_rs\"\ncrate-type = [\"c"
  },
  {
    "path": "crates/code2prompt-python/pyproject.toml",
    "chars": 1417,
    "preview": "[project]\nname = \"code2prompt_rs\"\nversion = \"3.2.1\"\ndescription = \"Python bindings for code2prompt\"\nauthors = [\n    { na"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/.gitignore",
    "chars": 3414,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/README.md",
    "chars": 3379,
    "preview": "# code2prompt Python SDK\n\nPython bindings for [code2prompt](https://github.com/mufeedvh/code2prompt) - A tool to generat"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/__init__.py",
    "chars": 431,
    "preview": "\"\"\"\ncode2prompt is a Python library for generating LLM prompts from codebases.\n\nIt provides a simple interface to the Ru"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/code2prompt_rs/__init__.py",
    "chars": 428,
    "preview": "\"\"\"\ncode2prompt is a Python library for generating LLM prompts from codebases.\n\nIt provides a simple interface to the Ru"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/code2prompt_rs/code2prompt.py",
    "chars": 4596,
    "preview": "# Import the Rust module\nfrom . import code2prompt_rs as rust_sdk\nfrom pathlib import Path\n\nclass RenderedPrompt:\n    de"
  },
  {
    "path": "crates/code2prompt-python/python-sdk/examples/basic_usage.py",
    "chars": 1859,
    "preview": "\"\"\"Example usage of the code2prompt Python SDK.\"\"\"\n\nfrom code2prompt_rs import Code2Prompt\n\ndef main():\n    # Create a C"
  },
  {
    "path": "crates/code2prompt-python/src/lib.rs",
    "chars": 12,
    "preview": "mod python;\n"
  },
  {
    "path": "crates/code2prompt-python/src/python.rs",
    "chars": 11219,
    "preview": "use pyo3::prelude::*;\nuse std::collections::HashMap;\nuse std::path::PathBuf;\n\nuse code2prompt_core::configuration::Code2"
  },
  {
    "path": "crates/code2prompt-python/src/python.rs.bak",
    "chars": 6903,
    "preview": "use pyo3::prelude::*;\nuse pyo3::types::PyDict;\nuse std::path::PathBuf;\n\nuse code2prompt_core::{\n    git::{get_git_diff, "
  },
  {
    "path": "crates/code2prompt-python/tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "crates/code2prompt-python/tests/conftest.py",
    "chars": 2033,
    "preview": "\"\"\"Pytest fixtures for code2prompt tests.\"\"\"\nimport os\nimport pytest\nimport tempfile\nimport shutil\nfrom pathlib import P"
  },
  {
    "path": "crates/code2prompt-python/tests/test_config.py",
    "chars": 2093,
    "preview": "\"\"\"Tests for Code2Prompt configuration.\"\"\"\nimport pytest\nfrom pathlib import Path\nfrom code2prompt_rs import Code2Prompt"
  },
  {
    "path": "crates/code2prompt-python/tests/test_generation.py",
    "chars": 4421,
    "preview": "\"\"\"Tests for prompt generation.\"\"\"\nimport pytest\nfrom code2prompt_rs import Code2Prompt\n\ndef test_generate_basic(test_di"
  },
  {
    "path": "crates/code2prompt-python/tests/test_special_feature.py",
    "chars": 2380,
    "preview": "## test_special_features.py - Tests pour fonctionnalités spéciales\n\n\"\"\"Tests for special features of Code2Prompt.\"\"\"\nimp"
  },
  {
    "path": "llms-install.md",
    "chars": 2940,
    "preview": "# Code2Prompt MCP Server Installation Guide\n\nThis guide is specifically designed for AI agents like Cline to install and"
  },
  {
    "path": "website/.gitignore",
    "chars": 248,
    "preview": "# build output\ndist/\n# generated types\n.astro/\n\n# dependencies\nnode_modules/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn"
  },
  {
    "path": "website/.vscode/extensions.json",
    "chars": 87,
    "preview": "{\n  \"recommendations\": [\"astro-build.astro-vscode\"],\n  \"unwantedRecommendations\": []\n}\n"
  },
  {
    "path": "website/.vscode/launch.json",
    "chars": 207,
    "preview": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"command\": \"./node_modules/.bin/astro dev\",\n      \"name\": \"Dev"
  },
  {
    "path": "website/README.md",
    "chars": 2538,
    "preview": "# Starlight Starter Kit: Basics\n\n[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https"
  },
  {
    "path": "website/astro.config.mjs",
    "chars": 4385,
    "preview": "// @ts-check\nimport { defineConfig } from \"astro/config\";\nimport starlight from \"@astrojs/starlight\";\nimport react from "
  },
  {
    "path": "website/package.json",
    "chars": 1040,
    "preview": "{\n  \"name\": \"code2prompt-website\",\n  \"type\": \"module\",\n  \"version\": \"0.1.0\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"dev"
  },
  {
    "path": "website/pnpm-workspace.yaml",
    "chars": 45,
    "preview": "onlyBuiltDependencies:\n  - esbuild\n  - sharp\n"
  },
  {
    "path": "website/public/CNAME",
    "chars": 15,
    "preview": "code2prompt.dev"
  },
  {
    "path": "website/public/assets/css/marquee.css",
    "chars": 2283,
    "preview": ".scroller__inner {\n  padding-block: 1rem;\n  display: flex;\n  flex-wrap: wrap;\n  gap: 1rem;\n}\n\n.scroller[data-animated=\"t"
  },
  {
    "path": "website/public/assets/js/main.js",
    "chars": 1223,
    "preview": "function copyToClipboard() {\r\n  const codeBlock = document.getElementById(\"code-block\").innerText;\r\n  navigator.clipboar"
  },
  {
    "path": "website/public/prism-theme.css",
    "chars": 1994,
    "preview": "/**\n * atom-dark theme for `prism.js`\n * Based on Atom's `atom-dark` theme: https://github.com/atom/atom-dark-syntax\n * "
  },
  {
    "path": "website/src/assets/examples/history_notes/history_notes/history/medieval.txt",
    "chars": 93,
    "preview": "Key events: Fall of Rome, Viking raids, Magna Carta\nImportant dates: 476 AD, 793 AD, 1215 AD\n"
  },
  {
    "path": "website/src/assets/examples/history_notes/history_notes/history/renaissance.txt",
    "chars": 112,
    "preview": "Key figures: Leonardo da Vinci, Michelangelo, Copernicus\nCultural shifts: Humanism, art revival, printing press\n"
  },
  {
    "path": "website/src/assets/examples/history_notes/history_notes/history/ww2.txt",
    "chars": 87,
    "preview": "Key events: D-Day, Hiroshima, End of War\nLeaders: Churchill, Roosevelt, Hitler, Stalin\n"
  },
  {
    "path": "website/src/assets/examples/history_notes/history_notes/meta/my_revision_goals.txt",
    "chars": 79,
    "preview": "Focus: Memorize key events, understand causes & consequences\nDeadline: 2 weeks\n"
  },
  {
    "path": "website/src/assets/examples/history_notes/prompt.md",
    "chars": 759,
    "preview": "Project Path: history_notes\n\nSource Tree:\n\n```txt\nhistory_notes\n├── history\n│   ├── medieval.txt\n│   ├── renaissance.txt"
  },
  {
    "path": "website/src/assets/examples/history_notes/question.txt",
    "chars": 289,
    "preview": "Goal: Create interactive flashcards for my upcoming history exam\n\nFormat: Generate question-answer pairs in markdown for"
  },
  {
    "path": "website/src/assets/examples/my_recipes/my_recipes/pantry/my_ingredients.txt",
    "chars": 39,
    "preview": "Available: pasta, tomato sauce, cheese\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/my_recipes/recipes/pasta.txt",
    "chars": 98,
    "preview": "Ingredients: pasta, tomato sauce, garlic, basil\nInstructions: Boil pasta, heat sauce, mix, serve.\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/my_recipes/recipes/pizza.txt",
    "chars": 96,
    "preview": "Ingredients: flour, tomato sauce, cheese, oregano\nInstructions: Make dough, add toppings, bake.\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/my_recipes/recipes/salad.txt",
    "chars": 100,
    "preview": "Ingredients: lettuce, tomato, cucumber, olive oil\nInstructions: Chop ingredients, mix, drizzle oil.\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/my_recipes/recipes/soup.txt",
    "chars": 104,
    "preview": "Ingredients: carrots, potatoes, chicken broth, onions\nInstructions: Boil broth, add vegetables, simmer.\n"
  },
  {
    "path": "website/src/assets/examples/my_recipes/prompt.md",
    "chars": 852,
    "preview": "Project Path: my_recipes\n\nSource Tree:\n\n```txt\nmy_recipes\n├── pantry\n│   └── my_ingredients.txt\n└── recipes\n    ├── past"
  },
  {
    "path": "website/src/assets/examples/my_recipes/question.txt",
    "chars": 288,
    "preview": "Goal: Suggest a recipe I can cook tonight based on my available ingredients\n\nFormat: Provide a personalized recommendati"
  },
  {
    "path": "website/src/assets/examples/node_app/node_app/README.md",
    "chars": 99,
    "preview": "Simple Node.js app to process JSON data.\nFeature idea: Add a filter function to sort users by age.\n"
  },
  {
    "path": "website/src/assets/examples/node_app/node_app/data/sample.json",
    "chars": 165,
    "preview": "{\n    \"users\": [\n        {\n            \"name\": \"Alice\",\n            \"age\": 25\n        },\n        {\n            \"name\": \""
  },
  {
    "path": "website/src/assets/examples/node_app/node_app/src/index.js",
    "chars": 94,
    "preview": "import express from \"express\";\n\nconst app = express();\nconst PORT = process.env.PORT || 3000;\n"
  },
  {
    "path": "website/src/assets/examples/node_app/node_app/src/utils.js",
    "chars": 99,
    "preview": "function processData() {\n  console.log(\"Processing data...\");\n}\n\nmodule.exports = { processData };\n"
  },
  {
    "path": "website/src/assets/examples/node_app/prompt.md",
    "chars": 746,
    "preview": "Project Path: node_app\n\nSource Tree:\n\n```txt\nnode_app\n├── README.md\n├── data\n│   └── sample.json\n└── src\n    ├── index.j"
  },
  {
    "path": "website/src/assets/examples/node_app/question.txt",
    "chars": 276,
    "preview": "Goal: Add a user filtering feature to sort users by age in ascending order\n\nFormat: Provide the complete implementation "
  },
  {
    "path": "website/src/components/Footer.astro",
    "chars": 76,
    "preview": "<footer id=\"footer\">\n  <p>&copy; Olivier D'Ancona & Mufeed VH</p>\n</footer>\n"
  },
  {
    "path": "website/src/components/Header.astro",
    "chars": 3281,
    "preview": "---\nimport { Image } from \"astro:assets\";\nimport Code2promptLogo from \"/src/assets/logo_light_v0.0.1.svg\";\n---\n\n<section"
  },
  {
    "path": "website/src/components/Section0.astro",
    "chars": 10272,
    "preview": "---\nimport { Prism } from \"@astrojs/prism\";\nimport { FileTree } from '@astrojs/starlight/components';\nimport fs from \"no"
  },
  {
    "path": "website/src/components/Section1.astro",
    "chars": 4310,
    "preview": "---\nimport { Prism } from \"@astrojs/prism\";\n---\n\n<div class=\"w-full bg-gray-100\">\n  <section id=\"one\" class=\"bg-gray-100"
  },
  {
    "path": "website/src/components/Section2.astro",
    "chars": 3845,
    "preview": "---\nimport { Image } from \"astro:assets\";\nimport SDK from \"/src/assets/SDK.svg\";\nimport CLI from \"/src/assets/CLI.svg\";\n"
  },
  {
    "path": "website/src/components/Section3.astro",
    "chars": 8130,
    "preview": "<section id=\"three\" class=\"bg-white py-24 sm:py-32\">\n  <div class=\"mx-auto max-w-7xl px-6 lg:px-4\">\n    <div class=\"mx-a"
  },
  {
    "path": "website/src/components/Section4.astro",
    "chars": 812,
    "preview": "<section id=\"four\" class=\"py-12 text-center\">\n  <div class=\"container mx-auto px-4\">\n    <h2 class=\"text-3xl font-bold m"
  },
  {
    "path": "website/src/content/docs/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "chars": 11697,
    "preview": "---\ntitle: \"Why I Developed Code2Prompt\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - open-source\n  - code2prompt\n"
  },
  {
    "path": "website/src/content/docs/de/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "chars": 13537,
    "preview": "---\ntitle: \"Warum ich Code2Prompt entwickelt habe\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - open-source\n  - co"
  },
  {
    "path": "website/src/content/docs/de/docs/explanations/glob_pattern_filter.mdx",
    "chars": 4350,
    "preview": "---\ntitle: Wie der Glob-Musterfilter funktioniert\ndescription: Wie Code2Prompt entscheidet, welche Dateien mit Include- "
  },
  {
    "path": "website/src/content/docs/de/docs/explanations/glob_patterns.md",
    "chars": 2989,
    "preview": "---\ntitle: Glob-Muster\ndescription: Eine Einführung in Glob-Muster, die Platzhalterzeichen verwenden, um Dateinamen und "
  },
  {
    "path": "website/src/content/docs/de/docs/explanations/tokenizers.md",
    "chars": 2421,
    "preview": "---\ntitle: Tokenisierung in Code2Prompt\ndescription: Erfahren Sie mehr über Tokenisierung und wie Code2Prompt Text für L"
  },
  {
    "path": "website/src/content/docs/de/docs/how_to/filter_files.md",
    "chars": 2793,
    "preview": "---\ntitle: Filtern von Dateien in Code2Prompt\ndescription: Eine Schritt-für-Schritt-Anleitung zum Einschließen oder Auss"
  },
  {
    "path": "website/src/content/docs/de/docs/how_to/install.mdx",
    "chars": 5876,
    "preview": "---\ntitle: Installation von Code2Prompt\ndescription: Eine umfassende Installationsanleitung für Code2Prompt auf verschie"
  },
  {
    "path": "website/src/content/docs/de/docs/how_to/ssh.md",
    "chars": 1158,
    "preview": "---\ntitle: Verwenden Sie Code2prompt CLI mit SSH\ndescription: Eine Anleitung zur Verwendung von Code2Prompt CLI mit SSH "
  },
  {
    "path": "website/src/content/docs/de/docs/references/command_line_options.md",
    "chars": 301,
    "preview": "---\ntitle: Code2Prompt-Befehlszeilenoptionen\ndescription: Ein Referenzhandbuch für alle verfügbaren CLI-Optionen in Code"
  },
  {
    "path": "website/src/content/docs/de/docs/references/default_template.md",
    "chars": 309,
    "preview": "---\ntitle: Standardvorlage für Code2Prompt\ndescription: Erfahren Sie mehr über die Standardvorlagestruktur, die in Code2"
  },
  {
    "path": "website/src/content/docs/de/docs/tutorials/getting_started.mdx",
    "chars": 4618,
    "preview": "---\ntitle: Erste Schritte mit Code2Prompt\ndescription: Ein umfassendes Tutorial, das die Kernfunktionalität von Code2Pro"
  },
  {
    "path": "website/src/content/docs/de/docs/tutorials/learn_filters.mdx",
    "chars": 4790,
    "preview": "---\ntitle: Lernen Sie Kontextfilterung mit Code2Prompt\ndescription: Lernen Sie, wie Sie Dateien in Ihren LLM-Eingaben mi"
  },
  {
    "path": "website/src/content/docs/de/docs/tutorials/learn_templates.mdx",
    "chars": 6576,
    "preview": "---\ntitle: Lernen Sie Handlebar-Vorlagen mit Code2Prompt kennen\ndescription: Verstehen Sie, wie Sie benutzerdefinierte H"
  },
  {
    "path": "website/src/content/docs/de/docs/vision.mdx",
    "chars": 4187,
    "preview": "---\ntitle: Die Vision von Code2Prompt\ndescription: Entdecken Sie die Vision hinter Code2Prompt und wie es die Interaktio"
  },
  {
    "path": "website/src/content/docs/de/docs/welcome.mdx",
    "chars": 4039,
    "preview": "---\ntitle: Code2Prompt-Dokumentation\ndescription: Offizielle Code2prompt-Dokumentation\ntemplate: splash\nhero:\n  tagline:"
  },
  {
    "path": "website/src/content/docs/docs/explanations/glob_pattern_filter.mdx",
    "chars": 3825,
    "preview": "---\ntitle: How the Glob Pattern Filter Works\ndescription: How Code2Prompt decides which files to keep or discard using i"
  },
  {
    "path": "website/src/content/docs/docs/explanations/glob_patterns.md",
    "chars": 2489,
    "preview": "---\ntitle: Understanding Glob Patterns\ndescription: A detailed explanation of glob patterns and how they are used in Cod"
  },
  {
    "path": "website/src/content/docs/docs/explanations/tokenizers.md",
    "chars": 2164,
    "preview": "---\ntitle: Tokenization in Code2Prompt\ndescription: Learn about tokenization and how Code2Prompt processes text for LLMs"
  },
  {
    "path": "website/src/content/docs/docs/how_to/filter_files.md",
    "chars": 2280,
    "preview": "---\ntitle: Filtering Files in Code2Prompt\ndescription: A step-by-step guide to including or excluding files using differ"
  },
  {
    "path": "website/src/content/docs/docs/how_to/install.mdx",
    "chars": 5401,
    "preview": "---\ntitle: Installing Code2Prompt\ndescription: A complete installation guide for Code2Prompt on different operating syst"
  },
  {
    "path": "website/src/content/docs/docs/how_to/ssh.md",
    "chars": 822,
    "preview": "---\ntitle: Use Code2prompt CLI with SSH\ndescription: A guide to using Code2Prompt CLI with SSH for remote codebase analy"
  },
  {
    "path": "website/src/content/docs/docs/references/command_line_options.md",
    "chars": 149,
    "preview": "---\ntitle: Code2Prompt Command-Line Options\ndescription: A reference guide for all available CLI options in Code2Prompt."
  },
  {
    "path": "website/src/content/docs/docs/references/default_template.md",
    "chars": 145,
    "preview": "---\ntitle: Default Template for Code2Prompt\ndescription: Learn about the default template structure used in Code2Prompt."
  },
  {
    "path": "website/src/content/docs/docs/tutorials/configuration.mdx",
    "chars": 5177,
    "preview": "---\ntitle: Configuring Code2Prompt 📖\ndescription: Learn how to use .c2pconfig to automate your prompt generation workflo"
  },
  {
    "path": "website/src/content/docs/docs/tutorials/getting_started.mdx",
    "chars": 4022,
    "preview": "---\ntitle: Getting Started with Code2Prompt\ndescription: A comprehensive tutorial introducing Code2Prompt's core functio"
  },
  {
    "path": "website/src/content/docs/docs/tutorials/learn_filters.mdx",
    "chars": 4734,
    "preview": "---\ntitle: Learn Context Filtering with Code2Prompt\ndescription: Learn how to exclude or include files in your LLM promp"
  },
  {
    "path": "website/src/content/docs/docs/tutorials/learn_templates.mdx",
    "chars": 5744,
    "preview": "---\ntitle: Learn Handlebar Templates with Code2Prompt\ndescription: Understand how to use and create custom Handlebars te"
  },
  {
    "path": "website/src/content/docs/docs/vision.mdx",
    "chars": 3641,
    "preview": "---\ntitle: Code2Prompt's Vision\ndescription: Discover the vision behind Code2Prompt and how it enhances LLM interactions"
  },
  {
    "path": "website/src/content/docs/docs/welcome.mdx",
    "chars": 3487,
    "preview": "---\ntitle: Code2Prompt Documentation\ndescription: Official Code2prompt Documentation\ntemplate: splash\nhero:\n  tagline: T"
  },
  {
    "path": "website/src/content/docs/es/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "chars": 13397,
    "preview": "---\ntitle: \"Por qué desarrollé Code2Prompt\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - open-source\n  - code2prom"
  },
  {
    "path": "website/src/content/docs/es/docs/explanations/glob_pattern_filter.mdx",
    "chars": 4491,
    "preview": "---\ntitle: Cómo funciona el filtro de patrones Glob\ndescription: Cómo Code2Prompt decide qué archivos conservar o descar"
  },
  {
    "path": "website/src/content/docs/es/docs/explanations/glob_patterns.md",
    "chars": 3058,
    "preview": "---\ntitle: Entendiendo patrones Glob\ndescription: Una explicación detallada de los patrones Glob y cómo se utilizan en C"
  },
  {
    "path": "website/src/content/docs/es/docs/explanations/tokenizers.md",
    "chars": 2441,
    "preview": "---\ntitle: Tokenización en Code2Prompt\ndescription: Aprende sobre la tokenización y cómo Code2Prompt procesa texto para "
  },
  {
    "path": "website/src/content/docs/es/docs/how_to/filter_files.md",
    "chars": 2646,
    "preview": "---\ntitle: Filtrado de Archivos en Code2Prompt\ndescription: Una guía paso a paso para incluir o excluir archivos utiliza"
  },
  {
    "path": "website/src/content/docs/es/docs/how_to/install.mdx",
    "chars": 6174,
    "preview": "---\ntitle: Instalación de Code2Prompt\ndescription: Una guía de instalación completa para Code2Prompt en diferentes siste"
  },
  {
    "path": "website/src/content/docs/es/docs/how_to/ssh.md",
    "chars": 1031,
    "preview": "---\ntitle: Uso de Code2prompt CLI con SSH\ndescription: Una guía para usar Code2Prompt CLI con SSH para análisis remoto d"
  },
  {
    "path": "website/src/content/docs/es/docs/references/command_line_options.md",
    "chars": 321,
    "preview": "---\ntitle: Opciones de línea de comandos de Code2Prompt\ndescription: Una guía de referencia para todas las opciones de C"
  },
  {
    "path": "website/src/content/docs/es/docs/references/default_template.md",
    "chars": 328,
    "preview": "---\ntitle: Plantilla Predeterminada para Code2Prompt\ndescription: Obtenga información sobre la estructura de la plantill"
  },
  {
    "path": "website/src/content/docs/es/docs/tutorials/getting_started.mdx",
    "chars": 4588,
    "preview": "---\ntitle: Getting Started with Code2Prompt\ndescription: A comprehensive tutorial introducing Code2Prompt's core functio"
  },
  {
    "path": "website/src/content/docs/es/docs/tutorials/learn_filters.mdx",
    "chars": 4586,
    "preview": "---\ntitle: Aprender filtrado de contexto con Code2Prompt\ndescription: Aprende a excluir o incluir archivos en tus indica"
  },
  {
    "path": "website/src/content/docs/es/docs/tutorials/learn_templates.mdx",
    "chars": 6431,
    "preview": "---\ntitle: Aprenda plantillas de Handlebar con Code2Prompt\ndescription: Entienda cómo usar y crear plantillas personaliz"
  },
  {
    "path": "website/src/content/docs/es/docs/vision.mdx",
    "chars": 4433,
    "preview": "---\ntitle: La visión de Code2Prompt\ndescription: Descubre la visión detrás de Code2Prompt y cómo mejora las interaccione"
  },
  {
    "path": "website/src/content/docs/es/docs/welcome.mdx",
    "chars": 4094,
    "preview": "---\ntitle: Documentación de Code2Prompt\ndescription: Documentación oficial de Code2prompt\ntemplate: splash\nhero:\n  tagli"
  },
  {
    "path": "website/src/content/docs/fr/blog/2025.04.11_why_I_wrote_code2prompt.mdx",
    "chars": 14032,
    "preview": "---\ntitle: \"Pourquoi j'ai développé Code2Prompt\"\ndate: 2025-04-11\nlastUpdated: 2025-04-11\ntags:\n  - open-source\n  - code"
  },
  {
    "path": "website/src/content/docs/fr/docs/explanations/glob_pattern_filter.mdx",
    "chars": 4679,
    "preview": "---\ntitle: Comment fonctionne le filtre de modèle Glob\ndescription: Comment Code2Prompt décide quelles fichiers garder o"
  },
  {
    "path": "website/src/content/docs/fr/docs/explanations/glob_patterns.md",
    "chars": 3207,
    "preview": "---\ntitle: Comprendre les modèles Glob\ndescription: Une explication détaillée des modèles Glob et de leur utilisation da"
  },
  {
    "path": "website/src/content/docs/fr/docs/explanations/tokenizers.md",
    "chars": 2496,
    "preview": "---\ntitle: Tokenisation dans Code2Prompt\ndescription: Découvrez la tokenisation et comment Code2Prompt traite le texte p"
  },
  {
    "path": "website/src/content/docs/fr/docs/how_to/filter_files.md",
    "chars": 2747,
    "preview": "---\ntitle: Filtrage de fichiers dans Code2Prompt\ndescription: Un guide étape par étape pour inclure ou exclure des fichi"
  },
  {
    "path": "website/src/content/docs/fr/docs/how_to/install.mdx",
    "chars": 6062,
    "preview": "---\ntitle: Installation de Code2Prompt\ndescription: Guide d'installation complet pour Code2Prompt sur différents système"
  },
  {
    "path": "website/src/content/docs/fr/docs/how_to/ssh.md",
    "chars": 1091,
    "preview": "---\ntitle: Utiliser Code2prompt CLI avec SSH\ndescription: Un guide pour utiliser Code2Prompt CLI avec SSH pour l'analyse"
  },
  {
    "path": "website/src/content/docs/fr/docs/references/command_line_options.md",
    "chars": 378,
    "preview": "---\ntitle: Options de ligne de commande Code2Prompt\ndescription: Guide de référence pour toutes les options CLI disponib"
  }
]

// ... and 55 more files (download for full content)

About this extraction

This page contains the full source code of the mufeedvh/code2prompt GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 255 files (943.9 KB), approximately 245.8k tokens, and a symbol index with 549 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!